2 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 #include "core/fetch/CrossOriginAccessControl.h"
30 #include "platform/network/HTTPParsers.h"
31 #include "platform/network/ResourceResponse.h"
32 #include "platform/weborigin/SecurityOrigin.h"
33 #include "wtf/Threading.h"
34 #include "wtf/text/AtomicString.h"
35 #include "wtf/text/StringBuilder.h"
39 bool isOnAccessControlSimpleRequestMethodWhitelist(const String& method)
41 return method == "GET" || method == "HEAD" || method == "POST";
44 bool isOnAccessControlSimpleRequestHeaderWhitelist(const AtomicString& name, const AtomicString& value)
46 if (equalIgnoringCase(name, "accept")
47 || equalIgnoringCase(name, "accept-language")
48 || equalIgnoringCase(name, "content-language")
49 || equalIgnoringCase(name, "origin")
50 || equalIgnoringCase(name, "referer"))
53 // Preflight is required for MIME types that can not be sent via form submission.
54 if (equalIgnoringCase(name, "content-type")) {
55 AtomicString mimeType = extractMIMETypeFromMediaType(value);
56 return equalIgnoringCase(mimeType, "application/x-www-form-urlencoded")
57 || equalIgnoringCase(mimeType, "multipart/form-data")
58 || equalIgnoringCase(mimeType, "text/plain");
64 bool isSimpleCrossOriginAccessRequest(const String& method, const HTTPHeaderMap& headerMap)
66 if (!isOnAccessControlSimpleRequestMethodWhitelist(method))
69 HTTPHeaderMap::const_iterator end = headerMap.end();
70 for (HTTPHeaderMap::const_iterator it = headerMap.begin(); it != end; ++it) {
71 if (!isOnAccessControlSimpleRequestHeaderWhitelist(it->key, it->value))
78 static PassOwnPtr<HTTPHeaderSet> createAllowedCrossOriginResponseHeadersSet()
80 OwnPtr<HTTPHeaderSet> headerSet = adoptPtr(new HashSet<String, CaseFoldingHash>);
82 headerSet->add("cache-control");
83 headerSet->add("content-language");
84 headerSet->add("content-type");
85 headerSet->add("expires");
86 headerSet->add("last-modified");
87 headerSet->add("pragma");
89 return headerSet.release();
92 bool isOnAccessControlResponseHeaderWhitelist(const String& name)
94 AtomicallyInitializedStatic(HTTPHeaderSet*, allowedCrossOriginResponseHeaders = createAllowedCrossOriginResponseHeadersSet().leakPtr());
96 return allowedCrossOriginResponseHeaders->contains(name);
99 void updateRequestForAccessControl(ResourceRequest& request, SecurityOrigin* securityOrigin, StoredCredentials allowCredentials)
101 request.removeCredentials();
102 request.setAllowCookies(allowCredentials == AllowStoredCredentials);
105 request.setHTTPOrigin(securityOrigin->toAtomicString());
108 ResourceRequest createAccessControlPreflightRequest(const ResourceRequest& request, SecurityOrigin* securityOrigin)
110 ResourceRequest preflightRequest(request.url());
111 updateRequestForAccessControl(preflightRequest, securityOrigin, DoNotAllowStoredCredentials);
112 preflightRequest.setHTTPMethod("OPTIONS");
113 preflightRequest.setHTTPHeaderField("Access-Control-Request-Method", request.httpMethod());
114 preflightRequest.setPriority(request.priority());
116 const HTTPHeaderMap& requestHeaderFields = request.httpHeaderFields();
118 if (requestHeaderFields.size() > 0) {
119 StringBuilder headerBuffer;
120 HTTPHeaderMap::const_iterator it = requestHeaderFields.begin();
121 headerBuffer.append(it->key);
124 HTTPHeaderMap::const_iterator end = requestHeaderFields.end();
125 for (; it != end; ++it) {
126 headerBuffer.appendLiteral(", ");
127 headerBuffer.append(it->key);
130 preflightRequest.setHTTPHeaderField("Access-Control-Request-Headers", AtomicString(headerBuffer.toString().lower()));
133 return preflightRequest;
136 static bool isOriginSeparator(UChar ch)
138 return isASCIISpace(ch) || ch == ',';
141 bool passesAccessControlCheck(const ResourceResponse& response, StoredCredentials includeCredentials, SecurityOrigin* securityOrigin, String& errorDescription)
143 AtomicallyInitializedStatic(AtomicString&, accessControlAllowOrigin = *new AtomicString("access-control-allow-origin", AtomicString::ConstructFromLiteral));
144 AtomicallyInitializedStatic(AtomicString&, accessControlAllowCredentials = *new AtomicString("access-control-allow-credentials", AtomicString::ConstructFromLiteral));
146 if (!response.httpStatusCode()) {
147 errorDescription = "Received an invalid response. Origin '" + securityOrigin->toString() + "' is therefore not allowed access.";
151 // A wildcard Access-Control-Allow-Origin can not be used if credentials are to be sent,
152 // even with Access-Control-Allow-Credentials set to true.
153 const AtomicString& accessControlOriginString = response.httpHeaderField(accessControlAllowOrigin);
154 if (accessControlOriginString == starAtom && includeCredentials == DoNotAllowStoredCredentials)
157 if (accessControlOriginString != securityOrigin->toAtomicString()) {
158 if (accessControlOriginString == starAtom) {
159 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.";
160 } else if (accessControlOriginString.isEmpty()) {
161 errorDescription = "No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin '" + securityOrigin->toString() + "' is therefore not allowed access.";
162 } else if (accessControlOriginString.string().find(isOriginSeparator, 0) != kNotFound) {
163 errorDescription = "The 'Access-Control-Allow-Origin' header contains multiple values '" + accessControlOriginString + "', but only one is allowed. Origin '" + securityOrigin->toString() + "' is therefore not allowed access.";
165 KURL headerOrigin(KURL(), accessControlOriginString);
166 if (!headerOrigin.isValid())
167 errorDescription = "The 'Access-Control-Allow-Origin' header contains the invalid value '" + accessControlOriginString + "'. Origin '" + securityOrigin->toString() + "' is therefore not allowed access.";
169 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.";
174 if (includeCredentials == AllowStoredCredentials) {
175 const AtomicString& accessControlCredentialsString = response.httpHeaderField(accessControlAllowCredentials);
176 if (accessControlCredentialsString != "true") {
177 errorDescription = "Credentials flag is 'true', but the 'Access-Control-Allow-Credentials' header is '" + accessControlCredentialsString + "'. It must be 'true' to allow credentials.";
185 bool passesPreflightStatusCheck(const ResourceResponse& response, String& errorDescription)
187 if (response.httpStatusCode() < 200 || response.httpStatusCode() >= 400) {
188 errorDescription = "Invalid HTTP status code " + String::number(response.httpStatusCode());
195 void parseAccessControlExposeHeadersAllowList(const String& headerValue, HTTPHeaderSet& headerSet)
197 Vector<String> headers;
198 headerValue.split(',', false, headers);
199 for (unsigned headerCount = 0; headerCount < headers.size(); headerCount++) {
200 String strippedHeader = headers[headerCount].stripWhiteSpace();
201 if (!strippedHeader.isEmpty())
202 headerSet.add(strippedHeader);
206 } // namespace WebCore