1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
3 # ***** BEGIN LICENSE BLOCK *****
4 # Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 # The contents of this file are subject to the Mozilla Public License Version
7 # 1.1 (the "License"); you may not use this file except in compliance with
8 # the License. You may obtain a copy of the License at
9 # http://www.mozilla.org/MPL/
11 # Software distributed under the License is distributed on an "AS IS" basis,
12 # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 # for the specific language governing rights and limitations under the
16 # The Original Code is the Blocklist Service.
18 # The Initial Developer of the Original Code is
19 # Mozilla Corporation.
20 # Portions created by the Initial Developer are Copyright (C) 2007
21 # the Initial Developer. All Rights Reserved.
24 # Robert Strong <robert.bugzilla@gmail.com>
25 # Michael Wu <flamingice@sourmilk.net>
26 # Dave Townsend <dtownsend@oxymoronical.com>
28 # Alternatively, the contents of this file may be used under the terms of
29 # either the GNU General Public License Version 2 or later (the "GPL"), or
30 # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
31 # in which case the provisions of the GPL or the LGPL are applicable instead
32 # of those above. If you wish to allow use of your version of this file only
33 # under the terms of either the GPL or the LGPL, and not to allow others to
34 # use your version of this file under the terms of the MPL, indicate your
35 # decision by deleting the provisions above and replace them with the notice
36 # and other provisions required by the GPL or the LGPL. If you do not delete
37 # the provisions above, a recipient may use your version of this file under
38 # the terms of any one of the MPL, the GPL or the LGPL.
40 # ***** END LICENSE BLOCK *****
43 const Cc = Components.classes;
44 const Ci = Components.interfaces;
45 const Cr = Components.results;
47 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
48 Components.utils.import("resource://gre/modules/FileUtils.jsm");
49 Components.utils.import("resource://gre/modules/AddonManager.jsm");
50 Components.utils.import("resource://gre/modules/Services.jsm");
52 const TOOLKIT_ID = "toolkit@mozilla.org"
53 const KEY_PROFILEDIR = "ProfD";
54 const KEY_APPDIR = "XCurProcD";
55 const FILE_BLOCKLIST = "blocklist.xml";
56 const PREF_BLOCKLIST_LASTUPDATETIME = "app.update.lastUpdateTime.blocklist-background-update-timer";
57 const PREF_BLOCKLIST_URL = "extensions.blocklist.url";
58 const PREF_BLOCKLIST_ENABLED = "extensions.blocklist.enabled";
59 const PREF_BLOCKLIST_INTERVAL = "extensions.blocklist.interval";
60 const PREF_BLOCKLIST_LEVEL = "extensions.blocklist.level";
61 const PREF_BLOCKLIST_PINGCOUNT = "extensions.blocklist.pingCount";
62 const PREF_BLOCKLIST_TOTALPINGCOUNT = "extensions.blocklist.totalPingCount";
63 const PREF_PLUGINS_NOTIFYUSER = "plugins.update.notifyUser";
64 const PREF_GENERAL_USERAGENT_LOCALE = "general.useragent.locale";
65 const PREF_PARTNER_BRANCH = "app.partner.";
66 const PREF_APP_DISTRIBUTION = "distribution.id";
67 const PREF_APP_DISTRIBUTION_VERSION = "distribution.version";
68 const PREF_APP_UPDATE_CHANNEL = "app.update.channel";
69 const PREF_EM_LOGGING_ENABLED = "extensions.logging.enabled";
70 const XMLURI_BLOCKLIST = "http://www.mozilla.org/2006/addons-blocklist";
71 const XMLURI_PARSE_ERROR = "http://www.mozilla.org/newlayout/xml/parsererror.xml"
72 const UNKNOWN_XPCOM_ABI = "unknownABI";
73 const URI_BLOCKLIST_DIALOG = "chrome://mozapps/content/extensions/blocklist.xul"
74 const DEFAULT_SEVERITY = 3;
75 const DEFAULT_LEVEL = 2;
76 const MAX_BLOCK_LEVEL = 3;
77 const SEVERITY_OUTDATED = 0;
79 var gLoggingEnabled = null;
80 var gBlocklistEnabled = true;
81 var gBlocklistLevel = DEFAULT_LEVEL;
83 XPCOMUtils.defineLazyServiceGetter(this, "gConsole",
84 "@mozilla.org/consoleservice;1",
87 XPCOMUtils.defineLazyServiceGetter(this, "gVersionChecker",
88 "@mozilla.org/xpcom/version-comparator;1",
89 "nsIVersionComparator");
91 XPCOMUtils.defineLazyGetter(this, "gPref", function bls_gPref() {
92 return Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefService).
93 QueryInterface(Ci.nsIPrefBranch2);
96 XPCOMUtils.defineLazyGetter(this, "gApp", function bls_gApp() {
97 return Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo).
98 QueryInterface(Ci.nsIXULRuntime);
101 XPCOMUtils.defineLazyGetter(this, "gABI", function bls_gABI() {
107 LOG("BlockList Global gABI: XPCOM ABI unknown.");
110 // Mac universal build should report a different ABI than either macppc
112 let macutils = Cc["@mozilla.org/xpcom/mac-utils;1"].
113 getService(Ci.nsIMacUtils);
115 if (macutils.isUniversalBinary)
116 abi += "-u-" + macutils.architecturesInBinary;
121 XPCOMUtils.defineLazyGetter(this, "gOSVersion", function bls_gOSVersion() {
123 let sysInfo = Cc["@mozilla.org/system-info;1"].
124 getService(Ci.nsIPropertyBag2);
126 osVersion = sysInfo.getProperty("name") + " " + sysInfo.getProperty("version");
129 LOG("BlockList Global gOSVersion: OS Version unknown.");
134 osVersion += " (" + sysInfo.getProperty("secondaryLibrary") + ")";
137 // Not all platforms have a secondary widget library, so an error is nothing to worry about.
139 osVersion = encodeURIComponent(osVersion);
144 // shared code for suppressing bad cert dialogs
145 XPCOMUtils.defineLazyGetter(this, "gCertUtils", function bls_gCertUtils() {
147 Components.utils.import("resource://gre/modules/CertUtils.jsm", temp);
151 function getObserverService() {
152 return Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
156 * Logs a string to the error console.
158 * The string to write to the error console..
160 function LOG(string) {
161 if (gLoggingEnabled) {
162 dump("*** " + string + "\n");
163 gConsole.logStringMessage(string);
168 * Gets a preference value, handling the case where there is no default.
170 * The name of the preference function to call, on nsIPrefBranch
172 * The name of the preference
173 * @param defaultValue
174 * The default value to return in the event the preference has
176 * @returns The value of the preference, or undefined if there was no
177 * user or default value.
179 function getPref(func, preference, defaultValue) {
181 return gPref[func](preference);
189 * Constructs a URI to a spec.
191 * The spec to construct a URI to
192 * @returns The nsIURI constructed.
194 function newURI(spec) {
195 var ioServ = Cc["@mozilla.org/network/io-service;1"].
196 getService(Ci.nsIIOService);
197 return ioServ.newURI(spec, null, null);
200 // Restarts the application checking in with observers first
201 function restartApp() {
202 // Notify all windows that an application quit has been requested.
203 var os = Cc["@mozilla.org/observer-service;1"].
204 getService(Ci.nsIObserverService);
205 var cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].
206 createInstance(Ci.nsISupportsPRBool);
207 os.notifyObservers(cancelQuit, "quit-application-requested", null);
209 // Something aborted the quit process.
213 var as = Cc["@mozilla.org/toolkit/app-startup;1"].
214 getService(Ci.nsIAppStartup);
215 as.quit(Ci.nsIAppStartup.eRestart | Ci.nsIAppStartup.eAttemptQuit);
219 * Checks whether this blocklist element is valid for the current OS and ABI.
220 * If the element has an "os" attribute then the current OS must appear in
221 * its comma separated list for the element to be valid. Similarly for the
222 * xpcomabi attribute.
224 function matchesOSABI(blocklistElement) {
225 if (blocklistElement.hasAttribute("os")) {
226 var choices = blocklistElement.getAttribute("os").split(",");
227 if (choices.length > 0 && choices.indexOf(gApp.OS) < 0)
231 if (blocklistElement.hasAttribute("xpcomabi")) {
232 choices = blocklistElement.getAttribute("xpcomabi").split(",");
233 if (choices.length > 0 && choices.indexOf(gApp.XPCOMABI) < 0)
241 * Gets the current value of the locale. It's possible for this preference to
242 * be localized, so we have to do a little extra work here. Similar code
243 * exists in nsHttpHandler.cpp when building the UA string.
245 function getLocale() {
247 // Get the default branch
248 var defaultPrefs = gPref.getDefaultBranch(null);
249 return defaultPrefs.getCharPref(PREF_GENERAL_USERAGENT_LOCALE);
252 return gPref.getCharPref(PREF_GENERAL_USERAGENT_LOCALE);
256 * Read the update channel from defaults only. We do this to ensure that
257 * the channel is tightly coupled with the application and does not apply
258 * to other installations of the application that may use the same profile.
260 function getUpdateChannel() {
261 var channel = "default";
265 var defaults = gPref.getDefaultBranch(null);
267 channel = defaults.getCharPref(PREF_APP_UPDATE_CHANNEL);
269 // use default when pref not found
273 var partners = gPref.getChildList(PREF_PARTNER_BRANCH);
274 if (partners.length) {
278 for each (prefName in partners) {
279 prefValue = gPref.getCharPref(prefName);
280 channel += "-" + prefValue;
285 Components.utils.reportError(e);
291 /* Get the distribution pref values, from defaults only */
292 function getDistributionPrefValue(aPrefName) {
293 var prefValue = "default";
295 var defaults = gPref.getDefaultBranch(null);
297 prefValue = defaults.getCharPref(aPrefName);
299 // use default when pref not found
306 * Manages the Blocklist. The Blocklist is a representation of the contents of
307 * blocklist.xml and allows us to remotely disable / re-enable blocklisted
308 * items managed by the Extension Manager with an item's appDisabled property.
309 * It also blocklists plugins with data from blocklist.xml.
312 function Blocklist() {
313 let os = getObserverService();
314 os.addObserver(this, "xpcom-shutdown", false);
315 gLoggingEnabled = getPref("getBoolPref", PREF_EM_LOGGING_ENABLED, false);
316 gBlocklistEnabled = getPref("getBoolPref", PREF_BLOCKLIST_ENABLED, true);
317 gBlocklistLevel = Math.min(getPref("getIntPref", PREF_BLOCKLIST_LEVEL, DEFAULT_LEVEL),
319 gPref.addObserver("extensions.blocklist.", this, false);
322 Blocklist.prototype = {
324 * Extension ID -> array of Version Ranges
325 * Each value in the version range array is a JS Object that has the
326 * following properties:
327 * "minVersion" The minimum version in a version range (default = 0)
328 * "maxVersion" The maximum version in a version range (default = *)
329 * "targetApps" Application ID -> array of Version Ranges
330 * (default = current application ID)
331 * Each value in the version range array is a JS Object that
332 * has the following properties:
333 * "minVersion" The minimum version in a version range
335 * "maxVersion" The maximum version in a version range
339 _pluginEntries: null,
341 observe: function(aSubject, aTopic, aData) {
343 case "xpcom-shutdown":
344 let os = getObserverService();
345 os.removeObserver(this, "xpcom-shutdown");
346 gPref.removeObserver("extensions.blocklist.", this);
348 case "nsPref:changed":
350 case PREF_BLOCKLIST_ENABLED:
351 gBlocklistEnabled = getPref("getBoolPref", PREF_BLOCKLIST_ENABLED, true);
352 this._loadBlocklist();
353 this._blocklistUpdated(null, null);
355 case PREF_BLOCKLIST_LEVEL:
356 gBlocklistLevel = Math.min(getPref("getIntPref", PREF_BLOCKLIST_LEVEL, DEFAULT_LEVEL),
358 this._blocklistUpdated(null, null);
365 /* See nsIBlocklistService */
366 isAddonBlocklisted: function(id, version, appVersion, toolkitVersion) {
367 return this.getAddonBlocklistState(id, version, appVersion, toolkitVersion) ==
368 Ci.nsIBlocklistService.STATE_BLOCKED;
371 /* See nsIBlocklistService */
372 getAddonBlocklistState: function(id, version, appVersion, toolkitVersion) {
373 if (!this._addonEntries)
374 this._loadBlocklist();
375 return this._getAddonBlocklistState(id, version, this._addonEntries,
376 appVersion, toolkitVersion);
380 * Private version of getAddonBlocklistState that allows the caller to pass in
381 * the add-on blocklist entries to compare against.
384 * The ID of the item to get the blocklist state for.
386 * The version of the item to get the blocklist state for.
387 * @param addonEntries
388 * The add-on blocklist entries to compare against.
390 * The application version to compare to, will use the current
392 * @param toolkitVersion
393 * The toolkit version to compare to, will use the current version if
395 * @returns The blocklist state for the item, one of the STATE constants as
396 * defined in nsIBlocklistService.
398 _getAddonBlocklistState: function(id, version, addonEntries, appVersion, toolkitVersion) {
399 if (!gBlocklistEnabled)
400 return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
403 appVersion = gApp.version;
405 toolkitVersion = gApp.platformVersion;
407 var blItem = addonEntries[id];
409 return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
411 for (var i = 0; i < blItem.length; ++i) {
412 if (blItem[i].includesItem(version, appVersion, toolkitVersion))
413 return blItem[i].severity >= gBlocklistLevel ? Ci.nsIBlocklistService.STATE_BLOCKED :
414 Ci.nsIBlocklistService.STATE_SOFTBLOCKED;
416 return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
419 notify: function(aTimer) {
420 if (!gBlocklistEnabled)
424 var dsURI = gPref.getCharPref(PREF_BLOCKLIST_URL);
427 LOG("Blocklist::notify: The " + PREF_BLOCKLIST_URL + " preference" +
432 var pingCount = getPref("getIntPref", PREF_BLOCKLIST_PINGCOUNT, 0);
433 var totalPingCount = getPref("getIntPref", PREF_BLOCKLIST_TOTALPINGCOUNT, 1);
434 var daysSinceLastPing;
436 daysSinceLastPing = pingCount == 0 ? "new" : "reset";
440 // Seconds in one day is used because nsIUpdateTimerManager stores the
441 // last update time in seconds.
442 let secondsInDay = 60 * 60 * 24;
443 let lastUpdateTime = getPref("getIntPref", PREF_BLOCKLIST_LASTUPDATETIME, 0);
444 if (lastUpdateTime != 0) {
445 let now = Math.round(Date.now() / 1000);
446 daysSinceLastPing = Math.floor((now - lastUpdateTime) / secondsInDay);
449 daysSinceLastPing = "invalid";
453 dsURI = dsURI.replace(/%APP_ID%/g, gApp.ID);
454 dsURI = dsURI.replace(/%APP_VERSION%/g, gApp.version);
455 dsURI = dsURI.replace(/%PRODUCT%/g, gApp.name);
456 dsURI = dsURI.replace(/%VERSION%/g, gApp.version);
457 dsURI = dsURI.replace(/%BUILD_ID%/g, gApp.appBuildID);
458 dsURI = dsURI.replace(/%BUILD_TARGET%/g, gApp.OS + "_" + gABI);
459 dsURI = dsURI.replace(/%OS_VERSION%/g, gOSVersion);
460 dsURI = dsURI.replace(/%LOCALE%/g, getLocale());
461 dsURI = dsURI.replace(/%CHANNEL%/g, getUpdateChannel());
462 dsURI = dsURI.replace(/%PLATFORM_VERSION%/g, gApp.platformVersion);
463 dsURI = dsURI.replace(/%DISTRIBUTION%/g,
464 getDistributionPrefValue(PREF_APP_DISTRIBUTION));
465 dsURI = dsURI.replace(/%DISTRIBUTION_VERSION%/g,
466 getDistributionPrefValue(PREF_APP_DISTRIBUTION_VERSION));
467 dsURI = dsURI.replace(/%PING_COUNT%/g, pingCount);
468 dsURI = dsURI.replace(/%TOTAL_PING_COUNT%/g, totalPingCount);
469 dsURI = dsURI.replace(/%DAYS_SINCE_LAST_PING%/g, daysSinceLastPing);
470 dsURI = dsURI.replace(/\+/g, "%2B");
472 // Under normal operations it will take around 5,883,516 years before the
473 // preferences used to store pingCount and totalPingCount will rollover
474 // so this code doesn't bother trying to do the "right thing" here.
476 if (pingCount > 2147483647) {
477 // Rollover to -1 if the value is greater than what is support by an
478 // integer preference. The -1 indicates that the counter has been reset.
481 gPref.setIntPref(PREF_BLOCKLIST_PINGCOUNT, pingCount);
484 if (totalPingCount > 2147483647) {
485 // Rollover to 1 if the value is greater than what is support by an
486 // integer preference.
489 gPref.setIntPref(PREF_BLOCKLIST_TOTALPINGCOUNT, totalPingCount);
491 // Verify that the URI is valid
493 var uri = newURI(dsURI);
496 LOG("Blocklist::notify: There was an error creating the blocklist URI\r\n" +
497 "for: " + dsURI + ", error: " + e);
501 var request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
502 createInstance(Ci.nsIXMLHttpRequest);
503 request.open("GET", uri.spec, true);
504 request.channel.notificationCallbacks = new gCertUtils.BadCertHandler();
505 request.overrideMimeType("text/xml");
506 request.setRequestHeader("Cache-Control", "no-cache");
507 request.QueryInterface(Components.interfaces.nsIJSXMLHttpRequest);
510 request.onerror = function(event) { self.onXMLError(event); };
511 request.onload = function(event) { self.onXMLLoad(event); };
514 // When the blocklist loads we need to compare it to the current copy so
515 // make sure we have loaded it.
516 if (!this._addonEntries)
517 this._loadBlocklist();
520 onXMLLoad: function(aEvent) {
521 var request = aEvent.target;
523 gCertUtils.checkCert(request.channel);
526 LOG("Blocklist::onXMLLoad: " + e);
529 var responseXML = request.responseXML;
530 if (!responseXML || responseXML.documentElement.namespaceURI == XMLURI_PARSE_ERROR ||
531 (request.status != 200 && request.status != 0)) {
532 LOG("Blocklist::onXMLLoad: there was an error during load");
535 var blocklistFile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_BLOCKLIST]);
536 if (blocklistFile.exists())
537 blocklistFile.remove(false);
538 var fos = FileUtils.openSafeFileOutputStream(blocklistFile);
539 fos.write(request.responseText, request.responseText.length);
540 FileUtils.closeSafeFileOutputStream(fos);
542 var oldAddonEntries = this._addonEntries;
543 var oldPluginEntries = this._pluginEntries;
544 this._addonEntries = { };
545 this._pluginEntries = { };
546 this._loadBlocklistFromFile(FileUtils.getFile(KEY_PROFILEDIR,
549 this._blocklistUpdated(oldAddonEntries, oldPluginEntries);
552 onXMLError: function(aEvent) {
554 var request = aEvent.target;
555 // the following may throw (e.g. a local file or timeout)
556 var status = request.status;
559 request = aEvent.target.channel.QueryInterface(Ci.nsIRequest);
560 status = request.status;
562 var statusText = "nsIXMLHttpRequest channel unavailable";
563 // When status is 0 we don't have a valid channel.
566 statusText = request.statusText;
570 LOG("Blocklist:onError: There was an error loading the blocklist file\r\n" +
575 * Finds the newest blocklist file from the application and the profile and
576 * load it or does nothing if neither exist.
578 _loadBlocklist: function() {
579 this._addonEntries = { };
580 this._pluginEntries = { };
581 var profFile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_BLOCKLIST]);
582 if (profFile.exists()) {
583 this._loadBlocklistFromFile(profFile);
586 var appFile = FileUtils.getFile(KEY_APPDIR, [FILE_BLOCKLIST]);
587 if (appFile.exists()) {
588 this._loadBlocklistFromFile(appFile);
591 LOG("Blocklist::_loadBlocklist: no XML File found");
595 # The blocklist XML file looks something like this:
597 # <blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist">
599 # <emItem id="item_1@domain">
600 # <versionRange minVersion="1.0" maxVersion="2.0.*">
601 # <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
602 # <versionRange minVersion="1.5" maxVersion="1.5.*"/>
603 # <versionRange minVersion="1.7" maxVersion="1.7.*"/>
604 # </targetApplication>
605 # <targetApplication id="toolkit@mozilla.org">
606 # <versionRange minVersion="1.9" maxVersion="1.9.*"/>
607 # </targetApplication>
609 # <versionRange minVersion="3.0" maxVersion="3.0.*">
610 # <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
611 # <versionRange minVersion="1.5" maxVersion="1.5.*"/>
612 # </targetApplication>
613 # <targetApplication id="toolkit@mozilla.org">
614 # <versionRange minVersion="1.9" maxVersion="1.9.*"/>
615 # </targetApplication>
618 # <emItem id="item_2@domain">
619 # <versionRange minVersion="3.1" maxVersion="4.*"/>
621 # <emItem id="item_3@domain">
623 # <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
624 # <versionRange minVersion="1.5" maxVersion="1.5.*"/>
625 # </targetApplication>
628 # <emItem id="item_4@domain">
630 # <targetApplication>
631 # <versionRange minVersion="1.5" maxVersion="1.5.*"/>
632 # </targetApplication>
634 # <emItem id="item_5@domain"/>
638 # <!-- All match tags must match a plugin to blocklist a plugin -->
639 # <match name="name" exp="some plugin"/>
640 # <match name="description" exp="1[.]2[.]3"/>
646 _loadBlocklistFromFile: function(file) {
647 if (!gBlocklistEnabled) {
648 LOG("Blocklist::_loadBlocklistFromFile: blocklist is disabled");
652 if (!file.exists()) {
653 LOG("Blocklist::_loadBlocklistFromFile: XML File does not exist");
657 var fileStream = Components.classes["@mozilla.org/network/file-input-stream;1"]
658 .createInstance(Components.interfaces.nsIFileInputStream);
659 fileStream.init(file, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0);
661 var parser = Cc["@mozilla.org/xmlextras/domparser;1"].
662 createInstance(Ci.nsIDOMParser);
663 var doc = parser.parseFromStream(fileStream, "UTF-8", file.fileSize, "text/xml");
664 if (doc.documentElement.namespaceURI != XMLURI_BLOCKLIST) {
665 LOG("Blocklist::_loadBlocklistFromFile: aborting due to incorrect " +
666 "XML Namespace.\r\nExpected: " + XMLURI_BLOCKLIST + "\r\n" +
667 "Received: " + doc.documentElement.namespaceURI);
671 var childNodes = doc.documentElement.childNodes;
672 for (var i = 0; i < childNodes.length; ++i) {
673 var element = childNodes[i];
674 if (!(element instanceof Ci.nsIDOMElement))
676 switch (element.localName) {
678 this._addonEntries = this._processItemNodes(element.childNodes, "em",
679 this._handleEmItemNode);
682 this._pluginEntries = this._processItemNodes(element.childNodes, "plugin",
683 this._handlePluginItemNode);
686 Services.obs.notifyObservers(element,
687 "blocklist-data-" + element.localName,
693 LOG("Blocklist::_loadBlocklistFromFile: Error constructing blocklist " + e);
699 _processItemNodes: function(itemNodes, prefix, handler) {
701 var itemName = prefix + "Item";
702 for (var i = 0; i < itemNodes.length; ++i) {
703 var blocklistElement = itemNodes.item(i);
704 if (!(blocklistElement instanceof Ci.nsIDOMElement) ||
705 blocklistElement.localName != itemName)
708 handler(blocklistElement, result);
713 _handleEmItemNode: function(blocklistElement, result) {
714 if (!matchesOSABI(blocklistElement))
717 var versionNodes = blocklistElement.childNodes;
718 var id = blocklistElement.getAttribute("id");
720 for (var x = 0; x < versionNodes.length; ++x) {
721 var versionRangeElement = versionNodes.item(x);
722 if (!(versionRangeElement instanceof Ci.nsIDOMElement) ||
723 versionRangeElement.localName != "versionRange")
726 result[id].push(new BlocklistItemData(versionRangeElement));
728 // if only the extension ID is specified block all versions of the
729 // extension for the current application.
730 if (result[id].length == 0)
731 result[id].push(new BlocklistItemData(null));
734 _handlePluginItemNode: function(blocklistElement, result) {
735 if (!matchesOSABI(blocklistElement))
738 var matchNodes = blocklistElement.childNodes;
743 var hasMatch = false;
744 for (var x = 0; x < matchNodes.length; ++x) {
745 var matchElement = matchNodes.item(x);
746 if (!(matchElement instanceof Ci.nsIDOMElement))
748 if (matchElement.localName == "match") {
749 var name = matchElement.getAttribute("name");
750 var exp = matchElement.getAttribute("exp");
752 blockEntry.matches[name] = new RegExp(exp, "m");
755 // Ignore invalid regular expressions
758 if (matchElement.localName == "versionRange")
759 blockEntry.versions.push(new BlocklistItemData(matchElement));
761 // Plugin entries require *something* to match to an actual plugin
764 // Add a default versionRange if there wasn't one specified
765 if (blockEntry.versions.length == 0)
766 blockEntry.versions.push(new BlocklistItemData(null));
767 result.push(blockEntry);
770 /* See nsIBlocklistService */
771 getPluginBlocklistState: function(plugin, appVersion, toolkitVersion) {
772 if (!this._pluginEntries)
773 this._loadBlocklist();
774 return this._getPluginBlocklistState(plugin, this._pluginEntries,
775 appVersion, toolkitVersion);
779 * Private version of getPluginBlocklistState that allows the caller to pass in
780 * the plugin blocklist entries.
783 * The nsIPluginTag to get the blocklist state for.
784 * @param pluginEntries
785 * The plugin blocklist entries to compare against.
787 * The application version to compare to, will use the current
789 * @param toolkitVersion
790 * The toolkit version to compare to, will use the current version if
792 * @returns The blocklist state for the item, one of the STATE constants as
793 * defined in nsIBlocklistService.
795 _getPluginBlocklistState: function(plugin, pluginEntries, appVersion, toolkitVersion) {
796 if (!gBlocklistEnabled)
797 return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
800 appVersion = gApp.version;
802 toolkitVersion = gApp.platformVersion;
804 for each (var blockEntry in pluginEntries) {
805 var matchFailed = false;
806 for (var name in blockEntry.matches) {
807 if (!(name in plugin) ||
808 typeof(plugin[name]) != "string" ||
809 !blockEntry.matches[name].test(plugin[name])) {
818 for (var i = 0; i < blockEntry.versions.length; i++) {
819 if (blockEntry.versions[i].includesItem(plugin.version, appVersion,
821 if (blockEntry.versions[i].severity >= gBlocklistLevel)
822 return Ci.nsIBlocklistService.STATE_BLOCKED;
823 if (blockEntry.versions[i].severity == SEVERITY_OUTDATED)
824 return Ci.nsIBlocklistService.STATE_OUTDATED;
825 return Ci.nsIBlocklistService.STATE_SOFTBLOCKED;
830 return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
833 _blocklistUpdated: function(oldAddonEntries, oldPluginEntries) {
837 AddonManager.getAddonsByTypes(["extension", "theme", "locale"], function(addons) {
839 for (let i = 0; i < addons.length; i++) {
840 let oldState = Ci.nsIBlocklistService.STATE_NOTBLOCKED;
842 oldState = self._getAddonBlocklistState(addons[i].id, addons[i].version,
844 let state = self.getAddonBlocklistState(addons[i].id, addons[i].version);
846 LOG("Blocklist state for " + addons[i].id + " changed from " +
847 oldState + " to " + state);
849 // Don't warn about add-ons becoming unblocked.
853 // We don't want to re-warn about add-ons
854 if (state == oldState)
857 // If an add-on has dropped from hard to soft blocked just mark it as
858 // user disabled and don't warn about it.
859 if (state == Ci.nsIBlocklistService.STATE_SOFTBLOCKED &&
860 oldState == Ci.nsIBlocklistService.STATE_BLOCKED) {
861 addons[i].userDisabled = true;
865 // If the add-on is already disabled for some reason then don't warn
867 if (!addons[i].isActive)
871 name: addons[i].name,
872 version: addons[i].version,
873 icon: addons[i].iconURL,
875 blocked: state == Ci.nsIBlocklistService.STATE_BLOCKED,
880 AddonManagerPrivate.updateAddonAppDisabledStates();
882 var phs = Cc["@mozilla.org/plugin/host;1"].
883 getService(Ci.nsIPluginHost);
884 var plugins = phs.getPluginTags();
886 for (let i = 0; i < plugins.length; i++) {
888 if (oldPluginEntries)
889 oldState = self._getPluginBlocklistState(plugins[i], oldPluginEntries);
890 let state = self.getPluginBlocklistState(plugins[i]);
891 LOG("Blocklist state for " + plugins[i].name + " changed from " +
892 oldState + " to " + state);
893 // We don't want to re-warn about items
894 if (state == oldState)
897 if (plugins[i].blocklisted) {
898 if (state == Ci.nsIBlocklistService.STATE_SOFTBLOCKED)
899 plugins[i].disabled = true;
901 else if (!plugins[i].disabled && state != Ci.nsIBlocklistService.STATE_NOT_BLOCKED) {
902 if (state == Ci.nsIBlocklistService.STATE_OUTDATED) {
903 gPref.setBoolPref(PREF_PLUGINS_NOTIFYUSER, true);
907 name: plugins[i].name,
908 version: plugins[i].version,
909 icon: "chrome://mozapps/skin/plugins/pluginGeneric.png",
911 blocked: state == Ci.nsIBlocklistService.STATE_BLOCKED,
916 plugins[i].blocklisted = state == Ci.nsIBlocklistService.STATE_BLOCKED;
919 if (addonList.length == 0)
922 if ("@mozilla.org/addons/blocklist-prompt;1" in Cc) {
924 let blockedPrompter = Cc["@mozilla.org/addons/blocklist-prompt;1"]
925 .getService(Ci.nsIBlocklistPrompt);
926 blockedPrompter.prompt(addonList);
937 // This lets the dialog get the raw js object
938 args.wrappedJSObject = args;
940 var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
941 getService(Ci.nsIWindowWatcher);
942 ww.openWindow(null, URI_BLOCKLIST_DIALOG, "",
943 "chrome,centerscreen,dialog,modal,titlebar", args);
945 for (let i = 0; i < addonList.length; i++) {
946 if (!addonList[i].disable)
949 if (addonList[i].item instanceof Ci.nsIPluginTag)
950 addonList[i].item.disabled = true;
952 addonList[i].item.userDisabled = true;
960 classID: Components.ID("{66354bc9-7ed1-4692-ae1d-8da97d6b205e}"),
961 QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
962 Ci.nsIBlocklistService,
963 Ci.nsITimerCallback]),
967 * Helper for constructing a blocklist.
969 function BlocklistItemData(versionRangeElement) {
970 var versionRange = this.getBlocklistVersionRange(versionRangeElement);
971 this.minVersion = versionRange.minVersion;
972 this.maxVersion = versionRange.maxVersion;
973 if (versionRangeElement && versionRangeElement.hasAttribute("severity"))
974 this.severity = versionRangeElement.getAttribute("severity");
976 this.severity = DEFAULT_SEVERITY;
977 this.targetApps = { };
980 if (versionRangeElement) {
981 for (var i = 0; i < versionRangeElement.childNodes.length; ++i) {
982 var targetAppElement = versionRangeElement.childNodes.item(i);
983 if (!(targetAppElement instanceof Ci.nsIDOMElement) ||
984 targetAppElement.localName != "targetApplication")
987 // default to the current application if id is not provided.
988 var appID = targetAppElement.hasAttribute("id") ? targetAppElement.getAttribute("id") : gApp.ID;
989 this.targetApps[appID] = this.getBlocklistAppVersions(targetAppElement);
992 // Default to all versions of the current application when no targetApplication
993 // elements were found
995 this.targetApps[gApp.ID] = this.getBlocklistAppVersions(null);
998 BlocklistItemData.prototype = {
1000 * Tests if a version of an item is included in the version range and target
1001 * application information represented by this BlocklistItemData using the
1002 * provided application and toolkit versions.
1004 * The version of the item being tested.
1006 * The application version to test with.
1007 * @param toolkitVersion
1008 * The toolkit version to test with.
1009 * @returns True if the version range covers the item version and application
1010 * or toolkit version.
1012 includesItem: function(version, appVersion, toolkitVersion) {
1013 // Some platforms have no version for plugins, these don't match if there
1014 // was a min/maxVersion provided
1015 if (!version && (this.minVersion || this.maxVersion))
1018 // Check if the item version matches
1019 if (!this.matchesRange(version, this.minVersion, this.maxVersion))
1022 // Check if the application version matches
1023 if (this.matchesTargetRange(gApp.ID, appVersion))
1026 // Check if the toolkit version matches
1027 return this.matchesTargetRange(TOOLKIT_ID, toolkitVersion);
1031 * Checks if a version is higher than or equal to the minVersion (if provided)
1032 * and lower than or equal to the maxVersion (if provided).
1034 * The version to test.
1036 * The minimum version. If null it is assumed that version is always
1039 * The maximum version. If null it is assumed that version is always
1042 matchesRange: function(version, minVersion, maxVersion) {
1043 if (minVersion && gVersionChecker.compare(version, minVersion) < 0)
1045 if (maxVersion && gVersionChecker.compare(version, maxVersion) > 0)
1051 * Tests if there is a matching range for the given target application id and
1054 * The application ID to test for, may be for an application or toolkit
1056 * The version of the application to test for.
1057 * @returns True if this version range covers the application version given.
1059 matchesTargetRange: function(appID, appVersion) {
1060 var blTargetApp = this.targetApps[appID];
1064 for (var x = 0; x < blTargetApp.length; ++x) {
1065 if (this.matchesRange(appVersion, blTargetApp[x].minVersion, blTargetApp[x].maxVersion))
1073 * Retrieves a version range (e.g. minVersion and maxVersion) for a
1074 * blocklist item's targetApplication element.
1075 * @param targetAppElement
1076 * A targetApplication blocklist element.
1077 * @returns An array of JS objects with the following properties:
1078 * "minVersion" The minimum version in a version range (default = null).
1079 * "maxVersion" The maximum version in a version range (default = null).
1081 getBlocklistAppVersions: function(targetAppElement) {
1082 var appVersions = [ ];
1084 if (targetAppElement) {
1085 for (var i = 0; i < targetAppElement.childNodes.length; ++i) {
1086 var versionRangeElement = targetAppElement.childNodes.item(i);
1087 if (!(versionRangeElement instanceof Ci.nsIDOMElement) ||
1088 versionRangeElement.localName != "versionRange")
1090 appVersions.push(this.getBlocklistVersionRange(versionRangeElement));
1093 // return minVersion = null and maxVersion = null if no specific versionRange
1094 // elements were found
1095 if (appVersions.length == 0)
1096 appVersions.push(this.getBlocklistVersionRange(null));
1101 * Retrieves a version range (e.g. minVersion and maxVersion) for a blocklist
1102 * versionRange element.
1103 * @param versionRangeElement
1104 * The versionRange blocklist element.
1105 * @returns A JS object with the following properties:
1106 * "minVersion" The minimum version in a version range (default = null).
1107 * "maxVersion" The maximum version in a version range (default = null).
1109 getBlocklistVersionRange: function(versionRangeElement) {
1110 var minVersion = null;
1111 var maxVersion = null;
1112 if (!versionRangeElement)
1113 return { minVersion: minVersion, maxVersion: maxVersion };
1115 if (versionRangeElement.hasAttribute("minVersion"))
1116 minVersion = versionRangeElement.getAttribute("minVersion");
1117 if (versionRangeElement.hasAttribute("maxVersion"))
1118 maxVersion = versionRangeElement.getAttribute("maxVersion");
1120 return { minVersion: minVersion, maxVersion: maxVersion };
1124 var NSGetFactory = XPCOMUtils.generateNSGetFactory([Blocklist]);