Bug 581226 - fix assertion: DoContent returned no listener?: 'abort || m_targetStreamListener', a=blocking2.0
authorBrandon Sterne <bsterne@mozilla.com>
Thu, 14 Oct 2010 15:26:59 -0700
changeset 55818 5fcb86d516919d4d0c731ec678f54d434b722b20
parent 55817 c5494ee56c47a37733a8337487a8385ebd7786ed
child 55819 ca9083cab8ef9424d862fe8fe02d86b6bb0a8eee
push idunknown
push userunknown
push dateunknown
reviewersblocking2.0
bugs581226
milestone2.0b8pre
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 581226 - fix assertion: DoContent returned no listener?: 'abort || m_targetStreamListener', a=blocking2.0
content/base/src/nsDocument.cpp
content/base/test/test_x-frame-options.html
docshell/base/nsDSURIContentListener.cpp
docshell/base/nsDSURIContentListener.h
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -2353,130 +2353,23 @@ nsDocument::StartDocumentLoad(const char
     semicolon = start;
     FindCharInReadable(';', semicolon, end);
     SetContentTypeInternal(Substring(start, semicolon));
   }
 
   RetrieveRelevantHeaders(aChannel);
 
   mChannel = aChannel;
-  
-  nsresult rv = CheckFrameOptions();
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  rv = InitCSP();
+
+  nsresult rv = InitCSP();
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
-// Check if X-Frame-Options permits this document to be loaded as a subdocument.
-nsresult nsDocument::CheckFrameOptions()
-{
-  nsAutoString xfoHeaderValue;
-  this->GetHeaderData(nsGkAtoms::headerXFO, xfoHeaderValue);
-
-  // return early if header does not have one of the two values with meaning
-  if (!xfoHeaderValue.LowerCaseEqualsLiteral("deny") &&
-      !xfoHeaderValue.LowerCaseEqualsLiteral("sameorigin"))
-    return NS_OK;
-
-  nsCOMPtr<nsIDocShell> docShell = do_QueryReferent(mDocumentContainer);
-
-  if (docShell) {
-    PRBool framingAllowed = true;
-
-    // We need to check the location of this window and the location of the top
-    // window, if we're not the top.  X-F-O: SAMEORIGIN requires that the
-    // document must be same-origin with top window.  X-F-O: DENY requires that
-    // the document must never be framed.
-    nsCOMPtr<nsIDOMWindow> thisWindow = do_GetInterface(docShell);
-    nsCOMPtr<nsIDOMWindow> topWindow;
-    thisWindow->GetTop(getter_AddRefs(topWindow));
-
-    // if the document is in the top window, it's not in a frame.
-    if (thisWindow == topWindow)
-      return NS_OK;
-
-    // Find the top docshell in our parent chain that doesn't have the system
-    // principal and use it for the principal comparison.  Finding the top
-    // content-type docshell doesn't work because some chrome documents are
-    // loaded in content docshells (see bug 593387).
-    nsIPrincipal* thisPrincipal = NodePrincipal();
-    nsCOMPtr<nsIDocShellTreeItem> thisDocShellItem(do_QueryInterface(docShell));
-    nsCOMPtr<nsIDocShellTreeItem> parentDocShellItem,
-                                  curDocShellItem = thisDocShellItem;
-    nsCOMPtr<nsIDocument> topDoc;
-    nsIScriptSecurityManager *ssm = nsContentUtils::GetSecurityManager();
-    if (!ssm)
-      return NS_ERROR_CONTENT_BLOCKED;
-
-    // Traverse up the parent chain to the top docshell that doesn't have
-    // a system principal
-    curDocShellItem->GetParent(getter_AddRefs(parentDocShellItem));
-    while (parentDocShellItem) {
-      PRBool system = PR_FALSE;
-      topDoc = do_GetInterface(parentDocShellItem);
-      if (topDoc) {
-        if (NS_SUCCEEDED(ssm->IsSystemPrincipal(topDoc->NodePrincipal(),
-                                                &system)) && system) {
-          break;
-        }
-      }
-      else {
-        return NS_ERROR_CONTENT_BLOCKED;
-      }
-      curDocShellItem = parentDocShellItem;
-      curDocShellItem->GetParent(getter_AddRefs(parentDocShellItem));
-    }
-
-    // If this document has the top non-SystemPrincipal docshell it is not being
-    // framed or it is being framed by a chrome document, which we allow.
-    nsCOMPtr<nsIDocShellTreeItem> item(do_QueryInterface(docShell));
-    if (curDocShellItem == thisDocShellItem)
-      return NS_OK;
-
-    // If the value of the header is DENY, then the document
-    // should never be permitted to load as a subdocument.
-    if (xfoHeaderValue.LowerCaseEqualsLiteral("deny")) {
-      framingAllowed = false;
-    }
-
-    else if (xfoHeaderValue.LowerCaseEqualsLiteral("sameorigin")) {
-      // 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.
-      nsCOMPtr<nsIURI> uri = static_cast<nsIDocument*>(this)->GetDocumentURI();
-      nsCOMPtr<nsIDOMDocument> topDOMDoc;
-      topWindow->GetDocument(getter_AddRefs(topDOMDoc));
-      topDoc = do_QueryInterface(topDOMDoc);
-      if (topDoc) {
-        nsCOMPtr<nsIURI> topUri = topDoc->GetDocumentURI();
-        nsresult rv = ssm->CheckSameOriginURI(uri, topUri, PR_TRUE);
-
-        if (NS_FAILED(rv)) {
-          framingAllowed = false;
-        }
-      }
-    }
-
-    if (!framingAllowed) {
-      // cancel the load and display about:blank
-      mChannel->Cancel(NS_BINDING_ABORTED);
-      nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(docShell));
-      if (webNav) {
-        webNav->LoadURI(NS_LITERAL_STRING("about:blank").get(),
-                        0, nsnull, nsnull, nsnull);
-      }
-      return NS_ERROR_CONTENT_BLOCKED;
-    }
-  }
-
-  return NS_OK;
-}
-
 nsresult
 nsDocument::InitCSP()
 {
   if (CSPService::sCSPEnabled) {
     nsAutoString cspHeaderValue;
     nsAutoString cspROHeaderValue;
 
     this->GetHeaderData(nsGkAtoms::headerCSP, cspHeaderValue);
--- a/content/base/test/test_x-frame-options.html
+++ b/content/base/test/test_x-frame-options.html
@@ -30,17 +30,54 @@ var testFramesLoaded = function() {
 
   for (var t in testExpectedResults) {
     var frame = harness.contentDocument.getElementById(t);
     // test if frame loaded by checking for a contentDocument we can access
     test = frame.contentDocument.getElementById("test");
     is(test != null, testExpectedResults[t], "test "+t);
   }
 
-  SimpleTest.finish();
+  // 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
+var testFrameInJSURI = function() {
+  var html = '<iframe id="sameorigin3" src="http://mochi.test:8888/tests/content/base/test/file_x-frame-options_page.sjs?testid=sameorigin3&xfo=sameorigin"></iframe>';
+  var win = window.open();
+  win.onload = function() {
+    var test = win.document.getElementById("sameorigin3")
+              .contentDocument.getElementById("test");
+    ok(test != null, "frame under javascript: URL should have loaded.");
+    win.close();
+
+    // run last test
+    testFrameInDataURI();
+   }
+  win.location.href = "javascript:document.write('"+html+"');document.close();";
+}
+
+// test that a document can be framed under a data: URL opened by the
+// same site as the frame
+var testFrameInDataURI = function() {
+  var html = '<iframe id="sameorigin4" src="http://mochi.test:8888/tests/content/base/test/file_x-frame-options_page.sjs?testid=sameorigin4&xfo=sameorigin"></iframe>';
+  var win = window.open();
+  win.onload = function() {
+    var test = win.document.getElementById("sameorigin4")
+              .contentDocument.getElementById("test");
+    ok(test != null, "frame under data: URL should have loaded.");
+    win.close();
+
+    // finalize test
+    SimpleTest.finish();
+   }
+  win.location.href = "data:text/html,"+html;
 }
 
 SimpleTest.waitForExplicitFinish();
 
 // load the test harness
 document.getElementById("harness").src = "file_x-frame-options_main.html";
 
 </script>
--- a/docshell/base/nsDSURIContentListener.cpp
+++ b/docshell/base/nsDSURIContentListener.cpp
@@ -41,16 +41,19 @@
 #include "nsDSURIContentListener.h"
 #include "nsIChannel.h"
 #include "nsServiceManagerUtils.h"
 #include "nsXPIDLString.h"
 #include "nsDocShellCID.h"
 #include "nsIWebNavigationInfo.h"
 #include "nsIDOMWindowInternal.h"
 #include "nsAutoPtr.h"
+#include "nsIHttpChannel.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsNetError.h"
 
 //*****************************************************************************
 //***    nsDSURIContentListener: Object Management
 //*****************************************************************************
 
 nsDSURIContentListener::nsDSURIContentListener(nsDocShell* aDocShell)
     : mDocShell(aDocShell), 
       mParentContentListener(nsnull)
@@ -112,18 +115,25 @@ nsDSURIContentListener::DoContent(const 
                                   PRBool aIsContentPreferred,
                                   nsIRequest* request,
                                   nsIStreamListener** aContentHandler,
                                   PRBool* aAbortProcess)
 {
     nsresult rv;
     NS_ENSURE_ARG_POINTER(aContentHandler);
     NS_ENSURE_TRUE(mDocShell, NS_ERROR_FAILURE);
-    if(aAbortProcess)
-        *aAbortProcess = PR_FALSE;
+
+    // Check whether X-Frame-Options permits us to load this content in an
+    // iframe
+    if (!CheckFrameOptions(request)) {
+        *aAbortProcess = PR_TRUE;
+        return NS_OK;
+    }
+
+    *aAbortProcess = PR_FALSE;
 
     // determine if the channel has just been retargeted to us...
     nsLoadFlags loadFlags = 0;
     nsCOMPtr<nsIChannel> aOpenedChannel = do_QueryInterface(request);
 
     if (aOpenedChannel)
       aOpenedChannel->GetLoadFlags(&loadFlags);
 
@@ -263,8 +273,110 @@ nsDSURIContentListener::SetParentContent
     }
     else
     {
         mWeakParentContentListener = nsnull;
         mParentContentListener = nsnull;
     }
     return NS_OK;
 }
+
+// Check if X-Frame-Options permits this document to be loaded as a subdocument.
+bool nsDSURIContentListener::CheckFrameOptions(nsIRequest* request)
+{
+    nsCAutoString xfoHeaderValue;
+
+    nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(request);
+    if (!httpChannel) {
+        return true;
+    }
+
+    httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("X-Frame-Options"),
+                                   xfoHeaderValue);
+
+    // return early if header does not have one of the two values with meaning
+    if (!xfoHeaderValue.LowerCaseEqualsLiteral("deny") &&
+        !xfoHeaderValue.LowerCaseEqualsLiteral("sameorigin"))
+        return true;
+
+    if (mDocShell) {
+        // We need to check the location of this window and the location of the top
+        // window, if we're not the top.  X-F-O: SAMEORIGIN requires that the
+        // document must be same-origin with top window.  X-F-O: DENY requires that
+        // the document must never be framed.
+        nsCOMPtr<nsIDOMWindow> thisWindow = do_GetInterface(static_cast<nsIDocShell*>(mDocShell));
+        nsCOMPtr<nsIDOMWindow> topWindow;
+        thisWindow->GetTop(getter_AddRefs(topWindow));
+
+        // if the document is in the top window, it's not in a frame.
+        if (thisWindow == topWindow)
+            return true;
+
+        // Find the top docshell in our parent chain that doesn't have the system
+        // principal and use it for the principal comparison.  Finding the top
+        // content-type docshell doesn't work because some chrome documents are
+        // loaded in content docshells (see bug 593387).
+        nsCOMPtr<nsIDocShellTreeItem> thisDocShellItem(do_QueryInterface(
+                                                       static_cast<nsIDocShell*> (mDocShell)));
+        nsCOMPtr<nsIDocShellTreeItem> parentDocShellItem,
+                                      curDocShellItem = thisDocShellItem;
+        nsCOMPtr<nsIDocument> topDoc;
+        nsresult rv;
+        nsCOMPtr<nsIScriptSecurityManager> ssm =
+            do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
+        if (!ssm)
+            return false;
+
+        // Traverse up the parent chain to the top docshell that doesn't have
+        // a system principal
+        while (NS_SUCCEEDED(curDocShellItem->GetParent(getter_AddRefs(parentDocShellItem)) &&
+                            parentDocShellItem)) {
+            PRBool system = PR_FALSE;
+            topDoc = do_GetInterface(parentDocShellItem);
+            if (topDoc) {
+                if (NS_SUCCEEDED(ssm->IsSystemPrincipal(topDoc->NodePrincipal(),
+                                                        &system)) && system) {
+                    break;
+                }
+            }
+            else {
+                return false;
+            }
+            curDocShellItem = parentDocShellItem;
+        }
+
+        // If this document has the top non-SystemPrincipal docshell it is not being
+        // framed or it is being framed by a chrome document, which we allow.
+        if (curDocShellItem == thisDocShellItem)
+            return true;
+
+        // 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 (xfoHeaderValue.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, PR_TRUE);
+            if (NS_SUCCEEDED(rv))
+                return true;
+        }
+
+        else {
+            // If the value of the header is DENY, then the document
+            // should never be permitted to load as a subdocument.
+            NS_ASSERTION(xfoHeaderValue.LowerCaseEqualsLiteral("deny"),
+                         "How did we get here with some random header value?");
+        }
+
+        // cancel the load and display about:blank
+        httpChannel->Cancel(NS_BINDING_ABORTED);
+        nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(static_cast<nsIDocShell*>(mDocShell)));
+        if (webNav) {
+            webNav->LoadURI(NS_LITERAL_STRING("about:blank").get(),
+                            0, nsnull, nsnull, nsnull);
+        }
+        return false;
+    }
+
+    return true;
+}
--- a/docshell/base/nsDSURIContentListener.h
+++ b/docshell/base/nsDSURIContentListener.h
@@ -63,16 +63,20 @@ public:
 protected:
     nsDSURIContentListener(nsDocShell* aDocShell);
     virtual ~nsDSURIContentListener();
 
     void DropDocShellreference() {
         mDocShell = nsnull;
     }
 
+    // Determine if X-Frame-Options allows content to be framed
+    // as a subdocument
+    bool CheckFrameOptions(nsIRequest* request);
+
 protected:
     nsDocShell*                      mDocShell;
 
     // Store the parent listener in either of these depending on
     // if supports weak references or not. Proper weak refs are
     // preferred and encouraged!
     nsWeakPtr                        mWeakParentContentListener;
     nsIURIContentListener*           mParentContentListener;