michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: const Cc = Components.classes; michael@0: const Ci = Components.interfaces; michael@0: const Cr = Components.results; michael@0: michael@0: Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: Components.utils.import("resource://gre/modules/AddonManager.jsm"); michael@0: Components.utils.import("resource://gre/modules/Services.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", michael@0: "resource://gre/modules/FileUtils.jsm"); michael@0: XPCOMUtils.defineLazyModuleGetter(this, "UpdateChannel", michael@0: "resource://gre/modules/UpdateChannel.jsm"); michael@0: XPCOMUtils.defineLazyModuleGetter(this, "OS", michael@0: "resource://gre/modules/osfile.jsm"); michael@0: XPCOMUtils.defineLazyModuleGetter(this, "Task", michael@0: "resource://gre/modules/Task.jsm"); michael@0: michael@0: const TOOLKIT_ID = "toolkit@mozilla.org" michael@0: const KEY_PROFILEDIR = "ProfD"; michael@0: const KEY_APPDIR = "XCurProcD"; michael@0: const FILE_BLOCKLIST = "blocklist.xml"; michael@0: const PREF_BLOCKLIST_LASTUPDATETIME = "app.update.lastUpdateTime.blocklist-background-update-timer"; michael@0: const PREF_BLOCKLIST_URL = "extensions.blocklist.url"; michael@0: const PREF_BLOCKLIST_ITEM_URL = "extensions.blocklist.itemURL"; michael@0: const PREF_BLOCKLIST_ENABLED = "extensions.blocklist.enabled"; michael@0: const PREF_BLOCKLIST_INTERVAL = "extensions.blocklist.interval"; michael@0: const PREF_BLOCKLIST_LEVEL = "extensions.blocklist.level"; michael@0: const PREF_BLOCKLIST_PINGCOUNTTOTAL = "extensions.blocklist.pingCountTotal"; michael@0: const PREF_BLOCKLIST_PINGCOUNTVERSION = "extensions.blocklist.pingCountVersion"; michael@0: const PREF_BLOCKLIST_SUPPRESSUI = "extensions.blocklist.suppressUI"; michael@0: const PREF_PLUGINS_NOTIFYUSER = "plugins.update.notifyUser"; michael@0: const PREF_GENERAL_USERAGENT_LOCALE = "general.useragent.locale"; michael@0: const PREF_APP_DISTRIBUTION = "distribution.id"; michael@0: const PREF_APP_DISTRIBUTION_VERSION = "distribution.version"; michael@0: const PREF_EM_LOGGING_ENABLED = "extensions.logging.enabled"; michael@0: const XMLURI_BLOCKLIST = "http://www.mozilla.org/2006/addons-blocklist"; michael@0: const XMLURI_PARSE_ERROR = "http://www.mozilla.org/newlayout/xml/parsererror.xml" michael@0: const UNKNOWN_XPCOM_ABI = "unknownABI"; michael@0: const URI_BLOCKLIST_DIALOG = "chrome://mozapps/content/extensions/blocklist.xul" michael@0: const DEFAULT_SEVERITY = 3; michael@0: const DEFAULT_LEVEL = 2; michael@0: const MAX_BLOCK_LEVEL = 3; michael@0: const SEVERITY_OUTDATED = 0; michael@0: const VULNERABILITYSTATUS_NONE = 0; michael@0: const VULNERABILITYSTATUS_UPDATE_AVAILABLE = 1; michael@0: const VULNERABILITYSTATUS_NO_UPDATE = 2; michael@0: michael@0: const EXTENSION_BLOCK_FILTERS = ["id", "name", "creator", "homepageURL", "updateURL"]; michael@0: michael@0: var gLoggingEnabled = null; michael@0: var gBlocklistEnabled = true; michael@0: var gBlocklistLevel = DEFAULT_LEVEL; michael@0: michael@0: XPCOMUtils.defineLazyServiceGetter(this, "gConsole", michael@0: "@mozilla.org/consoleservice;1", michael@0: "nsIConsoleService"); michael@0: michael@0: XPCOMUtils.defineLazyServiceGetter(this, "gVersionChecker", michael@0: "@mozilla.org/xpcom/version-comparator;1", michael@0: "nsIVersionComparator"); michael@0: michael@0: XPCOMUtils.defineLazyGetter(this, "gPref", function bls_gPref() { michael@0: return Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefService). michael@0: QueryInterface(Ci.nsIPrefBranch); michael@0: }); michael@0: michael@0: XPCOMUtils.defineLazyGetter(this, "gApp", function bls_gApp() { michael@0: return Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo). michael@0: QueryInterface(Ci.nsIXULRuntime); michael@0: }); michael@0: michael@0: XPCOMUtils.defineLazyGetter(this, "gABI", function bls_gABI() { michael@0: let abi = null; michael@0: try { michael@0: abi = gApp.XPCOMABI; michael@0: } michael@0: catch (e) { michael@0: LOG("BlockList Global gABI: XPCOM ABI unknown."); michael@0: } michael@0: #ifdef XP_MACOSX michael@0: // Mac universal build should report a different ABI than either macppc michael@0: // or mactel. michael@0: let macutils = Cc["@mozilla.org/xpcom/mac-utils;1"]. michael@0: getService(Ci.nsIMacUtils); michael@0: michael@0: if (macutils.isUniversalBinary) michael@0: abi += "-u-" + macutils.architecturesInBinary; michael@0: #endif michael@0: return abi; michael@0: }); michael@0: michael@0: XPCOMUtils.defineLazyGetter(this, "gOSVersion", function bls_gOSVersion() { michael@0: let osVersion; michael@0: let sysInfo = Cc["@mozilla.org/system-info;1"]. michael@0: getService(Ci.nsIPropertyBag2); michael@0: try { michael@0: osVersion = sysInfo.getProperty("name") + " " + sysInfo.getProperty("version"); michael@0: } michael@0: catch (e) { michael@0: LOG("BlockList Global gOSVersion: OS Version unknown."); michael@0: } michael@0: michael@0: if (osVersion) { michael@0: try { michael@0: osVersion += " (" + sysInfo.getProperty("secondaryLibrary") + ")"; michael@0: } michael@0: catch (e) { michael@0: // Not all platforms have a secondary widget library, so an error is nothing to worry about. michael@0: } michael@0: osVersion = encodeURIComponent(osVersion); michael@0: } michael@0: return osVersion; michael@0: }); michael@0: michael@0: // shared code for suppressing bad cert dialogs michael@0: XPCOMUtils.defineLazyGetter(this, "gCertUtils", function bls_gCertUtils() { michael@0: let temp = { }; michael@0: Components.utils.import("resource://gre/modules/CertUtils.jsm", temp); michael@0: return temp; michael@0: }); michael@0: michael@0: function getObserverService() { michael@0: return Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService); michael@0: } michael@0: michael@0: /** michael@0: * Logs a string to the error console. michael@0: * @param string michael@0: * The string to write to the error console.. michael@0: */ michael@0: function LOG(string) { michael@0: if (gLoggingEnabled) { michael@0: dump("*** " + string + "\n"); michael@0: gConsole.logStringMessage(string); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Gets a preference value, handling the case where there is no default. michael@0: * @param func michael@0: * The name of the preference function to call, on nsIPrefBranch michael@0: * @param preference michael@0: * The name of the preference michael@0: * @param defaultValue michael@0: * The default value to return in the event the preference has michael@0: * no setting michael@0: * @returns The value of the preference, or undefined if there was no michael@0: * user or default value. michael@0: */ michael@0: function getPref(func, preference, defaultValue) { michael@0: try { michael@0: return gPref[func](preference); michael@0: } michael@0: catch (e) { michael@0: } michael@0: return defaultValue; michael@0: } michael@0: michael@0: /** michael@0: * Constructs a URI to a spec. michael@0: * @param spec michael@0: * The spec to construct a URI to michael@0: * @returns The nsIURI constructed. michael@0: */ michael@0: function newURI(spec) { michael@0: var ioServ = Cc["@mozilla.org/network/io-service;1"]. michael@0: getService(Ci.nsIIOService); michael@0: return ioServ.newURI(spec, null, null); michael@0: } michael@0: michael@0: // Restarts the application checking in with observers first michael@0: function restartApp() { michael@0: // Notify all windows that an application quit has been requested. michael@0: var os = Cc["@mozilla.org/observer-service;1"]. michael@0: getService(Ci.nsIObserverService); michael@0: var cancelQuit = Cc["@mozilla.org/supports-PRBool;1"]. michael@0: createInstance(Ci.nsISupportsPRBool); michael@0: os.notifyObservers(cancelQuit, "quit-application-requested", null); michael@0: michael@0: // Something aborted the quit process. michael@0: if (cancelQuit.data) michael@0: return; michael@0: michael@0: var as = Cc["@mozilla.org/toolkit/app-startup;1"]. michael@0: getService(Ci.nsIAppStartup); michael@0: as.quit(Ci.nsIAppStartup.eRestart | Ci.nsIAppStartup.eAttemptQuit); michael@0: } michael@0: michael@0: /** michael@0: * Checks whether this blocklist element is valid for the current OS and ABI. michael@0: * If the element has an "os" attribute then the current OS must appear in michael@0: * its comma separated list for the element to be valid. Similarly for the michael@0: * xpcomabi attribute. michael@0: */ michael@0: function matchesOSABI(blocklistElement) { michael@0: if (blocklistElement.hasAttribute("os")) { michael@0: var choices = blocklistElement.getAttribute("os").split(","); michael@0: if (choices.length > 0 && choices.indexOf(gApp.OS) < 0) michael@0: return false; michael@0: } michael@0: michael@0: if (blocklistElement.hasAttribute("xpcomabi")) { michael@0: choices = blocklistElement.getAttribute("xpcomabi").split(","); michael@0: if (choices.length > 0 && choices.indexOf(gApp.XPCOMABI) < 0) michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: /** michael@0: * Gets the current value of the locale. It's possible for this preference to michael@0: * be localized, so we have to do a little extra work here. Similar code michael@0: * exists in nsHttpHandler.cpp when building the UA string. michael@0: */ michael@0: function getLocale() { michael@0: try { michael@0: // Get the default branch michael@0: var defaultPrefs = gPref.getDefaultBranch(null); michael@0: return defaultPrefs.getComplexValue(PREF_GENERAL_USERAGENT_LOCALE, michael@0: Ci.nsIPrefLocalizedString).data; michael@0: } catch (e) {} michael@0: michael@0: return gPref.getCharPref(PREF_GENERAL_USERAGENT_LOCALE); michael@0: } michael@0: michael@0: /* Get the distribution pref values, from defaults only */ michael@0: function getDistributionPrefValue(aPrefName) { michael@0: var prefValue = "default"; michael@0: michael@0: var defaults = gPref.getDefaultBranch(null); michael@0: try { michael@0: prefValue = defaults.getCharPref(aPrefName); michael@0: } catch (e) { michael@0: // use default when pref not found michael@0: } michael@0: michael@0: return prefValue; michael@0: } michael@0: michael@0: /** michael@0: * Parse a string representation of a regular expression. Needed because we michael@0: * use the /pattern/flags form (because it's detectable), which is only michael@0: * supported as a literal in JS. michael@0: * michael@0: * @param aStr michael@0: * String representation of regexp michael@0: * @return RegExp instance michael@0: */ michael@0: function parseRegExp(aStr) { michael@0: let lastSlash = aStr.lastIndexOf("/"); michael@0: let pattern = aStr.slice(1, lastSlash); michael@0: let flags = aStr.slice(lastSlash + 1); michael@0: return new RegExp(pattern, flags); michael@0: } michael@0: michael@0: /** michael@0: * Manages the Blocklist. The Blocklist is a representation of the contents of michael@0: * blocklist.xml and allows us to remotely disable / re-enable blocklisted michael@0: * items managed by the Extension Manager with an item's appDisabled property. michael@0: * It also blocklists plugins with data from blocklist.xml. michael@0: */ michael@0: michael@0: function Blocklist() { michael@0: let os = getObserverService(); michael@0: os.addObserver(this, "xpcom-shutdown", false); michael@0: gLoggingEnabled = getPref("getBoolPref", PREF_EM_LOGGING_ENABLED, false); michael@0: gBlocklistEnabled = getPref("getBoolPref", PREF_BLOCKLIST_ENABLED, true); michael@0: gBlocklistLevel = Math.min(getPref("getIntPref", PREF_BLOCKLIST_LEVEL, DEFAULT_LEVEL), michael@0: MAX_BLOCK_LEVEL); michael@0: gPref.addObserver("extensions.blocklist.", this, false); michael@0: gPref.addObserver(PREF_EM_LOGGING_ENABLED, this, false); michael@0: } michael@0: michael@0: Blocklist.prototype = { michael@0: /** michael@0: * Extension ID -> array of Version Ranges michael@0: * Each value in the version range array is a JS Object that has the michael@0: * following properties: michael@0: * "minVersion" The minimum version in a version range (default = 0) michael@0: * "maxVersion" The maximum version in a version range (default = *) michael@0: * "targetApps" Application ID -> array of Version Ranges michael@0: * (default = current application ID) michael@0: * Each value in the version range array is a JS Object that michael@0: * has the following properties: michael@0: * "minVersion" The minimum version in a version range michael@0: * (default = 0) michael@0: * "maxVersion" The maximum version in a version range michael@0: * (default = *) michael@0: */ michael@0: _addonEntries: null, michael@0: _pluginEntries: null, michael@0: michael@0: observe: function Blocklist_observe(aSubject, aTopic, aData) { michael@0: switch (aTopic) { michael@0: case "xpcom-shutdown": michael@0: let os = getObserverService(); michael@0: os.removeObserver(this, "xpcom-shutdown"); michael@0: gPref.removeObserver("extensions.blocklist.", this); michael@0: gPref.removeObserver(PREF_EM_LOGGING_ENABLED, this); michael@0: break; michael@0: case "nsPref:changed": michael@0: switch (aData) { michael@0: case PREF_EM_LOGGING_ENABLED: michael@0: gLoggingEnabled = getPref("getBoolPref", PREF_EM_LOGGING_ENABLED, false); michael@0: break; michael@0: case PREF_BLOCKLIST_ENABLED: michael@0: gBlocklistEnabled = getPref("getBoolPref", PREF_BLOCKLIST_ENABLED, true); michael@0: this._loadBlocklist(); michael@0: this._blocklistUpdated(null, null); michael@0: break; michael@0: case PREF_BLOCKLIST_LEVEL: michael@0: gBlocklistLevel = Math.min(getPref("getIntPref", PREF_BLOCKLIST_LEVEL, DEFAULT_LEVEL), michael@0: MAX_BLOCK_LEVEL); michael@0: this._blocklistUpdated(null, null); michael@0: break; michael@0: } michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: /* See nsIBlocklistService */ michael@0: isAddonBlocklisted: function Blocklist_isAddonBlocklisted(addon, appVersion, toolkitVersion) { michael@0: return this.getAddonBlocklistState(addon, appVersion, toolkitVersion) == michael@0: Ci.nsIBlocklistService.STATE_BLOCKED; michael@0: }, michael@0: michael@0: /* See nsIBlocklistService */ michael@0: getAddonBlocklistState: function Blocklist_getAddonBlocklistState(addon, appVersion, toolkitVersion) { michael@0: if (!this._addonEntries) michael@0: this._loadBlocklist(); michael@0: return this._getAddonBlocklistState(addon, this._addonEntries, michael@0: appVersion, toolkitVersion); michael@0: }, michael@0: michael@0: /** michael@0: * Private version of getAddonBlocklistState that allows the caller to pass in michael@0: * the add-on blocklist entries to compare against. michael@0: * michael@0: * @param id michael@0: * The ID of the item to get the blocklist state for. michael@0: * @param version michael@0: * The version of the item to get the blocklist state for. michael@0: * @param addonEntries michael@0: * The add-on blocklist entries to compare against. michael@0: * @param appVersion michael@0: * The application version to compare to, will use the current michael@0: * version if null. michael@0: * @param toolkitVersion michael@0: * The toolkit version to compare to, will use the current version if michael@0: * null. michael@0: * @returns The blocklist state for the item, one of the STATE constants as michael@0: * defined in nsIBlocklistService. michael@0: */ michael@0: _getAddonBlocklistState: function Blocklist_getAddonBlocklistStateCall(addon, michael@0: addonEntries, appVersion, toolkitVersion) { michael@0: if (!gBlocklistEnabled) michael@0: return Ci.nsIBlocklistService.STATE_NOT_BLOCKED; michael@0: michael@0: if (!appVersion) michael@0: appVersion = gApp.version; michael@0: if (!toolkitVersion) michael@0: toolkitVersion = gApp.platformVersion; michael@0: michael@0: var blItem = this._findMatchingAddonEntry(addonEntries, addon); michael@0: if (!blItem) michael@0: return Ci.nsIBlocklistService.STATE_NOT_BLOCKED; michael@0: michael@0: for (let currentblItem of blItem.versions) { michael@0: if (currentblItem.includesItem(addon.version, appVersion, toolkitVersion)) michael@0: return currentblItem.severity >= gBlocklistLevel ? Ci.nsIBlocklistService.STATE_BLOCKED : michael@0: Ci.nsIBlocklistService.STATE_SOFTBLOCKED; michael@0: } michael@0: return Ci.nsIBlocklistService.STATE_NOT_BLOCKED; michael@0: }, michael@0: michael@0: /** michael@0: * Returns the set of prefs of the add-on stored in the blocklist file michael@0: * (probably to revert them on disabling). michael@0: * @param addon michael@0: * The add-on whose to-be-reset prefs are to be found. michael@0: */ michael@0: _getAddonPrefs: function Blocklist_getAddonPrefs(addon) { michael@0: let entry = this._findMatchingAddonEntry(this._addonEntries, addon); michael@0: return entry.prefs.slice(0); michael@0: }, michael@0: michael@0: _findMatchingAddonEntry: function Blocklist_findMatchingAddonEntry(aAddonEntries, michael@0: aAddon) { michael@0: if (!aAddon) michael@0: return null; michael@0: // Returns true if the params object passes the constraints set by entry. michael@0: // (For every non-null property in entry, the same key must exist in michael@0: // params and value must be the same) michael@0: function checkEntry(entry, params) { michael@0: for (let [key, value] of entry) { michael@0: if (value === null || value === undefined) michael@0: continue; michael@0: if (params[key]) { michael@0: if (value instanceof RegExp) { michael@0: if (!value.test(params[key])) { michael@0: return false; michael@0: } michael@0: } else if (value !== params[key]) { michael@0: return false; michael@0: } michael@0: } else { michael@0: return false; michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: let params = {}; michael@0: for (let filter of EXTENSION_BLOCK_FILTERS) { michael@0: params[filter] = aAddon[filter]; michael@0: } michael@0: if (params.creator) michael@0: params.creator = params.creator.name; michael@0: for (let entry of aAddonEntries) { michael@0: if (checkEntry(entry.attributes, params)) { michael@0: return entry; michael@0: } michael@0: } michael@0: return null; michael@0: }, michael@0: michael@0: /* See nsIBlocklistService */ michael@0: getAddonBlocklistURL: function Blocklist_getAddonBlocklistURL(addon, appVersion, toolkitVersion) { michael@0: if (!gBlocklistEnabled) michael@0: return ""; michael@0: michael@0: if (!this._addonEntries) michael@0: this._loadBlocklist(); michael@0: michael@0: let blItem = this._findMatchingAddonEntry(this._addonEntries, addon); michael@0: if (!blItem || !blItem.blockID) michael@0: return null; michael@0: michael@0: return this._createBlocklistURL(blItem.blockID); michael@0: }, michael@0: michael@0: _createBlocklistURL: function Blocklist_createBlocklistURL(id) { michael@0: let url = Services.urlFormatter.formatURLPref(PREF_BLOCKLIST_ITEM_URL); michael@0: url = url.replace(/%blockID%/g, id); michael@0: michael@0: return url; michael@0: }, michael@0: michael@0: notify: function Blocklist_notify(aTimer) { michael@0: if (!gBlocklistEnabled) michael@0: return; michael@0: michael@0: try { michael@0: var dsURI = gPref.getCharPref(PREF_BLOCKLIST_URL); michael@0: } michael@0: catch (e) { michael@0: LOG("Blocklist::notify: The " + PREF_BLOCKLIST_URL + " preference" + michael@0: " is missing!"); michael@0: return; michael@0: } michael@0: michael@0: var pingCountVersion = getPref("getIntPref", PREF_BLOCKLIST_PINGCOUNTVERSION, 0); michael@0: var pingCountTotal = getPref("getIntPref", PREF_BLOCKLIST_PINGCOUNTTOTAL, 1); michael@0: var daysSinceLastPing = 0; michael@0: if (pingCountVersion == 0) { michael@0: daysSinceLastPing = "new"; michael@0: } michael@0: else { michael@0: // Seconds in one day is used because nsIUpdateTimerManager stores the michael@0: // last update time in seconds. michael@0: let secondsInDay = 60 * 60 * 24; michael@0: let lastUpdateTime = getPref("getIntPref", PREF_BLOCKLIST_LASTUPDATETIME, 0); michael@0: if (lastUpdateTime == 0) { michael@0: daysSinceLastPing = "invalid"; michael@0: } michael@0: else { michael@0: let now = Math.round(Date.now() / 1000); michael@0: daysSinceLastPing = Math.floor((now - lastUpdateTime) / secondsInDay); michael@0: } michael@0: michael@0: if (daysSinceLastPing == 0 || daysSinceLastPing == "invalid") { michael@0: pingCountVersion = pingCountTotal = "invalid"; michael@0: } michael@0: } michael@0: michael@0: if (pingCountVersion < 1) michael@0: pingCountVersion = 1; michael@0: if (pingCountTotal < 1) michael@0: pingCountTotal = 1; michael@0: michael@0: dsURI = dsURI.replace(/%APP_ID%/g, gApp.ID); michael@0: dsURI = dsURI.replace(/%APP_VERSION%/g, gApp.version); michael@0: dsURI = dsURI.replace(/%PRODUCT%/g, gApp.name); michael@0: dsURI = dsURI.replace(/%VERSION%/g, gApp.version); michael@0: dsURI = dsURI.replace(/%BUILD_ID%/g, gApp.appBuildID); michael@0: dsURI = dsURI.replace(/%BUILD_TARGET%/g, gApp.OS + "_" + gABI); michael@0: dsURI = dsURI.replace(/%OS_VERSION%/g, gOSVersion); michael@0: dsURI = dsURI.replace(/%LOCALE%/g, getLocale()); michael@0: dsURI = dsURI.replace(/%CHANNEL%/g, UpdateChannel.get()); michael@0: dsURI = dsURI.replace(/%PLATFORM_VERSION%/g, gApp.platformVersion); michael@0: dsURI = dsURI.replace(/%DISTRIBUTION%/g, michael@0: getDistributionPrefValue(PREF_APP_DISTRIBUTION)); michael@0: dsURI = dsURI.replace(/%DISTRIBUTION_VERSION%/g, michael@0: getDistributionPrefValue(PREF_APP_DISTRIBUTION_VERSION)); michael@0: dsURI = dsURI.replace(/%PING_COUNT%/g, pingCountVersion); michael@0: dsURI = dsURI.replace(/%TOTAL_PING_COUNT%/g, pingCountTotal); michael@0: dsURI = dsURI.replace(/%DAYS_SINCE_LAST_PING%/g, daysSinceLastPing); michael@0: dsURI = dsURI.replace(/\+/g, "%2B"); michael@0: michael@0: // Under normal operations it will take around 5,883,516 years before the michael@0: // preferences used to store pingCountVersion and pingCountTotal will rollover michael@0: // so this code doesn't bother trying to do the "right thing" here. michael@0: if (pingCountVersion != "invalid") { michael@0: pingCountVersion++; michael@0: if (pingCountVersion > 2147483647) { michael@0: // Rollover to -1 if the value is greater than what is support by an michael@0: // integer preference. The -1 indicates that the counter has been reset. michael@0: pingCountVersion = -1; michael@0: } michael@0: gPref.setIntPref(PREF_BLOCKLIST_PINGCOUNTVERSION, pingCountVersion); michael@0: } michael@0: michael@0: if (pingCountTotal != "invalid") { michael@0: pingCountTotal++; michael@0: if (pingCountTotal > 2147483647) { michael@0: // Rollover to 1 if the value is greater than what is support by an michael@0: // integer preference. michael@0: pingCountTotal = -1; michael@0: } michael@0: gPref.setIntPref(PREF_BLOCKLIST_PINGCOUNTTOTAL, pingCountTotal); michael@0: } michael@0: michael@0: // Verify that the URI is valid michael@0: try { michael@0: var uri = newURI(dsURI); michael@0: } michael@0: catch (e) { michael@0: LOG("Blocklist::notify: There was an error creating the blocklist URI\r\n" + michael@0: "for: " + dsURI + ", error: " + e); michael@0: return; michael@0: } michael@0: michael@0: LOG("Blocklist::notify: Requesting " + uri.spec); michael@0: var request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]. michael@0: createInstance(Ci.nsIXMLHttpRequest); michael@0: request.open("GET", uri.spec, true); michael@0: request.channel.notificationCallbacks = new gCertUtils.BadCertHandler(); michael@0: request.overrideMimeType("text/xml"); michael@0: request.setRequestHeader("Cache-Control", "no-cache"); michael@0: request.QueryInterface(Components.interfaces.nsIJSXMLHttpRequest); michael@0: michael@0: var self = this; michael@0: request.addEventListener("error", function errorEventListener(event) { michael@0: self.onXMLError(event); }, false); michael@0: request.addEventListener("load", function loadEventListener(event) { michael@0: self.onXMLLoad(event); }, false); michael@0: request.send(null); michael@0: michael@0: // When the blocklist loads we need to compare it to the current copy so michael@0: // make sure we have loaded it. michael@0: if (!this._addonEntries) michael@0: this._loadBlocklist(); michael@0: }, michael@0: michael@0: onXMLLoad: Task.async(function* (aEvent) { michael@0: let request = aEvent.target; michael@0: try { michael@0: gCertUtils.checkCert(request.channel); michael@0: } michael@0: catch (e) { michael@0: LOG("Blocklist::onXMLLoad: " + e); michael@0: return; michael@0: } michael@0: let responseXML = request.responseXML; michael@0: if (!responseXML || responseXML.documentElement.namespaceURI == XMLURI_PARSE_ERROR || michael@0: (request.status != 200 && request.status != 0)) { michael@0: LOG("Blocklist::onXMLLoad: there was an error during load"); michael@0: return; michael@0: } michael@0: michael@0: var oldAddonEntries = this._addonEntries; michael@0: var oldPluginEntries = this._pluginEntries; michael@0: this._addonEntries = []; michael@0: this._pluginEntries = []; michael@0: michael@0: this._loadBlocklistFromString(request.responseText); michael@0: this._blocklistUpdated(oldAddonEntries, oldPluginEntries); michael@0: michael@0: try { michael@0: let path = OS.Path.join(OS.Constants.Path.profileDir, FILE_BLOCKLIST); michael@0: yield OS.File.writeAtomic(path, request.responseText, {tmpPath: path + ".tmp"}); michael@0: } catch (e) { michael@0: LOG("Blocklist::onXMLLoad: " + e); michael@0: } michael@0: }), michael@0: michael@0: onXMLError: function Blocklist_onXMLError(aEvent) { michael@0: try { michael@0: var request = aEvent.target; michael@0: // the following may throw (e.g. a local file or timeout) michael@0: var status = request.status; michael@0: } michael@0: catch (e) { michael@0: request = aEvent.target.channel.QueryInterface(Ci.nsIRequest); michael@0: status = request.status; michael@0: } michael@0: var statusText = "nsIXMLHttpRequest channel unavailable"; michael@0: // When status is 0 we don't have a valid channel. michael@0: if (status != 0) { michael@0: try { michael@0: statusText = request.statusText; michael@0: } catch (e) { michael@0: } michael@0: } michael@0: LOG("Blocklist:onError: There was an error loading the blocklist file\r\n" + michael@0: statusText); michael@0: }, michael@0: michael@0: /** michael@0: * Finds the newest blocklist file from the application and the profile and michael@0: * load it or does nothing if neither exist. michael@0: */ michael@0: _loadBlocklist: function Blocklist_loadBlocklist() { michael@0: this._addonEntries = []; michael@0: this._pluginEntries = []; michael@0: var profFile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_BLOCKLIST]); michael@0: if (profFile.exists()) { michael@0: this._loadBlocklistFromFile(profFile); michael@0: return; michael@0: } michael@0: var appFile = FileUtils.getFile(KEY_APPDIR, [FILE_BLOCKLIST]); michael@0: if (appFile.exists()) { michael@0: this._loadBlocklistFromFile(appFile); michael@0: return; michael@0: } michael@0: LOG("Blocklist::_loadBlocklist: no XML File found"); michael@0: }, michael@0: michael@0: /** michael@0: # The blocklist XML file looks something like this: michael@0: # michael@0: # michael@0: # michael@0: # michael@0: # michael@0: # accessibility.accesskeycausesactivation michael@0: # accessibility.blockautorefresh michael@0: # michael@0: # michael@0: # michael@0: # michael@0: # michael@0: # michael@0: # michael@0: # michael@0: # michael@0: # michael@0: # michael@0: # michael@0: # michael@0: # michael@0: # michael@0: # michael@0: # michael@0: # michael@0: # michael@0: # michael@0: # michael@0: # michael@0: # michael@0: # michael@0: # michael@0: # michael@0: # michael@0: # michael@0: # michael@0: # michael@0: # michael@0: # michael@0: # michael@0: # michael@0: # michael@0: # michael@0: # michael@0: # michael@0: # michael@0: # michael@0: # michael@0: # michael@0: # michael@0: # michael@0: # michael@0: */ michael@0: michael@0: _loadBlocklistFromFile: function Blocklist_loadBlocklistFromFile(file) { michael@0: if (!gBlocklistEnabled) { michael@0: LOG("Blocklist::_loadBlocklistFromFile: blocklist is disabled"); michael@0: return; michael@0: } michael@0: michael@0: if (!file.exists()) { michael@0: LOG("Blocklist::_loadBlocklistFromFile: XML File does not exist " + file.path); michael@0: return; michael@0: } michael@0: michael@0: let text = ""; michael@0: let fstream = null; michael@0: let cstream = null; michael@0: michael@0: try { michael@0: fstream = Components.classes["@mozilla.org/network/file-input-stream;1"] michael@0: .createInstance(Components.interfaces.nsIFileInputStream); michael@0: cstream = Components.classes["@mozilla.org/intl/converter-input-stream;1"] michael@0: .createInstance(Components.interfaces.nsIConverterInputStream); michael@0: michael@0: fstream.init(file, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0); michael@0: cstream.init(fstream, "UTF-8", 0, 0); michael@0: michael@0: let (str = {}) { michael@0: let read = 0; michael@0: michael@0: do { michael@0: read = cstream.readString(0xffffffff, str); // read as much as we can and put it in str.value michael@0: text += str.value; michael@0: } while (read != 0); michael@0: } michael@0: } catch (e) { michael@0: LOG("Blocklist::_loadBlocklistFromFile: Failed to load XML file " + e); michael@0: } finally { michael@0: cstream.close(); michael@0: fstream.close(); michael@0: } michael@0: michael@0: text && this._loadBlocklistFromString(text); michael@0: }, michael@0: michael@0: _loadBlocklistFromString : function Blocklist_loadBlocklistFromString(text) { michael@0: try { michael@0: var parser = Cc["@mozilla.org/xmlextras/domparser;1"]. michael@0: createInstance(Ci.nsIDOMParser); michael@0: var doc = parser.parseFromString(text, "text/xml"); michael@0: if (doc.documentElement.namespaceURI != XMLURI_BLOCKLIST) { michael@0: LOG("Blocklist::_loadBlocklistFromFile: aborting due to incorrect " + michael@0: "XML Namespace.\r\nExpected: " + XMLURI_BLOCKLIST + "\r\n" + michael@0: "Received: " + doc.documentElement.namespaceURI); michael@0: return; michael@0: } michael@0: michael@0: var childNodes = doc.documentElement.childNodes; michael@0: for (let element of childNodes) { michael@0: if (!(element instanceof Ci.nsIDOMElement)) michael@0: continue; michael@0: switch (element.localName) { michael@0: case "emItems": michael@0: this._addonEntries = this._processItemNodes(element.childNodes, "em", michael@0: this._handleEmItemNode); michael@0: break; michael@0: case "pluginItems": michael@0: this._pluginEntries = this._processItemNodes(element.childNodes, "plugin", michael@0: this._handlePluginItemNode); michael@0: break; michael@0: default: michael@0: Services.obs.notifyObservers(element, michael@0: "blocklist-data-" + element.localName, michael@0: null); michael@0: } michael@0: } michael@0: } michael@0: catch (e) { michael@0: LOG("Blocklist::_loadBlocklistFromFile: Error constructing blocklist " + e); michael@0: return; michael@0: } michael@0: }, michael@0: michael@0: _processItemNodes: function Blocklist_processItemNodes(itemNodes, prefix, handler) { michael@0: var result = []; michael@0: var itemName = prefix + "Item"; michael@0: for (var i = 0; i < itemNodes.length; ++i) { michael@0: var blocklistElement = itemNodes.item(i); michael@0: if (!(blocklistElement instanceof Ci.nsIDOMElement) || michael@0: blocklistElement.localName != itemName) michael@0: continue; michael@0: michael@0: handler(blocklistElement, result); michael@0: } michael@0: return result; michael@0: }, michael@0: michael@0: _handleEmItemNode: function Blocklist_handleEmItemNode(blocklistElement, result) { michael@0: if (!matchesOSABI(blocklistElement)) michael@0: return; michael@0: michael@0: let blockEntry = { michael@0: versions: [], michael@0: prefs: [], michael@0: blockID: null, michael@0: attributes: new Map() michael@0: // Atleast one of EXTENSION_BLOCK_FILTERS must get added to attributes michael@0: }; michael@0: michael@0: // Any filter starting with '/' is interpreted as a regex. So if an attribute michael@0: // starts with a '/' it must be checked via a regex. michael@0: function regExpCheck(attr) { michael@0: return attr.startsWith("/") ? parseRegExp(attr) : attr; michael@0: } michael@0: michael@0: for (let filter of EXTENSION_BLOCK_FILTERS) { michael@0: let attr = blocklistElement.getAttribute(filter); michael@0: if (attr) michael@0: blockEntry.attributes.set(filter, regExpCheck(attr)); michael@0: } michael@0: michael@0: var childNodes = blocklistElement.childNodes; michael@0: michael@0: for (let x = 0; x < childNodes.length; x++) { michael@0: var childElement = childNodes.item(x); michael@0: if (!(childElement instanceof Ci.nsIDOMElement)) michael@0: continue; michael@0: if (childElement.localName === "prefs") { michael@0: let prefElements = childElement.childNodes; michael@0: for (let i = 0; i < prefElements.length; i++) { michael@0: let prefElement = prefElements.item(i); michael@0: if (!(prefElement instanceof Ci.nsIDOMElement) || michael@0: prefElement.localName !== "pref") michael@0: continue; michael@0: blockEntry.prefs.push(prefElement.textContent); michael@0: } michael@0: } michael@0: else if (childElement.localName === "versionRange") michael@0: blockEntry.versions.push(new BlocklistItemData(childElement)); michael@0: } michael@0: // if only the extension ID is specified block all versions of the michael@0: // extension for the current application. michael@0: if (blockEntry.versions.length == 0) michael@0: blockEntry.versions.push(new BlocklistItemData(null)); michael@0: michael@0: blockEntry.blockID = blocklistElement.getAttribute("blockID"); michael@0: michael@0: result.push(blockEntry); michael@0: }, michael@0: michael@0: _handlePluginItemNode: function Blocklist_handlePluginItemNode(blocklistElement, result) { michael@0: if (!matchesOSABI(blocklistElement)) michael@0: return; michael@0: michael@0: var matchNodes = blocklistElement.childNodes; michael@0: var blockEntry = { michael@0: matches: {}, michael@0: versions: [], michael@0: blockID: null, michael@0: }; michael@0: var hasMatch = false; michael@0: for (var x = 0; x < matchNodes.length; ++x) { michael@0: var matchElement = matchNodes.item(x); michael@0: if (!(matchElement instanceof Ci.nsIDOMElement)) michael@0: continue; michael@0: if (matchElement.localName == "match") { michael@0: var name = matchElement.getAttribute("name"); michael@0: var exp = matchElement.getAttribute("exp"); michael@0: try { michael@0: blockEntry.matches[name] = new RegExp(exp, "m"); michael@0: hasMatch = true; michael@0: } catch (e) { michael@0: // Ignore invalid regular expressions michael@0: } michael@0: } michael@0: if (matchElement.localName == "versionRange") michael@0: blockEntry.versions.push(new BlocklistItemData(matchElement)); michael@0: } michael@0: // Plugin entries require *something* to match to an actual plugin michael@0: if (!hasMatch) michael@0: return; michael@0: // Add a default versionRange if there wasn't one specified michael@0: if (blockEntry.versions.length == 0) michael@0: blockEntry.versions.push(new BlocklistItemData(null)); michael@0: michael@0: blockEntry.blockID = blocklistElement.getAttribute("blockID"); michael@0: michael@0: result.push(blockEntry); michael@0: }, michael@0: michael@0: /* See nsIBlocklistService */ michael@0: getPluginBlocklistState: function Blocklist_getPluginBlocklistState(plugin, michael@0: appVersion, toolkitVersion) { michael@0: if (!this._pluginEntries) michael@0: this._loadBlocklist(); michael@0: return this._getPluginBlocklistState(plugin, this._pluginEntries, michael@0: appVersion, toolkitVersion); michael@0: }, michael@0: michael@0: /** michael@0: * Private version of getPluginBlocklistState that allows the caller to pass in michael@0: * the plugin blocklist entries. michael@0: * michael@0: * @param plugin michael@0: * The nsIPluginTag to get the blocklist state for. michael@0: * @param pluginEntries michael@0: * The plugin blocklist entries to compare against. michael@0: * @param appVersion michael@0: * The application version to compare to, will use the current michael@0: * version if null. michael@0: * @param toolkitVersion michael@0: * The toolkit version to compare to, will use the current version if michael@0: * null. michael@0: * @returns The blocklist state for the item, one of the STATE constants as michael@0: * defined in nsIBlocklistService. michael@0: */ michael@0: _getPluginBlocklistState: function Blocklist_getPluginBlocklistState(plugin, michael@0: pluginEntries, appVersion, toolkitVersion) { michael@0: if (!gBlocklistEnabled) michael@0: return Ci.nsIBlocklistService.STATE_NOT_BLOCKED; michael@0: michael@0: if (!appVersion) michael@0: appVersion = gApp.version; michael@0: if (!toolkitVersion) michael@0: toolkitVersion = gApp.platformVersion; michael@0: michael@0: for each (var blockEntry in pluginEntries) { michael@0: var matchFailed = false; michael@0: for (var name in blockEntry.matches) { michael@0: if (!(name in plugin) || michael@0: typeof(plugin[name]) != "string" || michael@0: !blockEntry.matches[name].test(plugin[name])) { michael@0: matchFailed = true; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: if (matchFailed) michael@0: continue; michael@0: michael@0: for (let blockEntryVersion of blockEntry.versions) { michael@0: if (blockEntryVersion.includesItem(plugin.version, appVersion, michael@0: toolkitVersion)) { michael@0: if (blockEntryVersion.severity >= gBlocklistLevel) michael@0: return Ci.nsIBlocklistService.STATE_BLOCKED; michael@0: if (blockEntryVersion.severity == SEVERITY_OUTDATED) { michael@0: let vulnerabilityStatus = blockEntryVersion.vulnerabilityStatus; michael@0: if (vulnerabilityStatus == VULNERABILITYSTATUS_UPDATE_AVAILABLE) michael@0: return Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE; michael@0: if (vulnerabilityStatus == VULNERABILITYSTATUS_NO_UPDATE) michael@0: return Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE; michael@0: return Ci.nsIBlocklistService.STATE_OUTDATED; michael@0: } michael@0: return Ci.nsIBlocklistService.STATE_SOFTBLOCKED; michael@0: } michael@0: } michael@0: } michael@0: michael@0: return Ci.nsIBlocklistService.STATE_NOT_BLOCKED; michael@0: }, michael@0: michael@0: /* See nsIBlocklistService */ michael@0: getPluginBlocklistURL: function Blocklist_getPluginBlocklistURL(plugin) { michael@0: if (!gBlocklistEnabled) michael@0: return ""; michael@0: michael@0: if (!this._pluginEntries) michael@0: this._loadBlocklist(); michael@0: michael@0: for each (let blockEntry in this._pluginEntries) { michael@0: let matchFailed = false; michael@0: for (let name in blockEntry.matches) { michael@0: if (!(name in plugin) || michael@0: typeof(plugin[name]) != "string" || michael@0: !blockEntry.matches[name].test(plugin[name])) { michael@0: matchFailed = true; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: if (!matchFailed) { michael@0: if(!blockEntry.blockID) michael@0: return null; michael@0: else michael@0: return this._createBlocklistURL(blockEntry.blockID); michael@0: } michael@0: } michael@0: }, michael@0: michael@0: _blocklistUpdated: function Blocklist_blocklistUpdated(oldAddonEntries, oldPluginEntries) { michael@0: var addonList = []; michael@0: michael@0: // A helper function that reverts the prefs passed to default values. michael@0: function resetPrefs(prefs) { michael@0: for (let pref of prefs) michael@0: gPref.clearUserPref(pref); michael@0: } michael@0: var self = this; michael@0: const types = ["extension", "theme", "locale", "dictionary", "service"]; michael@0: AddonManager.getAddonsByTypes(types, function blocklistUpdated_getAddonsByTypes(addons) { michael@0: michael@0: for (let addon of addons) { michael@0: let oldState = Ci.nsIBlocklistService.STATE_NOTBLOCKED; michael@0: if (oldAddonEntries) michael@0: oldState = self._getAddonBlocklistState(addon, oldAddonEntries); michael@0: let state = self.getAddonBlocklistState(addon); michael@0: michael@0: LOG("Blocklist state for " + addon.id + " changed from " + michael@0: oldState + " to " + state); michael@0: michael@0: // We don't want to re-warn about add-ons michael@0: if (state == oldState) michael@0: continue; michael@0: michael@0: if (state === Ci.nsIBlocklistService.STATE_BLOCKED) { michael@0: // It's a hard block. We must reset certain preferences. michael@0: let prefs = self._getAddonPrefs(addon); michael@0: resetPrefs(prefs); michael@0: } michael@0: michael@0: // Ensure that softDisabled is false if the add-on is not soft blocked michael@0: if (state != Ci.nsIBlocklistService.STATE_SOFTBLOCKED) michael@0: addon.softDisabled = false; michael@0: michael@0: // Don't warn about add-ons becoming unblocked. michael@0: if (state == Ci.nsIBlocklistService.STATE_NOT_BLOCKED) michael@0: continue; michael@0: michael@0: // If an add-on has dropped from hard to soft blocked just mark it as michael@0: // soft disabled and don't warn about it. michael@0: if (state == Ci.nsIBlocklistService.STATE_SOFTBLOCKED && michael@0: oldState == Ci.nsIBlocklistService.STATE_BLOCKED) { michael@0: addon.softDisabled = true; michael@0: continue; michael@0: } michael@0: michael@0: // If the add-on is already disabled for some reason then don't warn michael@0: // about it michael@0: if (!addon.isActive) michael@0: continue; michael@0: michael@0: addonList.push({ michael@0: name: addon.name, michael@0: version: addon.version, michael@0: icon: addon.iconURL, michael@0: disable: false, michael@0: blocked: state == Ci.nsIBlocklistService.STATE_BLOCKED, michael@0: item: addon, michael@0: url: self.getAddonBlocklistURL(addon), michael@0: }); michael@0: } michael@0: michael@0: AddonManagerPrivate.updateAddonAppDisabledStates(); michael@0: michael@0: var phs = Cc["@mozilla.org/plugin/host;1"]. michael@0: getService(Ci.nsIPluginHost); michael@0: var plugins = phs.getPluginTags(); michael@0: michael@0: for (let plugin of plugins) { michael@0: let oldState = -1; michael@0: if (oldPluginEntries) michael@0: oldState = self._getPluginBlocklistState(plugin, oldPluginEntries); michael@0: let state = self.getPluginBlocklistState(plugin); michael@0: LOG("Blocklist state for " + plugin.name + " changed from " + michael@0: oldState + " to " + state); michael@0: // We don't want to re-warn about items michael@0: if (state == oldState) michael@0: continue; michael@0: michael@0: if (oldState == Ci.nsIBlocklistService.STATE_BLOCKED) { michael@0: if (state == Ci.nsIBlocklistService.STATE_SOFTBLOCKED) michael@0: plugin.enabledState = Ci.nsIPluginTag.STATE_DISABLED; michael@0: } michael@0: else if (!plugin.disabled && state != Ci.nsIBlocklistService.STATE_NOT_BLOCKED) { michael@0: if (state == Ci.nsIBlocklistService.STATE_OUTDATED) { michael@0: gPref.setBoolPref(PREF_PLUGINS_NOTIFYUSER, true); michael@0: } michael@0: else if (state != Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE && michael@0: state != Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE) { michael@0: addonList.push({ michael@0: name: plugin.name, michael@0: version: plugin.version, michael@0: icon: "chrome://mozapps/skin/plugins/pluginGeneric.png", michael@0: disable: false, michael@0: blocked: state == Ci.nsIBlocklistService.STATE_BLOCKED, michael@0: item: plugin, michael@0: url: self.getPluginBlocklistURL(plugin), michael@0: }); michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (addonList.length == 0) { michael@0: Services.obs.notifyObservers(self, "blocklist-updated", ""); michael@0: return; michael@0: } michael@0: michael@0: if ("@mozilla.org/addons/blocklist-prompt;1" in Cc) { michael@0: try { michael@0: let blockedPrompter = Cc["@mozilla.org/addons/blocklist-prompt;1"] michael@0: .getService(Ci.nsIBlocklistPrompt); michael@0: blockedPrompter.prompt(addonList); michael@0: } catch (e) { michael@0: LOG(e); michael@0: } michael@0: Services.obs.notifyObservers(self, "blocklist-updated", ""); michael@0: return; michael@0: } michael@0: michael@0: var args = { michael@0: restart: false, michael@0: list: addonList michael@0: }; michael@0: // This lets the dialog get the raw js object michael@0: args.wrappedJSObject = args; michael@0: michael@0: /* michael@0: Some tests run without UI, so the async code listens to a message michael@0: that can be sent programatically michael@0: */ michael@0: let applyBlocklistChanges = function blocklistUpdated_applyBlocklistChanges() { michael@0: for (let addon of addonList) { michael@0: if (!addon.disable) michael@0: continue; michael@0: michael@0: if (addon.item instanceof Ci.nsIPluginTag) michael@0: addon.item.enabledState = Ci.nsIPluginTag.STATE_DISABLED; michael@0: else { michael@0: // This add-on is softblocked. michael@0: addon.item.softDisabled = true; michael@0: // We must revert certain prefs. michael@0: let prefs = self._getAddonPrefs(addon.item); michael@0: resetPrefs(prefs); michael@0: } michael@0: } michael@0: michael@0: if (args.restart) michael@0: restartApp(); michael@0: michael@0: Services.obs.notifyObservers(self, "blocklist-updated", ""); michael@0: Services.obs.removeObserver(applyBlocklistChanges, "addon-blocklist-closed"); michael@0: } michael@0: michael@0: Services.obs.addObserver(applyBlocklistChanges, "addon-blocklist-closed", false); michael@0: michael@0: if (getPref("getBoolPref", PREF_BLOCKLIST_SUPPRESSUI, false)) { michael@0: applyBlocklistChanges(); michael@0: return; michael@0: } michael@0: michael@0: function blocklistUnloadHandler(event) { michael@0: if (event.target.location == URI_BLOCKLIST_DIALOG) { michael@0: applyBlocklistChanges(); michael@0: blocklistWindow.removeEventListener("unload", blocklistUnloadHandler); michael@0: } michael@0: } michael@0: michael@0: let blocklistWindow = Services.ww.openWindow(null, URI_BLOCKLIST_DIALOG, "", michael@0: "chrome,centerscreen,dialog,titlebar", args); michael@0: if (blocklistWindow) michael@0: blocklistWindow.addEventListener("unload", blocklistUnloadHandler, false); michael@0: }); michael@0: }, michael@0: michael@0: classID: Components.ID("{66354bc9-7ed1-4692-ae1d-8da97d6b205e}"), michael@0: QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, michael@0: Ci.nsIBlocklistService, michael@0: Ci.nsITimerCallback]), michael@0: }; michael@0: michael@0: /** michael@0: * Helper for constructing a blocklist. michael@0: */ michael@0: function BlocklistItemData(versionRangeElement) { michael@0: var versionRange = this.getBlocklistVersionRange(versionRangeElement); michael@0: this.minVersion = versionRange.minVersion; michael@0: this.maxVersion = versionRange.maxVersion; michael@0: if (versionRangeElement && versionRangeElement.hasAttribute("severity")) michael@0: this.severity = versionRangeElement.getAttribute("severity"); michael@0: else michael@0: this.severity = DEFAULT_SEVERITY; michael@0: if (versionRangeElement && versionRangeElement.hasAttribute("vulnerabilitystatus")) { michael@0: this.vulnerabilityStatus = versionRangeElement.getAttribute("vulnerabilitystatus"); michael@0: } else { michael@0: this.vulnerabilityStatus = VULNERABILITYSTATUS_NONE; michael@0: } michael@0: this.targetApps = { }; michael@0: var found = false; michael@0: michael@0: if (versionRangeElement) { michael@0: for (var i = 0; i < versionRangeElement.childNodes.length; ++i) { michael@0: var targetAppElement = versionRangeElement.childNodes.item(i); michael@0: if (!(targetAppElement instanceof Ci.nsIDOMElement) || michael@0: targetAppElement.localName != "targetApplication") michael@0: continue; michael@0: found = true; michael@0: // default to the current application if id is not provided. michael@0: var appID = targetAppElement.hasAttribute("id") ? targetAppElement.getAttribute("id") : gApp.ID; michael@0: this.targetApps[appID] = this.getBlocklistAppVersions(targetAppElement); michael@0: } michael@0: } michael@0: // Default to all versions of the current application when no targetApplication michael@0: // elements were found michael@0: if (!found) michael@0: this.targetApps[gApp.ID] = this.getBlocklistAppVersions(null); michael@0: } michael@0: michael@0: BlocklistItemData.prototype = { michael@0: /** michael@0: * Tests if a version of an item is included in the version range and target michael@0: * application information represented by this BlocklistItemData using the michael@0: * provided application and toolkit versions. michael@0: * @param version michael@0: * The version of the item being tested. michael@0: * @param appVersion michael@0: * The application version to test with. michael@0: * @param toolkitVersion michael@0: * The toolkit version to test with. michael@0: * @returns True if the version range covers the item version and application michael@0: * or toolkit version. michael@0: */ michael@0: includesItem: function BlocklistItemData_includesItem(version, appVersion, toolkitVersion) { michael@0: // Some platforms have no version for plugins, these don't match if there michael@0: // was a min/maxVersion provided michael@0: if (!version && (this.minVersion || this.maxVersion)) michael@0: return false; michael@0: michael@0: // Check if the item version matches michael@0: if (!this.matchesRange(version, this.minVersion, this.maxVersion)) michael@0: return false; michael@0: michael@0: // Check if the application version matches michael@0: if (this.matchesTargetRange(gApp.ID, appVersion)) michael@0: return true; michael@0: michael@0: // Check if the toolkit version matches michael@0: return this.matchesTargetRange(TOOLKIT_ID, toolkitVersion); michael@0: }, michael@0: michael@0: /** michael@0: * Checks if a version is higher than or equal to the minVersion (if provided) michael@0: * and lower than or equal to the maxVersion (if provided). michael@0: * @param version michael@0: * The version to test. michael@0: * @param minVersion michael@0: * The minimum version. If null it is assumed that version is always michael@0: * larger. michael@0: * @param maxVersion michael@0: * The maximum version. If null it is assumed that version is always michael@0: * smaller. michael@0: */ michael@0: matchesRange: function BlocklistItemData_matchesRange(version, minVersion, maxVersion) { michael@0: if (minVersion && gVersionChecker.compare(version, minVersion) < 0) michael@0: return false; michael@0: if (maxVersion && gVersionChecker.compare(version, maxVersion) > 0) michael@0: return false; michael@0: return true; michael@0: }, michael@0: michael@0: /** michael@0: * Tests if there is a matching range for the given target application id and michael@0: * version. michael@0: * @param appID michael@0: * The application ID to test for, may be for an application or toolkit michael@0: * @param appVersion michael@0: * The version of the application to test for. michael@0: * @returns True if this version range covers the application version given. michael@0: */ michael@0: matchesTargetRange: function BlocklistItemData_matchesTargetRange(appID, appVersion) { michael@0: var blTargetApp = this.targetApps[appID]; michael@0: if (!blTargetApp) michael@0: return false; michael@0: michael@0: for (let app of blTargetApp) { michael@0: if (this.matchesRange(appVersion, app.minVersion, app.maxVersion)) michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: }, michael@0: michael@0: /** michael@0: * Retrieves a version range (e.g. minVersion and maxVersion) for a michael@0: * blocklist item's targetApplication element. michael@0: * @param targetAppElement michael@0: * A targetApplication blocklist element. michael@0: * @returns An array of JS objects with the following properties: michael@0: * "minVersion" The minimum version in a version range (default = null). michael@0: * "maxVersion" The maximum version in a version range (default = null). michael@0: */ michael@0: getBlocklistAppVersions: function BlocklistItemData_getBlocklistAppVersions(targetAppElement) { michael@0: var appVersions = [ ]; michael@0: michael@0: if (targetAppElement) { michael@0: for (var i = 0; i < targetAppElement.childNodes.length; ++i) { michael@0: var versionRangeElement = targetAppElement.childNodes.item(i); michael@0: if (!(versionRangeElement instanceof Ci.nsIDOMElement) || michael@0: versionRangeElement.localName != "versionRange") michael@0: continue; michael@0: appVersions.push(this.getBlocklistVersionRange(versionRangeElement)); michael@0: } michael@0: } michael@0: // return minVersion = null and maxVersion = null if no specific versionRange michael@0: // elements were found michael@0: if (appVersions.length == 0) michael@0: appVersions.push(this.getBlocklistVersionRange(null)); michael@0: return appVersions; michael@0: }, michael@0: michael@0: /** michael@0: * Retrieves a version range (e.g. minVersion and maxVersion) for a blocklist michael@0: * versionRange element. michael@0: * @param versionRangeElement michael@0: * The versionRange blocklist element. michael@0: * @returns A JS object with the following properties: michael@0: * "minVersion" The minimum version in a version range (default = null). michael@0: * "maxVersion" The maximum version in a version range (default = null). michael@0: */ michael@0: getBlocklistVersionRange: function BlocklistItemData_getBlocklistVersionRange(versionRangeElement) { michael@0: var minVersion = null; michael@0: var maxVersion = null; michael@0: if (!versionRangeElement) michael@0: return { minVersion: minVersion, maxVersion: maxVersion }; michael@0: michael@0: if (versionRangeElement.hasAttribute("minVersion")) michael@0: minVersion = versionRangeElement.getAttribute("minVersion"); michael@0: if (versionRangeElement.hasAttribute("maxVersion")) michael@0: maxVersion = versionRangeElement.getAttribute("maxVersion"); michael@0: michael@0: return { minVersion: minVersion, maxVersion: maxVersion }; michael@0: } michael@0: }; michael@0: michael@0: this.NSGetFactory = XPCOMUtils.generateNSGetFactory([Blocklist]);