cross-origin XMLHttpRequest doesn't work with redirect
https://bugs.webkit.org/show_bug.cgi?id=57600
Reviewed by Adam Barth.
Source/WebCore:
Changes DocumentThreadableLoader to follow the CORS redirect steps when
asynchronously loading a cross origin request with access control. Synchronous
loads should not be affected. Also adds methods to ResourceRequestBase to
clear special request headers that aren't allowed when using access control.
Follows the CORS spec as described in the Latest Editor Draft at:
http://www.w3.org/TR/cors/
Test: http/tests/xmlhttprequest/access-control-and-redirects-async.html
* loader/DocumentThreadableLoader.cpp:
* loader/DocumentThreadableLoader.h:
* platform/network/ResourceRequestBase.cpp:
* platform/network/ResourceRequestBase.h:
LayoutTests:
Adds tests to verify that an asynchronous XHR load that receives a redirect
response follows the CORS redirect steps.
Follows the CORS spec as described in the Latest Editor Draft at:
http://www.w3.org/TR/cors/
* http/tests/security/resources/cors-redirect.php: Removed.
* http/tests/security/xhr-cors-redirect.html: Removed.
* http/tests/xmlhttprequest/access-control-and-redirects-async-expected.txt: Added.
* http/tests/xmlhttprequest/access-control-and-redirects-async.html: Added.
* http/tests/xmlhttprequest/resources/redirect-cors.php: Added.
git-svn-id: http://svn.webkit.org/repository/webkit/trunk@112217 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Source/WebCore/loader/DocumentThreadableLoader.cpp b/Source/WebCore/loader/DocumentThreadableLoader.cpp
index c357e80..a10fb70 100644
--- a/Source/WebCore/loader/DocumentThreadableLoader.cpp
+++ b/Source/WebCore/loader/DocumentThreadableLoader.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2011 Google Inc. All rights reserved.
+ * Copyright (C) 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
@@ -74,6 +74,7 @@
, m_document(document)
, m_options(options)
, m_sameOriginRequest(securityOrigin()->canRequest(request.url()))
+ , m_simpleRequest(true)
, m_async(blockingBehavior == LoadAsynchronously)
#if ENABLE(INSPECTOR)
, m_preflightRequestIdentifier(0)
@@ -84,6 +85,11 @@
// Setting an outgoing referer is only supported in the async code path.
ASSERT(m_async || request.httpReferrer().isEmpty());
+ makeRequest(request);
+}
+
+void DocumentThreadableLoader::makeRequest(const ResourceRequest& request)
+{
if (m_sameOriginRequest || m_options.crossOriginRequestPolicy == AllowCrossOriginRequests) {
loadRequest(request, DoSecurityCheck);
return;
@@ -93,7 +99,7 @@
m_client->didFail(ResourceError(errorDomainWebKitInternal, 0, request.url().string(), "Cross origin requests are not supported."));
return;
}
-
+
ASSERT(m_options.crossOriginRequestPolicy == UseAccessControl);
OwnPtr<ResourceRequest> crossOriginRequest = adoptPtr(new ResourceRequest(request));
@@ -102,6 +108,7 @@
if ((m_options.preflightPolicy == ConsiderPreflight && isSimpleCrossOriginAccessRequest(crossOriginRequest->httpMethod(), crossOriginRequest->httpHeaderFields())) || m_options.preflightPolicy == PreventPreflight)
makeSimpleCrossOriginAccessRequest(*crossOriginRequest);
else {
+ m_simpleRequest = false;
m_actualRequest = crossOriginRequest.release();
if (CrossOriginPreflightResultCache::shared().canSkipPreflight(securityOrigin()->toString(), m_actualRequest->url(), m_options.allowCredentials, m_actualRequest->httpMethod(), m_actualRequest->httpHeaderFields()))
@@ -167,13 +174,48 @@
ASSERT(m_client);
ASSERT_UNUSED(resource, resource == m_resource);
- if (!isAllowedRedirect(request.url())) {
- RefPtr<DocumentThreadableLoader> protect(this);
+ RefPtr<DocumentThreadableLoader> protect(this);
+ bool allowRedirect = false;
+ if (m_options.crossOriginRequestPolicy == UseAccessControl) {
+ // When using access control, only simple cross origin requests are allowed to redirect. The new request URL must have a supported
+ // scheme and not contain the userinfo production. In addition, the redirect response must pass the access control check.
+ if (m_simpleRequest) {
+ String accessControlErrorDescription;
+ allowRedirect = SchemeRegistry::shouldTreatURLSchemeAsCORSEnabled(request.url().protocol())
+ && request.url().user().isEmpty()
+ && request.url().pass().isEmpty()
+ && passesAccessControlCheck(redirectResponse, m_options.allowCredentials, securityOrigin(), accessControlErrorDescription);
+ }
+ } else
+ allowRedirect = isAllowedRedirect(request.url());
+
+ if (allowRedirect) {
+ if (m_options.crossOriginRequestPolicy == UseAccessControl) {
+ if (m_resource)
+ clearResource();
+
+ RefPtr<SecurityOrigin> originalOrigin = SecurityOrigin::createFromString(redirectResponse.url());
+ RefPtr<SecurityOrigin> requestOrigin = SecurityOrigin::createFromString(request.url());
+ // If the request URL origin is not same origin with the original URL origin, set source origin to a globally unique identifier.
+ if (!originalOrigin->isSameSchemeHostPort(requestOrigin.get()))
+ m_options.securityOrigin = SecurityOrigin::createUnique();
+ m_sameOriginRequest = securityOrigin()->canRequest(request.url());
+
+ // Remove any headers that may have been added by the network layer that cause access control to fail.
+ request.clearHTTPContentType();
+ request.clearHTTPReferrer();
+ request.clearHTTPOrigin();
+ request.clearHTTPUserAgent();
+ request.clearHTTPAccept();
+ makeRequest(request);
+ } else {
+ // If not using access control, allow clients to audit the redirect.
+ if (m_client->isDocumentThreadableLoaderClient())
+ static_cast<DocumentThreadableLoaderClient*>(m_client)->willSendRequest(request, redirectResponse);
+ }
+ } else {
m_client->didFailRedirectCheck();
request = ResourceRequest();
- } else {
- if (m_client->isDocumentThreadableLoaderClient())
- static_cast<DocumentThreadableLoaderClient*>(m_client)->willSendRequest(request, redirectResponse);
}
}
@@ -377,9 +419,6 @@
if (m_options.crossOriginRequestPolicy == AllowCrossOriginRequests)
return true;
- // FIXME: We need to implement access control for each redirect. This will require some refactoring though, because the code
- // that processes redirects doesn't know about access control and expects a synchronous answer from its client about whether
- // a redirect should proceed.
return m_sameOriginRequest && securityOrigin()->canRequest(url);
}