Bug 1507171 - Cleanup data on shutdown just for http, https and file URLs, r=johannh
authorAndrea Marchesini <amarchesini@mozilla.com>
Mon, 19 Nov 2018 16:02:23 +0100
changeset 503516 3b2da7e798ead163fc55d463bee46682d65d64ec
parent 503515 640b09ec036ea40e164b2193172bf247475d9930
child 503517 01f5b38c0cb71cc9c31fc2c372cf68c1c3c750e6
push id10290
push userffxbld-merge
push dateMon, 03 Dec 2018 16:23:23 +0000
treeherdermozilla-beta@700bed2445e6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjohannh
bugs1507171
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1507171 - Cleanup data on shutdown just for http, https and file URLs, r=johannh
browser/base/content/test/sanitize/browser_cookiePermission.js
browser/modules/Sanitizer.jsm
--- a/browser/base/content/test/sanitize/browser_cookiePermission.js
+++ b/browser/base/content/test/sanitize/browser_cookiePermission.js
@@ -1,12 +1,28 @@
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 const {Sanitizer} = ChromeUtils.import("resource:///modules/Sanitizer.jsm", {});
 const {SiteDataTestUtils} = ChromeUtils.import("resource://testing-common/SiteDataTestUtils.jsm", {});
 
+function checkDataForAboutURL() {
+  return new Promise(resolve => {
+    let data = true;
+    let uri = Services.io.newURI("about:newtab");
+    let principal =
+      Services.scriptSecurityManager.createCodebasePrincipal(uri, {});
+    let request = indexedDB.openForPrincipal(principal, "TestDatabase", 1);
+    request.onupgradeneeded = function(e) {
+      data = false;
+    };
+    request.onsuccess = function(e) {
+      resolve(data);
+    };
+  });
+}
+
 function createIndexedDB(host, originAttributes) {
   return SiteDataTestUtils.addToIndexedDB("https://" + host, "foo", "bar",
                                           originAttributes);
 }
 
 function checkIndexedDB(host, originAttributes) {
   return new Promise(resolve => {
     let data = true;
@@ -228,8 +244,78 @@ tests.forEach(methods => {
           cookiePermission: Ci.nsICookiePermission.ACCESS_SESSION,
           expectedForOrg: true,
           expectedForCom: false,
           fullHost: true,
         });
     });
   });
 });
+
+add_task(async function deleteStorageInAboutURL() {
+  info("Test about:newtab");
+
+  // Let's clean up all the data.
+  await new Promise(resolve => {
+    Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value => resolve());
+  });
+
+  await SpecialPowers.pushPrefEnv({"set": [
+    ["network.cookie.lifetimePolicy", Ci.nsICookieService.ACCEPT_SESSION],
+  ]});
+
+  // Let's create a tab with some data.
+  await SiteDataTestUtils.addToIndexedDB("about:newtab", "foo", "bar", {});
+
+  ok(await checkDataForAboutURL(), "We have data for about:newtab");
+
+  // Cleaning up.
+  await Sanitizer.runSanitizeOnShutdown();
+
+  ok(await checkDataForAboutURL(), "about:newtab data is not deleted.");
+
+  // Clean up.
+  await Sanitizer.sanitize([ "cookies", "offlineApps" ]);
+
+  let principal = Services.scriptSecurityManager.createCodebasePrincipalFromOrigin("about:newtab");
+  await new Promise(aResolve => {
+    let req = Services.qms.clearStoragesForPrincipal(principal, null, false);
+    req.callback = () => { aResolve(); };
+  });
+});
+
+add_task(async function deleteStorageOnlyCustomPermissionInAboutURL() {
+  info("Test about:newtab + permissions");
+
+  // Let's clean up all the data.
+  await new Promise(resolve => {
+    Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value => resolve());
+  });
+
+  await SpecialPowers.pushPrefEnv({"set": [
+    ["network.cookie.lifetimePolicy", Ci.nsICookieService.ACCEPT_NORMALLY],
+  ]});
+
+  // Custom permission without considering OriginAttributes
+  let uri = Services.io.newURI("about:newtab");
+  Services.perms.add(uri, "cookie", Ci.nsICookiePermission.ACCESS_SESSION);
+
+  // Let's create a tab with some data.
+  await SiteDataTestUtils.addToIndexedDB("about:newtab", "foo", "bar", {});
+
+  ok(await checkDataForAboutURL(), "We have data for about:newtab");
+
+  // Cleaning up.
+  await Sanitizer.runSanitizeOnShutdown();
+
+  ok(await checkDataForAboutURL(), "about:newtab data is not deleted.");
+
+  // Clean up.
+  await Sanitizer.sanitize([ "cookies", "offlineApps" ]);
+
+  let principal = Services.scriptSecurityManager.createCodebasePrincipalFromOrigin("about:newtab");
+  await new Promise(aResolve => {
+    let req = Services.qms.clearStoragesForPrincipal(principal, null, false);
+    req.callback = () => { aResolve(); };
+  });
+
+  Services.perms.remove(uri, "cookie");
+});
--- a/browser/modules/Sanitizer.jsm
+++ b/browser/modules/Sanitizer.jsm
@@ -686,25 +686,33 @@ async function sanitizeOnShutdown(progre
                                 Ci.nsICookieService.ACCEPT_NORMALLY) == Ci.nsICookieService.ACCEPT_SESSION) {
     let principals = await getAllPrincipals();
     await maybeSanitizeSessionPrincipals(principals);
   }
 
 
   // Let's see if we have to forget some particular site.
   for (let permission of Services.perms.enumerator) {
-    if (permission.type == "cookie" && permission.capability == Ci.nsICookiePermission.ACCESS_SESSION) {
-      // We use just the URI here, because permissions ignore OriginAttributes.
-      let principals = await getAllPrincipals(permission.principal.URI);
-      let promises = [];
-      principals.forEach(principal => {
-        promises.push(sanitizeSessionPrincipal(principal));
-      });
-      await Promise.all(promises);
+    if (permission.type != "cookie" ||
+        permission.capability != Ci.nsICookiePermission.ACCESS_SESSION) {
+      continue;
+    }
+
+    // We consider just permissions set for http, https and file URLs.
+    if (!isSupportedURI(permission.principal.URI)) {
+      continue;
     }
+
+    // We use just the URI here, because permissions ignore OriginAttributes.
+    let principals = await getAllPrincipals(permission.principal.URI);
+    let promises = [];
+    principals.forEach(principal => {
+      promises.push(sanitizeSessionPrincipal(principal));
+    });
+    await Promise.all(promises);
   }
 
   if (Sanitizer.shouldSanitizeNewTabContainer) {
     sanitizeNewTabSegregation();
     removePendingSanitization("newtab-container");
   }
 
   if (Sanitizer.shouldSanitizeOnShutdown) {
@@ -727,29 +735,33 @@ async function getAllPrincipals(matchUri
         resolve([]);
         return;
       }
 
       let list = [];
       for (let item of request.result) {
         let principal = Services.scriptSecurityManager.createCodebasePrincipalFromOrigin(item.origin);
         let uri = principal.URI;
-        if ((!matchUri || Services.eTLD.hasRootDomain(matchUri.host, uri.host)) &&
-            (uri.scheme == "http" || uri.scheme == "https" || uri.scheme == "file")) {
+        if (!isSupportedURI(uri)) {
+          continue;
+        }
+
+        if (!matchUri || Services.eTLD.hasRootDomain(matchUri.host, uri.host)) {
           list.push(principal);
         }
       }
       resolve(list);
     });
   }).catch(() => []);
 
   let serviceWorkers = serviceWorkerManager.getAllRegistrations();
   for (let i = 0; i < serviceWorkers.length; i++) {
     let sw = serviceWorkers.queryElementAt(i, Ci.nsIServiceWorkerRegistrationInfo);
     let uri = sw.principal.URI;
+    // We don't need to check the scheme. SW are just exposed to http/https URLs.
     if (!matchUri || Services.eTLD.hasRootDomain(matchUri.host, uri.host)) {
       principals.push(sw.principal);
     }
   }
 
   // Let's take the list of unique hosts+OA from cookies.
   let enumerator = Services.cookies.enumerator;
   let hosts = new Set();
