Bug 1271354 support moz-extension in webrequests, r?kmag
MozReview-Commit-ID: AFP68jIdHHo
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -574,16 +574,23 @@ this.ExtensionData = class {
whitelist.push(matcher);
perm = matcher.pattern;
} else if (type.api) {
this.apiNames.add(type.api);
}
this.permissions.add(perm);
}
+
+ // An extension always gets permission to its own url.
+ if (this.id) {
+ let matcher = new MatchPattern(this.getURL(), {ignorePath: true});
+ whitelist.push(matcher);
+ }
+
this.whiteListedHosts = new MatchPatternSet(whitelist);
for (let api of this.apiNames) {
this.dependencies.add(`${api}@experiments.addons.mozilla.org`);
}
return this.manifest;
}
--- a/toolkit/components/extensions/MatchPattern.cpp
+++ b/toolkit/components/extensions/MatchPattern.cpp
@@ -290,17 +290,17 @@ MatchPattern::Init(JSContext* aCx, const
if (index <= 0) {
aRv.Throw(NS_ERROR_INVALID_ARG);
return;
}
nsCOMPtr<nsIAtom> scheme = NS_AtomizeMainThread(StringHead(aPattern, index));
if (scheme == nsGkAtoms::_asterisk) {
mSchemes = AtomSet::Get<WILDCARD_SCHEMES>();
- } else if (permittedSchemes->Contains(scheme)) {
+ } else if (permittedSchemes->Contains(scheme) || scheme == nsGkAtoms::moz_extension) {
mSchemes = new AtomSet({scheme});
} else {
aRv.Throw(NS_ERROR_INVALID_ARG);
return;
}
/***************************************************************************
* Host
--- a/toolkit/components/extensions/test/mochitest/chrome.ini
+++ b/toolkit/components/extensions/test/mochitest/chrome.ini
@@ -29,16 +29,17 @@ skip-if = os == 'android' # unsupported.
skip-if = os == 'android' # Bug 1350559
[test_chrome_ext_storage_cleanup.html]
[test_chrome_ext_trackingprotection.html]
[test_chrome_ext_trustworthy_origin.html]
[test_chrome_ext_webnavigation_resolved_urls.html]
[test_chrome_ext_webrequest_background_events.html]
[test_chrome_ext_webrequest_errors.html]
[test_chrome_ext_webrequest_host_permissions.html]
+[test_chrome_ext_webrequest_mozextension.html]
[test_chrome_native_messaging_paths.html]
skip-if = os != "mac" && os != "linux"
[test_ext_cookies_expiry.html]
[test_ext_cookies_permissions_bad.html]
[test_ext_cookies_permissions_good.html]
[test_ext_cookies_containers.html]
[test_ext_jsversion.html]
[test_ext_schema.html]
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/mochitest/test_chrome_ext_webrequest_mozextension.html
@@ -0,0 +1,194 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test moz-extension protocol use</title>
+ <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script>
+ <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/ExtensionTestUtils.js"></script>
+ <script type="text/javascript" src="chrome_head.js"></script>
+ <script type="text/javascript" src="head.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+<script type="text/javascript">
+"use strict";
+
+let peakAchu;
+add_task(async function setup() {
+ peakAchu = ExtensionTestUtils.loadExtension({
+ manifest: {
+ permissions: [
+ "webRequest",
+ "<all_urls>",
+ ],
+ },
+ background() {
+ // ID for the extension in the tests. Try to observe it to ensure we cannot.
+ browser.webRequest.onBeforeRequest.addListener(details => {
+ browser.test.notifyFail(`PeakAchu onBeforeRequest ${details.url}`);
+ }, {urls: ["<all_urls>", "moz-extension://*/*"]});
+
+ browser.test.onMessage.addListener((msg, extensionUrl) => {
+ browser.test.log(`spying for ${extensionUrl}`);
+ browser.webRequest.onBeforeRequest.addListener(details => {
+ browser.test.notifyFail(`PeakAchu onBeforeRequest ${details.url}`);
+ }, {urls: [extensionUrl]});
+ });
+ },
+ });
+ await peakAchu.startup();
+});
+
+add_task(async function test_webRequest_no_mozextension_permission() {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ permissions: [
+ "webRequest",
+ "tabs",
+ "moz-extension://c9e007e0-e518-ed4c-8202-83849981dd21/*",
+ "moz-extension://*/*",
+ ],
+ },
+ background() {
+ browser.test.notifyPass("loaded");
+ },
+ });
+
+ let messages = [
+ {message: /processing permissions\.2: Value "moz-extension:\/\/c9e007e0-e518-ed4c-8202-83849981dd21\/\*"/},
+ {message: /processing permissions\.3: Value "moz-extension:\/\/\*\/\*"/},
+ ];
+
+ let waitForConsole = new Promise(resolve => {
+ SimpleTest.monitorConsole(resolve, messages);
+ });
+
+ await extension.startup();
+ await extension.awaitFinish("loaded");
+ await extension.unload();
+
+ SimpleTest.endMonitorConsole();
+ await waitForConsole;
+});
+
+add_task(async function test_webRequest_mozextension_fetch() {
+ function background() {
+ let page = browser.extension.getURL("fetched.html");
+ browser.webRequest.onBeforeRequest.addListener(details => {
+ browser.test.assertEq(details.url, page, "got correct url in onBeforeRequest");
+ browser.test.sendMessage("request-started");
+ }, {urls: [browser.extension.getURL("*")]}, ["blocking"]);
+ browser.webRequest.onCompleted.addListener(details => {
+ browser.test.assertEq(details.url, page, "got correct url in onCompleted");
+ browser.test.sendMessage("request-complete");
+ }, {urls: [browser.extension.getURL("*")]});
+
+ browser.test.onMessage.addListener((msg, data) => {
+ fetch(page).then(() => {
+ browser.test.notifyPass("fetch success");
+ browser.test.sendMessage("done");
+ }, () => {
+ browser.test.fail("fetch failed");
+ browser.test.sendMessage("done");
+ });
+ });
+ browser.test.sendMessage("extensionUrl", browser.extension.getURL("*"));
+ }
+
+ // Use webrequest to monitor moz-extension:// requests
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ permissions: [
+ "webRequest",
+ "tabs",
+ "<all_urls>",
+ ],
+ },
+ files: {
+ "fetched.html": `
+ <!DOCTYPE html>
+ <html>
+ <head>
+ <meta charset="utf-8">
+ </head>
+ <body>
+ <h1>moz-extension file</h1>
+ </body>
+ </html>
+ `.trim(),
+ },
+ background,
+ });
+
+ await extension.startup();
+ // send the url for this extension to the monitoring extension
+ peakAchu.sendMessage("extensionUrl", await extension.awaitMessage("extensionUrl"));
+
+ extension.sendMessage("testFetch");
+ await extension.awaitMessage("request-started");
+ await extension.awaitMessage("request-complete");
+ await extension.awaitMessage("done");
+
+ await extension.unload();
+});
+
+add_task(async function test_webRequest_mozextension_tab_query() {
+ function background() {
+ browser.test.sendMessage("extensionUrl", browser.extension.getURL("*"));
+ let page = browser.extension.getURL("tab.html");
+
+ async function onUpdated(tabId, tabInfo, tab) {
+ if (tabInfo.status !== "complete") {
+ return;
+ }
+ browser.test.log(`tab created ${tabId} ${JSON.stringify(tabInfo)} ${tab.url}`);
+ let tabs = await browser.tabs.query({url: browser.extension.getURL("*")});
+ browser.test.assertEq(1, tabs.length, "got one tab");
+ browser.test.assertEq(tabs.length && tabs[0].id, tab.id, "got the correct tab");
+ browser.test.assertEq(tabs.length && tabs[0].url, page, "got correct url in tab");
+ browser.tabs.remove(tabId);
+ browser.tabs.onUpdated.removeListener(onUpdated);
+ browser.test.sendMessage("tabs-done");
+ }
+ browser.tabs.onUpdated.addListener(onUpdated);
+ browser.tabs.create({url: page});
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ permissions: [
+ "webRequest",
+ "tabs",
+ "<all_urls>",
+ ],
+ },
+ files: {
+ "tab.html": `
+ <!DOCTYPE html>
+ <html>
+ <head>
+ <meta charset="utf-8">
+ </head>
+ <body>
+ <h1>moz-extension file</h1>
+ </body>
+ </html>
+ `.trim(),
+ },
+ background,
+ });
+
+ await extension.startup();
+ peakAchu.sendMessage("extensionUrl", await extension.awaitMessage("extensionUrl"));
+ await extension.awaitMessage("tabs-done");
+ await extension.unload();
+});
+
+add_task(async function teardown() {
+ await peakAchu.unload();
+});
+</script>
+
+</body>
+</html>
--- a/toolkit/components/extensions/test/xpcshell/test_ext_permissions.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_permissions.js
@@ -53,16 +53,18 @@ add_task(async function test_permissions
});
await AddonTestUtils.promiseStartupManager();
function background() {
browser.test.onMessage.addListener(async (method, arg) => {
if (method == "getAll") {
let perms = await browser.permissions.getAll();
+ let url = browser.extension.getURL("*");
+ perms.origins = perms.origins.filter(i => i != url);
browser.test.sendMessage("getAll.result", perms);
} else if (method == "contains") {
let result = await browser.permissions.contains(arg);
browser.test.sendMessage("contains.result", result);
} else if (method == "request") {
try {
let result = await browser.permissions.request(arg);
browser.test.sendMessage("request.result", {status: "success", result});
@@ -198,16 +200,18 @@ add_task(async function test_permissions
add_task(async function test_startup() {
async function background() {
browser.test.onMessage.addListener(async (perms) => {
await browser.permissions.request(perms);
browser.test.sendMessage("requested");
});
let all = await browser.permissions.getAll();
+ let url = browser.extension.getURL("*");
+ all.origins = all.origins.filter(i => i != url);
browser.test.sendMessage("perms", all);
}
const PERMS1 = {
permissions: ["clipboardRead", "tabs"],
};
const PERMS2 = {
origins: ["https://site2.com/*"],
--- a/toolkit/modules/addons/MatchPattern.jsm
+++ b/toolkit/modules/addons/MatchPattern.jsm
@@ -14,17 +14,17 @@ XPCOMUtils.defineLazyModuleGetter(this,
XPCOMUtils.defineLazyModuleGetter(this, "Services",
"resource://gre/modules/Services.jsm");
this.EXPORTED_SYMBOLS = ["MatchPattern", "MatchGlobs", "MatchURLFilters"];
/* globals MatchPattern, MatchGlobs */
const PERMITTED_SCHEMES = ["http", "https", "file", "ftp", "data"];
-const PERMITTED_SCHEMES_REGEXP = PERMITTED_SCHEMES.join("|");
+const PERMITTED_SCHEMES_REGEXP = [...PERMITTED_SCHEMES, "moz-extension"].join("|");
// The basic RE for matching patterns
const PATTERN_REGEXP = new RegExp(`^(${PERMITTED_SCHEMES_REGEXP}|\\*)://(\\*|\\*\\.[^*/]+|[^*/]+|)(/.*)$`);
// The schemes/protocols implied by a pattern that starts with *://
const WILDCARD_SCHEMES = ["http", "https"];
// This function converts a glob pattern (containing * and possibly ?
@@ -68,16 +68,23 @@ function SingleMatchPattern(pat) {
// We allow the host to be empty for file URLs.
if (match[2] == "" && this.schemes[0] != "file") {
Cu.reportError(`Invalid match pattern: '${pat}'`);
this.schemes = [];
return;
}
+ // We disallow the host to be * for moz-extension URLs.
+ if (match[2] == "*" && this.schemes[0] == "moz-extension") {
+ Cu.reportError(`Invalid match pattern: '${pat}'`);
+ this.schemes = [];
+ return;
+ }
+
this.host = match[2];
this.hostMatch = this.getHostMatcher(match[2]);
let pathMatch = globToRegexp(match[3], false);
this.pathMatch = pathMatch.test.bind(pathMatch);
}
}
--- a/toolkit/mozapps/extensions/test/xpcshell/test_ext_management.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_ext_management.js
@@ -4,16 +4,18 @@ add_task(async function setup() {
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "48", "48");
startupManager();
});
/* eslint-disable no-undef */
// Shared background function for getSelf tests
function backgroundGetSelf() {
browser.management.getSelf().then(extInfo => {
+ let url = browser.extension.getURL("*");
+ extInfo.hostPermissions = extInfo.hostPermissions.filter(i => i != url);
browser.test.sendMessage("management-getSelf", extInfo);
}, error => {
browser.test.notifyFail(`getSelf rejected with error: ${error}`);
});
}
/* eslint-enable no-undef */
add_task(async function test_management_get_self_complete() {