sanitize.js 18 KB

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