diff --git a/components/net/http_loader.rs b/components/net/http_loader.rs index 3d5bfe9612e..4b42355b067 100644 --- a/components/net/http_loader.rs +++ b/components/net/http_loader.rs @@ -26,7 +26,7 @@ use headers::{AccessControlAllowOrigin, AccessControlMaxAge}; use headers::{CacheControl, ContentEncoding, ContentLength}; use headers::{IfModifiedSince, LastModified, Origin as HyperOrigin, Pragma, Referer, UserAgent}; use http::header::{ - self, HeaderName, HeaderValue, CONTENT_ENCODING, CONTENT_LANGUAGE, CONTENT_LOCATION, + self, HeaderName, HeaderValue, ACCEPT, CONTENT_ENCODING, CONTENT_LANGUAGE, CONTENT_LOCATION, CONTENT_TYPE, }; use http::{HeaderMap, Request as HyperRequest}; @@ -39,7 +39,10 @@ use msg::constellation_msg::{HistoryStateId, PipelineId}; use net_traits::pub_domains::reg_suffix; use net_traits::quality::{quality_to_value, Quality, QualityItem}; use net_traits::request::Origin::Origin as SpecificOrigin; -use net_traits::request::{is_cors_safelisted_method, is_cors_safelisted_request_header}; +use net_traits::request::{ + get_cors_unsafe_header_names, is_cors_non_wildcard_request_header_name, + is_cors_safelisted_method, is_cors_safelisted_request_header, +}; use net_traits::request::{ BodyChunkRequest, BodyChunkResponse, RedirectMode, Referrer, Request, RequestBuilder, RequestMode, @@ -57,7 +60,6 @@ use std::collections::{HashMap, HashSet}; use std::iter::FromIterator; use std::mem; use std::ops::Deref; -use std::str::FromStr; use std::sync::{Arc as StdArc, Condvar, Mutex, RwLock}; use std::time::{Duration, SystemTime}; use time::{self, Tm}; @@ -1966,53 +1968,48 @@ fn cors_preflight_fetch( .initiator(request.initiator.clone()) .destination(request.destination.clone()) .referrer_policy(request.referrer_policy) + .mode(RequestMode::CorsMode) .build(); // Step 2 + preflight + .headers + .insert(ACCEPT, HeaderValue::from_static("*/*")); + + // Step 3 preflight .headers .typed_insert::(AccessControlRequestMethod::from( request.method.clone(), )); - // Step 3 - let mut headers = request - .headers - .iter() - .filter(|(name, value)| !is_cors_safelisted_request_header(&name, &value)) - .map(|(name, _)| name.as_str()) - .collect::>(); - headers.sort(); - let headers = headers - .iter() - .filter_map(|name| HeaderName::from_str(name).ok()) - .collect::>(); - // Step 4 + let headers = get_cors_unsafe_header_names(&request.headers); + + // Step 5 if !headers.is_empty() { preflight .headers .typed_insert(AccessControlRequestHeaders::from_iter(headers)); } - // Step 5 - let response = http_network_or_cache_fetch(&mut preflight, false, false, &mut None, context); - // Step 6 + let response = http_network_or_cache_fetch(&mut preflight, false, false, &mut None, context); + // Step 7 if cors_check(&request, &response).is_ok() && response .status .as_ref() .map_or(false, |(status, _)| status.is_success()) { - // Substep 1, 2 + // Substep 1 let mut methods = if response .headers .contains_key(header::ACCESS_CONTROL_ALLOW_METHODS) { match response.headers.typed_get::() { Some(methods) => methods.iter().collect(), - // Substep 4 + // Substep 3 None => { return Response::network_error(NetworkError::Internal( "CORS ACAM check failed".into(), @@ -2023,14 +2020,14 @@ fn cors_preflight_fetch( vec![] }; - // Substep 3 + // Substep 2 let header_names = if response .headers .contains_key(header::ACCESS_CONTROL_ALLOW_HEADERS) { match response.headers.typed_get::() { Some(names) => names.iter().collect(), - // Substep 4 + // Substep 3 None => { return Response::network_error(NetworkError::Internal( "CORS ACAH check failed".into(), @@ -2041,85 +2038,86 @@ fn cors_preflight_fetch( vec![] }; - // Substep 5 - if (methods.iter().any(|m| m.as_ref() == "*") || - header_names.iter().any(|hn| hn.as_str() == "*")) && - request.credentials_mode == CredentialsMode::Include - { - return Response::network_error(NetworkError::Internal( - "CORS ACAH/ACAM and request credentials mode mismatch".into(), - )); - } - - // Substep 6 - if methods.is_empty() && request.use_cors_preflight { - methods = vec![request.method.clone()]; - } - - // Substep 7 debug!( "CORS check: Allowed methods: {:?}, current method: {:?}", methods, request.method ); - if methods.iter().all(|method| *method != request.method) && + + // Substep 4 + if methods.is_empty() && request.use_cors_preflight { + methods = vec![request.method.clone()]; + } + + // Substep 5 + if methods + .iter() + .all(|m| *m.as_str() != *request.method.as_ref()) && !is_cors_safelisted_method(&request.method) && - methods.iter().all(|m| m.as_ref() != "*") + (request.credentials_mode == CredentialsMode::Include || + methods.iter().all(|m| m.as_ref() != "*")) { return Response::network_error(NetworkError::Internal( "CORS method check failed".into(), )); } - // Substep 8 + debug!( + "CORS check: Allowed headers: {:?}, current headers: {:?}", + header_names, request.headers + ); + + // Substep 6 if request.headers.iter().any(|(name, _)| { - name == header::AUTHORIZATION && header_names.iter().all(|hn| hn != name) + is_cors_non_wildcard_request_header_name(&name) && + header_names.iter().all(|hn| hn != name) }) { return Response::network_error(NetworkError::Internal( "CORS authorization check failed".into(), )); } - // Substep 9 - debug!( - "CORS check: Allowed headers: {:?}, current headers: {:?}", - header_names, request.headers - ); - let set: HashSet<&HeaderName> = HashSet::from_iter(header_names.iter()); - if request.headers.iter().any(|(name, value)| { - !set.contains(name) && !is_cors_safelisted_request_header(&name, &value) - }) { - return Response::network_error(NetworkError::Internal( - "CORS headers check failed".into(), - )); + // Substep 7 + let unsafe_names = get_cors_unsafe_header_names(&request.headers); + let header_names_set: HashSet<&HeaderName> = HashSet::from_iter(header_names.iter()); + let header_names_contains_star = header_names.iter().any(|hn| hn.as_str() == "*"); + for unsafe_name in unsafe_names.iter() { + if !header_names_set.contains(unsafe_name) && + (request.credentials_mode == CredentialsMode::Include || + !header_names_contains_star) + { + return Response::network_error(NetworkError::Internal( + "CORS headers check failed".into(), + )); + } } - // Substep 10, 11 + // Substep 8, 9 let max_age: Duration = response .headers .typed_get::() .map(|acma| acma.into()) - .unwrap_or(Duration::from_secs(0)); + .unwrap_or(Duration::from_secs(5)); let max_age = max_age.as_secs() as u32; - // Substep 12 + // Substep 10 // TODO: Need to define what an imposed limit on max-age is - // Substep 13 ignored, we do have a CORS cache + // Substep 11 ignored, we do have a CORS cache - // Substep 14, 15 + // Substep 12, 13 for method in &methods { cache.match_method_and_update(&*request, method.clone(), max_age); } - // Substep 16, 17 + // Substep 14, 15 for header_name in &header_names { cache.match_header_and_update(&*request, &*header_name, max_age); } - // Substep 18 + // Substep 16 return response; } - // Step 7 + // Step 8 Response::network_error(NetworkError::Internal("CORS check failed".into())) } diff --git a/components/net_traits/request.rs b/components/net_traits/request.rs index cc67ca0cd27..49cd548e093 100644 --- a/components/net_traits/request.rs +++ b/components/net_traits/request.rs @@ -6,6 +6,7 @@ use crate::response::HttpsState; use crate::ReferrerPolicy; use crate::ResourceTimingType; use content_security_policy::{self as csp, CspList}; +use http::header::{HeaderName, AUTHORIZATION}; use http::HeaderMap; use hyper::Method; use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; @@ -677,3 +678,48 @@ pub fn is_cors_safelisted_method(m: &Method) -> bool { _ => false, } } + +/// +pub fn is_cors_non_wildcard_request_header_name(name: &HeaderName) -> bool { + name == AUTHORIZATION +} + +/// +pub fn get_cors_unsafe_header_names(headers: &HeaderMap) -> Vec { + // Step 1 + let mut unsafe_names: Vec<&HeaderName> = vec![]; + // Step 2 + let mut potentillay_unsafe_names: Vec<&HeaderName> = vec![]; + // Step 3 + let mut safelist_value_size = 0; + + // Step 4 + for (name, value) in headers.iter() { + if !is_cors_safelisted_request_header(&name, &value) { + unsafe_names.push(name); + } else { + potentillay_unsafe_names.push(name); + safelist_value_size += value.as_ref().len(); + } + } + + // Step 5 + if safelist_value_size > 1024 { + unsafe_names.extend_from_slice(&potentillay_unsafe_names); + } + + // Step 6 + return convert_header_names_to_sorted_lowercase_set(unsafe_names); +} + +/// +pub fn convert_header_names_to_sorted_lowercase_set( + header_names: Vec<&HeaderName>, +) -> Vec { + // HeaderName does not implement the needed traits to use a BTreeSet + // So create a new Vec, sort, then dedup + let mut ordered_set = header_names.to_vec(); + ordered_set.sort_by(|a, b| a.as_str().partial_cmp(b.as_str()).unwrap()); + ordered_set.dedup(); + return ordered_set.into_iter().cloned().collect(); +} diff --git a/tests/wpt/metadata/fetch/api/cors/cors-origin.any.js.ini b/tests/wpt/metadata/fetch/api/cors/cors-origin.any.js.ini deleted file mode 100644 index d2e2a353f33..00000000000 --- a/tests/wpt/metadata/fetch/api/cors/cors-origin.any.js.ini +++ /dev/null @@ -1,12 +0,0 @@ -[cors-origin.any.worker.html] - [cors-origin] - expected: FAIL - - [CORS preflight [PUT\] [origin OK\]] - expected: FAIL - - -[cors-origin.any.html] - [CORS preflight [PUT\] [origin OK\]] - expected: FAIL - diff --git a/tests/wpt/metadata/fetch/api/cors/cors-preflight-not-cors-safelisted.any.js.ini b/tests/wpt/metadata/fetch/api/cors/cors-preflight-not-cors-safelisted.any.js.ini index ed09138dde1..679cf9c9082 100644 --- a/tests/wpt/metadata/fetch/api/cors/cors-preflight-not-cors-safelisted.any.js.ini +++ b/tests/wpt/metadata/fetch/api/cors/cors-preflight-not-cors-safelisted.any.js.ini @@ -1,60 +1,18 @@ [cors-preflight-not-cors-safelisted.any.html] - [Need CORS-preflight for content-language/@ header] - expected: FAIL - [Need CORS-preflight for content-language/\x01 header] expected: FAIL - [Need CORS-preflight for accept-language/@ header] - expected: FAIL - - [Need CORS-preflight for accept/" header] - expected: FAIL - [Need CORS-preflight for accept-language/\x01 header] expected: FAIL - [Need CORS-preflight for content-type/text/plain; long=0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901 header] - expected: FAIL - - [Need CORS-preflight for accept/012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678 header] - expected: FAIL - - [Need CORS-preflight for content-type/text/html header] - expected: FAIL - - [Need CORS-preflight for test/hi header] - expected: FAIL - [cors-preflight-not-cors-safelisted.any.worker.html] - [Need CORS-preflight for content-language/@ header] - expected: FAIL - [Need CORS-preflight for content-language/\x01 header] expected: FAIL - [Need CORS-preflight for accept-language/@ header] - expected: FAIL - - [Need CORS-preflight for accept/" header] - expected: FAIL - [Need CORS-preflight for accept-language/\x01 header] expected: FAIL - [Need CORS-preflight for content-type/text/plain; long=0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901 header] - expected: FAIL - - [Need CORS-preflight for accept/012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678 header] - expected: FAIL - [cors-preflight-not-cors-safelisted] expected: FAIL - [Need CORS-preflight for content-type/text/html header] - expected: FAIL - - [Need CORS-preflight for test/hi header] - expected: FAIL - diff --git a/tests/wpt/metadata/fetch/api/cors/cors-preflight-redirect.any.js.ini b/tests/wpt/metadata/fetch/api/cors/cors-preflight-redirect.any.js.ini index 995dab02728..b7a9d61adf4 100644 --- a/tests/wpt/metadata/fetch/api/cors/cors-preflight-redirect.any.js.ini +++ b/tests/wpt/metadata/fetch/api/cors/cors-preflight-redirect.any.js.ini @@ -1,11 +1,41 @@ [cors-preflight-redirect.any.html] type: testharness + [Redirection 301 after preflight failed] + expected: FAIL + + [Redirection 302 after preflight failed] + expected: FAIL + + [Redirection 303 after preflight failed] + expected: FAIL + + [Redirection 307 after preflight failed] + expected: FAIL + + [Redirection 308 after preflight failed] + expected: FAIL + [cors-preflight-redirect.any.worker.html] type: testharness [cors-preflight-redirect] expected: FAIL + [Redirection 301 after preflight failed] + expected: FAIL + + [Redirection 302 after preflight failed] + expected: FAIL + + [Redirection 303 after preflight failed] + expected: FAIL + + [Redirection 307 after preflight failed] + expected: FAIL + + [Redirection 308 after preflight failed] + expected: FAIL + [cors-preflight-redirect.any.sharedworker.html] expected: ERROR diff --git a/tests/wpt/metadata/fetch/api/cors/cors-preflight-referrer.any.js.ini b/tests/wpt/metadata/fetch/api/cors/cors-preflight-referrer.any.js.ini index 2bb5f8819e2..67761dcb248 100644 --- a/tests/wpt/metadata/fetch/api/cors/cors-preflight-referrer.any.js.ini +++ b/tests/wpt/metadata/fetch/api/cors/cors-preflight-referrer.any.js.ini @@ -1,68 +1,21 @@ [cors-preflight-referrer.any.worker.html] type: testharness - [Referrer policy: no-referrer and referrer: default] - expected: FAIL - - [Referrer policy: no-referrer and referrer: 'myreferrer'] - expected: FAIL - [Referrer policy: "" and referrer: default] expected: FAIL [Referrer policy: "" and referrer: 'myreferrer'] expected: FAIL - [Referrer policy: origin and referrer: default] - expected: FAIL - - [Referrer policy: origin and referrer: 'myreferrer'] - expected: FAIL - - [Referrer policy: origin-when-cross-origin and referrer: default] - expected: FAIL - - [Referrer policy: origin-when-cross-origin and referrer: 'myreferrer'] - expected: FAIL - - [Referrer policy: unsafe-url and referrer: default] - expected: FAIL - - [Referrer policy: unsafe-url and referrer: 'myreferrer'] - expected: FAIL - [cors-preflight-referrer] expected: FAIL [cors-preflight-referrer.any.html] type: testharness - [Referrer policy: no-referrer and referrer: default] - expected: FAIL - - [Referrer policy: no-referrer and referrer: 'myreferrer'] - expected: FAIL - [Referrer policy: "" and referrer: default] expected: FAIL [Referrer policy: "" and referrer: 'myreferrer'] expected: FAIL - [Referrer policy: origin and referrer: default] - expected: FAIL - - [Referrer policy: origin and referrer: 'myreferrer'] - expected: FAIL - - [Referrer policy: origin-when-cross-origin and referrer: default] - expected: FAIL - - [Referrer policy: origin-when-cross-origin and referrer: 'myreferrer'] - expected: FAIL - - [Referrer policy: unsafe-url and referrer: default] - expected: FAIL - - [Referrer policy: unsafe-url and referrer: 'myreferrer'] - expected: FAIL diff --git a/tests/wpt/metadata/fetch/api/cors/cors-preflight-star.any.js.ini b/tests/wpt/metadata/fetch/api/cors/cors-preflight-star.any.js.ini deleted file mode 100644 index 4be83946479..00000000000 --- a/tests/wpt/metadata/fetch/api/cors/cors-preflight-star.any.js.ini +++ /dev/null @@ -1,38 +0,0 @@ -[cors-preflight-star.any.html] - type: testharness - [CORS that succeeds with credentials: false; method: OK (allowed: *); header: X-Test,1 (allowed: *)] - expected: FAIL - - [CORS that succeeds with credentials: true; method: PUT (allowed: PUT); header: (allowed: *)] - expected: FAIL - - [CORS that succeeds with credentials: true; method: * (allowed: *); header: *,1 (allowed: *)] - expected: FAIL - - [CORS that succeeds with credentials: false; method: GET (allowed: get); header: X-Test,1 (allowed: x-test)] - expected: FAIL - - [CORS that succeeds with credentials: false; method: SUPER (allowed: *); header: X-Test,1 (allowed: x-test)] - expected: FAIL - - -[cors-preflight-star.any.worker.html] - type: testharness - [CORS that succeeds with credentials: false; method: OK (allowed: *); header: X-Test,1 (allowed: *)] - expected: FAIL - - [CORS that succeeds with credentials: true; method: PUT (allowed: PUT); header: (allowed: *)] - expected: FAIL - - [CORS that succeeds with credentials: true; method: * (allowed: *); header: *,1 (allowed: *)] - expected: FAIL - - [cors-preflight-star] - expected: FAIL - - [CORS that succeeds with credentials: false; method: GET (allowed: get); header: X-Test,1 (allowed: x-test)] - expected: FAIL - - [CORS that succeeds with credentials: false; method: SUPER (allowed: *); header: X-Test,1 (allowed: x-test)] - expected: FAIL - diff --git a/tests/wpt/metadata/fetch/api/cors/cors-preflight-status.any.js.ini b/tests/wpt/metadata/fetch/api/cors/cors-preflight-status.any.js.ini deleted file mode 100644 index 1e82624ce45..00000000000 --- a/tests/wpt/metadata/fetch/api/cors/cors-preflight-status.any.js.ini +++ /dev/null @@ -1,50 +0,0 @@ -[cors-preflight-status.any.html] - type: testharness - [Preflight answered with status 200] - expected: FAIL - - [Preflight answered with status 201] - expected: FAIL - - [Preflight answered with status 202] - expected: FAIL - - [Preflight answered with status 203] - expected: FAIL - - [Preflight answered with status 204] - expected: FAIL - - [Preflight answered with status 205] - expected: FAIL - - [Preflight answered with status 206] - expected: FAIL - - -[cors-preflight-status.any.worker.html] - type: testharness - [Preflight answered with status 200] - expected: FAIL - - [Preflight answered with status 201] - expected: FAIL - - [Preflight answered with status 202] - expected: FAIL - - [Preflight answered with status 203] - expected: FAIL - - [Preflight answered with status 204] - expected: FAIL - - [Preflight answered with status 205] - expected: FAIL - - [Preflight answered with status 206] - expected: FAIL - - [cors-preflight-status] - expected: FAIL - diff --git a/tests/wpt/metadata/fetch/api/cors/cors-preflight.any.js.ini b/tests/wpt/metadata/fetch/api/cors/cors-preflight.any.js.ini index d2e60ed7b85..29972520799 100644 --- a/tests/wpt/metadata/fetch/api/cors/cors-preflight.any.js.ini +++ b/tests/wpt/metadata/fetch/api/cors/cors-preflight.any.js.ini @@ -6,27 +6,6 @@ [CORS [PUT\] [several headers\], server allows] expected: FAIL - [CORS [PUT\], server allows] - expected: FAIL - - [CORS [GET\] [x-test-header: allowed\], server allows] - expected: FAIL - - [CORS [PUT\], server allows, check preflight has user agent] - expected: FAIL - - [CORS [DELETE\], server allows] - expected: FAIL - - [CORS [PUT\] [only safe headers\], server allows] - expected: FAIL - - [CORS [NEW\], server allows] - expected: FAIL - - [CORS [PATCH\], server allows] - expected: FAIL - [cors-preflight.any.worker.html] type: testharness @@ -39,24 +18,3 @@ [cors-preflight] expected: FAIL - [CORS [PUT\], server allows] - expected: FAIL - - [CORS [GET\] [x-test-header: allowed\], server allows] - expected: FAIL - - [CORS [PUT\], server allows, check preflight has user agent] - expected: FAIL - - [CORS [DELETE\], server allows] - expected: FAIL - - [CORS [PUT\] [only safe headers\], server allows] - expected: FAIL - - [CORS [NEW\], server allows] - expected: FAIL - - [CORS [PATCH\], server allows] - expected: FAIL - diff --git a/tests/wpt/metadata/fetch/api/cors/cors-redirect-preflight.any.js.ini b/tests/wpt/metadata/fetch/api/cors/cors-redirect-preflight.any.js.ini deleted file mode 100644 index 9183717d227..00000000000 --- a/tests/wpt/metadata/fetch/api/cors/cors-redirect-preflight.any.js.ini +++ /dev/null @@ -1,96 +0,0 @@ -[cors-redirect-preflight.any.worker.html] - [cors-redirect-preflight] - expected: FAIL - - [Redirect 302: same origin to cors (preflight after redirection success case)] - expected: FAIL - - [Redirect 303: cors to same origin (preflight after redirection success case)] - expected: FAIL - - [Redirect 302: cors to another cors (preflight after redirection success case)] - expected: FAIL - - [Redirect 308: same origin to cors (preflight after redirection success case)] - expected: FAIL - - [Redirect 302: cors to same origin (preflight after redirection success case)] - expected: FAIL - - [Redirect 301: same origin to cors (preflight after redirection success case)] - expected: FAIL - - [Redirect 307: cors to another cors (preflight after redirection success case)] - expected: FAIL - - [Redirect 301: cors to another cors (preflight after redirection success case)] - expected: FAIL - - [Redirect 307: same origin to cors (preflight after redirection success case)] - expected: FAIL - - [Redirect 308: cors to same origin (preflight after redirection success case)] - expected: FAIL - - [Redirect 303: cors to another cors (preflight after redirection success case)] - expected: FAIL - - [Redirect 307: cors to same origin (preflight after redirection success case)] - expected: FAIL - - [Redirect 301: cors to same origin (preflight after redirection success case)] - expected: FAIL - - [Redirect 303: same origin to cors (preflight after redirection success case)] - expected: FAIL - - [Redirect 308: cors to another cors (preflight after redirection success case)] - expected: FAIL - - -[cors-redirect-preflight.any.html] - [Redirect 302: same origin to cors (preflight after redirection success case)] - expected: FAIL - - [Redirect 303: cors to same origin (preflight after redirection success case)] - expected: FAIL - - [Redirect 302: cors to another cors (preflight after redirection success case)] - expected: FAIL - - [Redirect 308: same origin to cors (preflight after redirection success case)] - expected: FAIL - - [Redirect 302: cors to same origin (preflight after redirection success case)] - expected: FAIL - - [Redirect 301: same origin to cors (preflight after redirection success case)] - expected: FAIL - - [Redirect 307: cors to another cors (preflight after redirection success case)] - expected: FAIL - - [Redirect 301: cors to another cors (preflight after redirection success case)] - expected: FAIL - - [Redirect 307: same origin to cors (preflight after redirection success case)] - expected: FAIL - - [Redirect 308: cors to same origin (preflight after redirection success case)] - expected: FAIL - - [Redirect 303: cors to another cors (preflight after redirection success case)] - expected: FAIL - - [Redirect 307: cors to same origin (preflight after redirection success case)] - expected: FAIL - - [Redirect 301: cors to same origin (preflight after redirection success case)] - expected: FAIL - - [Redirect 303: same origin to cors (preflight after redirection success case)] - expected: FAIL - - [Redirect 308: cors to another cors (preflight after redirection success case)] - expected: FAIL -