Bug 515460 - Mochitests for CSP redirect handling, a=dholbert_sheriff
authorBrandon Sterne <bsterne@mozilla.com>
Fri, 23 Apr 2010 12:55:19 -0700
changeset 41213 3d3a5a5ea85614db0c66594b6fe28c2366b4244c
parent 41212 ae6dc952378ca2c6dbaade0176df6930ef5a12da
child 41214 426165b08c0c4b5d952d775c9e5981713b05881a
push idunknown
push userunknown
push dateunknown
reviewersdholbert_sheriff
bugs515460
milestone1.9.3a5pre
Bug 515460 - Mochitests for CSP redirect handling, a=dholbert_sheriff
content/base/test/Makefile.in
content/base/test/file_csp_redirects_main.html
content/base/test/file_csp_redirects_page.sjs
content/base/test/file_csp_redirects_resource.sjs
content/base/test/test_csp_redirects.html
--- a/content/base/test/Makefile.in
+++ b/content/base/test/Makefile.in
@@ -368,16 +368,20 @@ include $(topsrcdir)/config/rules.mk
 		test_bug553896.xhtml \
 		test_bug541937.html \
 		file_bug541937.html \
 		file_bug541937.xhtml \
 		test_bug558726.html \
 		test_bug557892.html \
 		file_bug557892.html \
 		test_bug559526.html \
+		test_csp_redirects.html \
+		file_csp_redirects_page.sjs \
+		file_csp_redirects_main.html \
+		file_csp_redirects_resource.sjs \
 		$(NULL)
 
 # This test fails on the Mac for some reason
 ifneq (,$(filter gtk2 windows,$(MOZ_WIDGET_TOOLKIT)))
 _TEST_FILES += 	test_copyimage.html \
 		$(NULL)
 endif
 
