client code for Bug 1315505 - Add min and max values to app update integer values configured by preferences as appropriate to prevent prefs from breaking app update. r=mhowell
authorRobert Strong <robert.bugzilla@gmail.com>
Thu, 10 Nov 2016 09:06:25 -0800
changeset 352103 8db63d1c00cf0fe27302bf06ba59faced4fceaec
parent 352102 487a5da50e56454385118bb8da2e9110f039e83d
child 352104 70b739aa2d6a03f41f567af7b4652bec9ed2bffb
push id6795
push userjlund@mozilla.com
push dateMon, 23 Jan 2017 14:19:46 +0000
treeherdermozilla-esr52@76101b503191 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmhowell
bugs1315505
milestone52.0a1
client code for Bug 1315505 - Add min and max values to app update integer values configured by preferences as appropriate to prevent prefs from breaking app update. r=mhowell
toolkit/components/timermanager/nsUpdateTimerManager.js
toolkit/mozapps/update/nsUpdateService.js
toolkit/mozapps/update/nsUpdateService.manifest
--- a/toolkit/components/timermanager/nsUpdateTimerManager.js
+++ b/toolkit/components/timermanager/nsUpdateTimerManager.js
@@ -88,22 +88,29 @@ TimerManager.prototype = {
     var minFirstInterval = 10000;
     switch (aTopic) {
       case "utm-test-init":
         // Enforce a minimum timer interval of 500 ms for tests and fall through
         // to profile-after-change to initialize the timer.
         minInterval = 500;
         minFirstInterval = 500;
       case "profile-after-change":
+        this._timerMinimumDelay = Math.max(1000 * getPref("getIntPref", PREF_APP_UPDATE_TIMERMINIMUMDELAY, 120),
+                                           minInterval);
+        // Prevent the timer delay between notifications to other consumers from
+        // being greater than 5 minutes which is 300000 milliseconds.
+        this._timerMinimumDelay = Math.min(this._timerMinimumDelay, 300000);
+        // Prevent the first interval from being less than the value of minFirstInterval
+        let firstInterval = Math.max(getPref("getIntPref", PREF_APP_UPDATE_TIMERFIRSTINTERVAL,
+                                             30000), minFirstInterval);
+        // Prevent the first interval from being greater than 2 minutes which is
+        // 120000 milliseconds.
+        firstInterval = Math.min(firstInterval, 120000);
         // Cancel the timer if it has already been initialized. This is primarily
         // for tests.
-        this._timerMinimumDelay = Math.max(1000 * getPref("getIntPref", PREF_APP_UPDATE_TIMERMINIMUMDELAY, 120),
-                                           minInterval);
-        let firstInterval = Math.max(getPref("getIntPref", PREF_APP_UPDATE_TIMERFIRSTINTERVAL,
-                                             this._timerMinimumDelay), minFirstInterval);
         this._canEnsureTimer = true;
         this._ensureTimer(firstInterval);
         break;
       case "xpcom-shutdown":
         Services.obs.removeObserver(this, "xpcom-shutdown");
 
         // Release everything we hold onto.
         this._cancelTimer();
@@ -166,28 +173,34 @@ TimerManager.prototype = {
     }
 
     var catMan = Cc["@mozilla.org/categorymanager;1"].
                  getService(Ci.nsICategoryManager);
     var entries = catMan.enumerateCategory(CATEGORY_UPDATE_TIMER);
     while (entries.hasMoreElements()) {
       let entry = entries.getNext().QueryInterface(Ci.nsISupportsCString).data;
       let value = catMan.getCategoryEntry(CATEGORY_UPDATE_TIMER, entry);
-      let [cid, method, timerID, prefInterval, defaultInterval] = value.split(",");
+      let [cid, method, timerID, prefInterval, defaultInterval, maxInterval] = value.split(",");
 
       defaultInterval = parseInt(defaultInterval);
       // cid and method are validated below when calling notify.
       if (!timerID || !defaultInterval || isNaN(defaultInterval)) {
         LOG("TimerManager:notify - update-timer category registered" +
             (cid ? " for " + cid : "") + " without required parameters - " +
              "skipping");
         continue;
       }
 
       let interval = getPref("getIntPref", prefInterval, defaultInterval);
+      // Allow the update-timer category to specify a maximum value to prevent
+      // values larger than desired.
+      maxInterval = parseInt(maxInterval);
+      if (maxInterval && !isNaN(maxInterval)) {
+        interval = Math.min(interval, maxInterval);
+      }
       let prefLastUpdate = PREF_APP_UPDATE_LASTUPDATETIME_FMT.replace(/%ID%/,
                                                                       timerID);
       // Initialize the last update time to 0 when the preference isn't set so
       // the timer will be notified soon after a new profile's first use.
       lastUpdateTime = getPref("getIntPref", prefLastUpdate, 0);
 
       // If the last update time is greater than the current time then reset
       // it to 0 and the timer manager will correct the value when it fires
--- a/toolkit/mozapps/update/nsUpdateService.js
+++ b/toolkit/mozapps/update/nsUpdateService.js
@@ -22,22 +22,20 @@ const UPDATESERVICE_CONTRACTID = "@mozil
 const PREF_APP_UPDATE_ALTWINDOWTYPE        = "app.update.altwindowtype";
 const PREF_APP_UPDATE_AUTO                 = "app.update.auto";
 const PREF_APP_UPDATE_BACKGROUNDINTERVAL   = "app.update.download.backgroundInterval";
 const PREF_APP_UPDATE_BACKGROUNDERRORS     = "app.update.backgroundErrors";
 const PREF_APP_UPDATE_BACKGROUNDMAXERRORS  = "app.update.backgroundMaxErrors";
 const PREF_APP_UPDATE_CANCELATIONS         = "app.update.cancelations";
 const PREF_APP_UPDATE_CANCELATIONS_OSX     = "app.update.cancelations.osx";
 const PREF_APP_UPDATE_CANCELATIONS_OSX_MAX = "app.update.cancelations.osx.max";
-const PREF_APP_UPDATE_CERT_REQUIREBUILTIN  = "app.update.cert.requireBuiltIn";
 const PREF_APP_UPDATE_ELEVATE_NEVER        = "app.update.elevate.never";
 const PREF_APP_UPDATE_ELEVATE_VERSION      = "app.update.elevate.version";
 const PREF_APP_UPDATE_ENABLED              = "app.update.enabled";
 const PREF_APP_UPDATE_IDLETIME             = "app.update.idletime";
-const PREF_APP_UPDATE_INTERVAL             = "app.update.interval";
 const PREF_APP_UPDATE_LOG                  = "app.update.log";
 const PREF_APP_UPDATE_NOTIFIEDUNSUPPORTED  = "app.update.notifiedUnsupported";
 const PREF_APP_UPDATE_POSTUPDATE           = "app.update.postupdate";
 const PREF_APP_UPDATE_PROMPTWAITTIME       = "app.update.promptWaitTime";
 const PREF_APP_UPDATE_SERVICE_ENABLED      = "app.update.service.enabled";
 const PREF_APP_UPDATE_SERVICE_ERRORS       = "app.update.service.errors";
 const PREF_APP_UPDATE_SERVICE_MAXERRORS    = "app.update.service.maxErrors";
 const PREF_APP_UPDATE_SILENT               = "app.update.silent";
@@ -51,24 +49,21 @@ const PREF_APP_UPDATE_URL_OVERRIDE      
 const PREFBRANCH_APP_UPDATE_NEVER = "app.update.never.";
 
 const URI_BRAND_PROPERTIES      = "chrome://branding/locale/brand.properties";
 const URI_UPDATE_HISTORY_DIALOG = "chrome://mozapps/content/update/history.xul";
 const URI_UPDATE_NS             = "http://www.mozilla.org/2005/app-update";
 const URI_UPDATE_PROMPT_DIALOG  = "chrome://mozapps/content/update/updates.xul";
 const URI_UPDATES_PROPERTIES    = "chrome://mozapps/locale/update/updates.properties";
 
-const KEY_GRED            = "GreD";
 const KEY_UPDROOT         = "UpdRootD";
 const KEY_EXECUTABLE      = "XREExeF";
 // Gonk only
 const KEY_UPDATE_ARCHIVE_DIR = "UpdArchD";
 
-const DIR_UPDATED         = "updated";
-const DIR_UPDATED_APP     = "Updated.app";
 const DIR_UPDATES         = "updates";
 
 const FILE_ACTIVE_UPDATE_XML = "active-update.xml";
 const FILE_BACKUP_UPDATE_LOG = "backup-update.log";
 const FILE_LAST_UPDATE_LOG   = "last-update.log";
 const FILE_UPDATES_XML       = "updates.xml";
 const FILE_UPDATE_LINK       = "update.link";
 const FILE_UPDATE_LOG        = "update.log";
@@ -197,17 +192,16 @@ const APPID_TO_TOPIC = {
   // Fennec
   "{aa3c5121-dab2-40e2-81ca-7ea25febc110}": "sessionstore-windows-restored",
   // Thunderbird
   "{3550f703-e582-4d05-9a08-453d09bdfdc6}": "mail-startup-done",
   // Instantbird
   "{33cb9019-c295-46dd-be21-8c4936574bee}": "xul-window-visible",
 };
 
-var gLocale = null;
 var gUpdateMutexHandle = null;
 
 // Gonk only
 var gSDCardMountLock = null;
 
 // Gonk only
 XPCOMUtils.defineLazyGetter(this, "gExtStorage", function aus_gExtStorage() {
   if (AppConstants.platform != "gonk") {
@@ -552,17 +546,17 @@ XPCOMUtils.defineLazyGetter(this, "gCanS
 });
 
 /**
  * Whether or not the application can stage an update.
  *
  * @return true if updates can be staged.
  */
 function getCanStageUpdates() {
-  // If background updates are disabled, then just bail out!
+  // If staging updates are disabled, then just bail out!
   if (!getPref("getBoolPref", PREF_APP_UPDATE_STAGING_ENABLED, false)) {
     LOG("getCanStageUpdates - staging updates is disabled by preference " +
         PREF_APP_UPDATE_STAGING_ENABLED);
     return false;
   }
 
   if (AppConstants.platform == "win" && shouldUseService()) {
     // No need to perform directory write checks, the maintenance service will
@@ -671,28 +665,16 @@ function binaryToHex(input) {
  *          specified by |key|
  * @return  nsIFile object for the location specified.
  */
 function getUpdateDirCreate(pathArray) {
   return FileUtils.getDir(KEY_UPDROOT, pathArray, true);
 }
 
 /**
- * Gets the specified directory at the specified hierarchy under the
- * update root directory and without creating it if it doesn't exist.
- * @param   pathArray
- *          An array of path components to locate beneath the directory
- *          specified by |key|
- * @return  nsIFile object for the location specified.
- */
-function getUpdateDirNoCreate(pathArray) {
-  return FileUtils.getDir(KEY_UPDROOT, pathArray, false);
-}
-
-/**
  * Gets the application base directory.
  *
  * @return  nsIFile object for the application base directory.
  */
 function getAppBaseDir() {
   return Services.dirsvc.get(KEY_EXECUTABLE, Ci.nsIFile).parent;
 }
 
@@ -1219,17 +1201,19 @@ function handleUpdateFailure(update, err
       let osxCancelations = getPref("getIntPref",
                                   PREF_APP_UPDATE_CANCELATIONS_OSX, 0);
       osxCancelations++;
       Services.prefs.setIntPref(PREF_APP_UPDATE_CANCELATIONS_OSX,
                                 osxCancelations);
       let maxCancels = getPref("getIntPref",
                                PREF_APP_UPDATE_CANCELATIONS_OSX_MAX,
                                DEFAULT_CANCELATIONS_OSX_MAX);
-      if (osxCancelations >= DEFAULT_CANCELATIONS_OSX_MAX) {
+      // Prevent the preference from setting a value greater than 5.
+      maxCancels = Math.min(maxCancels, 5);
+      if (osxCancelations >= maxCancels) {
         cleanupActiveUpdate();
       } else {
         writeStatusFile(getUpdatesDir(),
                         update.state = STATE_PENDING_ELEVATE);
       }
       update.statusText = gUpdateBundle.GetStringFromName("elevationFailure");
       update.QueryInterface(Ci.nsIWritablePropertyBag);
       update.setProperty("patchingFailed", "elevationFailure");
@@ -1251,17 +1235,18 @@ function handleUpdateFailure(update, err
 
   // Replace with Array.prototype.includes when it has stabilized.
   if (SERVICE_ERRORS.indexOf(update.errorCode) != -1) {
     var failCount = getPref("getIntPref",
                             PREF_APP_UPDATE_SERVICE_ERRORS, 0);
     var maxFail = getPref("getIntPref",
                           PREF_APP_UPDATE_SERVICE_MAXERRORS,
                           DEFAULT_SERVICE_MAX_ERRORS);
-
+    // Prevent the preference from setting a value greater than 10.
+    maxFail = Math.min(maxFail, 10);
     // As a safety, when the service reaches maximum failures, it will
     // disable itself and fallback to using the normal update mechanism
     // without the service.
     if (failCount >= maxFail) {
       Services.prefs.setBoolPref(PREF_APP_UPDATE_SERVICE_ENABLED, false);
       Services.prefs.clearUserPref(PREF_APP_UPDATE_SERVICE_ERRORS);
     } else {
       failCount++;
@@ -1615,17 +1600,17 @@ function Update(update) {
       }
     }
   }
 
   if (!this.displayVersion) {
     this.displayVersion = this.appVersion;
   }
 
-  // Don't allow the background interval to be greater than 10 minutes.
+  // Don't allow the background download interval to be greater than 10 minutes.
   this.backgroundInterval = Math.min(this.backgroundInterval, 600);
 
   // The Update Name is either the string provided by the <update> element, or
   // the string: "<App Name> <Update App Version>"
   var name = "";
   if (update.hasAttribute("name")) {
     name = update.getAttribute("name");
   } else {
@@ -2175,18 +2160,18 @@ UpdateService.prototype = {
     }
 
     // Send the error code to telemetry
     AUSTLMY.pingCheckExError(this._pingSuffix, update.errorCode);
     update.errorCode = BACKGROUNDCHECK_MULTIPLE_FAILURES;
     let errCount = getPref("getIntPref", PREF_APP_UPDATE_BACKGROUNDERRORS, 0);
     errCount++;
     Services.prefs.setIntPref(PREF_APP_UPDATE_BACKGROUNDERRORS, errCount);
-    let maxErrors = getPref("getIntPref", PREF_APP_UPDATE_BACKGROUNDMAXERRORS,
-                            10);
+    // Don't allow the preference to set a value greater than 20 for max errors.
+    let maxErrors = Math.min(getPref("getIntPref", PREF_APP_UPDATE_BACKGROUNDMAXERRORS, 10), 20);
 
     if (errCount >= maxErrors) {
       let prompter = Cc["@mozilla.org/updates/update-prompt;1"].
                      createInstance(Ci.nsIUpdatePrompt);
       prompter.showUpdateError(update);
       AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_GENERAL_ERROR_PROMPT);
     } else {
       AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_GENERAL_ERROR_SILENT);
@@ -2470,18 +2455,17 @@ UpdateService.prototype = {
                 PREF_APP_UPDATE_CANCELATIONS_OSX)) {
             Services.prefs.clearUserPref(PREF_APP_UPDATE_CANCELATIONS_OSX);
           }
           if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_ELEVATE_NEVER)) {
             Services.prefs.clearUserPref(PREF_APP_UPDATE_ELEVATE_NEVER);
           }
         } else {
           let numCancels = getPref("getIntPref",
-                                   PREF_APP_UPDATE_CANCELATIONS_OSX,
-                                   0);
+                                   PREF_APP_UPDATE_CANCELATIONS_OSX, 0);
           let rejectedVersion = getPref("getCharPref",
                                         PREF_APP_UPDATE_ELEVATE_NEVER, "");
           let maxCancels = getPref("getIntPref",
                                    PREF_APP_UPDATE_CANCELATIONS_OSX_MAX,
                                    DEFAULT_CANCELATIONS_OSX_MAX);
           if (numCancels >= maxCancels) {
             LOG("UpdateService:selectUpdate - the user requires elevation to " +
                 "install this update, but the user has exceeded the max " +
@@ -3117,17 +3101,16 @@ UpdateManager.prototype = {
   /**
    * See nsIUpdateService.idl
    */
   refreshUpdateStatus: function UM_refreshUpdateStatus() {
     var update = this._activeUpdate;
     if (!update) {
       return;
     }
-    var updateSucceeded = true;
     var status = readStatusFile(getUpdatesDir());
     pingStateAndStatusCodes(update, false, status);
     var parts = status.split(":");
     update.state = parts[0];
     if (update.state == STATE_FAILED && parts[1]) {
       update.errorCode = parseInt(parts[1]);
     }
     let um = Cc["@mozilla.org/updates/update-manager;1"].
@@ -3137,17 +3120,16 @@ UpdateManager.prototype = {
     um.saveUpdates();
 
     // Rotate the update logs so the update log isn't removed if a complete
     // update is downloaded. By passing false the patch directory won't be
     // removed.
     cleanUpUpdatesDir(false);
 
     if (update.state == STATE_FAILED && parts[1]) {
-      updateSucceeded = false;
       if (!handleUpdateFailure(update, parts[1])) {
         handleFallbackToCompleteUpdate(update, true);
       }
     }
     if (update.state == STATE_APPLIED && shouldUseService()) {
       writeStatusFile(getUpdatesDir(), update.state = STATE_APPLIED_SERVICE);
     }
 
@@ -3292,19 +3274,17 @@ Checker.prototype = {
     Services.obs.notifyObservers(null, "update-check-start", null);
 
     var url = this.getUpdateURL(force);
     if (!url || (!this.enabled && !force))
       return;
 
     this._request = new XMLHttpRequest();
     this._request.open("GET", url, true);
-    var allowNonBuiltIn = !getPref("getBoolPref",
-                                   PREF_APP_UPDATE_CERT_REQUIREBUILTIN, true);
-    this._request.channel.notificationCallbacks = new gCertUtils.BadCertHandler(allowNonBuiltIn);
+    this._request.channel.notificationCallbacks = new gCertUtils.BadCertHandler(false);
     // Prevent the request from reading from the cache.
     this._request.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
     // Prevent the request from writing to the cache.
     this._request.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
 
     this._request.overrideMimeType("text/xml");
     // The Cache-Control header is only interpreted by proxies and the
     // final destination. It does not help if a resource is already
@@ -4025,32 +4005,36 @@ Downloader.prototype = {
    * When data transfer ceases
    * @param   request
    *          The nsIRequest object for the transfer
    * @param   context
    *          Additional data
    * @param   status
    *          Status code containing the reason for the cessation.
    */
-  onStopRequest: function  Downloader_onStopRequest(request, context, status) {
+  onStopRequest: function Downloader_onStopRequest(request, context, status) {
     if (request instanceof Ci.nsIIncrementalDownload)
       LOG("Downloader:onStopRequest - original URI spec: " + request.URI.spec +
           ", final URI spec: " + request.finalURI.spec + ", status: " + status);
 
     // XXX ehsan shouldShowPrompt should always be false here.
     // But what happens when there is already a UI showing?
     var state = this._patch.state;
     var shouldShowPrompt = false;
     var shouldRegisterOnlineObserver = false;
     var shouldRetrySoon = false;
     var deleteActiveUpdate = false;
     var retryTimeout = getPref("getIntPref", PREF_APP_UPDATE_SOCKET_RETRYTIMEOUT,
                                DEFAULT_SOCKET_RETRYTIMEOUT);
+    // Prevent the preference from setting a value greater than 10000.
+    retryTimeout = Math.min(retryTimeout, 10000);
     var maxFail = getPref("getIntPref", PREF_APP_UPDATE_SOCKET_MAXERRORS,
                           DEFAULT_SOCKET_MAX_ERRORS);
+    // Prevent the preference from setting a value greater than 20.
+    maxFail = Math.min(maxFail, 20);
     LOG("Downloader:onStopRequest - status: " + status + ", " +
         "current fail: " + this.updateService._consecutiveSocketErrors + ", " +
         "max fail: " + maxFail + ", " + "retryTimeout: " + retryTimeout);
     if (Components.isSuccessCode(status)) {
       if (this._verifyDownload()) {
         if (shouldUseService()) {
           state = STATE_PENDING_SERVICE;
         } else if (getElevationRequired()) {
@@ -4462,18 +4446,18 @@ UpdatePrompt.prototype = {
       }
     };
 
     // bug 534090 - show the UI for update available notifications when the
     // the system has been idle for at least IDLE_TIME.
     if (page == "updatesavailable") {
       var idleService = Cc["@mozilla.org/widget/idleservice;1"].
                         getService(Ci.nsIIdleService);
-
-      const IDLE_TIME = getPref("getIntPref", PREF_APP_UPDATE_IDLETIME, 60);
+      // Don't allow the preference to set a value greater than 600 seconds for the idle time.
+      const IDLE_TIME = Math.min(getPref("getIntPref", PREF_APP_UPDATE_IDLETIME, 60), 600);
       if (idleService.idleTime / 1000 >= IDLE_TIME) {
         this._showUI(parent, uri, features, name, page, update);
         return;
       }
     }
 
     observer.service = Services.obs;
     observer.service.addObserver(observer, "quit-application", false);
@@ -4507,17 +4491,18 @@ UpdatePrompt.prototype = {
    *          An update to pass to the UI in the window arguments.
    *          Can be null
    */
   _showUIWhenIdle: function UP__showUIWhenIdle(parent, uri, features, name,
                                                page, update) {
     var idleService = Cc["@mozilla.org/widget/idleservice;1"].
                       getService(Ci.nsIIdleService);
 
-    const IDLE_TIME = getPref("getIntPref", PREF_APP_UPDATE_IDLETIME, 60);
+    // Don't allow the preference to set a value greater than 600 seconds for the idle time.
+    const IDLE_TIME = Math.min(getPref("getIntPref", PREF_APP_UPDATE_IDLETIME, 60), 600);
     if (idleService.idleTime / 1000 >= IDLE_TIME) {
       this._showUI(parent, uri, features, name, page, update);
     } else {
       var observer = {
         updatePrompt: this,
         observe: function (aSubject, aTopic, aData) {
           switch (aTopic) {
             case "idle":
--- a/toolkit/mozapps/update/nsUpdateService.manifest
+++ b/toolkit/mozapps/update/nsUpdateService.manifest
@@ -1,11 +1,11 @@
 component {B3C290A6-3943-4B89-8BBE-C01EB7B3B311} nsUpdateService.js
 contract @mozilla.org/updates/update-service;1 {B3C290A6-3943-4B89-8BBE-C01EB7B3B311}
-category update-timer nsUpdateService @mozilla.org/updates/update-service;1,getService,background-update-timer,app.update.interval,86400
+category update-timer nsUpdateService @mozilla.org/updates/update-service;1,getService,background-update-timer,app.update.interval,43200,86400
 component {093C2356-4843-4C65-8709-D7DBCBBE7DFB} nsUpdateService.js
 contract @mozilla.org/updates/update-manager;1 {093C2356-4843-4C65-8709-D7DBCBBE7DFB}
 component {898CDC9B-E43F-422F-9CC4-2F6291B415A3} nsUpdateService.js
 contract @mozilla.org/updates/update-checker;1 {898CDC9B-E43F-422F-9CC4-2F6291B415A3}
 component {27ABA825-35B5-4018-9FDD-F99250A0E722} nsUpdateService.js
 contract @mozilla.org/updates/update-prompt;1 {27ABA825-35B5-4018-9FDD-F99250A0E722}
 component {e43b0010-04ba-4da6-b523-1f92580bc150} nsUpdateServiceStub.js
 contract @mozilla.org/updates/update-service-stub;1 {e43b0010-04ba-4da6-b523-1f92580bc150}