Bug 1313595 - Missing interface in HSTSPrimerListener r?mayhemer draft
authorKate McKinley <kmckinley@mozilla.com>
Fri, 25 Nov 2016 15:38:18 +0900
changeset 443828 3eba2be4c588518c062f2a9c84ce5acec0fb06f0
parent 443827 d8622e7653939c01c3ba260cc562506ca65939df
child 538147 2c29ce03fb88baec29af110503674f0389117f27
push id37100
push userbmo:kmckinley@mozilla.com
push dateFri, 25 Nov 2016 07:41:04 +0000
reviewersmayhemer
bugs1313595
milestone53.0a1
Bug 1313595 - Missing interface in HSTSPrimerListener r?mayhemer MozReview-Commit-ID: Cdyyxv4dxyo
dom/security/test/hsts/file_hsts_priming_timeout.html
dom/security/test/hsts/file_timeout_host_frame.html
dom/security/test/hsts/file_timeout_server.sjs
dom/security/test/hsts/mochitest.ini
dom/security/test/hsts/test_hsts_priming_no-timeout.html
dom/security/test/hsts/test_hsts_priming_timeout.html
dom/security/test/moz.build
netwerk/protocol/http/HSTSPrimerListener.cpp
new file mode 100644
--- /dev/null
+++ b/dom/security/test/hsts/file_hsts_priming_timeout.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1313595
+Test that a timeout happens correctly
+-->
+<head>
+  <title>Test for Bug 1313595</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1313595">Mozilla Bug 1313595</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<iframe id = "content_iframe"></iframe>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+var timeout = 3000;
+
+function processEvent(ev) {
+  console.log("processing event %o", ev);
+
+  is(ev.data.result, "failed", "The load should fail");
+
+  ok(Math.abs(ev.data.timeout - timeout) < (timeout * .10), "The error should happen because of timeout.");
+
+  SpecialPowers.popPrefEnv();
+  SimpleTest.finish();
+}
+
+window.addEventListener("message", processEvent, false);
+
+SpecialPowers.pushPrefEnv({'set': [['security.mixed_content.hsts_priming_request_timeout', 3000]]});
+SimpleTest.waitForExplicitFinish();
+
+// save this for last so that our listeners are registered.
+// ... this loads the testbed of good and bad requests.
+let content_iframe = document.getElementById('content_iframe');
+content_iframe.onload = function() {
+  window.postMessage("Go", "https://example.com");
+};
+content_iframe.src = 'https://example.com/tests/dom/security/test/hsts/file_timeout_host_frame.html';
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/security/test/hsts/file_timeout_host_frame.html
@@ -0,0 +1,57 @@
+<html>
+  <head>
+  <script type="text/javascript">
+    var start;
+    var target;
+
+    function parse_query_string() {
+      var q = {};
+      document.location.search.substr(1).
+        split('&').forEach(function (item, idx, ar) {
+          let [k, v] = item.split('=');
+          q[k] = unescape(v);
+        });
+      return q;
+    }
+
+    var args = parse_query_string();
+
+    function run(ev) {
+      if (ev.origin != "http://mochi.test:8888") {
+        return;
+      }
+
+      let scr = document.createElement("script");
+      target = ev.source;
+
+      scr.onerror = fail;
+      scr.onload = success;
+      scr.type = "text/javascript";
+      scr.src = "http://example.com/tests/dom/security/test/hsts/file_timeout_server.sjs?timeout="+args['timeout'];
+      let content = document.getElementById("content");
+      start = performance.now();
+      content.appendChild(scr);
+    }
+
+    function get_timeout() {
+      return (performance.now() - start);
+    }
+
+    function success() {
+      var elapsed = get_timeout();
+      console.log("Success in " + elapsed + " ms");
+      target.postMessage({result: 'success', timeout: elapsed}, "http://mochi.test:8888");
+    }
+    function fail() {
+      var elapsed = get_timeout();
+      console.log("Failed in " + elapsed + " ms");
+      target.postMessage({result: 'failed', timeout: elapsed}, "http://mochi.test:8888");
+    }
+
+    window.addEventListener("message", run, false);
+  </script>
+  </head>
+  <body>
+    <div id="content"></div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/security/test/hsts/file_timeout_server.sjs
@@ -0,0 +1,26 @@
+// SJS file for HSTS priming timeout mochitest 
+
+Components.utils.import("resource://gre/modules/NetUtil.jsm");
+Components.utils.importGlobalProperties(["URLSearchParams"]);
+
+function handleRequest(request, response)
+{
+  const query = new URLSearchParams(request.queryString);
+  // timeout in ms
+  var timeout = query.get('timeout');
+  response.processAsync();
+
+  timer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer);
+  timer.initWithCallback(function()
+    {
+      if (!response) {
+        return;
+      }
+      // avoid confusing cache behaviors
+      response.setHeader("Cache-Control", "no-cache", false);
+      response.setHeader("Content-Type", "text/javascript", false);
+      // never sends Strict-Transport-Security
+      response.write('console.log("timeout: "+'+timeout+');');
+      response.finish();
+    }, timeout, Components.interfaces.nsITimer.TYPE_ONE_SHOT); 
+}
new file mode 100644
--- /dev/null
+++ b/dom/security/test/hsts/mochitest.ini
@@ -0,0 +1,8 @@
+[DEFAULT]
+tags = hsts-priming
+support-files =
+  file_timeout_server.sjs
+  file_timeout_host_frame.html
+
+[test_hsts_priming_timeout.html]
+[test_hsts_priming_no-timeout.html]
new file mode 100644
--- /dev/null
+++ b/dom/security/test/hsts/test_hsts_priming_no-timeout.html
@@ -0,0 +1,69 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1313595
+Test that a timeout happens correctly
+-->
+<head>
+  <title>Test for Bug 1313595</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1313595">Mozilla Bug 1313595</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<iframe id = "content_iframe"></iframe>
+</div>
+<pre id="test">
+<script type="application/javascript">
+SpecialPowers.cleanUpSTSData('http://example.com');
+
+var timeout = 0;
+var start = performance.now();
+
+function cleanup() {
+  clearInterval(interval);
+  window.removeEventListener("message", processEvent);
+  SpecialPowers.cleanUpSTSData('http://example.com');
+  SpecialPowers.popPrefEnv();
+  SimpleTest.finish();
+}
+
+function processEvent(ev) {
+  console.log("processing event %o", ev);
+
+  if (ev.origin != "https://example.com") {
+    return;
+  }
+
+  is(ev.data.result, "failed", "The load should fail");
+
+  ok(ev.data.timeout < 100, "The error should happen not because of timeout.");
+
+  cleanup();
+}
+
+var interval = setInterval(function () {
+  if ((performance.now() - start) > (timeout*2)) {
+    ok(false, "Did not receive error in a timely manner (" + 2*timeout + "ms)");
+    cleanup();
+  }
+}, 100);
+
+window.addEventListener("message", processEvent, false);
+
+SpecialPowers.pushPrefEnv({'set': [['security.mixed_content.hsts_priming_request_timeout', timeout]]});
+SimpleTest.waitForExplicitFinish();
+
+// save this for last so that our listeners are registered.
+// ... this loads the testbed of good and bad requests.
+let content_iframe = document.getElementById('content_iframe');
+content_iframe.onload = function() {
+  content_iframe.contentWindow.postMessage("Go", "https://example.com");
+};
+content_iframe.src = 'https://example.com/tests/dom/security/test/hsts/file_timeout_host_frame.html?timeout='+timeout;
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/security/test/hsts/test_hsts_priming_timeout.html
@@ -0,0 +1,69 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1313595
+Test that a timeout happens correctly
+-->
+<head>
+  <title>Test for Bug 1313595</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1313595">Mozilla Bug 1313595</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<iframe id = "content_iframe"></iframe>
+</div>
+<pre id="test">
+<script type="application/javascript">
+SpecialPowers.cleanUpSTSData('http://example.com');
+
+var timeout = 1000;
+var start = performance.now();
+
+function cleanup() {
+  clearInterval(interval);
+  window.removeEventListener("message", processEvent);
+  SpecialPowers.cleanUpSTSData('http://example.com');
+  SpecialPowers.popPrefEnv();
+  SimpleTest.finish();
+}
+
+function processEvent(ev) {
+  console.log("processing event %o", ev);
+
+  if (ev.origin != "https://example.com") {
+    return;
+  }
+
+  is(ev.data.result, "failed", "The load should fail");
+
+  ok(Math.abs(ev.data.timeout - timeout) < (timeout * .10), "The error should happen because of timeout. expected: " + timeout + ", got " + ev.data.timeout);
+
+  cleanup();
+}
+
+var interval = setInterval(function () {
+  if ((performance.now() - start) > (timeout*2)) {
+    ok(false, "Did not receive timeout in a timely manner (" + 2*timeout + "ms)");
+    cleanup();
+  }
+}, 100);
+
+window.addEventListener("message", processEvent, false);
+
+SpecialPowers.pushPrefEnv({'set': [['security.mixed_content.hsts_priming_request_timeout', timeout]]});
+SimpleTest.waitForExplicitFinish();
+
+// save this for last so that our listeners are registered.
+// ... this loads the testbed of good and bad requests.
+let content_iframe = document.getElementById('content_iframe');
+content_iframe.onload = function() {
+  content_iframe.contentWindow.postMessage("Go", "https://example.com");
+};
+content_iframe.src = 'https://example.com/tests/dom/security/test/hsts/file_timeout_host_frame.html?timeout='+timeout;
+</script>
+</pre>
+</body>
+</html>
--- a/dom/security/test/moz.build
+++ b/dom/security/test/moz.build
@@ -11,16 +11,17 @@ XPCSHELL_TESTS_MANIFESTS += [
 TEST_DIRS += [
     'gtest',
 ]
 
 MOCHITEST_MANIFESTS += [
     'cors/mochitest.ini',
     'csp/mochitest.ini',
     'general/mochitest.ini',
+    'hsts/mochitest.ini',
     'mixedcontentblocker/mochitest.ini',
     'sri/mochitest.ini',
 ]
 
 MOCHITEST_CHROME_MANIFESTS += [
     'general/chrome.ini',
 ]
 
--- a/netwerk/protocol/http/HSTSPrimerListener.cpp
+++ b/netwerk/protocol/http/HSTSPrimerListener.cpp
@@ -20,17 +20,18 @@
 #include "mozilla/Unused.h"
 
 namespace mozilla {
 namespace net {
 
 using namespace mozilla;
 
 NS_IMPL_ISUPPORTS(HSTSPrimingListener, nsIStreamListener,
-                  nsIRequestObserver, nsIInterfaceRequestor)
+                  nsIRequestObserver, nsIInterfaceRequestor,
+                  nsITimerCallback)
 
 // default to 3000ms, same as the preference
 uint32_t HSTSPrimingListener::sHSTSPrimingTimeout = 3000;
 
 
 HSTSPrimingListener::HSTSPrimingListener(nsIHstsPrimingCallback* aCallback)
   : mCallback(aCallback)
 {
@@ -66,24 +67,28 @@ HSTSPrimingListener::ReportTiming(nsresu
 }
 
 NS_IMETHODIMP
 HSTSPrimingListener::OnStartRequest(nsIRequest *aRequest,
                                     nsISupports *aContext)
 {
   nsCOMPtr<nsIHstsPrimingCallback> callback;
   callback.swap(mCallback);
+  mCallback = nullptr;
+
   if (mHSTSPrimingTimer) {
     Unused << mHSTSPrimingTimer->Cancel();
     mHSTSPrimingTimer = nullptr;
   }
 
   // if callback is null, we have already canceled this request and reported
   // the failure
-  NS_ENSURE_STATE(callback);
+  if (!callback) {
+    return NS_OK;
+  }
 
   nsresult primingResult = CheckHSTSPrimingRequestStatus(aRequest);
   ReportTiming(primingResult);
 
   if (NS_FAILED(primingResult)) {
     LOG(("HSTS Priming Failed (request was not approved)"));
     return callback->OnHSTSPrimingFailed(primingResult, false);
   }
@@ -169,23 +174,27 @@ HSTSPrimingListener::OnDataAvailable(nsI
 
 /** nsITimerCallback **/
 NS_IMETHODIMP
 HSTSPrimingListener::Notify(nsITimer* timer)
 {
   nsresult rv;
   nsCOMPtr<nsIHstsPrimingCallback> callback;
   callback.swap(mCallback);
-  NS_ENSURE_STATE(callback);
+  mCallback = nullptr;
+  if (!callback) {
+    // we already processed this channel
+    return NS_OK;
+  }
+
   ReportTiming(NS_ERROR_HSTS_PRIMING_TIMEOUT);
 
   if (mPrimingChannel) {
     rv = mPrimingChannel->Cancel(NS_ERROR_HSTS_PRIMING_TIMEOUT);
     if (NS_FAILED(rv)) {
-      // do what?
       NS_ERROR("HSTS Priming timed out, and we got an error canceling the priming channel.");
     }
   }
 
   rv = callback->OnHSTSPrimingFailed(NS_ERROR_HSTS_PRIMING_TIMEOUT, false);
   if (NS_FAILED(rv)) {
     NS_ERROR("HSTS Priming timed out, and we got an error reporting the failure.");
   }