new file mode 100644
--- /dev/null
+++ b/content/base/test/file_csp_redirects_main.html
@@ -0,0 +1,35 @@
+<html>
+<head>
+<title>CSP redirect tests</title>
+</head>
+<body>
+<div id="container"></div>
+</body>
+
+<script>
+var thisSite = "http://mochi.test:8888";
+var otherSite = "http://example.com";
+var page = "/tests/content/base/test/file_csp_redirects_page.sjs";
+
+var tests = { "font-src": thisSite+page+"?testid=font-src&csp=1",
+              "frame-src": thisSite+page+"?testid=frame-src&csp=1",
+              "img-src":  thisSite+page+"?testid=img-src&csp=1",
+              "media-src":  thisSite+page+"?testid=media-src&csp=1",
+              "object-src":  thisSite+page+"?testid=object-src&csp=1",
+              "script-src":  thisSite+page+"?testid=script-src&csp=1",
+              "style-src":  thisSite+page+"?testid=style-src&csp=1",
+              "worker":  thisSite+page+"?testid=worker&csp=1",
+              "xhr-src":  thisSite+page+"?testid=xhr-src&csp=1",
+            };
+
+var container = document.getElementById("container");
+
+// load each test in its own iframe
+for (tid in tests) {
+  var i = document.createElement("iframe");
+  i.id = tid;
+  i.src = tests[tid];
+  container.appendChild(i);
+}
+</script>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/base/test/file_csp_redirects_page.sjs
@@ -0,0 +1,79 @@
+// SJS file for CSP redirect mochitests
+// This file serves pages which can optionally specify a Content Security Policy
+function handleRequest(request, response)
+{
+  var query = {};
+  request.queryString.split('&').forEach(function (val) {
+    var [name, value] = val.split('=');
+    query[name] = unescape(value);
+  });
+
+  response.setHeader("Cache-Control", "no-cache", false);
+  response.setHeader("Content-Type", "text/html", false);
+
+  var resource = "/tests/content/base/test/file_csp_redirects_resource.sjs";
+
+  // CSP header value
+  if (query["csp"] == 1) {
+    response.setHeader("X-Content-Security-Policy", "allow 'self'", false);
+  }
+
+  // downloadable font that redirects to another site
+  if (query["testid"] == "font-src") {
+    var resp = '<style type="text/css"> @font-face { font-family:' +
+               '"Redirecting Font"; src: url("' + resource +
+               '?res=font&redir=other&id=font-src-redir")} #test{font-family:' +
+               '"Redirecting Font"}</style></head><body>' +
+               '<div id="test">test</div></body>';
+    response.write(resp);
+    return;
+  }
+
+  // iframe that redirects to another site
+  if (query["testid"] == "frame-src") {
+    response.write('<iframe src="'+resource+'?res=iframe&redir=other&id=frame-src-redir"></iframe>');
+    return;
+  }
+
+  // image that redirects to another site
+  if (query["testid"] == "img-src") {
+    response.write('<img src="'+resource+'?res=image&redir=other&id=img-src-redir" />');
+    return;
+  }
+
+  // video content that redirects to another site
+  if (query["testid"] == "media-src") {
+    response.write('<video src="'+resource+'?res=media&redir=other&id=media-src-redir"></video>');
+    return;
+  }
+
+  // object content that redirects to another site
+  if (query["testid"] == "object-src") {
+    response.write('<object type="text/html" data="'+resource+'?res=object&redir=other&id=object-src-redir"></object>');
+    return;
+  }
+
+  // external script that redirects to another site
+  if (query["testid"] == "script-src") {
+    response.write('<script src="'+resource+'?res=script&redir=other&id=script-src-redir"></script>');
+    return;
+  }
+
+  // external stylesheet that redirects to another site
+  if (query["testid"] == "style-src") {
+    response.write('<link rel="stylesheet" type="text/css" href="'+resource+'?res=style&redir=other&id=style-src-redir"></script>');
+    return;
+  }
+
+  // worker script resource that redirects to another site
+  if (query["testid"] == "worker") {
+    response.write('<script src="'+resource+'?res=worker&redir=other&id=worker-redir"></script>');
+    return;
+  }
+
+  // script that XHR's to a resource that redirects to another site
+  if (query["testid"] == "xhr-src") {
+    response.write('<script src="'+resource+'?res=xhr"></script>');
+    return;
+  }
+}
new file mode 100644
--- /dev/null
+++ b/content/base/test/file_csp_redirects_resource.sjs
@@ -0,0 +1,112 @@
+// SJS file to serve resources for CSP redirect tests
+// This file mimics serving resources, e.g. fonts, images, etc., which a CSP
+// can include. The resource may redirect to a different resource, if specified.
+function handleRequest(request, response)
+{
+  var query = {};
+  request.queryString.split('&').forEach(function (val) {
+    var [name, value] = val.split('=');
+    query[name] = unescape(value);
+  });
+
+  var thisSite = "http://mochi.test:8888";
+  var otherSite = "http://example.com";
+  var resource = "/tests/content/base/test/file_csp_redirects_resource.sjs";
+
+  response.setHeader("Cache-Control", "no-cache", false);
+
+  // redirect to a resource on this site
+  if (query["redir"] == "same") {
+    var loc = thisSite+resource+"?res="+query["res"]+"&testid="+query["id"];
+    response.setStatusLine("1.1", 302, "Found");
+    response.setHeader("Location", loc, false);
+    return;
+  }
+
+  // redirect to a resource on a different site
+  else if (query["redir"] == "other") {
+    var loc = otherSite+resource+"?res="+query["res"]+"&testid="+query["id"];
+    response.setStatusLine("1.1", 302, "Found");
+    response.setHeader("Location", loc, false);
+    return;
+  }
+
+  // not a redirect.  serve some content.
+  // the content doesn't have to be valid, since we're only checking whether
+  // the request for the content was sent or not.
+
+  // downloadable font
+  if (query["res"] == "font") {
+    response.setHeader("Access-Control-Allow-Origin", "*", false);
+    response.setHeader("Content-Type", "text/plain", false);
+    response.write("font data...");
+    return;
+  }
+
+  // iframe with arbitrary content
+  if (query["res"] == "iframe") {
+    response.setHeader("Content-Type", "text/html", false);
+    response.write("iframe content...");
+    return;
+  }
+
+  // image
+  if (query["res"] == "image") {
+    response.setHeader("Content-Type", "image/gif", false);
+    response.write("image data...");
+    return;
+  }
+
+  // media content, e.g. Ogg video
+  if (query["res"] == "media") {
+    response.setHeader("Content-Type", "video/ogg", false);
+    response.write("video data...");
+    return;
+  }
+
+  // plugin content, e.g. <object>
+  if (query["res"] == "object") {
+    response.setHeader("Content-Type", "text/html", false);
+    response.write("object data...");
+    return;
+  }
+
+  // script
+  if (query["res"] == "script") {
+    response.setHeader("Content-Type", "application/javascript", false);
+    response.write("some script...");
+    return;
+  }
+
+  // external stylesheet
+  if (query["res"] == "style") {
+    response.setHeader("Content-Type", "text/css", false);
+    response.write("css data...");
+    return;
+  }
+
+  // web worker resource
+  if (query["res"] == "worker") {
+    response.setHeader("Content-Type", "application/javascript", false);
+    response.write("worker script data...");
+    return;
+  }
+
+  // script that invokes XHR
+  if (query["res"] == "xhr") {
+    response.setHeader("Content-Type", "text/html", false);
+    var resp = 'var x = new XMLHttpRequest(); x.open("GET", "' + otherSite +
+               resource+'?res=xhr-resp&testid=xhr-src-redir", false); ' +
+               'x.send(null);';
+    response.write(resp);
+    return;
+  }
+
+  // response to XHR
+  if (query["res"] == "xhr-resp") {
+    response.setHeader("Access-Control-Allow-Origin", "*", false);
+    response.setHeader("Content-Type", "text/html", false);
+    response.write('XHR response...');
+    return;
+  }
+}
new file mode 100644
--- /dev/null
+++ b/content/base/test/test_csp_redirects.html
@@ -0,0 +1,131 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Tests for Content Security Policy during redirects</title>
+  <script type="text/javascript" src="/MochiKit/packed.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.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>
+
+<iframe style="width:100%;height:300px;" id="harness"></iframe>
+<pre id="log"></pre>
+<script class="testbody" type="text/javascript">
+
+var path = "/tests/content/base/test/";
+
+// debugging
+function log(s) {
+  return;
+  var log = document.getElementById("log");
+  log.textContent = log.textContent+s+"\n";
+}
+
+// used to watch if requests are blocked by CSP or allowed through
+function examiner() {
+  netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
+  var obsvc = Components.classes['@mozilla.org/observer-service;1']
+                        .getService(Components.interfaces.nsIObserverService);
+  obsvc.addObserver(this, "csp-on-violate-policy", false);
+  obsvc.addObserver(this, "http-on-modify-request", false);
+}
+examiner.prototype  = {
+  observe: function(subject, topic, data) {
+    netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
+    // subject should be an nsURI, and should be either allowed or blocked.
+    if(!subject.QueryInterface)
+      return;
+
+    var testpat = new RegExp("testid=([a-z0-9-]+)");
+    var uri;
+    var testid;
+
+    if (topic === "http-on-modify-request") {
+      // request was sent
+      uri = subject.QueryInterface(Components.interfaces.nsIHttpChannel).URI;
+      if (!testpat.test(uri.asciiSpec)) return;
+      testid = testpat.exec(uri.asciiSpec)[1];
+      if (testExpectedResults[testid] == "completed") return;
+      log("allowed: "+uri.asciiSpec);
+      window.testResult(testid, uri.asciiSpec, true);
+    }
+
+    else if (topic === "csp-on-violate-policy") {
+      // request was blocked
+      uri = subject.QueryInterface(Components.interfaces.nsIURI);
+      if (!testpat.test(uri.asciiSpec)) return;
+      testid = testpat.exec(uri.asciiSpec)[1];
+      // had to add this check because http-on-modify-request can fire after
+      // csp-on-violate-policy, apparently, even though the request does
+      // not hit the wire.
+      if (testExpectedResults[testid] == "completed") return;
+      log("BLOCKED: "+uri.asciiSpec);
+      window.testResult(testid, uri.asciiSpec, false);
+    }
+  },
+
+  remove: function() {
+    netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
+    var obsvc = Components.classes['@mozilla.org/observer-service;1']
+                          .getService(Components.interfaces.nsIObserverService);
+    obsvc.removeObserver(this, "csp-on-violate-policy");
+    obsvc.removeObserver(this, "http-on-modify-request");
+  }
+}
+window.examiner = new examiner();
+
+// contains { test_frame_id : expected_result }
+var testExpectedResults = { "font-src": true,
+                            "font-src-redir": false,
+                            "frame-src": true,
+                            "frame-src-redir": false,
+                            "img-src": true,
+                            "img-src-redir": false,
+                            "media-src": true,
+                            "media-src-redir": false,
+                            "object-src": true,
+                            "object-src-redir": false,
+                            "script-src": true,
+                            "script-src-redir": false,
+                            "style-src": true,
+                            "style-src-redir": false,
+                            "worker": true,
+                            "worker-redir": false,
+                            "xhr-src": true,
+                            "xhr-src-redir": false,
+                          };
+
+// takes the name of the test, the URL that was tested, and whether the
+// load occured
+var testResult = function(testName, url, result) {
+  netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
+  log("  testName: "+testName+", result: "+result+", expected: "+testExpectedResults[testName]+"\n");
+  is(result, testExpectedResults[testName], testName+" test: "+url);
+
+ // mark test as completed
+  testExpectedResults[testName] = "completed";
+
+  // don't finish until we've run all the tests
+  for (var t in testExpectedResults) {
+    if (testExpectedResults[t] != "completed")
+      return;
+  }
+
+  window.examiner.remove();
+  SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+
+// load the test harness
+document.getElementById("harness").src = "file_csp_redirects_main.html";
+
+</script>
+</pre>
+
+</body>
+</html>