Bug 1288633 - Add SafeBrowsing report false positive URL test.r=francois draft
authorThomas Nguyen <tnguyen@mozilla.com>
Tue, 21 Feb 2017 11:25:04 +0800
changeset 487166 8428f4665c24387d5ad6fb09693f7919f1fca0f8
parent 487165 ea1e2d52ff9ada8f865fb7a280ac4aa911bb004d
child 546406 ddc9f72de0d30c8864b2739eebab78b9128cb5e3
push id46163
push usertnguyen@mozilla.com
push dateTue, 21 Feb 2017 03:27:24 +0000
reviewersfrancois
bugs1288633
milestone54.0a1
Bug 1288633 - Add SafeBrowsing report false positive URL test.r=francois MozReview-Commit-ID: JEMLug9M85Q
testing/firefox-ui/tests/functional/security/test_safe_browsing_notification.py
toolkit/components/url-classifier/tests/mochitest/chrome.ini
toolkit/components/url-classifier/tests/mochitest/mochitest.ini
toolkit/components/url-classifier/tests/mochitest/report.sjs
toolkit/components/url-classifier/tests/mochitest/test_classifier.html
toolkit/components/url-classifier/tests/mochitest/test_classifier_match.html
toolkit/components/url-classifier/tests/mochitest/test_reporturl.html
--- a/testing/firefox-ui/tests/functional/security/test_safe_browsing_notification.py
+++ b/testing/firefox-ui/tests/functional/security/test_safe_browsing_notification.py
@@ -20,24 +20,28 @@ class TestSafeBrowsingNotificationBar(Pu
                 # First two properties are not needed,
                 # since these errors are not reported
                 'button_property': None,
                 'report_page': None,
                 'unsafe_page': 'https://www.itisatrap.org/firefox/unwanted.html'
             },
             # Phishing URL info
             {
-                'button_property': 'safebrowsing.notADeceptiveSiteButton.label',
-                'report_page': 'google.com/safebrowsing/report_error',
+                # First two properties are not needed, because only url which
+                # matches google's list could be reported
+                'button_property': None,
+                'report_page': None,
                 'unsafe_page': 'https://www.itisatrap.org/firefox/its-a-trap.html'
             },
             # Malware URL object
             {
-                'button_property': 'safebrowsing.notAnAttackButton.label',
-                'report_page': 'stopbadware.org',
+                # First two properties are not needed, because only url which
+                # matches google's list could be reported
+                'button_property': None,
+                'report_page': None,
                 'unsafe_page': 'https://www.itisatrap.org/firefox/its-an-attack.html'
             }
         ]
 
         self.marionette.set_pref('browser.safebrowsing.phishing.enabled', True)
         self.marionette.set_pref('browser.safebrowsing.malware.enabled', True)
 
         # Give the browser a little time, because SafeBrowsing.jsm takes a while
--- a/toolkit/components/url-classifier/tests/mochitest/chrome.ini
+++ b/toolkit/components/url-classifier/tests/mochitest/chrome.ini
@@ -1,23 +1,28 @@
 [DEFAULT]
 skip-if = os == 'android'
 support-files =
   allowlistAnnotatedFrame.html
   classifiedAnnotatedFrame.html
   classifiedAnnotatedPBFrame.html
   bug_1281083.html
+  report.sjs
+  gethash.sjs
+  classifierCommon.js
+  classifierHelper.js
 
 [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_reporturl.html]
--- a/toolkit/components/url-classifier/tests/mochitest/mochitest.ini
+++ b/toolkit/components/url-classifier/tests/mochitest/mochitest.ini
@@ -28,13 +28,14 @@ support-files =
   bad.css^headers^
   gethash.sjs
   gethashFrame.html
   tracker.js
   seek.webm
 
 [test_classifier.html]
 skip-if = (os == 'linux' && debug) #Bug 1199778
+[test_classifier_match.html]
 [test_classifier_worker.html]
 [test_classify_ping.html]
 [test_classify_track.html]
 [test_gethash.html]
 [test_bug1254766.html]
