/* 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/. */ "use strict"; Components.utils.import("resource://gre/modules/Services.jsm"); Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); Components.utils.import("resource://gre/modules/NetUtil.jsm"); Components.utils.import("resource://gre/modules/Task.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils", "resource://gre/modules/PlacesUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Downloads", "resource://gre/modules/Downloads.jsm"); this.EXPORTED_SYMBOLS = ["ForgetAboutSite"]; /** * Returns true if the string passed in is part of the root domain of the * current string. For example, if this is "www.mozilla.org", and we pass in * "mozilla.org", this will return true. It would return false the other way * around. */ function hasRootDomain(str, aDomain) { let index = str.indexOf(aDomain); // If aDomain is not found, we know we do not have it as a root domain. if (index == -1) return false; // If the strings are the same, we obviously have a match. if (str == aDomain) return true; // Otherwise, we have aDomain as our root domain iff the index of aDomain is // aDomain.length subtracted from our length and (since we do not have an // exact match) the character before the index is a dot or slash. let prevChar = str[index - 1]; return (index == (str.length - aDomain.length)) && (prevChar == "." || prevChar == "/"); } const Cc = Components.classes; const Ci = Components.interfaces; const Cu = Components.utils; this.ForgetAboutSite = { removeDataFromDomain: Task.async(function* (aDomain) { PlacesUtils.history.removePagesFromHost(aDomain, true); let promises = []; // Cache promises.push(Task.spawn(function*() { let cs = Cc["@mozilla.org/netwerk/cache-storage-service;1"]. getService(Ci.nsICacheStorageService); // NOTE: there is no way to clear just that domain, so we clear out // everything) cs.clear(); }).catch(ex => { throw new Error("Exception thrown while clearing the cache: " + ex); })); // Image Cache promises.push(Task.spawn(function*() { let imageCache = Cc["@mozilla.org/image/tools;1"]. getService(Ci.imgITools).getImgCacheForDocument(null); imageCache.clearCache(false); // true=chrome, false=content }).catch(ex => { throw new Error("Exception thrown while clearing the image cache: " + ex); })); // Cookies // Need to maximize the number of cookies cleaned here promises.push(Task.spawn(function*() { let cm = Cc["@mozilla.org/cookiemanager;1"]. getService(Ci.nsICookieManager2); let enumerator = cm.getCookiesWithOriginAttributes(JSON.stringify({}), aDomain); while (enumerator.hasMoreElements()) { let cookie = enumerator.getNext().QueryInterface(Ci.nsICookie); cm.remove(cookie.host, cookie.name, cookie.path, false, cookie.originAttributes); } }).catch(ex => { throw new Error("Exception thrown while clearning cookies: " + ex); })); // EME promises.push(Task.spawn(function*() { let mps = Cc["@mozilla.org/gecko-media-plugin-service;1"]. getService(Ci.mozIGeckoMediaPluginChromeService); mps.forgetThisSite(aDomain, JSON.stringify({})); }).catch(ex => { throw new Error("Exception thrown while clearing Encrypted Media Extensions: " + ex); })); // Plugin data const phInterface = Ci.nsIPluginHost; const FLAG_CLEAR_ALL = phInterface.FLAG_CLEAR_ALL; let ph = Cc["@mozilla.org/plugin/host;1"].getService(phInterface); let tags = ph.getPluginTags(); for (let i = 0; i < tags.length; i++) { promises.push(new Promise(resolve => { try { ph.clearSiteData(tags[i], aDomain, FLAG_CLEAR_ALL, -1, resolve); } catch (e) { // Ignore errors from the plugin, but resolve the promise // We cannot check if something is a bailout or an error resolve(); } })); } // Downloads promises.push(Task.spawn(function*() { let list = yield Downloads.getList(Downloads.ALL); list.removeFinished(download => hasRootDomain( NetUtil.newURI(download.source.url).host, aDomain)); }).catch(ex => { throw new Error("Exception in clearing Downloads: " + ex); })); // Passwords promises.push(Task.spawn(function*() { let lm = Cc["@mozilla.org/login-manager;1"]. getService(Ci.nsILoginManager); // Clear all passwords for domain let logins = lm.getAllLogins(); for (let i = 0; i < logins.length; i++) { if (hasRootDomain(logins[i].hostname, aDomain)) { lm.removeLogin(logins[i]); } } }).catch(ex => { // XXX: // Is there a better way to do this rather than this hacky comparison? // Copied this from toolkit/components/passwordmgr/crypto-SDR.js if (!ex.message.includes("User canceled master password entry")) { throw new Error("Exception occured in clearing passwords: " + ex); } })); // Permissions let pm = Cc["@mozilla.org/permissionmanager;1"]. getService(Ci.nsIPermissionManager); // Enumerate all of the permissions, and if one matches, remove it let enumerator = pm.enumerator; while (enumerator.hasMoreElements()) { let perm = enumerator.getNext().QueryInterface(Ci.nsIPermission); promises.push(new Promise((resolve, reject) => { try { if (hasRootDomain(perm.principal.URI.host, aDomain)) { pm.removePermission(perm); } } catch (ex) { // Ignore entry } finally { resolve(); } })); } // Offline Storages promises.push(Task.spawn(function*() { let qms = Cc["@mozilla.org/dom/quota-manager-service;1"]. getService(Ci.nsIQuotaManagerService); // delete data from both HTTP and HTTPS sites let httpURI = NetUtil.newURI("http://" + aDomain); let httpsURI = NetUtil.newURI("https://" + aDomain); // Following code section has been reverted to the state before Bug 1238183, // but added a new argument to clearStoragesForPrincipal() for indicating // clear all storages under a given origin. let httpPrincipal = Services.scriptSecurityManager .createCodebasePrincipal(httpURI, {}); let httpsPrincipal = Services.scriptSecurityManager .createCodebasePrincipal(httpsURI, {}); qms.clearStoragesForPrincipal(httpPrincipal, null, true); qms.clearStoragesForPrincipal(httpsPrincipal, null, true); }).catch(ex => { throw new Error("Exception occured while clearing offline storages: " + ex); })); // Content Preferences promises.push(Task.spawn(function*() { let cps2 = Cc["@mozilla.org/content-pref/service;1"]. getService(Ci.nsIContentPrefService2); cps2.removeBySubdomain(aDomain, null, { handleCompletion: (reason) => { // Notify other consumers, including extensions Services.obs.notifyObservers(null, "browser:purge-domain-data", aDomain); if (reason === cps2.COMPLETE_ERROR) { throw new Error("Exception occured while clearing content preferences"); } }, handleError() {} }); })); // Predictive network data - like cache, no way to clear this per // domain, so just trash it all promises.push(Task.spawn(function*() { let np = Cc["@mozilla.org/network/predictor;1"]. getService(Ci.nsINetworkPredictor); np.reset(); }).catch(ex => { throw new Error("Exception occured while clearing predictive network data: " + ex); })); // Push notifications promises.push(Task.spawn(function*() { var push = Cc["@mozilla.org/push/Service;1"]. getService(Ci.nsIPushService); push.clearForDomain(aDomain, status => { if (!Components.isSuccessCode(status)) { throw new Error("Exception occured while clearing push notifications: " + status); } }); })); // HSTS and HPKP // TODO (bug 1290529): also remove HSTS/HPKP information for subdomains. // Since we can't enumerate the information in the site security service // (bug 1115712), we can't implement this right now. promises.push(Task.spawn(function*() { let sss = Cc["@mozilla.org/ssservice;1"]. getService(Ci.nsISiteSecurityService); let httpsURI = NetUtil.newURI("https://" + aDomain); sss.removeState(Ci.nsISiteSecurityService.HEADER_HSTS, httpsURI, 0); sss.removeState(Ci.nsISiteSecurityService.HEADER_HPKP, httpsURI, 0); }).catch(ex => { throw new Error("Exception thrown while clearing HSTS/HPKP: " + ex); })); let ErrorCount = 0; for (let promise of promises) { try { yield promise; } catch (ex) { Cu.reportError(ex); ErrorCount++; } } if (ErrorCount !== 0) throw new Error(`There were a total of ${ErrorCount} errors during removal`); }) }