Bug 1271354 support moz-extension in webrequests, r=kmag
authorShane Caraveo <scaraveo@mozilla.com>
Thu, 06 Jul 2017 13:08:31 -0700
changeset 367735 60823f6c03f28519734dea56419d389e55700e1e
parent 367734 0fd21ff3bc0c745e8e2ca8d2ca5961090ad1efed
child 367736 a67e2718b0c535d6f3f863559108001e24cc397a
push id32142
push usercbook@mozilla.com
push dateFri, 07 Jul 2017 08:34:50 +0000
treeherdermozilla-central@78ff4c023b6a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskmag
bugs1271354
milestone56.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1271354 support moz-extension in webrequests, r=kmag MozReview-Commit-ID: AFP68jIdHHo
toolkit/components/extensions/Extension.jsm
toolkit/components/extensions/MatchPattern.cpp
toolkit/components/extensions/test/mochitest/chrome.ini
toolkit/components/extensions/test/mochitest/test_chrome_ext_webrequest_mozextension.html
toolkit/components/extensions/test/xpcshell/test_ext_permissions.js
toolkit/modules/addons/MatchPattern.jsm
toolkit/mozapps/extensions/test/xpcshell/test_ext_management.js
--- 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() {