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