AssociatedURLLoader adds support for the HTTP response header Access-Control-Expose...
authorbbudge@chromium.org <bbudge@chromium.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 17 Jan 2012 10:26:31 +0000 (10:26 +0000)
committerbbudge@chromium.org <bbudge@chromium.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 17 Jan 2012 10:26:31 +0000 (10:26 +0000)
https://bugs.webkit.org/show_bug.cgi?id=76419

Reviewed by Adam Barth.

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

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

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

index e6cdb3f..260b9f2 100644 (file)
@@ -1,3 +1,16 @@
+2012-01-17  Bill Budge  <bbudge@chromium.org>
+
+        AssociatedURLLoader adds support for the HTTP response header Access-Control-Expose-Header.
+        https://bugs.webkit.org/show_bug.cgi?id=76419
+
+        Reviewed by Adam Barth.
+
+        * src/AssociatedURLLoader.cpp:
+        (WebKit::AssociatedURLLoader::ClientAdapter::didReceiveResponse):
+        * tests/AssociatedURLLoaderTest.cpp:
+        (WebKit::AssociatedURLLoaderTest::CheckAccessControlHeaders):
+        (WebKit::TEST_F):
+
 2012-01-16  Bill Budge  <bbudge@chromium.org>
 
         Changes AssociatedURLLoader to remove non-whitelisted HTTP response headers for CORS requests,
index fdc3cd0..c314411 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010, 2011 Google Inc. All rights reserved.
+ * Copyright (C) 2010, 2011, 2012 Google Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are
@@ -50,7 +50,7 @@
 #include "platform/WebURLError.h"
 #include "platform/WebURLLoaderClient.h"
 #include "platform/WebURLRequest.h"
-#include <wtf/Vector.h>
+#include <wtf/HashSet.h>
 #include <wtf/text/WTFString.h>
 
 using namespace WebCore;
@@ -72,6 +72,8 @@ private:
     bool m_isSafe;
 };
 
+typedef HashSet<String, CaseFoldingHash> HTTPHeaderSet;
+
 void HTTPRequestHeaderValidator::visitHeader(const WebString& name, const WebString& value)
 {
     m_isSafe = m_isSafe && isValidHTTPToken(name) && XMLHttpRequest::isAllowedHTTPHeader(name) && isValidHTTPHeaderValue(value);
@@ -83,21 +85,40 @@ public:
     HTTPResponseHeaderValidator(bool usingAccessControl) : m_usingAccessControl(usingAccessControl) { }
 
     void visitHeader(const WebString& name, const WebString& value);
-    const Vector<WebString>& disallowedHeaders() const { return m_disallowedHeaders; }
+    const HTTPHeaderSet& blockedHeaders();
 
 private:
-    Vector<WebString> m_disallowedHeaders;
+    HTTPHeaderSet m_exposedHeaders;
+    HTTPHeaderSet m_blockedHeaders;
     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);
+    if (m_usingAccessControl) {
+        if (equalIgnoringCase(headerName, "access-control-expose-header"))
+            parseAccessControlExposeHeadersAllowList(value, m_exposedHeaders);
+        else if (!isOnAccessControlResponseHeaderWhitelist(headerName))
+            m_blockedHeaders.add(name);
+    }
+}
+
+const HTTPHeaderSet& HTTPResponseHeaderValidator::blockedHeaders()
+{
+    // Remove exposed headers from the blocked set.
+    if (!m_exposedHeaders.isEmpty()) {
+        // Don't allow Set-Cookie headers to be exposed.
+        m_exposedHeaders.remove("set-cookie");
+        m_exposedHeaders.remove("set-cookie2");
+        // Block Access-Control-Expose-Header itself. It could be exposed later.
+        m_blockedHeaders.add("access-control-expose-header");
+        HTTPHeaderSet::const_iterator end = m_exposedHeaders.end();
+        for (HTTPHeaderSet::const_iterator it = m_exposedHeaders.begin(); it != end; ++it)
+            m_blockedHeaders.remove(*it);
+    }
+
+    return m_blockedHeaders;
 }
 
 }
