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