Bug 399932: Add some variable substitution to updateInfoUrl. r=robstrong a=mconnor
authordtownsend@oxymoronical.com
Sat, 17 Nov 2007 04:52:03 -0800
changeset 8122 eaaaad97d8eb9065f9b2677b08dd06b4c3f7f0a4
parent 8121 c9f768c7806fd55e15234870a9187a3f48c0ee88
child 8123 921305bb2d5edd34297b330ace7d143893219d05
push idunknown
push userunknown
push dateunknown
reviewersrobstrong, mconnor
bugs399932
milestone1.9b2pre
Bug 399932: Add some variable substitution to updateInfoUrl. r=robstrong a=mconnor
toolkit/locales/en-US/chrome/mozapps/extensions/extensions.properties
toolkit/mozapps/extensions/src/nsExtensionManager.js.in
toolkit/mozapps/extensions/test/unit/addons/test_bug335238_1/install.rdf
toolkit/mozapps/extensions/test/unit/addons/test_bug335238_2/install.rdf
toolkit/mozapps/extensions/test/unit/test_bug335238.js
--- a/toolkit/locales/en-US/chrome/mozapps/extensions/extensions.properties
+++ b/toolkit/locales/en-US/chrome/mozapps/extensions/extensions.properties
@@ -33,17 +33,17 @@ cancelCancelInstallButton=No
 cancelUpgradeTitle=Cancel Upgrade of %S
 cancelUpgradeQueryMessage=Are you sure you want to cancel the upgrade of %S?
 cancelUpgradeButton=Yes
 cancelCancelUpgradeButton=No
 disableTitle=Disable %S
 disableWarningDependMessage=If you disable %S, the following items that require this extension will also be disabled:
 disableQueryMessage=Do you want to disable %S?
 
-extensions.update.url=https://addons.mozilla.org/update/VersionCheck.php?reqVersion=%REQ_VERSION%&id=%ITEM_ID%&version=%ITEM_VERSION%&maxAppVersion=%ITEM_MAXAPPVERSION%&status=%ITEM_STATUS%&appID=%APP_ID%&appVersion=%APP_VERSION%&appOS=%APP_OS%&appABI=%APP_ABI%
+extensions.update.url=https://addons.mozilla.org/update/VersionCheck.php?reqVersion=%REQ_VERSION%&id=%ITEM_ID%&version=%ITEM_VERSION%&maxAppVersion=%ITEM_MAXAPPVERSION%&status=%ITEM_STATUS%&appID=%APP_ID%&appVersion=%APP_VERSION%&appOS=%APP_OS%&appABI=%APP_ABI%&locale=%APP_LOCALE%
 extensions.getMoreExtensionsURL=https://%LOCALE%.add-ons.mozilla.com/%LOCALE%/%APP%/%VERSION%/extensions/
 extensions.getMoreThemesURL=https://%LOCALE%.add-ons.mozilla.com/%LOCALE%/%APP%/%VERSION%/themes/
 extensions.getMorePluginsURL=https://%LOCALE%.add-ons.mozilla.com/%LOCALE%/%APP%/%VERSION%/plugins/
 
 themesTitle=Themes
 extensionsTitle=Extensions
 
 statusFormatKBKB=#1 of #2 KB
