| OLD | NEW |
| (Empty) |
| 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 "core/fetch/CrossOriginAccessControl.h" | |
| 28 | |
| 29 #include "core/fetch/FetchUtils.h" | |
| 30 #include "core/fetch/Resource.h" | |
| 31 #include "core/fetch/ResourceLoaderOptions.h" | |
| 32 #include "platform/network/HTTPParsers.h" | |
| 33 #include "platform/network/ResourceRequest.h" | |
| 34 #include "platform/network/ResourceResponse.h" | |
| 35 #include "platform/weborigin/SchemeRegistry.h" | |
| 36 #include "platform/weborigin/SecurityOrigin.h" | |
| 37 #include "wtf/PtrUtil.h" | |
| 38 #include "wtf/Threading.h" | |
| 39 #include "wtf/text/AtomicString.h" | |
| 40 #include "wtf/text/StringBuilder.h" | |
| 41 #include <algorithm> | |
| 42 #include <memory> | |
| 43 | |
| 44 namespace blink { | |
| 45 | |
| 46 bool isOnAccessControlResponseHeaderWhitelist(const String& name) { | |
| 47 DEFINE_THREAD_SAFE_STATIC_LOCAL( | |
| 48 HTTPHeaderSet, allowedCrossOriginResponseHeaders, | |
| 49 (new HTTPHeaderSet({ | |
| 50 "cache-control", "content-language", "content-type", "expires", | |
| 51 "last-modified", "pragma", | |
| 52 }))); | |
| 53 return allowedCrossOriginResponseHeaders.contains(name); | |
| 54 } | |
| 55 | |
| 56 // Fetch API Spec: https://fetch.spec.whatwg.org/#cors-preflight-fetch-0 | |
| 57 static AtomicString createAccessControlRequestHeadersHeader( | |
| 58 const HTTPHeaderMap& headers) { | |
| 59 Vector<String> filteredHeaders; | |
| 60 for (const auto& header : headers) { | |
| 61 if (FetchUtils::isSimpleHeader(header.key, header.value)) { | |
| 62 // Exclude simple headers. | |
| 63 continue; | |
| 64 } | |
| 65 if (equalIgnoringCase(header.key, "referer")) { | |
| 66 // When the request is from a Worker, referrer header was added by | |
| 67 // WorkerThreadableLoader. But it should not be added to | |
| 68 // Access-Control-Request-Headers header. | |
| 69 continue; | |
| 70 } | |
| 71 filteredHeaders.push_back(header.key.lower()); | |
| 72 } | |
| 73 if (!filteredHeaders.size()) | |
| 74 return nullAtom; | |
| 75 | |
| 76 // Sort header names lexicographically. | |
| 77 std::sort(filteredHeaders.begin(), filteredHeaders.end(), | |
| 78 WTF::codePointCompareLessThan); | |
| 79 StringBuilder headerBuffer; | |
| 80 for (const String& header : filteredHeaders) { | |
| 81 if (!headerBuffer.isEmpty()) | |
| 82 headerBuffer.append(","); | |
| 83 headerBuffer.append(header); | |
| 84 } | |
| 85 | |
| 86 return AtomicString(headerBuffer.toString()); | |
| 87 } | |
| 88 | |
| 89 ResourceRequest createAccessControlPreflightRequest( | |
| 90 const ResourceRequest& request) { | |
| 91 const KURL& requestURL = request.url(); | |
| 92 | |
| 93 DCHECK(requestURL.user().isEmpty()); | |
| 94 DCHECK(requestURL.pass().isEmpty()); | |
| 95 | |
| 96 ResourceRequest preflightRequest(requestURL); | |
| 97 preflightRequest.setAllowStoredCredentials(false); | |
| 98 preflightRequest.setHTTPMethod(HTTPNames::OPTIONS); | |
| 99 preflightRequest.setHTTPHeaderField(HTTPNames::Access_Control_Request_Method, | |
| 100 AtomicString(request.httpMethod())); | |
| 101 preflightRequest.setPriority(request.priority()); | |
| 102 preflightRequest.setRequestContext(request.requestContext()); | |
| 103 preflightRequest.setSkipServiceWorker(WebURLRequest::SkipServiceWorker::All); | |
| 104 | |
| 105 if (request.isExternalRequest()) { | |
| 106 preflightRequest.setHTTPHeaderField( | |
| 107 HTTPNames::Access_Control_Request_External, "true"); | |
| 108 } | |
| 109 | |
| 110 AtomicString requestHeaders = | |
| 111 createAccessControlRequestHeadersHeader(request.httpHeaderFields()); | |
| 112 if (requestHeaders != nullAtom) { | |
| 113 preflightRequest.setHTTPHeaderField( | |
| 114 HTTPNames::Access_Control_Request_Headers, requestHeaders); | |
| 115 } | |
| 116 | |
| 117 return preflightRequest; | |
| 118 } | |
| 119 | |
| 120 static bool isOriginSeparator(UChar ch) { | |
| 121 return isASCIISpace(ch) || ch == ','; | |
| 122 } | |
| 123 | |
| 124 static bool isInterestingStatusCode(int statusCode) { | |
| 125 // Predicate that gates what status codes should be included in console error | |
| 126 // messages for responses containing no access control headers. | |
| 127 return statusCode >= 400; | |
| 128 } | |
| 129 | |
| 130 static void appendOriginDeniedMessage(StringBuilder& builder, | |
| 131 const SecurityOrigin* securityOrigin) { | |
| 132 builder.append(" Origin '"); | |
| 133 builder.append(securityOrigin->toString()); | |
| 134 builder.append("' is therefore not allowed access."); | |
| 135 } | |
| 136 | |
| 137 static void appendNoCORSInformationalMessage( | |
| 138 StringBuilder& builder, | |
| 139 WebURLRequest::RequestContext context) { | |
| 140 if (context != WebURLRequest::RequestContextFetch) | |
| 141 return; | |
| 142 builder.append( | |
| 143 " Have the server send the header with a valid value, or, if an " | |
| 144 "opaque response serves your needs, set the request's mode to " | |
| 145 "'no-cors' to fetch the resource with CORS disabled."); | |
| 146 } | |
| 147 | |
| 148 CrossOriginAccessControl::AccessStatus CrossOriginAccessControl::checkAccess( | |
| 149 const ResourceResponse& response, | |
| 150 StoredCredentials includeCredentials, | |
| 151 const SecurityOrigin* securityOrigin) { | |
| 152 DEFINE_THREAD_SAFE_STATIC_LOCAL( | |
| 153 AtomicString, allowOriginHeaderName, | |
| 154 (new AtomicString("access-control-allow-origin"))); | |
| 155 DEFINE_THREAD_SAFE_STATIC_LOCAL( | |
| 156 AtomicString, allowCredentialsHeaderName, | |
| 157 (new AtomicString("access-control-allow-credentials"))); | |
| 158 DEFINE_THREAD_SAFE_STATIC_LOCAL( | |
| 159 AtomicString, allowSuboriginHeaderName, | |
| 160 (new AtomicString("access-control-allow-suborigin"))); | |
| 161 | |
| 162 int statusCode = response.httpStatusCode(); | |
| 163 if (!statusCode) | |
| 164 return kInvalidResponse; | |
| 165 | |
| 166 const AtomicString& allowOriginHeaderValue = | |
| 167 response.httpHeaderField(allowOriginHeaderName); | |
| 168 | |
| 169 // Check Suborigins, unless the Access-Control-Allow-Origin is '*', which | |
| 170 // implies that all Suborigins are okay as well. | |
| 171 if (securityOrigin->hasSuborigin() && allowOriginHeaderValue != starAtom) { | |
| 172 const AtomicString& allowSuboriginHeaderValue = | |
| 173 response.httpHeaderField(allowSuboriginHeaderName); | |
| 174 AtomicString atomicSuboriginName(securityOrigin->suborigin()->name()); | |
| 175 if (allowSuboriginHeaderValue != starAtom && | |
| 176 allowSuboriginHeaderValue != atomicSuboriginName) { | |
| 177 return kSubOriginMismatch; | |
| 178 } | |
| 179 } | |
| 180 | |
| 181 if (allowOriginHeaderValue == starAtom) { | |
| 182 // A wildcard Access-Control-Allow-Origin can not be used if credentials are | |
| 183 // to be sent, even with Access-Control-Allow-Credentials set to true. | |
| 184 if (includeCredentials == DoNotAllowStoredCredentials) | |
| 185 return kAccessAllowed; | |
| 186 if (response.isHTTP()) { | |
| 187 return kWildcardOriginNotAllowed; | |
| 188 } | |
| 189 } else if (allowOriginHeaderValue != securityOrigin->toAtomicString()) { | |
| 190 if (allowOriginHeaderValue.isNull()) | |
| 191 return kMissingAllowOriginHeader; | |
| 192 if (allowOriginHeaderValue.getString().find(isOriginSeparator, 0) != | |
| 193 kNotFound) { | |
| 194 return kMultipleAllowOriginValues; | |
| 195 } | |
| 196 KURL headerOrigin(KURL(), allowOriginHeaderValue); | |
| 197 if (!headerOrigin.isValid()) | |
| 198 return kInvalidAllowOriginValue; | |
| 199 | |
| 200 return kAllowOriginMismatch; | |
| 201 } | |
| 202 | |
| 203 if (includeCredentials == AllowStoredCredentials) { | |
| 204 const AtomicString& allowCredentialsHeaderValue = | |
| 205 response.httpHeaderField(allowCredentialsHeaderName); | |
| 206 if (allowCredentialsHeaderValue != "true") { | |
| 207 return kDisallowCredentialsNotSetToTrue; | |
| 208 } | |
| 209 } | |
| 210 return kAccessAllowed; | |
| 211 } | |
| 212 | |
| 213 void CrossOriginAccessControl::accessControlErrorString( | |
| 214 StringBuilder& builder, | |
| 215 CrossOriginAccessControl::AccessStatus status, | |
| 216 const ResourceResponse& response, | |
| 217 const SecurityOrigin* securityOrigin, | |
| 218 WebURLRequest::RequestContext context) { | |
| 219 DEFINE_THREAD_SAFE_STATIC_LOCAL( | |
| 220 AtomicString, allowOriginHeaderName, | |
| 221 (new AtomicString("access-control-allow-origin"))); | |
| 222 DEFINE_THREAD_SAFE_STATIC_LOCAL( | |
| 223 AtomicString, allowCredentialsHeaderName, | |
| 224 (new AtomicString("access-control-allow-credentials"))); | |
| 225 DEFINE_THREAD_SAFE_STATIC_LOCAL( | |
| 226 AtomicString, allowSuboriginHeaderName, | |
| 227 (new AtomicString("access-control-allow-suborigin"))); | |
| 228 | |
| 229 switch (status) { | |
| 230 case kInvalidResponse: { | |
| 231 builder.append("Invalid response."); | |
| 232 appendOriginDeniedMessage(builder, securityOrigin); | |
| 233 return; | |
| 234 } | |
| 235 case kSubOriginMismatch: { | |
| 236 const AtomicString& allowSuboriginHeaderValue = | |
| 237 response.httpHeaderField(allowSuboriginHeaderName); | |
| 238 builder.append( | |
| 239 "The 'Access-Control-Allow-Suborigin' header has a value '"); | |
| 240 builder.append(allowSuboriginHeaderValue); | |
| 241 builder.append("' that is not equal to the supplied suborigin."); | |
| 242 appendOriginDeniedMessage(builder, securityOrigin); | |
| 243 return; | |
| 244 } | |
| 245 case kWildcardOriginNotAllowed: { | |
| 246 builder.append( | |
| 247 "The value of the 'Access-Control-Allow-Origin' header in the " | |
| 248 "response must not be the wildcard '*' when the request's " | |
| 249 "credentials mode is 'include'."); | |
| 250 appendOriginDeniedMessage(builder, securityOrigin); | |
| 251 if (context == WebURLRequest::RequestContextXMLHttpRequest) { | |
| 252 builder.append( | |
| 253 " The credentials mode of requests initiated by the " | |
| 254 "XMLHttpRequest is controlled by the withCredentials attribute."); | |
| 255 } | |
| 256 return; | |
| 257 } | |
| 258 case kMissingAllowOriginHeader: { | |
| 259 builder.append( | |
| 260 "No 'Access-Control-Allow-Origin' header is present on the requested " | |
| 261 "resource."); | |
| 262 appendOriginDeniedMessage(builder, securityOrigin); | |
| 263 int statusCode = response.httpStatusCode(); | |
| 264 if (isInterestingStatusCode(statusCode)) { | |
| 265 builder.append(" The response had HTTP status code "); | |
| 266 builder.append(String::number(statusCode)); | |
| 267 builder.append('.'); | |
| 268 } | |
| 269 if (context == WebURLRequest::RequestContextFetch) { | |
| 270 builder.append( | |
| 271 " If an opaque response serves your needs, set the request's mode " | |
| 272 "to 'no-cors' to fetch the resource with CORS disabled."); | |
| 273 } | |
| 274 return; | |
| 275 } | |
| 276 case kMultipleAllowOriginValues: { | |
| 277 const AtomicString& allowOriginHeaderValue = | |
| 278 response.httpHeaderField(allowOriginHeaderName); | |
| 279 builder.append( | |
| 280 "The 'Access-Control-Allow-Origin' header contains multiple values " | |
| 281 "'"); | |
| 282 builder.append(allowOriginHeaderValue); | |
| 283 builder.append("', but only one is allowed."); | |
| 284 appendOriginDeniedMessage(builder, securityOrigin); | |
| 285 appendNoCORSInformationalMessage(builder, context); | |
| 286 return; | |
| 287 } | |
| 288 case kInvalidAllowOriginValue: { | |
| 289 const AtomicString& allowOriginHeaderValue = | |
| 290 response.httpHeaderField(allowOriginHeaderName); | |
| 291 builder.append( | |
| 292 "The 'Access-Control-Allow-Origin' header contains the invalid " | |
| 293 "value '"); | |
| 294 builder.append(allowOriginHeaderValue); | |
| 295 builder.append("'."); | |
| 296 appendOriginDeniedMessage(builder, securityOrigin); | |
| 297 appendNoCORSInformationalMessage(builder, context); | |
| 298 return; | |
| 299 } | |
| 300 case kAllowOriginMismatch: { | |
| 301 const AtomicString& allowOriginHeaderValue = | |
| 302 response.httpHeaderField(allowOriginHeaderName); | |
| 303 builder.append("The 'Access-Control-Allow-Origin' header has a value '"); | |
| 304 builder.append(allowOriginHeaderValue); | |
| 305 builder.append("' that is not equal to the supplied origin."); | |
| 306 appendOriginDeniedMessage(builder, securityOrigin); | |
| 307 appendNoCORSInformationalMessage(builder, context); | |
| 308 return; | |
| 309 } | |
| 310 case kDisallowCredentialsNotSetToTrue: { | |
| 311 const AtomicString& allowCredentialsHeaderValue = | |
| 312 response.httpHeaderField(allowCredentialsHeaderName); | |
| 313 builder.append( | |
| 314 "The value of the 'Access-Control-Allow-Credentials' header in " | |
| 315 "the response is '"); | |
| 316 builder.append(allowCredentialsHeaderValue); | |
| 317 builder.append( | |
| 318 "' which must " | |
| 319 "be 'true' when the request's credentials mode is 'include'."); | |
| 320 appendOriginDeniedMessage(builder, securityOrigin); | |
| 321 if (context == WebURLRequest::RequestContextXMLHttpRequest) { | |
| 322 builder.append( | |
| 323 " The credentials mode of requests initiated by the " | |
| 324 "XMLHttpRequest is controlled by the withCredentials attribute."); | |
| 325 } | |
| 326 return; | |
| 327 } | |
| 328 default: | |
| 329 NOTREACHED(); | |
| 330 } | |
| 331 } | |
| 332 | |
| 333 CrossOriginAccessControl::PreflightStatus | |
| 334 CrossOriginAccessControl::checkPreflight(const ResourceResponse& response) { | |
| 335 // CORS preflight with 3XX is considered network error in | |
| 336 // Fetch API Spec: https://fetch.spec.whatwg.org/#cors-preflight-fetch | |
| 337 // CORS Spec: http://www.w3.org/TR/cors/#cross-origin-request-with-preflight-0 | |
| 338 // https://crbug.com/452394 | |
| 339 int statusCode = response.httpStatusCode(); | |
| 340 if (!FetchUtils::isOkStatus(statusCode)) | |
| 341 return kPreflightInvalidStatus; | |
| 342 | |
| 343 return kPreflightSuccess; | |
| 344 } | |
| 345 | |
| 346 CrossOriginAccessControl::PreflightStatus | |
| 347 CrossOriginAccessControl::checkExternalPreflight( | |
| 348 const ResourceResponse& response) { | |
| 349 AtomicString result = | |
| 350 response.httpHeaderField(HTTPNames::Access_Control_Allow_External); | |
| 351 if (result.isNull()) | |
| 352 return kPreflightMissingAllowExternal; | |
| 353 if (!equalIgnoringCase(result, "true")) | |
| 354 return kPreflightInvalidAllowExternal; | |
| 355 return kPreflightSuccess; | |
| 356 } | |
| 357 | |
| 358 void CrossOriginAccessControl::preflightErrorString( | |
| 359 StringBuilder& builder, | |
| 360 CrossOriginAccessControl::PreflightStatus status, | |
| 361 const ResourceResponse& response) { | |
| 362 switch (status) { | |
| 363 case kPreflightInvalidStatus: { | |
| 364 int statusCode = response.httpStatusCode(); | |
| 365 builder.append("Response for preflight has invalid HTTP status code "); | |
| 366 builder.append(String::number(statusCode)); | |
| 367 return; | |
| 368 } | |
| 369 case kPreflightMissingAllowExternal: { | |
| 370 builder.append( | |
| 371 "No 'Access-Control-Allow-External' header was present in "); | |
| 372 builder.append( | |
| 373 "the preflight response for this external request (This is"); | |
| 374 builder.append(" an experimental header which is defined in "); | |
| 375 builder.append("'https://wicg.github.io/cors-rfc1918/')."); | |
| 376 return; | |
| 377 } | |
| 378 case kPreflightInvalidAllowExternal: { | |
| 379 String result = | |
| 380 response.httpHeaderField(HTTPNames::Access_Control_Allow_External); | |
| 381 builder.append("The 'Access-Control-Allow-External' header in the "); | |
| 382 builder.append( | |
| 383 "preflight response for this external request had a value"); | |
| 384 builder.append(" of '"); | |
| 385 builder.append(result); | |
| 386 builder.append("', not 'true' (This is an experimental header which is"); | |
| 387 builder.append(" defined in 'https://wicg.github.io/cors-rfc1918/')."); | |
| 388 return; | |
| 389 } | |
| 390 default: | |
| 391 NOTREACHED(); | |
| 392 } | |
| 393 } | |
| 394 | |
| 395 void parseAccessControlExposeHeadersAllowList(const String& headerValue, | |
| 396 HTTPHeaderSet& headerSet) { | |
| 397 Vector<String> headers; | |
| 398 headerValue.split(',', false, headers); | |
| 399 for (unsigned headerCount = 0; headerCount < headers.size(); headerCount++) { | |
| 400 String strippedHeader = headers[headerCount].stripWhiteSpace(); | |
| 401 if (!strippedHeader.isEmpty()) | |
| 402 headerSet.add(strippedHeader); | |
| 403 } | |
| 404 } | |
| 405 | |
| 406 void extractCorsExposedHeaderNamesList(const ResourceResponse& response, | |
| 407 HTTPHeaderSet& headerSet) { | |
| 408 // If a response was fetched via a service worker, it will always have | |
| 409 // corsExposedHeaderNames set, either from the Access-Control-Expose-Headers | |
| 410 // header, or explicitly via foreign fetch. For requests that didn't come from | |
| 411 // a service worker, foreign fetch doesn't apply so just parse the CORS | |
| 412 // header. | |
| 413 if (response.wasFetchedViaServiceWorker()) { | |
| 414 for (const auto& header : response.corsExposedHeaderNames()) | |
| 415 headerSet.add(header); | |
| 416 return; | |
| 417 } | |
| 418 parseAccessControlExposeHeadersAllowList( | |
| 419 response.httpHeaderField(HTTPNames::Access_Control_Expose_Headers), | |
| 420 headerSet); | |
| 421 } | |
| 422 | |
| 423 CrossOriginAccessControl::RedirectStatus | |
| 424 CrossOriginAccessControl::checkRedirectLocation(const KURL& requestURL) { | |
| 425 // Block non HTTP(S) schemes as specified in the step 4 in | |
| 426 // https://fetch.spec.whatwg.org/#http-redirect-fetch. Chromium also allows | |
| 427 // the data scheme. | |
| 428 // | |
| 429 // TODO(tyoshino): This check should be performed regardless of the CORS flag | |
| 430 // and request's mode. | |
| 431 if (!SchemeRegistry::shouldTreatURLSchemeAsCORSEnabled(requestURL.protocol())) | |
| 432 return kRedirectDisallowedScheme; | |
| 433 | |
| 434 // Block URLs including credentials as specified in the step 9 in | |
| 435 // https://fetch.spec.whatwg.org/#http-redirect-fetch. | |
| 436 // | |
| 437 // TODO(tyoshino): This check should be performed also when request's | |
| 438 // origin is not same origin with the redirect destination's origin. | |
| 439 if (!(requestURL.user().isEmpty() && requestURL.pass().isEmpty())) | |
| 440 return kRedirectContainsCredentials; | |
| 441 | |
| 442 return kRedirectSuccess; | |
| 443 } | |
| 444 | |
| 445 void CrossOriginAccessControl::redirectErrorString( | |
| 446 StringBuilder& builder, | |
| 447 CrossOriginAccessControl::RedirectStatus status, | |
| 448 const KURL& requestURL) { | |
| 449 switch (status) { | |
| 450 case kRedirectDisallowedScheme: { | |
| 451 builder.append("Redirect location '"); | |
| 452 builder.append(requestURL.getString()); | |
| 453 builder.append("' has a disallowed scheme for cross-origin requests."); | |
| 454 return; | |
| 455 } | |
| 456 case kRedirectContainsCredentials: { | |
| 457 builder.append("Redirect location '"); | |
| 458 builder.append(requestURL.getString()); | |
| 459 builder.append( | |
| 460 "' contains a username and password, which is disallowed for" | |
| 461 " cross-origin requests."); | |
| 462 return; | |
| 463 } | |
| 464 default: | |
| 465 NOTREACHED(); | |
| 466 } | |
| 467 } | |
| 468 | |
| 469 bool CrossOriginAccessControl::handleRedirect( | |
| 470 PassRefPtr<SecurityOrigin> securityOrigin, | |
| 471 ResourceRequest& newRequest, | |
| 472 const ResourceResponse& redirectResponse, | |
| 473 StoredCredentials withCredentials, | |
| 474 ResourceLoaderOptions& options, | |
| 475 String& errorMessage) { | |
| 476 // http://www.w3.org/TR/cors/#redirect-steps terminology: | |
| 477 const KURL& lastURL = redirectResponse.url(); | |
| 478 const KURL& newURL = newRequest.url(); | |
| 479 | |
| 480 RefPtr<SecurityOrigin> currentSecurityOrigin = securityOrigin; | |
| 481 | |
| 482 RefPtr<SecurityOrigin> newSecurityOrigin = currentSecurityOrigin; | |
| 483 | |
| 484 // TODO(tyoshino): This should be fixed to check not only the last one but | |
| 485 // all redirect responses. | |
| 486 if (!currentSecurityOrigin->canRequest(lastURL)) { | |
| 487 // Follow http://www.w3.org/TR/cors/#redirect-steps | |
| 488 CrossOriginAccessControl::RedirectStatus redirectStatus = | |
| 489 CrossOriginAccessControl::checkRedirectLocation(newURL); | |
| 490 if (redirectStatus != kRedirectSuccess) { | |
| 491 StringBuilder builder; | |
| 492 builder.append("Redirect from '"); | |
| 493 builder.append(lastURL.getString()); | |
| 494 builder.append("' has been blocked by CORS policy: "); | |
| 495 CrossOriginAccessControl::redirectErrorString(builder, redirectStatus, | |
| 496 newURL); | |
| 497 errorMessage = builder.toString(); | |
| 498 return false; | |
| 499 } | |
| 500 | |
| 501 // Step 5: perform resource sharing access check. | |
| 502 CrossOriginAccessControl::AccessStatus corsStatus = | |
| 503 CrossOriginAccessControl::checkAccess(redirectResponse, withCredentials, | |
| 504 currentSecurityOrigin.get()); | |
| 505 if (corsStatus != kAccessAllowed) { | |
| 506 StringBuilder builder; | |
| 507 builder.append("Redirect from '"); | |
| 508 builder.append(lastURL.getString()); | |
| 509 builder.append("' has been blocked by CORS policy: "); | |
| 510 CrossOriginAccessControl::accessControlErrorString( | |
| 511 builder, corsStatus, redirectResponse, currentSecurityOrigin.get(), | |
| 512 newRequest.requestContext()); | |
| 513 errorMessage = builder.toString(); | |
| 514 return false; | |
| 515 } | |
| 516 | |
| 517 RefPtr<SecurityOrigin> lastOrigin = SecurityOrigin::create(lastURL); | |
| 518 // Set request's origin to a globally unique identifier as specified in | |
| 519 // the step 10 in https://fetch.spec.whatwg.org/#http-redirect-fetch. | |
| 520 if (!lastOrigin->canRequest(newURL)) { | |
| 521 options.securityOrigin = SecurityOrigin::createUnique(); | |
| 522 newSecurityOrigin = options.securityOrigin; | |
| 523 } | |
| 524 } | |
| 525 | |
| 526 if (!currentSecurityOrigin->canRequest(newURL)) { | |
| 527 newRequest.clearHTTPOrigin(); | |
| 528 newRequest.setHTTPOrigin(newSecurityOrigin.get()); | |
| 529 | |
| 530 // Unset credentials flag if request's credentials mode is "same-origin" as | |
| 531 // request's response tainting becomes "cors". | |
| 532 // | |
| 533 // This is equivalent to the step 2 in | |
| 534 // https://fetch.spec.whatwg.org/#http-network-or-cache-fetch | |
| 535 if (options.credentialsRequested == ClientDidNotRequestCredentials) | |
| 536 options.allowCredentials = DoNotAllowStoredCredentials; | |
| 537 } | |
| 538 return true; | |
| 539 } | |
| 540 | |
| 541 } // namespace blink | |
| OLD | NEW |