| OLD | NEW |
| 1 /* | 1 /* |
| 2 * Copyright (C) 2008 Apple Inc. All Rights Reserved. | 2 * Copyright (C) 2008 Apple Inc. All Rights Reserved. |
| 3 * | 3 * |
| 4 * Redistribution and use in source and binary forms, with or without | 4 * Redistribution and use in source and binary forms, with or without |
| 5 * modification, are permitted provided that the following conditions | 5 * modification, are permitted provided that the following conditions |
| 6 * are met: | 6 * are met: |
| 7 * 1. Redistributions of source code must retain the above copyright | 7 * 1. Redistributions of source code must retain the above copyright |
| 8 * notice, this list of conditions and the following disclaimer. | 8 * notice, this list of conditions and the following disclaimer. |
| 9 * 2. Redistributions in binary form must reproduce the above copyright | 9 * 2. Redistributions in binary form must reproduce the above copyright |
| 10 * notice, this list of conditions and the following disclaimer in the | 10 * notice, this list of conditions and the following disclaimer in the |
| (...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 76 preflightRequest.setRequestContext(request.requestContext()); | 76 preflightRequest.setRequestContext(request.requestContext()); |
| 77 preflightRequest.setSkipServiceWorker(WebURLRequest::SkipServiceWorker::All); | 77 preflightRequest.setSkipServiceWorker(WebURLRequest::SkipServiceWorker::All); |
| 78 | 78 |
| 79 if (request.isExternalRequest()) | 79 if (request.isExternalRequest()) |
| 80 preflightRequest.setHTTPHeaderField( | 80 preflightRequest.setHTTPHeaderField( |
| 81 HTTPNames::Access_Control_Request_External, "true"); | 81 HTTPNames::Access_Control_Request_External, "true"); |
| 82 | 82 |
| 83 const HTTPHeaderMap& requestHeaderFields = request.httpHeaderFields(); | 83 const HTTPHeaderMap& requestHeaderFields = request.httpHeaderFields(); |
| 84 | 84 |
| 85 if (requestHeaderFields.size() > 0) { | 85 if (requestHeaderFields.size() > 0) { |
| 86 // Fetch API Spec: | 86 // Fetch API Spec: https://fetch.spec.whatwg.org/#cors-preflight-fetch-0 |
| 87 // https://fetch.spec.whatwg.org/#cors-preflight-fetch-0 | |
| 88 Vector<String> headers; | 87 Vector<String> headers; |
| 89 for (const auto& header : requestHeaderFields) { | 88 for (const auto& header : requestHeaderFields) { |
| 90 if (FetchUtils::isSimpleHeader(header.key, header.value)) { | 89 if (FetchUtils::isSimpleHeader(header.key, header.value)) { |
| 91 // Exclude simple headers. | 90 // Exclude simple headers. |
| 92 continue; | 91 continue; |
| 93 } | 92 } |
| 94 if (equalIgnoringCase(header.key, "referer")) { | 93 if (equalIgnoringCase(header.key, "referer")) { |
| 95 // When the request is from a Worker, referrer header was added | 94 // When the request is from a Worker, referrer header was added by |
| 96 // by WorkerThreadableLoader. But it should not be added to | 95 // WorkerThreadableLoader. But it should not be added to |
| 97 // Access-Control-Request-Headers header. | 96 // Access-Control-Request-Headers header. |
| 98 continue; | 97 continue; |
| 99 } | 98 } |
| 100 headers.append(header.key.lower()); | 99 headers.append(header.key.lower()); |
| 101 } | 100 } |
| 102 // Sort header names lexicographically. | 101 // Sort header names lexicographically. |
| 103 std::sort(headers.begin(), headers.end(), WTF::codePointCompareLessThan); | 102 std::sort(headers.begin(), headers.end(), WTF::codePointCompareLessThan); |
| 104 StringBuilder headerBuffer; | 103 StringBuilder headerBuffer; |
| 105 for (const String& header : headers) { | 104 for (const String& header : headers) { |
| 106 if (!headerBuffer.isEmpty()) | 105 if (!headerBuffer.isEmpty()) |
| 107 headerBuffer.append(", "); | 106 headerBuffer.append(", "); |
| 108 headerBuffer.append(header); | 107 headerBuffer.append(header); |
| 109 } | 108 } |
| 110 preflightRequest.setHTTPHeaderField( | 109 preflightRequest.setHTTPHeaderField( |
| 111 HTTPNames::Access_Control_Request_Headers, | 110 HTTPNames::Access_Control_Request_Headers, |
| 112 AtomicString(headerBuffer.toString())); | 111 AtomicString(headerBuffer.toString())); |
| 113 } | 112 } |
| 114 | 113 |
| 115 return preflightRequest; | 114 return preflightRequest; |
| 116 } | 115 } |
| 117 | 116 |
| 118 static bool isOriginSeparator(UChar ch) { | 117 static bool isOriginSeparator(UChar ch) { |
| 119 return isASCIISpace(ch) || ch == ','; | 118 return isASCIISpace(ch) || ch == ','; |
| 120 } | 119 } |
| 121 | 120 |
| 122 static bool isInterestingStatusCode(int statusCode) { | 121 static bool isInterestingStatusCode(int statusCode) { |
| 123 // Predicate that gates what status codes should be included in | 122 // Predicate that gates what status codes should be included in console error |
| 124 // console error messages for responses containing no access | 123 // messages for responses containing no access control headers. |
| 125 // control headers. | |
| 126 return statusCode >= 400; | 124 return statusCode >= 400; |
| 127 } | 125 } |
| 128 | 126 |
| 129 static String buildAccessControlFailureMessage( | 127 static String buildAccessControlFailureMessage( |
| 130 const String& detail, | 128 const String& detail, |
| 131 const SecurityOrigin* securityOrigin) { | 129 const SecurityOrigin* securityOrigin) { |
| 132 return detail + " Origin '" + securityOrigin->toString() + | 130 return detail + " Origin '" + securityOrigin->toString() + |
| 133 "' is therefore not allowed access."; | 131 "' is therefore not allowed access."; |
| 134 } | 132 } |
| 135 | 133 |
| (...skipping 19 matching lines...) Expand all Loading... |
| 155 | 153 |
| 156 if (!statusCode) { | 154 if (!statusCode) { |
| 157 errorDescription = | 155 errorDescription = |
| 158 buildAccessControlFailureMessage("Invalid response.", securityOrigin); | 156 buildAccessControlFailureMessage("Invalid response.", securityOrigin); |
| 159 return false; | 157 return false; |
| 160 } | 158 } |
| 161 | 159 |
| 162 const AtomicString& allowOriginHeaderValue = | 160 const AtomicString& allowOriginHeaderValue = |
| 163 response.httpHeaderField(allowOriginHeaderName); | 161 response.httpHeaderField(allowOriginHeaderName); |
| 164 | 162 |
| 165 // Check Suborigins, unless the Access-Control-Allow-Origin is '*', | 163 // Check Suborigins, unless the Access-Control-Allow-Origin is '*', which |
| 166 // which implies that all Suborigins are okay as well. | 164 // implies that all Suborigins are okay as well. |
| 167 if (securityOrigin->hasSuborigin() && allowOriginHeaderValue != starAtom) { | 165 if (securityOrigin->hasSuborigin() && allowOriginHeaderValue != starAtom) { |
| 168 const AtomicString& allowSuboriginHeaderValue = | 166 const AtomicString& allowSuboriginHeaderValue = |
| 169 response.httpHeaderField(allowSuboriginHeaderName); | 167 response.httpHeaderField(allowSuboriginHeaderName); |
| 170 AtomicString atomicSuboriginName(securityOrigin->suborigin()->name()); | 168 AtomicString atomicSuboriginName(securityOrigin->suborigin()->name()); |
| 171 if (allowSuboriginHeaderValue != starAtom && | 169 if (allowSuboriginHeaderValue != starAtom && |
| 172 allowSuboriginHeaderValue != atomicSuboriginName) { | 170 allowSuboriginHeaderValue != atomicSuboriginName) { |
| 173 errorDescription = buildAccessControlFailureMessage( | 171 errorDescription = buildAccessControlFailureMessage( |
| 174 "The 'Access-Control-Allow-Suborigin' header has a value '" + | 172 "The 'Access-Control-Allow-Suborigin' header has a value '" + |
| 175 allowSuboriginHeaderValue + | 173 allowSuboriginHeaderValue + |
| 176 "' that is not equal to the supplied suborigin.", | 174 "' that is not equal to the supplied suborigin.", |
| 177 securityOrigin); | 175 securityOrigin); |
| 178 return false; | 176 return false; |
| 179 } | 177 } |
| 180 } | 178 } |
| 181 | 179 |
| 182 if (allowOriginHeaderValue == starAtom) { | 180 if (allowOriginHeaderValue == starAtom) { |
| 183 // A wildcard Access-Control-Allow-Origin can not be used if credentials are
to be sent, | 181 // A wildcard Access-Control-Allow-Origin can not be used if credentials are |
| 184 // even with Access-Control-Allow-Credentials set to true. | 182 // to be sent, even with Access-Control-Allow-Credentials set to true. |
| 185 if (includeCredentials == DoNotAllowStoredCredentials) | 183 if (includeCredentials == DoNotAllowStoredCredentials) |
| 186 return true; | 184 return true; |
| 187 if (response.isHTTP()) { | 185 if (response.isHTTP()) { |
| 188 errorDescription = buildAccessControlFailureMessage( | 186 errorDescription = buildAccessControlFailureMessage( |
| 189 "A wildcard '*' cannot be used in the 'Access-Control-Allow-Origin' " | 187 "A wildcard '*' cannot be used in the 'Access-Control-Allow-Origin' " |
| 190 "header when the credentials flag is true.", | 188 "header when the credentials flag is true.", |
| 191 securityOrigin); | 189 securityOrigin); |
| 192 | 190 |
| 193 if (context == WebURLRequest::RequestContextXMLHttpRequest) | 191 if (context == WebURLRequest::RequestContextXMLHttpRequest) |
| 194 errorDescription.append( | 192 errorDescription.append( |
| (...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 259 return false; | 257 return false; |
| 260 } | 258 } |
| 261 } | 259 } |
| 262 | 260 |
| 263 return true; | 261 return true; |
| 264 } | 262 } |
| 265 | 263 |
| 266 bool passesPreflightStatusCheck(const ResourceResponse& response, | 264 bool passesPreflightStatusCheck(const ResourceResponse& response, |
| 267 String& errorDescription) { | 265 String& errorDescription) { |
| 268 // CORS preflight with 3XX is considered network error in | 266 // CORS preflight with 3XX is considered network error in |
| 269 // Fetch API Spec: | 267 // Fetch API Spec: https://fetch.spec.whatwg.org/#cors-preflight-fetch |
| 270 // https://fetch.spec.whatwg.org/#cors-preflight-fetch | 268 // CORS Spec: http://www.w3.org/TR/cors/#cross-origin-request-with-preflight-0 |
| 271 // CORS Spec: | |
| 272 // http://www.w3.org/TR/cors/#cross-origin-request-with-preflight-0 | |
| 273 // https://crbug.com/452394 | 269 // https://crbug.com/452394 |
| 274 if (response.httpStatusCode() < 200 || response.httpStatusCode() >= 300) { | 270 if (response.httpStatusCode() < 200 || response.httpStatusCode() >= 300) { |
| 275 errorDescription = "Response for preflight has invalid HTTP status code " + | 271 errorDescription = "Response for preflight has invalid HTTP status code " + |
| 276 String::number(response.httpStatusCode()); | 272 String::number(response.httpStatusCode()); |
| 277 return false; | 273 return false; |
| 278 } | 274 } |
| 279 | 275 |
| 280 return true; | 276 return true; |
| 281 } | 277 } |
| 282 | 278 |
| (...skipping 29 matching lines...) Expand all Loading... |
| 312 String strippedHeader = headers[headerCount].stripWhiteSpace(); | 308 String strippedHeader = headers[headerCount].stripWhiteSpace(); |
| 313 if (!strippedHeader.isEmpty()) | 309 if (!strippedHeader.isEmpty()) |
| 314 headerSet.add(strippedHeader); | 310 headerSet.add(strippedHeader); |
| 315 } | 311 } |
| 316 } | 312 } |
| 317 | 313 |
| 318 void extractCorsExposedHeaderNamesList(const ResourceResponse& response, | 314 void extractCorsExposedHeaderNamesList(const ResourceResponse& response, |
| 319 HTTPHeaderSet& headerSet) { | 315 HTTPHeaderSet& headerSet) { |
| 320 // If a response was fetched via a service worker, it will always have | 316 // If a response was fetched via a service worker, it will always have |
| 321 // corsExposedHeaderNames set, either from the Access-Control-Expose-Headers | 317 // corsExposedHeaderNames set, either from the Access-Control-Expose-Headers |
| 322 // header, or explicitly via foreign fetch. For requests that didn't come | 318 // header, or explicitly via foreign fetch. For requests that didn't come from |
| 323 // from a service worker, foreign fetch doesn't apply so just parse the CORS | 319 // a service worker, foreign fetch doesn't apply so just parse the CORS |
| 324 // header. | 320 // header. |
| 325 if (response.wasFetchedViaServiceWorker()) { | 321 if (response.wasFetchedViaServiceWorker()) { |
| 326 for (const auto& header : response.corsExposedHeaderNames()) | 322 for (const auto& header : response.corsExposedHeaderNames()) |
| 327 headerSet.add(header); | 323 headerSet.add(header); |
| 328 return; | 324 return; |
| 329 } | 325 } |
| 330 parseAccessControlExposeHeadersAllowList( | 326 parseAccessControlExposeHeadersAllowList( |
| 331 response.httpHeaderField(HTTPNames::Access_Control_Expose_Headers), | 327 response.httpHeaderField(HTTPNames::Access_Control_Expose_Headers), |
| 332 headerSet); | 328 headerSet); |
| 333 } | 329 } |
| 334 | 330 |
| 335 bool CrossOriginAccessControl::isLegalRedirectLocation( | 331 bool CrossOriginAccessControl::isLegalRedirectLocation( |
| 336 const KURL& requestURL, | 332 const KURL& requestURL, |
| 337 String& errorDescription) { | 333 String& errorDescription) { |
| 338 // Block non HTTP(S) schemes as specified in the step 4 in | 334 // Block non HTTP(S) schemes as specified in the step 4 in |
| 339 // https://fetch.spec.whatwg.org/#http-redirect-fetch. Chromium also allows | 335 // https://fetch.spec.whatwg.org/#http-redirect-fetch. Chromium also allows |
| 340 // the data scheme. | 336 // the data scheme. |
| 341 // | 337 // |
| 342 // TODO(tyoshino): This check should be performed regardless of the CORS | 338 // TODO(tyoshino): This check should be performed regardless of the CORS flag |
| 343 // flag and request's mode. | 339 // and request's mode. |
| 344 if (!SchemeRegistry::shouldTreatURLSchemeAsCORSEnabled( | 340 if (!SchemeRegistry::shouldTreatURLSchemeAsCORSEnabled( |
| 345 requestURL.protocol())) { | 341 requestURL.protocol())) { |
| 346 errorDescription = "Redirect location '" + requestURL.getString() + | 342 errorDescription = "Redirect location '" + requestURL.getString() + |
| 347 "' has a disallowed scheme for cross-origin requests."; | 343 "' has a disallowed scheme for cross-origin requests."; |
| 348 return false; | 344 return false; |
| 349 } | 345 } |
| 350 | 346 |
| 351 // Block URLs including credentials as specified in the step 9 in | 347 // Block URLs including credentials as specified in the step 9 in |
| 352 // https://fetch.spec.whatwg.org/#http-redirect-fetch. | 348 // https://fetch.spec.whatwg.org/#http-redirect-fetch. |
| 353 // | 349 // |
| (...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 405 if (!lastOrigin->canRequest(newURL)) { | 401 if (!lastOrigin->canRequest(newURL)) { |
| 406 options.securityOrigin = SecurityOrigin::createUnique(); | 402 options.securityOrigin = SecurityOrigin::createUnique(); |
| 407 newSecurityOrigin = options.securityOrigin; | 403 newSecurityOrigin = options.securityOrigin; |
| 408 } | 404 } |
| 409 } | 405 } |
| 410 | 406 |
| 411 if (!currentSecurityOrigin->canRequest(newURL)) { | 407 if (!currentSecurityOrigin->canRequest(newURL)) { |
| 412 newRequest.clearHTTPOrigin(); | 408 newRequest.clearHTTPOrigin(); |
| 413 newRequest.setHTTPOrigin(newSecurityOrigin.get()); | 409 newRequest.setHTTPOrigin(newSecurityOrigin.get()); |
| 414 | 410 |
| 415 // Unset credentials flag if request's credentials mode is | 411 // Unset credentials flag if request's credentials mode is "same-origin" as |
| 416 // "same-origin" as request's response tainting becomes "cors". | 412 // request's response tainting becomes "cors". |
| 417 // | 413 // |
| 418 // This is equivalent to the step 2 in | 414 // This is equivalent to the step 2 in |
| 419 // https://fetch.spec.whatwg.org/#http-network-or-cache-fetch | 415 // https://fetch.spec.whatwg.org/#http-network-or-cache-fetch |
| 420 if (options.credentialsRequested == ClientDidNotRequestCredentials) | 416 if (options.credentialsRequested == ClientDidNotRequestCredentials) |
| 421 options.allowCredentials = DoNotAllowStoredCredentials; | 417 options.allowCredentials = DoNotAllowStoredCredentials; |
| 422 } | 418 } |
| 423 return true; | 419 return true; |
| 424 } | 420 } |
| 425 | 421 |
| 426 } // namespace blink | 422 } // namespace blink |
| OLD | NEW |