diff options
Diffstat (limited to 'toolkit/mozapps/extensions')
-rw-r--r-- | toolkit/mozapps/extensions/content/extensions.js | 8 | ||||
-rw-r--r-- | toolkit/mozapps/extensions/content/newaddon.js | 6 | ||||
-rw-r--r-- | toolkit/mozapps/extensions/content/selectAddons.js | 4 | ||||
-rw-r--r-- | toolkit/mozapps/extensions/extensions.manifest | 4 | ||||
-rw-r--r-- | toolkit/mozapps/extensions/internal/Content.js | 2 | ||||
-rw-r--r-- | toolkit/mozapps/extensions/internal/GMPProvider.jsm | 7 | ||||
-rw-r--r-- | toolkit/mozapps/extensions/internal/ProductAddonChecker.jsm | 467 | ||||
-rw-r--r-- | toolkit/mozapps/extensions/internal/XPIProvider.jsm | 1 | ||||
-rw-r--r-- | toolkit/mozapps/extensions/internal/XPIProviderUtils.js | 8 | ||||
-rw-r--r-- | toolkit/mozapps/extensions/internal/moz.build | 2 | ||||
-rw-r--r-- | toolkit/mozapps/extensions/moz.build | 1 | ||||
-rw-r--r-- | toolkit/mozapps/extensions/nsBlocklistService.js | 1478 |
12 files changed, 487 insertions, 1501 deletions
diff --git a/toolkit/mozapps/extensions/content/extensions.js b/toolkit/mozapps/extensions/content/extensions.js index fc4392231..1e185f879 100644 --- a/toolkit/mozapps/extensions/content/extensions.js +++ b/toolkit/mozapps/extensions/content/extensions.js @@ -4,10 +4,10 @@ "use strict"; -const Cc = Components.classes; -const Ci = Components.interfaces; -const Cu = Components.utils; -const Cr = Components.results; +var Cc = Components.classes; +var Ci = Components.interfaces; +var Cu = Components.utils; +var Cr = Components.results; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); diff --git a/toolkit/mozapps/extensions/content/newaddon.js b/toolkit/mozapps/extensions/content/newaddon.js index 2b2a54af5..e45b3c1cc 100644 --- a/toolkit/mozapps/extensions/content/newaddon.js +++ b/toolkit/mozapps/extensions/content/newaddon.js @@ -2,9 +2,9 @@ * 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 Cc = Components.classes; -const Ci = Components.interfaces; -const Cu = Components.utils; +var Cc = Components.classes; +var Ci = Components.interfaces; +var Cu = Components.utils; Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/AddonManager.jsm"); diff --git a/toolkit/mozapps/extensions/content/selectAddons.js b/toolkit/mozapps/extensions/content/selectAddons.js index f80932b3f..01b9030ee 100644 --- a/toolkit/mozapps/extensions/content/selectAddons.js +++ b/toolkit/mozapps/extensions/content/selectAddons.js @@ -10,8 +10,8 @@ Components.utils.import("resource://gre/modules/AddonManager.jsm"); Components.utils.import("resource://gre/modules/addons/AddonRepository.jsm"); Components.utils.import("resource://gre/modules/Services.jsm"); -const Cc = Components.classes; -const Ci = Components.interfaces; +var Cc = Components.classes; +var Ci = Components.interfaces; var gView = null; diff --git a/toolkit/mozapps/extensions/extensions.manifest b/toolkit/mozapps/extensions/extensions.manifest index 14aca3ff6..7efb74a9d 100644 --- a/toolkit/mozapps/extensions/extensions.manifest +++ b/toolkit/mozapps/extensions/extensions.manifest @@ -1,7 +1,3 @@ -component {66354bc9-7ed1-4692-ae1d-8da97d6b205e} nsBlocklistService.js -contract @mozilla.org/extensions/blocklist;1 {66354bc9-7ed1-4692-ae1d-8da97d6b205e} -category profile-after-change nsBlocklistService @mozilla.org/extensions/blocklist;1 -category update-timer nsBlocklistService @mozilla.org/extensions/blocklist;1,getService,blocklist-background-update-timer,extensions.blocklist.interval,86400 component {4399533d-08d1-458c-a87a-235f74451cfa} addonManager.js contract @mozilla.org/addons/integration;1 {4399533d-08d1-458c-a87a-235f74451cfa} category update-timer addonManager @mozilla.org/addons/integration;1,getService,addon-background-update-timer,extensions.update.interval,86400 diff --git a/toolkit/mozapps/extensions/internal/Content.js b/toolkit/mozapps/extensions/internal/Content.js index 61a8b0323..9ab3b9ad6 100644 --- a/toolkit/mozapps/extensions/internal/Content.js +++ b/toolkit/mozapps/extensions/internal/Content.js @@ -6,7 +6,7 @@ (function() { -const {classes: Cc, interfaces: Ci, utils: Cu} = Components; +var {classes: Cc, interfaces: Ci, utils: Cu} = Components; var {Services} = Cu.import("resource://gre/modules/Services.jsm", {}); diff --git a/toolkit/mozapps/extensions/internal/GMPProvider.jsm b/toolkit/mozapps/extensions/internal/GMPProvider.jsm index 52affa9ba..25651f1b8 100644 --- a/toolkit/mozapps/extensions/internal/GMPProvider.jsm +++ b/toolkit/mozapps/extensions/internal/GMPProvider.jsm @@ -4,9 +4,9 @@ "use strict"; -const Cc = Components.classes; -const Ci = Components.interfaces; -const Cu = Components.utils; +var Cc = Components.classes; +var Ci = Components.interfaces; +var Cu = Components.utils; this.EXPORTED_SYMBOLS = []; @@ -58,6 +58,7 @@ const GMP_PLUGINS = [ optionsURL: "chrome://mozapps/content/extensions/gmpPrefs.xul", isEME: true }]; +XPCOMUtils.defineConstant(this, "GMP_PLUGINS", GMP_PLUGINS); XPCOMUtils.defineLazyGetter(this, "pluginsBundle", () => Services.strings.createBundle("chrome://global/locale/plugins.properties")); diff --git a/toolkit/mozapps/extensions/internal/ProductAddonChecker.jsm b/toolkit/mozapps/extensions/internal/ProductAddonChecker.jsm new file mode 100644 index 000000000..f98dd2a94 --- /dev/null +++ b/toolkit/mozapps/extensions/internal/ProductAddonChecker.jsm @@ -0,0 +1,467 @@ +/* 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/. */ + +"use strict"; + +const { classes: Cc, interfaces: Ci, utils: Cu } = Components; + +const LOCAL_EME_SOURCES = [{ + "id": "gmp-eme-adobe", + "src": "chrome://global/content/gmp-sources/eme-adobe.json" +}, { + "id": "gmp-gmpopenh264", + "src": "chrome://global/content/gmp-sources/openh264.json" +}, { + "id": "gmp-widevinecdm", + "src": "chrome://global/content/gmp-sources/widevinecdm.json" +}]; + +this.EXPORTED_SYMBOLS = [ "ProductAddonChecker" ]; + +Cu.importGlobalProperties(["XMLHttpRequest"]); + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/Task.jsm"); +Cu.import("resource://gre/modules/Log.jsm"); +Cu.import("resource://gre/modules/CertUtils.jsm"); +/* globals checkCert, BadCertHandler*/ +Cu.import("resource://gre/modules/FileUtils.jsm"); +Cu.import("resource://gre/modules/NetUtil.jsm"); +Cu.import("resource://gre/modules/osfile.jsm"); + +/* globals GMPPrefs */ +XPCOMUtils.defineLazyModuleGetter(this, "GMPPrefs", + "resource://gre/modules/GMPUtils.jsm"); + +/* globals OS */ + +XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils", + "resource://gre/modules/UpdateUtils.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "ServiceRequest", + "resource://gre/modules/ServiceRequest.jsm"); + +// This exists so that tests can override the XHR behaviour for downloading +// the addon update XML file. +var CreateXHR = function() { + return Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]. + createInstance(Ci.nsISupports); +} + +var logger = Log.repository.getLogger("addons.productaddons"); + +/** + * Number of milliseconds after which we need to cancel `downloadXML`. + * + * Bug 1087674 suggests that the XHR we use in `downloadXML` may + * never terminate in presence of network nuisances (e.g. strange + * antivirus behavior). This timeout is a defensive measure to ensure + * that we fail cleanly in such case. + */ +const TIMEOUT_DELAY_MS = 20000; +// Chunk size for the incremental downloader +const DOWNLOAD_CHUNK_BYTES_SIZE = 300000; +// Incremental downloader interval +const DOWNLOAD_INTERVAL = 0; +// How much of a file to read into memory at a time for hashing +const HASH_CHUNK_SIZE = 8192; + +/** + * Gets the status of an XMLHttpRequest either directly or from its underlying + * channel. + * + * @param request + * The XMLHttpRequest. + * @return an integer status value. + */ +function getRequestStatus(request) { + let status = null; + try { + status = request.status; + } + catch (e) { + } + + if (status != null) { + return status; + } + + return request.channel.QueryInterface(Ci.nsIRequest).status; +} + +/** + * Downloads an XML document from a URL optionally testing the SSL certificate + * for certain attributes. + * + * @param url + * The url to download from. + * @param allowNonBuiltIn + * Whether to trust SSL certificates without a built-in CA issuer. + * @param allowedCerts + * The list of certificate attributes to match the SSL certificate + * against or null to skip checks. + * @return a promise that resolves to the DOM document downloaded or rejects + * with a JS exception in case of error. + */ +function downloadXML(url, allowNonBuiltIn = false, allowedCerts = null) { + return new Promise((resolve, reject) => { + let request = CreateXHR(); + // This is here to let unit test code override XHR + if (request.wrappedJSObject) { + request = request.wrappedJSObject; + } + request.open("GET", url, true); + request.channel.notificationCallbacks = new BadCertHandler(allowNonBuiltIn); + // Prevent the request from reading from the cache. + request.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE; + // Prevent the request from writing to the cache. + request.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING; + // Use conservative TLS settings. See bug 1325501. + // TODO move to ServiceRequest. + if (request.channel instanceof Ci.nsIHttpChannelInternal) { + request.channel.QueryInterface(Ci.nsIHttpChannelInternal).beConservative = true; + } + request.timeout = TIMEOUT_DELAY_MS; + + request.overrideMimeType("text/xml"); + // The Cache-Control header is only interpreted by proxies and the + // final destination. It does not help if a resource is already + // cached locally. + request.setRequestHeader("Cache-Control", "no-cache"); + // HTTP/1.0 servers might not implement Cache-Control and + // might only implement Pragma: no-cache + request.setRequestHeader("Pragma", "no-cache"); + + let fail = (event) => { + let request = event.target; + let status = getRequestStatus(request); + let message = "Failed downloading XML, status: " + status + ", reason: " + event.type; + logger.warn(message); + let ex = new Error(message); + ex.status = status; + reject(ex); + }; + + let success = (event) => { + logger.info("Completed downloading document"); + let request = event.target; + + try { + checkCert(request.channel, allowNonBuiltIn, allowedCerts); + } catch (ex) { + logger.error("Request failed certificate checks: " + ex); + ex.status = getRequestStatus(request); + reject(ex); + return; + } + + resolve(request.responseXML); + }; + + request.addEventListener("error", fail, false); + request.addEventListener("abort", fail, false); + request.addEventListener("timeout", fail, false); + request.addEventListener("load", success, false); + + logger.info("sending request to: " + url); + request.send(null); + }); +} + +function downloadJSON(uri) { + logger.info("fetching config from: " + uri); + return new Promise((resolve, reject) => { + let xmlHttp = new ServiceRequest({mozAnon: true}); + + xmlHttp.onload = function(aResponse) { + resolve(JSON.parse(this.responseText)); + }; + + xmlHttp.onerror = function(e) { + reject("Fetching " + uri + " results in error code: " + e.target.status); + }; + + xmlHttp.open("GET", uri); + xmlHttp.overrideMimeType("application/json"); + xmlHttp.send(); + }); +} + + +/** + * Parses a list of add-ons from a DOM document. + * + * @param document + * The DOM document to parse. + * @return null if there is no <addons> element otherwise an object containing + * an array of the addons listed and a field notifying whether the + * fallback was used. + */ +function parseXML(document) { + // Check that the root element is correct + if (document.documentElement.localName != "updates") { + throw new Error("got node name: " + document.documentElement.localName + + ", expected: updates"); + } + + // Check if there are any addons elements in the updates element + let addons = document.querySelector("updates:root > addons"); + if (!addons) { + return null; + } + + let results = []; + let addonList = document.querySelectorAll("updates:root > addons > addon"); + for (let addonElement of addonList) { + let addon = {}; + + for (let name of ["id", "URL", "hashFunction", "hashValue", "version", "size"]) { + if (addonElement.hasAttribute(name)) { + addon[name] = addonElement.getAttribute(name); + } + } + addon.size = Number(addon.size) || undefined; + + results.push(addon); + } + + return { + usedFallback: false, + gmpAddons: results + }; +} + +/** + * If downloading from the network fails (AUS server is down), + * load the sources from local build configuration. + */ +function downloadLocalConfig() { + + if (!GMPPrefs.get(GMPPrefs.KEY_UPDATE_ENABLED, true)) { + logger.info("Updates are disabled via media.gmp-manager.updateEnabled"); + return Promise.resolve({usedFallback: true, gmpAddons: []}); + } + + return Promise.all(LOCAL_EME_SOURCES.map(conf => { + return downloadJSON(conf.src).then(addons => { + + let platforms = addons.vendors[conf.id].platforms; + let target = Services.appinfo.OS + "_" + UpdateUtils.ABI; + let details = null; + + while (!details) { + if (!(target in platforms)) { + // There was no matching platform so return false, this addon + // will be filtered from the results below + logger.info("no details found for: " + target); + return false; + } + // Field either has the details of the binary or is an alias + // to another build target key that does + if (platforms[target].alias) { + target = platforms[target].alias; + } else { + details = platforms[target]; + } + } + + logger.info("found plugin: " + conf.id); + return { + "id": conf.id, + "URL": details.fileUrl, + "hashFunction": addons.hashFunction, + "hashValue": details.hashValue, + "version": addons.vendors[conf.id].version, + "size": details.filesize + }; + }); + })).then(addons => { + + // Some filters may not match this platform so + // filter those out + addons = addons.filter(x => x !== false); + + return { + usedFallback: true, + gmpAddons: addons + }; + }); +} + +/** + * Downloads file from a URL using XHR. + * + * @param url + * The url to download from. + * @return a promise that resolves to the path of a temporary file or rejects + * with a JS exception in case of error. + */ +function downloadFile(url) { + return new Promise((resolve, reject) => { + let xhr = new XMLHttpRequest(); + xhr.onload = function(response) { + logger.info("downloadXHR File download. status=" + xhr.status); + if (xhr.status != 200 && xhr.status != 206) { + reject(Components.Exception("File download failed", xhr.status)); + return; + } + Task.spawn(function* () { + let f = yield OS.File.openUnique(OS.Path.join(OS.Constants.Path.tmpDir, "tmpaddon")); + let path = f.path; + logger.info(`Downloaded file will be saved to ${path}`); + yield f.file.close(); + yield OS.File.writeAtomic(path, new Uint8Array(xhr.response)); + return path; + }).then(resolve, reject); + }; + + let fail = (event) => { + let request = event.target; + let status = getRequestStatus(request); + let message = "Failed downloading via XHR, status: " + status + ", reason: " + event.type; + logger.warn(message); + let ex = new Error(message); + ex.status = status; + reject(ex); + }; + xhr.addEventListener("error", fail); + xhr.addEventListener("abort", fail); + + xhr.responseType = "arraybuffer"; + try { + xhr.open("GET", url); + // Use conservative TLS settings. See bug 1325501. + // TODO move to ServiceRequest. + if (xhr.channel instanceof Ci.nsIHttpChannelInternal) { + xhr.channel.QueryInterface(Ci.nsIHttpChannelInternal).beConservative = true; + } + xhr.send(null); + } catch (ex) { + reject(ex); + } + }); +} + +/** + * Convert a string containing binary values to hex. + */ +function binaryToHex(input) { + let result = ""; + for (let i = 0; i < input.length; ++i) { + let hex = input.charCodeAt(i).toString(16); + if (hex.length == 1) { + hex = "0" + hex; + } + result += hex; + } + return result; +} + +/** + * Calculates the hash of a file. + * + * @param hashFunction + * The type of hash function to use, must be supported by nsICryptoHash. + * @param path + * The path of the file to hash. + * @return a promise that resolves to hash of the file or rejects with a JS + * exception in case of error. + */ +var computeHash = Task.async(function*(hashFunction, path) { + let file = yield OS.File.open(path, { existing: true, read: true }); + try { + let hasher = Cc["@mozilla.org/security/hash;1"]. + createInstance(Ci.nsICryptoHash); + hasher.initWithString(hashFunction); + + let bytes; + do { + bytes = yield file.read(HASH_CHUNK_SIZE); + hasher.update(bytes, bytes.length); + } while (bytes.length == HASH_CHUNK_SIZE); + + return binaryToHex(hasher.finish(false)); + } + finally { + yield file.close(); + } +}); + +/** + * Verifies that a downloaded file matches what was expected. + * + * @param properties + * The properties to check, `size` and `hashFunction` with `hashValue` + * are supported. Any properties missing won't be checked. + * @param path + * The path of the file to check. + * @return a promise that resolves if the file matched or rejects with a JS + * exception in case of error. + */ +var verifyFile = Task.async(function*(properties, path) { + if (properties.size !== undefined) { + let stat = yield OS.File.stat(path); + if (stat.size != properties.size) { + throw new Error("Downloaded file was " + stat.size + " bytes but expected " + properties.size + " bytes."); + } + } + + if (properties.hashFunction !== undefined) { + let expectedDigest = properties.hashValue.toLowerCase(); + let digest = yield computeHash(properties.hashFunction, path); + if (digest != expectedDigest) { + throw new Error("Hash was `" + digest + "` but expected `" + expectedDigest + "`."); + } + } +}); + +const ProductAddonChecker = { + /** + * Downloads a list of add-ons from a URL optionally testing the SSL + * certificate for certain attributes. + * + * @param url + * The url to download from. + * @param allowNonBuiltIn + * Whether to trust SSL certificates without a built-in CA issuer. + * @param allowedCerts + * The list of certificate attributes to match the SSL certificate + * against or null to skip checks. + * @return a promise that resolves to an object containing the list of add-ons + * and whether the local fallback was used, or rejects with a JS + * exception in case of error. + */ + getProductAddonList: function(url, allowNonBuiltIn = false, allowedCerts = null) { + if (!GMPPrefs.get(GMPPrefs.KEY_UPDATE_ENABLED, true)) { + logger.info("Updates are disabled via media.gmp-manager.updateEnabled"); + return Promise.resolve({usedFallback: true, gmpAddons: []}); + } + + return downloadXML(url, allowNonBuiltIn, allowedCerts) + .then(parseXML) + .catch(downloadLocalConfig); + }, + + /** + * Downloads an add-on to a local file and checks that it matches the expected + * file. The caller is responsible for deleting the temporary file returned. + * + * @param addon + * The addon to download. + * @return a promise that resolves to the temporary file downloaded or rejects + * with a JS exception in case of error. + */ + downloadAddon: Task.async(function*(addon) { + let path = yield downloadFile(addon.URL); + try { + yield verifyFile(addon, path); + return path; + } + catch (e) { + yield OS.File.remove(path); + throw e; + } + }) +} diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.jsm b/toolkit/mozapps/extensions/internal/XPIProvider.jsm index 5b3585cd8..b522bd3ae 100644 --- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm @@ -144,6 +144,7 @@ const FIREFOX_APPCOMPATVERSION = "56.9" // The value for this is in Makefile.in #expand const DB_SCHEMA = __MOZ_EXTENSIONS_DB_SCHEMA__; +XPCOMUtils.defineConstant(this, "DB_SCHEMA", DB_SCHEMA); #ifdef MOZ_DEVTOOLS const NOTIFICATION_TOOLBOXPROCESS_LOADED = "ToolboxProcessLoaded"; #endif diff --git a/toolkit/mozapps/extensions/internal/XPIProviderUtils.js b/toolkit/mozapps/extensions/internal/XPIProviderUtils.js index 6b37ed640..f2420bd1a 100644 --- a/toolkit/mozapps/extensions/internal/XPIProviderUtils.js +++ b/toolkit/mozapps/extensions/internal/XPIProviderUtils.js @@ -4,10 +4,10 @@ "use strict"; -const Cc = Components.classes; -const Ci = Components.interfaces; -const Cr = Components.results; -const Cu = Components.utils; +var Cc = Components.classes; +var Ci = Components.interfaces; +var Cr = Components.results; +var Cu = Components.utils; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); diff --git a/toolkit/mozapps/extensions/internal/moz.build b/toolkit/mozapps/extensions/internal/moz.build index efcd2af21..337df3104 100644 --- a/toolkit/mozapps/extensions/internal/moz.build +++ b/toolkit/mozapps/extensions/internal/moz.build @@ -5,12 +5,12 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. EXTRA_JS_MODULES.addons += [ - '../../webextensions/internal/ProductAddonChecker.jsm', 'AddonLogging.jsm', 'AddonRepository_SQLiteMigrator.jsm', 'Content.js', 'GMPProvider.jsm', 'LightweightThemeImageOptimizer.jsm', + 'ProductAddonChecker.jsm', 'SpellCheckDictionaryBootstrap.js', ] diff --git a/toolkit/mozapps/extensions/moz.build b/toolkit/mozapps/extensions/moz.build index 19f449210..ca04d74a0 100644 --- a/toolkit/mozapps/extensions/moz.build +++ b/toolkit/mozapps/extensions/moz.build @@ -25,7 +25,6 @@ EXTRA_COMPONENTS += [ EXTRA_PP_COMPONENTS += [ 'extensions.manifest', - 'nsBlocklistService.js', ] EXTRA_JS_MODULES += [ diff --git a/toolkit/mozapps/extensions/nsBlocklistService.js b/toolkit/mozapps/extensions/nsBlocklistService.js deleted file mode 100644 index 487dae8e5..000000000 --- a/toolkit/mozapps/extensions/nsBlocklistService.js +++ /dev/null @@ -1,1478 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ - -/* 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/. */ - -"use strict"; - -const Cc = Components.classes; -const Ci = Components.interfaces; -const Cr = Components.results; - -Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); -Components.utils.import("resource://gre/modules/Services.jsm"); - -try { - // AddonManager.jsm doesn't allow itself to be imported in the child - // process. We're used in the child process (for now), so guard against - // this. - Components.utils.import("resource://gre/modules/AddonManager.jsm"); -} catch (e) { -} - -XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", - "resource://gre/modules/FileUtils.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "UpdateChannel", - "resource://gre/modules/UpdateChannel.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "OS", - "resource://gre/modules/osfile.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "Task", - "resource://gre/modules/Task.jsm"); - -const TOOLKIT_ID = "toolkit@mozilla.org" -const KEY_PROFILEDIR = "ProfD"; -const KEY_APPDIR = "XCurProcD"; -const FILE_BLOCKLIST = "blocklist.xml"; -const PREF_BLOCKLIST_LASTUPDATETIME = "app.update.lastUpdateTime.blocklist-background-update-timer"; -const PREF_BLOCKLIST_URL = "extensions.blocklist.url"; -const PREF_BLOCKLIST_ITEM_URL = "extensions.blocklist.itemURL"; -const PREF_BLOCKLIST_ENABLED = "extensions.blocklist.enabled"; -const PREF_BLOCKLIST_INTERVAL = "extensions.blocklist.interval"; -const PREF_BLOCKLIST_LEVEL = "extensions.blocklist.level"; -const PREF_BLOCKLIST_PINGCOUNTTOTAL = "extensions.blocklist.pingCountTotal"; -const PREF_BLOCKLIST_PINGCOUNTVERSION = "extensions.blocklist.pingCountVersion"; -const PREF_BLOCKLIST_SUPPRESSUI = "extensions.blocklist.suppressUI"; -const PREF_PLUGINS_NOTIFYUSER = "plugins.update.notifyUser"; -const PREF_GENERAL_USERAGENT_LOCALE = "general.useragent.locale"; -const PREF_APP_DISTRIBUTION = "distribution.id"; -const PREF_APP_DISTRIBUTION_VERSION = "distribution.version"; -const PREF_EM_LOGGING_ENABLED = "extensions.logging.enabled"; -const XMLURI_BLOCKLIST = "http://www.mozilla.org/2006/addons-blocklist"; -const XMLURI_PARSE_ERROR = "http://www.mozilla.org/newlayout/xml/parsererror.xml" -const UNKNOWN_XPCOM_ABI = "unknownABI"; -const URI_BLOCKLIST_DIALOG = "chrome://mozapps/content/extensions/blocklist.xul" -const DEFAULT_SEVERITY = 3; -const DEFAULT_LEVEL = 2; -const MAX_BLOCK_LEVEL = 3; -const SEVERITY_OUTDATED = 0; -const VULNERABILITYSTATUS_NONE = 0; -const VULNERABILITYSTATUS_UPDATE_AVAILABLE = 1; -const VULNERABILITYSTATUS_NO_UPDATE = 2; - -const EXTENSION_BLOCK_FILTERS = ["id", "name", "creator", "homepageURL", "updateURL"]; - -var gLoggingEnabled = null; -var gBlocklistEnabled = true; -var gBlocklistLevel = DEFAULT_LEVEL; - -XPCOMUtils.defineLazyServiceGetter(this, "gConsole", - "@mozilla.org/consoleservice;1", - "nsIConsoleService"); - -XPCOMUtils.defineLazyServiceGetter(this, "gVersionChecker", - "@mozilla.org/xpcom/version-comparator;1", - "nsIVersionComparator"); - -XPCOMUtils.defineLazyServiceGetter(this, "gCertBlocklistService", - "@mozilla.org/security/certblocklist;1", - "nsICertBlocklist"); - -XPCOMUtils.defineLazyGetter(this, "gPref", function bls_gPref() { - return Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefService). - QueryInterface(Ci.nsIPrefBranch); -}); - -XPCOMUtils.defineLazyGetter(this, "gApp", function bls_gApp() { - return Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo). - QueryInterface(Ci.nsIXULRuntime); -}); - -XPCOMUtils.defineLazyGetter(this, "gABI", function bls_gABI() { - let abi = null; - try { - abi = gApp.XPCOMABI; - } - catch (e) { - LOG("BlockList Global gABI: XPCOM ABI unknown."); - } -#ifdef XP_MACOSX - // Mac universal build should report a different ABI than either macppc - // or mactel. - let macutils = Cc["@mozilla.org/xpcom/mac-utils;1"]. - getService(Ci.nsIMacUtils); - - if (macutils.isUniversalBinary) - abi += "-u-" + macutils.architecturesInBinary; -#endif - return abi; -}); - -XPCOMUtils.defineLazyGetter(this, "gOSVersion", function bls_gOSVersion() { - let osVersion; - let sysInfo = Cc["@mozilla.org/system-info;1"]. - getService(Ci.nsIPropertyBag2); - try { - osVersion = sysInfo.getProperty("name") + " " + sysInfo.getProperty("version"); - } - catch (e) { - LOG("BlockList Global gOSVersion: OS Version unknown."); - } - - if (osVersion) { - try { - osVersion += " (" + sysInfo.getProperty("secondaryLibrary") + ")"; - } - catch (e) { - // Not all platforms have a secondary widget library, so an error is nothing to worry about. - } - osVersion = encodeURIComponent(osVersion); - } - return osVersion; -}); - -// shared code for suppressing bad cert dialogs -XPCOMUtils.defineLazyGetter(this, "gCertUtils", function bls_gCertUtils() { - let temp = { }; - Components.utils.import("resource://gre/modules/CertUtils.jsm", temp); - return temp; -}); - -/** - * Logs a string to the error console. - * @param string - * The string to write to the error console.. - */ -function LOG(string) { - if (gLoggingEnabled) { - dump("*** " + string + "\n"); - gConsole.logStringMessage(string); - } -} - -/** - * Gets a preference value, handling the case where there is no default. - * @param func - * The name of the preference function to call, on nsIPrefBranch - * @param preference - * The name of the preference - * @param defaultValue - * The default value to return in the event the preference has - * no setting - * @returns The value of the preference, or undefined if there was no - * user or default value. - */ -function getPref(func, preference, defaultValue) { - try { - return gPref[func](preference); - } - catch (e) { - } - return defaultValue; -} - -/** - * Constructs a URI to a spec. - * @param spec - * The spec to construct a URI to - * @returns The nsIURI constructed. - */ -function newURI(spec) { - var ioServ = Cc["@mozilla.org/network/io-service;1"]. - getService(Ci.nsIIOService); - return ioServ.newURI(spec, null, null); -} - -// Restarts the application checking in with observers first -function restartApp() { - // Notify all windows that an application quit has been requested. - var os = Cc["@mozilla.org/observer-service;1"]. - getService(Ci.nsIObserverService); - var cancelQuit = Cc["@mozilla.org/supports-PRBool;1"]. - createInstance(Ci.nsISupportsPRBool); - os.notifyObservers(cancelQuit, "quit-application-requested", null); - - // Something aborted the quit process. - if (cancelQuit.data) - return; - - var as = Cc["@mozilla.org/toolkit/app-startup;1"]. - getService(Ci.nsIAppStartup); - as.quit(Ci.nsIAppStartup.eRestart | Ci.nsIAppStartup.eAttemptQuit); -} - -/** - * Checks whether this blocklist element is valid for the current OS and ABI. - * If the element has an "os" attribute then the current OS must appear in - * its comma separated list for the element to be valid. Similarly for the - * xpcomabi attribute. - */ -function matchesOSABI(blocklistElement) { - if (blocklistElement.hasAttribute("os")) { - var choices = blocklistElement.getAttribute("os").split(","); - if (choices.length > 0 && choices.indexOf(gApp.OS) < 0) - return false; - } - - if (blocklistElement.hasAttribute("xpcomabi")) { - choices = blocklistElement.getAttribute("xpcomabi").split(","); - if (choices.length > 0 && choices.indexOf(gApp.XPCOMABI) < 0) - return false; - } - - return true; -} - -/** - * Gets the current value of the locale. It's possible for this preference to - * be localized, so we have to do a little extra work here. Similar code - * exists in nsHttpHandler.cpp when building the UA string. - */ -function getLocale() { - try { - // Get the default branch - var defaultPrefs = gPref.getDefaultBranch(null); - return defaultPrefs.getComplexValue(PREF_GENERAL_USERAGENT_LOCALE, - Ci.nsIPrefLocalizedString).data; - } catch (e) {} - - return gPref.getCharPref(PREF_GENERAL_USERAGENT_LOCALE); -} - -/* Get the distribution pref values, from defaults only */ -function getDistributionPrefValue(aPrefName) { - var prefValue = "default"; - - var defaults = gPref.getDefaultBranch(null); - try { - prefValue = defaults.getCharPref(aPrefName); - } catch (e) { - // use default when pref not found - } - - return prefValue; -} - -/** - * Parse a string representation of a regular expression. Needed because we - * use the /pattern/flags form (because it's detectable), which is only - * supported as a literal in JS. - * - * @param aStr - * String representation of regexp - * @return RegExp instance - */ -function parseRegExp(aStr) { - let lastSlash = aStr.lastIndexOf("/"); - let pattern = aStr.slice(1, lastSlash); - let flags = aStr.slice(lastSlash + 1); - return new RegExp(pattern, flags); -} - -/** - * Helper function to test if the blockEntry matches with the plugin. - * - * @param blockEntry - * The plugin blocklist entries to compare against. - * @param plugin - * The nsIPluginTag to get the blocklist state for. - * @returns True if the blockEntry matches the plugin, false otherwise. - */ -function matchesAllPluginNames(blockEntry, plugin) { - for (let name in blockEntry.matches) { - if (!(name in plugin) || - typeof(plugin[name]) != "string" || - !blockEntry.matches[name].test(plugin[name])) { - return false; - } - } - return true; -} - -/** - * Manages the Blocklist. The Blocklist is a representation of the contents of - * blocklist.xml and allows us to remotely disable / re-enable blocklisted - * items managed by the Extension Manager with an item's appDisabled property. - * It also blocklists plugins with data from blocklist.xml. - */ - -function Blocklist() { - Services.obs.addObserver(this, "xpcom-shutdown", false); - Services.obs.addObserver(this, "sessionstore-windows-restored", false); - gLoggingEnabled = getPref("getBoolPref", PREF_EM_LOGGING_ENABLED, false); - gBlocklistEnabled = getPref("getBoolPref", PREF_BLOCKLIST_ENABLED, true); - gBlocklistLevel = Math.min(getPref("getIntPref", PREF_BLOCKLIST_LEVEL, DEFAULT_LEVEL), - MAX_BLOCK_LEVEL); - gPref.addObserver("extensions.blocklist.", this, false); - gPref.addObserver(PREF_EM_LOGGING_ENABLED, this, false); - this.wrappedJSObject = this; -} - -Blocklist.prototype = { - /** - * Extension ID -> array of Version Ranges - * Each value in the version range array is a JS Object that has the - * following properties: - * "minVersion" The minimum version in a version range (default = 0) - * "maxVersion" The maximum version in a version range (default = *) - * "targetApps" Application ID -> array of Version Ranges - * (default = current application ID) - * Each value in the version range array is a JS Object that - * has the following properties: - * "minVersion" The minimum version in a version range - * (default = 0) - * "maxVersion" The maximum version in a version range - * (default = *) - */ - _addonEntries: null, - _pluginEntries: null, - - observe: function Blocklist_observe(aSubject, aTopic, aData) { - switch (aTopic) { - case "xpcom-shutdown": - Services.obs.removeObserver(this, "xpcom-shutdown"); - gPref.removeObserver("extensions.blocklist.", this); - gPref.removeObserver(PREF_EM_LOGGING_ENABLED, this); - break; - case "nsPref:changed": - switch (aData) { - case PREF_EM_LOGGING_ENABLED: - gLoggingEnabled = getPref("getBoolPref", PREF_EM_LOGGING_ENABLED, false); - break; - case PREF_BLOCKLIST_ENABLED: - gBlocklistEnabled = getPref("getBoolPref", PREF_BLOCKLIST_ENABLED, true); - this._loadBlocklist(); - this._blocklistUpdated(null, null); - break; - case PREF_BLOCKLIST_LEVEL: - gBlocklistLevel = Math.min(getPref("getIntPref", PREF_BLOCKLIST_LEVEL, DEFAULT_LEVEL), - MAX_BLOCK_LEVEL); - this._blocklistUpdated(null, null); - break; - } - break; - case "sessionstore-windows-restored": - Services.obs.removeObserver(this, "sessionstore-windows-restored"); - this._preloadBlocklist(); - break; - } - }, - - /* See nsIBlocklistService */ - isAddonBlocklisted: function Blocklist_isAddonBlocklisted(addon, appVersion, toolkitVersion) { - return this.getAddonBlocklistState(addon, appVersion, toolkitVersion) == - Ci.nsIBlocklistService.STATE_BLOCKED; - }, - - /* See nsIBlocklistService */ - getAddonBlocklistState: function Blocklist_getAddonBlocklistState(addon, appVersion, toolkitVersion) { - if (!this._isBlocklistLoaded()) - this._loadBlocklist(); - return this._getAddonBlocklistState(addon, this._addonEntries, - appVersion, toolkitVersion); - }, - - /** - * Private version of getAddonBlocklistState that allows the caller to pass in - * the add-on blocklist entries to compare against. - * - * @param id - * The ID of the item to get the blocklist state for. - * @param version - * The version of the item to get the blocklist state for. - * @param addonEntries - * The add-on blocklist entries to compare against. - * @param appVersion - * The application version to compare to, will use the current - * version if null. - * @param toolkitVersion - * The toolkit version to compare to, will use the current version if - * null. - * @returns The blocklist state for the item, one of the STATE constants as - * defined in nsIBlocklistService. - */ - _getAddonBlocklistState: function Blocklist_getAddonBlocklistStateCall(addon, - addonEntries, appVersion, toolkitVersion) { - if (!gBlocklistEnabled) - return Ci.nsIBlocklistService.STATE_NOT_BLOCKED; - - if (!appVersion) - appVersion = gApp.version; - if (!toolkitVersion) - toolkitVersion = gApp.platformVersion; - - var blItem = this._findMatchingAddonEntry(addonEntries, addon); - if (!blItem) - return Ci.nsIBlocklistService.STATE_NOT_BLOCKED; - - for (let currentblItem of blItem.versions) { - if (currentblItem.includesItem(addon.version, appVersion, toolkitVersion)) - return currentblItem.severity >= gBlocklistLevel ? Ci.nsIBlocklistService.STATE_BLOCKED : - Ci.nsIBlocklistService.STATE_SOFTBLOCKED; - } - return Ci.nsIBlocklistService.STATE_NOT_BLOCKED; - }, - - /** - * Returns the set of prefs of the add-on stored in the blocklist file - * (probably to revert them on disabling). - * @param addon - * The add-on whose to-be-reset prefs are to be found. - */ - _getAddonPrefs: function Blocklist_getAddonPrefs(addon) { - let entry = this._findMatchingAddonEntry(this._addonEntries, addon); - return entry.prefs.slice(0); - }, - - _findMatchingAddonEntry: function Blocklist_findMatchingAddonEntry(aAddonEntries, - aAddon) { - if (!aAddon) - return null; - // Returns true if the params object passes the constraints set by entry. - // (For every non-null property in entry, the same key must exist in - // params and value must be the same) - function checkEntry(entry, params) { - for (let [key, value] of entry) { - if (value === null || value === undefined) - continue; - if (params[key]) { - if (value instanceof RegExp) { - if (!value.test(params[key])) { - return false; - } - } else if (value !== params[key]) { - return false; - } - } else { - return false; - } - } - return true; - } - - let params = {}; - for (let filter of EXTENSION_BLOCK_FILTERS) { - params[filter] = aAddon[filter]; - } - if (params.creator) - params.creator = params.creator.name; - for (let entry of aAddonEntries) { - if (checkEntry(entry.attributes, params)) { - return entry; - } - } - return null; - }, - - /* See nsIBlocklistService */ - getAddonBlocklistURL: function Blocklist_getAddonBlocklistURL(addon, appVersion, toolkitVersion) { - if (!gBlocklistEnabled) - return ""; - - if (!this._isBlocklistLoaded()) - this._loadBlocklist(); - - let blItem = this._findMatchingAddonEntry(this._addonEntries, addon); - if (!blItem || !blItem.blockID) - return null; - - return this._createBlocklistURL(blItem.blockID); - }, - - _createBlocklistURL: function Blocklist_createBlocklistURL(id) { - let url = Services.urlFormatter.formatURLPref(PREF_BLOCKLIST_ITEM_URL); - url = url.replace(/%blockID%/g, id); - - return url; - }, - - notify: function Blocklist_notify(aTimer) { - if (!gBlocklistEnabled) - return; - - try { - var dsURI = gPref.getCharPref(PREF_BLOCKLIST_URL); - } - catch (e) { - LOG("Blocklist::notify: The " + PREF_BLOCKLIST_URL + " preference" + - " is missing!"); - return; - } - - var pingCountVersion = getPref("getIntPref", PREF_BLOCKLIST_PINGCOUNTVERSION, 0); - var pingCountTotal = getPref("getIntPref", PREF_BLOCKLIST_PINGCOUNTTOTAL, 1); - var daysSinceLastPing = 0; - if (pingCountVersion == 0) { - daysSinceLastPing = "new"; - } - else { - // Seconds in one day is used because nsIUpdateTimerManager stores the - // last update time in seconds. - let secondsInDay = 60 * 60 * 24; - let lastUpdateTime = getPref("getIntPref", PREF_BLOCKLIST_LASTUPDATETIME, 0); - if (lastUpdateTime == 0) { - daysSinceLastPing = "invalid"; - } - else { - let now = Math.round(Date.now() / 1000); - daysSinceLastPing = Math.floor((now - lastUpdateTime) / secondsInDay); - } - - if (daysSinceLastPing == 0 || daysSinceLastPing == "invalid") { - pingCountVersion = pingCountTotal = "invalid"; - } - } - - if (pingCountVersion < 1) - pingCountVersion = 1; - if (pingCountTotal < 1) - pingCountTotal = 1; - - dsURI = dsURI.replace(/%APP_ID%/g, gApp.ID); - dsURI = dsURI.replace(/%APP_VERSION%/g, gApp.version); - dsURI = dsURI.replace(/%PRODUCT%/g, gApp.name); - dsURI = dsURI.replace(/%VERSION%/g, gApp.version); - dsURI = dsURI.replace(/%BUILD_ID%/g, gApp.appBuildID); - dsURI = dsURI.replace(/%BUILD_TARGET%/g, gApp.OS + "_" + gABI); - dsURI = dsURI.replace(/%OS_VERSION%/g, gOSVersion); - dsURI = dsURI.replace(/%LOCALE%/g, getLocale()); - dsURI = dsURI.replace(/%CHANNEL%/g, UpdateChannel.get()); - dsURI = dsURI.replace(/%PLATFORM_VERSION%/g, gApp.platformVersion); - dsURI = dsURI.replace(/%DISTRIBUTION%/g, - getDistributionPrefValue(PREF_APP_DISTRIBUTION)); - dsURI = dsURI.replace(/%DISTRIBUTION_VERSION%/g, - getDistributionPrefValue(PREF_APP_DISTRIBUTION_VERSION)); - dsURI = dsURI.replace(/%PING_COUNT%/g, pingCountVersion); - dsURI = dsURI.replace(/%TOTAL_PING_COUNT%/g, pingCountTotal); - dsURI = dsURI.replace(/%DAYS_SINCE_LAST_PING%/g, daysSinceLastPing); - dsURI = dsURI.replace(/\+/g, "%2B"); - - // Under normal operations it will take around 5,883,516 years before the - // preferences used to store pingCountVersion and pingCountTotal will rollover - // so this code doesn't bother trying to do the "right thing" here. - if (pingCountVersion != "invalid") { - pingCountVersion++; - if (pingCountVersion > 2147483647) { - // Rollover to -1 if the value is greater than what is support by an - // integer preference. The -1 indicates that the counter has been reset. - pingCountVersion = -1; - } - gPref.setIntPref(PREF_BLOCKLIST_PINGCOUNTVERSION, pingCountVersion); - } - - if (pingCountTotal != "invalid") { - pingCountTotal++; - if (pingCountTotal > 2147483647) { - // Rollover to 1 if the value is greater than what is support by an - // integer preference. - pingCountTotal = -1; - } - gPref.setIntPref(PREF_BLOCKLIST_PINGCOUNTTOTAL, pingCountTotal); - } - - // Verify that the URI is valid - try { - var uri = newURI(dsURI); - } - catch (e) { - LOG("Blocklist::notify: There was an error creating the blocklist URI\r\n" + - "for: " + dsURI + ", error: " + e); - return; - } - - LOG("Blocklist::notify: Requesting " + uri.spec); - var request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]. - createInstance(Ci.nsIXMLHttpRequest); - request.open("GET", uri.spec, true); - request.channel.notificationCallbacks = new gCertUtils.BadCertHandler(); - request.overrideMimeType("text/xml"); - request.setRequestHeader("Cache-Control", "no-cache"); - request.QueryInterface(Components.interfaces.nsIJSXMLHttpRequest); - - var self = this; - request.addEventListener("error", function errorEventListener(event) { - self.onXMLError(event); }, false); - request.addEventListener("load", function loadEventListener(event) { - self.onXMLLoad(event); }, false); - request.send(null); - - // When the blocklist loads we need to compare it to the current copy so - // make sure we have loaded it. - if (!this._isBlocklistLoaded()) - this._loadBlocklist(); - }, - - onXMLLoad: Task.async(function* (aEvent) { - let request = aEvent.target; - try { - gCertUtils.checkCert(request.channel); - } - catch (e) { - LOG("Blocklist::onXMLLoad: " + e); - return; - } - let responseXML = request.responseXML; - if (!responseXML || responseXML.documentElement.namespaceURI == XMLURI_PARSE_ERROR || - (request.status != 200 && request.status != 0)) { - LOG("Blocklist::onXMLLoad: there was an error during load"); - return; - } - - var oldAddonEntries = this._addonEntries; - var oldPluginEntries = this._pluginEntries; - this._addonEntries = []; - this._pluginEntries = []; - - this._loadBlocklistFromString(request.responseText); - this._blocklistUpdated(oldAddonEntries, oldPluginEntries); - - try { - let path = OS.Path.join(OS.Constants.Path.profileDir, FILE_BLOCKLIST); - yield OS.File.writeAtomic(path, request.responseText, {tmpPath: path + ".tmp"}); - } catch (e) { - LOG("Blocklist::onXMLLoad: " + e); - } - }), - - onXMLError: function Blocklist_onXMLError(aEvent) { - try { - var request = aEvent.target; - // the following may throw (e.g. a local file or timeout) - var status = request.status; - } - catch (e) { - request = aEvent.target.channel.QueryInterface(Ci.nsIRequest); - status = request.status; - } - var statusText = "nsIXMLHttpRequest channel unavailable"; - // When status is 0 we don't have a valid channel. - if (status != 0) { - try { - statusText = request.statusText; - } catch (e) { - } - } - LOG("Blocklist:onError: There was an error loading the blocklist file\r\n" + - statusText); - }, - - /** - * Finds the newest blocklist file from the application and the profile and - * load it or does nothing if neither exist. - */ - _loadBlocklist: function Blocklist_loadBlocklist() { - this._addonEntries = []; - this._pluginEntries = []; - var profFile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_BLOCKLIST]); - if (profFile.exists()) { - this._loadBlocklistFromFile(profFile); - return; - } - var appFile = FileUtils.getFile(KEY_APPDIR, [FILE_BLOCKLIST]); - if (appFile.exists()) { - this._loadBlocklistFromFile(appFile); - return; - } - LOG("Blocklist::_loadBlocklist: no XML File found"); - }, - - /** -# The blocklist XML file looks something like this: -# -# <blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist"> -# <emItems> -# <emItem id="item_1@domain" blockID="i1"> -# <prefs> -# <pref>accessibility.accesskeycausesactivation</pref> -# <pref>accessibility.blockautorefresh</pref> -# </prefs> -# <versionRange minVersion="1.0" maxVersion="2.0.*"> -# <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}"> -# <versionRange minVersion="1.5" maxVersion="1.5.*"/> -# <versionRange minVersion="1.7" maxVersion="1.7.*"/> -# </targetApplication> -# <targetApplication id="toolkit@mozilla.org"> -# <versionRange minVersion="1.9" maxVersion="1.9.*"/> -# </targetApplication> -# </versionRange> -# <versionRange minVersion="3.0" maxVersion="3.0.*"> -# <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}"> -# <versionRange minVersion="1.5" maxVersion="1.5.*"/> -# </targetApplication> -# <targetApplication id="toolkit@mozilla.org"> -# <versionRange minVersion="1.9" maxVersion="1.9.*"/> -# </targetApplication> -# </versionRange> -# </emItem> -# <emItem id="item_2@domain" blockID="i2"> -# <versionRange minVersion="3.1" maxVersion="4.*"/> -# </emItem> -# <emItem id="item_3@domain"> -# <versionRange> -# <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}"> -# <versionRange minVersion="1.5" maxVersion="1.5.*"/> -# </targetApplication> -# </versionRange> -# </emItem> -# <emItem id="item_4@domain" blockID="i3"> -# <versionRange> -# <targetApplication> -# <versionRange minVersion="1.5" maxVersion="1.5.*"/> -# </targetApplication> -# </versionRange> -# <emItem id="/@badperson\.com$/"/> -# </emItems> -# <pluginItems> -# <pluginItem blockID="i4"> -# <!-- All match tags must match a plugin to blocklist a plugin --> -# <match name="name" exp="some plugin"/> -# <match name="description" exp="1[.]2[.]3"/> -# </pluginItem> -# </pluginItems> -# <certItems> -# <!-- issuerName is the DER issuer name data base64 encoded... --> -# <certItem issuerName="MA0xCzAJBgNVBAMMAmNh"> -# <!-- ... as is the serial number DER data --> -# <serialNumber>AkHVNA==</serialNumber> -# </certItem> -# </certItems> -# </blocklist> - */ - - _loadBlocklistFromFile: function Blocklist_loadBlocklistFromFile(file) { - if (!gBlocklistEnabled) { - LOG("Blocklist::_loadBlocklistFromFile: blocklist is disabled"); - return; - } - - let telemetry = Services.telemetry; - - if (this._isBlocklistPreloaded()) { - telemetry.getHistogramById("BLOCKLIST_SYNC_FILE_LOAD").add(false); - this._loadBlocklistFromString(this._preloadedBlocklistContent); - delete this._preloadedBlocklistContent; - return; - } - - if (!file.exists()) { - LOG("Blocklist::_loadBlocklistFromFile: XML File does not exist " + file.path); - return; - } - - telemetry.getHistogramById("BLOCKLIST_SYNC_FILE_LOAD").add(true); - - let text = ""; - let fstream = null; - let cstream = null; - - try { - fstream = Components.classes["@mozilla.org/network/file-input-stream;1"] - .createInstance(Components.interfaces.nsIFileInputStream); - cstream = Components.classes["@mozilla.org/intl/converter-input-stream;1"] - .createInstance(Components.interfaces.nsIConverterInputStream); - - fstream.init(file, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0); - cstream.init(fstream, "UTF-8", 0, 0); - - let str = {}; - let read = 0; - - do { - read = cstream.readString(0xffffffff, str); // read as much as we can and put it in str.value - text += str.value; - } while (read != 0); - } catch (e) { - LOG("Blocklist::_loadBlocklistFromFile: Failed to load XML file " + e); - } finally { - if (cstream) - cstream.close(); - if (fstream) - fstream.close(); - } - - if (text) - this._loadBlocklistFromString(text); - }, - - _isBlocklistLoaded: function() { - return this._addonEntries != null && this._pluginEntries != null; - }, - - _isBlocklistPreloaded: function() { - return this._preloadedBlocklistContent != null; - }, - - /* Used for testing */ - _clear: function() { - this._addonEntries = null; - this._pluginEntries = null; - this._preloadedBlocklistContent = null; - }, - - _preloadBlocklist: Task.async(function*() { - let profPath = OS.Path.join(OS.Constants.Path.profileDir, FILE_BLOCKLIST); - try { - yield this._preloadBlocklistFile(profPath); - return; - } catch (e) { - LOG("Blocklist::_preloadBlocklist: Failed to load XML file " + e) - } - - var appFile = FileUtils.getFile(KEY_APPDIR, [FILE_BLOCKLIST]); - try{ - yield this._preloadBlocklistFile(appFile.path); - return; - } catch (e) { - LOG("Blocklist::_preloadBlocklist: Failed to load XML file " + e) - } - - LOG("Blocklist::_preloadBlocklist: no XML File found"); - }), - - _preloadBlocklistFile: Task.async(function* (path){ - if (this._addonEntries) { - // The file has been already loaded. - return; - } - - if (!gBlocklistEnabled) { - LOG("Blocklist::_preloadBlocklistFile: blocklist is disabled"); - return; - } - - let text = yield OS.File.read(path, { encoding: "utf-8" }); - - if (!this._addonEntries) { - // Store the content only if a sync load has not been performed in the meantime. - this._preloadedBlocklistContent = text; - } - }), - - _loadBlocklistFromString : function Blocklist_loadBlocklistFromString(text) { - try { - var parser = Cc["@mozilla.org/xmlextras/domparser;1"]. - createInstance(Ci.nsIDOMParser); - var doc = parser.parseFromString(text, "text/xml"); - if (doc.documentElement.namespaceURI != XMLURI_BLOCKLIST) { - LOG("Blocklist::_loadBlocklistFromFile: aborting due to incorrect " + - "XML Namespace.\r\nExpected: " + XMLURI_BLOCKLIST + "\r\n" + - "Received: " + doc.documentElement.namespaceURI); - return; - } - - var childNodes = doc.documentElement.childNodes; - for (let element of childNodes) { - if (!(element instanceof Ci.nsIDOMElement)) - continue; - switch (element.localName) { - case "emItems": - this._addonEntries = this._processItemNodes(element.childNodes, "em", - this._handleEmItemNode); - break; - case "pluginItems": - this._pluginEntries = this._processItemNodes(element.childNodes, "plugin", - this._handlePluginItemNode); - break; - case "certItems": - this._processItemNodes(element.childNodes, "cert", - this._handleCertItemNode.bind(this)); - break; - default: - Services.obs.notifyObservers(element, - "blocklist-data-" + element.localName, - null); - } - } - gCertBlocklistService.saveEntries(); - } - catch (e) { - LOG("Blocklist::_loadBlocklistFromFile: Error constructing blocklist " + e); - return; - } - }, - - _processItemNodes: function Blocklist_processItemNodes(itemNodes, prefix, handler) { - var result = []; - var itemName = prefix + "Item"; - for (var i = 0; i < itemNodes.length; ++i) { - var blocklistElement = itemNodes.item(i); - if (!(blocklistElement instanceof Ci.nsIDOMElement) || - blocklistElement.localName != itemName) - continue; - - handler(blocklistElement, result); - } - return result; - }, - - _handleCertItemNode: function Blocklist_handleCertItemNode(blocklistElement, - result) { - let issuer = blocklistElement.getAttribute("issuerName"); - for (let snElement of blocklistElement.children) { - try { - gCertBlocklistService.revokeCertByIssuerAndSerial(issuer, snElement.textContent); - } catch (e) { - // we want to keep trying other elements since missing all items - // is worse than missing one - LOG("Blocklist::_handleCertItemNode: Error adding revoked cert " + e); - } - } - }, - - _handleEmItemNode: function Blocklist_handleEmItemNode(blocklistElement, result) { - if (!matchesOSABI(blocklistElement)) - return; - - let blockEntry = { - versions: [], - prefs: [], - blockID: null, - attributes: new Map() - // Atleast one of EXTENSION_BLOCK_FILTERS must get added to attributes - }; - - // Any filter starting with '/' is interpreted as a regex. So if an attribute - // starts with a '/' it must be checked via a regex. - function regExpCheck(attr) { - return attr.startsWith("/") ? parseRegExp(attr) : attr; - } - - for (let filter of EXTENSION_BLOCK_FILTERS) { - let attr = blocklistElement.getAttribute(filter); - if (attr) - blockEntry.attributes.set(filter, regExpCheck(attr)); - } - - var childNodes = blocklistElement.childNodes; - - for (let x = 0; x < childNodes.length; x++) { - var childElement = childNodes.item(x); - if (!(childElement instanceof Ci.nsIDOMElement)) - continue; - if (childElement.localName === "prefs") { - let prefElements = childElement.childNodes; - for (let i = 0; i < prefElements.length; i++) { - let prefElement = prefElements.item(i); - if (!(prefElement instanceof Ci.nsIDOMElement) || - prefElement.localName !== "pref") - continue; - blockEntry.prefs.push(prefElement.textContent); - } - } - else if (childElement.localName === "versionRange") - blockEntry.versions.push(new BlocklistItemData(childElement)); - } - // if only the extension ID is specified block all versions of the - // extension for the current application. - if (blockEntry.versions.length == 0) - blockEntry.versions.push(new BlocklistItemData(null)); - - blockEntry.blockID = blocklistElement.getAttribute("blockID"); - - result.push(blockEntry); - }, - - _handlePluginItemNode: function Blocklist_handlePluginItemNode(blocklistElement, result) { - if (!matchesOSABI(blocklistElement)) - return; - - var matchNodes = blocklistElement.childNodes; - var blockEntry = { - matches: {}, - versions: [], - blockID: null, - infoURL: null, - }; - var hasMatch = false; - for (var x = 0; x < matchNodes.length; ++x) { - var matchElement = matchNodes.item(x); - if (!(matchElement instanceof Ci.nsIDOMElement)) - continue; - if (matchElement.localName == "match") { - var name = matchElement.getAttribute("name"); - var exp = matchElement.getAttribute("exp"); - try { - blockEntry.matches[name] = new RegExp(exp, "m"); - hasMatch = true; - } catch (e) { - // Ignore invalid regular expressions - } - } - if (matchElement.localName == "versionRange") { - blockEntry.versions.push(new BlocklistItemData(matchElement)); - } - else if (matchElement.localName == "infoURL") { - blockEntry.infoURL = matchElement.textContent; - } - } - // Plugin entries require *something* to match to an actual plugin - if (!hasMatch) - return; - // Add a default versionRange if there wasn't one specified - if (blockEntry.versions.length == 0) - blockEntry.versions.push(new BlocklistItemData(null)); - - blockEntry.blockID = blocklistElement.getAttribute("blockID"); - - result.push(blockEntry); - }, - - /* See nsIBlocklistService */ - getPluginBlocklistState: function Blocklist_getPluginBlocklistState(plugin, - appVersion, toolkitVersion) { - if (!this._isBlocklistLoaded()) - this._loadBlocklist(); - return this._getPluginBlocklistState(plugin, this._pluginEntries, - appVersion, toolkitVersion); - }, - - /** - * Private version of getPluginBlocklistState that allows the caller to pass in - * the plugin blocklist entries. - * - * @param plugin - * The nsIPluginTag to get the blocklist state for. - * @param pluginEntries - * The plugin blocklist entries to compare against. - * @param appVersion - * The application version to compare to, will use the current - * version if null. - * @param toolkitVersion - * The toolkit version to compare to, will use the current version if - * null. - * @returns The blocklist state for the item, one of the STATE constants as - * defined in nsIBlocklistService. - */ - _getPluginBlocklistState: function Blocklist_getPluginBlocklistState(plugin, - pluginEntries, appVersion, toolkitVersion) { - if (!gBlocklistEnabled) - return Ci.nsIBlocklistService.STATE_NOT_BLOCKED; - - if (!appVersion) - appVersion = gApp.version; - if (!toolkitVersion) - toolkitVersion = gApp.platformVersion; - - for each (var blockEntry in pluginEntries) { - var matchFailed = false; - for (var name in blockEntry.matches) { - if (!(name in plugin) || - typeof(plugin[name]) != "string" || - !blockEntry.matches[name].test(plugin[name])) { - matchFailed = true; - break; - } - } - - if (matchFailed) - continue; - - for (let blockEntryVersion of blockEntry.versions) { - if (blockEntryVersion.includesItem(plugin.version, appVersion, - toolkitVersion)) { - if (blockEntryVersion.severity >= gBlocklistLevel) - return Ci.nsIBlocklistService.STATE_BLOCKED; - if (blockEntryVersion.severity == SEVERITY_OUTDATED) { - let vulnerabilityStatus = blockEntryVersion.vulnerabilityStatus; - if (vulnerabilityStatus == VULNERABILITYSTATUS_UPDATE_AVAILABLE) - return Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE; - if (vulnerabilityStatus == VULNERABILITYSTATUS_NO_UPDATE) - return Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE; - return Ci.nsIBlocklistService.STATE_OUTDATED; - } - return Ci.nsIBlocklistService.STATE_SOFTBLOCKED; - } - } - } - - return Ci.nsIBlocklistService.STATE_NOT_BLOCKED; - }, - - /** - * Get the matching blocklist entry for the passed plugin, if - * available. - * @param plugin The plugin to find the block entry for. - * @returns The block entry which matches the passed plugin, null - * otherwise. - */ - _getPluginBlockEntry: function (plugin) { - if (!gBlocklistEnabled) - return null; - - if (!this._isBlocklistLoaded()) - this._loadBlocklist(); - - for each (let blockEntry in this._pluginEntries) { - if (matchesAllPluginNames(blockEntry, plugin)) { - return blockEntry; - } - } - - return null; - }, - - /* See nsIBlocklistService */ - getPluginBlocklistURL: function Blocklist_getPluginBlocklistURL(plugin) { - let blockEntry = this._getPluginBlockEntry(plugin); - if (!blockEntry || !blockEntry.blockID) { - return null; - } - - return this._createBlocklistURL(blockEntry.blockID); - }, - - /* See nsIBlocklistService */ - getPluginInfoURL: function (plugin) { - let blockEntry = this._getPluginBlockEntry(plugin); - if (!blockEntry || !blockEntry.blockID) { - return null; - } - - return blockEntry.infoURL; - }, - - _blocklistUpdated: function Blocklist_blocklistUpdated(oldAddonEntries, oldPluginEntries) { - var addonList = []; - - // A helper function that reverts the prefs passed to default values. - function resetPrefs(prefs) { - for (let pref of prefs) - gPref.clearUserPref(pref); - } - var self = this; - const types = ["extension", "theme", "locale", "dictionary", "service"]; - AddonManager.getAddonsByTypes(types, function blocklistUpdated_getAddonsByTypes(addons) { - - for (let addon of addons) { - let oldState = Ci.nsIBlocklistService.STATE_NOTBLOCKED; - if (oldAddonEntries) - oldState = self._getAddonBlocklistState(addon, oldAddonEntries); - let state = self.getAddonBlocklistState(addon); - - LOG("Blocklist state for " + addon.id + " changed from " + - oldState + " to " + state); - - // We don't want to re-warn about add-ons - if (state == oldState) - continue; - - if (state === Ci.nsIBlocklistService.STATE_BLOCKED) { - // It's a hard block. We must reset certain preferences. - let prefs = self._getAddonPrefs(addon); - resetPrefs(prefs); - } - - // Ensure that softDisabled is false if the add-on is not soft blocked - if (state != Ci.nsIBlocklistService.STATE_SOFTBLOCKED) - addon.softDisabled = false; - - // Don't warn about add-ons becoming unblocked. - if (state == Ci.nsIBlocklistService.STATE_NOT_BLOCKED) - continue; - - // If an add-on has dropped from hard to soft blocked just mark it as - // soft disabled and don't warn about it. - if (state == Ci.nsIBlocklistService.STATE_SOFTBLOCKED && - oldState == Ci.nsIBlocklistService.STATE_BLOCKED) { - addon.softDisabled = true; - continue; - } - - // If the add-on is already disabled for some reason then don't warn - // about it - if (!addon.isActive) - continue; - - addonList.push({ - name: addon.name, - version: addon.version, - icon: addon.iconURL, - disable: false, - blocked: state == Ci.nsIBlocklistService.STATE_BLOCKED, - item: addon, - url: self.getAddonBlocklistURL(addon), - }); - } - - AddonManagerPrivate.updateAddonAppDisabledStates(); - - var phs = Cc["@mozilla.org/plugin/host;1"]. - getService(Ci.nsIPluginHost); - var plugins = phs.getPluginTags(); - - for (let plugin of plugins) { - let oldState = -1; - if (oldPluginEntries) - oldState = self._getPluginBlocklistState(plugin, oldPluginEntries); - let state = self.getPluginBlocklistState(plugin); - LOG("Blocklist state for " + plugin.name + " changed from " + - oldState + " to " + state); - // We don't want to re-warn about items - if (state == oldState) - continue; - - if (oldState == Ci.nsIBlocklistService.STATE_BLOCKED) { - if (state == Ci.nsIBlocklistService.STATE_SOFTBLOCKED) - plugin.enabledState = Ci.nsIPluginTag.STATE_DISABLED; - } - else if (!plugin.disabled && state != Ci.nsIBlocklistService.STATE_NOT_BLOCKED) { - if (state == Ci.nsIBlocklistService.STATE_OUTDATED) { - gPref.setBoolPref(PREF_PLUGINS_NOTIFYUSER, true); - } - else if (state != Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE && - state != Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE) { - addonList.push({ - name: plugin.name, - version: plugin.version, - icon: "chrome://mozapps/skin/plugins/pluginGeneric.png", - disable: false, - blocked: state == Ci.nsIBlocklistService.STATE_BLOCKED, - item: plugin, - url: self.getPluginBlocklistURL(plugin), - }); - } - } - } - - if (addonList.length == 0) { - Services.obs.notifyObservers(self, "blocklist-updated", ""); - return; - } - - if ("@mozilla.org/addons/blocklist-prompt;1" in Cc) { - try { - let blockedPrompter = Cc["@mozilla.org/addons/blocklist-prompt;1"] - .getService(Ci.nsIBlocklistPrompt); - blockedPrompter.prompt(addonList); - } catch (e) { - LOG(e); - } - Services.obs.notifyObservers(self, "blocklist-updated", ""); - return; - } - - var args = { - restart: false, - list: addonList - }; - // This lets the dialog get the raw js object - args.wrappedJSObject = args; - - /* - Some tests run without UI, so the async code listens to a message - that can be sent programatically - */ - let applyBlocklistChanges = function blocklistUpdated_applyBlocklistChanges() { - for (let addon of addonList) { - if (!addon.disable) - continue; - - if (addon.item instanceof Ci.nsIPluginTag) - addon.item.enabledState = Ci.nsIPluginTag.STATE_DISABLED; - else { - // This add-on is softblocked. - addon.item.softDisabled = true; - // We must revert certain prefs. - let prefs = self._getAddonPrefs(addon.item); - resetPrefs(prefs); - } - } - - if (args.restart) - restartApp(); - - Services.obs.notifyObservers(self, "blocklist-updated", ""); - Services.obs.removeObserver(applyBlocklistChanges, "addon-blocklist-closed"); - } - - Services.obs.addObserver(applyBlocklistChanges, "addon-blocklist-closed", false); - - if (getPref("getBoolPref", PREF_BLOCKLIST_SUPPRESSUI, false)) { - applyBlocklistChanges(); - return; - } - - function blocklistUnloadHandler(event) { - if (event.target.location == URI_BLOCKLIST_DIALOG) { - applyBlocklistChanges(); - blocklistWindow.removeEventListener("unload", blocklistUnloadHandler); - } - } - - let blocklistWindow = Services.ww.openWindow(null, URI_BLOCKLIST_DIALOG, "", - "chrome,centerscreen,dialog,titlebar", args); - if (blocklistWindow) - blocklistWindow.addEventListener("unload", blocklistUnloadHandler, false); - }); - }, - - classID: Components.ID("{66354bc9-7ed1-4692-ae1d-8da97d6b205e}"), - QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, - Ci.nsIBlocklistService, - Ci.nsITimerCallback]), -}; - -/** - * Helper for constructing a blocklist. - */ -function BlocklistItemData(versionRangeElement) { - var versionRange = this.getBlocklistVersionRange(versionRangeElement); - this.minVersion = versionRange.minVersion; - this.maxVersion = versionRange.maxVersion; - if (versionRangeElement && versionRangeElement.hasAttribute("severity")) - this.severity = versionRangeElement.getAttribute("severity"); - else - this.severity = DEFAULT_SEVERITY; - if (versionRangeElement && versionRangeElement.hasAttribute("vulnerabilitystatus")) { - this.vulnerabilityStatus = versionRangeElement.getAttribute("vulnerabilitystatus"); - } else { - this.vulnerabilityStatus = VULNERABILITYSTATUS_NONE; - } - this.targetApps = { }; - var found = false; - - if (versionRangeElement) { - for (var i = 0; i < versionRangeElement.childNodes.length; ++i) { - var targetAppElement = versionRangeElement.childNodes.item(i); - if (!(targetAppElement instanceof Ci.nsIDOMElement) || - targetAppElement.localName != "targetApplication") - continue; - found = true; - // default to the current application if id is not provided. - var appID = targetAppElement.hasAttribute("id") ? targetAppElement.getAttribute("id") : gApp.ID; - this.targetApps[appID] = this.getBlocklistAppVersions(targetAppElement); - } - } - // Default to all versions of the current application when no targetApplication - // elements were found - if (!found) - this.targetApps[gApp.ID] = this.getBlocklistAppVersions(null); -} - -BlocklistItemData.prototype = { - /** - * Tests if a version of an item is included in the version range and target - * application information represented by this BlocklistItemData using the - * provided application and toolkit versions. - * @param version - * The version of the item being tested. - * @param appVersion - * The application version to test with. - * @param toolkitVersion - * The toolkit version to test with. - * @returns True if the version range covers the item version and application - * or toolkit version. - */ - includesItem: function BlocklistItemData_includesItem(version, appVersion, toolkitVersion) { - // Some platforms have no version for plugins, these don't match if there - // was a min/maxVersion provided - if (!version && (this.minVersion || this.maxVersion)) - return false; - - // Check if the item version matches - if (!this.matchesRange(version, this.minVersion, this.maxVersion)) - return false; - - // Check if the application version matches - if (this.matchesTargetRange(gApp.ID, appVersion)) - return true; - - // Check if the toolkit version matches - return this.matchesTargetRange(TOOLKIT_ID, toolkitVersion); - }, - - /** - * Checks if a version is higher than or equal to the minVersion (if provided) - * and lower than or equal to the maxVersion (if provided). - * @param version - * The version to test. - * @param minVersion - * The minimum version. If null it is assumed that version is always - * larger. - * @param maxVersion - * The maximum version. If null it is assumed that version is always - * smaller. - */ - matchesRange: function BlocklistItemData_matchesRange(version, minVersion, maxVersion) { - if (minVersion && gVersionChecker.compare(version, minVersion) < 0) - return false; - if (maxVersion && gVersionChecker.compare(version, maxVersion) > 0) - return false; - return true; - }, - - /** - * Tests if there is a matching range for the given target application id and - * version. - * @param appID - * The application ID to test for, may be for an application or toolkit - * @param appVersion - * The version of the application to test for. - * @returns True if this version range covers the application version given. - */ - matchesTargetRange: function BlocklistItemData_matchesTargetRange(appID, appVersion) { - var blTargetApp = this.targetApps[appID]; - if (!blTargetApp) - return false; - - for (let app of blTargetApp) { - if (this.matchesRange(appVersion, app.minVersion, app.maxVersion)) - return true; - } - - return false; - }, - - /** - * Retrieves a version range (e.g. minVersion and maxVersion) for a - * blocklist item's targetApplication element. - * @param targetAppElement - * A targetApplication blocklist element. - * @returns An array of JS objects with the following properties: - * "minVersion" The minimum version in a version range (default = null). - * "maxVersion" The maximum version in a version range (default = null). - */ - getBlocklistAppVersions: function BlocklistItemData_getBlocklistAppVersions(targetAppElement) { - var appVersions = [ ]; - - if (targetAppElement) { - for (var i = 0; i < targetAppElement.childNodes.length; ++i) { - var versionRangeElement = targetAppElement.childNodes.item(i); - if (!(versionRangeElement instanceof Ci.nsIDOMElement) || - versionRangeElement.localName != "versionRange") - continue; - appVersions.push(this.getBlocklistVersionRange(versionRangeElement)); - } - } - // return minVersion = null and maxVersion = null if no specific versionRange - // elements were found - if (appVersions.length == 0) - appVersions.push(this.getBlocklistVersionRange(null)); - return appVersions; - }, - - /** - * Retrieves a version range (e.g. minVersion and maxVersion) for a blocklist - * versionRange element. - * @param versionRangeElement - * The versionRange blocklist element. - * @returns A JS object with the following properties: - * "minVersion" The minimum version in a version range (default = null). - * "maxVersion" The maximum version in a version range (default = null). - */ - getBlocklistVersionRange: function BlocklistItemData_getBlocklistVersionRange(versionRangeElement) { - var minVersion = null; - var maxVersion = null; - if (!versionRangeElement) - return { minVersion: minVersion, maxVersion: maxVersion }; - - if (versionRangeElement.hasAttribute("minVersion")) - minVersion = versionRangeElement.getAttribute("minVersion"); - if (versionRangeElement.hasAttribute("maxVersion")) - maxVersion = versionRangeElement.getAttribute("maxVersion"); - - return { minVersion: minVersion, maxVersion: maxVersion }; - } -}; - -this.NSGetFactory = XPCOMUtils.generateNSGetFactory([Blocklist]); |