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