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->toString()); 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", 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 // A wildcard Access-Control-Allow-Origin can not be used if credentials are to be sent, 147 // even with Access-Control-Allow-Credentials set to true. 148 const AtomicString& accessControlOriginString = response.httpHeaderField(accessControlAllowOrigin); 149 if (accessControlOriginString == starAtom && includeCredentials == DoNotAllowStoredCredentials) 150 return true; 151 152 if (accessControlOriginString != securityOrigin->toString()) { 153 if (accessControlOriginString == starAtom) { 154 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."; 155 } else if (accessControlOriginString.isEmpty()) { 156 errorDescription = "No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin '" + securityOrigin->toString() + "' is therefore not allowed access."; 157 } else if (accessControlOriginString.string().find(isOriginSeparator, 0) != kNotFound) { 158 errorDescription = "The 'Access-Control-Allow-Origin' header contains multiple values '" + accessControlOriginString + "', but only one is allowed. Origin '" + securityOrigin->toString() + "' is therefore not allowed access."; 159 } else { 160 KURL headerOrigin(KURL(), accessControlOriginString); 161 if (!headerOrigin.isValid()) 162 errorDescription = "The 'Access-Control-Allow-Origin' header contains the invalid value '" + accessControlOriginString + "'. Origin '" + securityOrigin->toString() + "' is therefore not allowed access."; 163 else 164 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."; 165 } 166 return false; 167 } 168 169 if (includeCredentials == AllowStoredCredentials) { 170 const AtomicString& accessControlCredentialsString = response.httpHeaderField(accessControlAllowCredentials); 171 if (accessControlCredentialsString != "true") { 172 errorDescription = "Credentials flag is 'true', but the 'Access-Control-Allow-Credentials' header is '" + accessControlCredentialsString + "'. It must be 'true' to allow credentials."; 173 return false; 174 } 175 } 176 177 return true; 178 } 179 180 bool passesPreflightStatusCheck(const ResourceResponse& response, String& errorDescription) 181 { 182 if (response.httpStatusCode() < 200 || response.httpStatusCode() >= 400) { 183 errorDescription = "Invalid HTTP status code " + String::number(response.httpStatusCode()); 184 return false; 185 } 186 187 return true; 188 } 189 190 void parseAccessControlExposeHeadersAllowList(const String& headerValue, HTTPHeaderSet& headerSet) 191 { 192 Vector<String> headers; 193 headerValue.split(',', false, headers); 194 for (unsigned headerCount = 0; headerCount < headers.size(); headerCount++) { 195 String strippedHeader = headers[headerCount].stripWhiteSpace(); 196 if (!strippedHeader.isEmpty()) 197 headerSet.add(strippedHeader); 198 } 199 } 200 201 } // namespace WebCore 202