Bug 690168. Implement the 'Allow-From' directive for X-Frame-Options. r=bzbarsky
authorPhil Ames <philames@google.com>
Mon, 27 Aug 2012 15:46:24 -0400
changeset 105637 72755799451cc5e8d79dfc0e1c430634f19df023
parent 105636 721ff7492145e230ba54431548dc8afb7865c1c1
child 105638 8ac55d26cf220716f85cc55471b2eb5cbdc5069f
push id55
push usershu@rfrn.org
push dateThu, 30 Aug 2012 01:33:09 +0000
reviewersbzbarsky
bugs690168
milestone17.0a1
Bug 690168. Implement the 'Allow-From' directive for X-Frame-Options. r=bzbarsky
content/base/test/file_x-frame-options_main.html
content/base/test/file_x-frame-options_page.sjs
content/base/test/test_x-frame-options.html
docshell/base/nsDSURIContentListener.cpp
dom/browser-element/mochitest/Makefile.in
dom/browser-element/mochitest/browserElement_XFrameOptionsAllowFrom.js
dom/browser-element/mochitest/file_browserElement_XFrameOptionsAllowFrom.html
dom/browser-element/mochitest/file_browserElement_XFrameOptionsAllowFrom.sjs
dom/browser-element/mochitest/test_browserElement_inproc_XFrameOptionsAllowFrom.html
dom/browser-element/mochitest/test_browserElement_oop_XFrameOptionsAllowFrom.html
--- a/content/base/test/file_x-frame-options_main.html
+++ b/content/base/test/file_x-frame-options_main.html
@@ -14,11 +14,13 @@ window.addEventListener('load', parent.t
 <iframe id="deny" src="http://mochi.test:8888/tests/content/base/test/file_x-frame-options_page.sjs?testid=deny&xfo=deny"></iframe><br>
 <iframe id="sameorigin1" src="http://mochi.test:8888/tests/content/base/test/file_x-frame-options_page.sjs?testid=sameorigin1&xfo=sameorigin"></iframe><br>
 <iframe id="sameorigin2" src="http://example.com/tests/content/base/test/file_x-frame-options_page.sjs?testid=sameorigin2&xfo=sameorigin"></iframe><br>
 <iframe id="sameorigin5" src="http://example.com/tests/content/base/test/file_x-frame-options_page.sjs?testid=sameorigin5&xfo=sameorigin2"></iframe><br>
 <iframe id="sameorigin6" src="http://mochi.test:8888/tests/content/base/test/file_x-frame-options_page.sjs?testid=sameorigin6&xfo=sameorigin2"></iframe><br>
 <iframe id="sameorigin7" src="http://mochi.test:8888/tests/content/base/test/file_x-frame-options_page.sjs?testid=sameorigin7&xfo=sameorigin3"></iframe><br>
 <iframe id="sameorigin8" src="http://example.com/tests/content/base/test/file_x-frame-options_page.sjs?testid=sameorigin8&xfo=sameorigin3"></iframe><br>
 <iframe id="mixedpolicy" src="http://mochi.test:8888/tests/content/base/test/file_x-frame-options_page.sjs?testid=mixedpolicy&xfo=mixedpolicy"></iframe><br>
+<iframe id="allow-from-allow" src="http://example.com/tests/content/base/test/file_x-frame-options_page.sjs?testid=allow-from-allow&xfo=afa"></iframe><br>
+<iframe id="allow-from-deny" src="http://example.com/tests/content/base/test/file_x-frame-options_page.sjs?testid=allow-from-deny&xfo=afd"></iframe><br>
 
 </body>
 </html>
--- a/content/base/test/file_x-frame-options_page.sjs
+++ b/content/base/test/file_x-frame-options_page.sjs
@@ -21,13 +21,19 @@ function handleRequest(request, response
     response.setHeader("X-Frame-Options", "SAMEORIGIN, SAMEORIGIN", false);
   }
   else if (query['xfo'] == "sameorigin3") {
     response.setHeader("X-Frame-Options", "SAMEORIGIN,SAMEORIGIN , SAMEORIGIN", false);
   }
   else if (query['xfo'] == "mixedpolicy") {
     response.setHeader("X-Frame-Options", "DENY,SAMEORIGIN", false);
   }
+  else if (query['xfo'] == "afa") {
+    response.setHeader("X-Frame-Options", "ALLOW-FROM http://mochi.test:8888/", false);
+  }
+  else if (query['xfo'] == "afd") {
+    response.setHeader("X-Frame-Options", "ALLOW-FROM http://example.com/", false);
+  }
 
   // from the test harness we'll be checking for the presence of this element
   // to test if the page loaded
   response.write("<h1 id=\"test\">" + query["testid"] + "</h1>");
 }
