Bug 1450309 - Switch WebsiteFilter to nsIContentPolicy and allow blocking local files. r=emalysz
☠☠ backed out by efdf5255c248 ☠ ☠
authorMichael Kaply <mozilla@kaply.com>
Wed, 30 Sep 2020 18:12:24 +0000
changeset 550965 7fae6ea289bde450c37ca6bb1e3dfa404e026572
parent 550964 14c35856cea4a26f80a6f6d7dcbd13c7f7ff07fd
child 550966 7f034e0dfacecea6f9ff7c2065b5ebbe48bfea0a
push id127583
push usermozilla@kaply.com
push dateWed, 30 Sep 2020 21:34:18 +0000
treeherderautoland@7fae6ea289bd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersemalysz
bugs1450309
milestone83.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 1450309 - Switch WebsiteFilter to nsIContentPolicy and allow blocking local files. r=emalysz Differential Revision: https://phabricator.services.mozilla.com/D91488
browser/components/enterprisepolicies/Policies.jsm
browser/components/enterprisepolicies/helpers/WebsiteFilter.jsm
browser/components/enterprisepolicies/tests/browser/browser_policy_websitefilter.js
--- a/browser/components/enterprisepolicies/Policies.jsm
+++ b/browser/components/enterprisepolicies/Policies.jsm
@@ -1992,20 +1992,17 @@ var Policies = {
       if ("SkipOnboarding") {
         setAndLockPref("browser.aboutwelcome.enabled", false);
       }
     },
   },
 
   WebsiteFilter: {
     onBeforeUIStartup(manager, param) {
-      this.filter = new WebsiteFilter(
-        param.Block || [],
-        param.Exceptions || []
-      );
+      WebsiteFilter.init(param.Block || [], param.Exceptions || []);
     },
   },
 };
 
 /*
  * ====================
  * = HELPER FUNCTIONS =
  * ====================
--- a/browser/components/enterprisepolicies/helpers/WebsiteFilter.jsm
+++ b/browser/components/enterprisepolicies/helpers/WebsiteFilter.jsm
@@ -13,18 +13,18 @@
  *
  * The exceptions list takes the same as input. This list opens up
  * exceptions for rules on the blocklist that might be too strict.
  *
  * In addition to that, this allows the user to create a whitelist approach,
  * by using the special "<all_urls>" pattern for the blocklist, and then
  * adding all whitelisted websites on the exceptions list.
  *
- * Note that this module only blocks top-level website navigations. It doesn't
- * block any other accesses to these urls: image tags, scripts, XHR, etc.,
+ * Note that this module only blocks top-level website navigations and embeds.
+ * It does not block any other accesses to these urls: image tags, scripts, XHR, etc.,
  * because that could cause unexpected breakage. This is a policy to block
  * users from visiting certain websites, and not from blocking any network
  * connections to those websites. If the admin is looking for that, the recommended
  * way is to configure that with extensions or through a company firewall.
  */
 
 const { XPCOMUtils } = ChromeUtils.import(
   "resource://gre/modules/XPCOMUtils.jsm"
@@ -43,78 +43,91 @@ XPCOMUtils.defineLazyGetter(this, "log",
     // messages during development. See LOG_LEVELS in Console.jsm for details.
     maxLogLevel: "error",
     maxLogLevelPref: PREF_LOGLEVEL,
   });
 });
 
 var EXPORTED_SYMBOLS = ["WebsiteFilter"];
 
