Bug 1309161 - Update freshness of completion in getHash. r?francois draft
authorThomas Nguyen <tnguyen@mozilla.com>
Tue, 11 Oct 2016 17:27:36 +0800
changeset 423518 67d54be1ae805c37ccda02f51cad2a73f6c0f440
parent 421258 e8fa13708c070d1fadf488ed9d951464745b4e17
child 533471 df80018fd4bd15f47bd672bbb10906696f2931bf
push id31926
push usertnguyen@mozilla.com
push dateTue, 11 Oct 2016 09:29:12 +0000
reviewersfrancois
bugs1309161
milestone52.0a1
Bug 1309161 - Update freshness of completion in getHash. r?francois MozReview-Commit-ID: FcpX8hTaq1g
build/pgo/server-locations.txt
toolkit/components/url-classifier/Classifier.cpp
toolkit/components/url-classifier/tests/mochitest/chrome.ini
toolkit/components/url-classifier/tests/mochitest/completionAgeFrame.html
toolkit/components/url-classifier/tests/mochitest/test_completion_age.html
--- a/build/pgo/server-locations.txt
+++ b/build/pgo/server-locations.txt
@@ -169,16 +169,17 @@ https://sub.sectest1.example.org:443
 #
 http://malware.example.com:80
 http://unwanted.example.com:80
 http://tracking.example.com:80
 http://not-tracking.example.com:80
 http://tracking.example.org:80
 http://itisatracker.org:80
 http://trackertest.org:80
+http://completionage.com:80
 
 https://malware.example.com:443
 https://unwanted.example.com:443
 https://tracking.example.com:443
 https://not-tracking.example.com:443
 https://tracking.example.org:443
 
 # Bug 1281083
