Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / third_party / WebKit / Source / core / fetch / CrossOriginAccessControl.cpp
blob825e90c8c663df46f0d382422f5d95113eab1683
1 /*
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
6 * are met:
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.
27 #include "config.h"
28 #include "core/fetch/CrossOriginAccessControl.h"
30 #include "core/fetch/Resource.h"
31 #include "core/fetch/ResourceLoaderOptions.h"
32 #include "platform/network/HTTPParsers.h"
33 #include "platform/network/ResourceRequest.h"
34 #include "platform/network/ResourceResponse.h"
35 #include "platform/weborigin/SchemeRegistry.h"
36 #include "platform/weborigin/SecurityOrigin.h"
37 #include "wtf/Threading.h"
38 #include "wtf/text/AtomicString.h"
39 #include "wtf/text/StringBuilder.h"
40 #include <algorithm>
42 namespace blink {
44 static PassOwnPtr<HTTPHeaderSet> createAllowedCrossOriginResponseHeadersSet()
46 OwnPtr<HTTPHeaderSet> headerSet = adoptPtr(new HashSet<String, CaseFoldingHash>);
48 headerSet->add("cache-control");
49 headerSet->add("content-language");
50 headerSet->add("content-type");
51 headerSet->add("expires");
52 headerSet->add("last-modified");
53 headerSet->add("pragma");
55 return headerSet.release();
58 bool isOnAccessControlResponseHeaderWhitelist(const String& name)
60 AtomicallyInitializedStaticReference(HTTPHeaderSet, allowedCrossOriginResponseHeaders, (createAllowedCrossOriginResponseHeadersSet().leakPtr()));
62 return allowedCrossOriginResponseHeaders.contains(name);
65 void updateRequestForAccessControl(ResourceRequest& request, SecurityOrigin* securityOrigin, StoredCredentials allowCredentials)
67 request.removeCredentials();
68 request.setAllowStoredCredentials(allowCredentials == AllowStoredCredentials);
69 request.setFetchCredentialsMode(allowCredentials == AllowStoredCredentials ? WebURLRequest::FetchCredentialsModeInclude : WebURLRequest::FetchCredentialsModeOmit);
71 if (securityOrigin)
72 request.setHTTPOrigin(securityOrigin->toAtomicString());
75 ResourceRequest createAccessControlPreflightRequest(const ResourceRequest& request, SecurityOrigin* securityOrigin)
77 ResourceRequest preflightRequest(request.url());
78 updateRequestForAccessControl(preflightRequest, securityOrigin, DoNotAllowStoredCredentials);
79 preflightRequest.setHTTPMethod("OPTIONS");
80 preflightRequest.setHTTPHeaderField("Access-Control-Request-Method", request.httpMethod());
81 preflightRequest.setPriority(request.priority());
82 preflightRequest.setRequestContext(request.requestContext());
83 preflightRequest.setSkipServiceWorker(true);
85 const HTTPHeaderMap& requestHeaderFields = request.httpHeaderFields();
87 if (requestHeaderFields.size() > 0) {
88 // Sort header names lexicographically: https://crbug.com/452391
89 // Fetch API Spec:
90 // https://fetch.spec.whatwg.org/#cors-preflight-fetch-0
91 Vector<String> headers;
92 for (const auto& header : requestHeaderFields) {
93 if (equalIgnoringCase(header.key, "referer")) {
94 // When the request is from a Worker, referrer header was added
95 // by WorkerThreadableLoader. But it should not be added to
96 // Access-Control-Request-Headers header.
97 continue;
99 headers.append(header.key.lower());
101 std::sort(headers.begin(), headers.end(), WTF::codePointCompareLessThan);
102 StringBuilder headerBuffer;
103 for (const String& header : headers) {
104 if (!headerBuffer.isEmpty())
105 headerBuffer.appendLiteral(", ");
106 headerBuffer.append(header);
108 preflightRequest.setHTTPHeaderField("Access-Control-Request-Headers", AtomicString(headerBuffer.toString()));
111 return preflightRequest;
114 static bool isOriginSeparator(UChar ch)
116 return isASCIISpace(ch) || ch == ',';
119 static bool isInterestingStatusCode(int statusCode)
121 // Predicate that gates what status codes should be included in
122 // console error messages for responses containing no access
123 // control headers.
124 return statusCode >= 400;
127 static String buildAccessControlFailureMessage(const String& detail, SecurityOrigin* securityOrigin)
129 return detail + " Origin '" + securityOrigin->toString() + "' is therefore not allowed access.";
132 bool passesAccessControlCheck(const ResourceResponse& response, StoredCredentials includeCredentials, SecurityOrigin* securityOrigin, String& errorDescription, WebURLRequest::RequestContext context)
134 AtomicallyInitializedStaticReference(AtomicString, allowOriginHeaderName, (new AtomicString("access-control-allow-origin", AtomicString::ConstructFromLiteral)));
135 AtomicallyInitializedStaticReference(AtomicString, allowCredentialsHeaderName, (new AtomicString("access-control-allow-credentials", AtomicString::ConstructFromLiteral)));
137 int statusCode = response.httpStatusCode();
139 if (!statusCode) {
140 errorDescription = buildAccessControlFailureMessage("Invalid response.", securityOrigin);
141 return false;
144 const AtomicString& allowOriginHeaderValue = response.httpHeaderField(allowOriginHeaderName);
145 if (allowOriginHeaderValue == starAtom) {
146 // A wildcard Access-Control-Allow-Origin can not be used if credentials are to be sent,
147 // even with Access-Control-Allow-Credentials set to true.
148 if (includeCredentials == DoNotAllowStoredCredentials)
149 return true;
150 if (response.isHTTP()) {
151 errorDescription = buildAccessControlFailureMessage("A wildcard '*' cannot be used in the 'Access-Control-Allow-Origin' header when the credentials flag is true.", securityOrigin);
152 return false;
154 } else if (allowOriginHeaderValue != securityOrigin->toAtomicString()) {
155 if (allowOriginHeaderValue.isNull()) {
156 errorDescription = buildAccessControlFailureMessage("No 'Access-Control-Allow-Origin' header is present on the requested resource.", securityOrigin);
158 if (isInterestingStatusCode(statusCode))
159 errorDescription.append(" The response had HTTP status code " + String::number(statusCode) + ".");
161 if (context == WebURLRequest::RequestContextFetch)
162 errorDescription.append(" If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.");
164 return false;
167 String detail;
168 if (allowOriginHeaderValue.string().find(isOriginSeparator, 0) != kNotFound) {
169 detail = "The 'Access-Control-Allow-Origin' header contains multiple values '" + allowOriginHeaderValue + "', but only one is allowed.";
170 } else {
171 KURL headerOrigin(KURL(), allowOriginHeaderValue);
172 if (!headerOrigin.isValid())
173 detail = "The 'Access-Control-Allow-Origin' header contains the invalid value '" + allowOriginHeaderValue + "'.";
174 else
175 detail = "The 'Access-Control-Allow-Origin' header has a value '" + allowOriginHeaderValue + "' that is not equal to the supplied origin.";
177 errorDescription = buildAccessControlFailureMessage(detail, securityOrigin);
178 if (context == WebURLRequest::RequestContextFetch)
179 errorDescription.append(" Have the server send the header with a valid value, or, if an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.");
180 return false;
183 if (includeCredentials == AllowStoredCredentials) {
184 const AtomicString& allowCredentialsHeaderValue = response.httpHeaderField(allowCredentialsHeaderName);
185 if (allowCredentialsHeaderValue != "true") {
186 errorDescription = buildAccessControlFailureMessage("Credentials flag is 'true', but the 'Access-Control-Allow-Credentials' header is '" + allowCredentialsHeaderValue + "'. It must be 'true' to allow credentials.", securityOrigin);
187 return false;
191 return true;
194 bool passesPreflightStatusCheck(const ResourceResponse& response, String& errorDescription)
196 // CORS preflight with 3XX is considered network error in
197 // Fetch API Spec:
198 // https://fetch.spec.whatwg.org/#cors-preflight-fetch
199 // CORS Spec:
200 // http://www.w3.org/TR/cors/#cross-origin-request-with-preflight-0
201 // https://crbug.com/452394
202 if (response.httpStatusCode() < 200 || response.httpStatusCode() >= 300) {
203 errorDescription = "Response for preflight has invalid HTTP status code " + String::number(response.httpStatusCode());
204 return false;
207 return true;
210 void parseAccessControlExposeHeadersAllowList(const String& headerValue, HTTPHeaderSet& headerSet)
212 Vector<String> headers;
213 headerValue.split(',', false, headers);
214 for (unsigned headerCount = 0; headerCount < headers.size(); headerCount++) {
215 String strippedHeader = headers[headerCount].stripWhiteSpace();
216 if (!strippedHeader.isEmpty())
217 headerSet.add(strippedHeader);
221 bool CrossOriginAccessControl::isLegalRedirectLocation(const KURL& requestURL, String& errorDescription)
223 // CORS restrictions imposed on Location: URL -- http://www.w3.org/TR/cors/#redirect-steps (steps 2 + 3.)
224 if (!SchemeRegistry::shouldTreatURLSchemeAsCORSEnabled(requestURL.protocol())) {
225 errorDescription = "The request was redirected to a URL ('" + requestURL.string() + "') which has a disallowed scheme for cross-origin requests.";
226 return false;
229 if (!(requestURL.user().isEmpty() && requestURL.pass().isEmpty())) {
230 errorDescription = "The request was redirected to a URL ('" + requestURL.string() + "') containing userinfo, which is disallowed for cross-origin requests.";
231 return false;
234 return true;
237 bool CrossOriginAccessControl::handleRedirect(SecurityOrigin* securityOrigin, ResourceRequest& newRequest, const ResourceResponse& redirectResponse, StoredCredentials withCredentials, ResourceLoaderOptions& options, String& errorMessage)
239 // http://www.w3.org/TR/cors/#redirect-steps terminology:
240 const KURL& originalURL = redirectResponse.url();
241 const KURL& newURL = newRequest.url();
243 bool redirectCrossOrigin = !securityOrigin->canRequest(newURL);
245 // Same-origin request URLs that redirect are allowed without checking access.
246 if (!securityOrigin->canRequest(originalURL)) {
247 // Follow http://www.w3.org/TR/cors/#redirect-steps
248 String errorDescription;
250 // Steps 3 & 4 - check if scheme and other URL restrictions hold.
251 bool allowRedirect = isLegalRedirectLocation(newURL, errorDescription);
252 if (allowRedirect) {
253 // Step 5: perform resource sharing access check.
254 allowRedirect = passesAccessControlCheck(redirectResponse, withCredentials, securityOrigin, errorDescription, newRequest.requestContext());
255 if (allowRedirect) {
256 RefPtr<SecurityOrigin> originalOrigin = SecurityOrigin::create(originalURL);
257 // Step 6: if the request URL origin is not same origin as the original URL's,
258 // set the source origin to a globally unique identifier.
259 if (!originalOrigin->canRequest(newURL)) {
260 options.securityOrigin = SecurityOrigin::createUnique();
261 securityOrigin = options.securityOrigin.get();
265 if (!allowRedirect) {
266 const String& originalOrigin = SecurityOrigin::create(originalURL)->toString();
267 errorMessage = "Redirect at origin '" + originalOrigin + "' has been blocked from loading by Cross-Origin Resource Sharing policy: " + errorDescription;
268 return false;
271 if (redirectCrossOrigin) {
272 // If now to a different origin, update/set Origin:.
273 newRequest.clearHTTPOrigin();
274 newRequest.setHTTPOrigin(securityOrigin->toAtomicString());
275 // If the user didn't request credentials in the first place, update our
276 // state so we neither request them nor expect they must be allowed.
277 if (options.credentialsRequested == ClientDidNotRequestCredentials)
278 options.allowCredentials = DoNotAllowStoredCredentials;
280 return true;
283 } // namespace blink