Modified: trunk/Source/WebKit/chromium/ChangeLog (105133 => 105134)
--- trunk/Source/WebKit/chromium/ChangeLog 2012-01-17 09:28:03 UTC (rev 105133)
+++ trunk/Source/WebKit/chromium/ChangeLog 2012-01-17 10:26:31 UTC (rev 105134)
@@ -1,3 +1,16 @@
+2012-01-17 Bill Budge <[email protected]>
+
+ 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 <[email protected]>
Changes AssociatedURLLoader to remove non-whitelisted HTTP response headers for CORS requests,
Modified: trunk/Source/WebKit/chromium/src/AssociatedURLLoader.cpp (105133 => 105134)
--- trunk/Source/WebKit/chromium/src/AssociatedURLLoader.cpp 2012-01-17 09:28:03 UTC (rev 105133)
+++ trunk/Source/WebKit/chromium/src/AssociatedURLLoader.cpp 2012-01-17 10:26:31 UTC (rev 105134)
@@ -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 @@
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,25 +85,44 @@
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;
}
+}
+
// This class bridges the interface differences between WebCore and WebKit loader clients.
// It forwards its ThreadableLoaderClient notifications to a WebURLLoaderClient.
class AssociatedURLLoader::ClientAdapter : public DocumentThreadableLoaderClient {
@@ -187,12 +208,13 @@
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);
}
Modified: trunk/Source/WebKit/chromium/tests/AssociatedURLLoaderTest.cpp (105133 => 105134)
--- trunk/Source/WebKit/chromium/tests/AssociatedURLLoaderTest.cpp 2012-01-17 09:28:03 UTC (rev 105133)
+++ trunk/Source/WebKit/chromium/tests/AssociatedURLLoaderTest.cpp 2012-01-17 10:26:31 UTC (rev 105134)
@@ -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 @@
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 = ""
+ 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 @@
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 = ""
- WebURLRequest request;
- request.initialize();
- request.setURL(url);
+ // 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));
- 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);
+ // Test that non-whitelisted headers aren't returned.
+ EXPECT_FALSE(CheckAccessControlHeaders("non-whitelisted", false));
- 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);
+ // Test that Set-Cookie headers aren't returned.
+ EXPECT_FALSE(CheckAccessControlHeaders("Set-Cookie", false));
+ EXPECT_FALSE(CheckAccessControlHeaders("Set-Cookie2", false));
- 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());
+ // Test that exposed headers that aren't whitelisted are returned.
+ EXPECT_TRUE(CheckAccessControlHeaders("non-whitelisted", true));
- EXPECT_TRUE(m_actualResponse.httpHeaderField("Set-Cookie").isEmpty());
- EXPECT_TRUE(m_actualResponse.httpHeaderField("Set-Cookie2").isEmpty());
+ // Test that Set-Cookie headers aren't returned, even if exposed.
+ EXPECT_FALSE(CheckAccessControlHeaders("Set-Cookie", true));
}
}