--- a/content/base/test/test_x-frame-options.html
+++ b/content/base/test/test_x-frame-options.html
@@ -103,16 +103,26 @@ var testFramesLoaded = function() {
   var test9 = frame.contentDocument.getElementById("test");
   is(test9, null, "test sameorigin8");
 
   // iframe from same origin, X-F-O: DENY,SAMEORIGIN - should not load
   frame = harness.contentDocument.getElementById("mixedpolicy");
   var test10 = frame.contentDocument.getElementById("test");
   is(test10, null, "test mixedpolicy");
 
+  // iframe from different origin, allow-from: this origin - should load
+  frame = harness.contentDocument.getElementById("allow-from-allow");
+  var test11 = frame.contentDocument.getElementById("test").textContent;
+  is(test11, "allow-from-allow", "test allow-from-allow");
+
+  // iframe from different origin, with allow-from: other - should not load
+  frame = harness.contentDocument.getElementById("allow-from-deny");
+  var test12 = frame.contentDocument.getElementById("test");
+  is(test12, null, "test allow-from-deny");
+
   // call tests to check principal comparison, e.g. a document can open a window
   // to a data: or javascript: document which frames an
   // X-Frame-Options: SAMEORIGIN document and the frame should load
   testFrameInJSURI();
 }
 
 // test that a document can be framed under a javascript: URL opened by the
 // same site as the frame
--- a/docshell/base/nsDSURIContentListener.cpp
+++ b/docshell/base/nsDSURIContentListener.cpp
@@ -6,16 +6,17 @@
 #include "nsDocShell.h"
 #include "nsDSURIContentListener.h"
 #include "nsIChannel.h"
 #include "nsServiceManagerUtils.h"
 #include "nsXPIDLString.h"
 #include "nsDocShellCID.h"
 #include "nsIWebNavigationInfo.h"
 #include "nsIDOMWindow.h"
+#include "nsNetUtil.h"
 #include "nsAutoPtr.h"
 #include "nsIHttpChannel.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsError.h"
 #include "nsCharSeparatedTokenizer.h"
 #include "mozilla/Preferences.h"
 
 using namespace mozilla;
@@ -253,19 +254,25 @@ nsDSURIContentListener::SetParentContent
         mWeakParentContentListener = nullptr;
         mParentContentListener = nullptr;
     }
     return NS_OK;
 }
 
 bool nsDSURIContentListener::CheckOneFrameOptionsPolicy(nsIRequest *request,
                                                         const nsAString& policy) {
-    // return early if header does not have one of the two values with meaning
+    static const char allowFrom[] = "allow-from ";
+    const PRUint32 allowFromLen = ArrayLength(allowFrom) - 1;
+    bool isAllowFrom =
+        StringHead(policy, allowFromLen).LowerCaseEqualsLiteral(allowFrom);
+
+    // return early if header does not have one of the values with meaning
     if (!policy.LowerCaseEqualsLiteral("deny") &&
-        !policy.LowerCaseEqualsLiteral("sameorigin"))
+        !policy.LowerCaseEqualsLiteral("sameorigin") &&
+        !isAllowFrom)
         return true;
 
     nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(request);
     if (!httpChannel) {
         return true;
     }
 
     if (mDocShell) {
@@ -337,28 +344,42 @@ bool nsDSURIContentListener::CheckOneFra
 
         // If the value of the header is DENY, and the previous condition is
         // not met (current docshell is not the top docshell), prohibit the
         // load.
         if (policy.LowerCaseEqualsLiteral("deny")) {
             return false;
         }
 
+        topDoc = do_GetInterface(curDocShellItem);
+        nsCOMPtr<nsIURI> topUri;
+        topDoc->NodePrincipal()->GetURI(getter_AddRefs(topUri));
+        nsCOMPtr<nsIURI> uri;
+
         // If the X-Frame-Options value is SAMEORIGIN, then the top frame in the
         // parent chain must be from the same origin as this document.
         if (policy.LowerCaseEqualsLiteral("sameorigin")) {
-            nsCOMPtr<nsIURI> uri;
             httpChannel->GetURI(getter_AddRefs(uri));
-            topDoc = do_GetInterface(curDocShellItem);
-            nsCOMPtr<nsIURI> topUri;
-            topDoc->NodePrincipal()->GetURI(getter_AddRefs(topUri));
             rv = ssm->CheckSameOriginURI(uri, topUri, true);
             if (NS_FAILED(rv))
                 return false; /* wasn't same-origin */
         }
+
+        // If the X-Frame-Options value is "allow-from [uri]", then the top
+        // frame in the parent chain must be from that origin
+        if (isAllowFrom) {
+            rv = NS_NewURI(getter_AddRefs(uri),
+                           Substring(policy, allowFromLen));
+            if (NS_FAILED(rv))
+              return false;
+
+            rv = ssm->CheckSameOriginURI(uri, topUri, true);
+            if (NS_FAILED(rv))
+                return false;
+        }
     }
 
     return true;
 }
 
 // Check if X-Frame-Options permits this document to be loaded as a subdocument.
 // This will iterate through and check any number of X-Frame-Options policies
 // in the request (comma-separated in a header, multiple headers, etc).
