Bug 1380186 test http redirects to moz-ext protocol, r?kmag draft
authorShane Caraveo <scaraveo@mozilla.com>
Wed, 19 Jul 2017 12:27:26 -0700
changeset 611474 4d908d61657d8ceceeb8b82b9c78c3e411822372
parent 611473 70e4a3cec0916b6f721aba2b307b6c644be8b77a
child 638167 b22325a0f0b9885bd3b1a1539885fff095e32f20
push id69226
push usermixedpuppy@gmail.com
push dateWed, 19 Jul 2017 19:27:58 +0000
reviewerskmag
bugs1380186
milestone56.0a1
Bug 1380186 test http redirects to moz-ext protocol, r?kmag MozReview-Commit-ID: CJootsUvvfp
toolkit/components/extensions/test/browser/browser.ini
toolkit/components/extensions/test/browser/browser_ext_redirect_chrome.js
toolkit/components/extensions/test/mochitest/mochitest-common.ini
toolkit/components/extensions/test/mochitest/redirect.sjs
toolkit/components/extensions/test/mochitest/test_ext_redirects.html
--- a/toolkit/components/extensions/test/browser/browser.ini
+++ b/toolkit/components/extensions/test/browser/browser.ini
@@ -1,10 +1,12 @@
 [DEFAULT]
 support-files =
   head.js
+  !/toolkit/components/extensions/test/mochitest/redirect.sjs
 
 [browser_ext_themes_chromeparity.js]
 [browser_ext_themes_dynamic_updates.js]
 [browser_ext_themes_lwtsupport.js]
 [browser_ext_themes_multiple_backgrounds.js]
 [browser_ext_themes_persistence.js]
 [browser_ext_management_themes.js]
+[browser_ext_redirect_chrome.js]
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/browser_ext_redirect_chrome.js
@@ -0,0 +1,111 @@
+"use strict";
+
+// Tests whether we can redirect to a moz-extension: url.
+XPCOMUtils.defineLazyModuleGetter(this, "TestUtils",
+                                  "resource://testing-common/TestUtils.jsm");
+
+// nsIWebRequestListener is a nsIThreadRetargetableStreamListener that handles
+// forwarding of nsIRequestObserver for JS consumers.  It does nothing more
+// than that.
+let WebRequestListener = Components.Constructor("@mozilla.org/webextensions/webRequestListener;1",
+                                                "nsIWebRequestListener", "init");
+
+function onStopListener(channel) {
+  return new Promise(resolve => {
+    new WebRequestListener({
+      QueryInterface: XPCOMUtils.generateQI([Ci.nsIRequestObserver,
+                                             Ci.nsIStreamListener]),
+      onStartRequest: function(request, context) {
+        info(`setup stream onStartRequest uri [${request.URI && request.URI.spec}]`);
+      },
+      onStopRequest(request, context, statusCode) {
+        info(`setup stream onStopRequest uri [${request.URI && request.URI.spec}]`);
+        resolve(request.URI && request.URI.spec);
+      },
+    }, channel);
+    channel.resume();
+  });
+}
+
+async function onModifyListener(originUrl, redirectToUrl) {
+  return TestUtils.topicObserved("http-on-modify-request", (subject, data) => {
+    let channel = subject.QueryInterface(Ci.nsIHttpChannel);
+    return channel.URI && channel.URI.spec == originUrl;
+  }).then(([subject, data]) => {
+    let channel = subject.QueryInterface(Ci.nsIHttpChannel);
+    channel.suspend();
+    if (redirectToUrl) {
+      info(`redirecting channel to ${redirectToUrl}`);
+      channel.redirectTo(Services.io.newURI(redirectToUrl));
+    }
+    return onStopListener(channel).then(finalUrl => {
+      info(`onStop has completed ${finalUrl}`);
+      return finalUrl;
+    });
+  });
+}
+
+let extension;
+let redirectUrl;
+add_task(async function startup() {
+  // This extension does nothing except provide us a public moz-extension url
+  // that we can land on.
+  extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "web_accessible_resources": ["finished.html"],
+    },
+    files: {
+      "finished.html": `
+        <!DOCTYPE html>
+        <html>
+          <head>
+            <meta charset="utf-8">
+          </head>
+          <body>
+            <h1>redirected!</h1>
+          </body>
+        </html>
+      `.trim(),
+    },
+    background() {
+      // send the extensions public uri to the test.
+      let exturi = browser.extension.getURL("finished.html");
+      browser.test.sendMessage("redirectURI", exturi);
+    },
+  });
+  await extension.startup();
+  redirectUrl = await extension.awaitMessage("redirectURI");
+});
+
+async function redirection_test(url, redirectTo) {
+  // setup our observer
+  let watcher = onModifyListener(url, redirectTo);
+  // open a tab to the url and wait for watcher.
+  await BrowserTestUtils.openNewForegroundTab(gBrowser, url);
+  let finalUrl = await watcher;
+  if (!finalUrl) {
+    // non-e10, get the url off the tab
+    finalUrl = gBrowser.selectedTab.linkedBrowser.currentURI.spec;
+  }
+  is(finalUrl, redirectUrl, "redirect request is finished");
+  // The tab never finished loading, presumably due to the redirect failure,
+  // so waiting on the tab here will hang.
+  await BrowserTestUtils.removeTab(gBrowser.selectedTab);
+}
+
+// This test makes a request against a server that redirects with a 302.
+add_task(async function test_302_redirect_to_extension() {
+  let url = `https://example.com/tests/toolkit/components/extensions/test/mochitest/redirect.sjs?redirect_uri=${redirectUrl}`;
+  await redirection_test(url);
+});
+
+// This test uses channel.redirectTo during http-on-modify to redirect to the
+// moz-extension url.
+add_task(async function test_channel_redirect_to_extension() {
+  let url = `http://example.com/tests/toolkit/components/extensions/test/mochitest/file_dummy.html?r=${Math.random()}`;
+  await redirection_test(url, redirectUrl);
+});
+
+add_task(async function cleanup() {
+  await extension.unload();
+});
--- a/toolkit/components/extensions/test/mochitest/mochitest-common.ini
+++ b/toolkit/components/extensions/test/mochitest/mochitest-common.ini
@@ -36,16 +36,17 @@ support-files =
   file_script_redirect.js
   file_script_xhr.js
   file_remote_frame.html
   file_sample.html
   file_simple_xhr.html
   file_simple_xhr_frame.html
   file_simple_xhr_frame2.html
   redirection.sjs
