#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;
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; }
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.
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*/);
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)
{
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)
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;
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());
WebView* m_webView;
WebURLLoader* m_expectedLoader;
+ WebURLResponse m_actualResponse;
WebURLResponse m_expectedResponse;
WebURLRequest m_expectedNewRequest;
WebURLResponse m_expectedRedirectResponse;
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());
+}
+
}