--- a/toolkit/mozapps/extensions/src/nsExtensionManager.js.in
+++ b/toolkit/mozapps/extensions/src/nsExtensionManager.js.in
@@ -2645,17 +2645,17 @@ ExtensionManager.prototype = {
    *          The timer that fired
    */
   notify: function(timer) {
     if (!getPref("getBoolPref", PREF_EM_UPDATE_ENABLED, true))
       return;
 
     var items = this.getItemList(Ci.nsIUpdateItem.TYPE_ADDON, { });
 
-    var updater = new ExtensionItemUpdater(gApp.ID, gApp.version, this);
+    var updater = new ExtensionItemUpdater(this);
     updater.checkForUpdates(items, items.length,
                             Ci.nsIExtensionManager.UPDATE_CHECK_NEWVERSION,
                             new BackgroundUpdateCheckListener(this.datasource));
   },
 
   /**
    * See nsIExtensionManager.idl
    */
@@ -5147,23 +5147,20 @@ ExtensionManager.prototype = {
    */
   update: function(items, itemCount, updateCheckType, listener) {
     for (i = 0; i < itemCount; ++i) {
       var currItem = items[i];
       if (!currItem)
         throw Cr.NS_ERROR_ILLEGAL_VALUE;
     }
 
-    var appID = gApp.ID;
-    var appVersion = gApp.version;
-
     if (items.length == 0)
       items = this.getItemList(Ci.nsIUpdateItem.TYPE_ADDON, { });
 
-    var updater = new ExtensionItemUpdater(appID, appVersion, this);
+    var updater = new ExtensionItemUpdater(this);
     updater.checkForUpdates(items, items.length, updateCheckType, listener);
   },
 
 
   /**
    * Checks for changes to the blocklist using the local blocklist file,
    * application disables / enables items that have been added / removed from
    * the blocklist, and if there are additions to the blocklist this will
@@ -5791,29 +5788,25 @@ AddonUpdateCheckListener.prototype = {
     this._ds.onAddonUpdateEnded(addon, status);
   }
 };
 
 ///////////////////////////////////////////////////////////////////////////////
 //
 // ExtensionItemUpdater
 //
-function ExtensionItemUpdater(aTargetAppID, aTargetAppVersion, aEM)
+function ExtensionItemUpdater(aEM)
 {
-  this._appID = aTargetAppID;
-  this._appVersion = aTargetAppVersion;
   this._emDS = aEM._ds;
   this._em = aEM;
 
   getVersionChecker();
 }
 
 ExtensionItemUpdater.prototype = {
-  _appID              : "",
-  _appVersion         : "",
   _emDS               : null,
   _em                 : null,
   _updateCheckType    : 0,
   _items              : [],
   _listener           : null,
 
   /* ExtensionItemUpdater
 #
@@ -5956,16 +5949,79 @@ ExtensionItemUpdater.prototype = {
       }
       catch (e) {
         LOG("ExtensionItemUpdater:checkForDone: Failure in listener's onUpdateEnded: " + e);
       }
     }
   },
 };
 
+/**
+ * Replaces %...% strings in an addon url (update and updateInfo) with
+ * appropriate values.
+ * @param   aItem
+ *          The nsIUpdateItem representing the item
+ * @param   aURI
+ *          The uri to escape
+ * @param   aDS
+ *          The extensions datasource
+ *
+ * @returns the appropriately escaped uri.
+ */
+function escapeAddonURI(aItem, aURI, aDS)
+{
+  var itemStatus;
+  if (aDS.getItemProperty(aItem.id, "userDisabled") == "true" ||
+      aDS.getItemProperty(aItem.id, "userDisabled") == OP_NEEDS_ENABLE)
+    itemStatus = "userDisabled";
+  else
+    itemStatus = "userEnabled";
+
+  if (aDS.getItemProperty(aItem.id, "compatible") == "false")
+    itemStatus += ",incompatible";
+  if (aDS.getItemProperty(aItem.id, "blocklisted") == "true")
+    itemStatus += ",blocklisted";
+  if (aDS.getItemProperty(aItem.id, "satisfiesDependencies") == "false")
+    itemStatus += ",needsDependencies";
+
+  aURI = aURI.replace(/%ITEM_ID%/g, aItem.id);
+  aURI = aURI.replace(/%ITEM_VERSION%/g, aItem.version);
+  aURI = aURI.replace(/%ITEM_MAXAPPVERSION%/g, aItem.maxAppVersion);
+  aURI = aURI.replace(/%ITEM_STATUS%/g, itemStatus);
+  aURI = aURI.replace(/%APP_ID%/g, gApp.ID);
+  aURI = aURI.replace(/%APP_VERSION%/g, gApp.version);
+  aURI = aURI.replace(/%REQ_VERSION%/g, 1);
+  aURI = aURI.replace(/%APP_OS%/g, gOSTarget);
+  aURI = aURI.replace(/%APP_ABI%/g, gXPCOMABI);
+  aURI = aURI.replace(/%APP_LOCALE%/g, gLocale);
+
+  // Replace custom parameters (names of custom parameters must have at
+  // least 3 characters to prevent lookups for something like %D0%C8)
+  var catMan = null;
+  aURI = aURI.replace(/%(\w{3,})%/g, function(match, param) {
+    if (!catMan) {
+      catMan = Cc["@mozilla.org/categorymanager;1"].
+               getService(Ci.nsICategoryManager);
+    }
+
+    try {
+      var contractID = catMan.getCategoryEntry(CATEGORY_UPDATE_PARAMS, param);
+      var paramHandler = Cc[contractID].
+                         getService(Ci.nsIPropertyBag2);
+      return paramHandler.getPropertyAsAString(param);
+    }
+    catch(e) {
+      return match;
+    }
+  });
+
+  // escape() does not properly encode + symbols in any embedded FVF strings.
+  return aURI.replace(/\+/g, "%2B");
+}
+
 function RDFItemUpdater(aUpdater) {
   this._updater = aUpdater;
 }
 
 RDFItemUpdater.prototype = {
   _updater            : null,
   _updateCheckType    : 0,
   _item               : null,
@@ -6015,75 +6071,31 @@ RDFItemUpdater.prototype = {
       var status = Ci.nsIAddonUpdateCheckListener.STATUS_READ_ONLY;
       this._updater.checkForDone(aItem, status);
       return;
     }
 
     this._updateCheckType = aUpdateCheckType;
     this._item = aItem;
 
-    var itemStatus;
-    if (emDS.getItemProperty(aItem.id, "userDisabled") == "true" ||
-        emDS.getItemProperty(aItem.id, "userDisabled") == OP_NEEDS_ENABLE)
-      itemStatus = "userDisabled";
-    else
-      itemStatus = "userEnabled";
-
-    if (emDS.getItemProperty(aItem.id, "compatible") == "false")
-      itemStatus += ",incompatible";
-    if (emDS.getItemProperty(aItem.id, "blocklisted") == "true")
-      itemStatus += ",blocklisted";
-    if (emDS.getItemProperty(aItem.id, "satisfiesDependencies") == "false")
-      itemStatus += ",needsDependencies";
-
     // Look for a custom update URI: 1) supplied by a pref, 2) supplied by the
     // install manifest, 3) the default configuration
     try {
       var dsURI = gPref.getComplexValue(PREF_EM_ITEM_UPDATE_URL.replace(/%UUID%/, aItem.id),
                                         Ci.nsIPrefLocalizedString).data;
     }
     catch (e) { }
     if (!dsURI)
       dsURI = aItem.updateRDF;
     if (!dsURI) {
       dsURI = gPref.getComplexValue(PREF_UPDATE_DEFAULT_URL,
                                     Ci.nsIPrefLocalizedString).data;
     }
-    dsURI = dsURI.replace(/%ITEM_ID%/g, aItem.id);
-    dsURI = dsURI.replace(/%ITEM_VERSION%/g, aItem.version);
-    dsURI = dsURI.replace(/%ITEM_MAXAPPVERSION%/g, aItem.maxAppVersion);
-    dsURI = dsURI.replace(/%ITEM_STATUS%/g, itemStatus);
-    dsURI = dsURI.replace(/%APP_ID%/g, this._updater._appID);
-    dsURI = dsURI.replace(/%APP_VERSION%/g, this._updater._appVersion);
-    dsURI = dsURI.replace(/%REQ_VERSION%/g, 1);
-    dsURI = dsURI.replace(/%APP_OS%/g, gOSTarget);
-    dsURI = dsURI.replace(/%APP_ABI%/g, gXPCOMABI);
-
-    // Replace custom parameters (names of custom parameters must have at
-    // least 3 characters to prevent lookups for something like %D0%C8)
-    var catMan = null;
-    dsURI = dsURI.replace(/%(\w{3,})%/g, function(match, param) {
-      if (!catMan) {
-        catMan = Cc["@mozilla.org/categorymanager;1"].
-                 getService(Ci.nsICategoryManager);
-      }
-
-      try {
-        var contractID = catMan.getCategoryEntry(CATEGORY_UPDATE_PARAMS, param);
-        var paramHandler = Cc[contractID].
-                           getService(Ci.nsIPropertyBag2);
-        return paramHandler.getPropertyAsAString(param);
-      }
-      catch(e) {
-        return match;
-      }
-    });
-
-    // escape() does not properly encode + symbols in any embedded FVF strings.
-    dsURI = dsURI.replace(/\+/g, "%2B");
+
+    dsURI = escapeAddonURI(aItem, dsURI, emDS);
 
     // Verify that the URI provided is valid
     try {
       var uri = newURI(dsURI);
     }
     catch (e) {
       LOG("RDFItemUpdater:checkForUpdates: There was an error loading the \r\n" +
           " update datasource for: " + dsURI + ", item = " + aItem.id + ", error: " + e);
@@ -6445,17 +6457,17 @@ RDFItemUpdater.prototype = {
     var taArc = gRDF.GetResource(EM_NS("targetApplication"));
     var targetApps = aDataSource.GetTargets(aUpdateResource, taArc, true);
     
     // Track the best update we have found so far
     var newestUpdateItem = null;
     while (targetApps.hasMoreElements()) {
       var targetApp = targetApps.getNext().QueryInterface(Ci.nsIRDFResource);
       var appID = this._getPropertyFromResource(aDataSource, targetApp, "id", aLocalItem);
-      if (appID != this._updater._appID && appID != TOOLKIT_ID)
+      if (appID != gApp.ID && appID != TOOLKIT_ID)
         continue;
 
       var updateLink = this._getPropertyFromResource(aDataSource, targetApp, "updateLink", aLocalItem);
       var updateHash = this._getPropertyFromResource(aDataSource, targetApp, "updateHash", aLocalItem);
       if (aUpdateCheckType == Ci.nsIExtensionManager.UPDATE_CHECK_NEWVERSION) {
         // New version information is useless without a link to get it from
         if (!updateLink)
           continue;
@@ -6492,17 +6504,17 @@ RDFItemUpdater.prototype = {
           var infourl = this._getPropertyFromResource(aDataSource, targetApp,
                                                       "updateInfoURL");
           if (infourl)
             infourl = EM_L(infourl);
           this._updater._emDS.setItemProperty(aLocalItem.id,
                                               EM_R("availableUpdateInfo"),
                                               infourl);
         }
-        if (appID == this._updater._appID) {
+        if (appID == gApp.ID) {
           // App takes precedence over toolkit.  If we found the app, bail out.
           return updatedItem;
         }
         newestUpdateItem = updatedItem;
       }
     }
     return newestUpdateItem;
   }
@@ -8186,16 +8198,26 @@ ExtensionsDataSource.prototype = {
   },
 
   /**
    * Get the em:homepageURL property (homepage URL of the item)
    */
   _rdfGet_homepageURL: function(item, property) {
     return this._getLocalizablePropertyValue(item, property);
   },
+  
+  _rdfGet_availableUpdateInfo: function(item, property) {
+    var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
+    var uri = stringData(this._inner.GetTarget(item, EM_R("availableUpdateInfo"), true));
+    if (uri) {
+      uri = escapeAddonURI(this.getItemForID(id), uri, this);
+      return EM_L(uri);
+    }
+    return null;
+  },
 
   /**
    * Get the em:isDisabled property. This will be true if the item has a
    * appDisabled or a userDisabled property that is true or OP_NEEDS_ENABLE.
    */
   _rdfGet_isDisabled: function(item, property) {
     var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
     if (this.getItemProperty(id, "userDisabled") == "true" ||
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/unit/addons/test_bug335238_1/install.rdf
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+
+<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+     xmlns:em="http://www.mozilla.org/2004/em-rdf#">
+
+  <Description about="urn:mozilla:install-manifest">
+    <em:id>bug335238_1@tests.mozilla.org</em:id>
+    <em:version>1.3.4</em:version>
+    
+    <em:targetApplication>
+      <Description>
+        <em:id>xpcshell@tests.mozilla.org</em:id>
+        <em:minVersion>1</em:minVersion>
+        <em:maxVersion>5</em:maxVersion>
+      </Description>
+    </em:targetApplication>
+    
+    <em:name>Bug 335238</em:name>
+    <em:updateURL>http://localhost:4444/0?id=%ITEM_ID%&amp;version=%ITEM_VERSION%&amp;maxAppVersion=%ITEM_MAXAPPVERSION%&amp;status=%ITEM_STATUS%&amp;appId=%APP_ID%&amp;appVersion=%APP_VERSION%&amp;appOs=%APP_OS%&amp;appAbi=%APP_ABI%&amp;locale=%APP_LOCALE%&amp;reqVersion=%REQ_VERSION%</em:updateURL>
+    
+  </Description>      
+</RDF>
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/unit/addons/test_bug335238_2/install.rdf
@@ -0,0 +1,30 @@
+<?xml version="1.0"?>
+
+<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+     xmlns:em="http://www.mozilla.org/2004/em-rdf#">
+
+  <Description about="urn:mozilla:install-manifest">
+    <em:id>bug335238_2@tests.mozilla.org</em:id>
+    <em:version>28at</em:version>
+    
+    <em:targetApplication>
+      <Description>
+        <em:id>xpcshell@tests.mozilla.org</em:id>
+        <em:minVersion>1</em:minVersion>
+        <em:maxVersion>7</em:maxVersion>
+      </Description>
+    </em:targetApplication>
+    
+    <em:requires>
+      <Description>
+        <em:id>unknown@tests.mozilla.org</em:id>
+        <em:minVersion>2</em:minVersion>
+        <em:maxVersion>72</em:maxVersion>
+      </Description>
+    </em:requires>
+    
+    <em:name>Bug 335238</em:name>
+    <em:updateURL>http://localhost:4444/1?id=%ITEM_ID%&amp;version=%ITEM_VERSION%&amp;maxAppVersion=%ITEM_MAXAPPVERSION%&amp;status=%ITEM_STATUS%&amp;appId=%APP_ID%&amp;appVersion=%APP_VERSION%&amp;appOs=%APP_OS%&amp;appAbi=%APP_ABI%&amp;locale=%APP_LOCALE%&amp;reqVersion=%REQ_VERSION%</em:updateURL>
+    
+  </Description>      
+</RDF>
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/unit/test_bug335238.js
@@ -0,0 +1,152 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ *      Dave Townsend <dtownsend@oxymoronical.com>.
+ *
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL
+ *
+ * ***** END LICENSE BLOCK *****
+ */
+
+const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS";
+const PREF_SELECTED_LOCALE = "general.useragent.locale";
+
+// Disables security checking our updates which haven't been signed
+gPrefs.setBoolPref("extensions.checkUpdateSecurity", false);
+
+do_import_script("netwerk/test/httpserver/httpd.js");
+
+// This is the data we expect to see sent as part of the update url.
+var EXPECTED = [
+  {
+    id: "bug335238_1@tests.mozilla.org",
+    version: "1.3.4",
+    maxAppVersion: "5",
+    status: "userEnabled",
+    appId: "xpcshell@tests.mozilla.org",
+    appVersion: "1",
+    appOs: "XPCShell",
+    appAbi: "noarch-spidermonkey",
+    locale: "en-US",
+    reqVersion: "1"
+  },
+  {
+    id: "bug335238_2@tests.mozilla.org",
+    version: "28at",
+    maxAppVersion: "7",
+    status: "userDisabled,needsDependencies",
+    appId: "xpcshell@tests.mozilla.org",
+    appVersion: "1",
+    appOs: "XPCShell",
+    appAbi: "noarch-spidermonkey",
+    locale: "en-US",
+    reqVersion: "1"
+  }
+];
+
+var ADDONS = [
+  {id: "bug335238_1@tests.mozilla.org",
+   addon: "test_bug335238_1"},
+  {id: "bug335238_2@tests.mozilla.org",
+   addon: "test_bug335238_2"}
+];
+
+var server;
+
+var updateListener = {
+  onUpdateStarted: function()
+  {
+  },
+  
+  onUpdateEnded: function()
+  {
+    server.stop();
+    do_test_finished();
+  },
+  
+  onAddonUpdateStarted: function(addon)
+  {
+  },
+  
+  onAddonUpdateEnded: function(addon, status)
+  {
+    // No update rdf will get found so this should be a failure.
+    do_check_eq(status, Ci.nsIAddonUpdateCheckListener.STATUS_FAILURE);
+  }
+}
+
+var requestHandler = {
+  handle: function(metadata, response)
+  {
+    var expected = EXPECTED[metadata.path.substring(1)];
+    var params = metadata.queryString.split("&");
+    do_check_eq(params.length, 10);
+    for (var k in params) {
+      var pair = params[k].split("=");
+      var name = decodeURIComponent(pair[0]);
+      var value = decodeURIComponent(pair[1]);
+      do_check_eq(expected[name], value);
+    }
+    response.setStatusLine(metadata.httpVersion, 404, "Not Found");
+  }
+}
+
+function run_test() {
+  createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9");
+  
+  gPrefs.setBoolPref(PREF_MATCH_OS_LOCALE, false);
+  gPrefs.setCharPref(PREF_SELECTED_LOCALE, "en-US");
+
+  startupEM();
+  for (var k in ADDONS)
+    gEM.installItemFromFile(do_get_addon(ADDONS[k].addon),
+                            NS_INSTALL_LOCATION_APPPROFILE);
+
+  restartEM();
+  gEM.disableItem(ADDONS[1].id);
+  restartEM();
+
+  var updates = [];
+  for (var k in ADDONS) {
+    do_check_neq(gEM.getInstallLocation(ADDONS[k].id), null);
+    var addon = gEM.getItemForID(ADDONS[k].id);
+    updates.push(addon);
+  }
+
+  server = new nsHttpServer();
+  server.registerPathHandler("/0", requestHandler);
+  server.registerPathHandler("/1", requestHandler);
+  server.start(4444);
+  
+  gEM.update(updates, updates.length, false, updateListener);
+
+  do_test_pending();
+}