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