Diff
Modified: trunk/LayoutTests/ChangeLog (112216 => 112217)
--- trunk/LayoutTests/ChangeLog 2012-03-27 07:35:21 UTC (rev 112216)
+++ trunk/LayoutTests/ChangeLog 2012-03-27 07:38:47 UTC (rev 112217)
@@ -1,3 +1,21 @@
+2012-03-27 Bill Budge <[email protected]>
+
+ cross-origin XMLHttpRequest doesn't work with redirect
+ https://bugs.webkit.org/show_bug.cgi?id=57600
+
+ Reviewed by Adam Barth.
+
+ 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.
+
2012-03-27 Philippe Normand <[email protected]>
Unreviewed, GTK gardening after r112112.
Deleted: trunk/LayoutTests/http/tests/security/resources/cors-redirect.php (112216 => 112217)
--- trunk/LayoutTests/http/tests/security/resources/cors-redirect.php 2012-03-27 07:35:21 UTC (rev 112216)
+++ trunk/LayoutTests/http/tests/security/resources/cors-redirect.php 2012-03-27 07:38:47 UTC (rev 112217)
@@ -1,4 +0,0 @@
-<?php
-header("Access-Control-Allow-Origin: http://127.0.0.1:8000");
-header("Location: http://localhost:8000/security/resources/empty.html");
-?>
Deleted: trunk/LayoutTests/http/tests/security/xhr-cors-redirect.html (112216 => 112217)
--- trunk/LayoutTests/http/tests/security/xhr-cors-redirect.html 2012-03-27 07:35:21 UTC (rev 112216)
+++ trunk/LayoutTests/http/tests/security/xhr-cors-redirect.html 2012-03-27 07:38:47 UTC (rev 112217)
@@ -1,59 +0,0 @@
-<html>
-<head>
-<script>
-if (window.layoutTestController) {
- layoutTestController.dumpAsText();
- layoutTestController.waitUntilDone();
-}
-
-function log(msg) {
- document.getElementById("log").innerHTML += msg + "<br>";
-}
-
-function test() {
- var xhr = new XMLHttpRequest();
- var url = ""
- xhr.open("GET", url, false);
- try {
- xhr.send(null);
-
- if (xhr.status == 200)
- log("PASS: sync XHR successful");
- else
- log("FAIL: sync XHR failed");
- } catch (e) {
- log("FAIL: sync XHR failed");
- }
-
- xhr = new XMLHttpRequest();
- xhr.open("GET", url, true);
- xhr._onreadystatechanged_ = function(e) {
- if (xhr.readystate == 4) {
- if (xhr.status == 200)
- log("PASS: async XHR successful");
- else
- log("FAIL: async XHR failed");
- if (window.layoutTestController)
- layoutTestController.notifyDone();
- }
- };
- xhr._onerror_ = function() {
- log("FAIL: async XHR failed");
- if (window.layoutTestController)
- layoutTestController.notifyDone();
- };
- xhr.send(null);
-}
-</script>
-</head>
-<body _onload_="test()">
-<p>
-Test that a cross-origin XHR involving redirects works correctly. If this test
-passes, you will see a series of PASS messages.
-</p>
-<p>
-Currently not working, see http://webkit.org/b/57600
-</p>
-<div id="log"></div>
-</body>
-</html>
Added: trunk/LayoutTests/http/tests/xmlhttprequest/access-control-and-redirects-async-expected.txt (0 => 112217)
--- trunk/LayoutTests/http/tests/xmlhttprequest/access-control-and-redirects-async-expected.txt (rev 0)
+++ trunk/LayoutTests/http/tests/xmlhttprequest/access-control-and-redirects-async-expected.txt 2012-03-27 07:38:47 UTC (rev 112217)
@@ -0,0 +1,30 @@
+Tests that asynchronous XMLHttpRequests handle redirects according to the CORS standard.
+
+Testing resources/redirect-cors.php?url=""
+Expecting success: false
+PASS: 0
+Testing resources/redirect-cors.php?url="" access-control-allow-origin=http://localhost:8000& access-control-allow-credentials=true
+Expecting success: false
+PASS: 0
+Testing resources/redirect-cors.php?url="" access-control-allow-origin=http://localhost:8000& access-control-allow-credentials=true
+Expecting success: false
+PASS: 0
+Testing http://127.0.0.1:8000/xmlhttprequest/resources/redirect-cors.php?url=""
+Expecting success: false
+PASS: 0
+Testing http://127.0.0.1:8000/xmlhttprequest/resources/redirect-cors.php?url="" access-control-allow-origin=http://localhost:8000
+Expecting success: true
+FAIL: 0
+Testing http://127.0.0.1:8000/xmlhttprequest/resources/redirect-cors.php?url="" access-control-allow-origin=http://localhost:8000
+Expecting success: false
+PASS: 0
+Testing http://127.0.0.1:8000/xmlhttprequest/resources/redirect-cors.php?url="" access-control-allow-origin=http://localhost:8000
+Expecting success: false
+PASS: 0
+Testing http://127.0.0.1:8000/xmlhttprequest/resources/redirect-cors.php?redirect-preflight=true& url="" access-control-allow-origin=*
+Expecting success: false
+PASS: 0
+Testing http://127.0.0.1:8000/xmlhttprequest/resources/redirect-cors.php?redirect-preflight=false& url="" access-control-allow-origin=*& access-control-allow-headers=x-webkit
+Expecting success: false
+PASS: 0
+
Added: trunk/LayoutTests/http/tests/xmlhttprequest/access-control-and-redirects-async.html (0 => 112217)
--- trunk/LayoutTests/http/tests/xmlhttprequest/access-control-and-redirects-async.html (rev 0)
+++ trunk/LayoutTests/http/tests/xmlhttprequest/access-control-and-redirects-async.html 2012-03-27 07:38:47 UTC (rev 112217)
@@ -0,0 +1,109 @@
+<p>Tests that asynchronous XMLHttpRequests handle redirects according to the CORS standard.</p>
+
+<pre id="console"></pre>
+<script>
+if (window.layoutTestController) {
+ layoutTestController.dumpAsText();
+ layoutTestController.waitUntilDone();
+}
+
+function log(message)
+{
+ document.getElementById('console').appendChild(document.createTextNode(message + '\n'));
+}
+
+function runTestAsync(url, forcePreflight, expectSuccess) {
+ log("Testing " + url);
+ log("Expecting success: " + expectSuccess);
+
+ xhr = new XMLHttpRequest();
+ xhr.open("GET", url, true);
+ if (forcePreflight)
+ xhr.setRequestHeader("x-webkit", "foo");
+
+ xhr._onload_ = function() {
+ log((expectSuccess ? "PASS" : "FAIL") + ": " + xhr.responseText);
+ nextTest();
+ }
+ xhr._onerror_ = function() {
+ log((expectSuccess ? "FAIL" : "PASS") + ": " + xhr.status);
+ nextTest();
+ }
+ xhr.send(null);
+}
+
+var simple = false;
+var preflight = true;
+var succeeds = true;
+var fails = false;
+
+var tests = [
+// 1) Test simple same origin requests that receive cross origin redirects.
+
+// Request receives a cross-origin redirect response without CORS headers. The redirect response fails the access check.
+["resources/redirect-cors.php?url=""
+ simple, fails],
+
+// Request receives a cross-origin redirect response with CORS headers. The redirect response passes the access check,
+// but the resource response fails its access check because the security origin is a globally unique identifier after
+// the redirect and the same origin XHR has 'allowCredentials' true.
+["resources/redirect-cors.php?url=""
+ access-control-allow-origin=http://localhost:8000&\
+ access-control-allow-credentials=true",
+ simple, fails],
+
+// Same as above, but to a less permissive resource that only allows the requesting origin.
+["resources/redirect-cors.php?url=""
+ access-control-allow-origin=http://localhost:8000&\
+ access-control-allow-credentials=true",
+ simple, fails],
+
+// 2) Test simple cross origin requests that receive redirects.
+
+// Receives a redirect response without CORS headers. The redirect response fails the access check.
+["http://127.0.0.1:8000/xmlhttprequest/resources/redirect-cors.php?url=""
+ simple, fails],
+
+// Receives a redirect response with CORS headers. The redirect response passes the access check and the resource response
+// passes the access check.
+["http://127.0.0.1:8000/xmlhttprequest/resources/redirect-cors.php?url=""
+ access-control-allow-origin=http://localhost:8000",
+ simple, succeeds],
+
+// Receives a redirect response with a URL containing the userinfo production.
+["http://127.0.0.1:8000/xmlhttprequest/resources/redirect-cors.php?url=""
+ access-control-allow-origin=http://localhost:8000",
+ simple, fails],
+
+// Receives a redirect response with a URL with an unsupported scheme.
+["http://127.0.0.1:8000/xmlhttprequest/resources/redirect-cors.php?url=""
+ access-control-allow-origin=http://localhost:8000",
+ simple, fails],
+
+// 3) Test preflighted cross origin requests that receive redirects.
+
+// Receives a redirect response to the preflight request and fails.
+["http://127.0.0.1:8000/xmlhttprequest/resources/redirect-cors.php?redirect-preflight=true&\
+ url=""
+ access-control-allow-origin=*",
+ preflight, fails],
+
+// Successful preflight and receives a redirect response to the actual request and fails.
+["http://127.0.0.1:8000/xmlhttprequest/resources/redirect-cors.php?redirect-preflight=false&\
+ url=""
+ access-control-allow-origin=*&\
+ access-control-allow-headers=x-webkit",
+ preflight, fails],
+]
+
+var currentTest = 0;
+
+function nextTest() {
+ if (currentTest < tests.length)
+ runTestAsync.apply(null, tests[currentTest++]);
+ else if (window.layoutTestController)
+ layoutTestController.notifyDone();
+}
+
+nextTest();
+</script>
Added: trunk/LayoutTests/http/tests/xmlhttprequest/resources/redirect-cors.php (0 => 112217)
--- trunk/LayoutTests/http/tests/xmlhttprequest/resources/redirect-cors.php (rev 0)
+++ trunk/LayoutTests/http/tests/xmlhttprequest/resources/redirect-cors.php 2012-03-27 07:38:47 UTC (rev 112217)
@@ -0,0 +1,28 @@
+<?php
+ $url = ""
+ $redirect_preflight = $_GET['redirect-preflight'];
+ $access_control_allow_origin = $_GET['access-control-allow-origin'];
+ $access_control_allow_credentials = $_GET['access-control-allow-credentials'];
+ $access_control_allow_headers = $_GET['access-control-allow-headers'];
+
+ if ($_SERVER['REQUEST_METHOD'] == "OPTIONS") {
+ if ($redirect_preflight == "true") {
+ header("HTTP/1.1 302");
+ header("Location: $url");
+ }
+ else {
+ header("HTTP/1.1 200");
+ }
+ header("Access-Control-Allow-Methods: GET");
+ header("Access-Control-Max-Age: 1");
+ } else if ($_SERVER['REQUEST_METHOD'] == "GET") {
+ header("HTTP/1.1 302");
+ header("Location: $url");
+ }
+ if (!is_null($access_control_allow_origin))
+ header("Access-Control-Allow-Origin: $access_control_allow_origin");
+ if (!is_null($access_control_allow_credentials))
+ header("Access-Control-Allow-Credentials: $access_control_allow_credentials");
+ if (!is_null($access_control_allow_headers))
+ header("Access-Control-Allow-Headers: $access_control_allow_headers");
+?>
Modified: trunk/Source/WebCore/ChangeLog (112216 => 112217)
--- trunk/Source/WebCore/ChangeLog 2012-03-27 07:35:21 UTC (rev 112216)
+++ trunk/Source/WebCore/ChangeLog 2012-03-27 07:38:47 UTC (rev 112217)
@@ -1,3 +1,24 @@
+2012-03-27 Bill Budge <[email protected]>
+
+ cross-origin XMLHttpRequest doesn't work with redirect
+ https://bugs.webkit.org/show_bug.cgi?id=57600
+
+ Reviewed by Adam Barth.
+
+ 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:
+
2012-03-27 Adam Barth <[email protected]>
ImageLoader::m_firedLoadEvent is a confusing name
Modified: trunk/Source/WebCore/loader/DocumentThreadableLoader.cpp (112216 => 112217)
--- trunk/Source/WebCore/loader/DocumentThreadableLoader.cpp 2012-03-27 07:35:21 UTC (rev 112216)
+++ trunk/Source/WebCore/loader/DocumentThreadableLoader.cpp 2012-03-27 07:38:47 UTC (rev 112217)
@@ -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);
}
Modified: trunk/Source/WebCore/loader/DocumentThreadableLoader.h (112216 => 112217)
--- trunk/Source/WebCore/loader/DocumentThreadableLoader.h 2012-03-27 07:35:21 UTC (rev 112216)
+++ trunk/Source/WebCore/loader/DocumentThreadableLoader.h 2012-03-27 07:38:47 UTC (rev 112217)
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009 Google Inc. All rights reserved.
+ * Copyright (C) 2009, 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
@@ -89,6 +89,7 @@
void didReceiveResponse(unsigned long identifier, const ResourceResponse&);
void didFinishLoading(unsigned long identifier, double finishTime);
void didFail(const ResourceError&);
+ void makeRequest(const ResourceRequest&);
void makeSimpleCrossOriginAccessRequest(const ResourceRequest& request);
void makeCrossOriginAccessRequestWithPreflight(const ResourceRequest& request);
void preflightSuccess();
@@ -104,6 +105,7 @@
Document* m_document;
ThreadableLoaderOptions m_options;
bool m_sameOriginRequest;
+ bool m_simpleRequest;
bool m_async;
OwnPtr<ResourceRequest> m_actualRequest; // non-null during Access Control preflight checks
Modified: trunk/Source/WebCore/platform/network/ResourceRequestBase.cpp (112216 => 112217)
--- trunk/Source/WebCore/platform/network/ResourceRequestBase.cpp 2012-03-27 07:35:21 UTC (rev 112216)
+++ trunk/Source/WebCore/platform/network/ResourceRequestBase.cpp 2012-03-27 07:38:47 UTC (rev 112217)
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2003, 2006 Apple Computer, Inc. All rights reserved.
- * Copyright (C) 2009 Google Inc. All rights reserved.
+ * Copyright (C) 2009, 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
@@ -251,6 +251,16 @@
m_platformRequestUpdated = false;
}
+void ResourceRequestBase::clearHTTPContentType()
+{
+ updateResourceRequest();
+
+ m_httpHeaderFields.remove("Content-Type");
+
+ if (url().protocolIsInHTTPFamily())
+ m_platformRequestUpdated = false;
+}
+
void ResourceRequestBase::clearHTTPReferrer()
{
updateResourceRequest();
@@ -271,6 +281,26 @@
m_platformRequestUpdated = false;
}
+void ResourceRequestBase::clearHTTPUserAgent()
+{
+ updateResourceRequest();
+
+ m_httpHeaderFields.remove("User-Agent");
+
+ if (url().protocolIsInHTTPFamily())
+ m_platformRequestUpdated = false;
+}
+
+void ResourceRequestBase::clearHTTPAccept()
+{
+ updateResourceRequest();
+
+ m_httpHeaderFields.remove("Accept");
+
+ if (url().protocolIsInHTTPFamily())
+ m_platformRequestUpdated = false;
+}
+
void ResourceRequestBase::setResponseContentDispositionEncodingFallbackArray(const String& encoding1, const String& encoding2, const String& encoding3)
{
updateResourceRequest();
Modified: trunk/Source/WebCore/platform/network/ResourceRequestBase.h (112216 => 112217)
--- trunk/Source/WebCore/platform/network/ResourceRequestBase.h 2012-03-27 07:35:21 UTC (rev 112216)
+++ trunk/Source/WebCore/platform/network/ResourceRequestBase.h 2012-03-27 07:38:47 UTC (rev 112217)
@@ -1,7 +1,7 @@
/*
* Copyright (C) 2003, 2006 Apple Computer, Inc. All rights reserved.
* Copyright (C) 2006 Samuel Weinig <[email protected]>
- * Copyright (C) 2009 Google Inc. All rights reserved.
+ * Copyright (C) 2009, 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
@@ -88,7 +88,8 @@
String httpContentType() const { return httpHeaderField("Content-Type"); }
void setHTTPContentType(const String& httpContentType) { setHTTPHeaderField("Content-Type", httpContentType); }
-
+ void clearHTTPContentType();
+
String httpReferrer() const { return httpHeaderField("Referer"); }
void setHTTPReferrer(const String& httpReferrer) { setHTTPHeaderField("Referer", httpReferrer); }
void clearHTTPReferrer();
@@ -99,9 +100,11 @@
String httpUserAgent() const { return httpHeaderField("User-Agent"); }
void setHTTPUserAgent(const String& httpUserAgent) { setHTTPHeaderField("User-Agent", httpUserAgent); }
+ void clearHTTPUserAgent();
String httpAccept() const { return httpHeaderField("Accept"); }
void setHTTPAccept(const String& httpAccept) { setHTTPHeaderField("Accept", httpAccept); }
+ void clearHTTPAccept();
void setResponseContentDispositionEncodingFallbackArray(const String& encoding1, const String& encoding2 = String(), const String& encoding3 = String());