Bug 1621935 - Allow webRequest to see subresource requests in local files r=mixedpuppy
authorRob Wu <rob@robwu.nl>
Tue, 24 Mar 2020 18:58:46 +0000
changeset 520269 fec7d30f881143c9aa5ed4348bf378385ba8425a
parent 520268 b61cb05fbfc9c0091aa0945a19df81d6a7ad306a
child 520270 0e7a3ae29aa06ca711de68abbad94c37109b2320
push id37246
push useropoprus@mozilla.com
push dateWed, 25 Mar 2020 03:40:33 +0000
treeherdermozilla-central@14b59d4adc95 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmixedpuppy
bugs1621935
milestone76.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 1621935 - Allow webRequest to see subresource requests in local files r=mixedpuppy And remove the explicit "baseURL" origin check. This check was meant to ensure that extensions can always intercept requests that it generated, but changed in https://hg.mozilla.org/mozilla-central/rev/cd219dd096 by accident to allowing access to the real `jar:`/`file:`-URL that backs the `moz-extension:`-protocol handler. That mistake did not break functionality, because the check was redundant: the `moz-extension:`-origin is already explicitly added to the internal set of host permissions of an extension. This scenario is covered by the existing test_ext_webRequest_from_extension_page.js test. Differential Revision: https://phabricator.services.mozilla.com/D67735
toolkit/components/extensions/WebExtensionPolicy.h
toolkit/components/extensions/test/xpcshell/data/file_do_load_script_subresource.html
toolkit/components/extensions/test/xpcshell/test_ext_file_access.js
toolkit/components/extensions/webrequest/ChannelWrapper.cpp
--- a/toolkit/components/extensions/WebExtensionPolicy.h
+++ b/toolkit/components/extensions/WebExtensionPolicy.h
@@ -70,20 +70,21 @@ class WebExtensionPolicy final : public 
                              ErrorResult& aRv);
 
   void UnregisterContentScript(const WebExtensionContentScript& script,
                                ErrorResult& aRv);
 
   void InjectContentScripts(ErrorResult& aRv);
 
   bool CanAccessURI(const URLInfo& aURI, bool aExplicit = false,
-                    bool aCheckRestricted = true) const {
+                    bool aCheckRestricted = true,
+                    bool aAllowFilePermission = false) const {
     return (!aCheckRestricted || !IsRestrictedURI(aURI)) && mHostPermissions &&
            mHostPermissions->Matches(aURI, aExplicit) &&
-           aURI.Scheme() != nsGkAtoms::file;
+           (aURI.Scheme() != nsGkAtoms::file || aAllowFilePermission);
   }
 
   bool IsPathWebAccessible(const nsAString& aPath) const {
     return mWebAccessiblePaths.Matches(aPath);
   }
 
   bool HasPermission(const nsAtom* aPermission) const {
     return mPermissions->Contains(aPermission);
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/xpcshell/data/file_do_load_script_subresource.html
@@ -0,0 +1,9 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+<script src="http://example.net/intercept_by_webRequest.js"></script>
+</body>
+</html>
--- a/toolkit/components/extensions/test/xpcshell/test_ext_file_access.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_file_access.js
@@ -123,8 +123,71 @@ add_task(async function file_access_from
   });
 
   await extension.startup();
 
   await extension.awaitMessage("done");
 
   await extension.unload();
 });
+
+// webRequest listeners should see subresource requests from file:-principals.
+add_task(async function webRequest_script_request_from_file_principals() {
+  // Extension without file:-permission should not see the request.
+  let extensionWithoutFilePermission = ExtensionTestUtils.loadExtension({
+    manifest: {
+      permissions: ["http://example.net/", "webRequest"],
+    },
+    background() {
+      browser.webRequest.onBeforeRequest.addListener(
+        details => {
+          browser.test.fail(`Unexpected request from ${details.originUrl}`);
+        },
+        { urls: ["http://example.net/intercept_by_webRequest.js"] }
+      );
+    },
+  });
+
+  // Extension with <all_urls> (which matches the resource URL at example.net
+  // and the origin at file://*/*) can see the request.
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      permissions: ["<all_urls>", "webRequest", "webRequestBlocking"],
+      web_accessible_resources: ["testDONE.html"],
+    },
+    background() {
+      browser.webRequest.onBeforeRequest.addListener(
+        ({ originUrl }) => {
+          browser.test.assertTrue(
+            /^file:.*file_do_load_script_subresource.html/.test(originUrl),
+            `expected script to be loaded from a local file (${originUrl})`
+          );
+          let redirectUrl = browser.runtime.getURL("testDONE.html");
+          return {
+            redirectUrl: `data:text/javascript,location.href='${redirectUrl}';`,
+          };
+        },
+        { urls: ["http://example.net/intercept_by_webRequest.js"] },
+        ["blocking"]
+      );
+    },
+    files: {
+      "testDONE.html": `<!DOCTYPE html><script src="testDONE.js"></script>`,
+      "testDONE.js"() {
+        browser.test.sendMessage("webRequest_redirect_completed");
+      },
+    },
+  });
+
+  await extensionWithoutFilePermission.startup();
+  await extension.startup();
+
+  let contentPage = await ExtensionTestUtils.loadContentPage(
+    Services.io.newFileURI(
+      do_get_file("data/file_do_load_script_subresource.html")
+    ).spec
+  );
+  await extension.awaitMessage("webRequest_redirect_completed");
+  await contentPage.close();
+
+  await extension.unload();
+  await extensionWithoutFilePermission.unload();
+});
--- a/toolkit/components/extensions/webrequest/ChannelWrapper.cpp
+++ b/toolkit/components/extensions/webrequest/ChannelWrapper.cpp
@@ -567,35 +567,33 @@ bool ChannelWrapper::Matches(
     // Verify extension access to private requests
     if (isPrivate && !aExtension->PrivateBrowsingAllowed()) {
       return false;
     }
 
     bool isProxy =
         aOptions.mIsProxy && aExtension->HasPermission(nsGkAtoms::proxy);
     // Proxies are allowed access to all urls, including restricted urls.
-    if (!aExtension->CanAccessURI(urlInfo, false, !isProxy)) {
+    if (!aExtension->CanAccessURI(urlInfo, false, !isProxy, true)) {
       return false;
     }
 
     // If this isn't the proxy phase of the request, check that the extension
     // has origin permissions for origin that originated the request.
     if (!isProxy) {
       if (IsSystemLoad()) {
         return false;
       }
 
-      if (auto origin = DocumentURLInfo()) {
-        nsAutoCString baseURL;
-        aExtension->GetBaseURL(baseURL);
-
-        if (!StringBeginsWith(origin->CSpec(), baseURL) &&
-            !aExtension->CanAccessURI(*origin)) {
-          return false;
-        }
+      auto origin = DocumentURLInfo();
+      // Extensions with the file:-permission may observe requests from file:
+      // origins, because such documents can already be modified by content
+      // scripts anyway.
+      if (origin && !aExtension->CanAccessURI(*origin, false, true, true)) {
+        return false;
       }
     }
   }
 
   return true;
 }
 
 int64_t NormalizeWindowID(nsILoadInfo* aLoadInfo, uint64_t windowID) {