--- a/toolkit/components/extensions/ProxyScriptContext.jsm
+++ b/toolkit/components/extensions/ProxyScriptContext.jsm
@@ -20,16 +20,19 @@ XPCOMUtils.defineLazyModuleGetter(this,
XPCOMUtils.defineLazyModuleGetter(this, "Schemas",
"resource://gre/modules/Schemas.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "ProxyService",
"@mozilla.org/network/protocol-proxy-service;1",
"nsIProtocolProxyService");
const CATEGORY_EXTENSION_SCRIPTS_CONTENT = "webextension-scripts-content";
+// Should DNS be resolved on the SOCKS proxy server? (does not apply to HTTP and HTTP proxy servers)
+const TRANSPARENT_PROXY_RESOLVES_HOST = Ci.nsIProxyInfo.TRANSPARENT_PROXY_RESOLVES_HOST;
+
// The length of time (seconds) to wait for a proxy to resolve before ignoring it.
const PROXY_TIMEOUT_SEC = 10;
const {
defineLazyGetter,
} = ExtensionUtils;
const {
@@ -126,60 +129,84 @@ class ProxyScriptContext extends BaseCon
message: error.message,
fileName: error.fileName,
lineNumber: error.lineNumber,
stack: error.stack,
});
return defaultProxyInfo;
}
- if (!ret || typeof ret !== "string") {
+ if (!ret || (typeof ret !== "string" && !Array.isArray(ret))) {
this.extension.emit("proxy-error", {
- message: "FindProxyForURL: Return type must be a string",
+ message: "FindProxyForURL: Return type must be a string or array of objects",
});
return defaultProxyInfo;
}
+ let isLegacyFormat = typeof ret === "string";
+ let unparsedRules = isLegacyFormat ? ret.split(";") : ret;
let rules = [];
- for (let rule of ret.split(";")) {
+ for (let rule of unparsedRules) {
try {
- rule = this.parseLegacyRule(rule);
+ rule = isLegacyFormat ? this.parseLegacyRule(rule) : this.parseRule(rule);
+ rules.push(rule);
if (rule.type === PROXY_TYPES.DIRECT) {
break;
}
- rules.push(rule);
} catch (e) {
this.extension.emit("proxy-error", {
message: "FindProxyForURL: " + (e.message || e),
});
break;
}
}
+ return this.createProxyInfo(rules, defaultProxyInfo);
+ }
- let proxyInfo = this.createProxyInfo(rules);
+ parseRule(rule) {
+ let {type, host, port, username, password, proxyDNS, failoverTimeout} = rule;
+ type = this.validateType(type);
- return proxyInfo || defaultProxyInfo;
+ if (type === "direct") {
+ return {type};
+ }
+ host = this.validateHost(host);
+ port = this.validatePort(port);
+ this.validateUsername(username);
+ this.validatePassword(password);
+ this.validateProxyDNS(proxyDNS, type);
+ this.validateFailoverTimeout(failoverTimeout);
+ return {type, host, port, username, password, proxyDNS, failoverTimeout};
}
/**
* Creates a new proxy info object using the return value of FindProxyForURL.
*
- * @param {Array<string>} rules The list of proxy rules returned by FindProxyForURL.
- * (e.g. ["PROXY 1.2.3.4:8080", "SOCKS 1.1.1.1:9090", "DIRECT"])
+ * @param {Array<Object>} rules The list of proxy rules returned by FindProxyForURL.
+ * @param {Object} defaultProxyInfo The proxy (or list of proxies) that
+ * would be used by default for the given URI. This may be null.
* @returns {nsIProxyInfo} The proxy info to apply for the given URI.
*/
- createProxyInfo(rules) {
- let proxyInfo = null;
+ createProxyInfo(rules, defaultProxyInfo) {
+ let proxyInfo = defaultProxyInfo;
while (rules.length) {
- let {type, host, port} = rules.pop();
- proxyInfo = ProxyService.newProxyInfo(
- type, host, port, 0, PROXY_TIMEOUT_SEC, proxyInfo);
+ let {type, host, port, username, password, proxyDNS, failoverTimeout} = rules.pop();
if (type === "DIRECT") {
return proxyInfo;
}
+ // TODO: After bug1360404 is fixed, use ProxyService.newProxyInfoWithAuth() all the time--not just for SOCKS
+ if (type == PROXY_TYPES.SOCKS || type == PROXY_TYPES.SOCKS4) {
+ proxyInfo = ProxyService.newProxyInfoWithAuth(
+ type, host, port, username, password, proxyDNS ? TRANSPARENT_PROXY_RESOLVES_HOST : 0,
+ failoverTimeout ? failoverTimeout : PROXY_TIMEOUT_SEC, proxyInfo);
+ } else {
+ proxyInfo = ProxyService.newProxyInfo(
+ type, host, port, proxyDNS ? TRANSPARENT_PROXY_RESOLVES_HOST : 0,
+ failoverTimeout ? failoverTimeout : PROXY_TIMEOUT_SEC, proxyInfo);
+ }
}
return proxyInfo;
}
parseLegacyRule(rule) {
rule = rule.trim();
if (!rule) {
@@ -190,49 +217,99 @@ class ProxyScriptContext extends BaseCon
if (!parts[0]) {
throw new Error(`Too few arguments passed for proxy rule: "${rule}"`);
}
if (parts.length > 2) {
throw new Error(`Too many arguments passed for proxy rule: "${rule}"`);
}
- let type = parts[0].toLowerCase();
+ let type = this.validateType(parts[0]);
switch (type) {
- case PROXY_TYPES.PROXY:
case PROXY_TYPES.HTTP:
case PROXY_TYPES.HTTPS:
case PROXY_TYPES.SOCKS:
case PROXY_TYPES.SOCKS4:
- if (!parts[1]) {
- throw new Error(`Missing argument for proxy type: "${parts[0]}"`);
+ if (parts.length != 2) {
+ throw new Error(`Incorrect number of arguments for proxy rule: "${rule}"`);
}
-
- if (parts.length != 2) {
- throw new Error(`Too many arguments for proxy rule: "${rule}"`);
- }
-
let [host, port] = parts[1].split(":");
- if (!host || !port) {
- throw new Error(`Unable to parse host and port from proxy rule: "${rule}"`);
- }
-
- if (type == PROXY_TYPES.PROXY) {
- // PROXY_TYPES.HTTP and PROXY_TYPES.PROXY are synonyms
- type = PROXY_TYPES.HTTP;
- }
-
+ host = this.validateHost(host);
+ port = this.validatePort(port);
return {type, host, port};
case PROXY_TYPES.DIRECT:
if (parts.length != 1) {
- throw new Error(`Invalid argument for proxy type: "${parts[0]}"`);
+ throw new Error(`"${parts[0]}" takes no arguments`);
}
return {type};
- default:
- throw new Error(`Unrecognized proxy type: "${parts[0]}"`);
+ }
+ }
+
+ validateType(type) {
+ if (typeof type != "string" || !PROXY_TYPES.hasOwnProperty(type.toUpperCase())) {
+ throw new Error(`Invalid proxy server type: "${type}"`);
+ }
+ type = type.toLowerCase();
+ if (type == PROXY_TYPES.PROXY) {
+ // PROXY_TYPES.HTTP and PROXY_TYPES.PROXY are synonyms
+ return PROXY_TYPES.HTTP;
+ }
+ return type;
+ }
+
+ validateHost(host) {
+ if (typeof host != "string" || host.split(/\s+/).length != 1) {
+ throw new Error(`Invalid proxy server host: "${host}"`);
+ }
+ host = host.trim();
+ if (!host.length) {
+ throw new Error("Proxy server host cannot be empty");
+ }
+ return host;
+ }
+
+ validatePort(port) {
+ port = Number.parseInt(port, 10);
+ if (!Number.isInteger(port)) {
+ throw new Error(`Invalid proxy server port: "${port}"`);
+ }
+
+ if (port < 1 || port > 0xffff) {
+ throw new Error(`Proxy server port ${port} outside range 1 to 65535`);
+ }
+ return port;
+ }
+
+ validateUsername(username) {
+ if (username !== undefined && typeof username != "string") {
+ throw new Error(`Invalid proxy server username: "${username}"`);
+ }
+ }
+
+ validatePassword(password) {
+ if (password !== undefined && typeof password != "string") {
+ throw new Error(`Invalid proxy server password: "${password}"`);
+ }
+ }
+
+ validateProxyDNS(proxyDNS, type) {
+ // Do we want the SOCKS layer to send the hostname and port to the proxy and let it do the DNS?
+ if (proxyDNS !== undefined) {
+ if (typeof proxyDNS != "boolean") {
+ throw new Error(`Invalid proxyDNS value: "${proxyDNS}"`);
+ }
+ if (proxyDNS && type != PROXY_TYPES.SOCKS && type != PROXY_TYPES.SOCKS4) {
+ throw new Error(`proxyDNS can only be true for SOCKS proxy servers`);
+ }
+ }
+ }
+
+ validateFailoverTimeout(failoverTimeout) {
+ if (failoverTimeout !== undefined && (!Number.isInteger(failoverTimeout) || failoverTimeout < 1)) {
+ throw new Error(`Invalid failover timeout: "${failoverTimeout}"`);
}
}
/**
* Unloads the proxy filter and shuts down the sandbox.
*/
unload() {
super.unload();
--- a/toolkit/components/extensions/test/mochitest/mochitest-common.ini
+++ b/toolkit/components/extensions/test/mochitest/mochitest-common.ini
@@ -76,16 +76,17 @@ skip-if = os == 'android' # bug 1369440
[test_ext_exclude_include_globs.html]
[test_ext_external_messaging.html]
[test_ext_generate.html]
[test_ext_geolocation.html]
skip-if = os == 'android' # Android support Bug 1336194
[test_ext_notifications.html]
[test_ext_permission_xhr.html]
[test_ext_proxy.html]
+[test_ext_proxy_legacy.html]
skip-if = os == 'android' && debug # Bug 1357635
[test_ext_runtime_connect.html]
[test_ext_runtime_connect_twoway.html]
[test_ext_runtime_connect2.html]
[test_ext_runtime_disconnect.html]
[test_ext_runtime_id.html]
[test_ext_sandbox_var.html]
[test_ext_sendmessage_doublereply.html]
--- a/toolkit/components/extensions/test/mochitest/test_ext_proxy.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_proxy.html
@@ -79,38 +79,40 @@ add_task(async function test_invalid_Fin
message: "The proxy script must define FindProxyForURL as a function",
});
});
add_task(async function test_invalid_FindProxyForURL_return_types() {
await testProxyScript(
() => {
function FindProxyForURL() {
- return 5;
+ return {type: "socks", host: "foo.bar", port: 1080, username: "mungosantamaria", password: "pass123"};
}
}, {
- message: "FindProxyForURL: Return type must be a string",
+ message: "FindProxyForURL: Return type must be a string or array of objects",
});
await testProxyScript(
() => {
function FindProxyForURL() {
- return "INVALID";
+ return [{type: "pptp", host: "foo.bar", port: 1080, username: "mungosantamaria", password: "pass123", proxyDNS: true, failoverTimeout: 3},
+ {type: "http", host: "192.168.1.1", port: 3128, username: "mungosantamaria", password: "word321"}];
}
}, {
- message: "FindProxyForURL: Unrecognized proxy type: \"INVALID\"",
+ message: "FindProxyForURL: Invalid proxy server type: \"pptp\"",
});
await testProxyScript(
() => {
function FindProxyForURL() {
- return "SOCKS";
+ return [{type: "pptp", host: "foo.bar", port: 65536, username: "mungosantamaria", password: "pass123", proxyDNS: true, failoverTimeout: 3},
+ {type: "http", host: "192.168.1.1", port: 3128, username: "mungosantamaria", password: "word321"}];
}
}, {
- message: "FindProxyForURL: Missing argument for proxy type: \"SOCKS\"",
+ message: "FindProxyForURL: Proxy server port 65536 outside range 1 to 65535",
});
await testProxyScript(
() => {
function FindProxyForURL() {
return "PROXY 1.2.3.4:8080 EXTRA";
}
}, {
@@ -118,53 +120,53 @@ add_task(async function test_invalid_Fin
});
await testProxyScript(
() => {
function FindProxyForURL() {
return "PROXY :";
}
}, {
- message: "FindProxyForURL: Unable to parse host and port from proxy rule: \"PROXY :\"",
+ message: "FindProxyForURL: Proxy server host cannot be empty",
});
await testProxyScript(
() => {
function FindProxyForURL() {
return "PROXY :8080";
}
}, {
- message: "FindProxyForURL: Unable to parse host and port from proxy rule: \"PROXY :8080\"",
+ message: "FindProxyForURL: Proxy server host cannot be empty",
});
await testProxyScript(
() => {
function FindProxyForURL() {
return "PROXY ::";
}
}, {
- message: "FindProxyForURL: Unable to parse host and port from proxy rule: \"PROXY ::\"",
+ message: "FindProxyForURL: Proxy server host cannot be empty",
});
await testProxyScript(
() => {
function FindProxyForURL() {
return "PROXY 1.2.3.4:";
}
}, {
- message: "FindProxyForURL: Unable to parse host and port from proxy rule: \"PROXY 1.2.3.4:\"",
+ message: "FindProxyForURL: Invalid proxy server port: \"NaN\"",
});
await testProxyScript(
() => {
function FindProxyForURL() {
return "DIRECT 1.2.3.4:8080";
}
}, {
- message: "FindProxyForURL: Invalid argument for proxy type: \"DIRECT\"",
+ message: "FindProxyForURL: \"DIRECT\" takes no arguments",
});
});
add_task(async function test_runtime_error_in_FindProxyForURL() {
await testProxyScript(
() => {
function FindProxyForURL() {
return not_defined; // eslint-disable-line no-undef
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/mochitest/test_ext_proxy_legacy.html
@@ -0,0 +1,167 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Tests for the proxy API</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
+ <script type="text/javascript" src="head.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+<script type="text/javascript">
+/* eslint no-unused-vars: ["error", {"args": "none", "varsIgnorePattern": "^(FindProxyForURL)$"}] */
+
+"use strict";
+
+async function testProxyScript(script, expected) {
+ let extension = ExtensionTestUtils.loadExtension({
+ background() {
+ let errorReceived = false;
+ browser.proxy.onProxyError.addListener(error => {
+ if (!errorReceived) {
+ errorReceived = true;
+ browser.test.sendMessage("proxy-error-received", error);
+ }
+ });
+
+ browser.proxy.registerProxyScript("proxy_script.js");
+ },
+ manifest: {
+ "permissions": ["proxy"],
+ },
+ files: {
+ "proxy_script.js": String(script).replace(/^.*?\{([^]*)\}$/, "$1"),
+ },
+ });
+
+ await extension.startup();
+
+ let win = window.open("http://example.com/");
+ let error = await extension.awaitMessage("proxy-error-received");
+ is(error.message, expected.message, "Correct error message received");
+
+ if (expected.errorInfo) {
+ ok(error.fileName.includes("proxy_script.js"), "Error should include file name");
+ is(error.lineNumber, 3, "Error should include line number");
+ ok(error.stack.includes("proxy_script.js:3:9"), "Error should include stack trace");
+ }
+
+ win.close();
+ await extension.unload();
+}
+
+add_task(async function test_invalid_FindProxyForURL_type() {
+ await testProxyScript(
+ () => { }, {
+ message: "The proxy script must define FindProxyForURL as a function",
+ });
+
+ await testProxyScript(
+ () => {
+ var FindProxyForURL = 5; // eslint-disable-line mozilla/var-only-at-top-level
+ }, {
+ message: "The proxy script must define FindProxyForURL as a function",
+ });
+});
+
+add_task(async function test_invalid_FindProxyForURL_return_types() {
+ await testProxyScript(
+ () => {
+ function FindProxyForURL() {
+ return 5;
+ }
+ }, {
+ message: "FindProxyForURL: Return type must be a string or array of objects",
+ });
+
+ await testProxyScript(
+ () => {
+ function FindProxyForURL() {
+ return "INVALID";
+ }
+ }, {
+ message: "FindProxyForURL: Invalid proxy server type: \"INVALID\"",
+ });
+
+ await testProxyScript(
+ () => {
+ function FindProxyForURL() {
+ return "SOCKS";
+ }
+ }, {
+ message: "FindProxyForURL: Incorrect number of arguments for proxy rule: \"SOCKS\"",
+ });
+
+ await testProxyScript(
+ () => {
+ function FindProxyForURL() {
+ return "PROXY 1.2.3.4:8080 EXTRA";
+ }
+ }, {
+ message: "FindProxyForURL: Too many arguments passed for proxy rule: \"PROXY 1.2.3.4:8080 EXTRA\"",
+ });
+
+ await testProxyScript(
+ () => {
+ function FindProxyForURL() {
+ return "PROXY :";
+ }
+ }, {
+ message: "FindProxyForURL: Proxy server host cannot be empty",
+ });
+
+ await testProxyScript(
+ () => {
+ function FindProxyForURL() {
+ return "PROXY :8080";
+ }
+ }, {
+ message: "FindProxyForURL: Proxy server host cannot be empty",
+ });
+
+ await testProxyScript(
+ () => {
+ function FindProxyForURL() {
+ return "PROXY ::";
+ }
+ }, {
+ message: "FindProxyForURL: Proxy server host cannot be empty",
+ });
+
+ await testProxyScript(
+ () => {
+ function FindProxyForURL() {
+ return "PROXY 1.2.3.4:";
+ }
+ }, {
+ message: "FindProxyForURL: Invalid proxy server port: \"NaN\"",
+ });
+
+ await testProxyScript(
+ () => {
+ function FindProxyForURL() {
+ return "DIRECT 1.2.3.4:8080";
+ }
+ }, {
+ message: "FindProxyForURL: \"DIRECT\" takes no arguments",
+ });
+});
+
+add_task(async function test_runtime_error_in_FindProxyForURL() {
+ await testProxyScript(
+ () => {
+ function FindProxyForURL() {
+ return not_defined; // eslint-disable-line no-undef
+ }
+ }, {
+ message: "not_defined is not defined",
+ errorInfo: true,
+ });
+});
+
+</script>
+
+</body>
+</html>
--- a/toolkit/components/extensions/test/xpcshell/test_proxy_scripts.js
+++ b/toolkit/components/extensions/test/xpcshell/test_proxy_scripts.js
@@ -4,16 +4,18 @@
Cu.import("resource://gre/modules/Extension.jsm");
Cu.import("resource://gre/modules/ProxyScriptContext.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "gProxyService",
"@mozilla.org/network/protocol-proxy-service;1",
"nsIProtocolProxyService");
+const TRANSPARENT_PROXY_RESOLVES_HOST = Ci.nsIProxyInfo.TRANSPARENT_PROXY_RESOLVES_HOST;
+
async function testProxyScript(options, expected = {}) {
let scriptData = String(options.scriptData).replace(/^.*?\{([^]*)\}$/, "$1");
let extensionData = {
background() {
browser.test.onMessage.addListener((message, data) => {
if (message === "runtime-message") {
browser.runtime.onMessage.addListener((msg, sender, respond) => {
if (msg === "finish-from-pac-script") {
@@ -70,286 +72,137 @@ async function testProxyScript(options,
resolve(pi);
},
});
});
if (!proxyInfo) {
equal(proxyInfo, expected.proxyInfo, "Expected proxyInfo to be null");
} else {
- let expectedProxyInfo = expected.proxyInfo;
+ expected = expected.proxyInfo;
for (let proxy = proxyInfo; proxy; proxy = proxy.failoverProxy) {
- equal(proxy.host, expectedProxyInfo.host, `Expected proxy host to be ${expectedProxyInfo.host}`);
- equal(proxy.port, expectedProxyInfo.port, `Expected proxy port to be ${expectedProxyInfo.port}`);
- equal(proxy.type, expectedProxyInfo.type, `Expected proxy type to be ${expectedProxyInfo.type}`);
- expectedProxyInfo = expectedProxyInfo.failoverProxy;
+ if (expected) {
+ let {type, host, port, username, password, proxyDNS, failoverTimeout} = expected;
+ equal(proxy.host, host, `Expected proxy host to be ${host}`);
+ equal(proxy.port, port, `Expected proxy port to be ${port}`);
+ equal(proxy.type, type, `Expected proxy type to be ${type}`);
+ equal(proxy.username, username || "", `Expected proxy username to be ${username}`);
+ equal(proxy.password, password || "", `Expected proxy password to be ${password}`);
+ equal(proxy.flags, proxyDNS == undefined ? 0 : proxyDNS, `Expected proxyDNS to be ${proxyDNS}`);
+ // Default timeout is 10
+ equal(proxy.failoverTimeout, failoverTimeout || 10, `Expected failoverTimeout to be ${failoverTimeout}`);
+ expected = expected.failoverProxy;
+ }
}
}
await extension.unload();
script.unload();
}
-add_task(async function testUndefinedFindProxyForURL() {
- await testProxyScript({
- scriptData() { },
- }, {
- proxyInfo: null,
- });
-});
-
-add_task(async function testWrongTypeForFindProxyForURL() {
- await testProxyScript({
- scriptData() {
- let FindProxyForURL = "foo";
- },
- }, {
- proxyInfo: null,
- });
-});
-
-add_task(async function testInvalidReturnTypeForFindProxyForURL() {
+add_task(async function testEmptyObject() {
await testProxyScript({
scriptData() {
function FindProxyForURL(url, host) {
- return -1;
- }
- },
- }, {
- proxyInfo: null,
- });
-});
-
-add_task(async function testSimpleProxyScript() {
- await testProxyScript({
- scriptData() {
- function FindProxyForURL(url, host) {
- if (host === "www.mozilla.org") {
- return "DIRECT";
- }
- }
- },
- }, {
- proxyInfo: null,
- });
-});
-
-add_task(async function testRuntimeErrorInProxyScript() {
- await testProxyScript({
- scriptData() {
- function FindProxyForURL(url, host) {
- return RUNTIME_ERROR; // eslint-disable-line no-undef
- }
- },
- }, {
- proxyInfo: null,
- });
-});
-
-add_task(async function testProxyScriptWithUnexpectedReturnType() {
- await testProxyScript({
- scriptData() {
- function FindProxyForURL(url, host) {
- return "UNEXPECTED 1.2.3.4:8080";
+ return {};
}
},
}, {
proxyInfo: null,
});
});
-add_task(async function testSocksReturnType() {
+add_task(async function testHTTPType() {
await testProxyScript({
scriptData() {
function FindProxyForURL(url, host) {
- return "SOCKS foo.bar:1080";
+ return [{type: "http", host: "foo.bar", port: 3128}];
}
},
}, {
proxyInfo: {
host: "foo.bar",
- port: "1080",
- type: "socks",
- failoverProxy: null,
- },
- });
-});
-
-add_task(async function testSocks4ReturnType() {
- await testProxyScript({
- scriptData() {
- function FindProxyForURL(url, host) {
- return "SOCKS4 1.2.3.4:1080";
- }
- },
- }, {
- proxyInfo: {
- host: "1.2.3.4",
- port: "1080",
- type: "socks4",
- failoverProxy: null,
- },
- });
-});
-
-add_task(async function testSocksReturnTypeWithHostCheck() {
- await testProxyScript({
- scriptData() {
- function FindProxyForURL(url, host) {
- if (host === "www.mozilla.org") {
- return "SOCKS 4.4.4.4:9002";
- }
- }
- },
- }, {
- proxyInfo: {
- host: "4.4.4.4",
- port: "9002",
- type: "socks",
- failoverProxy: null,
+ port: "3128",
+ type: "http",
},
});
});
-add_task(async function testProxyReturnType() {
+add_task(async function testHTTPSType() {
await testProxyScript({
scriptData() {
function FindProxyForURL(url, host) {
- return "PROXY 1.2.3.4:8080";
+ return [{type: "https", host: "foo.com", port: 3129}];
}
},
}, {
proxyInfo: {
- host: "1.2.3.4",
- port: "8080",
- type: "http",
- failoverProxy: null,
+ host: "foo.com",
+ port: "3129",
+ type: "https",
},
});
});
-add_task(async function testUnusualWhitespaceForFindProxyForURL() {
+add_task(async function testAllOptionalProperties() {
await testProxyScript({
scriptData() {
function FindProxyForURL(url, host) {
- return " PROXY 1.2.3.4:8080 ";
+ return [{type: "socks", host: "foo.bar", port: 1080, username: "mungo", password: "santamaria123", proxyDNS: true, failoverTimeout: 5}];
}
},
}, {
proxyInfo: {
- host: "1.2.3.4",
- port: "8080",
- type: "http",
+ type: "socks",
+ host: "foo.bar",
+ port: 1080,
+ username: "mungo",
+ password: "santamaria123",
+ failoverTimeout: 5,
failoverProxy: null,
- },
- });
-});
-
-add_task(async function testInvalidProxyScriptIgnoresFailover() {
- await testProxyScript({
- scriptData() {
- function FindProxyForURL(url, host) {
- return "PROXY 1.2.3.4:8080; UNEXPECTED; SOCKS 1.2.3.4:8080";
- }
- },
- }, {
- proxyInfo: {
- host: "1.2.3.4",
- port: "8080",
- type: "http",
- failoverProxy: null,
+ proxyDNS: TRANSPARENT_PROXY_RESOLVES_HOST,
},
});
});
-add_task(async function testProxyScriptWithValidFailovers() {
+add_task(async function testAllOptionalPropertiesWithMultipleFailover() {
await testProxyScript({
scriptData() {
function FindProxyForURL(url, host) {
- return "PROXY 1.2.3.4:8080; SOCKS 4.4.4.4:9000; DIRECT";
- }
- },
- }, {
- proxyInfo: {
- host: "1.2.3.4",
- port: "8080",
- type: "http",
- failoverProxy: {
- host: "4.4.4.4",
- port: "9000",
- type: "socks",
- failoverProxy: null,
- },
- },
- });
-});
-
-add_task(async function testProxyScriptWithAnInvalidFailover() {
- await testProxyScript({
- scriptData() {
- function FindProxyForURL(url, host) {
- return "PROXY 1.2.3.4:8080; INVALID 1.2.3.4:9090; SOCKS 4.4.4.4:9000; DIRECT";
+ return [{type: "socks", host: "foo.bar", port: 1080, username: "johnsmith", password: "pass123", proxyDNS: true, failoverTimeout: 3},
+ {type: "http", host: "192.168.1.1", port: 3128}, {type: "https", host: "192.168.1.2", port: 1121, failoverTimeout: 1},
+ {type: "socks", host: "192.168.1.3", port: 1999, proxyDNS: true, username: "mungosantamaria", password: "foobar"}];
}
},
}, {
proxyInfo: {
- host: "1.2.3.4",
- port: "8080",
- type: "http",
- failoverProxy: null,
+ type: "socks",
+ host: "foo.bar",
+ port: 1080,
+ proxyDNS: true,
+ username: "johnsmith",
+ password: "pass123",
+ failoverTimeout: 3,
+ failoverProxy: {
+ host: "192.168.1.1",
+ port: 3128,
+ type: "http",
+ failoverProxy: {
+ host: "192.168.1.2",
+ port: 1121,
+ type: "https",
+ failoverTimeout: 1,
+ failoverProxy: {
+ host: "192.168.1.3",
+ port: 1999,
+ type: "socks",
+ proxyDNS: TRANSPARENT_PROXY_RESOLVES_HOST,
+ username: "mungosantamaria",
+ password: "foobar",
+ failoverProxy: {
+ type: "dect",
+ },
+ },
+ },
+ },
},
});
});
-
-add_task(async function testProxyScriptWithEmptyFailovers() {
- await testProxyScript({
- scriptData() {
- function FindProxyForURL(url, host) {
- return ";;;;;PROXY 1.2.3.4:8080";
- }
- },
- }, {
- proxyInfo: null,
- });
-});
-
-add_task(async function testProxyScriptWithInvalidReturn() {
- await testProxyScript({
- scriptData() {
- function FindProxyForURL(url, host) {
- return "SOCKS :8080;";
- }
- },
- }, {
- proxyInfo: null,
- });
-});
-
-add_task(async function testProxyScriptWithRuntimeUpdate() {
- await testProxyScript({
- scriptData() {
- let settings = {};
- function FindProxyForURL(url, host) {
- if (settings.host === "www.mozilla.org") {
- return "PROXY 1.2.3.4:8080;";
- }
- return "DIRECT";
- }
- browser.runtime.onMessage.addListener((msg, sender, respond) => {
- if (msg.host) {
- settings.host = msg.host;
- browser.runtime.sendMessage("finish-from-pac-script");
- return Promise.resolve(msg);
- }
- });
- },
- runtimeMessage: {
- host: "www.mozilla.org",
- },
- }, {
- proxyInfo: {
- host: "1.2.3.4",
- port: "8080",
- type: "http",
- failoverProxy: null,
- },
- });
-});
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/xpcshell/test_proxy_scripts_legacy.js
@@ -0,0 +1,359 @@
+"use strict";
+
+/* eslint no-unused-vars: ["error", {"args": "none", "varsIgnorePattern": "^(FindProxyForURL)$"}] */
+
+Cu.import("resource://gre/modules/Extension.jsm");
+Cu.import("resource://gre/modules/ProxyScriptContext.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gProxyService",
+ "@mozilla.org/network/protocol-proxy-service;1",
+ "nsIProtocolProxyService");
+
+async function testProxyScript(options, expected = {}) {
+ let scriptData = String(options.scriptData).replace(/^.*?\{([^]*)\}$/, "$1");
+ let extensionData = {
+ background() {
+ browser.test.onMessage.addListener((message, data) => {
+ if (message === "runtime-message") {
+ browser.runtime.onMessage.addListener((msg, sender, respond) => {
+ if (msg === "finish-from-pac-script") {
+ browser.test.notifyPass("proxy");
+ return Promise.resolve(msg);
+ }
+ });
+ browser.runtime.sendMessage(data, {toProxyScript: true}).then(response => {
+ browser.test.sendMessage("runtime-message-sent");
+ });
+ } else if (message === "finish-from-xpcshell-test") {
+ browser.test.notifyPass("proxy");
+ }
+ });
+ },
+ files: {
+ "proxy.js": scriptData,
+ },
+ };
+
+ let extension = ExtensionTestUtils.loadExtension(extensionData);
+ let extension_internal = extension.extension;
+
+ await extension.startup();
+
+ let script = new ProxyScriptContext(extension_internal, extension_internal.getURL("proxy.js"));
+
+ try {
+ await script.load();
+ } catch (error) {
+ equal(error, expected.error, "Expected error received");
+ script.unload();
+ await extension.unload();
+ return;
+ }
+
+ if (options.runtimeMessage) {
+ extension.sendMessage("runtime-message", options.runtimeMessage);
+ await extension.awaitMessage("runtime-message-sent");
+ } else {
+ extension.sendMessage("finish-from-xpcshell-test");
+ }
+
+ await extension.awaitFinish("proxy");
+
+ let proxyInfo = await new Promise((resolve, reject) => {
+ let channel = NetUtil.newChannel({
+ uri: "http://www.mozilla.org/",
+ loadUsingSystemPrincipal: true,
+ });
+
+ gProxyService.asyncResolve(channel, 0, {
+ onProxyAvailable(req, uri, pi, status) {
+ resolve(pi);
+ },
+ });
+ });
+
+ if (!proxyInfo) {
+ equal(proxyInfo, expected.proxyInfo, "Expected proxyInfo to be null");
+ } else {
+ let expectedProxyInfo = expected.proxyInfo;
+ for (let proxy = proxyInfo; proxy; proxy = proxy.failoverProxy) {
+ equal(proxy.host, expectedProxyInfo.host, `Expected proxy host to be ${expectedProxyInfo.host}`);
+ equal(proxy.port, expectedProxyInfo.port, `Expected proxy port to be ${expectedProxyInfo.port}`);
+ equal(proxy.type, expectedProxyInfo.type, `Expected proxy type to be ${expectedProxyInfo.type}`);
+ expectedProxyInfo = expectedProxyInfo.failoverProxy;
+ }
+ }
+
+ await extension.unload();
+ script.unload();
+}
+
+add_task(async function testUndefinedFindProxyForURL() {
+ await testProxyScript({
+ scriptData() { },
+ }, {
+ proxyInfo: null,
+ });
+});
+
+add_task(async function testWrongTypeForFindProxyForURL() {
+ await testProxyScript({
+ scriptData() {
+ let FindProxyForURL = "foo";
+ },
+ }, {
+ proxyInfo: null,
+ });
+});
+
+add_task(async function testInvalidReturnTypeForFindProxyForURL() {
+ await testProxyScript({
+ scriptData() {
+ function FindProxyForURL(url, host) {
+ return -1;
+ }
+ },
+ }, {
+ proxyInfo: null,
+ });
+});
+
+add_task(async function testSimpleProxyScript() {
+ await testProxyScript({
+ scriptData() {
+ function FindProxyForURL(url, host) {
+ if (host === "www.mozilla.org") {
+ return "DIRECT";
+ }
+ }
+ },
+ }, {
+ proxyInfo: null,
+ });
+});
+
+add_task(async function testRuntimeErrorInProxyScript() {
+ await testProxyScript({
+ scriptData() {
+ function FindProxyForURL(url, host) {
+ return RUNTIME_ERROR; // eslint-disable-line no-undef
+ }
+ },
+ }, {
+ proxyInfo: null,
+ });
+});
+
+add_task(async function testProxyScriptWithUnexpectedReturnType() {
+ await testProxyScript({
+ scriptData() {
+ function FindProxyForURL(url, host) {
+ return "UNEXPECTED 1.2.3.4:8080";
+ }
+ },
+ }, {
+ proxyInfo: null,
+ });
+});
+
+add_task(async function testSocksReturnType() {
+ await testProxyScript({
+ scriptData() {
+ function FindProxyForURL(url, host) {
+ return "SOCKS foo.bar:1080";
+ }
+ },
+ }, {
+ proxyInfo: {
+ host: "foo.bar",
+ port: "1080",
+ type: "socks",
+ failoverProxy: null,
+ },
+ });
+});
+
+add_task(async function testSocks4ReturnType() {
+ await testProxyScript({
+ scriptData() {
+ function FindProxyForURL(url, host) {
+ return "SOCKS4 1.2.3.4:1080";
+ }
+ },
+ }, {
+ proxyInfo: {
+ host: "1.2.3.4",
+ port: "1080",
+ type: "socks4",
+ failoverProxy: null,
+ },
+ });
+});
+
+add_task(async function testSocksReturnTypeWithHostCheck() {
+ await testProxyScript({
+ scriptData() {
+ function FindProxyForURL(url, host) {
+ if (host === "www.mozilla.org") {
+ return "SOCKS 4.4.4.4:9002";
+ }
+ }
+ },
+ }, {
+ proxyInfo: {
+ host: "4.4.4.4",
+ port: "9002",
+ type: "socks",
+ failoverProxy: null,
+ },
+ });
+});
+
+add_task(async function testProxyReturnType() {
+ await testProxyScript({
+ scriptData() {
+ function FindProxyForURL(url, host) {
+ return "PROXY 1.2.3.4:8080";
+ }
+ },
+ }, {
+ proxyInfo: {
+ host: "1.2.3.4",
+ port: "8080",
+ type: "http",
+ failoverProxy: null,
+ },
+ });
+});
+
+add_task(async function testUnusualWhitespaceForFindProxyForURL() {
+ await testProxyScript({
+ scriptData() {
+ function FindProxyForURL(url, host) {
+ return " PROXY 1.2.3.4:8080 ";
+ }
+ },
+ }, {
+ proxyInfo: {
+ host: "1.2.3.4",
+ port: "8080",
+ type: "http",
+ failoverProxy: null,
+ },
+ });
+});
+
+add_task(async function testInvalidProxyScriptIgnoresFailover() {
+ await testProxyScript({
+ scriptData() {
+ function FindProxyForURL(url, host) {
+ return "PROXY 1.2.3.4:8080; UNEXPECTED; SOCKS 1.2.3.4:8080";
+ }
+ },
+ }, {
+ proxyInfo: {
+ host: "1.2.3.4",
+ port: "8080",
+ type: "http",
+ failoverProxy: null,
+ },
+ });
+});
+
+add_task(async function testProxyScriptWithValidFailovers() {
+ await testProxyScript({
+ scriptData() {
+ function FindProxyForURL(url, host) {
+ return "PROXY 1.2.3.4:8080; SOCKS 4.4.4.4:9000; DIRECT";
+ }
+ },
+ }, {
+ proxyInfo: {
+ host: "1.2.3.4",
+ port: "8080",
+ type: "http",
+ failoverProxy: {
+ host: "4.4.4.4",
+ port: "9000",
+ type: "socks",
+ failoverProxy: {
+ type: "direct",
+ host: null,
+ port: -1,
+ },
+ },
+ },
+ });
+});
+
+add_task(async function testProxyScriptWithAnInvalidFailover() {
+ await testProxyScript({
+ scriptData() {
+ function FindProxyForURL(url, host) {
+ return "PROXY 1.2.3.4:8080; INVALID 1.2.3.4:9090; SOCKS 4.4.4.4:9000; DIRECT";
+ }
+ },
+ }, {
+ proxyInfo: {
+ host: "1.2.3.4",
+ port: "8080",
+ type: "http",
+ failoverProxy: null,
+ },
+ });
+});
+
+add_task(async function testProxyScriptWithEmptyFailovers() {
+ await testProxyScript({
+ scriptData() {
+ function FindProxyForURL(url, host) {
+ return ";;;;;PROXY 1.2.3.4:8080";
+ }
+ },
+ }, {
+ proxyInfo: null,
+ });
+});
+
+add_task(async function testProxyScriptWithInvalidReturn() {
+ await testProxyScript({
+ scriptData() {
+ function FindProxyForURL(url, host) {
+ return "SOCKS :8080;";
+ }
+ },
+ }, {
+ proxyInfo: null,
+ });
+});
+
+add_task(async function testProxyScriptWithRuntimeUpdate() {
+ await testProxyScript({
+ scriptData() {
+ let settings = {};
+ function FindProxyForURL(url, host) {
+ if (settings.host === "www.mozilla.org") {
+ return "PROXY 1.2.3.4:8080;";
+ }
+ return "DIRECT";
+ }
+ browser.runtime.onMessage.addListener((msg, sender, respond) => {
+ if (msg.host) {
+ settings.host = msg.host;
+ browser.runtime.sendMessage("finish-from-pac-script");
+ return Promise.resolve(msg);
+ }
+ });
+ },
+ runtimeMessage: {
+ host: "www.mozilla.org",
+ },
+ }, {
+ proxyInfo: {
+ host: "1.2.3.4",
+ port: "8080",
+ type: "http",
+ failoverProxy: null,
+ },
+ });
+});
--- a/toolkit/components/extensions/test/xpcshell/xpcshell.ini
+++ b/toolkit/components/extensions/test/xpcshell/xpcshell.ini
@@ -91,9 +91,10 @@ skip-if = os == "android"
[test_ext_unknown_permissions.js]
[test_ext_legacy_extension_context.js]
[test_ext_legacy_extension_embedding.js]
[test_locale_converter.js]
[test_locale_data.js]
[test_native_messaging.js]
skip-if = os == "android"
[test_proxy_scripts.js]
+[test_proxy_scripts_legacy.js]
[include:xpcshell-content.ini]