Upstream version 11.40.277.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / core / fetch / CrossOriginAccessControl.cpp
index feb858e..4ebc1ab 100644 (file)
 #include "config.h"
 #include "core/fetch/CrossOriginAccessControl.h"
 
+#include "core/fetch/Resource.h"
+#include "core/fetch/ResourceLoaderOptions.h"
 #include "platform/network/HTTPParsers.h"
 #include "platform/network/ResourceRequest.h"
 #include "platform/network/ResourceResponse.h"
-#include "weborigin/SecurityOrigin.h"
+#include "platform/weborigin/SchemeRegistry.h"
+#include "platform/weborigin/SecurityOrigin.h"
 #include "wtf/Threading.h"
 #include "wtf/text/AtomicString.h"
 #include "wtf/text/StringBuilder.h"
 
-namespace WebCore {
-
-bool isOnAccessControlSimpleRequestMethodWhitelist(const String& method)
-{
-    return method == "GET" || method == "HEAD" || method == "POST";
-}
-
-bool isOnAccessControlSimpleRequestHeaderWhitelist(const String& name, const String& value)
-{
-    if (equalIgnoringCase(name, "accept")
-        || equalIgnoringCase(name, "accept-language")
-        || equalIgnoringCase(name, "content-language")
-        || equalIgnoringCase(name, "origin")
-        || equalIgnoringCase(name, "referer"))
-        return true;
-
-    // Preflight is required for MIME types that can not be sent via form submission.
-    if (equalIgnoringCase(name, "content-type")) {
-        String mimeType = extractMIMETypeFromMediaType(value);
-        return equalIgnoringCase(mimeType, "application/x-www-form-urlencoded")
-            || equalIgnoringCase(mimeType, "multipart/form-data")
-            || equalIgnoringCase(mimeType, "text/plain");
-    }
-
-    return false;
-}
-
-bool isSimpleCrossOriginAccessRequest(const String& method, const HTTPHeaderMap& headerMap)
-{
-    if (!isOnAccessControlSimpleRequestMethodWhitelist(method))
-        return false;
-
-    HTTPHeaderMap::const_iterator end = headerMap.end();
-    for (HTTPHeaderMap::const_iterator it = headerMap.begin(); it != end; ++it) {
-        if (!isOnAccessControlSimpleRequestHeaderWhitelist(it->key, it->value))
-            return false;
-    }
-
-    return true;
-}
+namespace blink {
 
 static PassOwnPtr<HTTPHeaderSet> createAllowedCrossOriginResponseHeadersSet()
 {
@@ -100,10 +64,11 @@ bool isOnAccessControlResponseHeaderWhitelist(const String& name)
 void updateRequestForAccessControl(ResourceRequest& request, SecurityOrigin* securityOrigin, StoredCredentials allowCredentials)
 {
     request.removeCredentials();
-    request.setAllowCookies(allowCredentials == AllowStoredCredentials);
+    request.setAllowStoredCredentials(allowCredentials == AllowStoredCredentials);
+    request.setFetchCredentialsMode(allowCredentials == AllowStoredCredentials ? WebURLRequest::FetchCredentialsModeInclude : WebURLRequest::FetchCredentialsModeOmit);
 
     if (securityOrigin)
-        request.setHTTPOrigin(securityOrigin->toString());
+        request.setHTTPOrigin(securityOrigin->toAtomicString());
 }
 
 ResourceRequest createAccessControlPreflightRequest(const ResourceRequest& request, SecurityOrigin* securityOrigin)
@@ -113,6 +78,7 @@ ResourceRequest createAccessControlPreflightRequest(const ResourceRequest& reque
     preflightRequest.setHTTPMethod("OPTIONS");
     preflightRequest.setHTTPHeaderField("Access-Control-Request-Method", request.httpMethod());
     preflightRequest.setPriority(request.priority());
+    preflightRequest.setRequestContext(request.requestContext());
 
     const HTTPHeaderMap& requestHeaderFields = request.httpHeaderFields();
 
@@ -124,46 +90,69 @@ ResourceRequest createAccessControlPreflightRequest(const ResourceRequest& reque
 
         HTTPHeaderMap::const_iterator end = requestHeaderFields.end();
         for (; it != end; ++it) {
-            headerBuffer.append(',');
-            headerBuffer.append(' ');
+            headerBuffer.appendLiteral(", ");
             headerBuffer.append(it->key);
         }
 
-        preflightRequest.setHTTPHeaderField("Access-Control-Request-Headers", headerBuffer.toString().lower());
+        preflightRequest.setHTTPHeaderField("Access-Control-Request-Headers", AtomicString(headerBuffer.toString().lower()));
     }
 
     return preflightRequest;
 }
 
+static bool isOriginSeparator(UChar ch)
+{
+    return isASCIISpace(ch) || ch == ',';
+}
+
+static bool isInterestingStatusCode(int statusCode)
+{
+    // Predicate that gates what status codes should be included in
+    // console error messages for responses containing no access
+    // control headers.
+    return statusCode >= 400;
+}
+
 bool passesAccessControlCheck(const ResourceResponse& response, StoredCredentials includeCredentials, SecurityOrigin* securityOrigin, String& errorDescription)
 {
     AtomicallyInitializedStatic(AtomicString&, accessControlAllowOrigin = *new AtomicString("access-control-allow-origin", AtomicString::ConstructFromLiteral));
     AtomicallyInitializedStatic(AtomicString&, accessControlAllowCredentials = *new AtomicString("access-control-allow-credentials", AtomicString::ConstructFromLiteral));
 
-    // A wildcard Access-Control-Allow-Origin can not be used if credentials are to be sent,
-    // even with Access-Control-Allow-Credentials set to true.
-    const String& accessControlOriginString = response.httpHeaderField(accessControlAllowOrigin);
-    if (accessControlOriginString == "*" && includeCredentials == DoNotAllowStoredCredentials)
-        return true;
-
-    // FIXME: Access-Control-Allow-Origin can contain a list of origins.
-    if (accessControlOriginString != securityOrigin->toString()) {
-        if (accessControlOriginString == "*") {
-            errorDescription = "Wildcards cannot be used in the 'Access-Control-Allow-Origin' header when the credentials flag is true. Origin '" + securityOrigin->toString() + "' is therefore not allowed access.";
-        } else if (accessControlOriginString.isEmpty()) {
+    if (!response.httpStatusCode()) {
+        errorDescription = "Received an invalid response. Origin '" + securityOrigin->toString() + "' is therefore not allowed access.";
+        return false;
+    }
+
+    const AtomicString& accessControlOriginString = response.httpHeaderField(accessControlAllowOrigin);
+    if (accessControlOriginString == starAtom) {
+        // A wildcard Access-Control-Allow-Origin can not be used if credentials are to be sent,
+        // even with Access-Control-Allow-Credentials set to true.
+        if (includeCredentials == DoNotAllowStoredCredentials)
+            return true;
+        if (response.isHTTP()) {
+            errorDescription = "A wildcard '*' cannot be used in the 'Access-Control-Allow-Origin' header when the credentials flag is true. Origin '" + securityOrigin->toString() + "' is therefore not allowed access.";
+            return false;
+        }
+    } else if (accessControlOriginString != securityOrigin->toAtomicString()) {
+        if (accessControlOriginString.isNull()) {
             errorDescription = "No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin '" + securityOrigin->toString() + "' is therefore not allowed access.";
+
+            if (isInterestingStatusCode(response.httpStatusCode()))
+                errorDescription.append(" The response had HTTP status code " + String::number(response.httpStatusCode()) + ".");
+        } else if (accessControlOriginString.string().find(isOriginSeparator, 0) != kNotFound) {
+            errorDescription = "The 'Access-Control-Allow-Origin' header contains multiple values '" + accessControlOriginString + "', but only one is allowed. Origin '" + securityOrigin->toString() + "' is therefore not allowed access.";
         } else {
             KURL headerOrigin(KURL(), accessControlOriginString);
             if (!headerOrigin.isValid())
                 errorDescription = "The 'Access-Control-Allow-Origin' header contains the invalid value '" + accessControlOriginString + "'. Origin '" + securityOrigin->toString() + "' is therefore not allowed access.";
             else
-                errorDescription = "The 'Access-Control-Allow-Origin' whitelists only '" + accessControlOriginString + "'. Origin '" + securityOrigin->toString() + "' is not in the list, and is therefore not allowed access.";
+                errorDescription = "The 'Access-Control-Allow-Origin' header has a value '" + accessControlOriginString + "' that is not equal to the supplied origin. Origin '" + securityOrigin->toString() + "' is therefore not allowed access.";
         }
         return false;
     }
 
     if (includeCredentials == AllowStoredCredentials) {
-        const String& accessControlCredentialsString = response.httpHeaderField(accessControlAllowCredentials);
+        const AtomicString& accessControlCredentialsString = response.httpHeaderField(accessControlAllowCredentials);
         if (accessControlCredentialsString != "true") {
             errorDescription = "Credentials flag is 'true', but the 'Access-Control-Allow-Credentials' header is '" + accessControlCredentialsString + "'. It must be 'true' to allow credentials.";
             return false;
@@ -194,4 +183,67 @@ void parseAccessControlExposeHeadersAllowList(const String& headerValue, HTTPHea
     }
 }
 
-} // namespace WebCore
+bool CrossOriginAccessControl::isLegalRedirectLocation(const KURL& requestURL, String& errorDescription)
+{
+    // CORS restrictions imposed on Location: URL -- http://www.w3.org/TR/cors/#redirect-steps (steps 2 + 3.)
+    if (!SchemeRegistry::shouldTreatURLSchemeAsCORSEnabled(requestURL.protocol())) {
+        errorDescription = "The request was redirected to a URL ('" + requestURL.string() + "') which has a disallowed scheme for cross-origin requests.";
+        return false;
+    }
+
+    if (!(requestURL.user().isEmpty() && requestURL.pass().isEmpty())) {
+        errorDescription = "The request was redirected to a URL ('" + requestURL.string() + "') containing userinfo, which is disallowed for cross-origin requests.";
+        return false;
+    }
+
+    return true;
+}
+
+bool CrossOriginAccessControl::handleRedirect(Resource* resource, SecurityOrigin* securityOrigin, ResourceRequest& request, const ResourceResponse& redirectResponse, ResourceLoaderOptions& options, String& errorMessage)
+{
+    // http://www.w3.org/TR/cors/#redirect-steps terminology:
+    const KURL& originalURL = redirectResponse.url();
+    const KURL& requestURL = request.url();
+
+    bool redirectCrossOrigin = !securityOrigin->canRequest(requestURL);
+
+    // Same-origin request URLs that redirect are allowed without checking access.
+    if (!securityOrigin->canRequest(originalURL)) {
+        // Follow http://www.w3.org/TR/cors/#redirect-steps
+        String errorDescription;
+
+        // Steps 3 & 4 - check if scheme and other URL restrictions hold.
+        bool allowRedirect = isLegalRedirectLocation(requestURL, errorDescription);
+        if (allowRedirect) {
+            // Step 5: perform resource sharing access check.
+            StoredCredentials withCredentials = resource->lastResourceRequest().allowStoredCredentials() ? AllowStoredCredentials : DoNotAllowStoredCredentials;
+            allowRedirect = passesAccessControlCheck(redirectResponse, withCredentials, securityOrigin, errorDescription);
+            if (allowRedirect) {
+                RefPtr<SecurityOrigin> originalOrigin = SecurityOrigin::create(originalURL);
+                // Step 6: if the request URL origin is not same origin as the original URL's,
+                // set the source origin to a globally unique identifier.
+                if (!originalOrigin->canRequest(requestURL)) {
+                    options.securityOrigin = SecurityOrigin::createUnique();
+                    securityOrigin = options.securityOrigin.get();
+                }
+            }
+        }
+        if (!allowRedirect) {
+            const String& originalOrigin = SecurityOrigin::create(originalURL)->toString();
+            errorMessage = "Redirect at origin '" + originalOrigin + "' has been blocked from loading by Cross-Origin Resource Sharing policy: " + errorDescription;
+            return false;
+        }
+    }
+    if (redirectCrossOrigin) {
+        // If now to a different origin, update/set Origin:.
+        request.clearHTTPOrigin();
+        request.setHTTPOrigin(securityOrigin->toAtomicString());
+        // If the user didn't request credentials in the first place, update our
+        // state so we neither request them nor expect they must be allowed.
+        if (options.credentialsRequested == ClientDidNotRequestCredentials)
+            options.allowCredentials = DoNotAllowStoredCredentials;
+    }
+    return true;
+}
+
+} // namespace blink