Bug 1450309 - Switch WebsiteFilter to nsIContentPolicy and allow blocking local files. r=emalysz, a=RyanVM
--- 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);
+});