summaryrefslogtreecommitdiffstats
path: root/webbrowser/base/content/sanitize.js
diff options
context:
space:
mode:
Diffstat (limited to 'webbrowser/base/content/sanitize.js')
-rw-r--r--webbrowser/base/content/sanitize.js534
1 files changed, 534 insertions, 0 deletions
diff --git a/webbrowser/base/content/sanitize.js b/webbrowser/base/content/sanitize.js
new file mode 100644
index 0000000..b4d13d8
--- /dev/null
+++ b/webbrowser/base/content/sanitize.js
@@ -0,0 +1,534 @@
+# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FormHistory",
+ "resource://gre/modules/FormHistory.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
+ "resource://gre/modules/Downloads.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "console",
+ "resource://gre/modules/Console.jsm");
+
+function Sanitizer() {}
+Sanitizer.prototype = {
+ // warning to the caller: this one may raise an exception (e.g. bug #265028)
+ clearItem: function (aItemName)
+ {
+ if (this.items[aItemName].canClear)
+ this.items[aItemName].clear();
+ },
+
+ canClearItem: function (aItemName, aCallback, aArg)
+ {
+ let canClear = this.items[aItemName].canClear;
+ if (typeof canClear == "function") {
+ canClear(aCallback, aArg);
+ return false;
+ }
+
+ aCallback(aItemName, canClear, aArg);
+ return canClear;
+ },
+
+ prefDomain: "",
+ isShutDown: false,
+
+ getNameFromPreference: function (aPreferenceName)
+ {
+ return aPreferenceName.substr(this.prefDomain.length);
+ },
+
+ /**
+ * Deletes privacy sensitive data in a batch, according to user preferences.
+ * Returns a promise which is resolved if no errors occurred. If an error
+ * occurs, a message is reported to the console and all other items are still
+ * cleared before the promise is finally rejected.
+ */
+ sanitize: function ()
+ {
+ var deferred = Promise.defer();
+ var psvc = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefService);
+ var branch = psvc.getBranch(this.prefDomain);
+ var seenError = false;
+
+ // Cache the range of times to clear
+ if (this.ignoreTimespan)
+ var range = null; // If we ignore timespan, clear everything
+ else
+ range = this.range || Sanitizer.getClearRange();
+
+ let itemCount = Object.keys(this.items).length;
+ let onItemComplete = function() {
+ if (!--itemCount) {
+ seenError ? deferred.reject() : deferred.resolve();
+ }
+ };
+ for (var itemName in this.items) {
+ let item = this.items[itemName];
+ item.range = range;
+ item.isShutDown = this.isShutDown;
+ if ("clear" in item && branch.getBoolPref(itemName)) {
+ let clearCallback = (itemName, aCanClear) => {
+ // Some of these clear() may raise exceptions (see bug #265028)
+ // to sanitize as much as possible, we catch and store them,
+ // rather than fail fast.
+ // Callers should check returned errors and give user feedback
+ // about items that could not be sanitized
+ let item = this.items[itemName];
+ try {
+ if (aCanClear)
+ item.clear();
+ } catch(er) {
+ seenError = true;
+ console.error("Error sanitizing " + itemName + ": " + er + "\n");
+ }
+ onItemComplete();
+ };
+ this.canClearItem(itemName, clearCallback);
+ } else {
+ onItemComplete();
+ }
+ }
+
+ return deferred.promise;
+ },
+
+ // Time span only makes sense in certain cases. Consumers who want
+ // to only clear some private data can opt in by setting this to false,
+ // and can optionally specify a specific range. If timespan is not ignored,
+ // and range is not set, sanitize() will use the value of the timespan
+ // pref to determine a range
+ ignoreTimespan : true,
+ range : null,
+
+ items: {
+ cache: {
+ clear: function ()
+ {
+ var cache = Cc["@mozilla.org/netwerk/cache-storage-service;1"].
+ getService(Ci.nsICacheStorageService);
+ try {
+ // Cache doesn't consult timespan, nor does it have the
+ // facility for timespan-based eviction. Wipe it.
+ cache.clear();
+ } catch(er) {}
+
+ var imageCache = Cc["@mozilla.org/image/tools;1"].
+ getService(Ci.imgITools).getImgCacheForDocument(null);
+ try {
+ imageCache.clearCache(false); // true=chrome, false=content
+ } catch(er) {}
+ },
+
+ get canClear()
+ {
+ return true;
+ }
+ },
+
+ cookies: {
+ clear: function ()
+ {
+ var cookieMgr = Components.classes["@mozilla.org/cookiemanager;1"]
+ .getService(Ci.nsICookieManager);
+ if (this.range) {
+ // Iterate through the cookies and delete any created after our cutoff.
+ var cookiesEnum = cookieMgr.enumerator;
+ while (cookiesEnum.hasMoreElements()) {
+ var cookie = cookiesEnum.getNext().QueryInterface(Ci.nsICookie2);
+
+ if (cookie.creationTime > this.range[0])
+ // This cookie was created after our cutoff, clear it
+ cookieMgr.remove(cookie.host, cookie.name, cookie.path,
+ false, cookie.originAttributes);
+ }
+ }
+ else {
+ // Remove everything
+ cookieMgr.removeAll();
+ }
+
+ // Clear plugin data.
+ const phInterface = Ci.nsIPluginHost;
+ const FLAG_CLEAR_ALL = phInterface.FLAG_CLEAR_ALL;
+ let ph = Cc["@mozilla.org/plugin/host;1"].getService(phInterface);
+
+ // Determine age range in seconds. (-1 means clear all.) We don't know
+ // that this.range[1] is actually now, so we compute age range based
+ // on the lower bound. If this.range results in a negative age, do
+ // nothing.
+ let age = this.range ? (Date.now() / 1000 - this.range[0] / 1000000)
+ : -1;
+ if (!this.range || age >= 0) {
+ let tags = ph.getPluginTags();
+ for (let i = 0; i < tags.length; i++) {
+ try {
+ ph.clearSiteData(tags[i], null, FLAG_CLEAR_ALL, age);
+ } catch (e) {
+ // If the plugin doesn't support clearing by age, clear everything.
+ if (e.result == Components.results.
+ NS_ERROR_PLUGIN_TIME_RANGE_NOT_SUPPORTED) {
+ try {
+ ph.clearSiteData(tags[i], null, FLAG_CLEAR_ALL, -1);
+ } catch (e) {
+ // Ignore errors from the plugin
+ }
+ }
+ }
+ }
+ }
+ },
+
+ get canClear()
+ {
+ return true;
+ }
+ },
+
+ offlineApps: {
+ clear: function ()
+ {
+ Components.utils.import("resource:///modules/offlineAppCache.jsm");
+ OfflineAppCacheHelper.clear();
+ if (!this.range || this.isShutDown) {
+ Components.utils.import("resource:///modules/QuotaManager.jsm");
+ QuotaManagerHelper.clear(this.isShutDown);
+ }
+ },
+
+ get canClear()
+ {
+ return true;
+ }
+ },
+
+ history: {
+ clear: function ()
+ {
+ if (this.range) {
+ PlacesUtils.history.removeVisitsByFilter({
+ beginDate: new Date(this.range[0] / 1000),
+ endDate: new Date(this.range[1] / 1000)
+ }).catch(Components.utils.reportError);;
+ } else {
+ // Remove everything.
+ PlacesUtils.history.clear()
+ .catch(Components.utils.reportError);
+ }
+
+ try {
+ var os = Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService);
+ os.notifyObservers(null, "browser:purge-session-history", "");
+ }
+ catch (e) { }
+
+ // Clear last URL of the Open Web Location dialog
+ var prefs = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefBranch);
+ try {
+ prefs.clearUserPref("general.open_location.last_url");
+ }
+ catch (e) { }
+ },
+
+ get canClear()
+ {
+ // bug 347231: Always allow clearing history due to dependencies on
+ // the browser:purge-session-history notification. (like error console)
+ return true;
+ }
+ },
+
+ formdata: {
+ clear: function ()
+ {
+ // Clear undo history of all searchBars
+ var windowManager = Components.classes['@mozilla.org/appshell/window-mediator;1']
+ .getService(Components.interfaces.nsIWindowMediator);
+ var windows = windowManager.getEnumerator("navigator:browser");
+ while (windows.hasMoreElements()) {
+ let currentDocument = windows.getNext().document;
+ let searchBar = currentDocument.getElementById("searchbar");
+ if (searchBar)
+ searchBar.textbox.reset();
+ let findBar = currentDocument.getElementById("FindToolbar");
+ if (findBar)
+ findBar.clear();
+ }
+
+ let change = { op: "remove" };
+ if (this.range) {
+ [ change.firstUsedStart, change.firstUsedEnd ] = this.range;
+ }
+ FormHistory.update(change);
+ },
+
+ canClear : function(aCallback, aArg)
+ {
+ var windowManager = Components.classes['@mozilla.org/appshell/window-mediator;1']
+ .getService(Components.interfaces.nsIWindowMediator);
+ var windows = windowManager.getEnumerator("navigator:browser");
+ while (windows.hasMoreElements()) {
+ let currentDocument = windows.getNext().document;
+ let searchBar = currentDocument.getElementById("searchbar");
+ if (searchBar) {
+ let transactionMgr = searchBar.textbox.editor.transactionManager;
+ if (searchBar.value ||
+ transactionMgr.numberOfUndoItems ||
+ transactionMgr.numberOfRedoItems) {
+ aCallback("formdata", true, aArg);
+ return false;
+ }
+ }
+ let findBar = currentDocument.getElementById("FindToolbar");
+ if (findBar && findBar.canClear) {
+ aCallback("formdata", true, aArg);
+ return false;
+ }
+ }
+
+ let count = 0;
+ let countDone = {
+ handleResult : function(aResult) count = aResult,
+ handleError : function(aError) Components.utils.reportError(aError),
+ handleCompletion :
+ function(aReason) { aCallback("formdata", aReason == 0 && count > 0, aArg); }
+ };
+ FormHistory.count({}, countDone);
+ return false;
+ }
+ },
+
+ downloads: {
+ clear: Task.async(function* (range) {
+ let refObj = {};
+ try {
+ let filterByTime = null;
+ if (range) {
+ // Convert microseconds back to milliseconds for date comparisons.
+ let rangeBeginMs = range[0] / 1000;
+ let rangeEndMs = range[1] / 1000;
+ filterByTime = download => download.startTime >= rangeBeginMs &&
+ download.startTime <= rangeEndMs;
+ }
+
+ // Clear all completed/cancelled downloads
+ let list = yield Downloads.getList(Downloads.ALL);
+ list.removeFinished(filterByTime);
+ } finally {}
+ }),
+
+ get canClear()
+ {
+ //Clearing is always possible with JSTransfers
+ return true;
+ }
+ },
+
+ passwords: {
+ clear: function ()
+ {
+ var pwmgr = Components.classes["@mozilla.org/login-manager;1"]
+ .getService(Components.interfaces.nsILoginManager);
+ // Passwords are timeless, and don't respect the timeSpan setting
+ pwmgr.removeAllLogins();
+ },
+
+ get canClear()
+ {
+ var pwmgr = Components.classes["@mozilla.org/login-manager;1"]
+ .getService(Components.interfaces.nsILoginManager);
+ var count = pwmgr.countLogins("", "", ""); // count all logins
+ return (count > 0);
+ }
+ },
+
+ sessions: {
+ clear: function ()
+ {
+ // clear all auth tokens
+ var sdr = Components.classes["@mozilla.org/security/sdr;1"]
+ .getService(Components.interfaces.nsISecretDecoderRing);
+ sdr.logoutAndTeardown();
+
+ // clear FTP and plain HTTP auth sessions
+ var os = Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService);
+ os.notifyObservers(null, "net:clear-active-logins", null);
+ },
+
+ get canClear()
+ {
+ return true;
+ }
+ },
+
+ siteSettings: {
+ clear: function ()
+ {
+ // Clear site-specific permissions like "Allow this site to open popups"
+ var pm = Components.classes["@mozilla.org/permissionmanager;1"]
+ .getService(Components.interfaces.nsIPermissionManager);
+ pm.removeAll();
+
+ // Clear site-specific settings like page-zoom level
+ var cps = Components.classes["@mozilla.org/content-pref/service;1"]
+ .getService(Components.interfaces.nsIContentPrefService2);
+ cps.removeAllDomains(null);
+
+ // Clear "Never remember passwords for this site", which is not handled by
+ // the permission manager
+ var pwmgr = Components.classes["@mozilla.org/login-manager;1"]
+ .getService(Components.interfaces.nsILoginManager);
+ var hosts = pwmgr.getAllDisabledHosts();
+ for each (var host in hosts) {
+ pwmgr.setLoginSavingEnabled(host, true);
+ }
+ },
+
+ get canClear()
+ {
+ return true;
+ }
+ },
+
+ connectivityData: {
+ clear: function ()
+ {
+ // Clear site security settings
+ var sss = Components.classes["@mozilla.org/ssservice;1"]
+ .getService(Components.interfaces.nsISiteSecurityService);
+ sss.clearAll();
+ },
+
+ get canClear()
+ {
+ return true;
+ }
+ }
+ }
+};
+
+
+
+// "Static" members
+Sanitizer.prefDomain = "privacy.sanitize.";
+Sanitizer.prefShutdown = "sanitizeOnShutdown";
+Sanitizer.prefDidShutdown = "didShutdownSanitize";
+
+// Time span constants corresponding to values of the privacy.sanitize.timeSpan
+// pref. Used to determine how much history to clear, for various items
+Sanitizer.TIMESPAN_EVERYTHING = 0;
+Sanitizer.TIMESPAN_HOUR = 1;
+Sanitizer.TIMESPAN_2HOURS = 2;
+Sanitizer.TIMESPAN_4HOURS = 3;
+Sanitizer.TIMESPAN_TODAY = 4;
+
+Sanitizer.IS_SHUTDOWN = true;
+
+// Return a 2 element array representing the start and end times,
+// in the uSec-since-epoch format that PRTime likes. If we should
+// clear everything, return null. Use ts if it is defined; otherwise
+// use the timeSpan pref.
+Sanitizer.getClearRange = function (ts) {
+ if (ts === undefined)
+ ts = Sanitizer.prefs.getIntPref("timeSpan");
+ if (ts === Sanitizer.TIMESPAN_EVERYTHING)
+ return null;
+
+ // PRTime is microseconds while JS time is milliseconds
+ var endDate = Date.now() * 1000;
+ switch (ts) {
+ case Sanitizer.TIMESPAN_HOUR :
+ var startDate = endDate - 3600000000; // 1*60*60*1000000
+ break;
+ case Sanitizer.TIMESPAN_2HOURS :
+ startDate = endDate - 7200000000; // 2*60*60*1000000
+ break;
+ case Sanitizer.TIMESPAN_4HOURS :
+ startDate = endDate - 14400000000; // 4*60*60*1000000
+ break;
+ case Sanitizer.TIMESPAN_TODAY :
+ var d = new Date(); // Start with today
+ d.setHours(0); // zero us back to midnight...
+ d.setMinutes(0);
+ d.setSeconds(0);
+ startDate = d.valueOf() * 1000; // convert to epoch usec
+ break;
+ default:
+ throw "Invalid time span for clear private data: " + ts;
+ }
+ return [startDate, endDate];
+};
+
+Sanitizer._prefs = null;
+Sanitizer.__defineGetter__("prefs", function()
+{
+ return Sanitizer._prefs ? Sanitizer._prefs
+ : Sanitizer._prefs = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefService)
+ .getBranch(Sanitizer.prefDomain);
+});
+
+// Shows sanitization UI
+Sanitizer.showUI = function(aParentWindow)
+{
+ var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
+ .getService(Components.interfaces.nsIWindowWatcher);
+#ifdef XP_MACOSX
+ ww.openWindow(null, // make this an app-modal window on Mac
+#else
+ ww.openWindow(aParentWindow,
+#endif
+ "chrome://browser/content/sanitize.xul",
+ "Sanitize",
+ "chrome,titlebar,dialog,centerscreen,modal",
+ null);
+};
+
+/**
+ * Deletes privacy sensitive data in a batch, optionally showing the
+ * sanitize UI, according to user preferences
+ */
+Sanitizer.sanitize = function(aParentWindow)
+{
+ Sanitizer.showUI(aParentWindow);
+};
+
+Sanitizer.onStartup = function()
+{
+ // we check for unclean exit with pending sanitization
+ Sanitizer._checkAndSanitize();
+};
+
+Sanitizer.onShutdown = function()
+{
+ // we check if sanitization is needed and perform it
+ Sanitizer._checkAndSanitize(Sanitizer.IS_SHUTDOWN);
+};
+
+// this is called on startup and shutdown, to perform pending sanitizations
+Sanitizer._checkAndSanitize = function(isShutDown)
+{
+ const prefs = Sanitizer.prefs;
+ if (prefs.getBoolPref(Sanitizer.prefShutdown) &&
+ !prefs.prefHasUserValue(Sanitizer.prefDidShutdown)) {
+ // this is a shutdown or a startup after an unclean exit
+ var s = new Sanitizer();
+ s.prefDomain = "privacy.clearOnShutdown.";
+ s.isShutDown = isShutDown;
+ s.sanitize().then(function() {
+ prefs.setBoolPref(Sanitizer.prefDidShutdown, true);
+ });
+ }
+};