Bug 702820 - Allow XHR to data URL. r=sicking
authorMasatoshi Kimura <VYV03354@nifty.ne.jp>
Wed, 11 Jan 2012 10:47:46 +0100
changeset 85477 9f1349e72752bb9cf4d87fdd17db50fce302bd91
parent 85476 8202dc75af5d132f9fa38ef84860a9fdf569ca0a
child 85478 4202e19f36e29ed8f9d95fe5747cf352b1209d6d
push id805
push userakeybl@mozilla.com
push dateWed, 01 Feb 2012 18:17:35 +0000
treeherdermozilla-aurora@6fb3bf232436 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssicking
bugs702820
milestone12.0a1
Bug 702820 - Allow XHR to data URL. r=sicking
content/base/src/nsCrossSiteListenerProxy.cpp
content/base/src/nsCrossSiteListenerProxy.h
content/base/src/nsXMLHttpRequest.cpp
content/base/test/test_XHR.html
--- a/content/base/src/nsCrossSiteListenerProxy.cpp
+++ b/content/base/src/nsCrossSiteListenerProxy.cpp
@@ -408,16 +408,40 @@ nsCORSListenerProxy::nsCORSListenerProxy
     mOuterNotificationCallbacks = nsnull;
   }
 }
 
 nsCORSListenerProxy::nsCORSListenerProxy(nsIStreamListener* aOuter,
                                          nsIPrincipal* aRequestingPrincipal,
                                          nsIChannel* aChannel,
                                          bool aWithCredentials,
+                                         bool aAllowDataURI,
+                                         nsresult* aResult)
+  : mOuterListener(aOuter),
+    mRequestingPrincipal(aRequestingPrincipal),
+    mWithCredentials(aWithCredentials && !gDisableCORSPrivateData),
+    mRequestApproved(false),
+    mHasBeenCrossSite(false),
+    mIsPreflight(false)
+{
+  aChannel->GetNotificationCallbacks(getter_AddRefs(mOuterNotificationCallbacks));
+  aChannel->SetNotificationCallbacks(this);
+
+  *aResult = UpdateChannel(aChannel, aAllowDataURI);
+  if (NS_FAILED(*aResult)) {
+    mOuterListener = nsnull;
+    mRequestingPrincipal = nsnull;
+    mOuterNotificationCallbacks = nsnull;
+  }
+}
+
+nsCORSListenerProxy::nsCORSListenerProxy(nsIStreamListener* aOuter,
+                                         nsIPrincipal* aRequestingPrincipal,
+                                         nsIChannel* aChannel,
+                                         bool aWithCredentials,
                                          const nsCString& aPreflightMethod,
                                          const nsTArray<nsCString>& aPreflightHeaders,
                                          nsresult* aResult)
   : mOuterListener(aOuter),
     mRequestingPrincipal(aRequestingPrincipal),
     mWithCredentials(aWithCredentials && !gDisableCORSPrivateData),
     mRequestApproved(false),
     mHasBeenCrossSite(false),
@@ -718,23 +742,33 @@ nsCORSListenerProxy::OnRedirectVerifyCal
   mOldRedirectChannel = nsnull;
   mNewRedirectChannel = nsnull;
   mRedirectCallback->OnRedirectVerifyCallback(result);
   mRedirectCallback   = nsnull;
   return NS_OK;
 }
 
 nsresult
-nsCORSListenerProxy::UpdateChannel(nsIChannel* aChannel)
+nsCORSListenerProxy::UpdateChannel(nsIChannel* aChannel, bool aAllowDataURI)
 {
   nsCOMPtr<nsIURI> uri, originalURI;
   nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
   NS_ENSURE_SUCCESS(rv, rv);
   rv = aChannel->GetOriginalURI(getter_AddRefs(originalURI));
-  NS_ENSURE_SUCCESS(rv, rv);  
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // exempt data URIs from the same origin check.
+  if (aAllowDataURI && originalURI == uri) {
+    bool dataScheme = false;
+    rv = uri->SchemeIs("data", &dataScheme);
+    NS_ENSURE_SUCCESS(rv, rv);
+    if (dataScheme) {
+      return NS_OK;
+    }
+  }
 
   // Check that the uri is ok to load
   rv = nsContentUtils::GetSecurityManager()->
     CheckLoadURIWithPrincipal(mRequestingPrincipal, uri,
                               nsIScriptSecurityManager::STANDARD);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (originalURI != uri) {
--- a/content/base/src/nsCrossSiteListenerProxy.h
+++ b/content/base/src/nsCrossSiteListenerProxy.h
@@ -73,16 +73,22 @@ public:
                       nsIPrincipal* aRequestingPrincipal,
                       nsIChannel* aChannel,
                       bool aWithCredentials,
                       nsresult* aResult);
   nsCORSListenerProxy(nsIStreamListener* aOuter,
                       nsIPrincipal* aRequestingPrincipal,
                       nsIChannel* aChannel,
                       bool aWithCredentials,
+                      bool aAllowDataURI,
+                      nsresult* aResult);
+  nsCORSListenerProxy(nsIStreamListener* aOuter,
+                      nsIPrincipal* aRequestingPrincipal,
+                      nsIChannel* aChannel,
+                      bool aWithCredentials,
                       const nsCString& aPreflightMethod,
                       const nsTArray<nsCString>& aPreflightHeaders,
                       nsresult* aResult);
 
   NS_DECL_ISUPPORTS
   NS_DECL_NSIREQUESTOBSERVER
   NS_DECL_NSISTREAMLISTENER
   NS_DECL_NSIINTERFACEREQUESTOR
@@ -90,17 +96,17 @@ public:
   NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
 
   // Must be called at startup.
   static void Startup();
 
   static void Shutdown();
 
 private:
-  nsresult UpdateChannel(nsIChannel* aChannel);
+  nsresult UpdateChannel(nsIChannel* aChannel, bool aAllowDataURI = false);
   nsresult CheckRequestApproved(nsIRequest* aRequest);
 
   nsCOMPtr<nsIStreamListener> mOuterListener;
   nsCOMPtr<nsIPrincipal> mRequestingPrincipal;
   nsCOMPtr<nsIInterfaceRequestor> mOuterNotificationCallbacks;
   bool mWithCredentials;
   bool mRequestApproved;
   bool mHasBeenCrossSite;
--- a/content/base/src/nsXMLHttpRequest.cpp
+++ b/content/base/src/nsXMLHttpRequest.cpp
@@ -1454,23 +1454,33 @@ nsXMLHttpRequest::CheckChannelForCrossSi
     return NS_OK;
   }
 
   // ...or if this is a same-origin request.
   if (nsContentUtils::CheckMayLoad(mPrincipal, aChannel)) {
     return NS_OK;
   }
 
