Set CORS preflight requests' mode to cors

This commit is contained in:
Vincent Ricard 2020-10-14 21:40:59 +02:00
parent 4c3247e480
commit 5b40068587
10 changed files with 137 additions and 390 deletions

View file

@ -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>(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::<Vec<&str>>();
headers.sort();
let headers = headers
.iter()
.filter_map(|name| HeaderName::from_str(name).ok())
.collect::<Vec<HeaderName>>();
// 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::<AccessControlAllowMethods>() {
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::<AccessControlAllowHeaders>() {
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::<AccessControlMaxAge>()
.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()))
}

View file

@ -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,
}
}
/// <https://fetch.spec.whatwg.org/#cors-non-wildcard-request-header-name>
pub fn is_cors_non_wildcard_request_header_name(name: &HeaderName) -> bool {
name == AUTHORIZATION
}
/// <https://fetch.spec.whatwg.org/#cors-unsafe-request-header-names>
pub fn get_cors_unsafe_header_names(headers: &HeaderMap) -> Vec<HeaderName> {
// 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);
}
/// <https://fetch.spec.whatwg.org/#ref-for-convert-header-names-to-a-sorted-lowercase-set>
pub fn convert_header_names_to_sorted_lowercase_set(
header_names: Vec<&HeaderName>,
) -> Vec<HeaderName> {
// 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();
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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