diff options
author | Thomas Groman <tgroman@nuegia.net> | 2020-04-20 20:49:37 -0700 |
---|---|---|
committer | Thomas Groman <tgroman@nuegia.net> | 2020-04-20 20:49:37 -0700 |
commit | f9cab004186edb425a9b88ad649726605080a17c (patch) | |
tree | e2dae51d3144e83d097a12e7a1499e3ea93f90be /webbrowser/base/content/sanitize.js | |
parent | f428692de8b59ab89a66502c079e1823dfda8aeb (diff) | |
download | webbrowser-f9cab004186edb425a9b88ad649726605080a17c.tar webbrowser-f9cab004186edb425a9b88ad649726605080a17c.tar.gz webbrowser-f9cab004186edb425a9b88ad649726605080a17c.tar.lz webbrowser-f9cab004186edb425a9b88ad649726605080a17c.tar.xz webbrowser-f9cab004186edb425a9b88ad649726605080a17c.zip |
move browser to webbrowser/
Diffstat (limited to 'webbrowser/base/content/sanitize.js')
-rw-r--r-- | webbrowser/base/content/sanitize.js | 534 |
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); + }); + } +}; |