Bug 1443942 - Test for blocking midflight redirects in media elements. r=jya draft
authorChris Pearce <cpearce@mozilla.com>
Tue, 06 Mar 2018 14:44:26 +1300
changeset 779056 db7f0c61b64990623ef035b266cf052c45df1c76
parent 779055 a2f4b7c68b73ecc4c7525d4e41e834f4caf85707
child 779057 27dd68c9a1f607f85ccece081ad54a581d00b2e9
push id105646
push userbmo:cpearce@mozilla.com
push dateSun, 08 Apr 2018 22:04:39 +0000
reviewersjya
bugs1443942
milestone61.0a1
Bug 1443942 - Test for blocking midflight redirects in media elements. r=jya Test that playback works if we don't block, doesn't if we do block, and does if we do block and CORS is used. MozReview-Commit-ID: 9PTZXLOdHIU
dom/media/test/midflight-redirect.sjs
dom/media/test/mochitest.ini
dom/media/test/test_midflight_redirect_blocked.html
new file mode 100644
--- /dev/null
+++ b/dom/media/test/midflight-redirect.sjs
@@ -0,0 +1,71 @@
+function parseQuery(query, key) {
+  for (let p of query.split('&')) {
+    if (p == key) {
+      return true;
+    }
+    if (p.startsWith(key + "=")) {
+      return p.substring(key.length + 1);
+    }
+  }
+}
+
+// Return the first few bytes in a short byte range response. When Firefox
+// requests subsequent bytes in a second range request, respond with a
+// redirect. Requests after the first redirected are serviced as expected.
+function handleRequest(request, response)
+{
+  var query = request.queryString;
+  var resource = parseQuery(query, "resource");
+  var redirected = parseQuery(query, "redirected") || false;
+  var useCors = parseQuery(query, "cors") || false;
+
+  var file = Components.classes["@mozilla.org/file/directory_service;1"].
+                        getService(Components.interfaces.nsIProperties).
+                        get("CurWorkD", Components.interfaces.nsIFile);
+  var fis  = Components.classes['@mozilla.org/network/file-input-stream;1'].
+                        createInstance(Components.interfaces.nsIFileInputStream);
+  var bis  = Components.classes["@mozilla.org/binaryinputstream;1"].
+                        createInstance(Components.interfaces.nsIBinaryInputStream);
+  var paths = "tests/dom/media/test/" + resource;
+  var split = paths.split("/");
+  for (var i = 0; i < split.length; ++i) {
+    file.append(split[i]);
+  }
+  fis.init(file, -1, -1, false);
+
+  bis.setInputStream(fis);
+  var bytes = bis.readBytes(bis.available());
+  let [from, to] = request.getHeader("range").split("=")[1].split("-").map(s => parseInt(s));
+
+  if (!redirected && from > 0) {
+    var origin = request.host == "mochi.test" ? "example.org" : "mochi.test:8888";
+    response.setStatusLine(request.httpVersion, 303, "See Other");
+    let url = "http://" + origin +
+              "/tests/dom/media/test/midflight-redirect.sjs?redirected&" + query;
+    response.setHeader("Location", url);
+    response.setHeader("Content-Type", "text/html");
+    return;
+  }
+
+  if (from == 0 && !redirected) {
+    to = Math.min(bytes.length / 4, 200);
+  } else {
+    to = to || Math.max(from, bytes.length - 1);
+  }
+
+  byterange = bytes.substring(from, to + 1);
+
+  let contentRange = "bytes "+ from +"-"+ to +"/"+ bytes.length;
+  let contentLength = (to - from + 1).toString();
+
+  response.setStatusLine(request.httpVersion, 206, "Partial Content");
+  response.setHeader("Content-Range", contentRange);
+  response.setHeader("Content-Length", contentLength, false);
+  response.setHeader("Content-Type", "video/ogg", false);
+  response.setHeader("Accept-Ranges", "bytes", false);
+  if (redirected && useCors) {
+    response.setHeader("Access-Control-Allow-Origin", "*");
+  }
+  response.write(byterange, byterange.length);
+  bis.close();
+}
--- a/dom/media/test/mochitest.ini
+++ b/dom/media/test/mochitest.ini
@@ -490,16 +490,17 @@ support-files =
   invalid-m2c1.opus
   invalid-m2c1.opus^headers^
   invalid-neg_discard.webm
   invalid-neg_discard.webm^headers^
   invalid-preskip.webm
   invalid-preskip.webm^headers^
   long.vtt
   manifest.js
