browser/base/content/sanitize.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 # -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
     2 # This Source Code Form is subject to the terms of the Mozilla Public
     3 # License, v. 2.0. If a copy of the MPL was not distributed with this
     4 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
     6 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
     7 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
     8                                   "resource://gre/modules/PlacesUtils.jsm");
     9 XPCOMUtils.defineLazyModuleGetter(this, "FormHistory",
    10                                   "resource://gre/modules/FormHistory.jsm");
    11 XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
    12                                   "resource://gre/modules/Downloads.jsm");
    13 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
    14                                   "resource://gre/modules/Promise.jsm");
    15 XPCOMUtils.defineLazyModuleGetter(this, "Task",
    16                                   "resource://gre/modules/Task.jsm");
    17 XPCOMUtils.defineLazyModuleGetter(this, "DownloadsCommon",
    18                                   "resource:///modules/DownloadsCommon.jsm");
    20 function Sanitizer() {}
    21 Sanitizer.prototype = {
    22   // warning to the caller: this one may raise an exception (e.g. bug #265028)
    23   clearItem: function (aItemName)
    24   {
    25     if (this.items[aItemName].canClear)
    26       this.items[aItemName].clear();
    27   },
    29   canClearItem: function (aItemName, aCallback, aArg)
    30   {
    31     let canClear = this.items[aItemName].canClear;
    32     if (typeof canClear == "function") {
    33       canClear(aCallback, aArg);
    34       return false;
    35     }
    37     aCallback(aItemName, canClear, aArg);
    38     return canClear;
    39   },
    41   prefDomain: "",
    43   getNameFromPreference: function (aPreferenceName)
    44   {
    45     return aPreferenceName.substr(this.prefDomain.length);
    46   },
    48   /**
    49    * Deletes privacy sensitive data in a batch, according to user preferences.
    50    * Returns a promise which is resolved if no errors occurred.  If an error
    51    * occurs, a message is reported to the console and all other items are still
    52    * cleared before the promise is finally rejected.
    53    */
    54   sanitize: function ()
    55   {
    56     var deferred = Promise.defer();
    57     var psvc = Components.classes["@mozilla.org/preferences-service;1"]
    58                          .getService(Components.interfaces.nsIPrefService);
    59     var branch = psvc.getBranch(this.prefDomain);
    60     var seenError = false;
    62     // Cache the range of times to clear
    63     if (this.ignoreTimespan)
    64       var range = null;  // If we ignore timespan, clear everything
    65     else
    66       range = this.range || Sanitizer.getClearRange();
    68     let itemCount = Object.keys(this.items).length;
    69     let onItemComplete = function() {
    70       if (!--itemCount) {
    71         seenError ? deferred.reject() : deferred.resolve();
    72       }
    73     };
    74     for (var itemName in this.items) {
    75       let item = this.items[itemName];
    76       item.range = range;
    77       if ("clear" in item && branch.getBoolPref(itemName)) {
    78         let clearCallback = (itemName, aCanClear) => {
    79           // Some of these clear() may raise exceptions (see bug #265028)
    80           // to sanitize as much as possible, we catch and store them,
    81           // rather than fail fast.
    82           // Callers should check returned errors and give user feedback
    83           // about items that could not be sanitized
    84           let item = this.items[itemName];
    85           try {
    86             if (aCanClear)
    87               item.clear();
    88           } catch(er) {
    89             seenError = true;
    90             Components.utils.reportError("Error sanitizing " + itemName +
    91                                          ": " + er + "\n");
    92           }
    93           onItemComplete();
    94         };
    95         this.canClearItem(itemName, clearCallback);
    96       } else {
    97         onItemComplete();
    98       }
    99     }
   101     return deferred.promise;
   102   },
   104   // Time span only makes sense in certain cases.  Consumers who want
   105   // to only clear some private data can opt in by setting this to false,
   106   // and can optionally specify a specific range.  If timespan is not ignored,
   107   // and range is not set, sanitize() will use the value of the timespan
   108   // pref to determine a range
   109   ignoreTimespan : true,
   110   range : null,
   112   items: {
   113     cache: {
   114       clear: function ()
   115       {
   116         var cache = Cc["@mozilla.org/netwerk/cache-storage-service;1"].
   117                     getService(Ci.nsICacheStorageService);
   118         try {
   119           // Cache doesn't consult timespan, nor does it have the
   120           // facility for timespan-based eviction.  Wipe it.
   121           cache.clear();
   122         } catch(er) {}
   124         var imageCache = Cc["@mozilla.org/image/tools;1"].
   125                          getService(Ci.imgITools).getImgCacheForDocument(null);
   126         try {
   127           imageCache.clearCache(false); // true=chrome, false=content
   128         } catch(er) {}
   129       },
   131       get canClear()
   132       {
   133         return true;
   134       }
   135     },
   137     cookies: {
   138       clear: function ()
   139       {
   140         var cookieMgr = Components.classes["@mozilla.org/cookiemanager;1"]
   141                                   .getService(Ci.nsICookieManager);
   142         if (this.range) {
   143           // Iterate through the cookies and delete any created after our cutoff.
   144           var cookiesEnum = cookieMgr.enumerator;
   145           while (cookiesEnum.hasMoreElements()) {
   146             var cookie = cookiesEnum.getNext().QueryInterface(Ci.nsICookie2);
   148             if (cookie.creationTime > this.range[0])
   149               // This cookie was created after our cutoff, clear it
   150               cookieMgr.remove(cookie.host, cookie.name, cookie.path, false);
   151           }
   152         }
   153         else {
   154           // Remove everything
   155           cookieMgr.removeAll();
   156         }
   158         // Clear plugin data.
   159         const phInterface = Ci.nsIPluginHost;
   160         const FLAG_CLEAR_ALL = phInterface.FLAG_CLEAR_ALL;
   161         let ph = Cc["@mozilla.org/plugin/host;1"].getService(phInterface);
   163         // Determine age range in seconds. (-1 means clear all.) We don't know
   164         // that this.range[1] is actually now, so we compute age range based
   165         // on the lower bound. If this.range results in a negative age, do
   166         // nothing.
   167         let age = this.range ? (Date.now() / 1000 - this.range[0] / 1000000)
   168                              : -1;
   169         if (!this.range || age >= 0) {
   170           let tags = ph.getPluginTags();
   171           for (let i = 0; i < tags.length; i++) {
   172             try {
   173               ph.clearSiteData(tags[i], null, FLAG_CLEAR_ALL, age);
   174             } catch (e) {
   175               // If the plugin doesn't support clearing by age, clear everything.
   176               if (e.result == Components.results.
   177                     NS_ERROR_PLUGIN_TIME_RANGE_NOT_SUPPORTED) {
   178                 try {
   179                   ph.clearSiteData(tags[i], null, FLAG_CLEAR_ALL, -1);
   180                 } catch (e) {
   181                   // Ignore errors from the plugin
   182                 }
   183               }
   184             }
   185           }
   186         }
   187       },
   189       get canClear()
   190       {
   191         return true;
   192       }
   193     },
   195     offlineApps: {
   196       clear: function ()
   197       {
   198         Components.utils.import("resource:///modules/offlineAppCache.jsm");
   199         OfflineAppCacheHelper.clear();
   200       },
   202       get canClear()
   203       {
   204         return true;
   205       }
   206     },
   208     history: {
   209       clear: function ()
   210       {
   211         if (this.range)
   212           PlacesUtils.history.removeVisitsByTimeframe(this.range[0], this.range[1]);
   213         else
   214           PlacesUtils.history.removeAllPages();
   216         try {
   217           var os = Components.classes["@mozilla.org/observer-service;1"]
   218                              .getService(Components.interfaces.nsIObserverService);
   219           os.notifyObservers(null, "browser:purge-session-history", "");
   220         }
   221         catch (e) { }
   223         try {
   224           var seer = Components.classes["@mozilla.org/network/seer;1"]
   225                                .getService(Components.interfaces.nsINetworkSeer);
   226           seer.reset();
   227         } catch (e) { }
   228       },
   230       get canClear()
   231       {
   232         // bug 347231: Always allow clearing history due to dependencies on
   233         // the browser:purge-session-history notification. (like error console)
   234         return true;
   235       }
   236     },
   238     formdata: {
   239       clear: function ()
   240       {
   241         // Clear undo history of all searchBars
   242         var windowManager = Components.classes['@mozilla.org/appshell/window-mediator;1']
   243                                       .getService(Components.interfaces.nsIWindowMediator);
   244         var windows = windowManager.getEnumerator("navigator:browser");
   245         while (windows.hasMoreElements()) {
   246           let currentWindow = windows.getNext();
   247           let currentDocument = currentWindow.document;
   248           let searchBar = currentDocument.getElementById("searchbar");
   249           if (searchBar)
   250             searchBar.textbox.reset();
   251           let tabBrowser = currentWindow.gBrowser;
   252           for (let tab of tabBrowser.tabs) {
   253             if (tabBrowser.isFindBarInitialized(tab))
   254               tabBrowser.getFindBar(tab).clear();
   255           }
   256           // Clear any saved find value
   257           tabBrowser._lastFindValue = "";
   258         }
   260         let change = { op: "remove" };
   261         if (this.range) {
   262           [ change.firstUsedStart, change.firstUsedEnd ] = this.range;
   263         }
   264         FormHistory.update(change);
   265       },
   267       canClear : function(aCallback, aArg)
   268       {
   269         var windowManager = Components.classes['@mozilla.org/appshell/window-mediator;1']
   270                                       .getService(Components.interfaces.nsIWindowMediator);
   271         var windows = windowManager.getEnumerator("navigator:browser");
   272         while (windows.hasMoreElements()) {
   273           let currentWindow = windows.getNext();
   274           let currentDocument = currentWindow.document;
   275           let searchBar = currentDocument.getElementById("searchbar");
   276           if (searchBar) {
   277             let transactionMgr = searchBar.textbox.editor.transactionManager;
   278             if (searchBar.value ||
   279                 transactionMgr.numberOfUndoItems ||
   280                 transactionMgr.numberOfRedoItems) {
   281               aCallback("formdata", true, aArg);
   282               return false;
   283             }
   284           }
   285           let tabBrowser = currentWindow.gBrowser;
   286           let findBarCanClear = Array.some(tabBrowser.tabs, function (aTab) {
   287             return tabBrowser.isFindBarInitialized(aTab) &&
   288                    tabBrowser.getFindBar(aTab).canClear;
   289           });
   290           if (findBarCanClear) {
   291             aCallback("formdata", true, aArg);
   292             return false;
   293           }
   294         }
   296         let count = 0;
   297         let countDone = {
   298           handleResult : function(aResult) count = aResult,
   299           handleError : function(aError) Components.utils.reportError(aError),
   300           handleCompletion :
   301             function(aReason) { aCallback("formdata", aReason == 0 && count > 0, aArg); }
   302         };
   303         FormHistory.count({}, countDone);
   304         return false;
   305       }
   306     },
   308     downloads: {
   309       clear: function ()
   310       {
   311         Task.spawn(function () {
   312           let filterByTime = null;
   313           if (this.range) {
   314             // Convert microseconds back to milliseconds for date comparisons.
   315             let rangeBeginMs = this.range[0] / 1000;
   316             let rangeEndMs = this.range[1] / 1000;
   317             filterByTime = download => download.startTime >= rangeBeginMs &&
   318                                        download.startTime <= rangeEndMs;
   319           }
   321           // Clear all completed/cancelled downloads
   322           let list = yield Downloads.getList(Downloads.ALL);
   323           list.removeFinished(filterByTime);
   324         }.bind(this)).then(null, Components.utils.reportError);
   325       },
   327       canClear : function(aCallback, aArg)
   328       {
   329         aCallback("downloads", true, aArg);
   330         return false;
   331       }
   332     },
   334     passwords: {
   335       clear: function ()
   336       {
   337         var pwmgr = Components.classes["@mozilla.org/login-manager;1"]
   338                               .getService(Components.interfaces.nsILoginManager);
   339         // Passwords are timeless, and don't respect the timeSpan setting
   340         pwmgr.removeAllLogins();
   341       },
   343       get canClear()
   344       {
   345         var pwmgr = Components.classes["@mozilla.org/login-manager;1"]
   346                               .getService(Components.interfaces.nsILoginManager);
   347         var count = pwmgr.countLogins("", "", ""); // count all logins
   348         return (count > 0);
   349       }
   350     },
   352     sessions: {
   353       clear: function ()
   354       {
   355         // clear all auth tokens
   356         var sdr = Components.classes["@mozilla.org/security/sdr;1"]
   357                             .getService(Components.interfaces.nsISecretDecoderRing);
   358         sdr.logoutAndTeardown();
   360         // clear FTP and plain HTTP auth sessions
   361         var os = Components.classes["@mozilla.org/observer-service;1"]
   362                            .getService(Components.interfaces.nsIObserverService);
   363         os.notifyObservers(null, "net:clear-active-logins", null);
   364       },
   366       get canClear()
   367       {
   368         return true;
   369       }
   370     },
   372     siteSettings: {
   373       clear: function ()
   374       {
   375         // Clear site-specific permissions like "Allow this site to open popups"
   376         var pm = Components.classes["@mozilla.org/permissionmanager;1"]
   377                            .getService(Components.interfaces.nsIPermissionManager);
   378         pm.removeAll();
   380         // Clear site-specific settings like page-zoom level
   381         var cps = Components.classes["@mozilla.org/content-pref/service;1"]
   382                             .getService(Components.interfaces.nsIContentPrefService2);
   383         cps.removeAllDomains(null);
   385         // Clear "Never remember passwords for this site", which is not handled by
   386         // the permission manager
   387         var pwmgr = Components.classes["@mozilla.org/login-manager;1"]
   388                               .getService(Components.interfaces.nsILoginManager);
   389         var hosts = pwmgr.getAllDisabledHosts();
   390         for each (var host in hosts) {
   391           pwmgr.setLoginSavingEnabled(host, true);
   392         }
   393       },
   395       get canClear()
   396       {
   397         return true;
   398       }
   399     }
   400   }
   401 };
   405 // "Static" members
   406 Sanitizer.prefDomain          = "privacy.sanitize.";
   407 Sanitizer.prefShutdown        = "sanitizeOnShutdown";
   408 Sanitizer.prefDidShutdown     = "didShutdownSanitize";
   410 // Time span constants corresponding to values of the privacy.sanitize.timeSpan
   411 // pref.  Used to determine how much history to clear, for various items
   412 Sanitizer.TIMESPAN_EVERYTHING = 0;
   413 Sanitizer.TIMESPAN_HOUR       = 1;
   414 Sanitizer.TIMESPAN_2HOURS     = 2;
   415 Sanitizer.TIMESPAN_4HOURS     = 3;
   416 Sanitizer.TIMESPAN_TODAY      = 4;
   418 // Return a 2 element array representing the start and end times,
   419 // in the uSec-since-epoch format that PRTime likes.  If we should
   420 // clear everything, return null.  Use ts if it is defined; otherwise
   421 // use the timeSpan pref.
   422 Sanitizer.getClearRange = function (ts) {
   423   if (ts === undefined)
   424     ts = Sanitizer.prefs.getIntPref("timeSpan");
   425   if (ts === Sanitizer.TIMESPAN_EVERYTHING)
   426     return null;
   428   // PRTime is microseconds while JS time is milliseconds
   429   var endDate = Date.now() * 1000;
   430   switch (ts) {
   431     case Sanitizer.TIMESPAN_HOUR :
   432       var startDate = endDate - 3600000000; // 1*60*60*1000000
   433       break;
   434     case Sanitizer.TIMESPAN_2HOURS :
   435       startDate = endDate - 7200000000; // 2*60*60*1000000
   436       break;
   437     case Sanitizer.TIMESPAN_4HOURS :
   438       startDate = endDate - 14400000000; // 4*60*60*1000000
   439       break;
   440     case Sanitizer.TIMESPAN_TODAY :
   441       var d = new Date();  // Start with today
   442       d.setHours(0);      // zero us back to midnight...
   443       d.setMinutes(0);
   444       d.setSeconds(0);
   445       startDate = d.valueOf() * 1000; // convert to epoch usec
   446       break;
   447     default:
   448       throw "Invalid time span for clear private data: " + ts;
   449   }
   450   return [startDate, endDate];
   451 };
   453 Sanitizer._prefs = null;
   454 Sanitizer.__defineGetter__("prefs", function()
   455 {
   456   return Sanitizer._prefs ? Sanitizer._prefs
   457     : Sanitizer._prefs = Components.classes["@mozilla.org/preferences-service;1"]
   458                          .getService(Components.interfaces.nsIPrefService)
   459                          .getBranch(Sanitizer.prefDomain);
   460 });
   462 // Shows sanitization UI
   463 Sanitizer.showUI = function(aParentWindow)
   464 {
   465   var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
   466                      .getService(Components.interfaces.nsIWindowWatcher);
   467 #ifdef XP_MACOSX
   468   ww.openWindow(null, // make this an app-modal window on Mac
   469 #else
   470   ww.openWindow(aParentWindow,
   471 #endif
   472                 "chrome://browser/content/sanitize.xul",
   473                 "Sanitize",
   474                 "chrome,titlebar,dialog,centerscreen,modal",
   475                 null);
   476 };
   478 /**
   479  * Deletes privacy sensitive data in a batch, optionally showing the
   480  * sanitize UI, according to user preferences
   481  */
   482 Sanitizer.sanitize = function(aParentWindow)
   483 {
   484   Sanitizer.showUI(aParentWindow);
   485 };
   487 Sanitizer.onStartup = function()
   488 {
   489   // we check for unclean exit with pending sanitization
   490   Sanitizer._checkAndSanitize();
   491 };
   493 Sanitizer.onShutdown = function()
   494 {
   495   // we check if sanitization is needed and perform it
   496   Sanitizer._checkAndSanitize();
   497 };
   499 // this is called on startup and shutdown, to perform pending sanitizations
   500 Sanitizer._checkAndSanitize = function()
   501 {
   502   const prefs = Sanitizer.prefs;
   503   if (prefs.getBoolPref(Sanitizer.prefShutdown) &&
   504       !prefs.prefHasUserValue(Sanitizer.prefDidShutdown)) {
   505     // this is a shutdown or a startup after an unclean exit
   506     var s = new Sanitizer();
   507     s.prefDomain = "privacy.clearOnShutdown.";
   508     s.sanitize().then(function() {
   509       prefs.setBoolPref(Sanitizer.prefDidShutdown, true);
   510     });
   511   }
   512 };

mercurial