Improve use of NeverDestroyed
[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 void updateRequestForAccessControl(ResourceRequest& request, SecurityOrigin& securityOrigin, StoredCredentials allowCredentials)
62 {
63     request.removeCredentials();
64     request.setAllowCookies(allowCredentials == AllowStoredCredentials);
65     request.setHTTPOrigin(securityOrigin.toString());
66 }
67
68 ResourceRequest createAccessControlPreflightRequest(const ResourceRequest& request, SecurityOrigin& securityOrigin, const String& referrer)
69 {
70     ResourceRequest preflightRequest(request.url());
71     static const double platformDefaultTimeout = 0;
72     preflightRequest.setTimeoutInterval(platformDefaultTimeout);
73     updateRequestForAccessControl(preflightRequest, securityOrigin, DoNotAllowStoredCredentials);
74     preflightRequest.setHTTPMethod("OPTIONS");
75     preflightRequest.setHTTPHeaderField(HTTPHeaderName::AccessControlRequestMethod, request.httpMethod());
76     preflightRequest.setPriority(request.priority());
77     if (!referrer.isNull())
78         preflightRequest.setHTTPReferrer(referrer);
79
80     const HTTPHeaderMap& requestHeaderFields = request.httpHeaderFields();
81
82     if (!requestHeaderFields.isEmpty()) {
83         Vector<String> unsafeHeaders;
84         for (const auto& headerField : requestHeaderFields.commonHeaders()) {
85             if (!isCrossOriginSafeRequestHeader(headerField.key, headerField.value))
86                 unsafeHeaders.append(httpHeaderNameString(headerField.key).toStringWithoutCopying().convertToASCIILowercase());
87         }
88         for (const auto& headerField : requestHeaderFields.uncommonHeaders())
89             unsafeHeaders.append(headerField.key.convertToASCIILowercase());
90
91         std::sort(unsafeHeaders.begin(), unsafeHeaders.end(), WTF::codePointCompareLessThan);
92
93         StringBuilder headerBuffer;
94
95         bool appendComma = false;
96         for (const auto& headerField : unsafeHeaders) {
97             if (appendComma)
98                 headerBuffer.append(',');
99             else
100                 appendComma = true;
101
102             headerBuffer.append(headerField);
103         }
104         if (!headerBuffer.isEmpty())
105             preflightRequest.setHTTPHeaderField(HTTPHeaderName::AccessControlRequestHeaders, headerBuffer.toString());
106     }
107
108     return preflightRequest;
109 }
110
111 bool isValidCrossOriginRedirectionURL(const URL& redirectURL)
112 {
113     return SchemeRegistry::shouldTreatURLSchemeAsCORSEnabled(redirectURL.protocol().toStringWithoutCopying())
114         && redirectURL.user().isEmpty()
115         && redirectURL.pass().isEmpty();
116 }
117
118 void cleanRedirectedRequestForAccessControl(ResourceRequest& request)
119 {
120     // Remove headers that may have been added by the network layer that cause access control to fail.
121     request.clearHTTPContentType();
122     request.clearHTTPReferrer();
123     request.clearHTTPOrigin();
124     request.clearHTTPUserAgent();
125     request.clearHTTPAccept();
126     request.clearHTTPAcceptEncoding();
127 }
128
129 bool passesAccessControlCheck(const ResourceResponse& response, StoredCredentials includeCredentials, SecurityOrigin& securityOrigin, String& errorDescription)
130 {
131     // A wildcard Access-Control-Allow-Origin can not be used if credentials are to be sent,
132     // even with Access-Control-Allow-Credentials set to true.
133     const String& accessControlOriginString = response.httpHeaderField(HTTPHeaderName::AccessControlAllowOrigin);
134     if (accessControlOriginString == "*" && includeCredentials == DoNotAllowStoredCredentials)
135         return true;
136
137     String securityOriginString = securityOrigin.toString();
138     if (accessControlOriginString != securityOriginString) {
139         if (accessControlOriginString == "*")
140             errorDescription = ASCIILiteral("Cannot use wildcard in Access-Control-Allow-Origin when credentials flag is true.");
141         else if (accessControlOriginString.find(',') != notFound)
142             errorDescription = ASCIILiteral("Access-Control-Allow-Origin cannot contain more than one origin.");
143         else
144             errorDescription = makeString("Origin ", securityOriginString, " is not allowed by Access-Control-Allow-Origin.");
145         return false;
146     }
147
148     if (includeCredentials == AllowStoredCredentials) {
149         const String& accessControlCredentialsString = response.httpHeaderField(HTTPHeaderName::AccessControlAllowCredentials);
150         if (accessControlCredentialsString != "true") {
151             errorDescription = "Credentials flag is true, but Access-Control-Allow-Credentials is not \"true\".";
152             return false;
153         }
154     }
155
156     return true;
157 }
158
159 } // namespace WebCore