toolkit/mozapps/extensions/internal/PluginProvider.jsm

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this
     3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 "use strict";
     7 const Cc = Components.classes;
     8 const Ci = Components.interfaces;
     9 const Cu = Components.utils;
    11 this.EXPORTED_SYMBOLS = [];
    13 Cu.import("resource://gre/modules/AddonManager.jsm");
    14 Cu.import("resource://gre/modules/Services.jsm");
    16 const URI_EXTENSION_STRINGS  = "chrome://mozapps/locale/extensions/extensions.properties";
    17 const STRING_TYPE_NAME       = "type.%ID%.name";
    18 const LIST_UPDATED_TOPIC     = "plugins-list-updated";
    20 Cu.import("resource://gre/modules/Log.jsm");
    21 const LOGGER_ID = "addons.plugins";
    23 // Create a new logger for use by the Addons Plugin Provider
    24 // (Requires AddonManager.jsm)
    25 let logger = Log.repository.getLogger(LOGGER_ID);
    27 function getIDHashForString(aStr) {
    28   // return the two-digit hexadecimal code for a byte
    29   function toHexString(charCode)
    30     ("0" + charCode.toString(16)).slice(-2);
    32   let hasher = Cc["@mozilla.org/security/hash;1"].
    33                createInstance(Ci.nsICryptoHash);
    34   hasher.init(Ci.nsICryptoHash.MD5);
    35   let stringStream = Cc["@mozilla.org/io/string-input-stream;1"].
    36                      createInstance(Ci.nsIStringInputStream);
    37                      stringStream.data = aStr ? aStr : "null";
    38   hasher.updateFromStream(stringStream, -1);
    40   // convert the binary hash data to a hex string.
    41   let binary = hasher.finish(false);
    42   let hash = [toHexString(binary.charCodeAt(i)) for (i in binary)].join("").toLowerCase();
    43   return "{" + hash.substr(0, 8) + "-" +
    44                hash.substr(8, 4) + "-" +
    45                hash.substr(12, 4) + "-" +
    46                hash.substr(16, 4) + "-" +
    47                hash.substr(20) + "}";
    48 }
    50 var PluginProvider = {
    51   // A dictionary mapping IDs to names and descriptions
    52   plugins: null,
    54   startup: function PL_startup() {
    55     Services.obs.addObserver(this, LIST_UPDATED_TOPIC, false);
    56     Services.obs.addObserver(this, AddonManager.OPTIONS_NOTIFICATION_DISPLAYED, false);
    57   },
    59   /**
    60    * Called when the application is shutting down. Only necessary for tests
    61    * to be able to simulate a shutdown.
    62    */
    63   shutdown: function PL_shutdown() {
    64     this.plugins = null;
    65     Services.obs.removeObserver(this, AddonManager.OPTIONS_NOTIFICATION_DISPLAYED);
    66     Services.obs.removeObserver(this, LIST_UPDATED_TOPIC);
    67   },
    69   observe: function(aSubject, aTopic, aData) {
    70     switch (aTopic) {
    71     case AddonManager.OPTIONS_NOTIFICATION_DISPLAYED:
    72       this.getAddonByID(aData, function PL_displayPluginInfo(plugin) {
    73         if (!plugin)
    74           return;
    76         let libLabel = aSubject.getElementById("pluginLibraries");
    77         libLabel.textContent = plugin.pluginLibraries.join(", ");
    79         let typeLabel = aSubject.getElementById("pluginMimeTypes"), types = [];
    80         for (let type of plugin.pluginMimeTypes) {
    81           let extras = [type.description.trim(), type.suffixes].
    82                        filter(function(x) x).join(": ");
    83           types.push(type.type + (extras ? " (" + extras + ")" : ""));
    84         }
    85         typeLabel.textContent = types.join(",\n");
    86       });
    87       break;
    88     case LIST_UPDATED_TOPIC:
    89       if (this.plugins)
    90         this.updatePluginList();
    91       break;
    92     }
    93   },
    95   /**
    96    * Creates a PluginWrapper for a plugin object.
    97    */
    98   buildWrapper: function PL_buildWrapper(aPlugin) {
    99     return new PluginWrapper(aPlugin.id,
   100                              aPlugin.name,
   101                              aPlugin.description,
   102                              aPlugin.tags);
   103   },
   105   /**
   106    * Called to get an Addon with a particular ID.
   107    *
   108    * @param  aId
   109    *         The ID of the add-on to retrieve
   110    * @param  aCallback
   111    *         A callback to pass the Addon to
   112    */
   113   getAddonByID: function PL_getAddon(aId, aCallback) {
   114     if (!this.plugins)
   115       this.buildPluginList();
   117     if (aId in this.plugins)
   118       aCallback(this.buildWrapper(this.plugins[aId]));
   119     else
   120       aCallback(null);
   121   },
   123   /**
   124    * Called to get Addons of a particular type.
   125    *
   126    * @param  aTypes
   127    *         An array of types to fetch. Can be null to get all types.
   128    * @param  callback
   129    *         A callback to pass an array of Addons to
   130    */
   131   getAddonsByTypes: function PL_getAddonsByTypes(aTypes, aCallback) {
   132     if (aTypes && aTypes.indexOf("plugin") < 0) {
   133       aCallback([]);
   134       return;
   135     }
   137     if (!this.plugins)
   138       this.buildPluginList();
   140     let results = [];
   142     for (let id in this.plugins) {
   143       this.getAddonByID(id, function(aAddon) {
   144         results.push(aAddon);
   145       });
   146     }
   148     aCallback(results);
   149   },
   151   /**
   152    * Called to get Addons that have pending operations.
   153    *
   154    * @param  aTypes
   155    *         An array of types to fetch. Can be null to get all types
   156    * @param  aCallback
   157    *         A callback to pass an array of Addons to
   158    */
   159   getAddonsWithOperationsByTypes: function PL_getAddonsWithOperationsByTypes(aTypes, aCallback) {
   160     aCallback([]);
   161   },
   163   /**
   164    * Called to get the current AddonInstalls, optionally restricting by type.
   165    *
   166    * @param  aTypes
   167    *         An array of types or null to get all types
   168    * @param  aCallback
   169    *         A callback to pass the array of AddonInstalls to
   170    */
   171   getInstallsByTypes: function PL_getInstallsByTypes(aTypes, aCallback) {
   172     aCallback([]);
   173   },
   175   /**
   176    * Builds a list of the current plugins reported by the plugin host
   177    *
   178    * @return a dictionary of plugins indexed by our generated ID
   179    */
   180   getPluginList: function PL_getPluginList() {
   181     let tags = Cc["@mozilla.org/plugin/host;1"].
   182                getService(Ci.nsIPluginHost).
   183                getPluginTags({});
   185     let list = {};
   186     let seenPlugins = {};
   187     for (let tag of tags) {
   188       if (!(tag.name in seenPlugins))
   189         seenPlugins[tag.name] = {};
   190       if (!(tag.description in seenPlugins[tag.name])) {
   191         let plugin = {
   192           id: getIDHashForString(tag.name + tag.description),
   193           name: tag.name,
   194           description: tag.description,
   195           tags: [tag]
   196         };
   198         seenPlugins[tag.name][tag.description] = plugin;
   199         list[plugin.id] = plugin;
   200       }
   201       else {
   202         seenPlugins[tag.name][tag.description].tags.push(tag);
   203       }
   204     }
   206     return list;
   207   },
   209   /**
   210    * Builds the list of known plugins from the plugin host
   211    */
   212   buildPluginList: function PL_buildPluginList() {
   213     this.plugins = this.getPluginList();
   214   },
   216   /**
   217    * Updates the plugins from the plugin host by comparing the current plugins
   218    * to the last known list sending out any necessary API notifications for
   219    * changes.
   220    */
   221   updatePluginList: function PL_updatePluginList() {
   222     let newList = this.getPluginList();
   224     let lostPlugins = [this.buildWrapper(this.plugins[id])
   225                        for each (id in Object.keys(this.plugins)) if (!(id in newList))];
   226     let newPlugins = [this.buildWrapper(newList[id])
   227                       for each (id in Object.keys(newList)) if (!(id in this.plugins))];
   228     let matchedIDs = [id for each (id in Object.keys(newList)) if (id in this.plugins)];
   230     // The plugin host generates new tags for every plugin after a scan and
   231     // if the plugin's filename has changed then the disabled state won't have
   232     // been carried across, send out notifications for anything that has
   233     // changed (see bug 830267).
   234     let changedWrappers = [];
   235     for (let id of matchedIDs) {
   236       let oldWrapper = this.buildWrapper(this.plugins[id]);
   237       let newWrapper = this.buildWrapper(newList[id]);
   239       if (newWrapper.isActive != oldWrapper.isActive) {
   240         AddonManagerPrivate.callAddonListeners(newWrapper.isActive ?
   241                                                "onEnabling" : "onDisabling",
   242                                                newWrapper, false);
   243         changedWrappers.push(newWrapper);
   244       }
   245     }
   247     // Notify about new installs
   248     for (let plugin of newPlugins) {
   249       AddonManagerPrivate.callInstallListeners("onExternalInstall", null,
   250                                                plugin, null, false);
   251       AddonManagerPrivate.callAddonListeners("onInstalling", plugin, false);
   252     }
   254     // Notify for any plugins that have vanished.
   255     for (let plugin of lostPlugins)
   256       AddonManagerPrivate.callAddonListeners("onUninstalling", plugin, false);
   258     this.plugins = newList;
   260     // Signal that new installs are complete
   261     for (let plugin of newPlugins)
   262       AddonManagerPrivate.callAddonListeners("onInstalled", plugin);
   264     // Signal that enables/disables are complete
   265     for (let wrapper of changedWrappers) {
   266       AddonManagerPrivate.callAddonListeners(wrapper.isActive ?
   267                                              "onEnabled" : "onDisabled",
   268                                              wrapper);
   269     }
   271     // Signal that uninstalls are complete
   272     for (let plugin of lostPlugins)
   273       AddonManagerPrivate.callAddonListeners("onUninstalled", plugin);
   274   }
   275 };
   277 /**
   278  * The PluginWrapper wraps a set of nsIPluginTags to provide the data visible to
   279  * public callers through the API.
   280  */
   281 function PluginWrapper(aId, aName, aDescription, aTags) {
   282   let safedesc = aDescription.replace(/<\/?[a-z][^>]*>/gi, " ");
   283   let homepageURL = null;
   284   if (/<A\s+HREF=[^>]*>/i.test(aDescription))
   285     homepageURL = /<A\s+HREF=["']?([^>"'\s]*)/i.exec(aDescription)[1];
   287   this.__defineGetter__("id", function() aId);
   288   this.__defineGetter__("type", function() "plugin");
   289   this.__defineGetter__("name", function() aName);
   290   this.__defineGetter__("creator", function() null);
   291   this.__defineGetter__("description", function() safedesc);
   292   this.__defineGetter__("version", function() aTags[0].version);
   293   this.__defineGetter__("homepageURL", function() homepageURL);
   295   this.__defineGetter__("isActive", function() !aTags[0].blocklisted && !aTags[0].disabled);
   296   this.__defineGetter__("appDisabled", function() aTags[0].blocklisted);
   298   this.__defineGetter__("userDisabled", function() {
   299     if (aTags[0].disabled)
   300       return true;
   302     if ((Services.prefs.getBoolPref("plugins.click_to_play") && aTags[0].clicktoplay) ||
   303         this.blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE ||
   304         this.blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE)
   305       return AddonManager.STATE_ASK_TO_ACTIVATE;
   307     return false;
   308   });
   310   this.__defineSetter__("userDisabled", function(aVal) {
   311     let previousVal = this.userDisabled;
   312     if (aVal === previousVal)
   313       return aVal;
   315     for (let tag of aTags) {
   316       if (aVal === true)
   317         tag.enabledState = Ci.nsIPluginTag.STATE_DISABLED;
   318       else if (aVal === false)
   319         tag.enabledState = Ci.nsIPluginTag.STATE_ENABLED;
   320       else if (aVal == AddonManager.STATE_ASK_TO_ACTIVATE)
   321         tag.enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
   322     }
   324     // If 'userDisabled' was 'true' and we're going to a state that's not
   325     // that, we're enabling, so call those listeners.
   326     if (previousVal === true && aVal !== true) {
   327       AddonManagerPrivate.callAddonListeners("onEnabling", this, false);
   328       AddonManagerPrivate.callAddonListeners("onEnabled", this);
   329     }
   331     // If 'userDisabled' was not 'true' and we're going to a state where
   332     // it is, we're disabling, so call those listeners.
   333     if (previousVal !== true && aVal === true) {
   334       AddonManagerPrivate.callAddonListeners("onDisabling", this, false);
   335       AddonManagerPrivate.callAddonListeners("onDisabled", this);
   336     }
   338     // If the 'userDisabled' value involved AddonManager.STATE_ASK_TO_ACTIVATE,
   339     // call the onPropertyChanged listeners.
   340     if (previousVal == AddonManager.STATE_ASK_TO_ACTIVATE ||
   341         aVal == AddonManager.STATE_ASK_TO_ACTIVATE) {
   342       AddonManagerPrivate.callAddonListeners("onPropertyChanged", this, ["userDisabled"]);
   343     }
   345     return aVal;
   346   });
   349   this.__defineGetter__("blocklistState", function() {
   350     let bs = Cc["@mozilla.org/extensions/blocklist;1"].
   351              getService(Ci.nsIBlocklistService);
   352     return bs.getPluginBlocklistState(aTags[0]);
   353   });
   355   this.__defineGetter__("blocklistURL", function() {
   356     let bs = Cc["@mozilla.org/extensions/blocklist;1"].
   357              getService(Ci.nsIBlocklistService);
   358     return bs.getPluginBlocklistURL(aTags[0]);
   359   });
   361   this.__defineGetter__("size", function() {
   362     function getDirectorySize(aFile) {
   363       let size = 0;
   364       let entries = aFile.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator);
   365       let entry;
   366       while ((entry = entries.nextFile)) {
   367         if (entry.isSymlink() || !entry.isDirectory())
   368           size += entry.fileSize;
   369         else
   370           size += getDirectorySize(entry);
   371       }
   372       entries.close();
   373       return size;
   374     }
   376     let size = 0;
   377     let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
   378     for (let tag of aTags) {
   379       file.initWithPath(tag.fullpath);
   380       if (file.isDirectory())
   381         size += getDirectorySize(file);
   382       else
   383         size += file.fileSize;
   384     }
   385     return size;
   386   });
   388   this.__defineGetter__("pluginLibraries", function() {
   389     let libs = [];
   390     for (let tag of aTags)
   391       libs.push(tag.filename);
   392     return libs;
   393   });
   395   this.__defineGetter__("pluginFullpath", function() {
   396     let paths = [];
   397     for (let tag of aTags)
   398       paths.push(tag.fullpath);
   399     return paths;
   400   })
   402   this.__defineGetter__("pluginMimeTypes", function() {
   403     let types = [];
   404     for (let tag of aTags) {
   405       let mimeTypes = tag.getMimeTypes({});
   406       let mimeDescriptions = tag.getMimeDescriptions({});
   407       let extensions = tag.getExtensions({});
   408       for (let i = 0; i < mimeTypes.length; i++) {
   409         let type = {};
   410         type.type = mimeTypes[i];
   411         type.description = mimeDescriptions[i];
   412         type.suffixes = extensions[i];
   414         types.push(type);
   415       }
   416     }
   417     return types;
   418   });
   420   this.__defineGetter__("installDate", function() {
   421     let date = 0;
   422     for (let tag of aTags) {
   423       date = Math.max(date, tag.lastModifiedTime);
   424     }
   425     return new Date(date);
   426   });
   428   this.__defineGetter__("scope", function() {
   429     let path = aTags[0].fullpath;
   430     // Plugins inside the application directory are in the application scope
   431     let dir = Services.dirsvc.get("APlugns", Ci.nsIFile);
   432     if (path.startsWith(dir.path))
   433       return AddonManager.SCOPE_APPLICATION;
   435     // Plugins inside the profile directory are in the profile scope
   436     dir = Services.dirsvc.get("ProfD", Ci.nsIFile);
   437     if (path.startsWith(dir.path))
   438       return AddonManager.SCOPE_PROFILE;
   440     // Plugins anywhere else in the user's home are in the user scope,
   441     // but not all platforms have a home directory.
   442     try {
   443       dir = Services.dirsvc.get("Home", Ci.nsIFile);
   444       if (path.startsWith(dir.path))
   445         return AddonManager.SCOPE_USER;
   446     } catch (e if (e.result && e.result == Components.results.NS_ERROR_FAILURE)) {
   447       // Do nothing: missing "Home".
   448     }
   450     // Any other locations are system scope
   451     return AddonManager.SCOPE_SYSTEM;
   452   });
   454   this.__defineGetter__("pendingOperations", function() {
   455     return AddonManager.PENDING_NONE;
   456   });
   458   this.__defineGetter__("operationsRequiringRestart", function() {
   459     return AddonManager.OP_NEEDS_RESTART_NONE;
   460   });
   462   this.__defineGetter__("permissions", function() {
   463     let permissions = 0;
   464     if (!this.appDisabled) {
   466       if (this.userDisabled !== true)
   467         permissions |= AddonManager.PERM_CAN_DISABLE;
   469       let blocklistState = this.blocklistState;
   470       let isCTPBlocklisted =
   471         (blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE ||
   472          blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE);
   474       if (this.userDisabled !== AddonManager.STATE_ASK_TO_ACTIVATE &&
   475           (Services.prefs.getBoolPref("plugins.click_to_play") ||
   476            isCTPBlocklisted)) {
   477         permissions |= AddonManager.PERM_CAN_ASK_TO_ACTIVATE;
   478       }
   480       if (this.userDisabled !== false && !isCTPBlocklisted) {
   481         permissions |= AddonManager.PERM_CAN_ENABLE;
   482       }
   483     }
   484     return permissions;
   485   });
   486 }
   488 PluginWrapper.prototype = {
   489   optionsType: AddonManager.OPTIONS_TYPE_INLINE_INFO,
   490   optionsURL: "chrome://mozapps/content/extensions/pluginPrefs.xul",
   492   get updateDate() {
   493     return this.installDate;
   494   },
   496   get isCompatible() {
   497     return true;
   498   },
   500   get isPlatformCompatible() {
   501     return true;
   502   },
   504   get providesUpdatesSecurely() {
   505     return true;
   506   },
   508   get foreignInstall() {
   509     return true;
   510   },
   512   isCompatibleWith: function(aAppVerison, aPlatformVersion) {
   513     return true;
   514   },
   516   findUpdates: function(aListener, aReason, aAppVersion, aPlatformVersion) {
   517     if ("onNoCompatibilityUpdateAvailable" in aListener)
   518       aListener.onNoCompatibilityUpdateAvailable(this);
   519     if ("onNoUpdateAvailable" in aListener)
   520       aListener.onNoUpdateAvailable(this);
   521     if ("onUpdateFinished" in aListener)
   522       aListener.onUpdateFinished(this);
   523   }
   524 };
   526 AddonManagerPrivate.registerProvider(PluginProvider, [
   527   new AddonManagerPrivate.AddonType("plugin", URI_EXTENSION_STRINGS,
   528                                     STRING_TYPE_NAME,
   529                                     AddonManager.VIEW_TYPE_LIST, 6000,
   530                                     AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE)
   531 ]);

mercurial