--- a/dom/browser-element/mochitest/Makefile.in
+++ b/dom/browser-element/mochitest/Makefile.in
@@ -58,16 +58,20 @@ MOCHITEST_FILES = \
 		test_browserElement_inproc_XFrameOptions.html \
 		file_browserElement_XFrameOptions.sjs \
 		browserElement_XFrameOptionsDeny.js \
 		test_browserElement_inproc_XFrameOptionsDeny.html \
 		file_browserElement_XFrameOptionsDeny.html \
 		browserElement_XFrameOptionsSameOrigin.js \
 		test_browserElement_inproc_XFrameOptionsSameOrigin.html \
 		file_browserElement_XFrameOptionsSameOrigin.html \
+		browserElement_XFrameOptionsAllowFrom.js \
+		test_browserElement_inproc_XFrameOptionsAllowFrom.html \
+		file_browserElement_XFrameOptionsAllowFrom.html \
+		file_browserElement_XFrameOptionsAllowFrom.sjs \
 		browserElement_Alert.js \
 		test_browserElement_inproc_Alert.html \
 		browserElement_AlertInFrame.js \
 		test_browserElement_inproc_AlertInFrame.html \
 		file_browserElement_AlertInFrame.html \
 		file_browserElement_AlertInFrame_Inner.html \
 		browserElement_TargetTop.js \
 		test_browserElement_inproc_TargetTop.html \
@@ -149,16 +153,17 @@ MOCHITEST_FILES += \
 		test_browserElement_oop_GetScreenshot.html \
 		test_browserElement_oop_SetVisible.html \
 		test_browserElement_oop_SetVisibleFrames.html \
 		test_browserElement_oop_SetVisibleFrames2.html \
 		test_browserElement_oop_KeyEvents.html \
 		test_browserElement_oop_XFrameOptions.html \
 		test_browserElement_oop_XFrameOptionsDeny.html \
 		test_browserElement_oop_XFrameOptionsSameOrigin.html \
+		test_browserElement_oop_XFrameOptionsAllowFrom.html \
 		test_browserElement_oop_Alert.html \
 		test_browserElement_oop_AlertInFrame.html \
 		test_browserElement_oop_TargetTop.html \
 		test_browserElement_oop_ForwardName.html \
 		test_browserElement_oop_TargetBlank.html \
 		test_browserElement_oop_PromptCheck.html \
 		test_browserElement_oop_PromptConfirm.html \
 		test_browserElement_oop_Close.html \
