Bug 1281083 - P2. Testcase for changing the urlclassifier.*Table. r=francois
authordimi <dlee@mozilla.com>
Mon, 18 Jul 2016 22:31:34 +0800
changeset 389872 a0266bb6db011a81fd7e6425638b33d7bcb42025
parent 389871 158b91a92d4cac7db55806151e48328bc327a9ca
child 389873 2ba4e664fa4e0f1c13dbf6db3b4c1c8a33712019
push id23554
push userpbrosset@mozilla.com
push dateWed, 20 Jul 2016 09:13:33 +0000
reviewersfrancois
bugs1281083
milestone50.0a1
Bug 1281083 - P2. Testcase for changing the urlclassifier.*Table. r=francois MozReview-Commit-ID: Ge0NSpyEb82
build/pgo/server-locations.txt
toolkit/components/url-classifier/tests/mochitest/bug_1281083.html
toolkit/components/url-classifier/tests/mochitest/chrome.ini
toolkit/components/url-classifier/tests/mochitest/mochitest.ini
toolkit/components/url-classifier/tests/mochitest/test_classifier_changetablepref.html
toolkit/components/url-classifier/tests/mochitest/update.sjs
--- a/build/pgo/server-locations.txt
+++ b/build/pgo/server-locations.txt
@@ -176,16 +176,19 @@ http://itisatracker.org:80
 http://trackertest.org: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
+http://bug1281083.example.com:80
+
 # Bug 483437, 484111
 https://www.bank1.com:443           privileged,cert=escapeattack1
 
 #
 # CONNECT for redirproxy results in a 302 redirect to
 # test1.example.com
 #
 https://redirproxy.example.com:443          privileged,redir=test1.example.com
new file mode 100644
--- /dev/null
+++ b/toolkit/components/url-classifier/tests/mochitest/bug_1281083.html
@@ -0,0 +1,35 @@
+<html>
+<head>
+<title></title>
+
+<script type="text/javascript">
+
+var scriptItem = "untouched";
+
+function checkLoads() {
+  // Make sure the javascript did not load.
+  window.parent.is(scriptItem, "untouched", "Should not load bad javascript");
+
+  // Call parent.loadTestFrame again to test classification metadata in HTTP
+  // cache entries.
+  if (window.parent.firstLoad) {
+    window.parent.info("Reloading from cache...");
+    window.parent.firstLoad = false;
+    window.parent.loadTestFrame();
+    return;
+  }
+
+  // End (parent) test.
+  window.parent.SimpleTest.finish();
+}
+
+</script>
+
+<!-- Try loading from a malware javascript URI -->
+<script type="text/javascript" src="http://bug1281083.example.com/tests/toolkit/components/url-classifier/tests/mochitest/evil.js"></script>
+
+</head>
+
+<body onload="checkLoads()">
+</body>
+</html>
--- a/toolkit/components/url-classifier/tests/mochitest/chrome.ini
+++ b/toolkit/components/url-classifier/tests/mochitest/chrome.ini
@@ -1,20 +1,22 @@
 [DEFAULT]
 skip-if = buildapp == 'b2g' || os == 'android'
 support-files =
   allowlistAnnotatedFrame.html
   classifiedAnnotatedFrame.html
   classifiedAnnotatedPBFrame.html
+  bug_1281083.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_donottrack.html]
+[test_classifier_changetablepref.html]
--- a/toolkit/components/url-classifier/tests/mochitest/mochitest.ini
+++ b/toolkit/components/url-classifier/tests/mochitest/mochitest.ini
@@ -17,14 +17,15 @@ support-files =
   unwantedWorker.js
   vp9.webm
   whitelistFrame.html
   workerFrame.html
   ping.sjs
   basic.vtt
   dnt.html
   dnt.sjs
+  update.sjs
 
 [test_classifier.html]
 skip-if = (os == 'linux' && debug) #Bug 1199778
 [test_classifier_worker.html]
 [test_classify_ping.html]
 [test_classify_track.html]
