Upstream version 5.34.92.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 "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"
36
37 namespace WebCore {
38
39 bool isOnAccessControlSimpleRequestMethodWhitelist(const String& method)
40 {
41     return method == "GET" || method == "HEAD" || method == "POST";
42 }
43
44 bool isOnAccessControlSimpleRequestHeaderWhitelist(const AtomicString& name, const AtomicString& value)
45 {
46     if (equalIgnoringCase(name, "accept")
47         || equalIgnoringCase(name, "accept-language")
48         || equalIgnoringCase(name, "content-language")
49         || equalIgnoringCase(name, "origin")
50         || equalIgnoringCase(name, "referer"))
51         return true;
52
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");
59     }
60
61     return false;
62 }
63
64 bool isSimpleCrossOriginAccessRequest(const String& method, const HTTPHeaderMap& headerMap)
65 {
66     if (!isOnAccessControlSimpleRequestMethodWhitelist(method))
67         return false;
68
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))
72             return false;
73     }
74
75     return true;
76 }
77
78 static PassOwnPtr<HTTPHeaderSet> createAllowedCrossOriginResponseHeadersSet()
79 {
80     OwnPtr<HTTPHeaderSet> headerSet = adoptPtr(new HashSet<String, CaseFoldingHash>);
81
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");
88
89     return headerSet.release();
90 }
91
92 bool isOnAccessControlResponseHeaderWhitelist(const String& name)
93 {
94     AtomicallyInitializedStatic(HTTPHeaderSet*, allowedCrossOriginResponseHeaders = createAllowedCrossOriginResponseHeadersSet().leakPtr());
95
96     return allowedCrossOriginResponseHeaders->contains(name);
97 }
98
99 void updateRequestForAccessControl(ResourceRequest& request, SecurityOrigin* securityOrigin, StoredCredentials allowCredentials)
100 {
101     request.removeCredentials();
102     request.setAllowCookies(allowCredentials == AllowStoredCredentials);
103
104     if (securityOrigin)
105         request.setHTTPOrigin(securityOrigin->toAtomicString());
106 }
107
108 ResourceRequest createAccessControlPreflightRequest(const ResourceRequest& request, SecurityOrigin* securityOrigin)
109 {
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());
115
116     const HTTPHeaderMap& requestHeaderFields = request.httpHeaderFields();
117
118     if (requestHeaderFields.size() > 0) {
119         StringBuilder headerBuffer;
120         HTTPHeaderMap::const_iterator it = requestHeaderFields.begin();
121         headerBuffer.append(it->key);
122         ++it;
123
124         HTTPHeaderMap::const_iterator end = requestHeaderFields.end();
125         for (; it != end; ++it) {
126             headerBuffer.appendLiteral(", ");
127             headerBuffer.append(it->key);
128         }
129
130         preflightRequest.setHTTPHeaderField("Access-Control-Request-Headers", AtomicString(headerBuffer.toString().lower()));
131     }
132
133     return preflightRequest;
134 }
135
136 static bool isOriginSeparator(UChar ch)
137 {
138     return isASCIISpace(ch) || ch == ',';
139 }
140
141 bool passesAccessControlCheck(const ResourceResponse& response, StoredCredentials includeCredentials, SecurityOrigin* securityOrigin, String& errorDescription)
142 {
143     AtomicallyInitializedStatic(AtomicString&, accessControlAllowOrigin = *new AtomicString("access-control-allow-origin", AtomicString::ConstructFromLiteral));
144     AtomicallyInitializedStatic(AtomicString&, accessControlAllowCredentials = *new AtomicString("access-control-allow-credentials", AtomicString::ConstructFromLiteral));
145
146     if (!response.httpStatusCode()) {
147         errorDescription = "Received an invalid response. Origin '" + securityOrigin->toString() + "' is therefore not allowed access.";
148         return false;
149     }
150
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)
155         return true;
156
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.";
164         } else {
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.";
168             else
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.";
170         }
171         return false;
172     }
173
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.";
178             return false;
179         }
180     }
181
182     return true;
183 }
184
185 bool passesPreflightStatusCheck(const ResourceResponse& response, String& errorDescription)
186 {
187     if (response.httpStatusCode() < 200 || response.httpStatusCode() >= 400) {
188         errorDescription = "Invalid HTTP status code " + String::number(response.httpStatusCode());
189         return false;
190     }
191
192     return true;
193 }
194
195 void parseAccessControlExposeHeadersAllowList(const String& headerValue, HTTPHeaderSet& headerSet)
196 {
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);
203     }
204 }
205
206 } // namespace WebCore