+  redirect.sjs
   file_privilege_escalation.html
   file_ext_test_api_injection.js
   file_permission_xhr.html
   file_teardown_test.js
   return_headers.sjs
   webrequest_worker.js
   !/toolkit/components/passwordmgr/test/authenticate.sjs
   !/dom/tests/mochitest/geolocation/network_geolocation.sjs
@@ -76,16 +77,17 @@ skip-if = os == 'android' # bug 1369440
 [test_ext_exclude_include_globs.html]
 [test_ext_external_messaging.html]
 [test_ext_generate.html]
 [test_ext_geolocation.html]
 skip-if = os == 'android' # Android support Bug 1336194
 [test_ext_notifications.html]
 [test_ext_permission_xhr.html]
 [test_ext_proxy.html]
+[test_ext_redirects.html]
 skip-if = os == 'android' && debug # Bug 1357635
 [test_ext_runtime_connect.html]
 [test_ext_runtime_connect_twoway.html]
 [test_ext_runtime_connect2.html]
 [test_ext_runtime_disconnect.html]
 [test_ext_runtime_id.html]
 [test_ext_sandbox_var.html]
 [test_ext_sendmessage_doublereply.html]
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/mochitest/redirect.sjs
@@ -0,0 +1,15 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+Components.utils.importGlobalProperties(["URLSearchParams"]);
+
+function handleRequest(request, response) {
+  let params = new URLSearchParams(request.queryString);
+  if (params.has("no_redirect")) {
+    response.setStatusLine(request.httpVersion, 200, "OK");
+    response.write("ok");
+  } else {
+    response.setStatusLine(request.httpVersion, 302, "Moved Temporarily");
+    response.setHeader("Location", params.get("redirect_uri"));
+  }
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/mochitest/test_ext_redirects.html
@@ -0,0 +1,175 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test XHR capabilities</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
+  <script type="text/javascript" src="head.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+<script type="text/javascript">
+"use strict";
+
+const testExtension = {
+  manifest: {
+    "permissions": [
+      "webRequest",
+      "webRequestBlocking",
+      "tabs",
+      "https://example.com/",
+    ],
+  },
+  files: {
+    "finished.html": `
+      <!DOCTYPE html>
+      <html>
+        <head>
+          <meta charset="utf-8">
+        </head>
+        <body>
+          <h1>redirected!</h1>
+        </body>
+      </html>
+    `.trim(),
+  },
+};
+
+function backgroundScript(expectFail) {
+  let exturi = browser.extension.getURL("/finished.html");
+  let uri = "https://example.com/tests/toolkit/components/extensions/test/mochitest/redirect.sjs";
+  let url = `${uri}?redirect_uri=${exturi}`;
+
+  browser.webRequest.onBeforeRequest.addListener(details => {
+    browser.test.log(`onBeforeRequest url ${details.url}`);
+  }, {urls: ["<all_urls>"]}, ["blocking"]);
+  browser.webRequest.onBeforeRedirect.addListener(details => {
+    browser.test.log(`onBeforeRedirect url from [${details.url}] to [${details.redirectUrl}]`);
+  }, {urls: ["<all_urls>"]});
+  browser.webRequest.onCompleted.addListener(details => {
+    browser.test.assertEq(exturi, details.url, "got correct url on redirect");
+  }, {urls: ["<all_urls>"]});
+  browser.webRequest.onErrorOccurred.addListener(details => {
+    if (!expectFail) {
+      browser.test.fail(`moz-ext redirect failed ${details.url}`);
+      return;
+    }
+    browser.test.log(`onErrorOccurred url from [${details.url}]`);
+  }, {urls: ["<all_urls>"]});
+
+  let loadingUrl;
+  browser.tabs.onUpdated.addListener((changedTabId, changed) => {
+    browser.test.log(`tabs.onUpdated ${changedTabId} ${JSON.stringify(changed)}`);
+    if (changed.status === "loading") {
+      loadingUrl = changed.url;
+    }
+    if (changed.status === "complete" && (changed.url || loadingUrl)) {
+      if (expectFail) {
+        browser.test.assertEq(url, changed.url || loadingUrl, "tab should not redirect to moz-extension");
+      } else {
+        browser.test.assertEq(exturi, changed.url || loadingUrl, "tab should redirect to moz-extension");
+      }
+      browser.tabs.remove(changedTabId);
+      browser.test.sendMessage("done");
+    }
+  });
+  browser.tabs.create({url});
+}
+
+// Tests whether web content can redirect to a non-web-accessible moz-extension:
+// URL. This should result in a Corrupted Content Error page with an unchanged
+// url.
+add_task(async function test_extRedirect() {
+  testExtension.background = `(${backgroundScript})(true)`;
+  let extension = ExtensionTestUtils.loadExtension(testExtension);
+
+  await extension.startup();
+  await extension.awaitMessage("done");
+  await extension.unload();
+});
+
+// Tests whether we can redirect to a moz-extension: url.  This test adds the
+// redirect destination to web_accessible_resources which should allow the
+// redirect to occur.
+add_task(async function test_extRedirect_accessible() {
+  testExtension.background = `(${backgroundScript})(false)`;
+  testExtension.manifest.web_accessible_resources = ["finished.html"];
+  let extension = ExtensionTestUtils.loadExtension(testExtension);
+
+  await extension.startup();
+  await extension.awaitMessage("done");
+  await extension.unload();
+});
+
+// Tests whether an extension can redirect to a moz-extension: url using the
+// webRequest API.
+add_task(async function test_extRedirect_webRequest() {
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "permissions": [
+        "tabs",
+        "webRequest",
+        "webRequestBlocking",
+        "<all_urls>",
+      ],
+      "web_accessible_resources": ["finished.html"],
+    },
+    files: {
+      "finished.html": `
+        <!DOCTYPE html>
+        <html>
+          <head>
+            <meta charset="utf-8">
+          </head>
+          <body>
+            <h1>redirected!</h1>
+          </body>
+        </html>
+      `.trim(),
+    },
+    background() {
+      let url = `http://example.com/browser/browser/components/extensions/test/browser/file_dummy.html?r=${Math.random()}`;
+      let exturi = browser.extension.getURL("finished.html");
+      browser.webRequest.onBeforeRequest.addListener(details => {
+        browser.test.log(`onBeforeRequest url ${details.url}`);
+        if (details.url.includes("file_dummy.html")) {
+          browser.test.log(`... redirecting to ${exturi}`);
+          return {redirectUrl: exturi};
+        }
+      }, {urls: ["<all_urls>"]}, ["blocking"]);
+      browser.webRequest.onBeforeRedirect.addListener(details => {
+        browser.test.log(`onBeforeRedirect url from [${details.url}] to [${details.redirectUrl}]`);
+      }, {urls: ["<all_urls>"]});
+      browser.webRequest.onCompleted.addListener(details => {
+        browser.test.assertEq(exturi, details.url, "got correct url on redirect");
+      }, {urls: ["<all_urls>"]});
+      browser.webRequest.onErrorOccurred.addListener(details => {
+        browser.test.fail(`moz-ext redirect failed ${details.url}`);
+      }, {urls: ["<all_urls>"]});
+
+      let loadingUrl;
+      browser.tabs.onUpdated.addListener((changedTabId, changed) => {
+        browser.test.log(`tabs.onUpdated ${changedTabId} ${JSON.stringify(changed)}`);
+        if (changed.status === "loading") {
+          loadingUrl = changed.url;
+        }
+        if (changed.status === "complete") {
+          browser.test.assertEq(exturi, changed.url || loadingUrl, "tab should redirect to moz-extension");
+          browser.tabs.remove(changedTabId);
+          browser.test.sendMessage("done");
+        }
+      });
+      browser.tabs.create({url});
+    },
+  });
+
+  await extension.startup();
+  await extension.awaitMessage("done");
+  await extension.unload();
+});
+</script>
+
+</body>
+</html>