-function WebsiteFilter(blocklist, exceptionlist) {
-  let blockArray = [],
-    exceptionArray = [];
+let WebsiteFilter = {
+  init(blocklist, exceptionlist) {
+    let blockArray = [],
+      exceptionArray = [];
 
-  for (let i = 0; i < blocklist.length && i < LIST_LENGTH_LIMIT; i++) {
-    try {
-      let pattern = new MatchPattern(blocklist[i]);
-      blockArray.push(pattern);
-      log.debug(`Pattern added to WebsiteFilter. Block: ${blocklist[i]}`);
-    } catch (e) {
-      log.error(`Invalid pattern on WebsiteFilter. Block: ${blocklist[i]}`);
+    for (let i = 0; i < blocklist.length && i < LIST_LENGTH_LIMIT; i++) {
+      try {
+        let pattern = new MatchPattern(blocklist[i].toLowerCase());
+        blockArray.push(pattern);
+        log.debug(`Pattern added to WebsiteFilter. Block: ${blocklist[i]}`);
+      } catch (e) {
+        log.error(`Invalid pattern on WebsiteFilter. Block: ${blocklist[i]}`);
+      }
     }
-  }
+
+    this._blockPatterns = new MatchPatternSet(blockArray);
 
-  this._blockPatterns = new MatchPatternSet(blockArray);
+    for (let i = 0; i < exceptionlist.length && i < LIST_LENGTH_LIMIT; i++) {
+      try {
+        let pattern = new MatchPattern(exceptionlist[i].toLowerCase());
+        exceptionArray.push(pattern);
+        log.debug(
+          `Pattern added to WebsiteFilter. Exception: ${exceptionlist[i]}`
+        );
+      } catch (e) {
+        log.error(
+          `Invalid pattern on WebsiteFilter. Exception: ${exceptionlist[i]}`
+        );
+      }
+    }
 
-  for (let i = 0; i < exceptionlist.length && i < LIST_LENGTH_LIMIT; i++) {
-    try {
-      let pattern = new MatchPattern(exceptionlist[i]);
-      exceptionArray.push(pattern);
-      log.debug(
-        `Pattern added to WebsiteFilter. Exception: ${exceptionlist[i]}`
+    if (exceptionArray.length) {
+      this._exceptionsPatterns = new MatchPatternSet(exceptionArray);
+    }
+
+    let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+
+    if (!registrar.isContractIDRegistered(this.contractID)) {
+      registrar.registerFactory(
+        this.classID,
+        this.classDescription,
+        this.contractID,
+        this
       );
-    } catch (e) {
-      log.error(
-        `Invalid pattern on WebsiteFilter. Exception: ${exceptionlist[i]}`
+
+      Services.catMan.addCategoryEntry(
+        "content-policy",
+        this.contractID,
+        this.contractID,
+        false,
+        true
       );
     }
-  }
-
-  if (exceptionArray.length) {
-    this._exceptionsPatterns = new MatchPatternSet(exceptionArray);
-  }
-
-  Services.obs.addObserver(this, "http-on-modify-request", true);
-}
-
-WebsiteFilter.prototype = {
-  QueryInterface: ChromeUtils.generateQI([
-    "nsIObserver",
-    "nsISupportsWeakReference",
-  ]),
+  },
 
-  observe(subject, topic, data) {
-    let channel,
-      isDocument = false;
-    try {
-      channel = subject.QueryInterface(Ci.nsIHttpChannel);
-      isDocument = channel.isDocument;
-    } catch (e) {
-      return;
-    }
-
-    // Only filter document accesses
-    if (!isDocument) {
-      return;
-    }
-
-    if (this._blockPatterns.matches(channel.URI)) {
-      if (
-        !this._exceptionsPatterns ||
-        !this._exceptionsPatterns.matches(channel.URI)
-      ) {
-        // NS_ERROR_BLOCKED_BY_POLICY displays the error message
-        // designed for policy-related blocks.
-        channel.cancel(Cr.NS_ERROR_BLOCKED_BY_POLICY);
+  shouldLoad(contentLocation, loadInfo, mimeTypeGuess) {
+    let contentType = loadInfo.externalContentPolicyType;
+    if (
+      contentType == Ci.nsIContentPolicy.TYPE_DOCUMENT ||
+      contentType == Ci.nsIContentPolicy.TYPE_SUBDOCUMENT
+    ) {
+      if (this._blockPatterns.matches(contentLocation.spec.toLowerCase())) {
+        if (
+          !this._exceptionsPatterns ||
+          !this._exceptionsPatterns.matches(contentLocation.spec.toLowerCase())
+        ) {
+          return Ci.nsIContentPolicy.REJECT_POLICY;
+        }
       }
     }
+    return Ci.nsIContentPolicy.ACCEPT;
+  },
+  shouldProcess(contentLocation, loadInfo, mimeTypeGuess) {
+    return Ci.nsIContentPolicy.ACCEPT;
+  },
+  classDescription: "Policy Engine File Content Policy",
+  contractID: "@mozilla-org/policy-engine-file-content-policy-service;1",
+  classID: Components.ID("{c0bbb557-813e-4e25-809d-b46a531a258f}"),
+  QueryInterface: ChromeUtils.generateQI(["nsIContentPolicy"]),
+  createInstance(outer, iid) {
+    return this.QueryInterface(iid);
   },
 };
--- a/browser/components/enterprisepolicies/tests/browser/browser_policy_websitefilter.js
+++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_websitefilter.js
@@ -2,21 +2,47 @@
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 "use strict";
 
 const SUPPORT_FILES_PATH =
   "http://mochi.test:8888/browser/browser/components/enterprisepolicies/tests/browser";
 const BLOCKED_PAGE = `${SUPPORT_FILES_PATH}/policy_websitefilter_block.html`;
 const EXCEPTION_PAGE = `${SUPPORT_FILES_PATH}/policy_websitefilter_exception.html`;
 
-add_task(async function test() {
+add_task(async function test_http() {
   await setupPolicyEngineWithJson({
     policies: {
       WebsiteFilter: {
         Block: ["*://mochi.test/*policy_websitefilter_*"],
         Exceptions: ["*://mochi.test/*_websitefilter_exception*"],
       },
     },
   });
 
   await checkBlockedPage(BLOCKED_PAGE, true);
   await checkBlockedPage(EXCEPTION_PAGE, false);
 });
+
+add_task(async function test_http_mixed_case() {
+  await setupPolicyEngineWithJson({
+    policies: {
+      WebsiteFilter: {
+        Block: ["*://mochi.test/*policy_websitefilter_*"],
+        Exceptions: ["*://mochi.test/*_websitefilter_exception*"],
+      },
+    },
+  });
+
+  await checkBlockedPage(BLOCKED_PAGE.toUpperCase(), true);
+  await checkBlockedPage(EXCEPTION_PAGE.toUpperCase(), false);
+});
+
+add_task(async function test_file() {
+  await setupPolicyEngineWithJson({
+    policies: {
+      WebsiteFilter: {
+        Block: ["file:///*"],
+      },
+    },
+  });
+
+  await checkBlockedPage("file:///this_should_be_blocked", true);
+});