+  // exempt data URIs from the same origin check.
+  nsCOMPtr<nsIURI> channelURI;
+  bool dataScheme = false;
+  if (NS_SUCCEEDED(NS_GetFinalChannelURI(aChannel,
+                                         getter_AddRefs(channelURI))) &&
+      NS_SUCCEEDED(channelURI->SchemeIs("data", &dataScheme)) &&
+      dataScheme) {
+    return NS_OK;
+  }
+
   // This is a cross-site request
   mState |= XML_HTTP_REQUEST_USE_XSITE_AC;
 
   // Check if we need to do a preflight request.
   nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
   NS_ENSURE_TRUE(httpChannel, NS_ERROR_DOM_BAD_URI);
-    
+
   nsCAutoString method;
   httpChannel->GetRequestMethod(method);
   if (!mCORSUnsafeHeaders.IsEmpty() ||
       HasListenersFor(NS_LITERAL_STRING(UPLOADPROGRESS_STR)) ||
       (mUpload && mUpload->HasListeners()) ||
       (!method.LowerCaseEqualsLiteral("get") &&
        !method.LowerCaseEqualsLiteral("post") &&
        !method.LowerCaseEqualsLiteral("head"))) {
@@ -2582,17 +2592,17 @@ nsXMLHttpRequest::Send(nsIVariant *aBody
       return NS_ERROR_OUT_OF_MEMORY;
     }
   }
 
   if (!IsSystemXHR()) {
     // Always create a nsCORSListenerProxy here even if it's
     // a same-origin request right now, since it could be redirected.
     listener = new nsCORSListenerProxy(listener, mPrincipal, mChannel,
-                                       withCredentials, &rv);
+                                       withCredentials, true, &rv);
     NS_ENSURE_TRUE(listener, NS_ERROR_OUT_OF_MEMORY);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   // Bypass the network cache in cases where it makes no sense:
   // 1) Multipart responses are very large and would likely be doomed by the
   //    cache once they grow too large, so they are not worth caching.
   // 2) POST responses are always unique, and we provide no API that would
--- a/content/base/test/test_XHR.html
+++ b/content/base/test/test_XHR.html
@@ -16,32 +16,34 @@ SimpleTest.waitForExplicitFinish();
 
 var gen = runTests();
 function continueTest() { gen.next(); }
 
 function runTests() {
 
 var path = "/tests/content/base/test/";
 
-var passFiles = [['file_XHR_pass1.xml', 'GET'],
-                 ['file_XHR_pass2.txt', 'GET'],
-                 ['file_XHR_pass3.txt', 'GET'],
+var passFiles = [['file_XHR_pass1.xml', 'GET', 200],
+                 ['file_XHR_pass2.txt', 'GET', 200],
+                 ['file_XHR_pass3.txt', 'GET', 200],
+                 ['data:text/xml,%3Cres%3Ehello%3C/res%3E%0A', 'GET', 0],
+                 ['data:text/plain,hello%20pass%0A', 'GET', 0],
                  ];
 
 var failFiles = [['//example.com' + path + 'file_XHR_pass1.xml', 'GET'],
                  ['ftp://localhost' + path + 'file_XHR_pass1.xml', 'GET'],
                  ['file_XHR_fail1.txt', 'GET'],
                  ];
 
 for (i = 0; i < passFiles.length; ++i) {
   xhr = new XMLHttpRequest();
   is(xhr.responseType, "", "wrong initial responseType");
   xhr.open(passFiles[i][1], passFiles[i][0], false); 
   xhr.send(null);
-  is(xhr.status, 200, "wrong status");
+  is(xhr.status, passFiles[i][2], "wrong status");
   if (xhr.responseXML) {
     is((new XMLSerializer()).serializeToString(xhr.responseXML.documentElement),
        "<res>hello</res>",
        "wrong responseXML");
     is(xhr.response, "<res>hello</res>\n", "wrong response");
   }
   else {
     is(xhr.responseText, "hello pass\n", "wrong responseText");