diff options
Diffstat (limited to 'toolkit/components/extensions/test/xpcshell/test_proxy_failover.js')
-rw-r--r-- | toolkit/components/extensions/test/xpcshell/test_proxy_failover.js | 323 |
1 files changed, 323 insertions, 0 deletions
diff --git a/toolkit/components/extensions/test/xpcshell/test_proxy_failover.js b/toolkit/components/extensions/test/xpcshell/test_proxy_failover.js new file mode 100644 index 0000000000..e584f142fa --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_proxy_failover.js @@ -0,0 +1,323 @@ +"use strict"; + +AddonTestUtils.init(this); +AddonTestUtils.overrideCertDB(); +AddonTestUtils.createAppInfo( + "xpcshell@tests.mozilla.org", + "XPCShell", + "1", + "43" +); + +// Necessary for the pac script to proxy localhost requests +Services.prefs.setBoolPref("network.proxy.allow_hijacking_localhost", true); + +// Pref is not builtin if direct failover is disabled in compile config. +XPCOMUtils.defineLazyGetter(this, "directFailoverDisabled", () => { + return ( + Services.prefs.getPrefType("network.proxy.failover_direct") == + Ci.nsIPrefBranch.PREF_INVALID + ); +}); + +const { ServiceRequest } = ChromeUtils.importESModule( + "resource://gre/modules/ServiceRequest.sys.mjs" +); + +// Prevent the request from reaching out to the network. +const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js"); + +// No hosts defined to avoid the default proxy filter setup. +const nonProxiedServer = createHttpServer(); +nonProxiedServer.registerPathHandler("/", (request, response) => { + response.setStatusLine(request.httpVersion, 200, "OK"); + response.write("ok!"); +}); +const { primaryHost, primaryPort } = nonProxiedServer.identity; + +function getProxyData(channel) { + if (!(channel instanceof Ci.nsIProxiedChannel) || !channel.proxyInfo) { + return; + } + let { type, host, port, sourceId } = channel.proxyInfo; + return { type, host, port, sourceId }; +} + +// Get a free port with no listener to use in the proxyinfo. +function getBadProxyPort() { + let server = new HttpServer(); + server.start(-1); + const badPort = server.identity.primaryPort; + server.stop(); + return badPort; +} + +function xhr(url, options = { beConservative: true, bypassProxy: false }) { + return new Promise((resolve, reject) => { + let req = new XMLHttpRequest(); + req.open("GET", `${url}?t=${Math.random()}`); + req.channel.QueryInterface(Ci.nsIHttpChannelInternal).beConservative = + options.beConservative; + req.channel.QueryInterface(Ci.nsIHttpChannelInternal).bypassProxy = + options.bypassProxy; + req.onload = () => { + resolve({ text: req.responseText, proxy: getProxyData(req.channel) }); + }; + req.onerror = () => { + reject({ status: req.status, proxy: getProxyData(req.channel) }); + }; + req.send(); + }); +} + +// Same as the above xhr call, but ServiceRequest is always beConservative. +// This is here to specifically test bypassProxy with ServiceRequest. +function serviceRequest(url, options = { bypassProxy: false }) { + return new Promise((resolve, reject) => { + let req = new ServiceRequest(); + req.open("GET", `${url}?t=${Math.random()}`, options); + req.onload = () => { + resolve({ text: req.responseText, proxy: getProxyData(req.channel) }); + }; + req.onerror = () => { + reject({ status: req.status, proxy: getProxyData(req.channel) }); + }; + req.send(); + }); +} + +add_task(async function setup() { + await AddonTestUtils.promiseStartupManager(); +}); + +async function getProxyExtension(proxyDetails) { + async function background(proxyDetails) { + browser.proxy.onRequest.addListener( + details => { + return proxyDetails; + }, + { urls: ["<all_urls>"] } + ); + + browser.test.sendMessage("proxied"); + } + let extensionData = { + manifest: { + permissions: ["proxy", "<all_urls>"], + }, + background: `(${background})(${JSON.stringify(proxyDetails)})`, + incognitoOverride: "spanning", + useAddonManager: "temporary", + }; + + let extension = ExtensionTestUtils.loadExtension(extensionData); + await extension.startup(); + await extension.awaitMessage("proxied"); + return extension; +} + +add_task(async function test_failover_content_direct() { + // load a content page for fetch and non-system principal, expect + // failover to direct will work. + const proxyDetails = [ + { type: "http", host: "127.0.0.1", port: getBadProxyPort() }, + { type: "direct" }, + ]; + + // We need to load the content page before loading the proxy extension + // to ensure that we have a valid content page to run fetch from. + let contentUrl = `http://${primaryHost}:${primaryPort}/`; + let page = await ExtensionTestUtils.loadContentPage(contentUrl); + + let extension = await getProxyExtension(proxyDetails); + + await ExtensionTestUtils.fetch(contentUrl, `${contentUrl}?t=${Math.random()}`) + .then(text => { + equal(text, "ok!", "fetch completed"); + }) + .catch(() => { + ok(false, "fetch failed"); + }); + + await extension.unload(); + await page.close(); +}); + +add_task( + { skip_if: () => directFailoverDisabled }, + async function test_failover_content() { + // load a content page for fetch and non-system principal, expect + // no failover + const proxyDetails = [ + { type: "http", host: "127.0.0.1", port: getBadProxyPort() }, + ]; + + // We need to load the content page before loading the proxy extension + // to ensure that we have a valid content page to run fetch from. + let contentUrl = `http://${primaryHost}:${primaryPort}/`; + let page = await ExtensionTestUtils.loadContentPage(contentUrl); + + let extension = await getProxyExtension(proxyDetails); + + await ExtensionTestUtils.fetch( + contentUrl, + `${contentUrl}?t=${Math.random()}` + ) + .then(text => { + ok(false, "xhr unexpectedly completed"); + }) + .catch(e => { + equal( + e.message, + "NetworkError when attempting to fetch resource.", + "fetch failed" + ); + }); + + await extension.unload(); + await page.close(); + } +); + +add_task( + { skip_if: () => directFailoverDisabled }, + async function test_failover_system() { + const proxyDetails = [ + { type: "http", host: "127.0.0.1", port: getBadProxyPort() }, + { type: "http", host: "127.0.0.1", port: getBadProxyPort() }, + ]; + + let extension = await getProxyExtension(proxyDetails); + + await xhr(`http://${primaryHost}:${primaryPort}/`) + .then(req => { + equal(req.proxy.type, "direct", "proxy failover to direct"); + equal(req.text, "ok!", "xhr completed"); + }) + .catch(req => { + ok(false, "xhr failed"); + }); + + await extension.unload(); + } +); + +add_task( + { + skip_if: () => + AppConstants.platform === "android" || directFailoverDisabled, + }, + async function test_failover_pac() { + const badPort = getBadProxyPort(); + + async function background(badPort) { + let pac = `function FindProxyForURL(url, host) { return "PROXY 127.0.0.1:${badPort}"; }`; + let proxySettings = { + proxyType: "autoConfig", + autoConfigUrl: `data:application/x-ns-proxy-autoconfig;charset=utf-8,${encodeURIComponent( + pac + )}`, + }; + + await browser.proxy.settings.set({ value: proxySettings }); + browser.test.sendMessage("proxied"); + } + let extensionData = { + manifest: { + permissions: ["proxy", "<all_urls>"], + }, + background: `(${background})(${badPort})`, + incognitoOverride: "spanning", + useAddonManager: "temporary", + }; + + let extension = ExtensionTestUtils.loadExtension(extensionData); + await extension.startup(); + await extension.awaitMessage("proxied"); + equal( + Services.prefs.getIntPref("network.proxy.type"), + 2, + "autoconfig type set" + ); + ok( + Services.prefs.getStringPref("network.proxy.autoconfig_url"), + "autoconfig url set" + ); + + await xhr(`http://${primaryHost}:${primaryPort}/`) + .then(req => { + equal(req.proxy.type, "direct", "proxy failover to direct"); + equal(req.text, "ok!", "xhr completed"); + }) + .catch(() => { + ok(false, "xhr failed"); + }); + + await extension.unload(); + } +); + +add_task(async function test_bypass_proxy() { + const proxyDetails = [ + { type: "http", host: "127.0.0.1", port: getBadProxyPort() }, + ]; + + let extension = await getProxyExtension(proxyDetails); + + await xhr(`http://${primaryHost}:${primaryPort}/`, { bypassProxy: true }) + .then(req => { + equal(req.proxy, undefined, "no proxy used"); + ok(true, "xhr completed"); + }) + .catch(req => { + equal(req.proxy, undefined, "no proxy used"); + ok(false, "xhr error"); + }); + + await extension.unload(); +}); + +add_task(async function test_bypass_proxy() { + const proxyDetails = [ + { type: "http", host: "127.0.0.1", port: getBadProxyPort() }, + ]; + + let extension = await getProxyExtension(proxyDetails); + + await serviceRequest(`http://${primaryHost}:${primaryPort}/`, { + bypassProxy: true, + }) + .then(req => { + equal(req.proxy, undefined, "no proxy used"); + ok(true, "xhr completed"); + }) + .catch(req => { + equal(req.proxy, undefined, "no proxy used"); + ok(false, "xhr error"); + }); + + await extension.unload(); +}); + +add_task(async function test_failover_system_off() { + // Test test failover failures, uncomment and set pref to false + Services.prefs.setBoolPref("network.proxy.failover_direct", false); + + const proxyDetails = [ + { type: "http", host: "127.0.0.1", port: getBadProxyPort() }, + ]; + + let extension = await getProxyExtension(proxyDetails); + + await xhr(`http://${primaryHost}:${primaryPort}/`) + .then(req => { + equal(req.proxy.sourceId, extension.id, "extension matches"); + ok(false, "xhr completed"); + }) + .catch(req => { + equal(req.proxy.sourceId, extension.id, "extension matches"); + equal(req.status, 0, "xhr failed"); + }); + + await extension.unload(); +}); |