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 103599 72755799451cc5e8d79dfc0e1c430634f19df023
parent 103598 721ff7492145e230ba54431548dc8afb7865c1c1
child 103600 8ac55d26cf220716f85cc55471b2eb5cbdc5069f
push id14069
push userbzbarsky@mozilla.com
push dateMon, 27 Aug 2012 19:46:40 +0000
treeherdermozilla-inbound@72755799451c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbzbarsky
bugs690168
milestone17.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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>