Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / core / fetch / CrossOriginAccessControl.cpp
1 /*
2  * Copyright (C) 2008 Apple Inc. All Rights Reserved.
3  *
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.
12  *
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.
24  *
25  */
26
27 #include "config.h"
28 #include "core/fetch/CrossOriginAccessControl.h"
29
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
41 namespace blink {
42
43 static PassOwnPtr<HTTPHeaderSet> createAllowedCrossOriginResponseHeadersSet()
44 {
45     OwnPtr<HTTPHeaderSet> headerSet = adoptPtr(new HashSet<String, CaseFoldingHash>);
46
47     headerSet->add("cache-control");
48     headerSet->add("content-language");
49     headerSet->add("content-type");
50     headerSet->add("expires");
51     headerSet->add("last-modified");
52     headerSet->add("pragma");
53
54     return headerSet.release();
55 }
56
57 bool isOnAccessControlResponseHeaderWhitelist(const String& name)
58 {
59     AtomicallyInitializedStatic(HTTPHeaderSet*, allowedCrossOriginResponseHeaders = createAllowedCrossOriginResponseHeadersSet().leakPtr());
60
61     return allowedCrossOriginResponseHeaders->contains(name);
62 }
63
64 void updateRequestForAccessControl(ResourceRequest& request, SecurityOrigin* securityOrigin, StoredCredentials allowCredentials)
65 {
66     request.removeCredentials();
67     request.setAllowStoredCredentials(allowCredentials == AllowStoredCredentials);
68     request.setFetchCredentialsMode(allowCredentials == AllowStoredCredentials ? WebURLRequest::FetchCredentialsModeInclude : WebURLRequest::FetchCredentialsModeOmit);
69
70     if (securityOrigin)
71         request.setHTTPOrigin(securityOrigin->toAtomicString());
72 }
73
74 ResourceRequest createAccessControlPreflightRequest(const ResourceRequest& request, SecurityOrigin* securityOrigin)
75 {
76     ResourceRequest preflightRequest(request.url());
77     updateRequestForAccessControl(preflightRequest, securityOrigin, DoNotAllowStoredCredentials);
78     preflightRequest.setHTTPMethod("OPTIONS");
79     preflightRequest.setHTTPHeaderField("Access-Control-Request-Method", request.httpMethod());
80     preflightRequest.setPriority(request.priority());
81     preflightRequest.setRequestContext(request.requestContext());
82
83     const HTTPHeaderMap& requestHeaderFields = request.httpHeaderFields();
84
85     if (requestHeaderFields.size() > 0) {
86         StringBuilder headerBuffer;
87         HTTPHeaderMap::const_iterator it = requestHeaderFields.begin();
88         headerBuffer.append(it->key);
89         ++it;
90
91         HTTPHeaderMap::const_iterator end = requestHeaderFields.end();
92         for (; it != end; ++it) {
93             headerBuffer.appendLiteral(", ");
94             headerBuffer.append(it->key);
95         }
96
97         preflightRequest.setHTTPHeaderField("Access-Control-Request-Headers", AtomicString(headerBuffer.toString().lower()));
98     }
99
100     return preflightRequest;
101 }
102
103 static bool isOriginSeparator(UChar ch)
104 {
105     return isASCIISpace(ch) || ch == ',';
106 }
107
108 static bool isInterestingStatusCode(int statusCode)
109 {
110     // Predicate that gates what status codes should be included in
111     // console error messages for responses containing no access
112     // control headers.
113     return statusCode >= 400;
114 }
115
116 bool passesAccessControlCheck(const ResourceResponse& response, StoredCredentials includeCredentials, SecurityOrigin* securityOrigin, String& errorDescription)
117 {
118     AtomicallyInitializedStatic(AtomicString&, accessControlAllowOrigin = *new AtomicString("access-control-allow-origin", AtomicString::ConstructFromLiteral));
119     AtomicallyInitializedStatic(AtomicString&, accessControlAllowCredentials = *new AtomicString("access-control-allow-credentials", AtomicString::ConstructFromLiteral));
120
121     if (!response.httpStatusCode()) {
122         errorDescription = "Received an invalid response. Origin '" + securityOrigin->toString() + "' is therefore not allowed access.";
123         return false;
124     }
125
126     const AtomicString& accessControlOriginString = response.httpHeaderField(accessControlAllowOrigin);
127     if (accessControlOriginString == starAtom) {
128         // A wildcard Access-Control-Allow-Origin can not be used if credentials are to be sent,
129         // even with Access-Control-Allow-Credentials set to true.
130         if (includeCredentials == DoNotAllowStoredCredentials)
131             return true;
132         if (response.isHTTP()) {
133             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.";
134             return false;
135         }
136     } else if (accessControlOriginString != securityOrigin->toAtomicString()) {
137         if (accessControlOriginString.isNull()) {
138             errorDescription = "No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin '" + securityOrigin->toString() + "' is therefore not allowed access.";
139
140             if (isInterestingStatusCode(response.httpStatusCode()))
141                 errorDescription.append(" The response had HTTP status code " + String::number(response.httpStatusCode()) + ".");
142         } else if (accessControlOriginString.string().find(isOriginSeparator, 0) != kNotFound) {
143             errorDescription = "The 'Access-Control-Allow-Origin' header contains multiple values '" + accessControlOriginString + "', but only one is allowed. Origin '" + securityOrigin->toString() + "' is therefore not allowed access.";
144         } else {
145             KURL headerOrigin(KURL(), accessControlOriginString);
146             if (!headerOrigin.isValid())
147                 errorDescription = "The 'Access-Control-Allow-Origin' header contains the invalid value '" + accessControlOriginString + "'. Origin '" + securityOrigin->toString() + "' is therefore not allowed access.";
148             else
149                 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.";
150         }
151         return false;
152     }
153
154     if (includeCredentials == AllowStoredCredentials) {
155         const AtomicString& accessControlCredentialsString = response.httpHeaderField(accessControlAllowCredentials);
156         if (accessControlCredentialsString != "true") {
157             errorDescription = "Credentials flag is 'true', but the 'Access-Control-Allow-Credentials' header is '" + accessControlCredentialsString + "'. It must be 'true' to allow credentials.";
158             return false;
159         }
160     }
161
162     return true;
163 }
164
165 bool passesPreflightStatusCheck(const ResourceResponse& response, String& errorDescription)
166 {
167     if (response.httpStatusCode() < 200 || response.httpStatusCode() >= 400) {
168         errorDescription = "Invalid HTTP status code " + String::number(response.httpStatusCode());
169         return false;
170     }
171
172     return true;
173 }
174
175 void parseAccessControlExposeHeadersAllowList(const String& headerValue, HTTPHeaderSet& headerSet)
176 {
177     Vector<String> headers;
178     headerValue.split(',', false, headers);
179     for (unsigned headerCount = 0; headerCount < headers.size(); headerCount++) {
180         String strippedHeader = headers[headerCount].stripWhiteSpace();
181         if (!strippedHeader.isEmpty())
182             headerSet.add(strippedHeader);
183     }
184 }
185
186 bool CrossOriginAccessControl::isLegalRedirectLocation(const KURL& requestURL, String& errorDescription)
187 {
188     // CORS restrictions imposed on Location: URL -- http://www.w3.org/TR/cors/#redirect-steps (steps 2 + 3.)
189     if (!SchemeRegistry::shouldTreatURLSchemeAsCORSEnabled(requestURL.protocol())) {
190         errorDescription = "The request was redirected to a URL ('" + requestURL.string() + "') which has a disallowed scheme for cross-origin requests.";
191         return false;
192     }
193
194     if (!(requestURL.user().isEmpty() && requestURL.pass().isEmpty())) {
195         errorDescription = "The request was redirected to a URL ('" + requestURL.string() + "') containing userinfo, which is disallowed for cross-origin requests.";
196         return false;
197     }
198
199     return true;
200 }
201
202 bool CrossOriginAccessControl::handleRedirect(Resource* resource, SecurityOrigin* securityOrigin, ResourceRequest& request, const ResourceResponse& redirectResponse, ResourceLoaderOptions& options, String& errorMessage)
203 {
204     // http://www.w3.org/TR/cors/#redirect-steps terminology:
205     const KURL& originalURL = redirectResponse.url();
206     const KURL& requestURL = request.url();
207
208     bool redirectCrossOrigin = !securityOrigin->canRequest(requestURL);
209
210     // Same-origin request URLs that redirect are allowed without checking access.
211     if (!securityOrigin->canRequest(originalURL)) {
212         // Follow http://www.w3.org/TR/cors/#redirect-steps
213         String errorDescription;
214
215         // Steps 3 & 4 - check if scheme and other URL restrictions hold.
216         bool allowRedirect = isLegalRedirectLocation(requestURL, errorDescription);
217         if (allowRedirect) {
218             // Step 5: perform resource sharing access check.
219             StoredCredentials withCredentials = resource->lastResourceRequest().allowStoredCredentials() ? AllowStoredCredentials : DoNotAllowStoredCredentials;
220             allowRedirect = passesAccessControlCheck(redirectResponse, withCredentials, securityOrigin, errorDescription);
221             if (allowRedirect) {
222                 RefPtr<SecurityOrigin> originalOrigin = SecurityOrigin::create(originalURL);
223                 // Step 6: if the request URL origin is not same origin as the original URL's,
224                 // set the source origin to a globally unique identifier.
225                 if (!originalOrigin->canRequest(requestURL)) {
226                     options.securityOrigin = SecurityOrigin::createUnique();
227                     securityOrigin = options.securityOrigin.get();
228                 }
229             }
230         }
231         if (!allowRedirect) {
232             const String& originalOrigin = SecurityOrigin::create(originalURL)->toString();
233             errorMessage = "Redirect at origin '" + originalOrigin + "' has been blocked from loading by Cross-Origin Resource Sharing policy: " + errorDescription;
234             return false;
235         }
236     }
237     if (redirectCrossOrigin) {
238         // If now to a different origin, update/set Origin:.
239         request.clearHTTPOrigin();
240         request.setHTTPOrigin(securityOrigin->toAtomicString());
241         // If the user didn't request credentials in the first place, update our
242         // state so we neither request them nor expect they must be allowed.
243         if (options.credentialsRequested == ClientDidNotRequestCredentials)
244             options.allowCredentials = DoNotAllowStoredCredentials;
245     }
246     return true;
247 }
248
249 } // namespace blink