Bug 1309161 - Update freshness of completion in getHash. r?francois
MozReview-Commit-ID: FcpX8hTaq1g
--- 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>