new file mode 100644
--- /dev/null
+++ b/dom/browser-element/mochitest/browserElement_XFrameOptionsAllowFrom.js
@@ -0,0 +1,53 @@
+/* Any copyright is dedicated to the public domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Bug 690168 - Support Allow-From notation for X-Frame-Options header.
+"use strict";
+
+SimpleTest.waitForExplicitFinish();
+
+var initialScreenshot = null;
+
+function runTest() {
+  browserElementTestHelpers.setEnabledPref(true);
+  browserElementTestHelpers.addPermission();
+  var count = 0;
+
+  var iframe = document.createElement('iframe');
+  iframe.mozbrowser = true;
+  iframe.height = '1000px';
+
+  // The innermost page we load will fire an alert when it successfully loads.
+  iframe.addEventListener('mozbrowsershowmodalprompt', function(e) {
+    switch (e.detail.message) {
+    case 'step 1':
+      // Make the page wait for us to unblock it (which we do after we finish
+      // taking the screenshot).
+      e.preventDefault();
+
+      iframe.getScreenshot().onsuccess = function(sshot) {
+        if (initialScreenshot == null)
+          initialScreenshot = sshot.target.result;
+        e.detail.unblock();
+      };
+      break;
+    case 'step 2':
+      ok(false, 'cross origin page loaded');
+      break;
+    case 'finish':
+      // The page has now attempted to load the X-Frame-Options page; take
+      // another screenshot.
+      iframe.getScreenshot().onsuccess = function(sshot) {
+        is(sshot.target.result, initialScreenshot, "Screenshots should be identical");
+        SimpleTest.finish();
+      };
+      break;
+    }
+  });
+
+  document.body.appendChild(iframe);
+
+  iframe.src = 'http://example.com/tests/dom/browser-element/mochitest/file_browserElement_XFrameOptionsAllowFrom.html';
+}
+
+runTest();
new file mode 100644
--- /dev/null
+++ b/dom/browser-element/mochitest/file_browserElement_XFrameOptionsAllowFrom.html
@@ -0,0 +1,43 @@
+<html>
+<body>
+
+  <!-- Try to load in a frame a cross-origin page which sends:
+     "X-Frame-Options: Allow-From http://mochi.test:8888/",
+       and a cross-origin page which sends
+     "X-Frame-Options: Allow-From http://example.com/". -->
+
+<script>
+
+// Make sure these iframes aren't too tall; they both need to fit inside the
+// iframe this page is contained in, without scrolling, in order for the test's
+// screenshots to work properly.
+
+var frame_src = 'http://example.com/tests/dom/browser-element/mochitest/file_browserElement_XFrameOptionsAllowFrom.sjs';
+
+var iframe1 = document.createElement('iframe');
+iframe1.height = '300px';
+var iframe2 = document.createElement('iframe');
+iframe2.height = '300px';
+document.body.appendChild(iframe1);
+document.body.appendChild(iframe2);
+
+iframe1.addEventListener('load', function iframe1Load() {
+  iframe1.removeEventListener('load', iframe1Load);
+  // This causes our embedder to take a screenshot (and blocks until the
+  // screenshot is completed).
+  var iframe2Loaded = false;
+  iframe2.addEventListener('load', function iframe2Load() {
+    iframe2.removeEventListener('load', iframe2Load);
+    iframe2Loaded = true;
+    alert('finish');
+  });
+
+  setTimeout(function() { iframe2.src = frame_src; }, 1000);
+});
+
+
+iframe1.src = frame_src + '?iframe1';
+</script>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/browser-element/mochitest/file_browserElement_XFrameOptionsAllowFrom.sjs
@@ -0,0 +1,16 @@
+function handleRequest(request, response)
+{
+  var content = 'step 1';
+  if (request.queryString == "iframe1") {
+      response.setHeader("X-Frame-Options", "Allow-From http://mochi.test:8888/")
+      content = 'finish';
+  } else {
+      response.setHeader("X-Frame-Options", "Allow-From http://example.com")
+  }
+
+  response.setHeader("Content-Type", "text/html", false);
+
+  // Tests rely on this page not being entirely blank, because they take
+  // screenshots to determine whether this page was loaded.
+  response.write("<html><body>XFrameOptions test<script>alert('" + content + "')</script></body></html>");
+}
new file mode 100644
--- /dev/null
+++ b/dom/browser-element/mochitest/test_browserElement_inproc_XFrameOptionsAllowFrom.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for Bug 690168</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="browserElementTestHelpers.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<script type="application/javascript;version=1.7" src="browserElement_XFrameOptionsAllowFrom.js">
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/browser-element/mochitest/test_browserElement_oop_XFrameOptionsAllowFrom.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for Bug 690168</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="browserElementTestHelpers.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<script type="application/javascript;version=1.7" src="browserElement_XFrameOptionsAllowFrom.js">
+</script>
+</body>
+</html>