Changes AssociatedURLLoader to remove non-whitelisted HTTP response headers for CORS...
authorbbudge@chromium.org <bbudge@chromium.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 16 Jan 2012 21:18:39 +0000 (21:18 +0000)
committerbbudge@chromium.org <bbudge@chromium.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 16 Jan 2012 21:18:39 +0000 (21:18 +0000)
and Set-Cookie and Set-Cookie2 response headers for all requests.
https://bugs.webkit.org/show_bug.cgi?id=76228

Reviewed by Adam Barth.

* src/AssociatedURLLoader.cpp:
(WebKit::AssociatedURLLoader::ClientAdapter::create):
(WebKit::AssociatedURLLoader::ClientAdapter::ClientAdapter):
(WebKit::AssociatedURLLoader::ClientAdapter::didReceiveResponse):
(WebKit::AssociatedURLLoader::loadAsynchronously):
* tests/AssociatedURLLoaderTest.cpp:
(WebKit::AssociatedURLLoaderTest::didReceiveResponse):
(WebKit::TEST_F):

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@105087 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/WebKit/chromium/ChangeLog
Source/WebKit/chromium/src/AssociatedURLLoader.cpp
Source/WebKit/chromium/tests/AssociatedURLLoaderTest.cpp

index 050096b..e6cdb3f 100644 (file)
@@ -1,3 +1,20 @@
+2012-01-16  Bill Budge  <bbudge@chromium.org>
+
+        Changes AssociatedURLLoader to remove non-whitelisted HTTP response headers for CORS requests,
+        and Set-Cookie and Set-Cookie2 response headers for all requests.
+        https://bugs.webkit.org/show_bug.cgi?id=76228
+
+        Reviewed by Adam Barth.
+
+        * src/AssociatedURLLoader.cpp:
+        (WebKit::AssociatedURLLoader::ClientAdapter::create):
+        (WebKit::AssociatedURLLoader::ClientAdapter::ClientAdapter):
+        (WebKit::AssociatedURLLoader::ClientAdapter::didReceiveResponse):
+        (WebKit::AssociatedURLLoader::loadAsynchronously):
+        * tests/AssociatedURLLoaderTest.cpp:
+        (WebKit::AssociatedURLLoaderTest::didReceiveResponse):
+        (WebKit::TEST_F):
+
 2012-01-16  xueqing huang  <huangxueqing@baidu.com>
 
         Add offline web applications API applicationCache.abort.
index 6adeba7..fdc3cd0 100644 (file)
@@ -31,6 +31,7 @@
 #include "config.h"
 #include "AssociatedURLLoader.h"
 
+#include "CrossOriginAccessControl.h"
 #include "DocumentThreadableLoader.h"
 #include "DocumentThreadableLoaderClient.h"
 #include "HTTPValidation.h"
 #include "XMLHttpRequest.h"
 #include "platform/WebHTTPHeaderVisitor.h"
 #include "platform/WebKitPlatformSupport.h"
+#include "platform/WebString.h"
 #include "platform/WebURLError.h"
 #include "platform/WebURLLoaderClient.h"
 #include "platform/WebURLRequest.h"
+#include <wtf/Vector.h>
+#include <wtf/text/WTFString.h>
 
 using namespace WebCore;
 using namespace WTF;
