Bug 1450309 - Switch WebsiteFilter to nsIContentPolicy and allow blocking local files. r=emalysz, a=RyanVM FIREFOX_78_5_0esr_BUILD1 FIREFOX_78_5_0esr_RELEASE
authorMichael Kaply <mozilla@kaply.com>
Mon, 09 Nov 2020 15:37:59 -0600
changeset 600193 d482421dc3fe22020f2c57f573ed902f6d302ffa
parent 600192 26504829acfdf95268ad23572714a452d8b66e5b
child 600194 aba16c583e0c290ce18024295673f91bae4fbab8
push id124
push userryanvm@gmail.com
push dateTue, 10 Nov 2020 00:15:00 +0000
treeherdermozilla-esr78@d482421dc3fe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersemalysz, RyanVM
bugs1450309
milestone78.5.0
Bug 1450309 - Switch WebsiteFilter to nsIContentPolicy and allow blocking local files. r=emalysz, a=RyanVM
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
@@ -1979,20 +1979,17 @@ var Policies = {
         setAndLockPref("trailhead.firstrun.branches", "nofirstrun-empty");
         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([
-    Ci.nsIObserver,
-    Ci.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
@@ -1,22 +1,51 @@
 /* Any copyright is dedicated to the Public Domain.
  * 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`;
+  "http://mochi.test:8888/browser/browser/components/enterprisepolicies/tests/browser/";
+const BLOCKED_PAGE = "policy_websitefilter_block.html";
+const EXCEPTION_PAGE = "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);
+  await checkBlockedPage(SUPPORT_FILES_PATH + BLOCKED_PAGE, true);
+  await checkBlockedPage(SUPPORT_FILES_PATH + 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(SUPPORT_FILES_PATH + BLOCKED_PAGE.toUpperCase(), true);
+  await checkBlockedPage(
+    SUPPORT_FILES_PATH + EXCEPTION_PAGE.toUpperCase(),
+    false
+  );
+});
+
+add_task(async function test_file() {
+  await setupPolicyEngineWithJson({
+    policies: {
+      WebsiteFilter: {
+        Block: ["file:///*"],
+      },
+    },
+  });
+
+  await checkBlockedPage("file:///this_should_be_blocked", true);
+});