new file mode 100644
--- /dev/null
+++ b/toolkit/components/url-classifier/tests/mochitest/report.sjs
@@ -0,0 +1,69 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const SJS = "report.sjs?";
+const REDIRECT = "mochi.test:8888/chrome/toolkit/components/url-classifier/tests/mochitest/" + SJS;
+
+Components.utils.importGlobalProperties(["URLSearchParams"]);
+
+function createBlockedIframePage() {
+  return `<!DOCTYPE HTML>
+          <html>
+          <head>
+            <title></title>
+          </head>
+          <body>
+            <iframe id="phishingFrame" ></iframe>
+          </body>
+          </html>`;
+}
+
+function createPage() {
+  return `<!DOCTYPE HTML>
+          <html>
+          <head>
+            <title>Hello World</title>
+          </head>
+          <body>
+            <script></script>
+          </body>
+          </html>`;
+}
+
+function handleRequest(request, response)
+{
+  var params = new URLSearchParams(request.queryString);
+  var action = params.get("action");
+
+  if (action === "create-blocked-iframe") {
+    response.setHeader('Cache-Control', 'no-cache', false);
+    response.setHeader('Content-Type', 'text/html; charset=utf-8', false);
+    response.write(createBlockedIframePage());
+    return;
+  }
+
+  if (action === "redirect") {
+    response.setHeader('Cache-Control', 'no-cache', false);
+    response.setHeader('Content-Type', 'text/html; charset=utf-8', false);
+    response.write(createPage());
+    return;
+  }
+
+  if (action === "reporturl") {
+    response.setHeader('Cache-Control', 'no-cache', false);
+    response.setHeader('Content-Type', 'text/html; charset=utf-8', false);
+    response.write(createPage());
+    return;
+  }
+
+  if (action === "create-blocked-redirect") {
+    params.delete("action");
+    params.append("action", "redirect");
+    response.setStatusLine("1.1", 302, "found");
+    response.setHeader("Location",  "https://" + REDIRECT + params.toString(), false);
+    return;
+  }
+
+  response.write("I don't know action " + action);
+  return;
+}
--- a/toolkit/components/url-classifier/tests/mochitest/test_classifier.html
+++ b/toolkit/components/url-classifier/tests/mochitest/test_classifier.html
@@ -152,16 +152,17 @@ function testService() {
 
 SpecialPowers.pushPrefEnv(
   {"set" : [["urlclassifier.malwareTable", "test-malware-simple,test-unwanted-simple"],
             ["urlclassifier.phishTable", "test-phish-simple"],
             ["urlclassifier.downloadBlockTable", "test-block-simple"],
             ["urlclassifier.trackingTable", "test-track-simple"],
             ["privacy.trackingprotection.annotate_channels", true]]},
   function() {
+    netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
     classifierHelper.waitForInit()
       .then(() => classifierHelper.addUrlToDB(testData))
       .then(updateSuccess)
       .catch(err => {
         updateError(err);
       })
       .then(testService)
       .then(loadTestFrame);
new file mode 100644
--- /dev/null
+++ b/toolkit/components/url-classifier/tests/mochitest/test_classifier_match.html
@@ -0,0 +1,195 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test the URI Classifier Matched Info (bug 1288633) </title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="classifierHelper.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+
+<script class="testbody" type="application/javascript">
+var Cc = SpecialPowers.Cc;
+var Ci = SpecialPowers.Ci;
+var Cr = SpecialPowers.Cr;
+
+var inputDatas = [
+  { url: "malware.example.com/",
+    db: "mochi-block-simple",
+  },
+  { url: "malware1.example.com/",
+    db: "mochi1-block-simple",
+  },
+  { url: "malware1.example.com/",
+    db: "mochi1-malware-simple",
+    provider: "mozilla"
+  },
+  { url: "malware2.example.com/",
+    db: "mochi2-unwanted-simple",
+    provider: "mozilla"
+  },
+  { url: "malware2.example.com/",
+    db: "mochi2-malware-simple",
+    provider: "mozilla"
+  },
+  { url: "malware3.example.com/",
+    db: "mochig3-malware-simple",
+    provider: "google"
+  },
+  { url: "malware3.example.com/",
+    db: "mochim3-malware-simple",
+    provider: "mozilla"
+  },
+];
+
+function hashPrefix(str) {
+  function bytesFromString(str) {
+    let converter =
+      Cc["@mozilla.org/intl/scriptableunicodeconverter"]
+                       .createInstance(Ci.nsIScriptableUnicodeConverter);
+    converter.charset = "UTF-8";
+    return converter.convertToByteArray(str);
+  }
+
+  let hasher = Cc["@mozilla.org/security/hash;1"]
+                               .createInstance(Ci.nsICryptoHash);
+
+  let data = bytesFromString(str);
+  hasher.init(hasher.SHA256);
+  hasher.update(data, data.length);
+
+  return hasher.finish(false).slice(0, 4);
+}
+
+var testDatas = [
+  // Match empty provider
+  { url: "http://malware.example.com",
+    expect: { error: Cr.NS_ERROR_BLOCKED_URI,
+              table: "mochi-block-simple",
+              provider: "",
+              prefix: (function(){
+                return hashPrefix("malware.example.com/");
+              })(),
+            }
+  },
+  // Match multiple tables, only one has valid provider
+  { url: "http://malware1.example.com",
+    expect: { error: Cr.NS_ERROR_MALWARE_URI,
+              table: "mochi1-malware-simple",
+              provider: "mozilla",
+              prefix: (function(){
+                return hashPrefix("malware1.example.com/");
+              })(),
+            }
+  },
+  // Match multiple tables, handle order
+  { url: "http://malware2.example.com",
+    expect: { error: Cr.NS_ERROR_MALWARE_URI,
+              table: "mochi2-malware-simple",
+              provider: "mozilla",
+              prefix: (function(){
+                return hashPrefix("malware2.example.com/");
+              })(),
+            }
+  },
+  // Match multiple tables, handle order
+  { url: "http://malware3.example.com",
+    expect: { error: Cr.NS_ERROR_MALWARE_URI,
+              table: "mochig3-malware-simple",
+              provider: "google",
+              prefix: (function(){
+                return hashPrefix("malware3.example.com/");
+              })(),
+            }
+  },
+
+];
+
+SimpleTest.waitForExplicitFinish();
+
+function setupTestData(datas) {
+  let prefValues = {};
+  for (let data of datas) {
+    if (!data.provider) {
+      continue;
+    }
+    let providerPref = "browser.safebrowsing.provider." + data.provider + ".lists";
+    let prefValue;
+    if (!prefValues[providerPref]) {
+      prefValue = data.db;
+    } else {
+      prefValue = prefValues[providerPref] + "," + data.db;
+    }
+    prefValues[providerPref] = prefValue;
+  }
+
+  // Convert map to array
+  let prefArray = [];
+  for (var pref in prefValues) {
+    prefArray.push([pref, prefValues[pref]]);
+  }
+
+  let activeTablePref = "urlclassifier.malwareTable";
+  let activeTable = SpecialPowers.getCharPref(activeTablePref);
+  for (let data of datas) {
+      activeTable += "," + data.db;
+  }
+  prefArray.push([activeTablePref, activeTable]);
+
+  return SpecialPowers.pushPrefEnv({set: prefArray});
+}
+
+function runTest() {
+  return new Promise(resolve => {
+    let service = Cc["@mozilla.org/url-classifier/dbservice;1"].
+                getService(Ci.nsIURIClassifier);
+    let ssm = Cc["@mozilla.org/scriptsecuritymanager;1"].
+                getService(Ci.nsIScriptSecurityManager);
+    let ios = Cc["@mozilla.org/network/io-service;1"].
+                getService(Ci.nsIIOService);
+    function runNextTest() {
+      if (!testDatas.length) {
+        resolve();
+        return;
+      }
+      let test = testDatas.shift();
+      let uri = ios.newURI(test.url, null, null);
+      let prin = ssm.createCodebasePrincipal(uri, {});
+      service.classify(prin, false, function(errorCode, table, provider, prefix) {
+        is(errorCode, test.expect.error, `Test url ${test.url} correct error`);
+        is(table, test.expect.table, `Test url ${test.url} correct table`);
+        is(provider, test.expect.provider, `Test url ${test.url} correct provider`);
+        is(prefix, btoa(test.expect.prefix), `Test url ${test.url} correct prefix`);
+        runNextTest();
+      });
+    }
+    runNextTest();
+  });
+}
+
+SpecialPowers.pushPrefEnv(
+  {"set" : [["browser.safebrowsing.malware.enabled", true]]},
+  function() {
+    netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+    classifierHelper.waitForInit()
+      .then(() => setupTestData(inputDatas))
+      .then(() => classifierHelper.addUrlToDB(inputDatas))
+      .then(runTest)
+      .then(function() {
+        SimpleTest.finish();
+      }).catch(function(e) {
+        ok(false, "Some tests failed with error " + e);
+        SimpleTest.finish();
+      });
+  });
+</script>
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/components/url-classifier/tests/mochitest/test_reporturl.html
@@ -0,0 +1,227 @@
+<!DOCTYPE HTML>
+<!-- Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+<head>
+  <title>Test report matched URL info (Bug #1288633)</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 Ci = Components.interfaces;
+const Cu = Components.utils;
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://testing-common/BrowserTestUtils.jsm");
+Cu.import("resource://testing-common/ContentTask.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+
+var mainWindow = window.QueryInterface(Ci.nsIInterfaceRequestor)
+                    .getInterface(Ci.nsIWebNavigation)
+                    .QueryInterface(Ci.nsIDocShellTreeItem)
+                    .rootTreeItem
+                    .QueryInterface(Ci.nsIInterfaceRequestor)
+                    .getInterface(Ci.nsIDOMWindow);
+const SJS = "mochi.test:8888/chrome/toolkit/components/url-classifier/tests/mochitest/report.sjs";
+const BASE_URL = "http://" + SJS + "?";
+
+var pushPrefs = (...p) => SpecialPowers.pushPrefEnv({set: p});
+
+function addUrlToDB(list, url) {
+  let testData = [{ db: list, url}];
+
+  return classifierHelper.addUrlToDB(testData)
+    .catch(function(err) {
+      ok(false, "Couldn't update classifier. Error code: " + err);
+      // Abort test.
+      SimpleTest.finish();
+    });
+}
+
+function setupTestData(data) {
+  let promises = [];
+  let providerList = "browser.safebrowsing.provider." + data.provider + ".lists";
+  if (!Services.prefs.prefHasUserValue(providerList)) {
+    promises.push(pushPrefs([providerList, data.list]));
+  } else {
+    let pref = SpecialPowers.getCharPref(providerList);
+    pref += "," + data.list;
+    promises.push(pushPrefs([providerList, pref]));
+  }
+
+  let activeTablePref = "urlclassifier.phishTable";
+  let activeTable = SpecialPowers.getCharPref(activeTablePref);
+      activeTable += "," + data.list;
+  promises.push(pushPrefs([activeTablePref, activeTable]));
+
+  promises.push(addUrlToDB(data.list, data.testUrl));
+  return Promise.all(promises);
+}
+
+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);
+}
+
+function testOnWindow(aTestData, aCallback, aTestCreater) {
+  return new Promise(resolve  => {
+    let win = mainWindow.OpenBrowserWindow();
+
+    Task.spawn(function* () {
+      yield new Promise(resolve => whenDelayedStartupFinished(win, resolve));
+
+      let browser = win.gBrowser.selectedBrowser;
+      aTestCreater(win, browser, aTestData.topUrl, aTestData.testUrl);
+
+      let notification = yield BrowserTestUtils.waitForNotificationBar(win.gBrowser, browser, "blocked-badware-page");
+      ok(notification, "Notification box should be displayed");
+
+      let buttons = notification.getElementsByTagName("button");
+      let button = buttons[1];
+      if (aTestData.provider != "google" && aTestData.provider != "google4") {
+        is(button, undefined, "Report button should not be showed");
+        win.close();
+        resolve();
+      }
+
+      button.click();
+
+      let newTabBrowser = win.gBrowser.selectedTab.linkedBrowser;
+      yield BrowserTestUtils.browserLoaded(newTabBrowser);
+
+      aCallback(newTabBrowser);
+      win.close();
+      resolve();
+    });
+  });
+}
+
+var createBlockedIframe = function(aWindow, aBrowser, aTopUrl, aUrl) {
+  Task.spawn(function* () {
+    yield BrowserTestUtils.loadURI(aBrowser, aTopUrl);
+    yield BrowserTestUtils.browserLoaded(aBrowser);
+
+    yield ContentTask.spawn(aBrowser, aUrl, function* (aUrl) {
+      return new Promise(resolve => {
+        let listener = e => {
+          removeEventListener('AboutBlockedLoaded', listener, false, true);
+          resolve();
+        };
+        addEventListener('AboutBlockedLoaded', listener, false, true);
+        let frame = content.document.getElementById("phishingFrame");
+        frame.setAttribute('src', "http://" + aUrl);
+      });
+    });
+
+    let doc = aWindow.gBrowser.contentDocument.getElementsByTagName('iframe')[0].contentDocument;
+    let ignoreButton = doc.getElementById("ignoreWarningButton");
+    ok(ignoreButton, "ignoreWarningButton should exist");
+    ignoreButton.click();
+  });
+};
+
+var createBlockedPage = function(aWindow, aBrowser, aTopUrl, aUrl) {
+  Task.spawn(function* () {
+    yield BrowserTestUtils.loadURI(aBrowser, aTopUrl);
+    yield BrowserTestUtils.waitForContentEvent(aBrowser, "DOMContentLoaded")
+
+    let doc = aWindow.gBrowser.contentDocument;
+    let ignoreButton = doc.getElementById("ignoreWarningButton");
+    ok(ignoreButton, "ignoreWarningButton should exist");
+    ignoreButton.click();
+  });
+};
+
+function checkReportURL(aReportBrowser, aUrl) {
+    let expectedReportUrl = BASE_URL + "action=reporturl&reporturl=" + encodeURIComponent(aUrl);
+    is(aReportBrowser.contentDocument.location.href, expectedReportUrl, "Correct report URL");
+}
+
+var testDatas = [
+  { topUrl: "http://itisaphishingsite.org/phishing.html",
+    testUrl: "itisaphishingsite.org/phishing.html",
+    list: "mochi1-phish-simple",
+    provider: "google",
+    blockCreater : createBlockedPage,
+    expectedReportUri: "http://itisaphishingsite.org/phishing.html"
+  },
+
+  // Non-google provider, no report button is showed
+  { topUrl: "http://fakeitisaphishingsite.org/phishing.html",
+    testUrl: "fakeitisaphishingsite.org/phishing.html",
+    list: "fake-phish-simple",
+    provider: "fake",
+    blockCreater : createBlockedPage
+  },
+
+  // Iframe case:
+  // A top level page at
+  // http://mochi.test:8888/chrome/toolkit/components/url-classifier/tests/mochitest/report.sjs?action=create-blocked-iframe
+  // contains an iframe to http://phishing.example.com/test.html (blocked).
+
+  { topUrl: "http://mochi.test:8888/chrome/toolkit/components/url-classifier/tests/mochitest/report.sjs?action=create-blocked-iframe",
+    testUrl: "phishing.example.com/test.html",
+    list: "mochi2-phish-simple",
+    provider: "google4",
+    blockCreater : createBlockedIframe,
+    expectedReportUri: "http://phishing.example.com/test.html"
+  },
+
+  // Redirect case:
+  // A top level page at
+  // http://prefixexample.com/chrome/toolkit/components/url-classifier/tests/mochitest/report.sjs?action=create-blocked-redirect (blocked)
+  // will get redirected to
+  // https://mochi.test:8888/chrome/toolkit/components/url-classifier/tests/mochitest/report.sjs?action=create-blocked-redirect.
+  { topUrl: "http://prefixexample.com/chrome/toolkit/components/url-classifier/tests/mochitest/report.sjs?action=create-blocked-redirect",
+    testUrl: "prefixexample.com/chrome/toolkit/components/url-classifier/tests/mochitest/report.sjs?action=create-blocked-redirect",
+    list: "mochi3-phish-simple",
+    provider: "google4",
+    blockCreater : createBlockedPage,
+    expectedReportUri: "http://prefixexample.com/chrome/toolkit/components/url-classifier/tests/mochitest/report.sjs"
+  },
+
+];
+
+SpecialPowers.pushPrefEnv(
+  {"set" : [["browser.safebrowsing.provider.google.reportPhishMistakeURL", BASE_URL + "action=reporturl&reporturl="],
+            ["browser.safebrowsing.provider.google4.reportPhishMistakeURL", BASE_URL + "action=reporturl&reporturl="],
+            ["browser.safebrowsing.phishing.enabled", true]]},
+  test);
+
+function test() {
+  Task.spawn(function* () {
+    yield classifierHelper.waitForInit();
+
+    for (let testData of testDatas) {
+      yield setupTestData(testData);
+      yield testOnWindow(testData, function(browser) {
+        checkReportURL(browser, testData.expectedReportUri);
+      }, testData.blockCreater);
+
+      yield classifierHelper._cleanup();
+    }
+
+    SimpleTest.finish();
+  });
+}
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+
+</pre>
+<iframe id="testFrame" width="100%" height="100%" onload=""></iframe>
+</body>
+</html>