summaryrefslogtreecommitdiffstats
path: root/toolkit/components/url-classifier/SafeBrowsing.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--toolkit/components/url-classifier/SafeBrowsing.sys.mjs590
1 files changed, 590 insertions, 0 deletions
diff --git a/toolkit/components/url-classifier/SafeBrowsing.sys.mjs b/toolkit/components/url-classifier/SafeBrowsing.sys.mjs
new file mode 100644
index 0000000000..c92eca9e1a
--- /dev/null
+++ b/toolkit/components/url-classifier/SafeBrowsing.sys.mjs
@@ -0,0 +1,590 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const PREF_DEBUG_ENABLED = "browser.safebrowsing.debug";
+let loggingEnabled = false;
+
+// Log only if browser.safebrowsing.debug is true
+function log(...stuff) {
+ if (!loggingEnabled) {
+ return;
+ }
+
+ var d = new Date();
+ let msg = "SafeBrowsing: " + d.toTimeString() + ": " + stuff.join(" ");
+ dump(Services.urlFormatter.trimSensitiveURLs(msg) + "\n");
+}
+
+function getLists(prefName) {
+ log("getLists: " + prefName);
+ let pref = Services.prefs.getCharPref(prefName, "");
+
+ // Splitting an empty string returns [''], we really want an empty array.
+ if (!pref) {
+ return [];
+ }
+
+ return pref.split(",").map(value => value.trim());
+}
+
+const FEATURES = [
+ {
+ name: "phishing",
+ list: ["urlclassifier.phishTable"],
+ enabled() {
+ return Services.prefs.getBoolPref(
+ "browser.safebrowsing.phishing.enabled"
+ );
+ },
+ update() {
+ return Services.prefs.getBoolPref(
+ "browser.safebrowsing.features.phishing.update",
+ this.enabled()
+ );
+ },
+ },
+ {
+ name: "malware",
+ list: ["urlclassifier.malwareTable"],
+ enabled() {
+ return Services.prefs.getBoolPref("browser.safebrowsing.malware.enabled");
+ },
+ update() {
+ return Services.prefs.getBoolPref(
+ "browser.safebrowsing.features.malware.update",
+ this.enabled()
+ );
+ },
+ },
+ {
+ name: "blockedURIs",
+ list: ["urlclassifier.blockedTable"],
+ enabled() {
+ return Services.prefs.getBoolPref(
+ "browser.safebrowsing.blockedURIs.enabled"
+ );
+ },
+ update() {
+ return Services.prefs.getBoolPref(
+ "browser.safebrowsing.features.blockedURIs.update",
+ this.enabled()
+ );
+ },
+ },
+ {
+ name: "passwords",
+ list: ["urlclassifier.passwordAllowTable"],
+ enabled() {
+ return Services.prefs.getBoolPref(
+ "browser.safebrowsing.passwords.enabled"
+ );
+ },
+ update() {
+ return Services.prefs.getBoolPref(
+ "browser.safebrowsing.features.passwords.update",
+ this.enabled()
+ );
+ },
+ },
+ {
+ name: "downloads",
+ list: [
+ "urlclassifier.downloadBlockTable",
+ "urlclassifier.downloadAllowTable",
+ ],
+ enabled() {
+ return (
+ Services.prefs.getBoolPref("browser.safebrowsing.downloads.enabled") &&
+ Services.prefs.getBoolPref("browser.safebrowsing.malware.enabled")
+ );
+ },
+ update() {
+ return Services.prefs.getBoolPref(
+ "browser.safebrowsing.features.downloads.update",
+ this.enabled()
+ );
+ },
+ },
+ {
+ name: "trackingAnnotation",
+ list: [
+ "urlclassifier.trackingAnnotationTable",
+ "urlclassifier.trackingAnnotationWhitelistTable",
+ ],
+ enabled() {
+ return Services.prefs.getBoolPref(
+ "privacy.trackingprotection.annotate_channels"
+ );
+ },
+ update() {
+ return Services.prefs.getBoolPref(
+ "browser.safebrowsing.features.trackingAnnotation.update",
+ this.enabled()
+ );
+ },
+ },
+ {
+ name: "trackingProtection",
+ list: [
+ "urlclassifier.trackingTable",
+ "urlclassifier.trackingWhitelistTable",
+ ],
+ enabled() {
+ return (
+ Services.prefs.getBoolPref("privacy.trackingprotection.enabled") ||
+ Services.prefs.getBoolPref("privacy.trackingprotection.pbmode.enabled")
+ );
+ },
+ update() {
+ return Services.prefs.getBoolPref(
+ "browser.safebrowsing.features.trackingProtection.update",
+ this.enabled()
+ );
+ },
+ },
+ {
+ name: "fingerprinting-annotation",
+ list: [
+ "urlclassifier.features.fingerprinting.annotate.blacklistTables",
+ "urlclassifier.features.fingerprinting.annotate.whitelistTables",
+ ],
+ enabled() {
+ // Annotation features are enabled by default.
+ return true;
+ },
+ update() {
+ return Services.prefs.getBoolPref(
+ "browser.safebrowsing.features.fingerprinting.annotate.update",
+ this.enabled()
+ );
+ },
+ },
+ {
+ name: "fingerprinting-protection",
+ list: [
+ "urlclassifier.features.fingerprinting.blacklistTables",
+ "urlclassifier.features.fingerprinting.whitelistTables",
+ ],
+ enabled() {
+ return Services.prefs.getBoolPref(
+ "privacy.trackingprotection.fingerprinting.enabled",
+ false
+ );
+ },
+ update() {
+ return Services.prefs.getBoolPref(
+ "browser.safebrowsing.features.fingerprinting.update",
+ this.enabled()
+ );
+ },
+ },
+ {
+ name: "cryptomining-annotation",
+ list: [
+ "urlclassifier.features.cryptomining.annotate.blacklistTables",
+ "urlclassifier.features.cryptomining.annotate.whitelistTables",
+ ],
+ enabled() {
+ // Annotation features are enabled by default.
+ return true;
+ },
+ update() {
+ return Services.prefs.getBoolPref(
+ "browser.safebrowsing.features.cryptomining.annotate.update",
+ this.enabled()
+ );
+ },
+ },
+ {
+ name: "cryptomining-protection",
+ list: [
+ "urlclassifier.features.cryptomining.blacklistTables",
+ "urlclassifier.features.cryptomining.whitelistTables",
+ ],
+ enabled() {
+ return Services.prefs.getBoolPref(
+ "privacy.trackingprotection.cryptomining.enabled",
+ false
+ );
+ },
+ update() {
+ return Services.prefs.getBoolPref(
+ "browser.safebrowsing.features.cryptomining.update",
+ this.enabled()
+ );
+ },
+ },
+ {
+ name: "socialtracking-annotation",
+ list: [
+ "urlclassifier.features.socialtracking.annotate.blacklistTables",
+ "urlclassifier.features.socialtracking.annotate.whitelistTables",
+ ],
+ enabled() {
+ // Annotation features are enabled by default.
+ return true;
+ },
+ update() {
+ return Services.prefs.getBoolPref(
+ "browser.safebrowsing.features.socialtracking.annotate.update",
+ this.enabled()
+ );
+ },
+ },
+ {
+ name: "socialtracking-protection",
+ list: [
+ "urlclassifier.features.socialtracking.blacklistTables",
+ "urlclassifier.features.socialtracking.whitelistTables",
+ ],
+ enabled() {
+ return Services.prefs.getBoolPref(
+ "privacy.trackingprotection.socialtracking.enabled",
+ false
+ );
+ },
+ update() {
+ return Services.prefs.getBoolPref(
+ "browser.safebrowsing.features.socialtracking.update",
+ this.enabled()
+ );
+ },
+ },
+ {
+ name: "emailtracking-protection",
+ list: [
+ "urlclassifier.features.emailtracking.blocklistTables",
+ "urlclassifier.features.emailtracking.allowlistTables",
+ ],
+ enabled() {
+ return (
+ Services.prefs.getBoolPref(
+ "privacy.trackingprotection.emailtracking.enabled",
+ false
+ ) ||
+ Services.prefs.getBoolPref(
+ "privacy.trackingprotection.emailtracking.pbmode.enabled",
+ false
+ )
+ );
+ },
+ update() {
+ return Services.prefs.getBoolPref(
+ "browser.safebrowsing.features.emailtracking.update",
+ this.enabled()
+ );
+ },
+ },
+ {
+ name: "emailtracking-data-collection",
+ list: [
+ "urlclassifier.features.emailtracking.datacollection.blocklistTables",
+ "urlclassifier.features.emailtracking.datacollection.allowlistTables",
+ ],
+ enabled() {
+ // Data collection features are enabled by default.
+ return true;
+ },
+ update() {
+ return Services.prefs.getBoolPref(
+ "browser.safebrowsing.features.emailtracking.datacollection.update",
+ this.enabled()
+ );
+ },
+ },
+];
+
+export var SafeBrowsing = {
+ init() {
+ if (this.initialized) {
+ log("Already initialized");
+ return;
+ }
+
+ Services.prefs.addObserver("browser.safebrowsing", this);
+ Services.prefs.addObserver("privacy.trackingprotection", this);
+ Services.prefs.addObserver("urlclassifier", this);
+
+ this.readPrefs();
+
+ this.controlUpdateChecking();
+ this.initialized = true;
+
+ log("init() finished");
+ },
+
+ registerTableWithURLs(listname) {
+ let listManager = Cc[
+ "@mozilla.org/url-classifier/listmanager;1"
+ ].getService(Ci.nsIUrlListManager);
+
+ let providerName = this.listToProvider[listname];
+ let provider = this.providers[providerName];
+
+ if (!providerName || !provider) {
+ log("No provider info found for " + listname);
+ log("Check browser.safebrowsing.provider.[google/mozilla].lists");
+ return;
+ }
+
+ if (!provider.updateURL) {
+ log("Invalid update url " + listname);
+ return;
+ }
+
+ listManager.registerTable(
+ listname,
+ providerName,
+ provider.updateURL,
+ provider.gethashURL
+ );
+ },
+
+ registerTables() {
+ this.features.forEach(feature => {
+ feature.list.forEach(table => {
+ this.registerTableWithURLs(table);
+ });
+ });
+ },
+
+ unregisterTables(obsoleteLists) {
+ let listManager = Cc[
+ "@mozilla.org/url-classifier/listmanager;1"
+ ].getService(Ci.nsIUrlListManager);
+
+ obsoleteLists.forEach(list => {
+ list.forEach(table => {
+ listManager.unregisterTable(table);
+ });
+ });
+ },
+
+ initialized: false,
+
+ features: [],
+
+ updateURL: null,
+ gethashURL: null,
+ reportURL: null,
+
+ getReportURL(kind, info) {
+ let pref;
+ switch (kind) {
+ case "Phish":
+ pref = "browser.safebrowsing.reportPhishURL";
+ break;
+
+ case "PhishMistake":
+ case "MalwareMistake":
+ pref =
+ "browser.safebrowsing.provider." +
+ info.provider +
+ ".report" +
+ kind +
+ "URL";
+ break;
+
+ default:
+ let err =
+ "SafeBrowsing getReportURL() called with unknown kind: " + kind;
+ console.error(err);
+ throw err;
+ }
+
+ // The "Phish" reports are about submitting new phishing URLs to Google so
+ // they don't have an associated list URL
+ if (kind != "Phish" && (!info.list || !info.uri)) {
+ return null;
+ }
+
+ let reportUrl = Services.urlFormatter.formatURLPref(pref);
+ // formatURLPref might return "about:blank" if getting the pref fails
+ if (reportUrl == "about:blank") {
+ reportUrl = null;
+ }
+
+ if (reportUrl) {
+ reportUrl += encodeURIComponent(info.uri);
+ }
+ return reportUrl;
+ },
+
+ observe(aSubject, aTopic, aData) {
+ // skip nextupdatetime and lastupdatetime
+ if (aData.includes("lastupdatetime") || aData.includes("nextupdatetime")) {
+ return;
+ }
+
+ if (aData == PREF_DEBUG_ENABLED) {
+ loggingEnabled = Services.prefs.getBoolPref(PREF_DEBUG_ENABLED);
+ return;
+ }
+
+ this.readPrefs();
+ },
+
+ readPrefs() {
+ loggingEnabled = Services.prefs.getBoolPref(PREF_DEBUG_ENABLED);
+ log("reading prefs");
+
+ let obsoleteLists = [];
+ // Make a copy of the original lists before we re-read the prefs.
+ if (this.initialized) {
+ obsoleteLists = this.features.map(feature => {
+ return feature.list;
+ });
+ }
+
+ // Allow to disable all feature updates with a single preference for tests.
+ let update = Services.prefs.getBoolPref(
+ "browser.safebrowsing.update.enabled",
+ true
+ );
+
+ this.features = [];
+ for (let i = 0; i < FEATURES.length; ++i) {
+ this.features[i] = {
+ name: FEATURES[i].name,
+ list: [],
+ enabled: FEATURES[i].enabled(),
+ update: FEATURES[i].update() && update,
+ };
+
+ FEATURES[i].list.forEach(pref => {
+ this.features[i].list.push(...getLists(pref));
+ });
+ }
+
+ for (let i = 0; i < obsoleteLists.length; ++i) {
+ obsoleteLists[i] = obsoleteLists[i].filter(
+ list => !this.features[i].list.includes(list)
+ );
+ }
+
+ this.updateProviderURLs();
+ this.registerTables();
+ if (obsoleteLists) {
+ this.unregisterTables(obsoleteLists);
+ }
+
+ // XXX The listManager backend gets confused if this is called before the
+ // lists are registered. So only call it here when a pref changes, and not
+ // when doing initialization. I expect to refactor this later, so pardon the hack.
+ if (this.initialized) {
+ this.controlUpdateChecking();
+ }
+ },
+
+ updateProviderURLs() {
+ try {
+ var clientID = Services.prefs.getCharPref("browser.safebrowsing.id");
+ } catch (e) {
+ clientID = Services.appinfo.name;
+ }
+
+ log("initializing safe browsing URLs, client id", clientID);
+
+ // Get the different providers
+ let branch = Services.prefs.getBranch("browser.safebrowsing.provider.");
+ let children = branch.getChildList("");
+ this.providers = {};
+ this.listToProvider = {};
+
+ for (let child of children) {
+ log("Child: " + child);
+ let prefComponents = child.split(".");
+ let providerName = prefComponents[0];
+ this.providers[providerName] = {};
+ }
+
+ if (loggingEnabled) {
+ let providerStr = "";
+ Object.keys(this.providers).forEach(function (provider) {
+ if (providerStr === "") {
+ providerStr = provider;
+ } else {
+ providerStr += ", " + provider;
+ }
+ });
+ log("Providers: " + providerStr);
+ }
+
+ Object.keys(this.providers).forEach(function (provider) {
+ if (provider == "test") {
+ return; // skip
+ }
+ let updateURL = Services.urlFormatter.formatURLPref(
+ "browser.safebrowsing.provider." + provider + ".updateURL"
+ );
+ let gethashURL = Services.urlFormatter.formatURLPref(
+ "browser.safebrowsing.provider." + provider + ".gethashURL"
+ );
+ updateURL = updateURL.replace("SAFEBROWSING_ID", clientID);
+ gethashURL = gethashURL.replace("SAFEBROWSING_ID", clientID);
+
+ // Disable updates and gethash if the Google API key is missing.
+ let googleSafebrowsingKey = Services.urlFormatter
+ .formatURL("%GOOGLE_SAFEBROWSING_API_KEY%")
+ .trim();
+ if (
+ (provider == "google" || provider == "google4") &&
+ (!googleSafebrowsingKey ||
+ googleSafebrowsingKey == "no-google-safebrowsing-api-key")
+ ) {
+ log(
+ "Missing Google SafeBrowsing API key, clearing updateURL and gethashURL."
+ );
+ updateURL = "";
+ gethashURL = "";
+ }
+
+ log("Provider: " + provider + " updateURL=" + updateURL);
+ log("Provider: " + provider + " gethashURL=" + gethashURL);
+
+ // Urls used to update DB
+ this.providers[provider].updateURL = updateURL;
+ this.providers[provider].gethashURL = gethashURL;
+
+ // Get lists this provider manages
+ let lists = getLists(
+ "browser.safebrowsing.provider." + provider + ".lists"
+ );
+ if (lists) {
+ lists.forEach(function (list) {
+ this.listToProvider[list] = provider;
+ }, this);
+ } else {
+ log("Update URL given but no lists managed for provider: " + provider);
+ }
+ }, this);
+ },
+
+ controlUpdateChecking() {
+ if (loggingEnabled) {
+ this.features.forEach(feature => {
+ log("feature " + feature.name + ":");
+ log(" enabled:" + feature.enabled);
+ log(" update:" + feature.update);
+ log(" tables:" + feature.list);
+ });
+ }
+
+ let listManager = Cc[
+ "@mozilla.org/url-classifier/listmanager;1"
+ ].getService(Ci.nsIUrlListManager);
+
+ listManager.disableAllUpdates();
+
+ this.features.forEach(feature => {
+ if (feature.update) {
+ feature.list.forEach(table => {
+ listManager.enableUpdate(table);
+ });
+ }
+ });
+
+ listManager.maybeToggleUpdateChecking();
+ },
+};