From ed9c16575c274719cb2989049011beb342a07af3 Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Sat, 24 Jun 2017 03:33:51 -0700 Subject: [PATCH] Update main fetch --- components/net/fetch/methods.rs | 67 +++++++++++++++++++++++-------- components/net/http_loader.rs | 14 ++++--- components/net_traits/response.rs | 6 ++- 3 files changed, 63 insertions(+), 24 deletions(-) diff --git a/components/net/fetch/methods.rs b/components/net/fetch/methods.rs index 59da722fb03..7ffdc9b60ee 100644 --- a/components/net/fetch/methods.rs +++ b/components/net/fetch/methods.rs @@ -10,14 +10,14 @@ use filemanager_thread::FileManager; use http_loader::{HttpState, determine_request_referrer, http_fetch}; use http_loader::{set_default_accept, set_default_accept_language}; use hyper::{Error, Result as HyperResult}; -use hyper::header::{Accept, AcceptLanguage, ContentLanguage, ContentType}; +use hyper::header::{Accept, AcceptLanguage, AccessControlExposeHeaders, ContentLanguage, ContentType}; use hyper::header::{Header, HeaderFormat, HeaderView, Headers, Referer as RefererHeader}; use hyper::method::Method; use hyper::mime::{Mime, SubLevel, TopLevel}; use hyper::status::StatusCode; use mime_guess::guess_mime_type; use net_traits::{FetchTaskTarget, NetworkError, ReferrerPolicy}; -use net_traits::request::{Referrer, Request, RequestMode, ResponseTainting}; +use net_traits::request::{CredentialsMode, Referrer, Request, RequestMode, ResponseTainting}; use net_traits::request::{Type, Origin, Window}; use net_traits::response::{Response, ResponseBody, ResponseType}; use servo_url::ServoUrl; @@ -107,9 +107,8 @@ pub fn main_fetch(request: &mut Request, // Step 2. if request.local_urls_only { - match request.current_url().scheme() { - "about" | "blob" | "data" | "filesystem" => (), // Ok, the URL is local. - _ => response = Some(Response::network_error(NetworkError::Internal("Non-local scheme".into()))) + if !matches!(request.current_url().scheme(), "about" | "blob" | "data" | "filesystem") { + response = Some(Response::network_error(NetworkError::Internal("Non-local scheme".into()))); } } @@ -130,8 +129,7 @@ pub fn main_fetch(request: &mut Request, // TODO: handle request's client's referrer policy. // Step 7. - let referrer_policy = request.referrer_policy.unwrap_or(ReferrerPolicy::NoReferrerWhenDowngrade); - request.referrer_policy = Some(referrer_policy); + request.referrer_policy = request.referrer_policy.or(Some(ReferrerPolicy::NoReferrerWhenDowngrade)); // Step 8. { @@ -147,7 +145,7 @@ pub fn main_fetch(request: &mut Request, request.headers.remove::(); let current_url = request.current_url().clone(); determine_request_referrer(&mut request.headers, - referrer_policy, + request.referrer_policy.unwrap(), url, current_url) } @@ -168,7 +166,7 @@ pub fn main_fetch(request: &mut Request, // Not applicable: see fetch_async. // Step 12. - let response = response.unwrap_or_else(|| { + let mut response = response.unwrap_or_else(|| { let current_url = request.current_url(); let same_origin = if let Origin::Origin(ref origin) = request.origin { *origin == current_url.origin() @@ -178,16 +176,25 @@ pub fn main_fetch(request: &mut Request, if (same_origin && !cors_flag ) || current_url.scheme() == "data" || - current_url.scheme() == "file" || + current_url.scheme() == "file" || // FIXME: Fetch spec has already dropped filtering against file: + // and about: schemes, but CSS tests will break on loading Ahem + // since we load them through a file: URL. current_url.scheme() == "about" || request.mode == RequestMode::Navigate { + // Substep 1. + request.response_tainting = ResponseTainting::Basic; + + // Substep 2. basic_fetch(request, cache, target, done_chan, context) } else if request.mode == RequestMode::SameOrigin { Response::network_error(NetworkError::Internal("Cross-origin response".into())) } else if request.mode == RequestMode::NoCors { + // Substep 1. request.response_tainting = ResponseTainting::Opaque; + + // Substep 2. basic_fetch(request, cache, target, done_chan, context) } else if !matches!(current_url.scheme(), "http" | "https") { @@ -195,18 +202,24 @@ pub fn main_fetch(request: &mut Request, } else if request.use_cors_preflight || (request.unsafe_request && - (!is_simple_method(&request.method) || - request.headers.iter().any(|h| !is_simple_header(&h)))) { + (!is_cors_safelisted_method(&request.method) || + request.headers.iter().any(|h| !is_cors_safelisted_request_header(&h)))) { + // Substep 1. request.response_tainting = ResponseTainting::CorsTainting; + // Substep 2. let response = http_fetch(request, cache, true, true, false, target, done_chan, context); + // Substep 3. if response.is_network_error() { // TODO clear cache entries using request } + // Substep 4. response } else { + // Substep 1. request.response_tainting = ResponseTainting::CorsTainting; + // Substep 2. http_fetch(request, cache, true, false, false, target, done_chan, context) } }); @@ -217,9 +230,28 @@ pub fn main_fetch(request: &mut Request, } // Step 14. - // We don't need to check whether response is a network error, - // given its type would not be `Default` in this case. - let mut response = if response.response_type == ResponseType::Default { + let mut response = if !response.is_network_error() && response.internal_response.is_none() { + // Substep 1. + if request.response_tainting == ResponseTainting::CorsTainting { + // Subsubstep 1. + let header_names = response.headers.get::(); + match header_names { + // Subsubstep 2. + Some(list) if request.credentials_mode != CredentialsMode::Include => { + if list.len() == 1 && list[0] == "*" { + response.cors_exposed_header_name_list = + response.headers.iter().map(|h| h.name().to_owned()).collect(); + } + }, + // Subsubstep 3. + Some(list) => { + response.cors_exposed_header_name_list = list.iter().map(|h| (**h).clone()).collect(); + }, + _ => (), + } + } + + // Substep 2. let response_type = match request.response_tainting { ResponseTainting::Basic => ResponseType::Basic, ResponseTainting::CorsTainting => ResponseType::Cors, @@ -463,7 +495,7 @@ fn basic_fetch(request: &mut Request, } /// https://fetch.spec.whatwg.org/#cors-safelisted-request-header -pub fn is_simple_header(h: &HeaderView) -> bool { +pub fn is_cors_safelisted_request_header(h: &HeaderView) -> bool { if h.is::() { match h.value() { Some(&ContentType(Mime(TopLevel::Text, SubLevel::Plain, _))) | @@ -477,7 +509,8 @@ pub fn is_simple_header(h: &HeaderView) -> bool { } } -pub fn is_simple_method(m: &Method) -> bool { +/// https://fetch.spec.whatwg.org/#cors-safelisted-method +pub fn is_cors_safelisted_method(m: &Method) -> bool { match *m { Method::Get | Method::Head | Method::Post => true, _ => false diff --git a/components/net/http_loader.rs b/components/net/http_loader.rs index 6c59669a750..fceaf24c7ee 100644 --- a/components/net/http_loader.rs +++ b/components/net/http_loader.rs @@ -9,7 +9,8 @@ use cookie_storage::CookieStorage; use devtools_traits::{ChromeToDevtoolsControlMsg, DevtoolsControlMsg, HttpRequest as DevtoolsHttpRequest}; use devtools_traits::{HttpResponse as DevtoolsHttpResponse, NetworkEvent}; use fetch::cors_cache::CorsCache; -use fetch::methods::{Data, DoneChannel, FetchContext, Target, is_simple_header, is_simple_method, main_fetch}; +use fetch::methods::{Data, DoneChannel, FetchContext, Target}; +use fetch::methods::{is_cors_safelisted_request_header, is_cors_safelisted_method, main_fetch}; use flate2::read::{DeflateDecoder, GzDecoder}; use hsts::HstsList; use hyper::Error as HttpError; @@ -573,10 +574,10 @@ pub fn http_fetch(request: &mut Request, let method_cache_match = cache.match_method(&*request, request.method.clone()); - let method_mismatch = !method_cache_match && (!is_simple_method(&request.method) || + let method_mismatch = !method_cache_match && (!is_cors_safelisted_method(&request.method) || request.use_cors_preflight); let header_mismatch = request.headers.iter().any(|view| - !cache.match_header(&*request, view.name()) && !is_simple_header(&view) + !cache.match_header(&*request, view.name()) && !is_cors_safelisted_request_header(&view) ); // Sub-substep 1 @@ -1269,7 +1270,7 @@ fn cors_preflight_fetch(request: &Request, // Step 3, 4 let mut value = request.headers .iter() - .filter(|view| !is_simple_header(view)) + .filter(|view| !is_cors_safelisted_request_header(view)) .map(|view| UniCase(view.name().to_owned())) .collect::>>(); value.sort(); @@ -1315,14 +1316,15 @@ fn cors_preflight_fetch(request: &Request, debug!("CORS check: Allowed methods: {:?}, current method: {:?}", methods, request.method); if methods.iter().all(|method| *method != request.method) && - !is_simple_method(&request.method) { + !is_cors_safelisted_method(&request.method) { return Response::network_error(NetworkError::Internal("CORS method check failed".into())); } // Substep 6 debug!("CORS check: Allowed headers: {:?}, current headers: {:?}", header_names, request.headers); let set: HashSet<&UniCase> = HashSet::from_iter(header_names.iter()); - if request.headers.iter().any(|ref hv| !set.contains(&UniCase(hv.name().to_owned())) && !is_simple_header(hv)) { + if request.headers.iter().any( + |ref hv| !set.contains(&UniCase(hv.name().to_owned())) && !is_cors_safelisted_request_header(hv)) { return Response::network_error(NetworkError::Internal("CORS headers check failed".into())); } diff --git a/components/net_traits/response.rs b/components/net_traits/response.rs index 74525a85f07..ebb7681afba 100644 --- a/components/net_traits/response.rs +++ b/components/net_traits/response.rs @@ -102,12 +102,14 @@ pub struct Response { pub cache_state: CacheState, pub https_state: HttpsState, pub referrer: Option, + pub referrer_policy: Option, + /// [CORS-exposed header-name list](https://fetch.spec.whatwg.org/#concept-response-cors-exposed-header-name-list) + pub cors_exposed_header_name_list: Vec, /// [Internal response](https://fetch.spec.whatwg.org/#concept-internal-response), only used if the Response /// is a filtered response pub internal_response: Option>, /// whether or not to try to return the internal_response when asked for actual_response pub return_internal: bool, - pub referrer_policy: Option, } impl Response { @@ -125,6 +127,7 @@ impl Response { https_state: HttpsState::None, referrer: None, referrer_policy: None, + cors_exposed_header_name_list: vec![], internal_response: None, return_internal: true, } @@ -151,6 +154,7 @@ impl Response { https_state: HttpsState::None, referrer: None, referrer_policy: None, + cors_exposed_header_name_list: vec![], internal_response: None, return_internal: true, }