+  midflight-redirect.sjs
   multiple-bos.ogg
   multiple-bos.ogg^headers^
   multiple-bos-more-header-fileds.ogg
   multiple-bos-more-header-fileds.ogg^headers^
   no-cues.webm
   no-cues.webm^headers^
   notags.mp3
   notags.mp3^headers^
@@ -945,16 +946,17 @@ skip-if = android_version == '17' # andr
 tags=msg
 [test_mediatrack_events.html]
 skip-if = android_version == '17' # android(bug 1232305)
 [test_mediatrack_parsing_ogg.html]
 skip-if = android_version == '17' # android(bug 1232305)
 [test_mediatrack_replay_from_end.html]
 skip-if = toolkit == 'android' # android(bug 1232305)
 [test_metadata.html]
+[test_midflight_redirect_blocked.html]
 [test_mixed_principals.html]
 skip-if = toolkit == 'android' # bug 1309814, android(bug 1232305)
 [test_mozHasAudio.html]
 skip-if = toolkit == 'android' # android(bug 1232305)
 [test_multiple_mediastreamtracks.html]
 skip-if = android_version == '17' # android(bug 1232305)
 [test_networkState.html]
 skip-if = android_version == '17' # android(bug 1232305)
new file mode 100644
--- /dev/null
+++ b/dom/media/test/test_midflight_redirect_blocked.html
@@ -0,0 +1,95 @@
+<!DOCTYPE HTML>
+<html>
+  <head>
+    <title>Test mid-flight cross site redirects are blocked</title>
+    <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+    <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+    <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+    <script type="text/javascript" src="manifest.js"></script>
+  </head>
+  <body>
+    <pre id='test'>
+    <script class="testbody" type='application/javascript'>
+
+      function testIfLoadsToMetadata(test, useCors) {
+        return new Promise(function(resolve, reject) {
+          var elemType = getMajorMimeType(test.type);
+          var element = document.createElement(elemType);
+
+          if (useCors) {
+            element.crossOrigin = "anonymous";
+          }
+
+          // Log events for debugging.
+          [
+            "suspend", "play", "canplay", "canplaythrough", "loadstart",
+            "loadedmetadata", "loadeddata", "playing", "ended", "error",
+            "stalled", "emptied", "abort", "waiting", "pause"
+          ].forEach((eventName) => {
+            element.addEventListener(eventName, (event)=> {
+              info(test.name + " " + event.type);
+            });
+          });
+
+          element.addEventListener("loadedmetadata", ()=>{
+            resolve(true);
+            removeNodeAndSource(element);
+          }, false);
+
+          element.addEventListener("error", ()=>{
+            resolve(false);
+            removeNodeAndSource(element);
+          }, false);
+
+          let suffix = useCors ? "&cors" : "";
+          element.src = "midflight-redirect.sjs?resource=" + test.name + suffix;
+          element.preload = "metadata";
+          document.body.appendChild(element);
+          element.load()
+        });
+      }
+
+      let v = document.createElement("video");
+      const testCases = gSmallTests.filter(t => v.canPlayType(t.type));
+
+      function testMediaLoad(expectedToLoad, message, useCors) {
+        return new Promise(async function(resolve, reject) {
+          for (let test of testCases) {
+            let loaded = await testIfLoadsToMetadata(test, useCors);
+            is(loaded, expectedToLoad, test.name + " " + message);
+          }
+          resolve();
+        });
+      }
+
+      async function runTest() {
+        try {
+          SimpleTest.info("Allowing midflight redirects...");
+          await SpecialPowers.pushPrefEnv({'set': [["media.block-midflight-redirects", false]]});
+
+          SimpleTest.info("Test that all media plays...");
+          await testMediaLoad(true, "expected to load", false);
+
+          SimpleTest.info("Blocking midflight redirects...");
+          await SpecialPowers.pushPrefEnv({'set': [["media.block-midflight-redirects", true]]});
+
+          SimpleTest.info("Test that all media no longer play...");
+          await testMediaLoad(false, "expected to be blocked", false);
+
+          SimpleTest.info("Test that all media play if CORS used...");
+          await testMediaLoad(true, "expected to play with CORS", true);
+        } catch (e) {
+          info("Exception " + e.message);
+          ok(false, "Threw exception " + e.message);
+        }
+        SimpleTest.finish();
+      }
+
+      SimpleTest.waitForExplicitFinish();
+
+      addLoadEvent(runTest);
+
+    </script>
+    </pre>
+  </body>
+</html>