@@ -187,12 +208,13 @@ void AssociatedURLLoader::ClientAdapter::didReceiveResponse(unsigned long, const
     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()) {
+    // If there are blocked headers, copy the response so we can remove them.
+    const HTTPHeaderSet& blockedHeaders = validator.blockedHeaders();
+    if (!blockedHeaders.isEmpty()) {
         validatedResponse = WebURLResponse(validatedResponse);
-        for (size_t i = 0; i < disallowedHeaders.size(); ++i)
-            validatedResponse.clearHTTPHeaderField(disallowedHeaders[i]);
+        HTTPHeaderSet::const_iterator end = blockedHeaders.end();
+        for (HTTPHeaderSet::const_iterator it = blockedHeaders.begin(); it != end; ++it)
+            validatedResponse.clearHTTPHeaderField(*it);
     }
     m_client->didReceiveResponse(m_loader, validatedResponse);
 }
index 4bb492b..677865a 100644 (file)
@@ -40,6 +40,7 @@
 #include "platform/WebURLLoaderClient.h"
 #include "platform/WebURLRequest.h"
 #include "platform/WebURLResponse.h"
+#include <wtf/text/WTFString.h>
 
 #include <googleurl/src/gurl.h>
 #include <gtest/gtest.h>
@@ -221,6 +222,42 @@ public:
         EXPECT_FALSE(m_didReceiveResponse);
     }
 
+    bool CheckAccessControlHeaders(const char* headerName, bool exposed)
+    {
+        std::string id("http://www.other.com/CheckAccessControlExposeHeaders_");
+        id.append(headerName);
+        if (exposed)
+            id.append("-Exposed");
+        id.append(".html");
+
+        GURL url = GURL(id);
+        WebURLRequest request;
+        request.initialize();
+        request.setURL(url);
+
+        WebString headerNameString(WebString::fromUTF8(headerName));
+        m_expectedResponse = WebURLResponse();
+        m_expectedResponse.initialize();
+        m_expectedResponse.setMIMEType("text/html");
+        m_expectedResponse.addHTTPHeaderField("Access-Control-Allow-Origin", "*");
+        if (exposed)
+            m_expectedResponse.addHTTPHeaderField("access-control-expose-header", headerNameString);
+        m_expectedResponse.addHTTPHeaderField(headerNameString, "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);
+
+        return !m_actualResponse.httpHeaderField(headerNameString).isEmpty();
+    }
+
 protected:
     WebString m_frameFilePath;
     TestWebFrameClient m_webFrameClient;
@@ -489,50 +526,29 @@ TEST_F(AssociatedURLLoaderTest, UntrustedCheckHeaders)
     CheckHeaderFails("foo", "bar\x0d\x0ax-csrf-token:\x20test1234");
 }
 
-// Test that a CORS load only returns whitelisted headers.
+// Test that the loader filters response headers according to the CORS standard.
 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());
+    // Test that whitelisted headers are returned without exposing them.
+    EXPECT_TRUE(CheckAccessControlHeaders("cache-control", false));
+    EXPECT_TRUE(CheckAccessControlHeaders("content-language", false));
+    EXPECT_TRUE(CheckAccessControlHeaders("content-type", false));
+    EXPECT_TRUE(CheckAccessControlHeaders("expires", false));
+    EXPECT_TRUE(CheckAccessControlHeaders("last-modified", false));
+    EXPECT_TRUE(CheckAccessControlHeaders("pragma", false));
+
+    // Test that non-whitelisted headers aren't returned.
+    EXPECT_FALSE(CheckAccessControlHeaders("non-whitelisted", false));
+
+    // Test that Set-Cookie headers aren't returned.
+    EXPECT_FALSE(CheckAccessControlHeaders("Set-Cookie", false));
+    EXPECT_FALSE(CheckAccessControlHeaders("Set-Cookie2", false));
+
+    // Test that exposed headers that aren't whitelisted are returned.
+    EXPECT_TRUE(CheckAccessControlHeaders("non-whitelisted", true));
+
+    // Test that Set-Cookie headers aren't returned, even if exposed.
+    EXPECT_FALSE(CheckAccessControlHeaders("Set-Cookie", true));
 }
 
 }