Bug 527667 - DOM Storage (localStorage, sessionStorage) data is not cleared when...
[mozilla-central.git] / browser / base / content / sanitize.js
blob8c6d9a7dd91772efbdb64b352034f963d58d2615
1 # -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 # ***** BEGIN LICENSE BLOCK *****
3 # Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 # The contents of this file are subject to the Mozilla Public License Version
6 # 1.1 (the "License"); you may not use this file except in compliance with
7 # the License. You may obtain a copy of the License at
8 # http://www.mozilla.org/MPL/
10 # Software distributed under the License is distributed on an "AS IS" basis,
11 # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 # for the specific language governing rights and limitations under the
13 # License.
15 # The Original Code is the Firefox Sanitizer.
17 # The Initial Developer of the Original Code is
18 # Ben Goodger.
19 # Portions created by the Initial Developer are Copyright (C) 2005
20 # the Initial Developer. All Rights Reserved.
22 # Contributor(s):
23 #   Ben Goodger <ben@mozilla.org>
24 #   Giorgio Maone <g.maone@informaction.com>
25 #   Johnathan Nightingale <johnath@mozilla.com>
26 #   Ehsan Akhgari <ehsan.akhgari@gmail.com>
28 # Alternatively, the contents of this file may be used under the terms of
29 # either the GNU General Public License Version 2 or later (the "GPL"), or
30 # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
31 # in which case the provisions of the GPL or the LGPL are applicable instead
32 # of those above. If you wish to allow use of your version of this file only
33 # under the terms of either the GPL or the LGPL, and not to allow others to
34 # use your version of this file under the terms of the MPL, indicate your
35 # decision by deleting the provisions above and replace them with the notice
36 # and other provisions required by the GPL or the LGPL. If you do not delete
37 # the provisions above, a recipient may use your version of this file under
38 # the terms of any one of the MPL, the GPL or the LGPL.
40 # ***** END LICENSE BLOCK *****
42 function Sanitizer() {}
43 Sanitizer.prototype = {
44   // warning to the caller: this one may raise an exception (e.g. bug #265028)
45   clearItem: function (aItemName)
46   {
47     if (this.items[aItemName].canClear)
48       this.items[aItemName].clear();
49   },
51   canClearItem: function (aItemName)
52   {
53     return this.items[aItemName].canClear;
54   },
55   
56   prefDomain: "",
57   
58   getNameFromPreference: function (aPreferenceName)
59   {
60     return aPreferenceName.substr(this.prefDomain.length);
61   },
62   
63   /**
64    * Deletes privacy sensitive data in a batch, according to user preferences
65    *
66    * @returns  null if everything's fine;  an object in the form
67    *           { itemName: error, ... } on (partial) failure
68    */
69   sanitize: function ()
70   {
71     var psvc = Components.classes["@mozilla.org/preferences-service;1"]
72                          .getService(Components.interfaces.nsIPrefService);
73     var branch = psvc.getBranch(this.prefDomain);
74     var errors = null;
76     // Cache the range of times to clear
77     if (this.ignoreTimespan)
78       var range = null;  // If we ignore timespan, clear everything
79     else
80       range = this.range || Sanitizer.getClearRange();
81       
82     for (var itemName in this.items) {
83       var item = this.items[itemName];
84       item.range = range;
85       if ("clear" in item && item.canClear && branch.getBoolPref(itemName)) {
86         // Some of these clear() may raise exceptions (see bug #265028)
87         // to sanitize as much as possible, we catch and store them, 
88         // rather than fail fast.
89         // Callers should check returned errors and give user feedback
90         // about items that could not be sanitized
91         try {
92           item.clear();
93         } catch(er) {
94           if (!errors) 
95             errors = {};
96           errors[itemName] = er;
97           dump("Error sanitizing " + itemName + ": " + er + "\n");
98         }
99       }
100     }
101     return errors;
102   },
103   
104   // Time span only makes sense in certain cases.  Consumers who want
105   // to only clear some private data can opt in by setting this to false,
106   // and can optionally specify a specific range.  If timespan is not ignored,
107   // and range is not set, sanitize() will use the value of the timespan
108   // pref to determine a range
109   ignoreTimespan : true,
110   range : null,
111   
112   items: {
113     cache: {
114       clear: function ()
115       {
116         const Cc = Components.classes;
117         const Ci = Components.interfaces;
118         var cacheService = Cc["@mozilla.org/network/cache-service;1"].
119                           getService(Ci.nsICacheService);
120         try {
121           // Cache doesn't consult timespan, nor does it have the
122           // facility for timespan-based eviction.  Wipe it.
123           cacheService.evictEntries(Ci.nsICache.STORE_ANYWHERE);
124         } catch(er) {}
126         var imageCache = Cc["@mozilla.org/image/cache;1"].
127                          getService(Ci.imgICache);
128         try {
129           imageCache.clearCache(false); // true=chrome, false=content
130         } catch(er) {}
131       },
132       
133       get canClear()
134       {
135         return true;
136       }
137     },
138     
139     cookies: {
140       clear: function ()
141       {
142         const Ci = Components.interfaces;
143         var cookieMgr = Components.classes["@mozilla.org/cookiemanager;1"]
144                                   .getService(Ci.nsICookieManager);
145         if (this.range) {
146           // Iterate through the cookies and delete any created after our cutoff.
147           var cookiesEnum = cookieMgr.enumerator;
148           while (cookiesEnum.hasMoreElements()) {
149             var cookie = cookiesEnum.getNext().QueryInterface(Ci.nsICookie2);
150             
151             if (cookie.creationTime > this.range[0])
152               // This cookie was created after our cutoff, clear it
153               cookieMgr.remove(cookie.host, cookie.name, cookie.path, false);
154           }
156           // Also handle all DOM storage data created after the cutoff.
157           var domStorageManager = Components.classes["@mozilla.org/dom/storagemanager;1"]
158                                             .getService(Ci.nsIDOMStorageManager);
159           domStorageManager.clearStorageDataSince(this.range[0]);
160         }
161         else {
162           // Remove everything
163           cookieMgr.removeAll();
164         }
166         // clear any network geolocation provider sessions
167         var psvc = Components.classes["@mozilla.org/preferences-service;1"]
168                              .getService(Components.interfaces.nsIPrefService);
169         try {
170             var branch = psvc.getBranch("geo.wifi.access_token.");
171             branch.deleteBranch("");
172         } catch (e) {}
174       },
175       
176       get canClear()
177       {
178         return true;
179       }
180     },
181     
182     offlineApps: {
183       clear: function ()
184       {
185         const Cc = Components.classes;
186         const Ci = Components.interfaces;
187         var cacheService = Cc["@mozilla.org/network/cache-service;1"].
188                            getService(Ci.nsICacheService);
189         try {
190           // Offline app data is "timeless", and doesn't respect
191           // the setting of timespan, it always clears everything
192           cacheService.evictEntries(Ci.nsICache.STORE_OFFLINE);
193         } catch(er) {}
195         var storageManagerService = Cc["@mozilla.org/dom/storagemanager;1"].
196                                     getService(Ci.nsIDOMStorageManager);
197         storageManagerService.clearOfflineApps();
198       },
200       get canClear()
201       {
202           return true;
203       }
204     },
206     history: {
207       clear: function ()
208       {
209         var globalHistory = Components.classes["@mozilla.org/browser/global-history;2"]
210                                       .getService(Components.interfaces.nsIBrowserHistory);
211         if (this.range)
212           globalHistory.removeVisitsByTimeframe(this.range[0], this.range[1]);
213         else
214           globalHistory.removeAllPages();
215         
216         try {
217           var os = Components.classes["@mozilla.org/observer-service;1"]
218                              .getService(Components.interfaces.nsIObserverService);
219           os.notifyObservers(null, "browser:purge-session-history", "");
220         }
221         catch (e) { }
222         
223         // Clear last URL of the Open Web Location dialog
224         var prefs = Components.classes["@mozilla.org/preferences-service;1"]
225                               .getService(Components.interfaces.nsIPrefBranch2);
226         try {
227           prefs.clearUserPref("general.open_location.last_url");
228         }
229         catch (e) { }
230       },
231       
232       get canClear()
233       {
234         // bug 347231: Always allow clearing history due to dependencies on
235         // the browser:purge-session-history notification. (like error console)
236         return true;
237       }
238     },
239     
240     formdata: {
241       clear: function ()
242       {
243         // Clear undo history of all searchBars
244         var windowManager = Components.classes['@mozilla.org/appshell/window-mediator;1']
245                                       .getService(Components.interfaces.nsIWindowMediator);
246         var windows = windowManager.getEnumerator("navigator:browser");
247         while (windows.hasMoreElements()) {
248           var searchBar = windows.getNext().document.getElementById("searchbar");
249           if (searchBar)
250             searchBar.textbox.reset();
251         }
253         var formHistory = Components.classes["@mozilla.org/satchel/form-history;1"]
254                                     .getService(Components.interfaces.nsIFormHistory2);
255         if (this.range)
256           formHistory.removeEntriesByTimeframe(this.range[0], this.range[1]);
257         else
258           formHistory.removeAllEntries();
259       },
261       get canClear()
262       {
263         var windowManager = Components.classes['@mozilla.org/appshell/window-mediator;1']
264                                       .getService(Components.interfaces.nsIWindowMediator);
265         var windows = windowManager.getEnumerator("navigator:browser");
266         while (windows.hasMoreElements()) {
267           var searchBar = windows.getNext().document.getElementById("searchbar");
268           if (searchBar) {
269             var transactionMgr = searchBar.textbox.editor.transactionManager;
270             if (searchBar.value ||
271                 transactionMgr.numberOfUndoItems ||
272                 transactionMgr.numberOfRedoItems)
273               return true;
274           }
275         }
277         var formHistory = Components.classes["@mozilla.org/satchel/form-history;1"]
278                                     .getService(Components.interfaces.nsIFormHistory2);
279         return formHistory.hasEntries;
280       }
281     },
282     
283     downloads: {
284       clear: function ()
285       {
286         var dlMgr = Components.classes["@mozilla.org/download-manager;1"]
287                               .getService(Components.interfaces.nsIDownloadManager);
289         var dlIDsToRemove = [];
290         if (this.range) {
291           // First, remove the completed/cancelled downloads
292           dlMgr.removeDownloadsByTimeframe(this.range[0], this.range[1]);
293           
294           // Queue up any active downloads that started in the time span as well
295           var dlsEnum = dlMgr.activeDownloads;
296           while(dlsEnum.hasMoreElements()) {
297             var dl = dlsEnum.next();
298             if(dl.startTime >= this.range[0])
299               dlIDsToRemove.push(dl.id);
300           }
301         }
302         else {
303           // Clear all completed/cancelled downloads
304           dlMgr.cleanUp();
305           
306           // Queue up all active ones as well
307           var dlsEnum = dlMgr.activeDownloads;
308           while(dlsEnum.hasMoreElements()) {
309             dlIDsToRemove.push(dlsEnum.next().id);
310           }
311         }
312         
313         // Remove any queued up active downloads
314         dlIDsToRemove.forEach(function(id) {
315           dlMgr.removeDownload(id);
316         });
317       },
319       get canClear()
320       {
321         var dlMgr = Components.classes["@mozilla.org/download-manager;1"]
322                               .getService(Components.interfaces.nsIDownloadManager);
323         return dlMgr.canCleanUp;
324       }
325     },
326     
327     passwords: {
328       clear: function ()
329       {
330         var pwmgr = Components.classes["@mozilla.org/login-manager;1"]
331                               .getService(Components.interfaces.nsILoginManager);
332         // Passwords are timeless, and don't respect the timeSpan setting
333         pwmgr.removeAllLogins();
334       },
335       
336       get canClear()
337       {
338         var pwmgr = Components.classes["@mozilla.org/login-manager;1"]
339                               .getService(Components.interfaces.nsILoginManager);
340         var count = pwmgr.countLogins("", "", ""); // count all logins
341         return (count > 0);
342       }
343     },
344     
345     sessions: {
346       clear: function ()
347       {
348         // clear all auth tokens
349         var sdr = Components.classes["@mozilla.org/security/sdr;1"]
350                             .getService(Components.interfaces.nsISecretDecoderRing);
351         sdr.logoutAndTeardown();
353         // clear FTP and plain HTTP auth sessions
354         var os = Components.classes["@mozilla.org/observer-service;1"]
355                            .getService(Components.interfaces.nsIObserverService);
356         os.notifyObservers(null, "net:clear-active-logins", null);
357       },
358       
359       get canClear()
360       {
361         return true;
362       }
363     },
364     
365     siteSettings: {
366       clear: function ()
367       {
368         // Clear site-specific permissions like "Allow this site to open popups"
369         var pm = Components.classes["@mozilla.org/permissionmanager;1"]
370                            .getService(Components.interfaces.nsIPermissionManager);
371         pm.removeAll();
372         
373         // Clear site-specific settings like page-zoom level
374         var cps = Components.classes["@mozilla.org/content-pref/service;1"]
375                             .getService(Components.interfaces.nsIContentPrefService);
376         cps.removeGroupedPrefs();
377         
378         // Clear "Never remember passwords for this site", which is not handled by
379         // the permission manager
380         var pwmgr = Components.classes["@mozilla.org/login-manager;1"]
381                               .getService(Components.interfaces.nsILoginManager);
382         var hosts = pwmgr.getAllDisabledHosts();
383         for each (var host in hosts) {
384           pwmgr.setLoginSavingEnabled(host, true);
385         }
386       },
387       
388       get canClear()
389       {
390         return true;
391       }
392     }
393   }
398 // "Static" members
399 Sanitizer.prefDomain          = "privacy.sanitize.";
400 Sanitizer.prefShutdown        = "sanitizeOnShutdown";
401 Sanitizer.prefDidShutdown     = "didShutdownSanitize";
403 // Time span constants corresponding to values of the privacy.sanitize.timeSpan
404 // pref.  Used to determine how much history to clear, for various items
405 Sanitizer.TIMESPAN_EVERYTHING = 0;
406 Sanitizer.TIMESPAN_HOUR       = 1;
407 Sanitizer.TIMESPAN_2HOURS     = 2;
408 Sanitizer.TIMESPAN_4HOURS     = 3;
409 Sanitizer.TIMESPAN_TODAY      = 4;
411 // Return a 2 element array representing the start and end times,
412 // in the uSec-since-epoch format that PRTime likes.  If we should
413 // clear everything, return null.  Use ts if it is defined; otherwise
414 // use the timeSpan pref.
415 Sanitizer.getClearRange = function (ts) {
416   if (ts === undefined)
417     ts = Sanitizer.prefs.getIntPref("timeSpan");
418   if (ts === Sanitizer.TIMESPAN_EVERYTHING)
419     return null;
420   
421   // PRTime is microseconds while JS time is milliseconds
422   var endDate = Date.now() * 1000;
423   switch (ts) {
424     case Sanitizer.TIMESPAN_HOUR :
425       var startDate = endDate - 3600000000; // 1*60*60*1000000
426       break;
427     case Sanitizer.TIMESPAN_2HOURS :
428       startDate = endDate - 7200000000; // 2*60*60*1000000
429       break;
430     case Sanitizer.TIMESPAN_4HOURS :
431       startDate = endDate - 14400000000; // 4*60*60*1000000
432       break;
433     case Sanitizer.TIMESPAN_TODAY :
434       var d = new Date();  // Start with today
435       d.setHours(0);      // zero us back to midnight...
436       d.setMinutes(0);
437       d.setSeconds(0);
438       startDate = d.valueOf() * 1000; // convert to epoch usec
439       break;
440     default:
441       throw "Invalid time span for clear private data: " + ts;
442   }
443   return [startDate, endDate];
446 Sanitizer._prefs = null;
447 Sanitizer.__defineGetter__("prefs", function() 
449   return Sanitizer._prefs ? Sanitizer._prefs
450     : Sanitizer._prefs = Components.classes["@mozilla.org/preferences-service;1"]
451                          .getService(Components.interfaces.nsIPrefService)
452                          .getBranch(Sanitizer.prefDomain);
455 // Shows sanitization UI
456 Sanitizer.showUI = function(aParentWindow) 
458   var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
459                      .getService(Components.interfaces.nsIWindowWatcher);
460 #ifdef XP_MACOSX
461   ww.openWindow(null, // make this an app-modal window on Mac
462 #else
463   ww.openWindow(aParentWindow,
464 #endif
465                 "chrome://browser/content/sanitize.xul",
466                 "Sanitize",
467                 "chrome,titlebar,dialog,centerscreen,modal",
468                 null);
471 /** 
472  * Deletes privacy sensitive data in a batch, optionally showing the 
473  * sanitize UI, according to user preferences
474  */
475 Sanitizer.sanitize = function(aParentWindow) 
477   Sanitizer.showUI(aParentWindow);
480 Sanitizer.onStartup = function() 
482   // we check for unclean exit with pending sanitization
483   Sanitizer._checkAndSanitize();
486 Sanitizer.onShutdown = function() 
488   // we check if sanitization is needed and perform it
489   Sanitizer._checkAndSanitize();
492 // this is called on startup and shutdown, to perform pending sanitizations
493 Sanitizer._checkAndSanitize = function() 
495   const prefs = Sanitizer.prefs;
496   if (prefs.getBoolPref(Sanitizer.prefShutdown) && 
497       !prefs.prefHasUserValue(Sanitizer.prefDidShutdown)) {
498     // this is a shutdown or a startup after an unclean exit
499     var s = new Sanitizer();
500     s.prefDomain = "privacy.clearOnShutdown.";
501     s.sanitize() || // sanitize() returns null on full success
502       prefs.setBoolPref(Sanitizer.prefDidShutdown, true);
503   }