@@ -56,10 +60,10 @@ namespace WebKit {
 
 namespace {
 
-class SafeHTTPHeaderValidator : public WebHTTPHeaderVisitor {
-    WTF_MAKE_NONCOPYABLE(SafeHTTPHeaderValidator);
+class HTTPRequestHeaderValidator : public WebHTTPHeaderVisitor {
+    WTF_MAKE_NONCOPYABLE(HTTPRequestHeaderValidator);
 public:
-    SafeHTTPHeaderValidator() : m_isSafe(true) { }
+    HTTPRequestHeaderValidator() : m_isSafe(true) { }
 
     void visitHeader(const WebString& name, const WebString& value);
     bool isSafe() const { return m_isSafe; }
@@ -68,11 +72,34 @@ private:
     bool m_isSafe;
 };
 
-void SafeHTTPHeaderValidator::visitHeader(const WebString& name, const WebString& value)
+void HTTPRequestHeaderValidator::visitHeader(const WebString& name, const WebString& value)
 {
     m_isSafe = m_isSafe && isValidHTTPToken(name) && XMLHttpRequest::isAllowedHTTPHeader(name) && isValidHTTPHeaderValue(value);
 }
 
+class HTTPResponseHeaderValidator : public WebHTTPHeaderVisitor {
+    WTF_MAKE_NONCOPYABLE(HTTPResponseHeaderValidator);
+public:
+    HTTPResponseHeaderValidator(bool usingAccessControl) : m_usingAccessControl(usingAccessControl) { }
+
+    void visitHeader(const WebString& name, const WebString& value);
+    const Vector<WebString>& disallowedHeaders() const { return m_disallowedHeaders; }
+
+private:
+    Vector<WebString> m_disallowedHeaders;
+    bool m_usingAccessControl;
+};
+
+void HTTPResponseHeaderValidator::visitHeader(const WebString& name, const WebString& value)
+{
+    String headerName(name);
+    // Hide non-whitelisted headers for CORS requests.
+    // Hide Set-Cookie headers for all requests.
+    if ((m_usingAccessControl && !isOnAccessControlResponseHeaderWhitelist(headerName))
+         || (equalIgnoringCase(headerName, "set-cookie") || equalIgnoringCase(headerName, "set-cookie2")))
+        m_disallowedHeaders.append(name);
+}
+
 }
 
 // This class bridges the interface differences between WebCore and WebKit loader clients.
@@ -80,7 +107,7 @@ void SafeHTTPHeaderValidator::visitHeader(const WebString& name, const WebString
 class AssociatedURLLoader::ClientAdapter : public DocumentThreadableLoaderClient {
     WTF_MAKE_NONCOPYABLE(ClientAdapter);
 public:
-    static PassOwnPtr<ClientAdapter> create(AssociatedURLLoader*, WebURLLoaderClient*, bool /*downloadToFile*/);
+    static PassOwnPtr<ClientAdapter> create(AssociatedURLLoader*, WebURLLoaderClient*, const WebURLLoaderOptions&);
 
     virtual void didSendData(unsigned long long /*bytesSent*/, unsigned long long /*totalBytesToBeSent*/);
     virtual void willSendRequest(ResourceRequest& /*newRequest*/, const ResourceResponse& /*redirectResponse*/);
@@ -105,30 +132,30 @@ public:
     void clearClient() { m_client = 0; } 
 
 private:
-    ClientAdapter(AssociatedURLLoader*, WebURLLoaderClient*, bool /*downloadToFile*/);
+    ClientAdapter(AssociatedURLLoader*, WebURLLoaderClient*, const WebURLLoaderOptions&);
 
     void notifyError(Timer<ClientAdapter>*);
 
     AssociatedURLLoader* m_loader;
     WebURLLoaderClient* m_client;
+    WebURLLoaderOptions m_options;
     WebURLError m_error;
 
     Timer<ClientAdapter> m_errorTimer;
-    bool m_downloadToFile;
     bool m_enableErrorNotifications;
     bool m_didFail;
 };
 
-PassOwnPtr<AssociatedURLLoader::ClientAdapter> AssociatedURLLoader::ClientAdapter::create(AssociatedURLLoader* loader, WebURLLoaderClient* client, bool downloadToFile)
+PassOwnPtr<AssociatedURLLoader::ClientAdapter> AssociatedURLLoader::ClientAdapter::create(AssociatedURLLoader* loader, WebURLLoaderClient* client, const WebURLLoaderOptions& options)
 {
-    return adoptPtr(new ClientAdapter(loader, client, downloadToFile));
+    return adoptPtr(new ClientAdapter(loader, client, options));
 }
 
-AssociatedURLLoader::ClientAdapter::ClientAdapter(AssociatedURLLoader* loader, WebURLLoaderClient* client, bool downloadToFile)
+AssociatedURLLoader::ClientAdapter::ClientAdapter(AssociatedURLLoader* loader, WebURLLoaderClient* client, const WebURLLoaderOptions& options)
     : m_loader(loader)
     , m_client(client)
+    , m_options(options)
     , m_errorTimer(this, &ClientAdapter::notifyError)
-    , m_downloadToFile(downloadToFile)
     , m_enableErrorNotifications(false)
     , m_didFail(false)
 {
@@ -156,8 +183,18 @@ void AssociatedURLLoader::ClientAdapter::didSendData(unsigned long long bytesSen
 
 void AssociatedURLLoader::ClientAdapter::didReceiveResponse(unsigned long, const ResourceResponse& response)
 {
-    WrappedResourceResponse wrappedResponse(response);
-    m_client->didReceiveResponse(m_loader, wrappedResponse);
+    // Try to use the original ResourceResponse if possible.
+    WebURLResponse validatedResponse = WrappedResourceResponse(response);
+    HTTPResponseHeaderValidator validator(m_options.crossOriginRequestPolicy == WebURLLoaderOptions::CrossOriginRequestPolicyUseAccessControl);
+    validatedResponse.visitHTTPHeaderFields(&validator);
+    // If there are disallowed headers, copy the response so we can remove them.
+    const Vector<WebString>& disallowedHeaders = validator.disallowedHeaders();
+    if (!disallowedHeaders.isEmpty()) {
+        validatedResponse = WebURLResponse(validatedResponse);
+        for (size_t i = 0; i < disallowedHeaders.size(); ++i)
+            validatedResponse.clearHTTPHeaderField(disallowedHeaders[i]);
+    }
+    m_client->didReceiveResponse(m_loader, validatedResponse);
 }
 
 void AssociatedURLLoader::ClientAdapter::didDownloadData(int dataLength)
@@ -263,13 +300,13 @@ void AssociatedURLLoader::loadAsynchronously(const WebURLRequest& request, WebUR
         allowLoad = isValidHTTPToken(method) && XMLHttpRequest::isAllowedHTTPMethod(method);
         if (allowLoad) {
             newRequest.setHTTPMethod(XMLHttpRequest::uppercaseKnownHTTPMethod(method));
-            SafeHTTPHeaderValidator validator;
+            HTTPRequestHeaderValidator validator;
             newRequest.visitHTTPHeaderFields(&validator);
             allowLoad = validator.isSafe();
         }
     }
 
-    m_clientAdapter = ClientAdapter::create(this, m_client, request.downloadToFile());
+    m_clientAdapter = ClientAdapter::create(this, m_client, m_options);
 
     if (allowLoad) {
         ThreadableLoaderOptions options;
index 62d12b4..4bb492b 100644 (file)
@@ -137,6 +137,7 @@ public:
     void didReceiveResponse(WebURLLoader* loader, const WebURLResponse& response)
     {
         m_didReceiveResponse = true;
+        m_actualResponse = WebURLResponse(response);
         EXPECT_EQ(m_expectedLoader, loader);
         EXPECT_EQ(m_expectedResponse.url(), response.url());
         EXPECT_EQ(m_expectedResponse.httpStatusCode(), response.httpStatusCode());
@@ -226,6 +227,7 @@ protected:
     WebView* m_webView;
 
     WebURLLoader* m_expectedLoader;
+    WebURLResponse m_actualResponse;
     WebURLResponse m_expectedResponse;
     WebURLRequest m_expectedNewRequest;
     WebURLResponse m_expectedRedirectResponse;
@@ -487,4 +489,50 @@ TEST_F(AssociatedURLLoaderTest, UntrustedCheckHeaders)
     CheckHeaderFails("foo", "bar\x0d\x0ax-csrf-token:\x20test1234");
 }
 
+// Test that a CORS load only returns whitelisted headers.
+TEST_F(AssociatedURLLoaderTest, CrossOriginHeaderWhitelisting)
+{
+    // This is cross-origin since the frame was loaded from www.test.com.
+    GURL url = GURL("http://www.other.com/CrossOriginHeaderWhitelisting.html");
+    WebURLRequest request;
+    request.initialize();
+    request.setURL(url);
+
+    m_expectedResponse = WebURLResponse();
+    m_expectedResponse.initialize();
+    m_expectedResponse.setMIMEType("text/html");
+    m_expectedResponse.addHTTPHeaderField("Access-Control-Allow-Origin", "*");
+    // These headers are whitelisted and should be in the response.
+    m_expectedResponse.addHTTPHeaderField("cache-control", "foo");
+    m_expectedResponse.addHTTPHeaderField("content-language", "foo");
+    m_expectedResponse.addHTTPHeaderField("content-type", "foo");
+    m_expectedResponse.addHTTPHeaderField("expires", "foo");
+    m_expectedResponse.addHTTPHeaderField("last-modified", "foo");
+    m_expectedResponse.addHTTPHeaderField("pragma", "foo");
+    // These should never be in the response.
+    m_expectedResponse.addHTTPHeaderField("Set-Cookie", "foo");
+    m_expectedResponse.addHTTPHeaderField("Set-Cookie2", "foo");
+    webkit_support::RegisterMockedURL(url, m_expectedResponse, m_frameFilePath);
+
+    WebURLLoaderOptions options;
+    options.crossOriginRequestPolicy = WebURLLoaderOptions::CrossOriginRequestPolicyUseAccessControl;
+    m_expectedLoader = createAssociatedURLLoader(options);
+    EXPECT_TRUE(m_expectedLoader);
+    m_expectedLoader->loadAsynchronously(request, this);
+    serveRequests();
+    EXPECT_TRUE(m_didReceiveResponse);
+    EXPECT_TRUE(m_didReceiveData);
+    EXPECT_TRUE(m_didFinishLoading);
+
+    EXPECT_FALSE(m_actualResponse.httpHeaderField("cache-control").isEmpty());
+    EXPECT_FALSE(m_actualResponse.httpHeaderField("content-language").isEmpty());
+    EXPECT_FALSE(m_actualResponse.httpHeaderField("content-type").isEmpty());
+    EXPECT_FALSE(m_actualResponse.httpHeaderField("expires").isEmpty());
+    EXPECT_FALSE(m_actualResponse.httpHeaderField("last-modified").isEmpty());
+    EXPECT_FALSE(m_actualResponse.httpHeaderField("pragma").isEmpty());
+
+    EXPECT_TRUE(m_actualResponse.httpHeaderField("Set-Cookie").isEmpty());
+    EXPECT_TRUE(m_actualResponse.httpHeaderField("Set-Cookie2").isEmpty());
+}
+
 }