toolkit/mozapps/extensions/nsBlocklistService.js

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

michael@0 1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2
michael@0 3 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 "use strict";
michael@0 8
michael@0 9 const Cc = Components.classes;
michael@0 10 const Ci = Components.interfaces;
michael@0 11 const Cr = Components.results;
michael@0 12
michael@0 13 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
michael@0 14 Components.utils.import("resource://gre/modules/AddonManager.jsm");
michael@0 15 Components.utils.import("resource://gre/modules/Services.jsm");
michael@0 16
michael@0 17 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
michael@0 18 "resource://gre/modules/FileUtils.jsm");
michael@0 19 XPCOMUtils.defineLazyModuleGetter(this, "UpdateChannel",
michael@0 20 "resource://gre/modules/UpdateChannel.jsm");
michael@0 21 XPCOMUtils.defineLazyModuleGetter(this, "OS",
michael@0 22 "resource://gre/modules/osfile.jsm");
michael@0 23 XPCOMUtils.defineLazyModuleGetter(this, "Task",
michael@0 24 "resource://gre/modules/Task.jsm");
michael@0 25
michael@0 26 const TOOLKIT_ID = "toolkit@mozilla.org"
michael@0 27 const KEY_PROFILEDIR = "ProfD";
michael@0 28 const KEY_APPDIR = "XCurProcD";
michael@0 29 const FILE_BLOCKLIST = "blocklist.xml";
michael@0 30 const PREF_BLOCKLIST_LASTUPDATETIME = "app.update.lastUpdateTime.blocklist-background-update-timer";
michael@0 31 const PREF_BLOCKLIST_URL = "extensions.blocklist.url";
michael@0 32 const PREF_BLOCKLIST_ITEM_URL = "extensions.blocklist.itemURL";
michael@0 33 const PREF_BLOCKLIST_ENABLED = "extensions.blocklist.enabled";
michael@0 34 const PREF_BLOCKLIST_INTERVAL = "extensions.blocklist.interval";
michael@0 35 const PREF_BLOCKLIST_LEVEL = "extensions.blocklist.level";
michael@0 36 const PREF_BLOCKLIST_PINGCOUNTTOTAL = "extensions.blocklist.pingCountTotal";
michael@0 37 const PREF_BLOCKLIST_PINGCOUNTVERSION = "extensions.blocklist.pingCountVersion";
michael@0 38 const PREF_BLOCKLIST_SUPPRESSUI = "extensions.blocklist.suppressUI";
michael@0 39 const PREF_PLUGINS_NOTIFYUSER = "plugins.update.notifyUser";
michael@0 40 const PREF_GENERAL_USERAGENT_LOCALE = "general.useragent.locale";
michael@0 41 const PREF_APP_DISTRIBUTION = "distribution.id";
michael@0 42 const PREF_APP_DISTRIBUTION_VERSION = "distribution.version";
michael@0 43 const PREF_EM_LOGGING_ENABLED = "extensions.logging.enabled";
michael@0 44 const XMLURI_BLOCKLIST = "http://www.mozilla.org/2006/addons-blocklist";
michael@0 45 const XMLURI_PARSE_ERROR = "http://www.mozilla.org/newlayout/xml/parsererror.xml"
michael@0 46 const UNKNOWN_XPCOM_ABI = "unknownABI";
michael@0 47 const URI_BLOCKLIST_DIALOG = "chrome://mozapps/content/extensions/blocklist.xul"
michael@0 48 const DEFAULT_SEVERITY = 3;
michael@0 49 const DEFAULT_LEVEL = 2;
michael@0 50 const MAX_BLOCK_LEVEL = 3;
michael@0 51 const SEVERITY_OUTDATED = 0;
michael@0 52 const VULNERABILITYSTATUS_NONE = 0;
michael@0 53 const VULNERABILITYSTATUS_UPDATE_AVAILABLE = 1;
michael@0 54 const VULNERABILITYSTATUS_NO_UPDATE = 2;
michael@0 55
michael@0 56 const EXTENSION_BLOCK_FILTERS = ["id", "name", "creator", "homepageURL", "updateURL"];
michael@0 57
michael@0 58 var gLoggingEnabled = null;
michael@0 59 var gBlocklistEnabled = true;
michael@0 60 var gBlocklistLevel = DEFAULT_LEVEL;
michael@0 61
michael@0 62 XPCOMUtils.defineLazyServiceGetter(this, "gConsole",
michael@0 63 "@mozilla.org/consoleservice;1",
michael@0 64 "nsIConsoleService");
michael@0 65
michael@0 66 XPCOMUtils.defineLazyServiceGetter(this, "gVersionChecker",
michael@0 67 "@mozilla.org/xpcom/version-comparator;1",
michael@0 68 "nsIVersionComparator");
michael@0 69
michael@0 70 XPCOMUtils.defineLazyGetter(this, "gPref", function bls_gPref() {
michael@0 71 return Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefService).
michael@0 72 QueryInterface(Ci.nsIPrefBranch);
michael@0 73 });
michael@0 74
michael@0 75 XPCOMUtils.defineLazyGetter(this, "gApp", function bls_gApp() {
michael@0 76 return Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo).
michael@0 77 QueryInterface(Ci.nsIXULRuntime);
michael@0 78 });
michael@0 79
michael@0 80 XPCOMUtils.defineLazyGetter(this, "gABI", function bls_gABI() {
michael@0 81 let abi = null;
michael@0 82 try {
michael@0 83 abi = gApp.XPCOMABI;
michael@0 84 }
michael@0 85 catch (e) {
michael@0 86 LOG("BlockList Global gABI: XPCOM ABI unknown.");
michael@0 87 }
michael@0 88 #ifdef XP_MACOSX
michael@0 89 // Mac universal build should report a different ABI than either macppc
michael@0 90 // or mactel.
michael@0 91 let macutils = Cc["@mozilla.org/xpcom/mac-utils;1"].
michael@0 92 getService(Ci.nsIMacUtils);
michael@0 93
michael@0 94 if (macutils.isUniversalBinary)
michael@0 95 abi += "-u-" + macutils.architecturesInBinary;
michael@0 96 #endif
michael@0 97 return abi;
michael@0 98 });
michael@0 99
michael@0 100 XPCOMUtils.defineLazyGetter(this, "gOSVersion", function bls_gOSVersion() {
michael@0 101 let osVersion;
michael@0 102 let sysInfo = Cc["@mozilla.org/system-info;1"].
michael@0 103 getService(Ci.nsIPropertyBag2);
michael@0 104 try {
michael@0 105 osVersion = sysInfo.getProperty("name") + " " + sysInfo.getProperty("version");
michael@0 106 }
michael@0 107 catch (e) {
michael@0 108 LOG("BlockList Global gOSVersion: OS Version unknown.");
michael@0 109 }
michael@0 110
michael@0 111 if (osVersion) {
michael@0 112 try {
michael@0 113 osVersion += " (" + sysInfo.getProperty("secondaryLibrary") + ")";
michael@0 114 }
michael@0 115 catch (e) {
michael@0 116 // Not all platforms have a secondary widget library, so an error is nothing to worry about.
michael@0 117 }
michael@0 118 osVersion = encodeURIComponent(osVersion);
michael@0 119 }
michael@0 120 return osVersion;
michael@0 121 });
michael@0 122
michael@0 123 // shared code for suppressing bad cert dialogs
michael@0 124 XPCOMUtils.defineLazyGetter(this, "gCertUtils", function bls_gCertUtils() {
michael@0 125 let temp = { };
michael@0 126 Components.utils.import("resource://gre/modules/CertUtils.jsm", temp);
michael@0 127 return temp;
michael@0 128 });
michael@0 129
michael@0 130 function getObserverService() {
michael@0 131 return Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
michael@0 132 }
michael@0 133
michael@0 134 /**
michael@0 135 * Logs a string to the error console.
michael@0 136 * @param string
michael@0 137 * The string to write to the error console..
michael@0 138 */
michael@0 139 function LOG(string) {
michael@0 140 if (gLoggingEnabled) {
michael@0 141 dump("*** " + string + "\n");
michael@0 142 gConsole.logStringMessage(string);
michael@0 143 }
michael@0 144 }
michael@0 145
michael@0 146 /**
michael@0 147 * Gets a preference value, handling the case where there is no default.
michael@0 148 * @param func
michael@0 149 * The name of the preference function to call, on nsIPrefBranch
michael@0 150 * @param preference
michael@0 151 * The name of the preference
michael@0 152 * @param defaultValue
michael@0 153 * The default value to return in the event the preference has
michael@0 154 * no setting
michael@0 155 * @returns The value of the preference, or undefined if there was no
michael@0 156 * user or default value.
michael@0 157 */
michael@0 158 function getPref(func, preference, defaultValue) {
michael@0 159 try {
michael@0 160 return gPref[func](preference);
michael@0 161 }
michael@0 162 catch (e) {
michael@0 163 }
michael@0 164 return defaultValue;
michael@0 165 }
michael@0 166
michael@0 167 /**
michael@0 168 * Constructs a URI to a spec.
michael@0 169 * @param spec
michael@0 170 * The spec to construct a URI to
michael@0 171 * @returns The nsIURI constructed.
michael@0 172 */
michael@0 173 function newURI(spec) {
michael@0 174 var ioServ = Cc["@mozilla.org/network/io-service;1"].
michael@0 175 getService(Ci.nsIIOService);
michael@0 176 return ioServ.newURI(spec, null, null);
michael@0 177 }
michael@0 178
michael@0 179 // Restarts the application checking in with observers first
michael@0 180 function restartApp() {
michael@0 181 // Notify all windows that an application quit has been requested.
michael@0 182 var os = Cc["@mozilla.org/observer-service;1"].
michael@0 183 getService(Ci.nsIObserverService);
michael@0 184 var cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].
michael@0 185 createInstance(Ci.nsISupportsPRBool);
michael@0 186 os.notifyObservers(cancelQuit, "quit-application-requested", null);
michael@0 187
michael@0 188 // Something aborted the quit process.
michael@0 189 if (cancelQuit.data)
michael@0 190 return;
michael@0 191
michael@0 192 var as = Cc["@mozilla.org/toolkit/app-startup;1"].
michael@0 193 getService(Ci.nsIAppStartup);
michael@0 194 as.quit(Ci.nsIAppStartup.eRestart | Ci.nsIAppStartup.eAttemptQuit);
michael@0 195 }
michael@0 196
michael@0 197 /**
michael@0 198 * Checks whether this blocklist element is valid for the current OS and ABI.
michael@0 199 * If the element has an "os" attribute then the current OS must appear in
michael@0 200 * its comma separated list for the element to be valid. Similarly for the
michael@0 201 * xpcomabi attribute.
michael@0 202 */
michael@0 203 function matchesOSABI(blocklistElement) {
michael@0 204 if (blocklistElement.hasAttribute("os")) {
michael@0 205 var choices = blocklistElement.getAttribute("os").split(",");
michael@0 206 if (choices.length > 0 && choices.indexOf(gApp.OS) < 0)
michael@0 207 return false;
michael@0 208 }
michael@0 209
michael@0 210 if (blocklistElement.hasAttribute("xpcomabi")) {
michael@0 211 choices = blocklistElement.getAttribute("xpcomabi").split(",");
michael@0 212 if (choices.length > 0 && choices.indexOf(gApp.XPCOMABI) < 0)
michael@0 213 return false;
michael@0 214 }
michael@0 215
michael@0 216 return true;
michael@0 217 }
michael@0 218
michael@0 219 /**
michael@0 220 * Gets the current value of the locale. It's possible for this preference to
michael@0 221 * be localized, so we have to do a little extra work here. Similar code
michael@0 222 * exists in nsHttpHandler.cpp when building the UA string.
michael@0 223 */
michael@0 224 function getLocale() {
michael@0 225 try {
michael@0 226 // Get the default branch
michael@0 227 var defaultPrefs = gPref.getDefaultBranch(null);
michael@0 228 return defaultPrefs.getComplexValue(PREF_GENERAL_USERAGENT_LOCALE,
michael@0 229 Ci.nsIPrefLocalizedString).data;
michael@0 230 } catch (e) {}
michael@0 231
michael@0 232 return gPref.getCharPref(PREF_GENERAL_USERAGENT_LOCALE);
michael@0 233 }
michael@0 234
michael@0 235 /* Get the distribution pref values, from defaults only */
michael@0 236 function getDistributionPrefValue(aPrefName) {
michael@0 237 var prefValue = "default";
michael@0 238
michael@0 239 var defaults = gPref.getDefaultBranch(null);
michael@0 240 try {
michael@0 241 prefValue = defaults.getCharPref(aPrefName);
michael@0 242 } catch (e) {
michael@0 243 // use default when pref not found
michael@0 244 }
michael@0 245
michael@0 246 return prefValue;
michael@0 247 }
michael@0 248
michael@0 249 /**
michael@0 250 * Parse a string representation of a regular expression. Needed because we
michael@0 251 * use the /pattern/flags form (because it's detectable), which is only
michael@0 252 * supported as a literal in JS.
michael@0 253 *
michael@0 254 * @param aStr
michael@0 255 * String representation of regexp
michael@0 256 * @return RegExp instance
michael@0 257 */
michael@0 258 function parseRegExp(aStr) {
michael@0 259 let lastSlash = aStr.lastIndexOf("/");
michael@0 260 let pattern = aStr.slice(1, lastSlash);
michael@0 261 let flags = aStr.slice(lastSlash + 1);
michael@0 262 return new RegExp(pattern, flags);
michael@0 263 }
michael@0 264
michael@0 265 /**
michael@0 266 * Manages the Blocklist. The Blocklist is a representation of the contents of
michael@0 267 * blocklist.xml and allows us to remotely disable / re-enable blocklisted
michael@0 268 * items managed by the Extension Manager with an item's appDisabled property.
michael@0 269 * It also blocklists plugins with data from blocklist.xml.
michael@0 270 */
michael@0 271
michael@0 272 function Blocklist() {
michael@0 273 let os = getObserverService();
michael@0 274 os.addObserver(this, "xpcom-shutdown", false);
michael@0 275 gLoggingEnabled = getPref("getBoolPref", PREF_EM_LOGGING_ENABLED, false);
michael@0 276 gBlocklistEnabled = getPref("getBoolPref", PREF_BLOCKLIST_ENABLED, true);
michael@0 277 gBlocklistLevel = Math.min(getPref("getIntPref", PREF_BLOCKLIST_LEVEL, DEFAULT_LEVEL),
michael@0 278 MAX_BLOCK_LEVEL);
michael@0 279 gPref.addObserver("extensions.blocklist.", this, false);
michael@0 280 gPref.addObserver(PREF_EM_LOGGING_ENABLED, this, false);
michael@0 281 }
michael@0 282
michael@0 283 Blocklist.prototype = {
michael@0 284 /**
michael@0 285 * Extension ID -> array of Version Ranges
michael@0 286 * Each value in the version range array is a JS Object that has the
michael@0 287 * following properties:
michael@0 288 * "minVersion" The minimum version in a version range (default = 0)
michael@0 289 * "maxVersion" The maximum version in a version range (default = *)
michael@0 290 * "targetApps" Application ID -> array of Version Ranges
michael@0 291 * (default = current application ID)
michael@0 292 * Each value in the version range array is a JS Object that
michael@0 293 * has the following properties:
michael@0 294 * "minVersion" The minimum version in a version range
michael@0 295 * (default = 0)
michael@0 296 * "maxVersion" The maximum version in a version range
michael@0 297 * (default = *)
michael@0 298 */
michael@0 299 _addonEntries: null,
michael@0 300 _pluginEntries: null,
michael@0 301
michael@0 302 observe: function Blocklist_observe(aSubject, aTopic, aData) {
michael@0 303 switch (aTopic) {
michael@0 304 case "xpcom-shutdown":
michael@0 305 let os = getObserverService();
michael@0 306 os.removeObserver(this, "xpcom-shutdown");
michael@0 307 gPref.removeObserver("extensions.blocklist.", this);
michael@0 308 gPref.removeObserver(PREF_EM_LOGGING_ENABLED, this);
michael@0 309 break;
michael@0 310 case "nsPref:changed":
michael@0 311 switch (aData) {
michael@0 312 case PREF_EM_LOGGING_ENABLED:
michael@0 313 gLoggingEnabled = getPref("getBoolPref", PREF_EM_LOGGING_ENABLED, false);
michael@0 314 break;
michael@0 315 case PREF_BLOCKLIST_ENABLED:
michael@0 316 gBlocklistEnabled = getPref("getBoolPref", PREF_BLOCKLIST_ENABLED, true);
michael@0 317 this._loadBlocklist();
michael@0 318 this._blocklistUpdated(null, null);
michael@0 319 break;
michael@0 320 case PREF_BLOCKLIST_LEVEL:
michael@0 321 gBlocklistLevel = Math.min(getPref("getIntPref", PREF_BLOCKLIST_LEVEL, DEFAULT_LEVEL),
michael@0 322 MAX_BLOCK_LEVEL);
michael@0 323 this._blocklistUpdated(null, null);
michael@0 324 break;
michael@0 325 }
michael@0 326 break;
michael@0 327 }
michael@0 328 },
michael@0 329
michael@0 330 /* See nsIBlocklistService */
michael@0 331 isAddonBlocklisted: function Blocklist_isAddonBlocklisted(addon, appVersion, toolkitVersion) {
michael@0 332 return this.getAddonBlocklistState(addon, appVersion, toolkitVersion) ==
michael@0 333 Ci.nsIBlocklistService.STATE_BLOCKED;
michael@0 334 },
michael@0 335
michael@0 336 /* See nsIBlocklistService */
michael@0 337 getAddonBlocklistState: function Blocklist_getAddonBlocklistState(addon, appVersion, toolkitVersion) {
michael@0 338 if (!this._addonEntries)
michael@0 339 this._loadBlocklist();
michael@0 340 return this._getAddonBlocklistState(addon, this._addonEntries,
michael@0 341 appVersion, toolkitVersion);
michael@0 342 },
michael@0 343
michael@0 344 /**
michael@0 345 * Private version of getAddonBlocklistState that allows the caller to pass in
michael@0 346 * the add-on blocklist entries to compare against.
michael@0 347 *
michael@0 348 * @param id
michael@0 349 * The ID of the item to get the blocklist state for.
michael@0 350 * @param version
michael@0 351 * The version of the item to get the blocklist state for.
michael@0 352 * @param addonEntries
michael@0 353 * The add-on blocklist entries to compare against.
michael@0 354 * @param appVersion
michael@0 355 * The application version to compare to, will use the current
michael@0 356 * version if null.
michael@0 357 * @param toolkitVersion
michael@0 358 * The toolkit version to compare to, will use the current version if
michael@0 359 * null.
michael@0 360 * @returns The blocklist state for the item, one of the STATE constants as
michael@0 361 * defined in nsIBlocklistService.
michael@0 362 */
michael@0 363 _getAddonBlocklistState: function Blocklist_getAddonBlocklistStateCall(addon,
michael@0 364 addonEntries, appVersion, toolkitVersion) {
michael@0 365 if (!gBlocklistEnabled)
michael@0 366 return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
michael@0 367
michael@0 368 if (!appVersion)
michael@0 369 appVersion = gApp.version;
michael@0 370 if (!toolkitVersion)
michael@0 371 toolkitVersion = gApp.platformVersion;
michael@0 372
michael@0 373 var blItem = this._findMatchingAddonEntry(addonEntries, addon);
michael@0 374 if (!blItem)
michael@0 375 return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
michael@0 376
michael@0 377 for (let currentblItem of blItem.versions) {
michael@0 378 if (currentblItem.includesItem(addon.version, appVersion, toolkitVersion))
michael@0 379 return currentblItem.severity >= gBlocklistLevel ? Ci.nsIBlocklistService.STATE_BLOCKED :
michael@0 380 Ci.nsIBlocklistService.STATE_SOFTBLOCKED;
michael@0 381 }
michael@0 382 return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
michael@0 383 },
michael@0 384
michael@0 385 /**
michael@0 386 * Returns the set of prefs of the add-on stored in the blocklist file
michael@0 387 * (probably to revert them on disabling).
michael@0 388 * @param addon
michael@0 389 * The add-on whose to-be-reset prefs are to be found.
michael@0 390 */
michael@0 391 _getAddonPrefs: function Blocklist_getAddonPrefs(addon) {
michael@0 392 let entry = this._findMatchingAddonEntry(this._addonEntries, addon);
michael@0 393 return entry.prefs.slice(0);
michael@0 394 },
michael@0 395
michael@0 396 _findMatchingAddonEntry: function Blocklist_findMatchingAddonEntry(aAddonEntries,
michael@0 397 aAddon) {
michael@0 398 if (!aAddon)
michael@0 399 return null;
michael@0 400 // Returns true if the params object passes the constraints set by entry.
michael@0 401 // (For every non-null property in entry, the same key must exist in
michael@0 402 // params and value must be the same)
michael@0 403 function checkEntry(entry, params) {
michael@0 404 for (let [key, value] of entry) {
michael@0 405 if (value === null || value === undefined)
michael@0 406 continue;
michael@0 407 if (params[key]) {
michael@0 408 if (value instanceof RegExp) {
michael@0 409 if (!value.test(params[key])) {
michael@0 410 return false;
michael@0 411 }
michael@0 412 } else if (value !== params[key]) {
michael@0 413 return false;
michael@0 414 }
michael@0 415 } else {
michael@0 416 return false;
michael@0 417 }
michael@0 418 }
michael@0 419 return true;
michael@0 420 }
michael@0 421
michael@0 422 let params = {};
michael@0 423 for (let filter of EXTENSION_BLOCK_FILTERS) {
michael@0 424 params[filter] = aAddon[filter];
michael@0 425 }
michael@0 426 if (params.creator)
michael@0 427 params.creator = params.creator.name;
michael@0 428 for (let entry of aAddonEntries) {
michael@0 429 if (checkEntry(entry.attributes, params)) {
michael@0 430 return entry;
michael@0 431 }
michael@0 432 }
michael@0 433 return null;
michael@0 434 },
michael@0 435
michael@0 436 /* See nsIBlocklistService */
michael@0 437 getAddonBlocklistURL: function Blocklist_getAddonBlocklistURL(addon, appVersion, toolkitVersion) {
michael@0 438 if (!gBlocklistEnabled)
michael@0 439 return "";
michael@0 440
michael@0 441 if (!this._addonEntries)
michael@0 442 this._loadBlocklist();
michael@0 443
michael@0 444 let blItem = this._findMatchingAddonEntry(this._addonEntries, addon);
michael@0 445 if (!blItem || !blItem.blockID)
michael@0 446 return null;
michael@0 447
michael@0 448 return this._createBlocklistURL(blItem.blockID);
michael@0 449 },
michael@0 450
michael@0 451 _createBlocklistURL: function Blocklist_createBlocklistURL(id) {
michael@0 452 let url = Services.urlFormatter.formatURLPref(PREF_BLOCKLIST_ITEM_URL);
michael@0 453 url = url.replace(/%blockID%/g, id);
michael@0 454
michael@0 455 return url;
michael@0 456 },
michael@0 457
michael@0 458 notify: function Blocklist_notify(aTimer) {
michael@0 459 if (!gBlocklistEnabled)
michael@0 460 return;
michael@0 461
michael@0 462 try {
michael@0 463 var dsURI = gPref.getCharPref(PREF_BLOCKLIST_URL);
michael@0 464 }
michael@0 465 catch (e) {
michael@0 466 LOG("Blocklist::notify: The " + PREF_BLOCKLIST_URL + " preference" +
michael@0 467 " is missing!");
michael@0 468 return;
michael@0 469 }
michael@0 470
michael@0 471 var pingCountVersion = getPref("getIntPref", PREF_BLOCKLIST_PINGCOUNTVERSION, 0);
michael@0 472 var pingCountTotal = getPref("getIntPref", PREF_BLOCKLIST_PINGCOUNTTOTAL, 1);
michael@0 473 var daysSinceLastPing = 0;
michael@0 474 if (pingCountVersion == 0) {
michael@0 475 daysSinceLastPing = "new";
michael@0 476 }
michael@0 477 else {
michael@0 478 // Seconds in one day is used because nsIUpdateTimerManager stores the
michael@0 479 // last update time in seconds.
michael@0 480 let secondsInDay = 60 * 60 * 24;
michael@0 481 let lastUpdateTime = getPref("getIntPref", PREF_BLOCKLIST_LASTUPDATETIME, 0);
michael@0 482 if (lastUpdateTime == 0) {
michael@0 483 daysSinceLastPing = "invalid";
michael@0 484 }
michael@0 485 else {
michael@0 486 let now = Math.round(Date.now() / 1000);
michael@0 487 daysSinceLastPing = Math.floor((now - lastUpdateTime) / secondsInDay);
michael@0 488 }
michael@0 489
michael@0 490 if (daysSinceLastPing == 0 || daysSinceLastPing == "invalid") {
michael@0 491 pingCountVersion = pingCountTotal = "invalid";
michael@0 492 }
michael@0 493 }
michael@0 494
michael@0 495 if (pingCountVersion < 1)
michael@0 496 pingCountVersion = 1;
michael@0 497 if (pingCountTotal < 1)
michael@0 498 pingCountTotal = 1;
michael@0 499
michael@0 500 dsURI = dsURI.replace(/%APP_ID%/g, gApp.ID);
michael@0 501 dsURI = dsURI.replace(/%APP_VERSION%/g, gApp.version);
michael@0 502 dsURI = dsURI.replace(/%PRODUCT%/g, gApp.name);
michael@0 503 dsURI = dsURI.replace(/%VERSION%/g, gApp.version);
michael@0 504 dsURI = dsURI.replace(/%BUILD_ID%/g, gApp.appBuildID);
michael@0 505 dsURI = dsURI.replace(/%BUILD_TARGET%/g, gApp.OS + "_" + gABI);
michael@0 506 dsURI = dsURI.replace(/%OS_VERSION%/g, gOSVersion);
michael@0 507 dsURI = dsURI.replace(/%LOCALE%/g, getLocale());
michael@0 508 dsURI = dsURI.replace(/%CHANNEL%/g, UpdateChannel.get());
michael@0 509 dsURI = dsURI.replace(/%PLATFORM_VERSION%/g, gApp.platformVersion);
michael@0 510 dsURI = dsURI.replace(/%DISTRIBUTION%/g,
michael@0 511 getDistributionPrefValue(PREF_APP_DISTRIBUTION));
michael@0 512 dsURI = dsURI.replace(/%DISTRIBUTION_VERSION%/g,
michael@0 513 getDistributionPrefValue(PREF_APP_DISTRIBUTION_VERSION));
michael@0 514 dsURI = dsURI.replace(/%PING_COUNT%/g, pingCountVersion);
michael@0 515 dsURI = dsURI.replace(/%TOTAL_PING_COUNT%/g, pingCountTotal);
michael@0 516 dsURI = dsURI.replace(/%DAYS_SINCE_LAST_PING%/g, daysSinceLastPing);
michael@0 517 dsURI = dsURI.replace(/\+/g, "%2B");
michael@0 518
michael@0 519 // Under normal operations it will take around 5,883,516 years before the
michael@0 520 // preferences used to store pingCountVersion and pingCountTotal will rollover
michael@0 521 // so this code doesn't bother trying to do the "right thing" here.
michael@0 522 if (pingCountVersion != "invalid") {
michael@0 523 pingCountVersion++;
michael@0 524 if (pingCountVersion > 2147483647) {
michael@0 525 // Rollover to -1 if the value is greater than what is support by an
michael@0 526 // integer preference. The -1 indicates that the counter has been reset.
michael@0 527 pingCountVersion = -1;
michael@0 528 }
michael@0 529 gPref.setIntPref(PREF_BLOCKLIST_PINGCOUNTVERSION, pingCountVersion);
michael@0 530 }
michael@0 531
michael@0 532 if (pingCountTotal != "invalid") {
michael@0 533 pingCountTotal++;
michael@0 534 if (pingCountTotal > 2147483647) {
michael@0 535 // Rollover to 1 if the value is greater than what is support by an
michael@0 536 // integer preference.
michael@0 537 pingCountTotal = -1;
michael@0 538 }
michael@0 539 gPref.setIntPref(PREF_BLOCKLIST_PINGCOUNTTOTAL, pingCountTotal);
michael@0 540 }
michael@0 541
michael@0 542 // Verify that the URI is valid
michael@0 543 try {
michael@0 544 var uri = newURI(dsURI);
michael@0 545 }
michael@0 546 catch (e) {
michael@0 547 LOG("Blocklist::notify: There was an error creating the blocklist URI\r\n" +
michael@0 548 "for: " + dsURI + ", error: " + e);
michael@0 549 return;
michael@0 550 }
michael@0 551
michael@0 552 LOG("Blocklist::notify: Requesting " + uri.spec);
michael@0 553 var request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
michael@0 554 createInstance(Ci.nsIXMLHttpRequest);
michael@0 555 request.open("GET", uri.spec, true);
michael@0 556 request.channel.notificationCallbacks = new gCertUtils.BadCertHandler();
michael@0 557 request.overrideMimeType("text/xml");
michael@0 558 request.setRequestHeader("Cache-Control", "no-cache");
michael@0 559 request.QueryInterface(Components.interfaces.nsIJSXMLHttpRequest);
michael@0 560
michael@0 561 var self = this;
michael@0 562 request.addEventListener("error", function errorEventListener(event) {
michael@0 563 self.onXMLError(event); }, false);
michael@0 564 request.addEventListener("load", function loadEventListener(event) {
michael@0 565 self.onXMLLoad(event); }, false);
michael@0 566 request.send(null);
michael@0 567
michael@0 568 // When the blocklist loads we need to compare it to the current copy so
michael@0 569 // make sure we have loaded it.
michael@0 570 if (!this._addonEntries)
michael@0 571 this._loadBlocklist();
michael@0 572 },
michael@0 573
michael@0 574 onXMLLoad: Task.async(function* (aEvent) {
michael@0 575 let request = aEvent.target;
michael@0 576 try {
michael@0 577 gCertUtils.checkCert(request.channel);
michael@0 578 }
michael@0 579 catch (e) {
michael@0 580 LOG("Blocklist::onXMLLoad: " + e);
michael@0 581 return;
michael@0 582 }
michael@0 583 let responseXML = request.responseXML;
michael@0 584 if (!responseXML || responseXML.documentElement.namespaceURI == XMLURI_PARSE_ERROR ||
michael@0 585 (request.status != 200 && request.status != 0)) {
michael@0 586 LOG("Blocklist::onXMLLoad: there was an error during load");
michael@0 587 return;
michael@0 588 }
michael@0 589
michael@0 590 var oldAddonEntries = this._addonEntries;
michael@0 591 var oldPluginEntries = this._pluginEntries;
michael@0 592 this._addonEntries = [];
michael@0 593 this._pluginEntries = [];
michael@0 594
michael@0 595 this._loadBlocklistFromString(request.responseText);
michael@0 596 this._blocklistUpdated(oldAddonEntries, oldPluginEntries);
michael@0 597
michael@0 598 try {
michael@0 599 let path = OS.Path.join(OS.Constants.Path.profileDir, FILE_BLOCKLIST);
michael@0 600 yield OS.File.writeAtomic(path, request.responseText, {tmpPath: path + ".tmp"});
michael@0 601 } catch (e) {
michael@0 602 LOG("Blocklist::onXMLLoad: " + e);
michael@0 603 }
michael@0 604 }),
michael@0 605
michael@0 606 onXMLError: function Blocklist_onXMLError(aEvent) {
michael@0 607 try {
michael@0 608 var request = aEvent.target;
michael@0 609 // the following may throw (e.g. a local file or timeout)
michael@0 610 var status = request.status;
michael@0 611 }
michael@0 612 catch (e) {
michael@0 613 request = aEvent.target.channel.QueryInterface(Ci.nsIRequest);
michael@0 614 status = request.status;
michael@0 615 }
michael@0 616 var statusText = "nsIXMLHttpRequest channel unavailable";
michael@0 617 // When status is 0 we don't have a valid channel.
michael@0 618 if (status != 0) {
michael@0 619 try {
michael@0 620 statusText = request.statusText;
michael@0 621 } catch (e) {
michael@0 622 }
michael@0 623 }
michael@0 624 LOG("Blocklist:onError: There was an error loading the blocklist file\r\n" +
michael@0 625 statusText);
michael@0 626 },
michael@0 627
michael@0 628 /**
michael@0 629 * Finds the newest blocklist file from the application and the profile and
michael@0 630 * load it or does nothing if neither exist.
michael@0 631 */
michael@0 632 _loadBlocklist: function Blocklist_loadBlocklist() {
michael@0 633 this._addonEntries = [];
michael@0 634 this._pluginEntries = [];
michael@0 635 var profFile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_BLOCKLIST]);
michael@0 636 if (profFile.exists()) {
michael@0 637 this._loadBlocklistFromFile(profFile);
michael@0 638 return;
michael@0 639 }
michael@0 640 var appFile = FileUtils.getFile(KEY_APPDIR, [FILE_BLOCKLIST]);
michael@0 641 if (appFile.exists()) {
michael@0 642 this._loadBlocklistFromFile(appFile);
michael@0 643 return;
michael@0 644 }
michael@0 645 LOG("Blocklist::_loadBlocklist: no XML File found");
michael@0 646 },
michael@0 647
michael@0 648 /**
michael@0 649 # The blocklist XML file looks something like this:
michael@0 650 #
michael@0 651 # <blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist">
michael@0 652 # <emItems>
michael@0 653 # <emItem id="item_1@domain" blockID="i1">
michael@0 654 # <prefs>
michael@0 655 # <pref>accessibility.accesskeycausesactivation</pref>
michael@0 656 # <pref>accessibility.blockautorefresh</pref>
michael@0 657 # </prefs>
michael@0 658 # <versionRange minVersion="1.0" maxVersion="2.0.*">
michael@0 659 # <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
michael@0 660 # <versionRange minVersion="1.5" maxVersion="1.5.*"/>
michael@0 661 # <versionRange minVersion="1.7" maxVersion="1.7.*"/>
michael@0 662 # </targetApplication>
michael@0 663 # <targetApplication id="toolkit@mozilla.org">
michael@0 664 # <versionRange minVersion="1.9" maxVersion="1.9.*"/>
michael@0 665 # </targetApplication>
michael@0 666 # </versionRange>
michael@0 667 # <versionRange minVersion="3.0" maxVersion="3.0.*">
michael@0 668 # <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
michael@0 669 # <versionRange minVersion="1.5" maxVersion="1.5.*"/>
michael@0 670 # </targetApplication>
michael@0 671 # <targetApplication id="toolkit@mozilla.org">
michael@0 672 # <versionRange minVersion="1.9" maxVersion="1.9.*"/>
michael@0 673 # </targetApplication>
michael@0 674 # </versionRange>
michael@0 675 # </emItem>
michael@0 676 # <emItem id="item_2@domain" blockID="i2">
michael@0 677 # <versionRange minVersion="3.1" maxVersion="4.*"/>
michael@0 678 # </emItem>
michael@0 679 # <emItem id="item_3@domain">
michael@0 680 # <versionRange>
michael@0 681 # <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
michael@0 682 # <versionRange minVersion="1.5" maxVersion="1.5.*"/>
michael@0 683 # </targetApplication>
michael@0 684 # </versionRange>
michael@0 685 # </emItem>
michael@0 686 # <emItem id="item_4@domain" blockID="i3">
michael@0 687 # <versionRange>
michael@0 688 # <targetApplication>
michael@0 689 # <versionRange minVersion="1.5" maxVersion="1.5.*"/>
michael@0 690 # </targetApplication>
michael@0 691 # </versionRange>
michael@0 692 # <emItem id="/@badperson\.com$/"/>
michael@0 693 # </emItems>
michael@0 694 # <pluginItems>
michael@0 695 # <pluginItem blockID="i4">
michael@0 696 # <!-- All match tags must match a plugin to blocklist a plugin -->
michael@0 697 # <match name="name" exp="some plugin"/>
michael@0 698 # <match name="description" exp="1[.]2[.]3"/>
michael@0 699 # </pluginItem>
michael@0 700 # </pluginItems>
michael@0 701 # </blocklist>
michael@0 702 */
michael@0 703
michael@0 704 _loadBlocklistFromFile: function Blocklist_loadBlocklistFromFile(file) {
michael@0 705 if (!gBlocklistEnabled) {
michael@0 706 LOG("Blocklist::_loadBlocklistFromFile: blocklist is disabled");
michael@0 707 return;
michael@0 708 }
michael@0 709
michael@0 710 if (!file.exists()) {
michael@0 711 LOG("Blocklist::_loadBlocklistFromFile: XML File does not exist " + file.path);
michael@0 712 return;
michael@0 713 }
michael@0 714
michael@0 715 let text = "";
michael@0 716 let fstream = null;
michael@0 717 let cstream = null;
michael@0 718
michael@0 719 try {
michael@0 720 fstream = Components.classes["@mozilla.org/network/file-input-stream;1"]
michael@0 721 .createInstance(Components.interfaces.nsIFileInputStream);
michael@0 722 cstream = Components.classes["@mozilla.org/intl/converter-input-stream;1"]
michael@0 723 .createInstance(Components.interfaces.nsIConverterInputStream);
michael@0 724
michael@0 725 fstream.init(file, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0);
michael@0 726 cstream.init(fstream, "UTF-8", 0, 0);
michael@0 727
michael@0 728 let (str = {}) {
michael@0 729 let read = 0;
michael@0 730
michael@0 731 do {
michael@0 732 read = cstream.readString(0xffffffff, str); // read as much as we can and put it in str.value
michael@0 733 text += str.value;
michael@0 734 } while (read != 0);
michael@0 735 }
michael@0 736 } catch (e) {
michael@0 737 LOG("Blocklist::_loadBlocklistFromFile: Failed to load XML file " + e);
michael@0 738 } finally {
michael@0 739 cstream.close();
michael@0 740 fstream.close();
michael@0 741 }
michael@0 742
michael@0 743 text && this._loadBlocklistFromString(text);
michael@0 744 },
michael@0 745
michael@0 746 _loadBlocklistFromString : function Blocklist_loadBlocklistFromString(text) {
michael@0 747 try {
michael@0 748 var parser = Cc["@mozilla.org/xmlextras/domparser;1"].
michael@0 749 createInstance(Ci.nsIDOMParser);
michael@0 750 var doc = parser.parseFromString(text, "text/xml");
michael@0 751 if (doc.documentElement.namespaceURI != XMLURI_BLOCKLIST) {
michael@0 752 LOG("Blocklist::_loadBlocklistFromFile: aborting due to incorrect " +
michael@0 753 "XML Namespace.\r\nExpected: " + XMLURI_BLOCKLIST + "\r\n" +
michael@0 754 "Received: " + doc.documentElement.namespaceURI);
michael@0 755 return;
michael@0 756 }
michael@0 757
michael@0 758 var childNodes = doc.documentElement.childNodes;
michael@0 759 for (let element of childNodes) {
michael@0 760 if (!(element instanceof Ci.nsIDOMElement))
michael@0 761 continue;
michael@0 762 switch (element.localName) {
michael@0 763 case "emItems":
michael@0 764 this._addonEntries = this._processItemNodes(element.childNodes, "em",
michael@0 765 this._handleEmItemNode);
michael@0 766 break;
michael@0 767 case "pluginItems":
michael@0 768 this._pluginEntries = this._processItemNodes(element.childNodes, "plugin",
michael@0 769 this._handlePluginItemNode);
michael@0 770 break;
michael@0 771 default:
michael@0 772 Services.obs.notifyObservers(element,
michael@0 773 "blocklist-data-" + element.localName,
michael@0 774 null);
michael@0 775 }
michael@0 776 }
michael@0 777 }
michael@0 778 catch (e) {
michael@0 779 LOG("Blocklist::_loadBlocklistFromFile: Error constructing blocklist " + e);
michael@0 780 return;
michael@0 781 }
michael@0 782 },
michael@0 783
michael@0 784 _processItemNodes: function Blocklist_processItemNodes(itemNodes, prefix, handler) {
michael@0 785 var result = [];
michael@0 786 var itemName = prefix + "Item";
michael@0 787 for (var i = 0; i < itemNodes.length; ++i) {
michael@0 788 var blocklistElement = itemNodes.item(i);
michael@0 789 if (!(blocklistElement instanceof Ci.nsIDOMElement) ||
michael@0 790 blocklistElement.localName != itemName)
michael@0 791 continue;
michael@0 792
michael@0 793 handler(blocklistElement, result);
michael@0 794 }
michael@0 795 return result;
michael@0 796 },
michael@0 797
michael@0 798 _handleEmItemNode: function Blocklist_handleEmItemNode(blocklistElement, result) {
michael@0 799 if (!matchesOSABI(blocklistElement))
michael@0 800 return;
michael@0 801
michael@0 802 let blockEntry = {
michael@0 803 versions: [],
michael@0 804 prefs: [],
michael@0 805 blockID: null,
michael@0 806 attributes: new Map()
michael@0 807 // Atleast one of EXTENSION_BLOCK_FILTERS must get added to attributes
michael@0 808 };
michael@0 809
michael@0 810 // Any filter starting with '/' is interpreted as a regex. So if an attribute
michael@0 811 // starts with a '/' it must be checked via a regex.
michael@0 812 function regExpCheck(attr) {
michael@0 813 return attr.startsWith("/") ? parseRegExp(attr) : attr;
michael@0 814 }
michael@0 815
michael@0 816 for (let filter of EXTENSION_BLOCK_FILTERS) {
michael@0 817 let attr = blocklistElement.getAttribute(filter);
michael@0 818 if (attr)
michael@0 819 blockEntry.attributes.set(filter, regExpCheck(attr));
michael@0 820 }
michael@0 821
michael@0 822 var childNodes = blocklistElement.childNodes;
michael@0 823
michael@0 824 for (let x = 0; x < childNodes.length; x++) {
michael@0 825 var childElement = childNodes.item(x);
michael@0 826 if (!(childElement instanceof Ci.nsIDOMElement))
michael@0 827 continue;
michael@0 828 if (childElement.localName === "prefs") {
michael@0 829 let prefElements = childElement.childNodes;
michael@0 830 for (let i = 0; i < prefElements.length; i++) {
michael@0 831 let prefElement = prefElements.item(i);
michael@0 832 if (!(prefElement instanceof Ci.nsIDOMElement) ||
michael@0 833 prefElement.localName !== "pref")
michael@0 834 continue;
michael@0 835 blockEntry.prefs.push(prefElement.textContent);
michael@0 836 }
michael@0 837 }
michael@0 838 else if (childElement.localName === "versionRange")
michael@0 839 blockEntry.versions.push(new BlocklistItemData(childElement));
michael@0 840 }
michael@0 841 // if only the extension ID is specified block all versions of the
michael@0 842 // extension for the current application.
michael@0 843 if (blockEntry.versions.length == 0)
michael@0 844 blockEntry.versions.push(new BlocklistItemData(null));
michael@0 845
michael@0 846 blockEntry.blockID = blocklistElement.getAttribute("blockID");
michael@0 847
michael@0 848 result.push(blockEntry);
michael@0 849 },
michael@0 850
michael@0 851 _handlePluginItemNode: function Blocklist_handlePluginItemNode(blocklistElement, result) {
michael@0 852 if (!matchesOSABI(blocklistElement))
michael@0 853 return;
michael@0 854
michael@0 855 var matchNodes = blocklistElement.childNodes;
michael@0 856 var blockEntry = {
michael@0 857 matches: {},
michael@0 858 versions: [],
michael@0 859 blockID: null,
michael@0 860 };
michael@0 861 var hasMatch = false;
michael@0 862 for (var x = 0; x < matchNodes.length; ++x) {
michael@0 863 var matchElement = matchNodes.item(x);
michael@0 864 if (!(matchElement instanceof Ci.nsIDOMElement))
michael@0 865 continue;
michael@0 866 if (matchElement.localName == "match") {
michael@0 867 var name = matchElement.getAttribute("name");
michael@0 868 var exp = matchElement.getAttribute("exp");
michael@0 869 try {
michael@0 870 blockEntry.matches[name] = new RegExp(exp, "m");
michael@0 871 hasMatch = true;
michael@0 872 } catch (e) {
michael@0 873 // Ignore invalid regular expressions
michael@0 874 }
michael@0 875 }
michael@0 876 if (matchElement.localName == "versionRange")
michael@0 877 blockEntry.versions.push(new BlocklistItemData(matchElement));
michael@0 878 }
michael@0 879 // Plugin entries require *something* to match to an actual plugin
michael@0 880 if (!hasMatch)
michael@0 881 return;
michael@0 882 // Add a default versionRange if there wasn't one specified
michael@0 883 if (blockEntry.versions.length == 0)
michael@0 884 blockEntry.versions.push(new BlocklistItemData(null));
michael@0 885
michael@0 886 blockEntry.blockID = blocklistElement.getAttribute("blockID");
michael@0 887
michael@0 888 result.push(blockEntry);
michael@0 889 },
michael@0 890
michael@0 891 /* See nsIBlocklistService */
michael@0 892 getPluginBlocklistState: function Blocklist_getPluginBlocklistState(plugin,
michael@0 893 appVersion, toolkitVersion) {
michael@0 894 if (!this._pluginEntries)
michael@0 895 this._loadBlocklist();
michael@0 896 return this._getPluginBlocklistState(plugin, this._pluginEntries,
michael@0 897 appVersion, toolkitVersion);
michael@0 898 },
michael@0 899
michael@0 900 /**
michael@0 901 * Private version of getPluginBlocklistState that allows the caller to pass in
michael@0 902 * the plugin blocklist entries.
michael@0 903 *
michael@0 904 * @param plugin
michael@0 905 * The nsIPluginTag to get the blocklist state for.
michael@0 906 * @param pluginEntries
michael@0 907 * The plugin blocklist entries to compare against.
michael@0 908 * @param appVersion
michael@0 909 * The application version to compare to, will use the current
michael@0 910 * version if null.
michael@0 911 * @param toolkitVersion
michael@0 912 * The toolkit version to compare to, will use the current version if
michael@0 913 * null.
michael@0 914 * @returns The blocklist state for the item, one of the STATE constants as
michael@0 915 * defined in nsIBlocklistService.
michael@0 916 */
michael@0 917 _getPluginBlocklistState: function Blocklist_getPluginBlocklistState(plugin,
michael@0 918 pluginEntries, appVersion, toolkitVersion) {
michael@0 919 if (!gBlocklistEnabled)
michael@0 920 return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
michael@0 921
michael@0 922 if (!appVersion)
michael@0 923 appVersion = gApp.version;
michael@0 924 if (!toolkitVersion)
michael@0 925 toolkitVersion = gApp.platformVersion;
michael@0 926
michael@0 927 for each (var blockEntry in pluginEntries) {
michael@0 928 var matchFailed = false;
michael@0 929 for (var name in blockEntry.matches) {
michael@0 930 if (!(name in plugin) ||
michael@0 931 typeof(plugin[name]) != "string" ||
michael@0 932 !blockEntry.matches[name].test(plugin[name])) {
michael@0 933 matchFailed = true;
michael@0 934 break;
michael@0 935 }
michael@0 936 }
michael@0 937
michael@0 938 if (matchFailed)
michael@0 939 continue;
michael@0 940
michael@0 941 for (let blockEntryVersion of blockEntry.versions) {
michael@0 942 if (blockEntryVersion.includesItem(plugin.version, appVersion,
michael@0 943 toolkitVersion)) {
michael@0 944 if (blockEntryVersion.severity >= gBlocklistLevel)
michael@0 945 return Ci.nsIBlocklistService.STATE_BLOCKED;
michael@0 946 if (blockEntryVersion.severity == SEVERITY_OUTDATED) {
michael@0 947 let vulnerabilityStatus = blockEntryVersion.vulnerabilityStatus;
michael@0 948 if (vulnerabilityStatus == VULNERABILITYSTATUS_UPDATE_AVAILABLE)
michael@0 949 return Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE;
michael@0 950 if (vulnerabilityStatus == VULNERABILITYSTATUS_NO_UPDATE)
michael@0 951 return Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE;
michael@0 952 return Ci.nsIBlocklistService.STATE_OUTDATED;
michael@0 953 }
michael@0 954 return Ci.nsIBlocklistService.STATE_SOFTBLOCKED;
michael@0 955 }
michael@0 956 }
michael@0 957 }
michael@0 958
michael@0 959 return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
michael@0 960 },
michael@0 961
michael@0 962 /* See nsIBlocklistService */
michael@0 963 getPluginBlocklistURL: function Blocklist_getPluginBlocklistURL(plugin) {
michael@0 964 if (!gBlocklistEnabled)
michael@0 965 return "";
michael@0 966
michael@0 967 if (!this._pluginEntries)
michael@0 968 this._loadBlocklist();
michael@0 969
michael@0 970 for each (let blockEntry in this._pluginEntries) {
michael@0 971 let matchFailed = false;
michael@0 972 for (let name in blockEntry.matches) {
michael@0 973 if (!(name in plugin) ||
michael@0 974 typeof(plugin[name]) != "string" ||
michael@0 975 !blockEntry.matches[name].test(plugin[name])) {
michael@0 976 matchFailed = true;
michael@0 977 break;
michael@0 978 }
michael@0 979 }
michael@0 980
michael@0 981 if (!matchFailed) {
michael@0 982 if(!blockEntry.blockID)
michael@0 983 return null;
michael@0 984 else
michael@0 985 return this._createBlocklistURL(blockEntry.blockID);
michael@0 986 }
michael@0 987 }
michael@0 988 },
michael@0 989
michael@0 990 _blocklistUpdated: function Blocklist_blocklistUpdated(oldAddonEntries, oldPluginEntries) {
michael@0 991 var addonList = [];
michael@0 992
michael@0 993 // A helper function that reverts the prefs passed to default values.
michael@0 994 function resetPrefs(prefs) {
michael@0 995 for (let pref of prefs)
michael@0 996 gPref.clearUserPref(pref);
michael@0 997 }
michael@0 998 var self = this;
michael@0 999 const types = ["extension", "theme", "locale", "dictionary", "service"];
michael@0 1000 AddonManager.getAddonsByTypes(types, function blocklistUpdated_getAddonsByTypes(addons) {
michael@0 1001
michael@0 1002 for (let addon of addons) {
michael@0 1003 let oldState = Ci.nsIBlocklistService.STATE_NOTBLOCKED;
michael@0 1004 if (oldAddonEntries)
michael@0 1005 oldState = self._getAddonBlocklistState(addon, oldAddonEntries);
michael@0 1006 let state = self.getAddonBlocklistState(addon);
michael@0 1007
michael@0 1008 LOG("Blocklist state for " + addon.id + " changed from " +
michael@0 1009 oldState + " to " + state);
michael@0 1010
michael@0 1011 // We don't want to re-warn about add-ons
michael@0 1012 if (state == oldState)
michael@0 1013 continue;
michael@0 1014
michael@0 1015 if (state === Ci.nsIBlocklistService.STATE_BLOCKED) {
michael@0 1016 // It's a hard block. We must reset certain preferences.
michael@0 1017 let prefs = self._getAddonPrefs(addon);
michael@0 1018 resetPrefs(prefs);
michael@0 1019 }
michael@0 1020
michael@0 1021 // Ensure that softDisabled is false if the add-on is not soft blocked
michael@0 1022 if (state != Ci.nsIBlocklistService.STATE_SOFTBLOCKED)
michael@0 1023 addon.softDisabled = false;
michael@0 1024
michael@0 1025 // Don't warn about add-ons becoming unblocked.
michael@0 1026 if (state == Ci.nsIBlocklistService.STATE_NOT_BLOCKED)
michael@0 1027 continue;
michael@0 1028
michael@0 1029 // If an add-on has dropped from hard to soft blocked just mark it as
michael@0 1030 // soft disabled and don't warn about it.
michael@0 1031 if (state == Ci.nsIBlocklistService.STATE_SOFTBLOCKED &&
michael@0 1032 oldState == Ci.nsIBlocklistService.STATE_BLOCKED) {
michael@0 1033 addon.softDisabled = true;
michael@0 1034 continue;
michael@0 1035 }
michael@0 1036
michael@0 1037 // If the add-on is already disabled for some reason then don't warn
michael@0 1038 // about it
michael@0 1039 if (!addon.isActive)
michael@0 1040 continue;
michael@0 1041
michael@0 1042 addonList.push({
michael@0 1043 name: addon.name,
michael@0 1044 version: addon.version,
michael@0 1045 icon: addon.iconURL,
michael@0 1046 disable: false,
michael@0 1047 blocked: state == Ci.nsIBlocklistService.STATE_BLOCKED,
michael@0 1048 item: addon,
michael@0 1049 url: self.getAddonBlocklistURL(addon),
michael@0 1050 });
michael@0 1051 }
michael@0 1052
michael@0 1053 AddonManagerPrivate.updateAddonAppDisabledStates();
michael@0 1054
michael@0 1055 var phs = Cc["@mozilla.org/plugin/host;1"].
michael@0 1056 getService(Ci.nsIPluginHost);
michael@0 1057 var plugins = phs.getPluginTags();
michael@0 1058
michael@0 1059 for (let plugin of plugins) {
michael@0 1060 let oldState = -1;
michael@0 1061 if (oldPluginEntries)
michael@0 1062 oldState = self._getPluginBlocklistState(plugin, oldPluginEntries);
michael@0 1063 let state = self.getPluginBlocklistState(plugin);
michael@0 1064 LOG("Blocklist state for " + plugin.name + " changed from " +
michael@0 1065 oldState + " to " + state);
michael@0 1066 // We don't want to re-warn about items
michael@0 1067 if (state == oldState)
michael@0 1068 continue;
michael@0 1069
michael@0 1070 if (oldState == Ci.nsIBlocklistService.STATE_BLOCKED) {
michael@0 1071 if (state == Ci.nsIBlocklistService.STATE_SOFTBLOCKED)
michael@0 1072 plugin.enabledState = Ci.nsIPluginTag.STATE_DISABLED;
michael@0 1073 }
michael@0 1074 else if (!plugin.disabled && state != Ci.nsIBlocklistService.STATE_NOT_BLOCKED) {
michael@0 1075 if (state == Ci.nsIBlocklistService.STATE_OUTDATED) {
michael@0 1076 gPref.setBoolPref(PREF_PLUGINS_NOTIFYUSER, true);
michael@0 1077 }
michael@0 1078 else if (state != Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE &&
michael@0 1079 state != Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE) {
michael@0 1080 addonList.push({
michael@0 1081 name: plugin.name,
michael@0 1082 version: plugin.version,
michael@0 1083 icon: "chrome://mozapps/skin/plugins/pluginGeneric.png",
michael@0 1084 disable: false,
michael@0 1085 blocked: state == Ci.nsIBlocklistService.STATE_BLOCKED,
michael@0 1086 item: plugin,
michael@0 1087 url: self.getPluginBlocklistURL(plugin),
michael@0 1088 });
michael@0 1089 }
michael@0 1090 }
michael@0 1091 }
michael@0 1092
michael@0 1093 if (addonList.length == 0) {
michael@0 1094 Services.obs.notifyObservers(self, "blocklist-updated", "");
michael@0 1095 return;
michael@0 1096 }
michael@0 1097
michael@0 1098 if ("@mozilla.org/addons/blocklist-prompt;1" in Cc) {
michael@0 1099 try {
michael@0 1100 let blockedPrompter = Cc["@mozilla.org/addons/blocklist-prompt;1"]
michael@0 1101 .getService(Ci.nsIBlocklistPrompt);
michael@0 1102 blockedPrompter.prompt(addonList);
michael@0 1103 } catch (e) {
michael@0 1104 LOG(e);
michael@0 1105 }
michael@0 1106 Services.obs.notifyObservers(self, "blocklist-updated", "");
michael@0 1107 return;
michael@0 1108 }
michael@0 1109
michael@0 1110 var args = {
michael@0 1111 restart: false,
michael@0 1112 list: addonList
michael@0 1113 };
michael@0 1114 // This lets the dialog get the raw js object
michael@0 1115 args.wrappedJSObject = args;
michael@0 1116
michael@0 1117 /*
michael@0 1118 Some tests run without UI, so the async code listens to a message
michael@0 1119 that can be sent programatically
michael@0 1120 */
michael@0 1121 let applyBlocklistChanges = function blocklistUpdated_applyBlocklistChanges() {
michael@0 1122 for (let addon of addonList) {
michael@0 1123 if (!addon.disable)
michael@0 1124 continue;
michael@0 1125
michael@0 1126 if (addon.item instanceof Ci.nsIPluginTag)
michael@0 1127 addon.item.enabledState = Ci.nsIPluginTag.STATE_DISABLED;
michael@0 1128 else {
michael@0 1129 // This add-on is softblocked.
michael@0 1130 addon.item.softDisabled = true;
michael@0 1131 // We must revert certain prefs.
michael@0 1132 let prefs = self._getAddonPrefs(addon.item);
michael@0 1133 resetPrefs(prefs);
michael@0 1134 }
michael@0 1135 }
michael@0 1136
michael@0 1137 if (args.restart)
michael@0 1138 restartApp();
michael@0 1139
michael@0 1140 Services.obs.notifyObservers(self, "blocklist-updated", "");
michael@0 1141 Services.obs.removeObserver(applyBlocklistChanges, "addon-blocklist-closed");
michael@0 1142 }
michael@0 1143
michael@0 1144 Services.obs.addObserver(applyBlocklistChanges, "addon-blocklist-closed", false);
michael@0 1145
michael@0 1146 if (getPref("getBoolPref", PREF_BLOCKLIST_SUPPRESSUI, false)) {
michael@0 1147 applyBlocklistChanges();
michael@0 1148 return;
michael@0 1149 }
michael@0 1150
michael@0 1151 function blocklistUnloadHandler(event) {
michael@0 1152 if (event.target.location == URI_BLOCKLIST_DIALOG) {
michael@0 1153 applyBlocklistChanges();
michael@0 1154 blocklistWindow.removeEventListener("unload", blocklistUnloadHandler);
michael@0 1155 }
michael@0 1156 }
michael@0 1157
michael@0 1158 let blocklistWindow = Services.ww.openWindow(null, URI_BLOCKLIST_DIALOG, "",
michael@0 1159 "chrome,centerscreen,dialog,titlebar", args);
michael@0 1160 if (blocklistWindow)
michael@0 1161 blocklistWindow.addEventListener("unload", blocklistUnloadHandler, false);
michael@0 1162 });
michael@0 1163 },
michael@0 1164
michael@0 1165 classID: Components.ID("{66354bc9-7ed1-4692-ae1d-8da97d6b205e}"),
michael@0 1166 QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
michael@0 1167 Ci.nsIBlocklistService,
michael@0 1168 Ci.nsITimerCallback]),
michael@0 1169 };
michael@0 1170
michael@0 1171 /**
michael@0 1172 * Helper for constructing a blocklist.
michael@0 1173 */
michael@0 1174 function BlocklistItemData(versionRangeElement) {
michael@0 1175 var versionRange = this.getBlocklistVersionRange(versionRangeElement);
michael@0 1176 this.minVersion = versionRange.minVersion;
michael@0 1177 this.maxVersion = versionRange.maxVersion;
michael@0 1178 if (versionRangeElement && versionRangeElement.hasAttribute("severity"))
michael@0 1179 this.severity = versionRangeElement.getAttribute("severity");
michael@0 1180 else
michael@0 1181 this.severity = DEFAULT_SEVERITY;
michael@0 1182 if (versionRangeElement && versionRangeElement.hasAttribute("vulnerabilitystatus")) {
michael@0 1183 this.vulnerabilityStatus = versionRangeElement.getAttribute("vulnerabilitystatus");
michael@0 1184 } else {
michael@0 1185 this.vulnerabilityStatus = VULNERABILITYSTATUS_NONE;
michael@0 1186 }
michael@0 1187 this.targetApps = { };
michael@0 1188 var found = false;
michael@0 1189
michael@0 1190 if (versionRangeElement) {
michael@0 1191 for (var i = 0; i < versionRangeElement.childNodes.length; ++i) {
michael@0 1192 var targetAppElement = versionRangeElement.childNodes.item(i);
michael@0 1193 if (!(targetAppElement instanceof Ci.nsIDOMElement) ||
michael@0 1194 targetAppElement.localName != "targetApplication")
michael@0 1195 continue;
michael@0 1196 found = true;
michael@0 1197 // default to the current application if id is not provided.
michael@0 1198 var appID = targetAppElement.hasAttribute("id") ? targetAppElement.getAttribute("id") : gApp.ID;
michael@0 1199 this.targetApps[appID] = this.getBlocklistAppVersions(targetAppElement);
michael@0 1200 }
michael@0 1201 }
michael@0 1202 // Default to all versions of the current application when no targetApplication
michael@0 1203 // elements were found
michael@0 1204 if (!found)
michael@0 1205 this.targetApps[gApp.ID] = this.getBlocklistAppVersions(null);
michael@0 1206 }
michael@0 1207
michael@0 1208 BlocklistItemData.prototype = {
michael@0 1209 /**
michael@0 1210 * Tests if a version of an item is included in the version range and target
michael@0 1211 * application information represented by this BlocklistItemData using the
michael@0 1212 * provided application and toolkit versions.
michael@0 1213 * @param version
michael@0 1214 * The version of the item being tested.
michael@0 1215 * @param appVersion
michael@0 1216 * The application version to test with.
michael@0 1217 * @param toolkitVersion
michael@0 1218 * The toolkit version to test with.
michael@0 1219 * @returns True if the version range covers the item version and application
michael@0 1220 * or toolkit version.
michael@0 1221 */
michael@0 1222 includesItem: function BlocklistItemData_includesItem(version, appVersion, toolkitVersion) {
michael@0 1223 // Some platforms have no version for plugins, these don't match if there
michael@0 1224 // was a min/maxVersion provided
michael@0 1225 if (!version && (this.minVersion || this.maxVersion))
michael@0 1226 return false;
michael@0 1227
michael@0 1228 // Check if the item version matches
michael@0 1229 if (!this.matchesRange(version, this.minVersion, this.maxVersion))
michael@0 1230 return false;
michael@0 1231
michael@0 1232 // Check if the application version matches
michael@0 1233 if (this.matchesTargetRange(gApp.ID, appVersion))
michael@0 1234 return true;
michael@0 1235
michael@0 1236 // Check if the toolkit version matches
michael@0 1237 return this.matchesTargetRange(TOOLKIT_ID, toolkitVersion);
michael@0 1238 },
michael@0 1239
michael@0 1240 /**
michael@0 1241 * Checks if a version is higher than or equal to the minVersion (if provided)
michael@0 1242 * and lower than or equal to the maxVersion (if provided).
michael@0 1243 * @param version
michael@0 1244 * The version to test.
michael@0 1245 * @param minVersion
michael@0 1246 * The minimum version. If null it is assumed that version is always
michael@0 1247 * larger.
michael@0 1248 * @param maxVersion
michael@0 1249 * The maximum version. If null it is assumed that version is always
michael@0 1250 * smaller.
michael@0 1251 */
michael@0 1252 matchesRange: function BlocklistItemData_matchesRange(version, minVersion, maxVersion) {
michael@0 1253 if (minVersion && gVersionChecker.compare(version, minVersion) < 0)
michael@0 1254 return false;
michael@0 1255 if (maxVersion && gVersionChecker.compare(version, maxVersion) > 0)
michael@0 1256 return false;
michael@0 1257 return true;
michael@0 1258 },
michael@0 1259
michael@0 1260 /**
michael@0 1261 * Tests if there is a matching range for the given target application id and
michael@0 1262 * version.
michael@0 1263 * @param appID
michael@0 1264 * The application ID to test for, may be for an application or toolkit
michael@0 1265 * @param appVersion
michael@0 1266 * The version of the application to test for.
michael@0 1267 * @returns True if this version range covers the application version given.
michael@0 1268 */
michael@0 1269 matchesTargetRange: function BlocklistItemData_matchesTargetRange(appID, appVersion) {
michael@0 1270 var blTargetApp = this.targetApps[appID];
michael@0 1271 if (!blTargetApp)
michael@0 1272 return false;
michael@0 1273
michael@0 1274 for (let app of blTargetApp) {
michael@0 1275 if (this.matchesRange(appVersion, app.minVersion, app.maxVersion))
michael@0 1276 return true;
michael@0 1277 }
michael@0 1278
michael@0 1279 return false;
michael@0 1280 },
michael@0 1281
michael@0 1282 /**
michael@0 1283 * Retrieves a version range (e.g. minVersion and maxVersion) for a
michael@0 1284 * blocklist item's targetApplication element.
michael@0 1285 * @param targetAppElement
michael@0 1286 * A targetApplication blocklist element.
michael@0 1287 * @returns An array of JS objects with the following properties:
michael@0 1288 * "minVersion" The minimum version in a version range (default = null).
michael@0 1289 * "maxVersion" The maximum version in a version range (default = null).
michael@0 1290 */
michael@0 1291 getBlocklistAppVersions: function BlocklistItemData_getBlocklistAppVersions(targetAppElement) {
michael@0 1292 var appVersions = [ ];
michael@0 1293
michael@0 1294 if (targetAppElement) {
michael@0 1295 for (var i = 0; i < targetAppElement.childNodes.length; ++i) {
michael@0 1296 var versionRangeElement = targetAppElement.childNodes.item(i);
michael@0 1297 if (!(versionRangeElement instanceof Ci.nsIDOMElement) ||
michael@0 1298 versionRangeElement.localName != "versionRange")
michael@0 1299 continue;
michael@0 1300 appVersions.push(this.getBlocklistVersionRange(versionRangeElement));
michael@0 1301 }
michael@0 1302 }
michael@0 1303 // return minVersion = null and maxVersion = null if no specific versionRange
michael@0 1304 // elements were found
michael@0 1305 if (appVersions.length == 0)
michael@0 1306 appVersions.push(this.getBlocklistVersionRange(null));
michael@0 1307 return appVersions;
michael@0 1308 },
michael@0 1309
michael@0 1310 /**
michael@0 1311 * Retrieves a version range (e.g. minVersion and maxVersion) for a blocklist
michael@0 1312 * versionRange element.
michael@0 1313 * @param versionRangeElement
michael@0 1314 * The versionRange blocklist element.
michael@0 1315 * @returns A JS object with the following properties:
michael@0 1316 * "minVersion" The minimum version in a version range (default = null).
michael@0 1317 * "maxVersion" The maximum version in a version range (default = null).
michael@0 1318 */
michael@0 1319 getBlocklistVersionRange: function BlocklistItemData_getBlocklistVersionRange(versionRangeElement) {
michael@0 1320 var minVersion = null;
michael@0 1321 var maxVersion = null;
michael@0 1322 if (!versionRangeElement)
michael@0 1323 return { minVersion: minVersion, maxVersion: maxVersion };
michael@0 1324
michael@0 1325 if (versionRangeElement.hasAttribute("minVersion"))
michael@0 1326 minVersion = versionRangeElement.getAttribute("minVersion");
michael@0 1327 if (versionRangeElement.hasAttribute("maxVersion"))
michael@0 1328 maxVersion = versionRangeElement.getAttribute("maxVersion");
michael@0 1329
michael@0 1330 return { minVersion: minVersion, maxVersion: maxVersion };
michael@0 1331 }
michael@0 1332 };
michael@0 1333
michael@0 1334 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([Blocklist]);

mercurial