toolkit/mozapps/extensions/nsBlocklistService.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/toolkit/mozapps/extensions/nsBlocklistService.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,1334 @@
     1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     1.5 +
     1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.9 +
    1.10 +"use strict";
    1.11 +
    1.12 +const Cc = Components.classes;
    1.13 +const Ci = Components.interfaces;
    1.14 +const Cr = Components.results;
    1.15 +
    1.16 +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
    1.17 +Components.utils.import("resource://gre/modules/AddonManager.jsm");
    1.18 +Components.utils.import("resource://gre/modules/Services.jsm");
    1.19 +
    1.20 +XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
    1.21 +                                  "resource://gre/modules/FileUtils.jsm");
    1.22 +XPCOMUtils.defineLazyModuleGetter(this, "UpdateChannel",
    1.23 +                                  "resource://gre/modules/UpdateChannel.jsm");
    1.24 +XPCOMUtils.defineLazyModuleGetter(this, "OS",
    1.25 +                                  "resource://gre/modules/osfile.jsm");
    1.26 +XPCOMUtils.defineLazyModuleGetter(this, "Task",
    1.27 +                                  "resource://gre/modules/Task.jsm");
    1.28 +
    1.29 +const TOOLKIT_ID                      = "toolkit@mozilla.org"
    1.30 +const KEY_PROFILEDIR                  = "ProfD";
    1.31 +const KEY_APPDIR                      = "XCurProcD";
    1.32 +const FILE_BLOCKLIST                  = "blocklist.xml";
    1.33 +const PREF_BLOCKLIST_LASTUPDATETIME   = "app.update.lastUpdateTime.blocklist-background-update-timer";
    1.34 +const PREF_BLOCKLIST_URL              = "extensions.blocklist.url";
    1.35 +const PREF_BLOCKLIST_ITEM_URL         = "extensions.blocklist.itemURL";
    1.36 +const PREF_BLOCKLIST_ENABLED          = "extensions.blocklist.enabled";
    1.37 +const PREF_BLOCKLIST_INTERVAL         = "extensions.blocklist.interval";
    1.38 +const PREF_BLOCKLIST_LEVEL            = "extensions.blocklist.level";
    1.39 +const PREF_BLOCKLIST_PINGCOUNTTOTAL   = "extensions.blocklist.pingCountTotal";
    1.40 +const PREF_BLOCKLIST_PINGCOUNTVERSION = "extensions.blocklist.pingCountVersion";
    1.41 +const PREF_BLOCKLIST_SUPPRESSUI       = "extensions.blocklist.suppressUI";
    1.42 +const PREF_PLUGINS_NOTIFYUSER         = "plugins.update.notifyUser";
    1.43 +const PREF_GENERAL_USERAGENT_LOCALE   = "general.useragent.locale";
    1.44 +const PREF_APP_DISTRIBUTION           = "distribution.id";
    1.45 +const PREF_APP_DISTRIBUTION_VERSION   = "distribution.version";
    1.46 +const PREF_EM_LOGGING_ENABLED         = "extensions.logging.enabled";
    1.47 +const XMLURI_BLOCKLIST                = "http://www.mozilla.org/2006/addons-blocklist";
    1.48 +const XMLURI_PARSE_ERROR              = "http://www.mozilla.org/newlayout/xml/parsererror.xml"
    1.49 +const UNKNOWN_XPCOM_ABI               = "unknownABI";
    1.50 +const URI_BLOCKLIST_DIALOG            = "chrome://mozapps/content/extensions/blocklist.xul"
    1.51 +const DEFAULT_SEVERITY                = 3;
    1.52 +const DEFAULT_LEVEL                   = 2;
    1.53 +const MAX_BLOCK_LEVEL                 = 3;
    1.54 +const SEVERITY_OUTDATED               = 0;
    1.55 +const VULNERABILITYSTATUS_NONE             = 0;
    1.56 +const VULNERABILITYSTATUS_UPDATE_AVAILABLE = 1;
    1.57 +const VULNERABILITYSTATUS_NO_UPDATE        = 2;
    1.58 +
    1.59 +const EXTENSION_BLOCK_FILTERS = ["id", "name", "creator", "homepageURL", "updateURL"];
    1.60 +
    1.61 +var gLoggingEnabled = null;
    1.62 +var gBlocklistEnabled = true;
    1.63 +var gBlocklistLevel = DEFAULT_LEVEL;
    1.64 +
    1.65 +XPCOMUtils.defineLazyServiceGetter(this, "gConsole",
    1.66 +                                   "@mozilla.org/consoleservice;1",
    1.67 +                                   "nsIConsoleService");
    1.68 +
    1.69 +XPCOMUtils.defineLazyServiceGetter(this, "gVersionChecker",
    1.70 +                                   "@mozilla.org/xpcom/version-comparator;1",
    1.71 +                                   "nsIVersionComparator");
    1.72 +
    1.73 +XPCOMUtils.defineLazyGetter(this, "gPref", function bls_gPref() {
    1.74 +  return Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefService).
    1.75 +         QueryInterface(Ci.nsIPrefBranch);
    1.76 +});
    1.77 +
    1.78 +XPCOMUtils.defineLazyGetter(this, "gApp", function bls_gApp() {
    1.79 +  return Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo).
    1.80 +         QueryInterface(Ci.nsIXULRuntime);
    1.81 +});
    1.82 +
    1.83 +XPCOMUtils.defineLazyGetter(this, "gABI", function bls_gABI() {
    1.84 +  let abi = null;
    1.85 +  try {
    1.86 +    abi = gApp.XPCOMABI;
    1.87 +  }
    1.88 +  catch (e) {
    1.89 +    LOG("BlockList Global gABI: XPCOM ABI unknown.");
    1.90 +  }
    1.91 +#ifdef XP_MACOSX
    1.92 +  // Mac universal build should report a different ABI than either macppc
    1.93 +  // or mactel.
    1.94 +  let macutils = Cc["@mozilla.org/xpcom/mac-utils;1"].
    1.95 +                 getService(Ci.nsIMacUtils);
    1.96 +
    1.97 +  if (macutils.isUniversalBinary)
    1.98 +    abi += "-u-" + macutils.architecturesInBinary;
    1.99 +#endif
   1.100 +  return abi;
   1.101 +});
   1.102 +
   1.103 +XPCOMUtils.defineLazyGetter(this, "gOSVersion", function bls_gOSVersion() {
   1.104 +  let osVersion;
   1.105 +  let sysInfo = Cc["@mozilla.org/system-info;1"].
   1.106 +                getService(Ci.nsIPropertyBag2);
   1.107 +  try {
   1.108 +    osVersion = sysInfo.getProperty("name") + " " + sysInfo.getProperty("version");
   1.109 +  }
   1.110 +  catch (e) {
   1.111 +    LOG("BlockList Global gOSVersion: OS Version unknown.");
   1.112 +  }
   1.113 +
   1.114 +  if (osVersion) {
   1.115 +    try {
   1.116 +      osVersion += " (" + sysInfo.getProperty("secondaryLibrary") + ")";
   1.117 +    }
   1.118 +    catch (e) {
   1.119 +      // Not all platforms have a secondary widget library, so an error is nothing to worry about.
   1.120 +    }
   1.121 +    osVersion = encodeURIComponent(osVersion);
   1.122 +  }
   1.123 +  return osVersion;
   1.124 +});
   1.125 +
   1.126 +// shared code for suppressing bad cert dialogs
   1.127 +XPCOMUtils.defineLazyGetter(this, "gCertUtils", function bls_gCertUtils() {
   1.128 +  let temp = { };
   1.129 +  Components.utils.import("resource://gre/modules/CertUtils.jsm", temp);
   1.130 +  return temp;
   1.131 +});
   1.132 +
   1.133 +function getObserverService() {
   1.134 +  return Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
   1.135 +}
   1.136 +
   1.137 +/**
   1.138 + * Logs a string to the error console.
   1.139 + * @param   string
   1.140 + *          The string to write to the error console..
   1.141 + */
   1.142 +function LOG(string) {
   1.143 +  if (gLoggingEnabled) {
   1.144 +    dump("*** " + string + "\n");
   1.145 +    gConsole.logStringMessage(string);
   1.146 +  }
   1.147 +}
   1.148 +
   1.149 +/**
   1.150 + * Gets a preference value, handling the case where there is no default.
   1.151 + * @param   func
   1.152 + *          The name of the preference function to call, on nsIPrefBranch
   1.153 + * @param   preference
   1.154 + *          The name of the preference
   1.155 + * @param   defaultValue
   1.156 + *          The default value to return in the event the preference has
   1.157 + *          no setting
   1.158 + * @returns The value of the preference, or undefined if there was no
   1.159 + *          user or default value.
   1.160 + */
   1.161 +function getPref(func, preference, defaultValue) {
   1.162 +  try {
   1.163 +    return gPref[func](preference);
   1.164 +  }
   1.165 +  catch (e) {
   1.166 +  }
   1.167 +  return defaultValue;
   1.168 +}
   1.169 +
   1.170 +/**
   1.171 + * Constructs a URI to a spec.
   1.172 + * @param   spec
   1.173 + *          The spec to construct a URI to
   1.174 + * @returns The nsIURI constructed.
   1.175 + */
   1.176 +function newURI(spec) {
   1.177 +  var ioServ = Cc["@mozilla.org/network/io-service;1"].
   1.178 +               getService(Ci.nsIIOService);
   1.179 +  return ioServ.newURI(spec, null, null);
   1.180 +}
   1.181 +
   1.182 +// Restarts the application checking in with observers first
   1.183 +function restartApp() {
   1.184 +  // Notify all windows that an application quit has been requested.
   1.185 +  var os = Cc["@mozilla.org/observer-service;1"].
   1.186 +           getService(Ci.nsIObserverService);
   1.187 +  var cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].
   1.188 +                   createInstance(Ci.nsISupportsPRBool);
   1.189 +  os.notifyObservers(cancelQuit, "quit-application-requested", null);
   1.190 +
   1.191 +  // Something aborted the quit process.
   1.192 +  if (cancelQuit.data)
   1.193 +    return;
   1.194 +
   1.195 +  var as = Cc["@mozilla.org/toolkit/app-startup;1"].
   1.196 +           getService(Ci.nsIAppStartup);
   1.197 +  as.quit(Ci.nsIAppStartup.eRestart | Ci.nsIAppStartup.eAttemptQuit);
   1.198 +}
   1.199 +
   1.200 +/**
   1.201 + * Checks whether this blocklist element is valid for the current OS and ABI.
   1.202 + * If the element has an "os" attribute then the current OS must appear in
   1.203 + * its comma separated list for the element to be valid. Similarly for the
   1.204 + * xpcomabi attribute.
   1.205 + */
   1.206 +function matchesOSABI(blocklistElement) {
   1.207 +  if (blocklistElement.hasAttribute("os")) {
   1.208 +    var choices = blocklistElement.getAttribute("os").split(",");
   1.209 +    if (choices.length > 0 && choices.indexOf(gApp.OS) < 0)
   1.210 +      return false;
   1.211 +  }
   1.212 +
   1.213 +  if (blocklistElement.hasAttribute("xpcomabi")) {
   1.214 +    choices = blocklistElement.getAttribute("xpcomabi").split(",");
   1.215 +    if (choices.length > 0 && choices.indexOf(gApp.XPCOMABI) < 0)
   1.216 +      return false;
   1.217 +  }
   1.218 +
   1.219 +  return true;
   1.220 +}
   1.221 +
   1.222 +/**
   1.223 + * Gets the current value of the locale.  It's possible for this preference to
   1.224 + * be localized, so we have to do a little extra work here.  Similar code
   1.225 + * exists in nsHttpHandler.cpp when building the UA string.
   1.226 + */
   1.227 +function getLocale() {
   1.228 +  try {
   1.229 +      // Get the default branch
   1.230 +      var defaultPrefs = gPref.getDefaultBranch(null);
   1.231 +      return defaultPrefs.getComplexValue(PREF_GENERAL_USERAGENT_LOCALE,
   1.232 +                                          Ci.nsIPrefLocalizedString).data;
   1.233 +  } catch (e) {}
   1.234 +
   1.235 +  return gPref.getCharPref(PREF_GENERAL_USERAGENT_LOCALE);
   1.236 +}
   1.237 +
   1.238 +/* Get the distribution pref values, from defaults only */
   1.239 +function getDistributionPrefValue(aPrefName) {
   1.240 +  var prefValue = "default";
   1.241 +
   1.242 +  var defaults = gPref.getDefaultBranch(null);
   1.243 +  try {
   1.244 +    prefValue = defaults.getCharPref(aPrefName);
   1.245 +  } catch (e) {
   1.246 +    // use default when pref not found
   1.247 +  }
   1.248 +
   1.249 +  return prefValue;
   1.250 +}
   1.251 +
   1.252 +/**
   1.253 + * Parse a string representation of a regular expression. Needed because we
   1.254 + * use the /pattern/flags form (because it's detectable), which is only
   1.255 + * supported as a literal in JS.
   1.256 + *
   1.257 + * @param  aStr
   1.258 + *         String representation of regexp
   1.259 + * @return RegExp instance
   1.260 + */
   1.261 +function parseRegExp(aStr) {
   1.262 +  let lastSlash = aStr.lastIndexOf("/");
   1.263 +  let pattern = aStr.slice(1, lastSlash);
   1.264 +  let flags = aStr.slice(lastSlash + 1);
   1.265 +  return new RegExp(pattern, flags);
   1.266 +}
   1.267 +
   1.268 +/**
   1.269 + * Manages the Blocklist. The Blocklist is a representation of the contents of
   1.270 + * blocklist.xml and allows us to remotely disable / re-enable blocklisted
   1.271 + * items managed by the Extension Manager with an item's appDisabled property.
   1.272 + * It also blocklists plugins with data from blocklist.xml.
   1.273 + */
   1.274 +
   1.275 +function Blocklist() {
   1.276 +  let os = getObserverService();
   1.277 +  os.addObserver(this, "xpcom-shutdown", false);
   1.278 +  gLoggingEnabled = getPref("getBoolPref", PREF_EM_LOGGING_ENABLED, false);
   1.279 +  gBlocklistEnabled = getPref("getBoolPref", PREF_BLOCKLIST_ENABLED, true);
   1.280 +  gBlocklistLevel = Math.min(getPref("getIntPref", PREF_BLOCKLIST_LEVEL, DEFAULT_LEVEL),
   1.281 +                                     MAX_BLOCK_LEVEL);
   1.282 +  gPref.addObserver("extensions.blocklist.", this, false);
   1.283 +  gPref.addObserver(PREF_EM_LOGGING_ENABLED, this, false);
   1.284 +}
   1.285 +
   1.286 +Blocklist.prototype = {
   1.287 +  /**
   1.288 +   * Extension ID -> array of Version Ranges
   1.289 +   * Each value in the version range array is a JS Object that has the
   1.290 +   * following properties:
   1.291 +   *   "minVersion"  The minimum version in a version range (default = 0)
   1.292 +   *   "maxVersion"  The maximum version in a version range (default = *)
   1.293 +   *   "targetApps"  Application ID -> array of Version Ranges
   1.294 +   *                 (default = current application ID)
   1.295 +   *                 Each value in the version range array is a JS Object that
   1.296 +   *                 has the following properties:
   1.297 +   *                   "minVersion"  The minimum version in a version range
   1.298 +   *                                 (default = 0)
   1.299 +   *                   "maxVersion"  The maximum version in a version range
   1.300 +   *                                 (default = *)
   1.301 +   */
   1.302 +  _addonEntries: null,
   1.303 +  _pluginEntries: null,
   1.304 +
   1.305 +  observe: function Blocklist_observe(aSubject, aTopic, aData) {
   1.306 +    switch (aTopic) {
   1.307 +    case "xpcom-shutdown":
   1.308 +      let os = getObserverService();
   1.309 +      os.removeObserver(this, "xpcom-shutdown");
   1.310 +      gPref.removeObserver("extensions.blocklist.", this);
   1.311 +      gPref.removeObserver(PREF_EM_LOGGING_ENABLED, this);
   1.312 +      break;
   1.313 +    case "nsPref:changed":
   1.314 +      switch (aData) {
   1.315 +        case PREF_EM_LOGGING_ENABLED:
   1.316 +          gLoggingEnabled = getPref("getBoolPref", PREF_EM_LOGGING_ENABLED, false);
   1.317 +          break;
   1.318 +        case PREF_BLOCKLIST_ENABLED:
   1.319 +          gBlocklistEnabled = getPref("getBoolPref", PREF_BLOCKLIST_ENABLED, true);
   1.320 +          this._loadBlocklist();
   1.321 +          this._blocklistUpdated(null, null);
   1.322 +          break;
   1.323 +        case PREF_BLOCKLIST_LEVEL:
   1.324 +          gBlocklistLevel = Math.min(getPref("getIntPref", PREF_BLOCKLIST_LEVEL, DEFAULT_LEVEL),
   1.325 +                                     MAX_BLOCK_LEVEL);
   1.326 +          this._blocklistUpdated(null, null);
   1.327 +          break;
   1.328 +      }
   1.329 +      break;
   1.330 +    }
   1.331 +  },
   1.332 +
   1.333 +  /* See nsIBlocklistService */
   1.334 +  isAddonBlocklisted: function Blocklist_isAddonBlocklisted(addon, appVersion, toolkitVersion) {
   1.335 +    return this.getAddonBlocklistState(addon, appVersion, toolkitVersion) ==
   1.336 +                   Ci.nsIBlocklistService.STATE_BLOCKED;
   1.337 +  },
   1.338 +
   1.339 +  /* See nsIBlocklistService */
   1.340 +  getAddonBlocklistState: function Blocklist_getAddonBlocklistState(addon, appVersion, toolkitVersion) {
   1.341 +    if (!this._addonEntries)
   1.342 +      this._loadBlocklist();
   1.343 +    return this._getAddonBlocklistState(addon, this._addonEntries,
   1.344 +                                        appVersion, toolkitVersion);
   1.345 +  },
   1.346 +
   1.347 +  /**
   1.348 +   * Private version of getAddonBlocklistState that allows the caller to pass in
   1.349 +   * the add-on blocklist entries to compare against.
   1.350 +   *
   1.351 +   * @param   id
   1.352 +   *          The ID of the item to get the blocklist state for.
   1.353 +   * @param   version
   1.354 +   *          The version of the item to get the blocklist state for.
   1.355 +   * @param   addonEntries
   1.356 +   *          The add-on blocklist entries to compare against.
   1.357 +   * @param   appVersion
   1.358 +   *          The application version to compare to, will use the current
   1.359 +   *          version if null.
   1.360 +   * @param   toolkitVersion
   1.361 +   *          The toolkit version to compare to, will use the current version if
   1.362 +   *          null.
   1.363 +   * @returns The blocklist state for the item, one of the STATE constants as
   1.364 +   *          defined in nsIBlocklistService.
   1.365 +   */
   1.366 +  _getAddonBlocklistState: function Blocklist_getAddonBlocklistStateCall(addon,
   1.367 +                           addonEntries, appVersion, toolkitVersion) {
   1.368 +    if (!gBlocklistEnabled)
   1.369 +      return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
   1.370 +
   1.371 +    if (!appVersion)
   1.372 +      appVersion = gApp.version;
   1.373 +    if (!toolkitVersion)
   1.374 +      toolkitVersion = gApp.platformVersion;
   1.375 +
   1.376 +    var blItem = this._findMatchingAddonEntry(addonEntries, addon);
   1.377 +    if (!blItem)
   1.378 +      return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
   1.379 +
   1.380 +    for (let currentblItem of blItem.versions) {
   1.381 +      if (currentblItem.includesItem(addon.version, appVersion, toolkitVersion))
   1.382 +        return currentblItem.severity >= gBlocklistLevel ? Ci.nsIBlocklistService.STATE_BLOCKED :
   1.383 +                                                       Ci.nsIBlocklistService.STATE_SOFTBLOCKED;
   1.384 +    }
   1.385 +    return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
   1.386 +  },
   1.387 +
   1.388 +  /**
   1.389 +   * Returns the set of prefs of the add-on stored in the blocklist file
   1.390 +   * (probably to revert them on disabling).
   1.391 +   * @param addon
   1.392 +   *        The add-on whose to-be-reset prefs are to be found.
   1.393 +   */
   1.394 +  _getAddonPrefs: function Blocklist_getAddonPrefs(addon) {
   1.395 +    let entry = this._findMatchingAddonEntry(this._addonEntries, addon);
   1.396 +    return entry.prefs.slice(0);
   1.397 +  },
   1.398 +
   1.399 +  _findMatchingAddonEntry: function Blocklist_findMatchingAddonEntry(aAddonEntries,
   1.400 +                                                                     aAddon) {
   1.401 +    if (!aAddon)
   1.402 +      return null;
   1.403 +    // Returns true if the params object passes the constraints set by entry.
   1.404 +    // (For every non-null property in entry, the same key must exist in
   1.405 +    // params and value must be the same)
   1.406 +    function checkEntry(entry, params) {
   1.407 +      for (let [key, value] of entry) {
   1.408 +        if (value === null || value === undefined)
   1.409 +          continue;
   1.410 +        if (params[key]) {
   1.411 +          if (value instanceof RegExp) {
   1.412 +            if (!value.test(params[key])) {
   1.413 +              return false;
   1.414 +            }
   1.415 +          } else if (value !== params[key]) {
   1.416 +            return false;
   1.417 +          }
   1.418 +        } else {
   1.419 +          return false;
   1.420 +        }
   1.421 +      }
   1.422 +      return true;
   1.423 +    }
   1.424 +
   1.425 +    let params = {};
   1.426 +    for (let filter of EXTENSION_BLOCK_FILTERS) {
   1.427 +      params[filter] = aAddon[filter];
   1.428 +    }
   1.429 +    if (params.creator)
   1.430 +      params.creator = params.creator.name;
   1.431 +    for (let entry of aAddonEntries) {
   1.432 +      if (checkEntry(entry.attributes, params)) {
   1.433 +         return entry;
   1.434 +       }
   1.435 +     }
   1.436 +     return null;
   1.437 +  },
   1.438 +
   1.439 +  /* See nsIBlocklistService */
   1.440 +  getAddonBlocklistURL: function Blocklist_getAddonBlocklistURL(addon, appVersion, toolkitVersion) {
   1.441 +    if (!gBlocklistEnabled)
   1.442 +      return "";
   1.443 +
   1.444 +    if (!this._addonEntries)
   1.445 +      this._loadBlocklist();
   1.446 +
   1.447 +    let blItem = this._findMatchingAddonEntry(this._addonEntries, addon);
   1.448 +    if (!blItem || !blItem.blockID)
   1.449 +      return null;
   1.450 +
   1.451 +    return this._createBlocklistURL(blItem.blockID);
   1.452 +  },
   1.453 +
   1.454 +  _createBlocklistURL: function Blocklist_createBlocklistURL(id) {
   1.455 +    let url = Services.urlFormatter.formatURLPref(PREF_BLOCKLIST_ITEM_URL);
   1.456 +    url = url.replace(/%blockID%/g, id);
   1.457 +
   1.458 +    return url;
   1.459 +  },
   1.460 +
   1.461 +  notify: function Blocklist_notify(aTimer) {
   1.462 +    if (!gBlocklistEnabled)
   1.463 +      return;
   1.464 +
   1.465 +    try {
   1.466 +      var dsURI = gPref.getCharPref(PREF_BLOCKLIST_URL);
   1.467 +    }
   1.468 +    catch (e) {
   1.469 +      LOG("Blocklist::notify: The " + PREF_BLOCKLIST_URL + " preference" +
   1.470 +          " is missing!");
   1.471 +      return;
   1.472 +    }
   1.473 +
   1.474 +    var pingCountVersion = getPref("getIntPref", PREF_BLOCKLIST_PINGCOUNTVERSION, 0);
   1.475 +    var pingCountTotal = getPref("getIntPref", PREF_BLOCKLIST_PINGCOUNTTOTAL, 1);
   1.476 +    var daysSinceLastPing = 0;
   1.477 +    if (pingCountVersion == 0) {
   1.478 +      daysSinceLastPing = "new";
   1.479 +    }
   1.480 +    else {
   1.481 +      // Seconds in one day is used because nsIUpdateTimerManager stores the
   1.482 +      // last update time in seconds.
   1.483 +      let secondsInDay = 60 * 60 * 24;
   1.484 +      let lastUpdateTime = getPref("getIntPref", PREF_BLOCKLIST_LASTUPDATETIME, 0);
   1.485 +      if (lastUpdateTime == 0) {
   1.486 +        daysSinceLastPing = "invalid";
   1.487 +      }
   1.488 +      else {
   1.489 +        let now = Math.round(Date.now() / 1000);
   1.490 +        daysSinceLastPing = Math.floor((now - lastUpdateTime) / secondsInDay);
   1.491 +      }
   1.492 +
   1.493 +      if (daysSinceLastPing == 0 || daysSinceLastPing == "invalid") {
   1.494 +        pingCountVersion = pingCountTotal = "invalid";
   1.495 +      }
   1.496 +    }
   1.497 +
   1.498 +    if (pingCountVersion < 1)
   1.499 +      pingCountVersion = 1;
   1.500 +    if (pingCountTotal < 1)
   1.501 +      pingCountTotal = 1;
   1.502 +
   1.503 +    dsURI = dsURI.replace(/%APP_ID%/g, gApp.ID);
   1.504 +    dsURI = dsURI.replace(/%APP_VERSION%/g, gApp.version);
   1.505 +    dsURI = dsURI.replace(/%PRODUCT%/g, gApp.name);
   1.506 +    dsURI = dsURI.replace(/%VERSION%/g, gApp.version);
   1.507 +    dsURI = dsURI.replace(/%BUILD_ID%/g, gApp.appBuildID);
   1.508 +    dsURI = dsURI.replace(/%BUILD_TARGET%/g, gApp.OS + "_" + gABI);
   1.509 +    dsURI = dsURI.replace(/%OS_VERSION%/g, gOSVersion);
   1.510 +    dsURI = dsURI.replace(/%LOCALE%/g, getLocale());
   1.511 +    dsURI = dsURI.replace(/%CHANNEL%/g, UpdateChannel.get());
   1.512 +    dsURI = dsURI.replace(/%PLATFORM_VERSION%/g, gApp.platformVersion);
   1.513 +    dsURI = dsURI.replace(/%DISTRIBUTION%/g,
   1.514 +                      getDistributionPrefValue(PREF_APP_DISTRIBUTION));
   1.515 +    dsURI = dsURI.replace(/%DISTRIBUTION_VERSION%/g,
   1.516 +                      getDistributionPrefValue(PREF_APP_DISTRIBUTION_VERSION));
   1.517 +    dsURI = dsURI.replace(/%PING_COUNT%/g, pingCountVersion);
   1.518 +    dsURI = dsURI.replace(/%TOTAL_PING_COUNT%/g, pingCountTotal);
   1.519 +    dsURI = dsURI.replace(/%DAYS_SINCE_LAST_PING%/g, daysSinceLastPing);
   1.520 +    dsURI = dsURI.replace(/\+/g, "%2B");
   1.521 +
   1.522 +    // Under normal operations it will take around 5,883,516 years before the
   1.523 +    // preferences used to store pingCountVersion and pingCountTotal will rollover
   1.524 +    // so this code doesn't bother trying to do the "right thing" here.
   1.525 +    if (pingCountVersion != "invalid") {
   1.526 +      pingCountVersion++;
   1.527 +      if (pingCountVersion > 2147483647) {
   1.528 +        // Rollover to -1 if the value is greater than what is support by an
   1.529 +        // integer preference. The -1 indicates that the counter has been reset.
   1.530 +        pingCountVersion = -1;
   1.531 +      }
   1.532 +      gPref.setIntPref(PREF_BLOCKLIST_PINGCOUNTVERSION, pingCountVersion);
   1.533 +    }
   1.534 +
   1.535 +    if (pingCountTotal != "invalid") {
   1.536 +      pingCountTotal++;
   1.537 +      if (pingCountTotal > 2147483647) {
   1.538 +        // Rollover to 1 if the value is greater than what is support by an
   1.539 +        // integer preference.
   1.540 +        pingCountTotal = -1;
   1.541 +      }
   1.542 +      gPref.setIntPref(PREF_BLOCKLIST_PINGCOUNTTOTAL, pingCountTotal);
   1.543 +    }
   1.544 +
   1.545 +    // Verify that the URI is valid
   1.546 +    try {
   1.547 +      var uri = newURI(dsURI);
   1.548 +    }
   1.549 +    catch (e) {
   1.550 +      LOG("Blocklist::notify: There was an error creating the blocklist URI\r\n" +
   1.551 +          "for: " + dsURI + ", error: " + e);
   1.552 +      return;
   1.553 +    }
   1.554 +
   1.555 +    LOG("Blocklist::notify: Requesting " + uri.spec);
   1.556 +    var request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
   1.557 +                  createInstance(Ci.nsIXMLHttpRequest);
   1.558 +    request.open("GET", uri.spec, true);
   1.559 +    request.channel.notificationCallbacks = new gCertUtils.BadCertHandler();
   1.560 +    request.overrideMimeType("text/xml");
   1.561 +    request.setRequestHeader("Cache-Control", "no-cache");
   1.562 +    request.QueryInterface(Components.interfaces.nsIJSXMLHttpRequest);
   1.563 +
   1.564 +    var self = this;
   1.565 +    request.addEventListener("error", function errorEventListener(event) {
   1.566 +                                      self.onXMLError(event); }, false);
   1.567 +    request.addEventListener("load", function loadEventListener(event) {
   1.568 +                                     self.onXMLLoad(event);  }, false);
   1.569 +    request.send(null);
   1.570 +
   1.571 +    // When the blocklist loads we need to compare it to the current copy so
   1.572 +    // make sure we have loaded it.
   1.573 +    if (!this._addonEntries)
   1.574 +      this._loadBlocklist();
   1.575 +  },
   1.576 +
   1.577 +  onXMLLoad: Task.async(function* (aEvent) {
   1.578 +    let request = aEvent.target;
   1.579 +    try {
   1.580 +      gCertUtils.checkCert(request.channel);
   1.581 +    }
   1.582 +    catch (e) {
   1.583 +      LOG("Blocklist::onXMLLoad: " + e);
   1.584 +      return;
   1.585 +    }
   1.586 +    let responseXML = request.responseXML;
   1.587 +    if (!responseXML || responseXML.documentElement.namespaceURI == XMLURI_PARSE_ERROR ||
   1.588 +        (request.status != 200 && request.status != 0)) {
   1.589 +      LOG("Blocklist::onXMLLoad: there was an error during load");
   1.590 +      return;
   1.591 +    }
   1.592 +
   1.593 +    var oldAddonEntries = this._addonEntries;
   1.594 +    var oldPluginEntries = this._pluginEntries;
   1.595 +    this._addonEntries = [];
   1.596 +    this._pluginEntries = [];
   1.597 +
   1.598 +    this._loadBlocklistFromString(request.responseText);
   1.599 +    this._blocklistUpdated(oldAddonEntries, oldPluginEntries);
   1.600 +
   1.601 +    try {
   1.602 +      let path = OS.Path.join(OS.Constants.Path.profileDir, FILE_BLOCKLIST);
   1.603 +      yield OS.File.writeAtomic(path, request.responseText, {tmpPath: path + ".tmp"});
   1.604 +    } catch (e) {
   1.605 +      LOG("Blocklist::onXMLLoad: " + e);
   1.606 +    }
   1.607 +  }),
   1.608 +
   1.609 +  onXMLError: function Blocklist_onXMLError(aEvent) {
   1.610 +    try {
   1.611 +      var request = aEvent.target;
   1.612 +      // the following may throw (e.g. a local file or timeout)
   1.613 +      var status = request.status;
   1.614 +    }
   1.615 +    catch (e) {
   1.616 +      request = aEvent.target.channel.QueryInterface(Ci.nsIRequest);
   1.617 +      status = request.status;
   1.618 +    }
   1.619 +    var statusText = "nsIXMLHttpRequest channel unavailable";
   1.620 +    // When status is 0 we don't have a valid channel.
   1.621 +    if (status != 0) {
   1.622 +      try {
   1.623 +        statusText = request.statusText;
   1.624 +      } catch (e) {
   1.625 +      }
   1.626 +    }
   1.627 +    LOG("Blocklist:onError: There was an error loading the blocklist file\r\n" +
   1.628 +        statusText);
   1.629 +  },
   1.630 +
   1.631 +  /**
   1.632 +   * Finds the newest blocklist file from the application and the profile and
   1.633 +   * load it or does nothing if neither exist.
   1.634 +   */
   1.635 +  _loadBlocklist: function Blocklist_loadBlocklist() {
   1.636 +    this._addonEntries = [];
   1.637 +    this._pluginEntries = [];
   1.638 +    var profFile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_BLOCKLIST]);
   1.639 +    if (profFile.exists()) {
   1.640 +      this._loadBlocklistFromFile(profFile);
   1.641 +      return;
   1.642 +    }
   1.643 +    var appFile = FileUtils.getFile(KEY_APPDIR, [FILE_BLOCKLIST]);
   1.644 +    if (appFile.exists()) {
   1.645 +      this._loadBlocklistFromFile(appFile);
   1.646 +      return;
   1.647 +    }
   1.648 +    LOG("Blocklist::_loadBlocklist: no XML File found");
   1.649 +  },
   1.650 +
   1.651 +  /**
   1.652 +#    The blocklist XML file looks something like this:
   1.653 +#
   1.654 +#    <blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist">
   1.655 +#      <emItems>
   1.656 +#        <emItem id="item_1@domain" blockID="i1">
   1.657 +#          <prefs>
   1.658 +#            <pref>accessibility.accesskeycausesactivation</pref>
   1.659 +#            <pref>accessibility.blockautorefresh</pref>
   1.660 +#          </prefs>
   1.661 +#          <versionRange minVersion="1.0" maxVersion="2.0.*">
   1.662 +#            <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
   1.663 +#              <versionRange minVersion="1.5" maxVersion="1.5.*"/>
   1.664 +#              <versionRange minVersion="1.7" maxVersion="1.7.*"/>
   1.665 +#            </targetApplication>
   1.666 +#            <targetApplication id="toolkit@mozilla.org">
   1.667 +#              <versionRange minVersion="1.9" maxVersion="1.9.*"/>
   1.668 +#            </targetApplication>
   1.669 +#          </versionRange>
   1.670 +#          <versionRange minVersion="3.0" maxVersion="3.0.*">
   1.671 +#            <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
   1.672 +#              <versionRange minVersion="1.5" maxVersion="1.5.*"/>
   1.673 +#            </targetApplication>
   1.674 +#            <targetApplication id="toolkit@mozilla.org">
   1.675 +#              <versionRange minVersion="1.9" maxVersion="1.9.*"/>
   1.676 +#            </targetApplication>
   1.677 +#          </versionRange>
   1.678 +#        </emItem>
   1.679 +#        <emItem id="item_2@domain" blockID="i2">
   1.680 +#          <versionRange minVersion="3.1" maxVersion="4.*"/>
   1.681 +#        </emItem>
   1.682 +#        <emItem id="item_3@domain">
   1.683 +#          <versionRange>
   1.684 +#            <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
   1.685 +#              <versionRange minVersion="1.5" maxVersion="1.5.*"/>
   1.686 +#            </targetApplication>
   1.687 +#          </versionRange>
   1.688 +#        </emItem>
   1.689 +#        <emItem id="item_4@domain" blockID="i3">
   1.690 +#          <versionRange>
   1.691 +#            <targetApplication>
   1.692 +#              <versionRange minVersion="1.5" maxVersion="1.5.*"/>
   1.693 +#            </targetApplication>
   1.694 +#          </versionRange>
   1.695 +#        <emItem id="/@badperson\.com$/"/>
   1.696 +#      </emItems>
   1.697 +#      <pluginItems>
   1.698 +#        <pluginItem blockID="i4">
   1.699 +#          <!-- All match tags must match a plugin to blocklist a plugin -->
   1.700 +#          <match name="name" exp="some plugin"/>
   1.701 +#          <match name="description" exp="1[.]2[.]3"/>
   1.702 +#        </pluginItem>
   1.703 +#      </pluginItems>
   1.704 +#    </blocklist>
   1.705 +   */
   1.706 +
   1.707 +  _loadBlocklistFromFile: function Blocklist_loadBlocklistFromFile(file) {
   1.708 +    if (!gBlocklistEnabled) {
   1.709 +      LOG("Blocklist::_loadBlocklistFromFile: blocklist is disabled");
   1.710 +      return;
   1.711 +    }
   1.712 +
   1.713 +    if (!file.exists()) {
   1.714 +      LOG("Blocklist::_loadBlocklistFromFile: XML File does not exist " + file.path);
   1.715 +      return;
   1.716 +    }
   1.717 +
   1.718 +    let text = "";
   1.719 +    let fstream = null;
   1.720 +    let cstream = null;
   1.721 +
   1.722 +    try {
   1.723 +      fstream = Components.classes["@mozilla.org/network/file-input-stream;1"]
   1.724 +                          .createInstance(Components.interfaces.nsIFileInputStream);
   1.725 +      cstream = Components.classes["@mozilla.org/intl/converter-input-stream;1"]
   1.726 +                          .createInstance(Components.interfaces.nsIConverterInputStream);
   1.727 +
   1.728 +      fstream.init(file, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0);
   1.729 +      cstream.init(fstream, "UTF-8", 0, 0);
   1.730 +
   1.731 +      let (str = {}) {
   1.732 +        let read = 0;
   1.733 +
   1.734 +        do {
   1.735 +          read = cstream.readString(0xffffffff, str); // read as much as we can and put it in str.value
   1.736 +          text += str.value;
   1.737 +        } while (read != 0);
   1.738 +      }
   1.739 +    } catch (e) {
   1.740 +      LOG("Blocklist::_loadBlocklistFromFile: Failed to load XML file " + e);
   1.741 +    } finally {
   1.742 +      cstream.close();
   1.743 +      fstream.close();
   1.744 +    }
   1.745 +
   1.746 +    text && this._loadBlocklistFromString(text);
   1.747 +  },
   1.748 +
   1.749 +  _loadBlocklistFromString : function Blocklist_loadBlocklistFromString(text) {
   1.750 +    try {
   1.751 +      var parser = Cc["@mozilla.org/xmlextras/domparser;1"].
   1.752 +                   createInstance(Ci.nsIDOMParser);
   1.753 +      var doc = parser.parseFromString(text, "text/xml");
   1.754 +      if (doc.documentElement.namespaceURI != XMLURI_BLOCKLIST) {
   1.755 +        LOG("Blocklist::_loadBlocklistFromFile: aborting due to incorrect " +
   1.756 +            "XML Namespace.\r\nExpected: " + XMLURI_BLOCKLIST + "\r\n" +
   1.757 +            "Received: " + doc.documentElement.namespaceURI);
   1.758 +        return;
   1.759 +      }
   1.760 +
   1.761 +      var childNodes = doc.documentElement.childNodes;
   1.762 +      for (let element of childNodes) {
   1.763 +        if (!(element instanceof Ci.nsIDOMElement))
   1.764 +          continue;
   1.765 +        switch (element.localName) {
   1.766 +        case "emItems":
   1.767 +          this._addonEntries = this._processItemNodes(element.childNodes, "em",
   1.768 +                                                      this._handleEmItemNode);
   1.769 +          break;
   1.770 +        case "pluginItems":
   1.771 +          this._pluginEntries = this._processItemNodes(element.childNodes, "plugin",
   1.772 +                                                       this._handlePluginItemNode);
   1.773 +          break;
   1.774 +        default:
   1.775 +          Services.obs.notifyObservers(element,
   1.776 +                                       "blocklist-data-" + element.localName,
   1.777 +                                       null);
   1.778 +        }
   1.779 +      }
   1.780 +    }
   1.781 +    catch (e) {
   1.782 +      LOG("Blocklist::_loadBlocklistFromFile: Error constructing blocklist " + e);
   1.783 +      return;
   1.784 +    }
   1.785 +  },
   1.786 +
   1.787 +  _processItemNodes: function Blocklist_processItemNodes(itemNodes, prefix, handler) {
   1.788 +    var result = [];
   1.789 +    var itemName = prefix + "Item";
   1.790 +    for (var i = 0; i < itemNodes.length; ++i) {
   1.791 +      var blocklistElement = itemNodes.item(i);
   1.792 +      if (!(blocklistElement instanceof Ci.nsIDOMElement) ||
   1.793 +          blocklistElement.localName != itemName)
   1.794 +        continue;
   1.795 +
   1.796 +      handler(blocklistElement, result);
   1.797 +    }
   1.798 +    return result;
   1.799 +  },
   1.800 +
   1.801 +  _handleEmItemNode: function Blocklist_handleEmItemNode(blocklistElement, result) {
   1.802 +    if (!matchesOSABI(blocklistElement))
   1.803 +      return;
   1.804 +
   1.805 +    let blockEntry = {
   1.806 +      versions: [],
   1.807 +      prefs: [],
   1.808 +      blockID: null,
   1.809 +      attributes: new Map()
   1.810 +      // Atleast one of EXTENSION_BLOCK_FILTERS must get added to attributes
   1.811 +    };
   1.812 +
   1.813 +    // Any filter starting with '/' is interpreted as a regex. So if an attribute
   1.814 +    // starts with a '/' it must be checked via a regex.
   1.815 +    function regExpCheck(attr) {
   1.816 +      return attr.startsWith("/") ? parseRegExp(attr) : attr;
   1.817 +    }
   1.818 +
   1.819 +    for (let filter of EXTENSION_BLOCK_FILTERS) {
   1.820 +      let attr = blocklistElement.getAttribute(filter);
   1.821 +      if (attr)
   1.822 +        blockEntry.attributes.set(filter, regExpCheck(attr));
   1.823 +    }
   1.824 +
   1.825 +    var childNodes = blocklistElement.childNodes;
   1.826 +
   1.827 +    for (let x = 0; x < childNodes.length; x++) {
   1.828 +      var childElement = childNodes.item(x);
   1.829 +      if (!(childElement instanceof Ci.nsIDOMElement))
   1.830 +        continue;
   1.831 +      if (childElement.localName === "prefs") {
   1.832 +        let prefElements = childElement.childNodes;
   1.833 +        for (let i = 0; i < prefElements.length; i++) {
   1.834 +          let prefElement = prefElements.item(i);
   1.835 +          if (!(prefElement instanceof Ci.nsIDOMElement) ||
   1.836 +              prefElement.localName !== "pref")
   1.837 +            continue;
   1.838 +          blockEntry.prefs.push(prefElement.textContent);
   1.839 +        }
   1.840 +      }
   1.841 +      else if (childElement.localName === "versionRange")
   1.842 +        blockEntry.versions.push(new BlocklistItemData(childElement));
   1.843 +    }
   1.844 +    // if only the extension ID is specified block all versions of the
   1.845 +    // extension for the current application.
   1.846 +    if (blockEntry.versions.length == 0)
   1.847 +      blockEntry.versions.push(new BlocklistItemData(null));
   1.848 +
   1.849 +    blockEntry.blockID = blocklistElement.getAttribute("blockID");
   1.850 +
   1.851 +    result.push(blockEntry);
   1.852 +  },
   1.853 +
   1.854 +  _handlePluginItemNode: function Blocklist_handlePluginItemNode(blocklistElement, result) {
   1.855 +    if (!matchesOSABI(blocklistElement))
   1.856 +      return;
   1.857 +
   1.858 +    var matchNodes = blocklistElement.childNodes;
   1.859 +    var blockEntry = {
   1.860 +      matches: {},
   1.861 +      versions: [],
   1.862 +      blockID: null,
   1.863 +    };
   1.864 +    var hasMatch = false;
   1.865 +    for (var x = 0; x < matchNodes.length; ++x) {
   1.866 +      var matchElement = matchNodes.item(x);
   1.867 +      if (!(matchElement instanceof Ci.nsIDOMElement))
   1.868 +        continue;
   1.869 +      if (matchElement.localName == "match") {
   1.870 +        var name = matchElement.getAttribute("name");
   1.871 +        var exp = matchElement.getAttribute("exp");
   1.872 +        try {
   1.873 +          blockEntry.matches[name] = new RegExp(exp, "m");
   1.874 +          hasMatch = true;
   1.875 +        } catch (e) {
   1.876 +          // Ignore invalid regular expressions
   1.877 +        }
   1.878 +      }
   1.879 +      if (matchElement.localName == "versionRange")
   1.880 +        blockEntry.versions.push(new BlocklistItemData(matchElement));
   1.881 +    }
   1.882 +    // Plugin entries require *something* to match to an actual plugin
   1.883 +    if (!hasMatch)
   1.884 +      return;
   1.885 +    // Add a default versionRange if there wasn't one specified
   1.886 +    if (blockEntry.versions.length == 0)
   1.887 +      blockEntry.versions.push(new BlocklistItemData(null));
   1.888 +
   1.889 +    blockEntry.blockID = blocklistElement.getAttribute("blockID");
   1.890 +
   1.891 +    result.push(blockEntry);
   1.892 +  },
   1.893 +
   1.894 +  /* See nsIBlocklistService */
   1.895 +  getPluginBlocklistState: function Blocklist_getPluginBlocklistState(plugin,
   1.896 +                           appVersion, toolkitVersion) {
   1.897 +    if (!this._pluginEntries)
   1.898 +      this._loadBlocklist();
   1.899 +    return this._getPluginBlocklistState(plugin, this._pluginEntries,
   1.900 +                                         appVersion, toolkitVersion);
   1.901 +  },
   1.902 +
   1.903 +  /**
   1.904 +   * Private version of getPluginBlocklistState that allows the caller to pass in
   1.905 +   * the plugin blocklist entries.
   1.906 +   *
   1.907 +   * @param   plugin
   1.908 +   *          The nsIPluginTag to get the blocklist state for.
   1.909 +   * @param   pluginEntries
   1.910 +   *          The plugin blocklist entries to compare against.
   1.911 +   * @param   appVersion
   1.912 +   *          The application version to compare to, will use the current
   1.913 +   *          version if null.
   1.914 +   * @param   toolkitVersion
   1.915 +   *          The toolkit version to compare to, will use the current version if
   1.916 +   *          null.
   1.917 +   * @returns The blocklist state for the item, one of the STATE constants as
   1.918 +   *          defined in nsIBlocklistService.
   1.919 +   */
   1.920 +  _getPluginBlocklistState: function Blocklist_getPluginBlocklistState(plugin,
   1.921 +                            pluginEntries, appVersion, toolkitVersion) {
   1.922 +    if (!gBlocklistEnabled)
   1.923 +      return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
   1.924 +
   1.925 +    if (!appVersion)
   1.926 +      appVersion = gApp.version;
   1.927 +    if (!toolkitVersion)
   1.928 +      toolkitVersion = gApp.platformVersion;
   1.929 +
   1.930 +    for each (var blockEntry in pluginEntries) {
   1.931 +      var matchFailed = false;
   1.932 +      for (var name in blockEntry.matches) {
   1.933 +        if (!(name in plugin) ||
   1.934 +            typeof(plugin[name]) != "string" ||
   1.935 +            !blockEntry.matches[name].test(plugin[name])) {
   1.936 +          matchFailed = true;
   1.937 +          break;
   1.938 +        }
   1.939 +      }
   1.940 +
   1.941 +      if (matchFailed)
   1.942 +        continue;
   1.943 +
   1.944 +      for (let blockEntryVersion of blockEntry.versions) {
   1.945 +        if (blockEntryVersion.includesItem(plugin.version, appVersion,
   1.946 +                                                toolkitVersion)) {
   1.947 +          if (blockEntryVersion.severity >= gBlocklistLevel)
   1.948 +            return Ci.nsIBlocklistService.STATE_BLOCKED;
   1.949 +          if (blockEntryVersion.severity == SEVERITY_OUTDATED) {
   1.950 +            let vulnerabilityStatus = blockEntryVersion.vulnerabilityStatus;
   1.951 +            if (vulnerabilityStatus == VULNERABILITYSTATUS_UPDATE_AVAILABLE)
   1.952 +              return Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE;
   1.953 +            if (vulnerabilityStatus == VULNERABILITYSTATUS_NO_UPDATE)
   1.954 +              return Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE;
   1.955 +            return Ci.nsIBlocklistService.STATE_OUTDATED;
   1.956 +          }
   1.957 +          return Ci.nsIBlocklistService.STATE_SOFTBLOCKED;
   1.958 +        }
   1.959 +      }
   1.960 +    }
   1.961 +
   1.962 +    return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
   1.963 +  },
   1.964 +
   1.965 +  /* See nsIBlocklistService */
   1.966 +  getPluginBlocklistURL: function Blocklist_getPluginBlocklistURL(plugin) {
   1.967 +    if (!gBlocklistEnabled)
   1.968 +      return "";
   1.969 +
   1.970 +    if (!this._pluginEntries)
   1.971 +      this._loadBlocklist();
   1.972 +
   1.973 +    for each (let blockEntry in this._pluginEntries) {
   1.974 +      let matchFailed = false;
   1.975 +      for (let name in blockEntry.matches) {
   1.976 +        if (!(name in plugin) ||
   1.977 +            typeof(plugin[name]) != "string" ||
   1.978 +            !blockEntry.matches[name].test(plugin[name])) {
   1.979 +          matchFailed = true;
   1.980 +          break;
   1.981 +        }
   1.982 +      }
   1.983 +
   1.984 +      if (!matchFailed) {
   1.985 +        if(!blockEntry.blockID)
   1.986 +          return null;
   1.987 +        else
   1.988 +          return this._createBlocklistURL(blockEntry.blockID);
   1.989 +      }
   1.990 +    }
   1.991 +  },
   1.992 +
   1.993 +  _blocklistUpdated: function Blocklist_blocklistUpdated(oldAddonEntries, oldPluginEntries) {
   1.994 +    var addonList = [];
   1.995 +
   1.996 +    // A helper function that reverts the prefs passed to default values.
   1.997 +    function resetPrefs(prefs) {
   1.998 +      for (let pref of prefs)
   1.999 +        gPref.clearUserPref(pref);
  1.1000 +    }
  1.1001 +    var self = this;
  1.1002 +    const types = ["extension", "theme", "locale", "dictionary", "service"];
  1.1003 +    AddonManager.getAddonsByTypes(types, function blocklistUpdated_getAddonsByTypes(addons) {
  1.1004 +
  1.1005 +      for (let addon of addons) {
  1.1006 +        let oldState = Ci.nsIBlocklistService.STATE_NOTBLOCKED;
  1.1007 +        if (oldAddonEntries)
  1.1008 +          oldState = self._getAddonBlocklistState(addon, oldAddonEntries);
  1.1009 +        let state = self.getAddonBlocklistState(addon);
  1.1010 +
  1.1011 +        LOG("Blocklist state for " + addon.id + " changed from " +
  1.1012 +            oldState + " to " + state);
  1.1013 +
  1.1014 +        // We don't want to re-warn about add-ons
  1.1015 +        if (state == oldState)
  1.1016 +          continue;
  1.1017 +
  1.1018 +        if (state === Ci.nsIBlocklistService.STATE_BLOCKED) {
  1.1019 +          // It's a hard block. We must reset certain preferences.
  1.1020 +          let prefs = self._getAddonPrefs(addon);
  1.1021 +          resetPrefs(prefs);
  1.1022 +         }
  1.1023 +
  1.1024 +        // Ensure that softDisabled is false if the add-on is not soft blocked
  1.1025 +        if (state != Ci.nsIBlocklistService.STATE_SOFTBLOCKED)
  1.1026 +          addon.softDisabled = false;
  1.1027 +
  1.1028 +        // Don't warn about add-ons becoming unblocked.
  1.1029 +        if (state == Ci.nsIBlocklistService.STATE_NOT_BLOCKED)
  1.1030 +          continue;
  1.1031 +
  1.1032 +        // If an add-on has dropped from hard to soft blocked just mark it as
  1.1033 +        // soft disabled and don't warn about it.
  1.1034 +        if (state == Ci.nsIBlocklistService.STATE_SOFTBLOCKED &&
  1.1035 +            oldState == Ci.nsIBlocklistService.STATE_BLOCKED) {
  1.1036 +          addon.softDisabled = true;
  1.1037 +          continue;
  1.1038 +        }
  1.1039 +
  1.1040 +        // If the add-on is already disabled for some reason then don't warn
  1.1041 +        // about it
  1.1042 +        if (!addon.isActive)
  1.1043 +          continue;
  1.1044 +
  1.1045 +        addonList.push({
  1.1046 +          name: addon.name,
  1.1047 +          version: addon.version,
  1.1048 +          icon: addon.iconURL,
  1.1049 +          disable: false,
  1.1050 +          blocked: state == Ci.nsIBlocklistService.STATE_BLOCKED,
  1.1051 +          item: addon,
  1.1052 +          url: self.getAddonBlocklistURL(addon),
  1.1053 +        });
  1.1054 +      }
  1.1055 +
  1.1056 +      AddonManagerPrivate.updateAddonAppDisabledStates();
  1.1057 +
  1.1058 +      var phs = Cc["@mozilla.org/plugin/host;1"].
  1.1059 +                getService(Ci.nsIPluginHost);
  1.1060 +      var plugins = phs.getPluginTags();
  1.1061 +
  1.1062 +      for (let plugin of plugins) {
  1.1063 +        let oldState = -1;
  1.1064 +        if (oldPluginEntries)
  1.1065 +          oldState = self._getPluginBlocklistState(plugin, oldPluginEntries);
  1.1066 +        let state = self.getPluginBlocklistState(plugin);
  1.1067 +        LOG("Blocklist state for " + plugin.name + " changed from " +
  1.1068 +            oldState + " to " + state);
  1.1069 +        // We don't want to re-warn about items
  1.1070 +        if (state == oldState)
  1.1071 +          continue;
  1.1072 +
  1.1073 +        if (oldState == Ci.nsIBlocklistService.STATE_BLOCKED) {
  1.1074 +          if (state == Ci.nsIBlocklistService.STATE_SOFTBLOCKED)
  1.1075 +            plugin.enabledState = Ci.nsIPluginTag.STATE_DISABLED;
  1.1076 +        }
  1.1077 +        else if (!plugin.disabled && state != Ci.nsIBlocklistService.STATE_NOT_BLOCKED) {
  1.1078 +          if (state == Ci.nsIBlocklistService.STATE_OUTDATED) {
  1.1079 +            gPref.setBoolPref(PREF_PLUGINS_NOTIFYUSER, true);
  1.1080 +          }
  1.1081 +          else if (state != Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE &&
  1.1082 +                   state != Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE) {
  1.1083 +            addonList.push({
  1.1084 +              name: plugin.name,
  1.1085 +              version: plugin.version,
  1.1086 +              icon: "chrome://mozapps/skin/plugins/pluginGeneric.png",
  1.1087 +              disable: false,
  1.1088 +              blocked: state == Ci.nsIBlocklistService.STATE_BLOCKED,
  1.1089 +              item: plugin,
  1.1090 +              url: self.getPluginBlocklistURL(plugin),
  1.1091 +            });
  1.1092 +          }
  1.1093 +        }
  1.1094 +      }
  1.1095 +
  1.1096 +      if (addonList.length == 0) {
  1.1097 +        Services.obs.notifyObservers(self, "blocklist-updated", "");
  1.1098 +        return;
  1.1099 +      }
  1.1100 +
  1.1101 +      if ("@mozilla.org/addons/blocklist-prompt;1" in Cc) {
  1.1102 +        try {
  1.1103 +          let blockedPrompter = Cc["@mozilla.org/addons/blocklist-prompt;1"]
  1.1104 +                                 .getService(Ci.nsIBlocklistPrompt);
  1.1105 +          blockedPrompter.prompt(addonList);
  1.1106 +        } catch (e) {
  1.1107 +          LOG(e);
  1.1108 +        }
  1.1109 +        Services.obs.notifyObservers(self, "blocklist-updated", "");
  1.1110 +        return;
  1.1111 +      }
  1.1112 +
  1.1113 +      var args = {
  1.1114 +        restart: false,
  1.1115 +        list: addonList
  1.1116 +      };
  1.1117 +      // This lets the dialog get the raw js object
  1.1118 +      args.wrappedJSObject = args;
  1.1119 +
  1.1120 +      /*
  1.1121 +        Some tests run without UI, so the async code listens to a message
  1.1122 +        that can be sent programatically
  1.1123 +      */
  1.1124 +      let applyBlocklistChanges = function blocklistUpdated_applyBlocklistChanges() {
  1.1125 +        for (let addon of addonList) {
  1.1126 +          if (!addon.disable)
  1.1127 +            continue;
  1.1128 +
  1.1129 +          if (addon.item instanceof Ci.nsIPluginTag)
  1.1130 +            addon.item.enabledState = Ci.nsIPluginTag.STATE_DISABLED;
  1.1131 +          else {
  1.1132 +            // This add-on is softblocked.
  1.1133 +            addon.item.softDisabled = true;
  1.1134 +            // We must revert certain prefs.
  1.1135 +            let prefs = self._getAddonPrefs(addon.item);
  1.1136 +            resetPrefs(prefs);
  1.1137 +          }
  1.1138 +        }
  1.1139 +
  1.1140 +        if (args.restart)
  1.1141 +          restartApp();
  1.1142 +
  1.1143 +        Services.obs.notifyObservers(self, "blocklist-updated", "");
  1.1144 +        Services.obs.removeObserver(applyBlocklistChanges, "addon-blocklist-closed");
  1.1145 +      }
  1.1146 +
  1.1147 +      Services.obs.addObserver(applyBlocklistChanges, "addon-blocklist-closed", false);
  1.1148 +
  1.1149 +      if (getPref("getBoolPref", PREF_BLOCKLIST_SUPPRESSUI, false)) {
  1.1150 +        applyBlocklistChanges();
  1.1151 +        return;
  1.1152 +      }
  1.1153 +
  1.1154 +      function blocklistUnloadHandler(event) {
  1.1155 +        if (event.target.location == URI_BLOCKLIST_DIALOG) {
  1.1156 +          applyBlocklistChanges();
  1.1157 +          blocklistWindow.removeEventListener("unload", blocklistUnloadHandler);
  1.1158 +        }
  1.1159 +      }
  1.1160 +
  1.1161 +      let blocklistWindow = Services.ww.openWindow(null, URI_BLOCKLIST_DIALOG, "",
  1.1162 +                              "chrome,centerscreen,dialog,titlebar", args);
  1.1163 +      if (blocklistWindow)
  1.1164 +        blocklistWindow.addEventListener("unload", blocklistUnloadHandler, false);
  1.1165 +    });
  1.1166 +  },
  1.1167 +
  1.1168 +  classID: Components.ID("{66354bc9-7ed1-4692-ae1d-8da97d6b205e}"),
  1.1169 +  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
  1.1170 +                                         Ci.nsIBlocklistService,
  1.1171 +                                         Ci.nsITimerCallback]),
  1.1172 +};
  1.1173 +
  1.1174 +/**
  1.1175 + * Helper for constructing a blocklist.
  1.1176 + */
  1.1177 +function BlocklistItemData(versionRangeElement) {
  1.1178 +  var versionRange = this.getBlocklistVersionRange(versionRangeElement);
  1.1179 +  this.minVersion = versionRange.minVersion;
  1.1180 +  this.maxVersion = versionRange.maxVersion;
  1.1181 +  if (versionRangeElement && versionRangeElement.hasAttribute("severity"))
  1.1182 +    this.severity = versionRangeElement.getAttribute("severity");
  1.1183 +  else
  1.1184 +    this.severity = DEFAULT_SEVERITY;
  1.1185 +  if (versionRangeElement && versionRangeElement.hasAttribute("vulnerabilitystatus")) {
  1.1186 +    this.vulnerabilityStatus = versionRangeElement.getAttribute("vulnerabilitystatus");
  1.1187 +  } else {
  1.1188 +    this.vulnerabilityStatus = VULNERABILITYSTATUS_NONE;
  1.1189 +  }
  1.1190 +  this.targetApps = { };
  1.1191 +  var found = false;
  1.1192 +
  1.1193 +  if (versionRangeElement) {
  1.1194 +    for (var i = 0; i < versionRangeElement.childNodes.length; ++i) {
  1.1195 +      var targetAppElement = versionRangeElement.childNodes.item(i);
  1.1196 +      if (!(targetAppElement instanceof Ci.nsIDOMElement) ||
  1.1197 +          targetAppElement.localName != "targetApplication")
  1.1198 +        continue;
  1.1199 +      found = true;
  1.1200 +      // default to the current application if id is not provided.
  1.1201 +      var appID = targetAppElement.hasAttribute("id") ? targetAppElement.getAttribute("id") : gApp.ID;
  1.1202 +      this.targetApps[appID] = this.getBlocklistAppVersions(targetAppElement);
  1.1203 +    }
  1.1204 +  }
  1.1205 +  // Default to all versions of the current application when no targetApplication
  1.1206 +  // elements were found
  1.1207 +  if (!found)
  1.1208 +    this.targetApps[gApp.ID] = this.getBlocklistAppVersions(null);
  1.1209 +}
  1.1210 +
  1.1211 +BlocklistItemData.prototype = {
  1.1212 +  /**
  1.1213 +   * Tests if a version of an item is included in the version range and target
  1.1214 +   * application information represented by this BlocklistItemData using the
  1.1215 +   * provided application and toolkit versions.
  1.1216 +   * @param   version
  1.1217 +   *          The version of the item being tested.
  1.1218 +   * @param   appVersion
  1.1219 +   *          The application version to test with.
  1.1220 +   * @param   toolkitVersion
  1.1221 +   *          The toolkit version to test with.
  1.1222 +   * @returns True if the version range covers the item version and application
  1.1223 +   *          or toolkit version.
  1.1224 +   */
  1.1225 +  includesItem: function BlocklistItemData_includesItem(version, appVersion, toolkitVersion) {
  1.1226 +    // Some platforms have no version for plugins, these don't match if there
  1.1227 +    // was a min/maxVersion provided
  1.1228 +    if (!version && (this.minVersion || this.maxVersion))
  1.1229 +      return false;
  1.1230 +
  1.1231 +    // Check if the item version matches
  1.1232 +    if (!this.matchesRange(version, this.minVersion, this.maxVersion))
  1.1233 +      return false;
  1.1234 +
  1.1235 +    // Check if the application version matches
  1.1236 +    if (this.matchesTargetRange(gApp.ID, appVersion))
  1.1237 +      return true;
  1.1238 +
  1.1239 +    // Check if the toolkit version matches
  1.1240 +    return this.matchesTargetRange(TOOLKIT_ID, toolkitVersion);
  1.1241 +  },
  1.1242 +
  1.1243 +  /**
  1.1244 +   * Checks if a version is higher than or equal to the minVersion (if provided)
  1.1245 +   * and lower than or equal to the maxVersion (if provided).
  1.1246 +   * @param   version
  1.1247 +   *          The version to test.
  1.1248 +   * @param   minVersion
  1.1249 +   *          The minimum version. If null it is assumed that version is always
  1.1250 +   *          larger.
  1.1251 +   * @param   maxVersion
  1.1252 +   *          The maximum version. If null it is assumed that version is always
  1.1253 +   *          smaller.
  1.1254 +   */
  1.1255 +  matchesRange: function BlocklistItemData_matchesRange(version, minVersion, maxVersion) {
  1.1256 +    if (minVersion && gVersionChecker.compare(version, minVersion) < 0)
  1.1257 +      return false;
  1.1258 +    if (maxVersion && gVersionChecker.compare(version, maxVersion) > 0)
  1.1259 +      return false;
  1.1260 +    return true;
  1.1261 +  },
  1.1262 +
  1.1263 +  /**
  1.1264 +   * Tests if there is a matching range for the given target application id and
  1.1265 +   * version.
  1.1266 +   * @param   appID
  1.1267 +   *          The application ID to test for, may be for an application or toolkit
  1.1268 +   * @param   appVersion
  1.1269 +   *          The version of the application to test for.
  1.1270 +   * @returns True if this version range covers the application version given.
  1.1271 +   */
  1.1272 +  matchesTargetRange: function BlocklistItemData_matchesTargetRange(appID, appVersion) {
  1.1273 +    var blTargetApp = this.targetApps[appID];
  1.1274 +    if (!blTargetApp)
  1.1275 +      return false;
  1.1276 +
  1.1277 +    for (let app of blTargetApp) {
  1.1278 +      if (this.matchesRange(appVersion, app.minVersion, app.maxVersion))
  1.1279 +        return true;
  1.1280 +    }
  1.1281 +
  1.1282 +    return false;
  1.1283 +  },
  1.1284 +
  1.1285 +  /**
  1.1286 +   * Retrieves a version range (e.g. minVersion and maxVersion) for a
  1.1287 +   * blocklist item's targetApplication element.
  1.1288 +   * @param   targetAppElement
  1.1289 +   *          A targetApplication blocklist element.
  1.1290 +   * @returns An array of JS objects with the following properties:
  1.1291 +   *          "minVersion"  The minimum version in a version range (default = null).
  1.1292 +   *          "maxVersion"  The maximum version in a version range (default = null).
  1.1293 +   */
  1.1294 +  getBlocklistAppVersions: function BlocklistItemData_getBlocklistAppVersions(targetAppElement) {
  1.1295 +    var appVersions = [ ];
  1.1296 +
  1.1297 +    if (targetAppElement) {
  1.1298 +      for (var i = 0; i < targetAppElement.childNodes.length; ++i) {
  1.1299 +        var versionRangeElement = targetAppElement.childNodes.item(i);
  1.1300 +        if (!(versionRangeElement instanceof Ci.nsIDOMElement) ||
  1.1301 +            versionRangeElement.localName != "versionRange")
  1.1302 +          continue;
  1.1303 +        appVersions.push(this.getBlocklistVersionRange(versionRangeElement));
  1.1304 +      }
  1.1305 +    }
  1.1306 +    // return minVersion = null and maxVersion = null if no specific versionRange
  1.1307 +    // elements were found
  1.1308 +    if (appVersions.length == 0)
  1.1309 +      appVersions.push(this.getBlocklistVersionRange(null));
  1.1310 +    return appVersions;
  1.1311 +  },
  1.1312 +
  1.1313 +  /**
  1.1314 +   * Retrieves a version range (e.g. minVersion and maxVersion) for a blocklist
  1.1315 +   * versionRange element.
  1.1316 +   * @param   versionRangeElement
  1.1317 +   *          The versionRange blocklist element.
  1.1318 +   * @returns A JS object with the following properties:
  1.1319 +   *          "minVersion"  The minimum version in a version range (default = null).
  1.1320 +   *          "maxVersion"  The maximum version in a version range (default = null).
  1.1321 +   */
  1.1322 +  getBlocklistVersionRange: function BlocklistItemData_getBlocklistVersionRange(versionRangeElement) {
  1.1323 +    var minVersion = null;
  1.1324 +    var maxVersion = null;
  1.1325 +    if (!versionRangeElement)
  1.1326 +      return { minVersion: minVersion, maxVersion: maxVersion };
  1.1327 +
  1.1328 +    if (versionRangeElement.hasAttribute("minVersion"))
  1.1329 +      minVersion = versionRangeElement.getAttribute("minVersion");
  1.1330 +    if (versionRangeElement.hasAttribute("maxVersion"))
  1.1331 +      maxVersion = versionRangeElement.getAttribute("maxVersion");
  1.1332 +
  1.1333 +    return { minVersion: minVersion, maxVersion: maxVersion };
  1.1334 +  }
  1.1335 +};
  1.1336 +
  1.1337 +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([Blocklist]);

mercurial