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
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>