new file mode 100644
--- /dev/null
+++ b/toolkit/components/url-classifier/tests/mochitest/test_classifier_changetablepref.html
@@ -0,0 +1,149 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Bug 1281083 - Changing the urlclassifier.*Table prefs doesn't take effect before the next browser restart.</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">
+
+const testTable = "moz-track-digest256";
+const UPDATE_URL = "http://mochi.test:8888/tests/toolkit/components/url-classifier/tests/mochitest/update.sjs";
+
+var Cc = SpecialPowers.Cc;
+var Ci = SpecialPowers.Ci;
+
+var prefService = Cc["@mozilla.org/preferences-service;1"]
+                  .getService(Ci.nsIPrefService);
+
+var timer = Cc["@mozilla.org/timer;1"]
+            .createInstance(Ci.nsITimer);
+
+// If default preference contain the table we want to test,
+// We should change test table to a different one.
+var trackingTables = SpecialPowers.getCharPref("urlclassifier.trackingTable").split(",");
+ok(!trackingTables.includes(testTable), "test table should not be in the preference");
+
+var listmanager = Cc["@mozilla.org/url-classifier/listmanager;1"].
+                    getService(Ci.nsIUrlListManager);
+
+is(listmanager.getGethashUrl(testTable), "",
+   "gethash url for test table should be empty before setting to preference");
+
+function loadTestFrame() {
+  // gethash url of test table "moz-track-digest256" should be updated
+  // after setting preference.
+  var url = listmanager.getGethashUrl(testTable);
+  var expected = SpecialPowers.getCharPref("browser.safebrowsing.provider.mozilla.gethashURL");
+
+  is(url, expected, testTable + " matches its gethash url");
+
+  // Trigger update
+  listmanager.disableUpdate(testTable);
+  listmanager.enableUpdate(testTable);
+  listmanager.maybeToggleUpdateChecking();
+
+  // We wait until "nextupdattime" was set as a signal that update is complete.
+  waitForUpdateSuccess(function() {
+    document.getElementById("testFrame").src = "bug_1281083.html";
+  });
+}
+
+function waitForUpdateSuccess(callback) {
+  let nextupdatetime =
+    SpecialPowers.getCharPref("browser.safebrowsing.provider.mozilla.nextupdatetime");
+
+  if (nextupdatetime !== "1") {
+    callback();
+    return;
+  }
+
+  timer.initWithCallback(function() {
+    waitForUpdateSuccess(callback);
+  }, 10, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
+}
+
+function addCompletionToServer(list, url) {
+  return new Promise(function(resolve, reject) {
+    var listParam = "list=" + list;
+    var fullhashParam = "fullhash=" + hash(url);
+
+    var xhr = new XMLHttpRequest;
+    xhr.open("PUT", UPDATE_URL + "?" +
+             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 runTest() {
+  /**
+   * In this test we try to modify only urlclassifier.*Table preference to see if
+   * url specified in the table will be blocked after update.
+   */
+  var pushPrefPromise = SpecialPowers.pushPrefEnv(
+    {"set" : [["urlclassifier.trackingTable", testTable]]});
+
+  // To make sure url is not blocked by an already blocked url.
+  // Here we use non-tracking.example.com as a tracked url.
+  // Since this table is only used in this bug, so it won't affect other testcases.
+  var addCompletePromise =
+    addCompletionToServer(testTable, "bug1281083.example.com/");
+
+  Promise.all([pushPrefPromise, addCompletePromise])
+    .then(() => {
+      loadTestFrame();
+    });
+}
+
+// Set nextupdatetime to 1 to trigger an update
+SpecialPowers.pushPrefEnv(
+  {"set" : [["privacy.trackingprotection.enabled", true],
+            ["channelclassifier.allowlist_example", true],
+            ["browser.safebrowsing.provider.mozilla.nextupdatetime", "1"],
+            ["browser.safebrowsing.provider.mozilla.lists", testTable],
+            ["browser.safebrowsing.provider.mozilla.updateURL", UPDATE_URL]]},
+  runTest);
+
+// Expected finish() call is in "bug_1281083.html".
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+<iframe id="testFrame" width="100%" height="100%" onload=""></iframe>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/toolkit/components/url-classifier/tests/mochitest/update.sjs
@@ -0,0 +1,114 @@
+const CC = Components.Constructor;
+const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
+                             "nsIBinaryInputStream",
+                             "setInputStream");
+
+function handleRequest(request, response)
+{
+  var query = {};
+  request.queryString.split('&').forEach(function (val) {
+    var idx = val.indexOf('=');
+    query[val.slice(0, idx)] = unescape(val.slice(idx + 1));
+  });
+
+  // Store fullhash in the server side.
+  if ("list" in query && "fullhash" in query) {
+    // In the server side we will store:
+    // 1. All the full hashes for a given list
+    // 2. All the lists we have right now
+    // data is separate by '\n'
+    let list = query["list"];
+    let hashes = getState(list);
+
+    let hash = base64ToString(query["fullhash"]);
+    hashes += hash + "\n";
+    setState(list, hashes);
+
+    let lists = getState("lists");
+    if (lists.indexOf(list) == -1) {
+      lists += list + "\n";
+      setState("lists", lists);
+    }
+
+    return;
+  }
+
+  var body = new BinaryInputStream(request.bodyInputStream);
+  var avail;
+  var bytes = [];
+
+  while ((avail = body.available()) > 0) {
+    Array.prototype.push.apply(bytes, body.readByteArray(avail));
+  }
+
+  var responseBody = parseV2Request(bytes);
+
+  response.setHeader("Content-Type", "text/plain", false);
+  response.write(responseBody);
+}
+
+function parseV2Request(bytes) {
+  var table = String.fromCharCode.apply(this, bytes).slice(0,-2);
+
+  var ret = "";
+  getState("lists").split("\n").forEach(function(list) {
+    if (list == table) {
+      var completions = getState(list).split("\n");
+      ret += "n:1000\n"
+      ret += "i:" + list + "\n";
+      ret += "a:1:32:" + 32*(completions.length - 1) + "\n";
+
+      for (var completion of completions) {
+        ret += completion;
+      }
+    }
+  });
+
+  return ret;
+}
+
+/* Convert Base64 data to a string */
+const toBinaryTable = [
+    -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
+    -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
+    -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63,
+    52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1, 0,-1,-1,
+    -1, 0, 1, 2,  3, 4, 5, 6,  7, 8, 9,10, 11,12,13,14,
+    15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1,
+    -1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
+    41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1
+];
+const base64Pad = '=';
+
+function base64ToString(data) {
+    var result = '';
+    var leftbits = 0; // number of bits decoded, but yet to be appended
+    var leftdata = 0; // bits decoded, but yet to be appended
+
+    // Convert one by one.
+    for (var i = 0; i < data.length; i++) {
+        var c = toBinaryTable[data.charCodeAt(i) & 0x7f];
+        var padding = (data[i] == base64Pad);
+        // Skip illegal characters and whitespace
+        if (c == -1) continue;
+
+        // Collect data into leftdata, update bitcount
+        leftdata = (leftdata << 6) | c;
+        leftbits += 6;
+
+        // If we have 8 or more bits, append 8 bits to the result
+        if (leftbits >= 8) {
+            leftbits -= 8;
+            // Append if not padding.
+            if (!padding)
+                result += String.fromCharCode((leftdata >> leftbits) & 0xff);
+            leftdata &= (1 << leftbits) - 1;
+        }
+    }
+
+    // If there are any bits left, the base64 string was corrupted
+    if (leftbits)
+        throw Components.Exception('Corrupted base64 string');
+
+    return result;
+}