diff options
Diffstat (limited to 'toolkit/modules/ServiceRequest.sys.mjs')
-rw-r--r-- | toolkit/modules/ServiceRequest.sys.mjs | 180 |
1 files changed, 180 insertions, 0 deletions
diff --git a/toolkit/modules/ServiceRequest.sys.mjs b/toolkit/modules/ServiceRequest.sys.mjs new file mode 100644 index 0000000000..a88ba9c324 --- /dev/null +++ b/toolkit/modules/ServiceRequest.sys.mjs @@ -0,0 +1,180 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * This module consolidates various code and data update requests, so flags + * can be set, Telemetry collected, etc. in a central place. + */ + +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; + +const lazy = {}; + +XPCOMUtils.defineLazyServiceGetter( + lazy, + "ProxyService", + "@mozilla.org/network/protocol-proxy-service;1", + "nsIProtocolProxyService" +); + +ChromeUtils.defineESModuleGetters(lazy, { + ExtensionPreferencesManager: + "resource://gre/modules/ExtensionPreferencesManager.sys.mjs", +}); + +XPCOMUtils.defineLazyServiceGetter( + lazy, + "CaptivePortalService", + "@mozilla.org/network/captive-portal-service;1", + "nsICaptivePortalService" +); + +XPCOMUtils.defineLazyServiceGetter( + lazy, + "gNetworkLinkService", + "@mozilla.org/network/network-link-service;1", + "nsINetworkLinkService" +); + +const PROXY_CONFIG_TYPES = [ + "direct", + "manual", + "pac", + "unused", // nsIProtocolProxyService.idl skips index 3. + "wpad", + "system", +]; + +function recordEvent(service, source = {}) { + try { + Services.telemetry.setEventRecordingEnabled("service_request", true); + Services.telemetry.recordEvent( + "service_request", + "bypass", + "proxy_info", + service, + source + ); + } catch (err) { + // If the telemetry throws just log the error so it doesn't break any + // functionality. + console.error(err); + } +} + +// If proxy.settings is used to change the proxy, an extension will +// be "in control". This returns the id of that extension. +async function getControllingExtension() { + if ( + !WebExtensionPolicy.getActiveExtensions().some(p => + p.permissions.includes("proxy") + ) + ) { + return undefined; + } + // Is this proxied by an extension that set proxy prefs? + let setting = await lazy.ExtensionPreferencesManager.getSetting( + "proxy.settings" + ); + return setting?.id; +} + +async function getProxySource(proxyInfo) { + // sourceId is set when using proxy.onRequest + if (proxyInfo.sourceId) { + return { + source: proxyInfo.sourceId, + type: "api", + }; + } + let type = PROXY_CONFIG_TYPES[lazy.ProxyService.proxyConfigType] || "unknown"; + + // If we have a policy it will have set the prefs. + if ( + Services.policies && + Services.policies.status === Services.policies.ACTIVE + ) { + let policies = Services.policies.getActivePolicies()?.filter(p => p.Proxy); + if (policies?.length) { + return { + source: "policy", + type, + }; + } + } + + let source = await getControllingExtension(); + return { + source: source || "prefs", + type, + }; +} + +/** + * ServiceRequest is intended to be a drop-in replacement for current users + * of XMLHttpRequest. + * + * @param {Object} options - Options for underlying XHR, e.g. { mozAnon: bool } + */ +export class ServiceRequest extends XMLHttpRequest { + constructor(options) { + super(options); + } + /** + * Opens an XMLHttpRequest, and sets the NSS "beConservative" flag. + * Requests are always async. + * + * @param {String} method - HTTP method to use, e.g. "GET". + * @param {String} url - URL to open. + * @param {Object} options - Additional options { bypassProxy: bool }. + */ + open(method, url, options) { + super.open(method, url, true); + + if (super.channel instanceof Ci.nsIHttpChannelInternal) { + let internal = super.channel.QueryInterface(Ci.nsIHttpChannelInternal); + // Disable cutting edge features, like TLS 1.3, where middleboxes might brick us + internal.beConservative = true; + // Disable use of proxy for this request if necessary. + if (options?.bypassProxy && this.bypassProxyEnabled) { + internal.bypassProxy = true; + } + } + } + + get bypassProxy() { + let { channel } = this; + return channel.QueryInterface(Ci.nsIHttpChannelInternal).bypassProxy; + } + + get isProxied() { + let { channel } = this; + return !!(channel instanceof Ci.nsIProxiedChannel && channel.proxyInfo); + } + + get bypassProxyEnabled() { + return Services.prefs.getBoolPref("network.proxy.allow_bypass", true); + } + + static async logProxySource(channel, service) { + if (channel.proxyInfo) { + let source = await getProxySource(channel.proxyInfo); + recordEvent(service, source); + } + } + + static get isOffline() { + try { + return ( + Services.io.offline || + lazy.CaptivePortalService.state == + lazy.CaptivePortalService.LOCKED_PORTAL || + !lazy.gNetworkLinkService.isLinkUp + ); + } catch (ex) { + // we cannot get state, assume the best. + } + return false; + } +} |