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 "SecurityOrigin.h" 35#include <mutex> 36#include <wtf/NeverDestroyed.h> 37#include <wtf/text/AtomicString.h> 38#include <wtf/text/StringBuilder.h> 39 40namespace WebCore { 41 42bool isOnAccessControlSimpleRequestMethodWhitelist(const String& method) 43{ 44 return method == "GET" || method == "HEAD" || method == "POST"; 45} 46 47bool isOnAccessControlSimpleRequestHeaderWhitelist(const String& name, const String& value) 48{ 49 if (equalIgnoringCase(name, "accept") 50 || equalIgnoringCase(name, "accept-language") 51 || equalIgnoringCase(name, "content-language") 52 || equalIgnoringCase(name, "origin") 53 || equalIgnoringCase(name, "referer")) 54 return true; 55 56 // Preflight is required for MIME types that can not be sent via form submission. 57 if (equalIgnoringCase(name, "content-type")) { 58 String mimeType = extractMIMETypeFromMediaType(value); 59 return equalIgnoringCase(mimeType, "application/x-www-form-urlencoded") 60 || equalIgnoringCase(mimeType, "multipart/form-data") 61 || equalIgnoringCase(mimeType, "text/plain"); 62 } 63 64 return false; 65} 66 67bool isSimpleCrossOriginAccessRequest(const String& method, const HTTPHeaderMap& headerMap) 68{ 69 if (!isOnAccessControlSimpleRequestMethodWhitelist(method)) 70 return false; 71 72 for (const auto& header : headerMap) { 73 if (!isOnAccessControlSimpleRequestHeaderWhitelist(header.key, header.value)) 74 return false; 75 } 76 77 return true; 78} 79 80bool isOnAccessControlResponseHeaderWhitelist(const String& name) 81{ 82 static std::once_flag onceFlag; 83 static LazyNeverDestroyed<HTTPHeaderSet> allowedCrossOriginResponseHeaders; 84 85 std::call_once(onceFlag, []{ 86 allowedCrossOriginResponseHeaders.construct<std::initializer_list<String>>({ 87 "cache-control", 88 "content-language", 89 "content-type", 90 "expires", 91 "last-modified", 92 "pragma" 93 }); 94 }); 95 96 return allowedCrossOriginResponseHeaders.get().contains(name); 97} 98 99void updateRequestForAccessControl(ResourceRequest& request, SecurityOrigin* securityOrigin, StoredCredentials allowCredentials) 100{ 101 request.removeCredentials(); 102 request.setAllowCookies(allowCredentials == AllowStoredCredentials); 103 request.setHTTPOrigin(securityOrigin->toString()); 104} 105 106ResourceRequest createAccessControlPreflightRequest(const ResourceRequest& request, SecurityOrigin* securityOrigin) 107{ 108 ResourceRequest preflightRequest(request.url()); 109 updateRequestForAccessControl(preflightRequest, securityOrigin, DoNotAllowStoredCredentials); 110 preflightRequest.setHTTPMethod("OPTIONS"); 111 preflightRequest.setHTTPHeaderField(HTTPHeaderName::AccessControlRequestMethod, request.httpMethod()); 112 preflightRequest.setPriority(request.priority()); 113 114 const HTTPHeaderMap& requestHeaderFields = request.httpHeaderFields(); 115 116 if (!requestHeaderFields.isEmpty()) { 117 StringBuilder headerBuffer; 118 119 bool appendComma = false; 120 for (const auto& headerField : requestHeaderFields) { 121 if (appendComma) 122 headerBuffer.appendLiteral(", "); 123 else 124 appendComma = true; 125 126 headerBuffer.append(headerField.key); 127 } 128 129 preflightRequest.setHTTPHeaderField(HTTPHeaderName::AccessControlRequestHeaders, headerBuffer.toString().lower()); 130 } 131 132 return preflightRequest; 133} 134 135bool passesAccessControlCheck(const ResourceResponse& response, StoredCredentials includeCredentials, SecurityOrigin* securityOrigin, String& errorDescription) 136{ 137 // A wildcard Access-Control-Allow-Origin can not be used if credentials are to be sent, 138 // even with Access-Control-Allow-Credentials set to true. 139 const String& accessControlOriginString = response.httpHeaderField(HTTPHeaderName::AccessControlAllowOrigin); 140 if (accessControlOriginString == "*" && includeCredentials == DoNotAllowStoredCredentials) 141 return true; 142 143 if (securityOrigin->isUnique()) { 144 errorDescription = "Cannot make any requests from " + securityOrigin->toString() + "."; 145 return false; 146 } 147 148 // FIXME: Access-Control-Allow-Origin can contain a list of origins. 149 if (accessControlOriginString != securityOrigin->toString()) { 150 if (accessControlOriginString == "*") 151 errorDescription = "Cannot use wildcard in Access-Control-Allow-Origin when credentials flag is true."; 152 else 153 errorDescription = "Origin " + securityOrigin->toString() + " is not allowed by Access-Control-Allow-Origin."; 154 return false; 155 } 156 157 if (includeCredentials == AllowStoredCredentials) { 158 const String& accessControlCredentialsString = response.httpHeaderField(HTTPHeaderName::AccessControlAllowCredentials); 159 if (accessControlCredentialsString != "true") { 160 errorDescription = "Credentials flag is true, but Access-Control-Allow-Credentials is not \"true\"."; 161 return false; 162 } 163 } 164 165 return true; 166} 167 168void parseAccessControlExposeHeadersAllowList(const String& headerValue, HTTPHeaderSet& headerSet) 169{ 170 Vector<String> headers; 171 headerValue.split(',', false, headers); 172 for (unsigned headerCount = 0; headerCount < headers.size(); headerCount++) { 173 String strippedHeader = headers[headerCount].stripWhiteSpace(); 174 if (!strippedHeader.isEmpty()) 175 headerSet.add(strippedHeader); 176 } 177} 178 179} // namespace WebCore 180