--- a/toolkit/components/extensions/ProxyScriptContext.jsm
+++ b/toolkit/components/extensions/ProxyScriptContext.jsm
@@ -20,39 +20,176 @@ 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";
+// DNS is resolved on the SOCKS proxy server.
+const {TRANSPARENT_PROXY_RESOLVES_HOST} = Ci.nsIProxyInfo;
+
// The length of time (seconds) to wait for a proxy to resolve before ignoring it.
const PROXY_TIMEOUT_SEC = 10;
const {
+ ExtensionError,
defineLazyGetter,
} = ExtensionUtils;
const {
BaseContext,
CanOfAPIs,
LocalAPIImplementation,
SchemaAPIManager,
} = ExtensionCommon;
const PROXY_TYPES = Object.freeze({
DIRECT: "direct",
HTTPS: "https",
- PROXY: "proxy",
- HTTP: "http", // Synonym for PROXY_TYPES.PROXY
- SOCKS: "socks", // SOCKS5
+ PROXY: "http", // Synonym for PROXY_TYPES.HTTP
+ HTTP: "http",
+ SOCKS: "socks", // SOCKS5
SOCKS4: "socks4",
});
+const ProxyInfoData = {
+ validate(proxyData) {
+ if (proxyData.type && proxyData.type.toLowerCase() === "direct") {
+ return {type: proxyData.type};
+ }
+ for (let prop of ["type", "host", "port", "username", "password", "proxyDNS", "failoverTimeout"]) {
+ this[prop](proxyData);
+ }
+ return proxyData;
+ },
+
+ type(proxyData) {
+ let {type} = proxyData;
+ if (typeof type !== "string" || !PROXY_TYPES.hasOwnProperty(type.toUpperCase())) {
+ throw new ExtensionError(`FindProxyForURL: Invalid proxy server type: "${type}"`);
+ }
+ proxyData.type = PROXY_TYPES[type.toUpperCase()];
+ },
+
+ host(proxyData) {
+ let {host} = proxyData;
+ if (typeof host !== "string" || host.includes(" ")) {
+ throw new ExtensionError(`FindProxyForURL: Invalid proxy server host: "${host}"`);
+ }
+ if (!host.length) {
+ throw new ExtensionError("FindProxyForURL: Proxy server host cannot be empty");
+ }
+ proxyData.host = host;
+ },
+
+ port(proxyData) {
+ let port = Number.parseInt(proxyData.port, 10);
+ if (!Number.isInteger(port)) {
+ throw new ExtensionError(`FindProxyForURL: Invalid proxy server port: "${port}"`);
+ }
+
+ if (port < 1 || port > 0xffff) {
+ throw new ExtensionError(`FindProxyForURL: Proxy server port ${port} outside range 1 to 65535`);
+ }
+ proxyData.port = port;
+ },
+
+ username(proxyData) {
+ let {username} = proxyData;
+ if (username !== undefined && typeof username !== "string") {
+ throw new ExtensionError(`FindProxyForURL: Invalid proxy server username: "${username}"`);
+ }
+ },
+
+ password(proxyData) {
+ let {password} = proxyData;
+ if (password !== undefined && typeof password !== "string") {
+ throw new ExtensionError(`FindProxyForURL: Invalid proxy server password: "${password}"`);
+ }
+ },
+
+ proxyDNS(proxyData) {
+ let {proxyDNS, type} = proxyData;
+ if (proxyDNS !== undefined) {
+ if (typeof proxyDNS !== "boolean") {
+ throw new ExtensionError(`FindProxyForURL: Invalid proxyDNS value: "${proxyDNS}"`);
+ }
+ if (proxyDNS && type !== PROXY_TYPES.SOCKS && type !== PROXY_TYPES.SOCKS4) {
+ throw new ExtensionError(`FindProxyForURL: proxyDNS can only be true for SOCKS proxy servers`);
+ }
+ }
+ },
+
+ failoverTimeout(proxyData) {
+ let {failoverTimeout} = proxyData;
+ if (failoverTimeout !== undefined && (!Number.isInteger(failoverTimeout) || failoverTimeout < 1)) {
+ throw new ExtensionError(`FindProxyForURL: Invalid failover timeout: "${failoverTimeout}"`);
+ }
+ },
+
+ createProxyInfoFromData(proxyDataList, defaultProxyInfo) {
+ let {type, host, port, username, password, proxyDNS, failoverTimeout} =
+ ProxyInfoData.validate(proxyDataList.shift());
+ if (type === PROXY_TYPES.DIRECT) {
+ return defaultProxyInfo;
+ }
+ let failoverProxy = proxyDataList.length > 0 ? this.createProxyInfoFromData(proxyDataList, defaultProxyInfo) : defaultProxyInfo;
+ // When Bug 1360404 is fixed use ProxyService.newProxyInfoWithAuth() for all types.
+ if (type === PROXY_TYPES.SOCKS || type === PROXY_TYPES.SOCKS4) {
+ return ProxyService.newProxyInfoWithAuth(
+ type, host, port, username, password, proxyDNS ? TRANSPARENT_PROXY_RESOLVES_HOST : 0,
+ failoverTimeout ? failoverTimeout : PROXY_TIMEOUT_SEC, failoverProxy);
+ }
+ return ProxyService.newProxyInfo(
+ type, host, port, proxyDNS ? TRANSPARENT_PROXY_RESOLVES_HOST : 0,
+ failoverTimeout ? failoverTimeout : PROXY_TIMEOUT_SEC, failoverProxy);
+ },
+
+ /**
+ * Creates a new proxy info data object using the return value of FindProxyForURL.
+ *
+ * @param {Array<string>} rule A single proxy rule returned by FindProxyForURL.
+ * (e.g. "PROXY 1.2.3.4:8080", "SOCKS 1.1.1.1:9090" or "DIRECT")
+ * @returns {nsIProxyInfo} The proxy info to apply for the given URI.
+ */
+ parseProxyInfoDataFromPAC(rule) {
+ if (!rule) {
+ throw new ExtensionError("FindProxyForURL: Missing Proxy Rule");
+ }
+
+ let parts = rule.toLowerCase().split(/\s+/);
+ if (!parts[0] || parts.length > 2) {
+ throw new ExtensionError(`FindProxyForURL: Invalid arguments passed for proxy rule: "${rule}"`);
+ }
+ let type = parts[0];
+ let [host, port] = parts.length > 1 ? parts[1].split(":") : [];
+
+ switch (PROXY_TYPES[type.toUpperCase()]) {
+ case PROXY_TYPES.HTTP:
+ case PROXY_TYPES.HTTPS:
+ case PROXY_TYPES.SOCKS:
+ case PROXY_TYPES.SOCKS4:
+ if (!host || !port) {
+ throw new ExtensionError(`FindProxyForURL: Invalid host or port from proxy rule: "${rule}"`);
+ }
+ return {type, host, port};
+ case PROXY_TYPES.DIRECT:
+ if (host || port) {
+ throw new ExtensionError(`FindProxyForURL: Invalid argument for proxy type: "${type}"`);
+ }
+ return {type};
+ default:
+ throw new ExtensionError(`FindProxyForURL: Unrecognized proxy type: "${type}"`);
+ }
+ },
+
+};
+
class ProxyScriptContext extends BaseContext {
constructor(extension, url, contextInfo = {}) {
super("proxy_script", extension);
this.contextInfo = contextInfo;
this.extension = extension;
this.messageManager = Services.cpmm;
this.sandbox = Cu.Sandbox(this.extension.principal, {
sandboxName: `proxyscript:${extension.id}:${url}`,
@@ -99,142 +236,76 @@ class ProxyScriptContext extends BaseCon
get principal() {
return this.extension.principal;
}
get cloneScope() {
return this.sandbox;
}
+ proxyInfoFromProxyData(proxyData, defaultProxyInfo) {
+ switch (typeof proxyData) {
+ case "string":
+ let proxyRules = [];
+ try {
+ for (let result of proxyData.split(";")) {
+ proxyRules.push(ProxyInfoData.parseProxyInfoDataFromPAC(result.trim()));
+ }
+ } catch (e) {
+ // If we have valid proxies already, lets use them and just emit
+ // errors for the failovers.
+ if (proxyRules.length === 0) {
+ throw e;
+ }
+ let error = this.normalizeError(e);
+ this.extension.emit("proxy-error", {
+ message: error.message,
+ fileName: error.fileName,
+ lineNumber: error.lineNumber,
+ stack: error.stack,
+ });
+ }
+ proxyData = proxyRules;
+ // fall through
+ case "object":
+ if (Array.isArray(proxyData) && proxyData.length > 0) {
+ return ProxyInfoData.createProxyInfoFromData(proxyData, defaultProxyInfo);
+ }
+ // Not an array, fall through to error.
+ default:
+ throw new ExtensionError("FindProxyForURL: Return type must be a string or array of objects");
+ }
+ }
+
/**
* This method (which is required by the nsIProtocolProxyService interface)
* is called to apply proxy filter rules for the given URI and proxy object
* (or list of proxy objects).
*
* @param {Object} service A reference to the Protocol Proxy Service.
* @param {Object} uri The URI for which these proxy settings apply.
* @param {Object} defaultProxyInfo The proxy (or list of proxies) that
* would be used by default for the given URI. This may be null.
* @returns {Object} The proxy info to apply for the given URI.
*/
applyFilter(service, uri, defaultProxyInfo) {
- let ret;
try {
// Bug 1337001 - provide path and query components to non-https URLs.
- ret = this.FindProxyForURL(uri.prePath, uri.host, this.contextInfo);
+ let ret = this.FindProxyForURL(uri.prePath, uri.host, this.contextInfo);
+ return this.proxyInfoFromProxyData(ret, defaultProxyInfo);
} catch (e) {
let error = this.normalizeError(e);
this.extension.emit("proxy-error", {
message: error.message,
fileName: error.fileName,
lineNumber: error.lineNumber,
stack: error.stack,
});
- return defaultProxyInfo;
}
-
- if (!ret || typeof ret !== "string") {
- this.extension.emit("proxy-error", {
- message: "FindProxyForURL: Return type must be a string",
- });
- return defaultProxyInfo;
- }
-
- let rules = ret.split(";");
- let proxyInfo = this.createProxyInfo(rules);
-
- return proxyInfo || defaultProxyInfo;
- }
-
- /**
- * 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"])
- * @returns {nsIProxyInfo} The proxy info to apply for the given URI.
- */
- createProxyInfo(rules) {
- if (!rules.length) {
- return null;
- }
-
- let rule = rules[0].trim();
-
- if (!rule) {
- this.extension.emit("proxy-error", {
- message: "FindProxyForURL: Missing Proxy Rule",
- });
- return null;
- }
-
- let parts = rule.split(/\s+/);
- if (!parts[0]) {
- this.extension.emit("proxy-error", {
- message: `FindProxyForURL: Too many arguments passed for proxy rule: "${rule}"`,
- });
- return null;
- }
-
- if (parts.length > 2) {
- this.extension.emit("proxy-error", {
- message: `FindProxyForURL: Too many arguments passed for proxy rule: "${rule}"`,
- });
- return null;
- }
-
- switch (parts[0].toLowerCase()) {
- case PROXY_TYPES.PROXY:
- case PROXY_TYPES.HTTP:
- case PROXY_TYPES.HTTPS:
- case PROXY_TYPES.SOCKS:
- case PROXY_TYPES.SOCKS4:
- if (!parts[1]) {
- this.extension.emit("proxy-error", {
- message: `FindProxyForURL: Missing argument for proxy type: "${parts[0]}"`,
- });
- return null;
- }
-
- if (parts.length != 2) {
- this.extension.emit("proxy-error", {
- message: `FindProxyForURL: Too many arguments for proxy rule: "${rule}"`,
- });
- return null;
- }
-
- let [host, port] = parts[1].split(":");
- if (!host || !port) {
- this.extension.emit("proxy-error", {
- message: `FindProxyForURL: Unable to parse host and port from proxy rule: "${rule}"`,
- });
- return null;
- }
-
- let type = parts[0];
- if (parts[0].toLowerCase() == PROXY_TYPES.PROXY) {
- // PROXY_TYPES.HTTP and PROXY_TYPES.PROXY are synonyms
- type = PROXY_TYPES.HTTP;
- }
-
- let failoverProxy = this.createProxyInfo(rules.slice(1));
- return ProxyService.newProxyInfo(type, host, port, 0,
- PROXY_TIMEOUT_SEC, failoverProxy);
- case PROXY_TYPES.DIRECT:
- if (parts.length != 1) {
- this.extension.emit("proxy-error", {
- message: `FindProxyForURL: Invalid argument for proxy type: "${parts[0]}"`,
- });
- }
- return null;
- default:
- this.extension.emit("proxy-error", {
- message: `FindProxyForURL: Unrecognized proxy type: "${parts[0]}"`,
- });
- return null;
- }
+ return defaultProxyInfo;
}
/**
* Unloads the proxy filter and shuts down the sandbox.
*/
unload() {
super.unload();
ProxyService.unregisterFilter(this);
--- a/toolkit/components/extensions/test/mochitest/mochitest-common.ini
+++ b/toolkit/components/extensions/test/mochitest/mochitest-common.ini
@@ -85,17 +85,16 @@ 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_new_tab_processType.html]
[test_ext_notifications.html]
[test_ext_permission_xhr.html]
-[test_ext_proxy.html]
skip-if = os == 'android' && debug # Bug 1357635
[test_ext_redirect_jar.html]
[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]
deleted file mode 100644
--- a/toolkit/components/extensions/test/mochitest/test_ext_proxy.html
+++ /dev/null
@@ -1,181 +0,0 @@
-<!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.register("proxy_script.js").then(() => {
- browser.test.sendMessage("ready");
- });
-
- browser.test.onMessage.addListener(msg => {
- if (msg === "unregister-proxy-script") {
- browser.proxy.unregister().then(() => {
- browser.test.notifyPass("proxy");
- });
- }
- });
- },
- manifest: {
- "permissions": ["proxy"],
- },
- files: {
- "proxy_script.js": String(script).replace(/^.*?\{([^]*)\}$/, "$1"),
- },
- });
-
- await extension.startup();
- await extension.awaitMessage("ready");
-
- 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");
- }
-
- extension.sendMessage("unregister-proxy-script");
- await extension.awaitFinish("proxy");
-
- 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",
- });
-
- await testProxyScript(
- () => {
- function FindProxyForURL() {
- return "INVALID";
- }
- }, {
- message: "FindProxyForURL: Unrecognized proxy type: \"INVALID\"",
- });
-
- await testProxyScript(
- () => {
- function FindProxyForURL() {
- return "SOCKS";
- }
- }, {
- message: "FindProxyForURL: Missing argument for proxy type: \"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: Unable to parse host and port from proxy rule: \"PROXY :\"",
- });
-
- await testProxyScript(
- () => {
- function FindProxyForURL() {
- return "PROXY :8080";
- }
- }, {
- message: "FindProxyForURL: Unable to parse host and port from proxy rule: \"PROXY :8080\"",
- });
-
- await testProxyScript(
- () => {
- function FindProxyForURL() {
- return "PROXY ::";
- }
- }, {
- message: "FindProxyForURL: Unable to parse host and port from proxy rule: \"PROXY ::\"",
- });
-
- 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:\"",
- });
-
- await testProxyScript(
- () => {
- function FindProxyForURL() {
- return "DIRECT 1.2.3.4:8080";
- }
- }, {
- message: "FindProxyForURL: Invalid argument for proxy type: \"DIRECT\"",
- });
-});
-
-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,352 +4,167 @@
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) => {
+function getProxyInfo() {
+ return 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);
},
});
});
+}
+async function testProxyScript(script, expected = {}) {
+ let scriptData = String(script).replace(/^.*?\{([^]*)\}$/, "$1");
+ let extensionData = {
+ manifest: {
+ "permissions": ["proxy"],
+ },
+ background() {
+ // Some tests generate multiple errors, we'll just rely on the first.
+ let seenError = false;
+ browser.proxy.onProxyError.addListener(error => {
+ if (!seenError) {
+ browser.test.sendMessage("proxy-error-received", error);
+ seenError = true;
+ }
+ });
- if (!proxyInfo) {
- equal(proxyInfo, expected.proxyInfo, "Expected proxyInfo to be null");
+ browser.test.onMessage.addListener(msg => {
+ if (msg === "unregister-proxy-script") {
+ browser.proxy.unregister().then(() => {
+ browser.test.notifyPass("proxy");
+ });
+ }
+ });
+
+ browser.proxy.register("proxy.js").then(() => {
+ browser.test.sendMessage("ready");
+ });
+ },
+ files: {
+ "proxy.js": scriptData,
+ },
+ };
+
+ let extension = ExtensionTestUtils.loadExtension(extensionData);
+ await extension.startup();
+ await extension.awaitMessage("ready");
+
+ let errorWait = extension.awaitMessage("proxy-error-received");
+
+ let proxyInfo = await getProxyInfo();
+
+ let error = await errorWait;
+ equal(error.message, expected.message, "Correct error message received");
+ if (!expected.proxyInfo) {
+ equal(proxyInfo, null, "no proxyInfo received");
} 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;
- }
+ let {host, port, type} = expected.proxyInfo;
+ equal(proxyInfo.host, host, `Expected proxy host to be ${host}`);
+ equal(proxyInfo.port, port, `Expected proxy port to be ${port}`);
+ equal(proxyInfo.type, type, `Expected proxy type to be ${type}`);
}
-
+ if (expected.errorInfo) {
+ ok(error.fileName.includes("proxy.js"), "Error should include file name");
+ equal(error.lineNumber, 3, "Error should include line number");
+ ok(error.stack.includes("proxy.js:3:7"), "Error should include stack trace");
+ }
+ extension.sendMessage("unregister-proxy-script");
+ await extension.awaitFinish("proxy");
await extension.unload();
- script.unload();
}
-add_task(async function testUndefinedFindProxyForURL() {
- await testProxyScript({
- scriptData() { },
- }, {
- proxyInfo: null,
+add_task(async function test_invalid_FindProxyForURL_function() {
+ await testProxyScript(() => { }, {
+ message: "The proxy script must define FindProxyForURL as a function",
});
-});
-
-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";
- }
- }
- },
+ await testProxyScript(() => {
+ var FindProxyForURL = 5; // eslint-disable-line mozilla/var-only-at-top-level
}, {
- proxyInfo: null,
+ message: "The proxy script must define FindProxyForURL as a function",
});
-});
-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";
- }
- },
+ await testProxyScript(() => {
+ function FindProxyForURL() {
+ return not_defined; // eslint-disable-line no-undef
+ }
}, {
- proxyInfo: null,
+ message: "not_defined is not defined",
+ errorInfo: true,
});
-});
-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";
- }
- },
+ // The following tests will produce multiple errors.
+ await testProxyScript(() => {
+ function FindProxyForURL() {
+ return ";;;;;PROXY 1.2.3.4:8080";
+ }
}, {
- proxyInfo: {
- host: "1.2.3.4",
- port: "1080",
- type: "socks4",
- failoverProxy: null,
- },
+ message: "FindProxyForURL: Missing Proxy Rule",
});
-});
-add_task(async function testSocksReturnTypeWithHostCheck() {
- await testProxyScript({
- scriptData() {
- function FindProxyForURL(url, host) {
- if (host === "www.mozilla.org") {
- return "SOCKS 4.4.4.4:9002";
- }
- }
- },
+ // We take any valid proxy up to the error.
+ await testProxyScript(() => {
+ function FindProxyForURL() {
+ return "PROXY 1.2.3.4:8080; UNEXPECTED; SOCKS 1.2.3.4:8080";
+ }
}, {
- 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";
- }
- },
- }, {
+ message: "FindProxyForURL: Unrecognized proxy type: \"unexpected\"",
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: null,
- },
+async function getExtension(proxyResult) {
+ let extensionData = {
+ manifest: {
+ "permissions": ["proxy"],
},
- });
-});
-
-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);
- }
+ background() {
+ browser.proxy.register("proxy.js").then(() => {
+ browser.test.sendMessage("ready");
});
},
- runtimeMessage: {
- host: "www.mozilla.org",
+ files: {
+ "proxy.js": `
+ function FindProxyForURL(url, host) {
+ return ${proxyResult};
+ }`,
},
- }, {
- proxyInfo: {
- host: "1.2.3.4",
- port: "8080",
- type: "http",
- failoverProxy: null,
- },
- });
+ };
+ let extension = ExtensionTestUtils.loadExtension(extensionData);
+ await extension.startup();
+ await extension.awaitMessage("ready");
+ return extension;
+}
+
+add_task(async function test_passthrough() {
+ let ext1 = await getExtension(null);
+ let ext2 = await getExtension("\"PROXY 1.2.3.4:8888\"");
+
+ let proxyInfo = await getProxyInfo();
+
+ equal(proxyInfo.host, "1.2.3.4", `second extension won`);
+ equal(proxyInfo.port, "8888", `second extension won`);
+ equal(proxyInfo.type, "http", `second extension won`);
+
+ await ext2.unload();
+
+ proxyInfo = await getProxyInfo();
+ equal(proxyInfo, null, `expected no proxy`);
+ await ext1.unload();
});
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/xpcshell/test_proxy_scripts_results.js
@@ -0,0 +1,338 @@
+"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");
+
+const TRANSPARENT_PROXY_RESOLVES_HOST = Ci.nsIProxyInfo.TRANSPARENT_PROXY_RESOLVES_HOST;
+
+let extension;
+add_task(async function setup() {
+ let extensionData = {
+ manifest: {
+ "permissions": ["proxy"],
+ },
+ background() {
+ browser.proxy.onProxyError.addListener(error => {
+ browser.test.sendMessage("proxy-error-received", error);
+ });
+ browser.test.onMessage.addListener((message, data) => {
+ if (message === "set-proxy") {
+ browser.runtime.sendMessage(data, {toProxyScript: true}).then(response => {
+ browser.test.sendMessage("proxy-set", response);
+ });
+ }
+ });
+ browser.proxy.register("proxy.js").then(() => {
+ browser.test.sendMessage("ready");
+ });
+ },
+ files: {
+ "proxy.js": `"use strict";
+ let settings = {proxy: null};
+ function FindProxyForURL(url, host) {
+ return settings.proxy;
+ }
+ browser.runtime.onMessage.addListener((msg, sender, respond) => {
+ if (msg.proxy) {
+ settings.proxy = msg.proxy;
+ return Promise.resolve(settings.proxy);
+ }
+ });
+ `,
+ },
+ };
+ extension = ExtensionTestUtils.loadExtension(extensionData);
+ await extension.startup();
+ await extension.awaitMessage("ready");
+});
+
+async function testProxyScript(test) {
+ let {proxy, expected} = test;
+ extension.sendMessage("set-proxy", {proxy});
+ let proxyInfoSent = await extension.awaitMessage("proxy-set");
+ deepEqual(proxyInfoSent, proxy, "got back proxy data from proxy script");
+
+ let errorMsg;
+ if (expected.error) {
+ errorMsg = extension.awaitMessage("proxy-error-received");
+ }
+ 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 && pi.QueryInterface(Ci.nsIProxyInfo));
+ },
+ });
+ });
+
+ let expectedProxyInfo = expected.proxyInfo;
+ if (expected.error) {
+ equal(proxyInfo, null, "Expected proxyInfo to be null");
+ equal((await errorMsg).message, expected.error, "error received");
+ } else if (proxy == null) {
+ equal(proxyInfo, expectedProxyInfo, "proxy is direct");
+ } else {
+ for (let proxyUsed = proxyInfo; proxyUsed; proxyUsed = proxyUsed.failoverProxy) {
+ let {type, host, port, username, password, proxyDNS, failoverTimeout} = expectedProxyInfo;
+ equal(proxyUsed.host, host, `Expected proxy host to be ${host}`);
+ equal(proxyUsed.port, port, `Expected proxy port to be ${port}`);
+ equal(proxyUsed.type, type, `Expected proxy type to be ${type}`);
+ // May be null or undefined depending on use of newProxyInfoWithAuth or newProxyInfo
+ equal(proxyUsed.username || "", username || "", `Expected proxy username to be ${username}`);
+ equal(proxyUsed.password || "", password || "", `Expected proxy password to be ${password}`);
+ equal(proxyUsed.flags, proxyDNS == undefined ? 0 : proxyDNS, `Expected proxyDNS to be ${proxyDNS}`);
+ // Default timeout is 10
+ equal(proxyUsed.failoverTimeout, failoverTimeout || 10, `Expected failoverTimeout to be ${failoverTimeout}`);
+ expectedProxyInfo = expectedProxyInfo.failoverProxy;
+ }
+ }
+}
+
+add_task(async function test_pac_results() {
+ let tests = [
+ {
+ proxy: undefined,
+ expected: {
+ error: "FindProxyForURL: Return type must be a string or array of objects",
+ },
+ },
+ {
+ proxy: 5,
+ expected: {
+ error: "FindProxyForURL: Return type must be a string or array of objects",
+ },
+ },
+ {
+ proxy: "INVALID",
+ expected: {
+ error: "FindProxyForURL: Unrecognized proxy type: \"invalid\"",
+ },
+ },
+ {
+ proxy: "SOCKS",
+ expected: {
+ error: "FindProxyForURL: Invalid host or port from proxy rule: \"SOCKS\"",
+ },
+ },
+ {
+ proxy: "PROXY 1.2.3.4:8080 EXTRA",
+ expected: {
+ error: "FindProxyForURL: Invalid arguments passed for proxy rule: \"PROXY 1.2.3.4:8080 EXTRA\"",
+ },
+ },
+ {
+ proxy: "PROXY :",
+ expected: {
+ error: "FindProxyForURL: Invalid host or port from proxy rule: \"PROXY :\"",
+ },
+ },
+ {
+ proxy: "PROXY :8080",
+ expected: {
+ error: "FindProxyForURL: Invalid host or port from proxy rule: \"PROXY :8080\"",
+ },
+ },
+ {
+ proxy: "PROXY ::",
+ expected: {
+ error: "FindProxyForURL: Invalid host or port from proxy rule: \"PROXY ::\"",
+ },
+ },
+ {
+ proxy: "PROXY 1.2.3.4:",
+ expected: {
+ error: "FindProxyForURL: Invalid host or port from proxy rule: \"PROXY 1.2.3.4:\"",
+ },
+ },
+ {
+ proxy: "DIRECT 1.2.3.4:8080",
+ expected: {
+ error: "FindProxyForURL: Invalid argument for proxy type: \"direct\"",
+ },
+ },
+ {
+ proxy: ["SOCKS foo.bar:1080", {type: "http", host: "foo.bar", port: 3128}],
+ expected: {
+ error: "FindProxyForURL: Invalid proxy server type: \"undefined\"",
+ },
+ },
+ {
+ proxy: {type: "socks", host: "foo.bar", port: 1080, username: "mungosantamaria", password: "pass123"},
+ expected: {
+ error: "FindProxyForURL: Return type must be a string or array of objects",
+ },
+ },
+ {
+ proxy: [{type: "pptp", host: "foo.bar", port: 1080, username: "mungosantamaria", password: "pass123", proxyDNS: true, failoverTimeout: 3},
+ {type: "http", host: "192.168.1.1", port: 1128, username: "mungosantamaria", password: "word321"}],
+ expected: {
+ error: "FindProxyForURL: Invalid proxy server type: \"pptp\"",
+ },
+ },
+ {
+ proxy: [{type: "http", 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"}],
+ expected: {
+ error: "FindProxyForURL: Proxy server port 65536 outside range 1 to 65535",
+ },
+ },
+ {
+ proxy: [{type: "direct"}],
+ expected: {
+ proxyInfo: null,
+ },
+ },
+ {
+ proxy: "PROXY 1.2.3.4:8080",
+ expected: {
+ proxyInfo: {
+ host: "1.2.3.4",
+ port: "8080",
+ type: "http",
+ failoverProxy: null,
+ },
+ },
+ },
+ {
+ proxy: " PROXY 2.3.4.5:8080 ",
+ expected: {
+ proxyInfo: {
+ host: "2.3.4.5",
+ port: "8080",
+ type: "http",
+ failoverProxy: null,
+ },
+ },
+ },
+ {
+ proxy: "PROXY 1.2.3.4:8080; SOCKS 4.4.4.4:9000; DIRECT",
+ expected: {
+ 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,
+ },
+ },
+ },
+ },
+ },
+ {
+ proxy: [{type: "http", host: "foo.bar", port: 3128}],
+ expected: {
+ proxyInfo: {
+ host: "foo.bar",
+ port: "3128",
+ type: "http",
+ },
+ },
+ },
+ {
+ proxy: "SOCKS foo.bar:1080",
+ expected: {
+ proxyInfo: {
+ host: "foo.bar",
+ port: "1080",
+ type: "socks",
+ },
+ },
+ },
+ {
+ proxy: "SOCKS4 foo.bar:1080",
+ expected: {
+ proxyInfo: {
+ host: "foo.bar",
+ port: "1080",
+ type: "socks4",
+ },
+ },
+ },
+ {
+ proxy: [{type: "https", host: "foo.bar", port: 3128}],
+ expected: {
+ proxyInfo: {
+ host: "foo.bar",
+ port: "3128",
+ type: "https",
+ },
+ },
+ },
+ {
+ proxy: [{type: "socks", host: "foo.bar", port: 1080, username: "mungo", password: "santamaria123", proxyDNS: true, failoverTimeout: 5}],
+ expected: {
+ proxyInfo: {
+ type: "socks",
+ host: "foo.bar",
+ port: 1080,
+ username: "mungo",
+ password: "santamaria123",
+ failoverTimeout: 5,
+ failoverProxy: null,
+ proxyDNS: TRANSPARENT_PROXY_RESOLVES_HOST,
+ },
+ },
+ },
+ {
+ proxy: [{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"}],
+ expected: {
+ proxyInfo: {
+ 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: "direct",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ];
+ for (let test of tests) {
+ await testProxyScript(test);
+ }
+});
+
+add_task(async function shutdown() {
+ await extension.unload();
+});
--- a/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini
+++ b/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini
@@ -68,8 +68,9 @@ skip-if = os == "android"
[test_ext_storage_telemetry.js]
skip-if = os == "android" # checking for telemetry needs to be updated: 1384923
[test_ext_topSites.js]
skip-if = os == "android"
[test_native_messaging.js]
skip-if = os == "android"
[test_proxy_scripts.js]
skip-if = os == "linux" # bug 1393940
+[test_proxy_scripts_results.js]