diff --git a/browser/installer/unix/packages-static b/browser/installer/unix/packages-static index d9296584e0e..1a0617dd63d 100644 --- a/browser/installer/unix/packages-static +++ b/browser/installer/unix/packages-static @@ -218,6 +218,7 @@ bin/components/nsSidebar.js ; bin/components/nsUpdateNotifier.js not needed for firefox bin/components/nsXmlRpcClient.js bin/components/nsExtensionManager.js +bin/components/nsBlocklistService.js bin/components/nsUpdateService.js bin/components/pluginGlue.js bin/components/extensions.xpt diff --git a/browser/installer/windows/packages-static b/browser/installer/windows/packages-static index 03413559837..528d4b085d0 100644 --- a/browser/installer/windows/packages-static +++ b/browser/installer/windows/packages-static @@ -211,6 +211,7 @@ bin\components\nsSearchSuggestions.js bin\components\nsSidebar.js bin\components\nsXmlRpcClient.js bin\components\nsExtensionManager.js +bin\components\nsBlocklistService.js bin\components\nsUpdateService.js bin\components\nsMicrosummaryService.js bin\components\nsPlacesTransactionsService.js diff --git a/toolkit/mozapps/extensions/public/Makefile.in b/toolkit/mozapps/extensions/public/Makefile.in index 8294dfc678f..1be36b54c8a 100644 --- a/toolkit/mozapps/extensions/public/Makefile.in +++ b/toolkit/mozapps/extensions/public/Makefile.in @@ -44,7 +44,7 @@ include $(DEPTH)/config/autoconf.mk MODULE = extensions XPIDL_MODULE = extensions -XPIDLSRCS = nsIExtensionManager.idl +XPIDLSRCS = nsIExtensionManager.idl nsIBlocklistService.idl include $(topsrcdir)/config/rules.mk diff --git a/toolkit/mozapps/extensions/public/nsIBlocklistService.idl b/toolkit/mozapps/extensions/public/nsIBlocklistService.idl new file mode 100644 index 00000000000..1299a15e2e5 --- /dev/null +++ b/toolkit/mozapps/extensions/public/nsIBlocklistService.idl @@ -0,0 +1,64 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Blocklist Service. + * + * The Initial Developer of the Original Code is + * Mozilla Corporation + * Portions created by the Initial Developer are Copyright (C) 2007 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Michael Wu (original author) + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + + +#include "nsISupports.idl" + +[scriptable, uuid(0c3fe697-d50d-4f42-b747-0c5855cfc60e)] +interface nsIBlocklistService : nsISupports +{ + /** + * Determine if an item is blocklisted + * @param id + * The GUID of the item. + * @param version + * The item's version. + * @param appVersion + * The version of the application we are checking in the blocklist. + * If this parameter is undefined, the version of the running + * application is used. + * @param toolkitVersion + * The version of the toolkit we are checking in the blocklist. + * If this parameter is undefined, the version of the running + * toolkit is used. + * @returns true if the item is compatible with this version of the + * application or this version of the toolkit, false, otherwise. + */ + boolean isAddonBlocklisted(in AString id, in AString version, + in AString appVersion, in AString toolkitVersion); +}; diff --git a/toolkit/mozapps/extensions/src/Makefile.in b/toolkit/mozapps/extensions/src/Makefile.in index df22339cb97..6c7b5e19d58 100644 --- a/toolkit/mozapps/extensions/src/Makefile.in +++ b/toolkit/mozapps/extensions/src/Makefile.in @@ -45,6 +45,7 @@ include $(DEPTH)/config/autoconf.mk MODULE = extensions EXTRA_COMPONENTS = nsExtensionManager.js +EXTRA_PP_COMPONENTS = nsBlocklistService.js GARBAGE += nsExtensionManager.js include $(topsrcdir)/config/rules.mk diff --git a/toolkit/mozapps/extensions/src/nsBlocklistService.js b/toolkit/mozapps/extensions/src/nsBlocklistService.js new file mode 100644 index 00000000000..cc7ce1a4fde --- /dev/null +++ b/toolkit/mozapps/extensions/src/nsBlocklistService.js @@ -0,0 +1,696 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* + # ***** BEGIN LICENSE BLOCK ***** + # Version: MPL 1.1/GPL 2.0/LGPL 2.1 + # + # The contents of this file are subject to the Mozilla Public License Version + # 1.1 (the "License"); you may not use this file except in compliance with + # the License. You may obtain a copy of the License at + # http://www.mozilla.org/MPL/ + # + # Software distributed under the License is distributed on an "AS IS" basis, + # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + # for the specific language governing rights and limitations under the + # License. + # + # The Original Code is the Blocklist Service. + # + # The Initial Developer of the Original Code is + # Mozilla Corporation. + # Portions created by the Initial Developer are Copyright (C) 2007 + # the Initial Developer. All Rights Reserved. + # + # Contributor(s): + # Robert Strong + # Michael Wu + # + # Alternatively, the contents of this file may be used under the terms of + # either the GNU General Public License Version 2 or later (the "GPL"), or + # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + # in which case the provisions of the GPL or the LGPL are applicable instead + # of those above. If you wish to allow use of your version of this file only + # under the terms of either the GPL or the LGPL, and not to allow others to + # use your version of this file under the terms of the MPL, indicate your + # decision by deleting the provisions above and replace them with the notice + # and other provisions required by the GPL or the LGPL. If you do not delete + # the provisions above, a recipient may use your version of this file under + # the terms of any one of the MPL, the GPL or the LGPL. + # + # ***** END LICENSE BLOCK ***** + */ + +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cr = Components.results; + +const kELEMENT_NODE = Ci.nsIDOMNode.ELEMENT_NODE; +const TOOLKIT_ID = "toolkit@mozilla.org" +const KEY_PROFILEDIR = "ProfD"; +const FILE_BLOCKLIST = "blocklist.xml"; +const PREF_BLOCKLIST_URL = "extensions.blocklist.url"; +const PREF_BLOCKLIST_ENABLED = "extensions.blocklist.enabled"; +const PREF_BLOCKLIST_INTERVAL = "extensions.blocklist.interval"; +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 MODE_RDONLY = 0x01; +const MODE_WRONLY = 0x02; +const MODE_CREATE = 0x08; +const MODE_APPEND = 0x10; +const MODE_TRUNCATE = 0x20; + +const PERMS_FILE = 0644; +const PERMS_DIRECTORY = 0755; + +const CID = Components.ID("{66354bc9-7ed1-4692-ae1d-8da97d6b205e}"); +const CONTRACT_ID = "@mozilla.org/extensions/blocklist;1" +const CLASS_NAME = "Blocklist Service"; + +var gApp = null; +var gPref = null; +var gOS = null; +var gConsole = null; +var gVersionChecker = null; +var gLoggingEnabled = null; + +// shared code for suppressing bad cert dialogs +#include ../../shared/src/badCertHandler.js + +/** + * 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; +} + +/** + * Gets the file at the specified hierarchy under a Directory Service key. + * @param key + * The Directory Service Key to start from + * @param pathArray + * An array of path components to locate beneath the directory + * specified by |key|. The last item in this array must be the + * leaf name of a file. + * @return nsIFile object for the file specified. The file is NOT created + * if it does not exist, however all required directories along + * the way are. + */ +function getFile(key, pathArray) { + var fileLocator = Cc["@mozilla.org/file/directory_service;1"]. + getService(Ci.nsIProperties); + var file = fileLocator.get(key, Ci.nsILocalFile); + for (var i = 0; i < pathArray.length - 1; ++i) { + file.append(pathArray[i]); + if (!file.exists()) + file.create(Ci.nsILocalFile.DIRECTORY_TYPE, PERMS_DIRECTORY); + } + file.followLinks = false; + file.append(pathArray[pathArray.length - 1]); + return file; +} + +/** + * Opens a safe file output stream for writing. + * @param file + * The file to write to. + * @param modeFlags + * (optional) File open flags. Can be undefined. + * @returns nsIFileOutputStream to write to. + */ +function openSafeFileOutputStream(file, modeFlags) { + var fos = Cc["@mozilla.org/network/safe-file-output-stream;1"]. + createInstance(Ci.nsIFileOutputStream); + if (modeFlags === undefined) + modeFlags = MODE_WRONLY | MODE_CREATE | MODE_TRUNCATE; + if (!file.exists()) + file.create(Ci.nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE); + fos.init(file, modeFlags, PERMS_FILE, 0); + return fos; +} + +/** + * Closes a safe file output stream. + * @param stream + * The stream to close. + */ +function closeSafeFileOutputStream(stream) { + if (stream instanceof Ci.nsISafeOutputStream) + stream.finish(); + else + stream.close(); +} + +/** + * 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); +} + +/** + * 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() { + gApp = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo); + gApp.QueryInterface(Ci.nsIXULRuntime); + gPref = Cc["@mozilla.org/preferences-service;1"]. + getService(Ci.nsIPrefBranch2); + gVersionChecker = Cc["@mozilla.org/xpcom/version-comparator;1"]. + getService(Ci.nsIVersionComparator); + gConsole = Cc["@mozilla.org/consoleservice;1"]. + getService(Ci.nsIConsoleService); + + gOS = Cc["@mozilla.org/observer-service;1"]. + getService(Ci.nsIObserverService); + gOS.addObserver(this, "xpcom-shutdown", false); +} + +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 (aSubject, aTopic, aData) { + switch (aTopic) { + case "app-startup": + gOS.addObserver(this, "plugins-list-updated", false); + gOS.addObserver(this, "profile-after-change", false); + break; + case "profile-after-change": + gLoggingEnabled = getPref("getBoolPref", PREF_EM_LOGGING_ENABLED, false); + var tm = Cc["@mozilla.org/updates/timer-manager;1"]. + getService(Ci.nsIUpdateTimerManager); + var interval = getPref("getIntPref", PREF_BLOCKLIST_INTERVAL, 86400); + tm.registerTimer("blocklist-background-update-timer", this, interval); + break; + case "plugins-list-updated": + this._checkPluginsList(); + break; + case "xpcom-shutdown": + gOS.removeObserver(this, "xpcom-shutdown"); + gOS.removeObserver(this, "profile-after-change"); + gOS.removeObserver(this, "plugins-list-updated"); + gOS = null; + gPref = null; + gConsole = null; + gVersionChecker = null; + gApp = null; + break; + } + }, + + isAddonBlocklisted: function(id, version, appVersion, toolkitVersion) { + if (!this._addonEntries) + this._loadBlocklistFromFile(getFile(KEY_PROFILEDIR, [FILE_BLOCKLIST])); + if (appVersion === undefined) + appVersion = gApp.version; + if (toolkitVersion === undefined) + toolkitVersion = gApp.platformVersion; + + var blItem = this._addonEntries[id]; + if (!blItem) + return false; + + for (var i = 0; i < blItem.length; ++i) { + if (gVersionChecker.compare(version, blItem[i].minVersion) < 0 || + gVersionChecker.compare(version, blItem[i].maxVersion) > 0) + continue; + + var blTargetApp = blItem[i].targetApps[gApp.ID]; + if (blTargetApp) { + for (var x = 0; x < blTargetApp.length; ++x) { + if (gVersionChecker.compare(appVersion, blTargetApp[x].minVersion) < 0 || + gVersionChecker.compare(appVersion, blTargetApp[x].maxVersion) > 0) + continue; + return true; + } + } + + blTargetApp = blItem[i].targetApps[TOOLKIT_ID]; + if (!blTargetApp) + return false; + for (x = 0; x < blTargetApp.length; ++x) { + if (gVersionChecker.compare(toolkitVersion, blTargetApp[x].minVersion) < 0 || + gVersionChecker.compare(toolkitVersion, blTargetApp[x].maxVersion) > 0) + continue; + return true; + } + } + return false; + }, + + notify: function(aTimer) { + if (getPref("getBoolPref", PREF_BLOCKLIST_ENABLED, true) == false) + return; + + try { + var dsURI = gPref.getCharPref(PREF_BLOCKLIST_URL); + } + catch (e) { + LOG("Blocklist::notify: The " + PREF_BLOCKLIST_URL + " preference" + + " is missing!"); + return; + } + + dsURI = dsURI.replace(/%APP_ID%/g, gApp.ID); + dsURI = dsURI.replace(/%APP_VERSION%/g, gApp.version); + // 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; + } + + var request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]. + createInstance(Ci.nsIXMLHttpRequest); + request.open("GET", uri.spec, true); + request.channel.notificationCallbacks = new BadCertHandler(); + request.overrideMimeType("text/xml"); + request.setRequestHeader("Cache-Control", "no-cache"); + request.QueryInterface(Components.interfaces.nsIJSXMLHttpRequest); + + var self = this; + request.onerror = function(event) { self.onXMLError(event); }; + request.onload = function(event) { self.onXMLLoad(event); }; + request.send(null); + }, + + onXMLLoad: function(aEvent) { + var request = aEvent.target; + try { + checkCert(request.channel); + } + catch (e) { + LOG("Blocklist::onXMLLoad: " + e); + return; + } + var 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 blocklistFile = getFile(KEY_PROFILEDIR, [FILE_BLOCKLIST]); + if (blocklistFile.exists()) + blocklistFile.remove(false); + var fos = openSafeFileOutputStream(blocklistFile); + fos.write(request.responseText, request.responseText.length); + closeSafeFileOutputStream(fos); + this._loadBlocklistFromFile(getFile(KEY_PROFILEDIR, [FILE_BLOCKLIST])); + var em = Cc["@mozilla.org/extensions/manager;1"]. + getService(Ci.nsIExtensionManager); + em.checkForBlocklistChanges(); + this._checkPluginsList(); + }, + + onXMLError: function(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 = request.statusText; + // When status is 0 we don't have a valid channel. + if (status == 0) + statusText = "nsIXMLHttpRequest channel unavailable"; + LOG("Blocklist:onError: There was an error loading the blocklist file\r\n" + + statusText); + }, + + /** + # The blocklist XML file looks something like this: + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + */ + + _loadBlocklistFromFile: function(file) { + this._addonEntries = { }; + this._pluginEntries = { }; + if (getPref("getBoolPref", PREF_BLOCKLIST_ENABLED, true) == false) { + LOG("Blocklist::_loadBlocklistFromFile: blocklist is disabled"); + return; + } + + if (!file.exists()) { + LOG("Blocklist::_loadBlocklistFromFile: XML File does not exist"); + return; + } + + var fileStream = Components.classes["@mozilla.org/network/file-input-stream;1"] + .createInstance(Components.interfaces.nsIFileInputStream); + fileStream.init(file, MODE_RDONLY, PERMS_FILE, 0); + try { + var parser = Cc["@mozilla.org/xmlextras/domparser;1"]. + createInstance(Ci.nsIDOMParser); + var doc = parser.parseFromStream(fileStream, "UTF-8", file.fileSize, "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; + this._addonEntries = this._processItemNodes(childNodes, "em", + this._handleEmItemNode); + this._pluginEntries = this._processItemNodes(childNodes, "plugin", + this._handlePluginItemNode); + } + catch (e) { + LOG("Blocklist::_loadBlocklistFromFile: Error constructing blocklist " + e); + return; + } + fileStream.close(); + }, + + _processItemNodes: function(deChildNodes, prefix, handler) { + var result = []; + var itemNodes; + var containerName = prefix + "Items"; + for (var i = 0; i < deChildNodes.length; ++i) { + var emItemsElement = deChildNodes[i]; + if (emItemsElement.nodeType == kELEMENT_NODE && + emItemsElement.localName == containerName) { + itemNodes = emItemsElement.childNodes; + break; + } + } + if (!itemNodes) + return result; + + var itemName = prefix + "Item"; + for (var i = 0; i < itemNodes.length; ++i) { + var blocklistElement = itemNodes[i]; + if (blocklistElement.nodeType != kELEMENT_NODE || + blocklistElement.localName != itemName) + continue; + + blocklistElement.QueryInterface(Ci.nsIDOMElement); + handler(blocklistElement, result); + } + return result; + }, + + _handleEmItemNode: function(blocklistElement, result) { + var versionNodes = blocklistElement.childNodes; + var id = blocklistElement.getAttribute("id"); + result[id] = []; + for (var x = 0; x < versionNodes.length; ++x) { + var versionRangeElement = versionNodes[x]; + if (versionRangeElement.nodeType != kELEMENT_NODE || + versionRangeElement.localName != "versionRange") + continue; + + result[id].push(new BlocklistItemData(versionRangeElement)); + } + // if only the extension ID is specified block all versions of the + // extension for the current application. + if (result[id].length == 0) + result[id].push(new BlocklistItemData(null)); + }, + + _handlePluginItemNode: function(blocklistElement, result) { + var matchNodes = blocklistElement.childNodes; + var matchList; + for (var x = 0; x < matchNodes.length; ++x) { + var matchElement = matchNodes[x]; + if (matchElement.nodeType != kELEMENT_NODE || + matchElement.localName != "match") + continue; + + var name = matchElement.getAttribute("name"); + var exp = matchElement.getAttribute("exp"); + if (!matchList) + matchList = { }; + matchList[name] = new RegExp(exp, "m"); + } + if (matchList) + result.push(matchList); + }, + + _checkPlugin: function(plugin) { + for each (var matchList in this._pluginEntries) { + var matchFailed = false; + for (var name in matchList) { + if (typeof(plugin[name]) != "string" || + !matchList[name].test(plugin[name])) { + matchFailed = true; + break; + } + } + + if (!matchFailed) { + plugin.blocklisted = true; + return; + } + } + plugin.blocklisted = false; + }, + + _checkPluginsList: function() { + if (!this._addonEntries) + this._loadBlocklistFromFile(getFile(KEY_PROFILEDIR, [FILE_BLOCKLIST])); + var phs = Cc["@mozilla.org/plugin/host;1"]. + getService(Ci.nsIPluginHost); + phs.getPluginTags({ }).forEach(this._checkPlugin, this); + }, + + QueryInterface: function(aIID) { + if (!aIID.equals(Ci.nsIObserver) && + !aIID.equals(Ci.nsIBlocklistService) && + !aIID.equals(Ci.nsITimerCallback) && + !aIID.equals(Ci.nsISupports)) + throw Cr.NS_ERROR_NO_INTERFACE; + return this; + } +}; + +/** + * Helper for constructing a blocklist. + */ +function BlocklistItemData(versionRangeElement) { + var versionRange = this.getBlocklistVersionRange(versionRangeElement); + this.minVersion = versionRange.minVersion; + this.maxVersion = versionRange.maxVersion; + this.targetApps = { }; + var found = false; + + if (versionRangeElement) { + for (var i = 0; i < versionRangeElement.childNodes.length; ++i) { + var targetAppElement = versionRangeElement.childNodes[i]; + if (targetAppElement.nodeType != Ci.nsIDOMNode.ELEMENT_NODE || + 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 extension and the current application when + // versionRange is not defined. + if (!found) + this.targetApps[gApp.ID] = this.getBlocklistAppVersions(null); +} + +BlocklistItemData.prototype = { +/** + * 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 = 0). + * "maxVersion" The maximum version in a version range (default = *). + */ + getBlocklistAppVersions: function(targetAppElement) { + var appVersions = [ ]; + var found = false; + + if (targetAppElement) { + for (var i = 0; i < targetAppElement.childNodes.length; ++i) { + var versionRangeElement = targetAppElement.childNodes[i]; + if (versionRangeElement.nodeType != Ci.nsIDOMNode.ELEMENT_NODE || + versionRangeElement.localName != "versionRange") + continue; + found = true; + appVersions.push(this.getBlocklistVersionRange(versionRangeElement)); + } + } + // return minVersion = 0 and maxVersion = * if not available + if (!found) + return [ 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 = 0). + * "maxVersion" The maximum version in a version range (default = *). + */ + getBlocklistVersionRange: function(versionRangeElement) { + var minVersion = "0"; + var maxVersion = "*"; + 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 }; + } +}; + +const BlocklistFactory = { + createInstance: function(aOuter, aIID) { + if (aOuter != null) + throw Cr.NS_ERROR_NO_AGGREGATION; + + return (new Blocklist()).QueryInterface(aIID); + } +}; + +const gModule = { + registerSelf: function(aCompMgr, aFileSpec, aLocation, aType) { + aCompMgr.QueryInterface(Ci.nsIComponentRegistrar); + aCompMgr.registerFactoryLocation(CID, CLASS_NAME, CONTRACT_ID, + aFileSpec, aLocation, aType); + + var catMan = Cc["@mozilla.org/categorymanager;1"]. + getService(Ci.nsICategoryManager); + catMan.addCategoryEntry("app-startup", CLASS_NAME, "service," + CONTRACT_ID, true, true); + }, + + unregisterSelf: function(aCompMgr, aLocation, aType) { + aCompMgr.QueryInterface(Ci.nsIComponentRegistrar); + aCompMgr.unregisterFactoryLocation(CID, aLocation); + + var catMan = Cc["@mozilla.org/categorymanager;1"]. + getService(Ci.nsICategoryManager); + catMan.deleteCategoryEntry("app-startup", "service," + CONTRACT_ID, true); + }, + + getClassObject: function(aCompMgr, aCID, aIID) { + if (aCID.equals(CID)) + return BlocklistFactory; + + throw Cr.NS_ERROR_NOT_REGISTERED; + }, + + canUnload: function(aCompMgr) { + return true; + } +}; + +function NSGetModule(aCompMgr, aFileSpec) { + return gModule; +} diff --git a/toolkit/mozapps/extensions/src/nsExtensionManager.js.in b/toolkit/mozapps/extensions/src/nsExtensionManager.js.in index 3206351a26a..62d4b46016b 100644 --- a/toolkit/mozapps/extensions/src/nsExtensionManager.js.in +++ b/toolkit/mozapps/extensions/src/nsExtensionManager.js.in @@ -73,10 +73,6 @@ const PREF_DSS_SKIN_TO_SELECT = "extensions.lastSelectedSkin"; const PREF_GENERAL_SKINS_SELECTEDSKIN = "general.skins.selectedSkin"; const PREF_EM_LOGGING_ENABLED = "extensions.logging.enabled"; const PREF_EM_UPDATE_INTERVAL = "extensions.update.interval"; -const PREF_BLOCKLIST_URL = "extensions.blocklist.url"; -const PREF_BLOCKLIST_DETAILS_URL = "extensions.blocklist.detailsURL"; -const PREF_BLOCKLIST_ENABLED = "extensions.blocklist.enabled"; -const PREF_BLOCKLIST_INTERVAL = "extensions.blocklist.interval"; const PREF_UPDATE_NOTIFYUSER = "extensions.update.notifyUser"; const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS"; const PREF_SELECTED_LOCALE = "general.useragent.locale"; @@ -91,7 +87,6 @@ const FILE_AUTOREG = ".autoreg"; const FILE_INSTALL_MANIFEST = "install.rdf"; const FILE_CONTENTS_MANIFEST = "contents.rdf"; const FILE_CHROME_MANIFEST = "chrome.manifest"; -const FILE_BLOCKLIST = "blocklist.xml"; const UNKNOWN_XPCOM_ABI = "unknownABI"; @@ -135,7 +130,6 @@ const RDFURI_INSTALL_MANIFEST_ROOT = "urn:mozilla:install-manifest"; const RDFURI_ITEM_ROOT = "urn:mozilla:item:root" const RDFURI_DEFAULT_THEME = "urn:mozilla:item:{972ce4c6-7e08-4474-a285-3208198ce6fd}"; const XMLURI_PARSE_ERROR = "http://www.mozilla.org/newlayout/xml/parsererror.xml" -const XMLURI_BLOCKLIST = "http://www.mozilla.org/2006/addons-blocklist"; const URI_GENERIC_ICON_XPINSTALL = "chrome://mozapps/skin/xpinstall/xpinstallItemGeneric.png"; const URI_GENERIC_ICON_THEME = "chrome://mozapps/skin/extensions/themeGeneric.png"; @@ -168,6 +162,7 @@ var gApp = null; var gPref = null; var gRDF = null; var gOS = null; +var gBlocklist = null; var gXPCOMABI = null; var gOSTarget = null; var gConsole = null; @@ -2303,318 +2298,6 @@ var StartupCache = { } }; -/** - * 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. - */ -var Blocklist = { - /** - * 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 = *) - */ - entries: null, - - notify: function() { - if (getPref("getBoolPref", PREF_BLOCKLIST_ENABLED, true) == false) - return; - - try { - var dsURI = gPref.getCharPref(PREF_BLOCKLIST_URL); - } - catch (e) { - LOG("Blocklist::notify: The " + PREF_BLOCKLIST_URL + " preference" + - " is missing!"); - return; - } - - dsURI = dsURI.replace(/%APP_ID%/g, gApp.ID); - dsURI = dsURI.replace(/%APP_VERSION%/g, gApp.version); - // 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; - } - - var request = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"] - .createInstance(Components.interfaces.nsIXMLHttpRequest); - request.open("GET", uri.spec, true); - request.channel.notificationCallbacks = new BadCertHandler(); - request.overrideMimeType("text/xml"); - request.setRequestHeader("Cache-Control", "no-cache"); - request.QueryInterface(Components.interfaces.nsIJSXMLHttpRequest); - - var self = this; - request.onerror = function(event) { self.onXMLError(event); }; - request.onload = function(event) { self.onXMLLoad(event); }; - request.send(null); - }, - - onXMLLoad: function(aEvent) { - var request = aEvent.target; - try { - checkCert(request.channel); - } - catch (e) { - LOG("Blocklist::onXMLLoad: " + e); - return; - } - var 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 blocklistFile = getFile(KEY_PROFILEDIR, [FILE_BLOCKLIST]); - if (blocklistFile.exists()) - blocklistFile.remove(false); - var fos = openSafeFileOutputStream(blocklistFile); - fos.write(request.responseText, request.responseText.length); - closeSafeFileOutputStream(fos); - this.entries = this._loadBlocklistFromFile(getFile(KEY_PROFILEDIR, - [FILE_BLOCKLIST])); - var em = Components.classes["@mozilla.org/extensions/manager;1"] - .getService(Components.interfaces.nsIExtensionManager); - em.checkForBlocklistChanges(); - }, - - onXMLError: function(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(Components.interfaces.nsIRequest); - status = request.status; - } - var statusText = request.statusText; - // When status is 0 we don't have a valid channel. - if (status == 0) - statusText = "nsIXMLHttpRequest channel unavailable"; - LOG("Blocklist:onError: There was an error loading the blocklist file\r\n" + - statusText); - }, - - /** - * The blocklist XML file looks something like this: - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - */ - _loadBlocklistFromFile: function(file) { - if (getPref("getBoolPref", PREF_BLOCKLIST_ENABLED, true) == false) { - LOG("Blocklist::_loadBlocklistFromFile: blocklist is disabled"); - return { }; - } - - if (!file.exists()) { - LOG("Blocklist::_loadBlocklistFromFile: XML File does not exist"); - return { }; - } - - var result = { }; - var fileStream = Components.classes["@mozilla.org/network/file-input-stream;1"] - .createInstance(Components.interfaces.nsIFileInputStream); - fileStream.init(file, MODE_RDONLY, PERMS_FILE, 0); - try { - var parser = Components.classes["@mozilla.org/xmlextras/domparser;1"] - .createInstance(Components.interfaces.nsIDOMParser); - var doc = parser.parseFromStream(fileStream, "UTF-8", file.fileSize, "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 { }; - } - - const kELEMENT_NODE = Components.interfaces.nsIDOMNode.ELEMENT_NODE; - var itemNodes = this._getItemNodes(doc.documentElement.childNodes); - for (var i = 0; i < itemNodes.length; ++i) { - var blocklistElement = itemNodes[i]; - if (blocklistElement.nodeType != kELEMENT_NODE || - blocklistElement.localName != "emItem") - continue; - - blocklistElement.QueryInterface(Components.interfaces.nsIDOMElement); - var versionNodes = blocklistElement.childNodes; - var id = blocklistElement.getAttribute("id"); - result[id] = []; - for (var x = 0; x < versionNodes.length; ++x) { - var versionRangeElement = versionNodes[x]; - if (versionRangeElement.nodeType != kELEMENT_NODE || - versionRangeElement.localName != "versionRange") - continue; - - result[id].push(new BlocklistItemData(versionRangeElement)); - } - // if only the extension ID is specified block all versions of the - // extension for the current application. - if (result[id].length == 0) - result[id].push(new BlocklistItemData(null)); - } - } - catch (e) { - LOG("Blocklist::_loadBlocklistFromFile: Error constructing blocklist " + e); - return { }; - } - fileStream.close(); - return result; - }, - - _getItemNodes: function(deChildNodes) { - const kELEMENT_NODE = Components.interfaces.nsIDOMNode.ELEMENT_NODE; - for (var i = 0; i < deChildNodes.length; ++i) { - var emItemsElement = deChildNodes[i]; - if (emItemsElement.nodeType == kELEMENT_NODE && - emItemsElement.localName == "emItems") - return emItemsElement.childNodes; - } - return [ ]; - }, - - _ensureBlocklist: function() { - if (!this.entries) - this.entries = this._loadBlocklistFromFile(getFile(KEY_PROFILEDIR, - [FILE_BLOCKLIST])); - } -}; - -/** - * Helper for constructing a blocklist. - */ -function BlocklistItemData(versionRangeElement) { - var versionRange = this.getBlocklistVersionRange(versionRangeElement); - this.minVersion = versionRange.minVersion; - this.maxVersion = versionRange.maxVersion; - this.targetApps = { }; - var found = false; - - if (versionRangeElement) { - for (var i = 0; i < versionRangeElement.childNodes.length; ++i) { - var targetAppElement = versionRangeElement.childNodes[i]; - if (targetAppElement.nodeType != Components.interfaces.nsIDOMNode.ELEMENT_NODE || - 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 extension and the current application when - // versionRange is not defined. - if (!found) - this.targetApps[gApp.ID] = this.getBlocklistAppVersions(null); -} - -BlocklistItemData.prototype = { -/** - * 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 = 0). - * "maxVersion" The maximum version in a version range (default = *). - */ - getBlocklistAppVersions: function(targetAppElement) { - var appVersions = [ ]; - var found = false; - - if (targetAppElement) { - for (var i = 0; i < targetAppElement.childNodes.length; ++i) { - var versionRangeElement = targetAppElement.childNodes[i]; - if (versionRangeElement.nodeType != Components.interfaces.nsIDOMNode.ELEMENT_NODE || - versionRangeElement.localName != "versionRange") - continue; - found = true; - appVersions.push(this.getBlocklistVersionRange(versionRangeElement)); - } - } - // return minVersion = 0 and maxVersion = * if not available - if (!found) - return [ 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 = 0). - * "maxVersion" The maximum version in a version range (default = *). - */ - getBlocklistVersionRange: function(versionRangeElement) { - var minVersion = "0"; - var maxVersion = "*"; - 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 }; - } -}; - /** * Installs, manages and tracks compatibility for Extensions and Themes * @constructor @@ -2932,9 +2615,6 @@ ExtensionManager.prototype = { .getService(Components.interfaces.nsIUpdateTimerManager); var interval = getPref("getIntPref", PREF_EM_UPDATE_INTERVAL, 86400); tm.registerTimer("addon-background-update-timer", this, interval); - - interval = getPref("getIntPref", PREF_BLOCKLIST_INTERVAL, 86400); - tm.registerTimer("blocklist-background-update-timer", Blocklist, interval); }, /** @@ -4241,7 +3921,10 @@ ExtensionManager.prototype = { installData.error = INSTALLERROR_INCOMPATIBLE_VERSION; // Check if the item is blocklisted. - if (this.datasource.isBlocklisted(installData.id, installData.version, + if (!gBlocklist) + gBlocklist = Components.classes["@mozilla.org/extensions/blocklist;1"] + .getService(Components.interfaces.nsIBlocklistService); + if (gBlocklist.isAddonBlocklisted(installData.id, installData.version, undefined, undefined)) installData.error = INSTALLERROR_BLOCKLISTED; @@ -6071,8 +5754,11 @@ ExtensionItemUpdater.prototype = { gVersionChecker.compare(appExtensionsVersion, aMaxAppVersion) > 0) return false; - if (this._emDS.isBlocklisted(aLocalItem.id, aVersion, - undefined, undefined)) + if (!gBlocklist) + gBlocklist = Components.classes["@mozilla.org/extensions/blocklist;1"] + .getService(Components.interfaces.nsIBlocklistService); + if (gBlocklist.isAddonBlocklisted(aLocalItem.id, aVersion, + undefined, undefined)) return false; return true; @@ -6672,62 +6358,6 @@ ExtensionsDataSource.prototype = { return false; }, - /** - * Determine if an item is blocklisted - * @param id - * The id of the item to check. - * @param extVersion - * The item's version. - * @param appVersion - * The version of the application we are checking in the blocklist. - * If this parameter is undefined, the version of the running - * application is used. - * @param toolkitVersion - * The version of the toolkit we are checking in the blocklist. - * If this parameter is undefined, the version of the running - * toolkit is used. - * @returns true if the item is compatible with this version of the - * application, false, otherwise. - */ - isBlocklisted: function(id, extVersion, appVersion, toolkitVersion) { - if (appVersion === undefined) - appVersion = gApp.version; - if (toolkitVersion === undefined) - toolkitVersion = gApp.platformVersion; - - var blItem = Blocklist.entries[id]; - if (!blItem) - return false; - - var versionChecker = getVersionChecker(); - for (var i = 0; i < blItem.length; ++i) { - if (versionChecker.compare(extVersion, blItem[i].minVersion) < 0 || - versionChecker.compare(extVersion, blItem[i].maxVersion) > 0) - continue; - - var blTargetApp = blItem[i].targetApps[gApp.ID]; - if (blTargetApp) { - for (var x = 0; x < blTargetApp.length; ++x) { - if (versionChecker.compare(appVersion, blTargetApp[x].minVersion) < 0 || - versionChecker.compare(appVersion, blTargetApp[x].maxVersion) > 0) - continue; - return true; - } - } - - blTargetApp = blItem[i].targetApps[TOOLKIT_ID]; - if (!blTargetApp) - return false; - for (x = 0; x < blTargetApp.length; ++x) { - if (versionChecker.compare(toolkitVersion, blTargetApp[x].minVersion) < 0 || - versionChecker.compare(toolkitVersion, blTargetApp[x].maxVersion) > 0) - continue; - return true; - } - } - return false; - }, - /** * Gets a list of items that are incompatible with a specific application version. * @param appID @@ -6789,6 +6419,9 @@ ExtensionsDataSource.prototype = { */ getBlocklistedItemList: function(appVersion, toolkitVersion, desiredType, includeAppDisabled) { + if (!gBlocklist) + gBlocklist = Components.classes["@mozilla.org/extensions/blocklist;1"] + .getService(Components.interfaces.nsIBlocklistService); var items = []; var ctr = getContainer(this._inner, this._itemRoot); var elements = ctr.GetElements(); @@ -6802,9 +6435,9 @@ ExtensionsDataSource.prototype = { this.getItemProperty(id, "appDisabled") == OP_NEEDS_DISABLE)) continue; - var extVersion = this.getItemProperty(id, "version"); + var version = this.getItemProperty(id, "version"); if (type != -1 && (type & desiredType) && - this.isBlocklisted(id, extVersion, appVersion, toolkitVersion)) + gBlocklist.isAddonBlocklisted(id, version, appVersion, toolkitVersion)) items.push(this.getItemForID(id)); } return items; @@ -7737,7 +7370,6 @@ ExtensionsDataSource.prototype = { * Load the Extensions Datasource from disk. */ loadExtensions: function() { - Blocklist._ensureBlocklist(); var extensionsFile = getFile(KEY_PROFILEDIR, [FILE_EXTENSIONS]); try { this._inner = gRDF.GetDataSourceBlocking(getURLSpecFromFile(extensionsFile)); @@ -7929,40 +7561,15 @@ ExtensionsDataSource.prototype = { * Get the em:blocklisted property (whether or not this item is blocklisted) */ _rdfGet_blocklisted: function(item, property) { - Blocklist._ensureBlocklist(); var id = stripPrefix(item.Value, PREFIX_ITEM_URI); - var blItem = Blocklist.entries[id]; - if (!blItem) - return EM_L("false"); - - getVersionChecker(); var version = this.getItemProperty(id, "version"); - var appVersion = gApp.version; - for (var i = 0; i < blItem.length; ++i) { - if (gVersionChecker.compare(version, blItem[i].minVersion) < 0 || - gVersionChecker.compare(version, blItem[i].maxVersion) > 0) - continue; + if (!gBlocklist) + gBlocklist = Components.classes["@mozilla.org/extensions/blocklist;1"] + .getService(Components.interfaces.nsIBlocklistService); + if (gBlocklist.isAddonBlocklisted(id, version, + undefined, undefined)) + return EM_L("true"); - var blTargetApp = blItem[i].targetApps[gApp.ID]; - if (blTargetApp) { - for (var x = 0; x < blTargetApp.length; ++x) { - if (gVersionChecker.compare(appVersion, blTargetApp[x].minVersion) < 0 || - gVersionChecker.compare(appVersion, blTargetApp[x].maxVersion) > 0) - continue; - return EM_L("true"); - } - } - - blTargetApp = blItem[i].targetApps[TOOLKIT_ID]; - if (!blTargetApp) - return EM_L("false"); - for (x = 0; x < blTargetApp.length; ++x) { - if (gVersionChecker.compare(gApp.platformVersion, blTargetApp[x].minVersion) < 0 || - gVersionChecker.compare(gApp.platformVersion, blTargetApp[x].maxVersion) > 0) - continue; - return EM_L("true"); - } - } return EM_L("false"); },