@@ -798,16 +810,21 @@ function cookiesAllowedForDomainOrSubDom
     return false;
   }
 
   for (let perm of Services.perms.enumerator) {
     if (perm.type != "cookie") {
       continue;
     }
 
+    // We consider just permissions set for http, https and file URLs.
+    if (!isSupportedURI(perm.principal.URI)) {
+      continue;
+    }
+
     // We don't care about scheme, port, and anything else.
     if (Services.eTLD.hasRootDomain(perm.principal.URI.host,
                                     principal.URI.host)) {
       return cookiesAllowedForDomainOrSubDomain(perm.principal);
     }
   }
 
   return false;
@@ -854,30 +871,33 @@ function getItemsToClearFromPrefBranch(b
  * @param options The Sanitize options
  */
 function addPendingSanitization(id, itemsToClear, options) {
   let pendingSanitizations = safeGetPendingSanitizations();
   pendingSanitizations.push({id, itemsToClear, options});
   Services.prefs.setStringPref(Sanitizer.PREF_PENDING_SANITIZATIONS,
                                JSON.stringify(pendingSanitizations));
 }
+
 function removePendingSanitization(id) {
   let pendingSanitizations = safeGetPendingSanitizations();
   let i = pendingSanitizations.findIndex(s => s.id == id);
   let [s] = pendingSanitizations.splice(i, 1);
   Services.prefs.setStringPref(Sanitizer.PREF_PENDING_SANITIZATIONS,
     JSON.stringify(pendingSanitizations));
   return s;
 }
+
 function getAndClearPendingSanitizations() {
   let pendingSanitizations = safeGetPendingSanitizations();
   if (pendingSanitizations.length)
     Services.prefs.clearUserPref(Sanitizer.PREF_PENDING_SANITIZATIONS);
   return pendingSanitizations;
 }
+
 function safeGetPendingSanitizations() {
   try {
     return JSON.parse(
       Services.prefs.getStringPref(Sanitizer.PREF_PENDING_SANITIZATIONS, "[]"));
   } catch (ex) {
     Cu.reportError("Invalid JSON value for pending sanitizations: " + ex);
     return [];
   }
@@ -890,8 +910,14 @@ async function clearData(range, flags) {
                                                flags, resolve);
     });
   } else {
     await new Promise(resolve => {
       Services.clearData.deleteData(flags, resolve);
     });
   }
 }
+
+function isSupportedURI(uri) {
+  return uri.scheme == "http" ||
+         uri.scheme == "https" ||
+         uri.scheme == "file";
+}