36b55a12d2db8b05607c8cde5656031a23cdd308
[WebKit-https.git] / Source / WebCore / loader / 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 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 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 "CrossOriginAccessControl.h"
29
30 #include "HTTPHeaderNames.h"
31 #include "HTTPParsers.h"
32 #include "ResourceRequest.h"
33 #include "ResourceResponse.h"
34 #include "SchemeRegistry.h"
35 #include "SecurityOrigin.h"
36 #include <mutex>
37 #include <wtf/NeverDestroyed.h>
38 #include <wtf/text/AtomicString.h>
39 #include <wtf/text/StringBuilder.h>
40
41 namespace WebCore {
42
43 bool isOnAccessControlSimpleRequestMethodWhitelist(const String& method)
44 {
45     return method == "GET" || method == "HEAD" || method == "POST";
46 }
47
48 bool isSimpleCrossOriginAccessRequest(const String& method, const HTTPHeaderMap& headerMap)
49 {
50     if (!isOnAccessControlSimpleRequestMethodWhitelist(method))
51         return false;
52
53     for (const auto& header : headerMap) {
54         if (!header.keyAsHTTPHeaderName || !isCrossOriginSafeRequestHeader(header.keyAsHTTPHeaderName.value(), header.value))
55             return false;
56     }
57
58     return true;
59 }
60
61 bool isOnAccessControlResponseHeaderWhitelist(const String& name)
62 {
63     static std::once_flag onceFlag;
64     static LazyNeverDestroyed<HTTPHeaderSet> allowedCrossOriginResponseHeaders;
65
66     std::call_once(onceFlag, []{
67         allowedCrossOriginResponseHeaders.construct<std::initializer_list<String>>({
68             "cache-control",
69             "content-language",
70             "content-type",
71             "expires",
72             "last-modified",
73             "pragma"
74         });
75     });
76
77     return allowedCrossOriginResponseHeaders.get().contains(name);
78 }
79
80 void updateRequestForAccessControl(ResourceRequest& request, SecurityOrigin& securityOrigin, StoredCredentials allowCredentials)
81 {
82     request.removeCredentials();
83     request.setAllowCookies(allowCredentials == AllowStoredCredentials);
84     request.setHTTPOrigin(securityOrigin.toString());
85 }
86
87 ResourceRequest createAccessControlPreflightRequest(const ResourceRequest& request, SecurityOrigin& securityOrigin, const String& referrer)
88 {
89     ResourceRequest preflightRequest(request.url());
90     static const double platformDefaultTimeout = 0;
91     preflightRequest.setTimeoutInterval(platformDefaultTimeout);
92     updateRequestForAccessControl(preflightRequest, securityOrigin, DoNotAllowStoredCredentials);
93     preflightRequest.setHTTPMethod("OPTIONS");
94     preflightRequest.setHTTPHeaderField(HTTPHeaderName::AccessControlRequestMethod, request.httpMethod());
95     preflightRequest.setPriority(request.priority());
96     if (!referrer.isNull())
97         preflightRequest.setHTTPReferrer(referrer);
98
99     const HTTPHeaderMap& requestHeaderFields = request.httpHeaderFields();
100
101     if (!requestHeaderFields.isEmpty()) {
102         Vector<String> unsafeHeaders;
103         for (const auto& headerField : requestHeaderFields.commonHeaders()) {
104             if (!isCrossOriginSafeRequestHeader(headerField.key, headerField.value))
105                 unsafeHeaders.append(httpHeaderNameString(headerField.key).toStringWithoutCopying().convertToASCIILowercase());
106         }
107         for (const auto& headerField : requestHeaderFields.uncommonHeaders())
108             unsafeHeaders.append(headerField.key.convertToASCIILowercase());
109
110         std::sort(unsafeHeaders.begin(), unsafeHeaders.end(), WTF::codePointCompareLessThan);
111
112         StringBuilder headerBuffer;
113
114         bool appendComma = false;
115         for (const auto& headerField : unsafeHeaders) {
116             if (appendComma)
117                 headerBuffer.append(',');
118             else
119                 appendComma = true;
120
121             headerBuffer.append(headerField);
122         }
123         if (!headerBuffer.isEmpty())
124             preflightRequest.setHTTPHeaderField(HTTPHeaderName::AccessControlRequestHeaders, headerBuffer.toString());
125     }
126
127     return preflightRequest;
128 }
129
130 bool isValidCrossOriginRedirectionURL(const URL& redirectURL)
131 {
132     return SchemeRegistry::shouldTreatURLSchemeAsCORSEnabled(redirectURL.protocol().toStringWithoutCopying())
133         && redirectURL.user().isEmpty()
134         && redirectURL.pass().isEmpty();
135 }
136
137 void cleanRedirectedRequestForAccessControl(ResourceRequest& request)
138 {
139     // Remove headers that may have been added by the network layer that cause access control to fail.
140     request.clearHTTPContentType();
141     request.clearHTTPReferrer();
142     request.clearHTTPOrigin();
143     request.clearHTTPUserAgent();
144     request.clearHTTPAccept();
145     request.clearHTTPAcceptEncoding();
146 }
147
148 bool passesAccessControlCheck(const ResourceResponse& response, StoredCredentials includeCredentials, SecurityOrigin& securityOrigin, String& errorDescription)
149 {
150     // A wildcard Access-Control-Allow-Origin can not be used if credentials are to be sent,
151     // even with Access-Control-Allow-Credentials set to true.
152     const String& accessControlOriginString = response.httpHeaderField(HTTPHeaderName::AccessControlAllowOrigin);
153     if (accessControlOriginString == "*" && includeCredentials == DoNotAllowStoredCredentials)
154         return true;
155
156     String securityOriginString = securityOrigin.toString();
157     if (accessControlOriginString != securityOriginString) {
158         if (accessControlOriginString == "*")
159             errorDescription = ASCIILiteral("Cannot use wildcard in Access-Control-Allow-Origin when credentials flag is true.");
160         else if (accessControlOriginString.find(',') != notFound)
161             errorDescription = ASCIILiteral("Access-Control-Allow-Origin cannot contain more than one origin.");
162         else
163             errorDescription = makeString("Origin ", securityOriginString, " is not allowed by Access-Control-Allow-Origin.");
164         return false;
165     }
166
167     if (includeCredentials == AllowStoredCredentials) {
168         const String& accessControlCredentialsString = response.httpHeaderField(HTTPHeaderName::AccessControlAllowCredentials);
169         if (accessControlCredentialsString != "true") {
170             errorDescription = "Credentials flag is true, but Access-Control-Allow-Credentials is not \"true\".";
171             return false;
172         }
173     }
174
175     return true;
176 }
177
178 } // namespace WebCore