// -*- 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) { // If we ignore timespan, clear everything var range = null; } 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 => { return 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); // count all logins var count = pwmgr.countLogins("", "", ""); 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); ww.openWindow(aParentWindow, "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); }); } };