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 |