--- a/toolkit/components/url-classifier/Classifier.cpp
+++ b/toolkit/components/url-classifier/Classifier.cpp
@@ -1032,16 +1032,20 @@ Classifier::UpdateCache(TableUpdate* aUp
 
   auto updateV2 = TableUpdate::Cast<TableUpdateV2>(aUpdate);
   lookupCache->AddCompletionsToCache(updateV2->AddCompletes());
 
 #if defined(DEBUG)
   lookupCache->DumpCache();
 #endif
 
+  int64_t now = (PR_Now() / PR_USEC_PER_SEC);
+  LOG(("Updated completion age from getHash %s\n", table.get()));
+  mTableFreshness.Put(table, now);
+
   return NS_OK;
 }
 
 LookupCache *
 Classifier::GetLookupCache(const nsACString& aTable)
 {
   for (uint32_t i = 0; i < mLookupCaches.Length(); i++) {
     if (mLookupCaches[i]->TableName().Equals(aTable)) {
--- a/toolkit/components/url-classifier/tests/mochitest/chrome.ini
+++ b/toolkit/components/url-classifier/tests/mochitest/chrome.ini
@@ -1,23 +1,31 @@
 [DEFAULT]
 skip-if = buildapp == 'b2g' || os == 'android'
 support-files =
   allowlistAnnotatedFrame.html
   classifiedAnnotatedFrame.html
   classifiedAnnotatedPBFrame.html
   bug_1281083.html
+  gethash.sjs
+  update.sjs
+  classifierCommon.js
+  classifierHelper.js
+  evil.js
+  evil.js^headers^
+  completionAgeFrame.html
 
 [test_lookup_system_principal.html]
 [test_classified_annotations.html]
 tags = trackingprotection
 skip-if = os == 'linux' && asan # Bug 1202548 
 [test_allowlisted_annotations.html]
 tags = trackingprotection
 [test_privatebrowsing_trackingprotection.html]
 tags = trackingprotection
 [test_trackingprotection_bug1157081.html]
 tags = trackingprotection
 [test_trackingprotection_whitelist.html]
 tags = trackingprotection
 [test_safebrowsing_bug1272239.html]
 [test_donottrack.html]
 [test_classifier_changetablepref.html]
+[test_completion_age.html]
new file mode 100644
--- /dev/null
+++ b/toolkit/components/url-classifier/tests/mochitest/completionAgeFrame.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<!-- Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+<head>
+<title></title>
+</head>
+<body>
+
+<script id="badscript" data-touched="not sure" src="http://completionage.com/chrome/toolkit/components/url-classifier/tests/mochitest/evil.js" onload="this.dataset.touched = 'yes';" onerror="this.dataset.touched = 'no';"></script>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/components/url-classifier/tests/mochitest/test_completion_age.html
@@ -0,0 +1,210 @@
+<!DOCTYPE HTML>
+<!-- Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+<head>
+  <title>Test completion age</title>
+  <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="classifierHelper.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+
+<script class="testbody" type="text/javascript">
+
+let Cc = SpecialPowers.Cc;
+let Ci = SpecialPowers.Ci;
+
+let mainWindow = window.QueryInterface(Ci.nsIInterfaceRequestor)
+                       .getInterface(Ci.nsIWebNavigation)
+                       .QueryInterface(Ci.nsIDocShellTreeItem)
+                       .rootTreeItem
+                       .QueryInterface(Ci.nsIInterfaceRequestor)
+                       .getInterface(Ci.nsIDOMWindow);
+
+var listmanager = Cc["@mozilla.org/url-classifier/listmanager;1"].
+                    getService(Ci.nsIUrlListManager);
+
+const testTable = "moz-track-digest256";
+const UPDATE_URL = "http://mochi.test:8888/chrome/toolkit/components/url-classifier/tests/mochitest/update.sjs";
+const GETHASH_URL = "http://mochi.test:8888/chrome/toolkit/components/url-classifier/tests/mochitest/gethash.sjs";
+const GETHASH_URL_ERROR = "http://gethasherrrorurl.com/";
+const TEST_URL = "completionage.com/";
+const PAGE = "chrome://mochitests/content/chrome/toolkit/components/url-classifier/tests/mochitest/completionAgeFrame.html";
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://testing-common/BrowserTestUtils.jsm");
+Components.utils.import("resource://testing-common/ContentTask.jsm");
+Components.utils.import("resource://gre/modules/Task.jsm");
+
+function addCompletionToServer(list, url, server) {
+  return new Promise(function(resolve, reject) {
+    var listParam = "list=" + list;
+    var fullhashParam = "fullhash=" + hash(url);
+
+    var xhr = new XMLHttpRequest;
+    xhr.open("PUT", server + "?" +
+             listParam + "&" +
+             fullhashParam, true);
+    xhr.setRequestHeader("Content-Type", "text/plain");
+    xhr.onreadystatechange = function() {
+      if (this.readyState == this.DONE) {
+        resolve();
+      }
+    };
+    xhr.send();
+  });
+}
+
+function hash(str) {
+  function bytesFromString(str) {
+    var converter =
+      SpecialPowers.Cc["@mozilla.org/intl/scriptableunicodeconverter"]
+                       .createInstance(SpecialPowers.Ci.nsIScriptableUnicodeConverter);
+    converter.charset = "UTF-8";
+    return converter.convertToByteArray(str);
+  }
+
+  var hasher = SpecialPowers.Cc["@mozilla.org/security/hash;1"]
+                                .createInstance(SpecialPowers.Ci.nsICryptoHash);
+
+  var data = bytesFromString(str);
+  hasher.init(hasher.SHA256);
+  hasher.update(data, data.length);
+
+  return hasher.finish(true);
+}
+
+function triggerUpdate() {
+  return new Promise(function(resolve, reject) {
+    var updateFinishedObsever = {
+      observe: function(aSubject, aTopic, aData) {
+        if (aData.indexOf("nextupdatetime") >= 0) {
+          let nextupdatetime =
+            SpecialPowers.getCharPref("browser.safebrowsing.provider.mozilla.nextupdatetime");
+          if (nextupdatetime !== "1") {
+            Services.prefs.removeObserver("browser.safebrowsing.provider.mozilla.nextupdatetime",
+                                          updateFinishedObsever, false);
+            resolve();
+          }
+        }
+      }
+    }
+
+    Services.prefs.addObserver("browser.safebrowsing.provider.mozilla.nextupdatetime", updateFinishedObsever, false);
+    listmanager.disableUpdate(testTable);
+    listmanager.enableUpdate(testTable);
+    listmanager.maybeToggleUpdateChecking();
+  });
+}
+
+function whenDelayedStartupFinished(aWindow, aCallback) {
+  Services.obs.addObserver(function observer(aSubject, aTopic) {
+    if (aWindow == aSubject) {
+      Services.obs.removeObserver(observer, aTopic);
+      setTimeout(aCallback, 0);
+    }
+  }, "browser-delayed-startup-finished", false);
+}
+
+// Return a promise that resolves after the specified delay in MS
+function waitForTime(ms) {
+  return new Promise((resolve, reject) => {
+    setTimeout(resolve, ms);
+  });
+}
+
+// Set the max age of completion in second
+function setMaxCompletionAge(aAge) {
+  return new Promise(resolve => SpecialPowers.pushPrefEnv({
+    "set": [["urlclassifier.max-complete-age", aAge]],
+  }, resolve));
+}
+
+function testOnWindow(aContentPage, aCallback) {
+  return new Promise(resolve  => {
+    let win = mainWindow.OpenBrowserWindow();
+
+    Task.spawn(function* () {
+      yield new Promise(resolve => whenDelayedStartupFinished(win, resolve));
+
+      let browser = win.gBrowser.selectedBrowser;
+      yield BrowserTestUtils.loadURI(browser, aContentPage);
+      yield BrowserTestUtils.waitForContentEvent(browser, "DOMContentLoaded");
+
+      aCallback(win);
+      win.close();
+      resolve();
+    });
+  });
+}
+
+function checkBlocked(aWindow, aBlocked) {
+  var win = aWindow.content;
+  is(win.document.getElementById("badscript").dataset.touched, aBlocked ? "no" : "yes", "Should not load javascript");
+}
+
+function runTest() {
+  Task.spawn(function* () {
+    yield addCompletionToServer(testTable, TEST_URL, UPDATE_URL);
+    yield triggerUpdate();
+    yield setMaxCompletionAge("20");
+    yield waitForTime(1000);
+    yield testOnWindow(PAGE, function(browser) {
+      checkBlocked(browser, true);
+    });
+
+    // Completion expired
+    yield waitForTime(20000);
+    yield testOnWindow(PAGE, function(browser) {
+      checkBlocked(browser, false);
+    });
+
+    // Then the getHash will update the freshness of completion
+    yield classifierHelper._cleanup();
+    classifierHelper.allowCompletion(testTable, GETHASH_URL);
+    yield addCompletionToServer(testTable, TEST_URL, GETHASH_URL);
+    yield testOnWindow(PAGE, function(browser) {
+      checkBlocked(browser, true);
+    });
+
+    // Completion is cached and fresh
+    classifierHelper.allowCompletion(testTable, GETHASH_URL_ERROR);
+    yield waitForTime(1000);
+    yield testOnWindow(PAGE, function(browser) {
+      checkBlocked(browser, true);
+    });
+
+    // Completion expired
+    yield waitForTime(20000);
+    yield testOnWindow(PAGE, function(browser) {
+      checkBlocked(browser, false);
+    });
+
+    SimpleTest.finish();
+  });
+}
+
+// Set nextupdatetime to 1 to trigger an update
+SpecialPowers.pushPrefEnv(
+  {"set" : [["browser.safebrowsing.provider.mozilla.nextupdatetime", "1"],
+            ["browser.safebrowsing.provider.mozilla.lists", testTable],
+            ["privacy.trackingprotection.enabled", true],
+            ["urlclassifier.trackingTable", testTable],
+            ["channelclassifier.allowlist_example", true],
+            ["browser.safebrowsing.provider.mozilla.gethashURL", GETHASH_URL_ERROR],
+            ["browser.safebrowsing.provider.mozilla.updateURL", UPDATE_URL]]},
+  runTest);
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+<iframe id="testFrame" width="100%" height="100%" onload=""></iframe>
+</body>
+</html>