diff options
author | Ms2ger <Ms2ger@gmail.com> | 2016-11-22 01:24:48 +0100 |
---|---|---|
committer | Ms2ger <Ms2ger@gmail.com> | 2016-11-24 14:00:35 +0100 |
commit | 38db554b5ea582cffcdad561f9a064fc2edb0b30 (patch) | |
tree | 7a9ae4416ec5e2e2a30b60c7e92cd1911f35a361 /components/net/fetch/methods.rs | |
parent | 675d8f518c1f730b85d79e029261bac2ce286fcd (diff) | |
download | servo-38db554b5ea582cffcdad561f9a064fc2edb0b30.tar.gz servo-38db554b5ea582cffcdad561f9a064fc2edb0b30.zip |
Move the http-specific fetch code to http_loader.
Diffstat (limited to 'components/net/fetch/methods.rs')
-rw-r--r-- | components/net/fetch/methods.rs | 882 |
1 files changed, 17 insertions, 865 deletions
diff --git a/components/net/fetch/methods.rs b/components/net/fetch/methods.rs index 0c4113911a4..97c6fc1d871 100644 --- a/components/net/fetch/methods.rs +++ b/components/net/fetch/methods.rs @@ -3,46 +3,26 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use blob_loader::load_blob_sync; -use connector::create_http_connector; use data_loader::decode; use devtools_traits::DevtoolsControlMsg; use fetch::cors_cache::CorsCache; use filemanager_thread::FileManager; -use http_loader::{HttpState, set_default_accept_encoding, set_default_accept_language, set_request_cookies}; -use http_loader::{NetworkHttpRequestFactory, StreamedResponse, obtain_response, read_block}; -use http_loader::{auth_from_cache, determine_request_referrer, set_cookies_from_headers}; -use http_loader::{send_response_to_devtools, send_request_to_devtools, LoadErrorType}; -use hyper::header::{Accept, AcceptLanguage, Authorization, AccessControlAllowCredentials}; -use hyper::header::{AccessControlAllowOrigin, AccessControlAllowHeaders, AccessControlAllowMethods}; -use hyper::header::{AccessControlRequestHeaders, AccessControlMaxAge, AccessControlRequestMethod, Basic}; -use hyper::header::{CacheControl, CacheDirective, ContentEncoding, ContentLength, ContentLanguage, ContentType}; -use hyper::header::{Encoding, HeaderView, Headers, Host, IfMatch, IfRange, IfUnmodifiedSince, IfModifiedSince}; -use hyper::header::{IfNoneMatch, Pragma, Location, QualityItem, Referer as RefererHeader, UserAgent, q, qitem}; +use http_loader::{HttpState, determine_request_referrer, http_fetch, set_default_accept_language}; +use hyper::header::{Accept, AcceptLanguage, ContentLanguage, ContentType}; +use hyper::header::{HeaderView, QualityItem, Referer as RefererHeader, q, qitem}; use hyper::method::Method; use hyper::mime::{Mime, SubLevel, TopLevel}; use hyper::status::StatusCode; -use hyper_serde::Serde; use mime_guess::guess_mime_type; -use net_traits::{FetchTaskTarget, FetchMetadata, NetworkError, ReferrerPolicy}; -use net_traits::request::{CacheMode, CredentialsMode, Destination}; +use net_traits::{FetchTaskTarget, NetworkError, ReferrerPolicy}; use net_traits::request::{RedirectMode, Referrer, Request, RequestMode, ResponseTainting}; use net_traits::request::{Type, Origin, Window}; -use net_traits::response::{HttpsState, Response, ResponseBody, ResponseType}; -use servo_url::ServoUrl; +use net_traits::response::{Response, ResponseBody, ResponseType}; use std::borrow::Cow; -use std::collections::HashSet; -use std::error::Error; use std::fs::File; use std::io::Read; -use std::iter::FromIterator; -use std::mem::swap; -use std::ops::Deref; use std::rc::Rc; -use std::sync::mpsc::{channel, Sender, Receiver}; -use unicase::UniCase; -use url::{Origin as UrlOrigin}; -use util::thread::spawn_named; -use uuid; +use std::sync::mpsc::{Sender, Receiver}; pub type Target = Option<Box<FetchTaskTarget + Send>>; @@ -58,7 +38,7 @@ pub struct FetchContext { pub filemanager: FileManager, } -type DoneChannel = Option<(Sender<Data>, Receiver<Data>)>; +pub type DoneChannel = Option<(Sender<Data>, Receiver<Data>)>; /// [Fetch](https://fetch.spec.whatwg.org#concept-fetch) pub fn fetch(request: Rc<Request>, @@ -135,14 +115,14 @@ pub fn fetch_with_cors_cache(request: Rc<Request>, } /// [Main fetch](https://fetch.spec.whatwg.org/#concept-main-fetch) -fn main_fetch(request: Rc<Request>, - cache: &mut CorsCache, - cors_flag: bool, - recursive_flag: bool, - target: &mut Target, - done_chan: &mut DoneChannel, - context: &FetchContext) - -> Response { +pub fn main_fetch(request: Rc<Request>, + cache: &mut CorsCache, + cors_flag: bool, + recursive_flag: bool, + target: &mut Target, + done_chan: &mut DoneChannel, + context: &FetchContext) + -> Response { // TODO: Implement main fetch spec // Step 1 @@ -490,831 +470,8 @@ fn basic_fetch(request: Rc<Request>, } } -/// [HTTP fetch](https://fetch.spec.whatwg.org#http-fetch) -fn http_fetch(request: Rc<Request>, - cache: &mut CorsCache, - cors_flag: bool, - cors_preflight_flag: bool, - authentication_fetch_flag: bool, - target: &mut Target, - done_chan: &mut DoneChannel, - context: &FetchContext) - -> Response { - // This is a new async fetch, reset the channel we are waiting on - *done_chan = None; - // Step 1 - let mut response: Option<Response> = None; - - // Step 2 - // nothing to do, since actual_response is a function on response - - // Step 3 - if !request.skip_service_worker.get() && !request.is_service_worker_global_scope { - // Substep 1 - // TODO (handle fetch unimplemented) - - if let Some(ref res) = response { - // Substep 2 - // nothing to do, since actual_response is a function on response - - // Substep 3 - if (res.response_type == ResponseType::Opaque && - request.mode != RequestMode::NoCors) || - (res.response_type == ResponseType::OpaqueRedirect && - request.redirect_mode.get() != RedirectMode::Manual) || - (res.url_list.borrow().len() > 1 && - request.redirect_mode.get() != RedirectMode::Follow) || - res.is_network_error() { - return Response::network_error(NetworkError::Internal("Request failed".into())); - } - - // Substep 4 - // TODO: set response's CSP list on actual_response - } - } - - // Step 4 - let credentials = match request.credentials_mode { - CredentialsMode::Include => true, - CredentialsMode::CredentialsSameOrigin if request.response_tainting.get() == ResponseTainting::Basic - => true, - _ => false - }; - // Step 5 - if response.is_none() { - // Substep 1 - if cors_preflight_flag { - let method_cache_match = cache.match_method(&*request, - request.method.borrow().clone()); - - let method_mismatch = !method_cache_match && (!is_simple_method(&request.method.borrow()) || - request.use_cors_preflight); - let header_mismatch = request.headers.borrow().iter().any(|view| - !cache.match_header(&*request, view.name()) && !is_simple_header(&view) - ); - - // Sub-substep 1 - if method_mismatch || header_mismatch { - let preflight_result = cors_preflight_fetch(request.clone(), cache, context); - // Sub-substep 2 - if let Some(e) = preflight_result.get_network_error() { - return Response::network_error(e.clone()); - } - } - } - - // Substep 2 - request.skip_service_worker.set(true); - - // Substep 3 - let fetch_result = http_network_or_cache_fetch(request.clone(), credentials, authentication_fetch_flag, - done_chan, context); - - // Substep 4 - if cors_flag && cors_check(request.clone(), &fetch_result).is_err() { - return Response::network_error(NetworkError::Internal("CORS check failed".into())); - } - - fetch_result.return_internal.set(false); - response = Some(fetch_result); - } - - // response is guaranteed to be something by now - let mut response = response.unwrap(); - - // Step 5 - match response.actual_response().status { - // Code 301, 302, 303, 307, 308 - Some(StatusCode::MovedPermanently) | - Some(StatusCode::Found) | - Some(StatusCode::SeeOther) | - Some(StatusCode::TemporaryRedirect) | - Some(StatusCode::PermanentRedirect) => { - response = match request.redirect_mode.get() { - RedirectMode::Error => Response::network_error(NetworkError::Internal("Redirect mode error".into())), - RedirectMode::Manual => { - response.to_filtered(ResponseType::OpaqueRedirect) - }, - RedirectMode::Follow => { - // set back to default - response.return_internal.set(true); - http_redirect_fetch(request, cache, response, - cors_flag, target, done_chan, context) - } - } - }, - - // Code 401 - Some(StatusCode::Unauthorized) => { - // Step 1 - // FIXME: Figure out what to do with request window objects - if cors_flag || !credentials { - return response; - } - - // Step 2 - // TODO: Spec says requires testing on multiple WWW-Authenticate headers - - // Step 3 - if !request.use_url_credentials || authentication_fetch_flag { - // TODO: Prompt the user for username and password from the window - // Wrong, but will have to do until we are able to prompt the user - // otherwise this creates an infinite loop - // We basically pretend that the user declined to enter credentials - return response; - } - - // Step 4 - return http_fetch(request, cache, cors_flag, cors_preflight_flag, - true, target, done_chan, context); - } - - // Code 407 - Some(StatusCode::ProxyAuthenticationRequired) => { - // Step 1 - // TODO: Figure out what to do with request window objects - - // Step 2 - // TODO: Spec says requires testing on Proxy-Authenticate headers - - // Step 3 - // TODO: Prompt the user for proxy authentication credentials - // Wrong, but will have to do until we are able to prompt the user - // otherwise this creates an infinite loop - // We basically pretend that the user declined to enter credentials - return response; - - // Step 4 - // return http_fetch(request, cache, - // cors_flag, cors_preflight_flag, - // authentication_fetch_flag, target, - // done_chan, context); - } - - _ => { } - } - - // Step 6 - if authentication_fetch_flag { - // TODO: Create authentication entry for this request - } - - // set back to default - response.return_internal.set(true); - // Step 7 - response -} - -/// [HTTP redirect fetch](https://fetch.spec.whatwg.org#http-redirect-fetch) -fn http_redirect_fetch(request: Rc<Request>, - cache: &mut CorsCache, - response: Response, - cors_flag: bool, - target: &mut Target, - done_chan: &mut DoneChannel, - context: &FetchContext) - -> Response { - // Step 1 - assert_eq!(response.return_internal.get(), true); - - // Step 2 - if !response.actual_response().headers.has::<Location>() { - return response; - } - - // Step 3 - let location = match response.actual_response().headers.get::<Location>() { - Some(&Location(ref location)) => location.clone(), - _ => return Response::network_error(NetworkError::Internal("Location header parsing failure".into())) - }; - let response_url = response.actual_response().url().unwrap(); - let location_url = response_url.join(&*location); - let location_url = match location_url { - Ok(url) => url, - _ => return Response::network_error(NetworkError::Internal("Location URL parsing failure".into())) - }; - - // Step 4 - // TODO implement return network_error if not HTTP(S) - - // Step 5 - if request.redirect_count.get() >= 20 { - return Response::network_error(NetworkError::Internal("Too many redirects".into())); - } - - // Step 6 - request.redirect_count.set(request.redirect_count.get() + 1); - - // Step 7 - let same_origin = if let Origin::Origin(ref origin) = *request.origin.borrow() { - *origin == request.current_url().origin() - } else { - false - }; - let has_credentials = has_credentials(&location_url); - - if request.mode == RequestMode::CorsMode && !same_origin && has_credentials { - return Response::network_error(NetworkError::Internal("Cross-origin credentials check failed".into())); - } - - // Step 8 - if cors_flag && has_credentials { - return Response::network_error(NetworkError::Internal("Credentials check failed".into())); - } - - // Step 9 - if cors_flag && !same_origin { - *request.origin.borrow_mut() = Origin::Origin(UrlOrigin::new_opaque()); - } - - // Step 10 - let status_code = response.actual_response().status.unwrap(); - if ((status_code == StatusCode::MovedPermanently || status_code == StatusCode::Found) && - *request.method.borrow() == Method::Post) || - status_code == StatusCode::SeeOther { - *request.method.borrow_mut() = Method::Get; - *request.body.borrow_mut() = None; - } - - // Step 11 - request.url_list.borrow_mut().push(location_url); - - // Step 12 - // TODO implement referrer policy - - // Step 13 - main_fetch(request, cache, cors_flag, true, target, done_chan, context) -} - -/// [HTTP network or cache fetch](https://fetch.spec.whatwg.org#http-network-or-cache-fetch) -fn http_network_or_cache_fetch(request: Rc<Request>, - credentials_flag: bool, - authentication_fetch_flag: bool, - done_chan: &mut DoneChannel, - context: &FetchContext) - -> Response { - // TODO: Implement Window enum for Request - let request_has_no_window = true; - - // Step 1 - let http_request = if request_has_no_window && - request.redirect_mode.get() == RedirectMode::Error { - request - } else { - Rc::new((*request).clone()) - }; - - let content_length_value = match *http_request.body.borrow() { - None => - match *http_request.method.borrow() { - // Step 3 - Method::Head | Method::Post | Method::Put => - Some(0), - // Step 2 - _ => None - }, - // Step 4 - Some(ref http_request_body) => Some(http_request_body.len() as u64) - }; - - // Step 5 - if let Some(content_length_value) = content_length_value { - http_request.headers.borrow_mut().set(ContentLength(content_length_value)); - } - - // Step 6 - match *http_request.referrer.borrow() { - Referrer::NoReferrer => (), - Referrer::ReferrerUrl(ref http_request_referrer) => - http_request.headers.borrow_mut().set(RefererHeader(http_request_referrer.to_string())), - Referrer::Client => - // it should be impossible for referrer to be anything else during fetching - // https://fetch.spec.whatwg.org/#concept-request-referrer - unreachable!() - }; - - // Step 7 - if http_request.omit_origin_header.get() == false { - // TODO update this when https://github.com/hyperium/hyper/pull/691 is finished - // http_request.headers.borrow_mut().set_raw("origin", origin); - } - - // Step 8 - if !http_request.headers.borrow().has::<UserAgent>() { - let user_agent = context.user_agent.clone().into_owned(); - http_request.headers.borrow_mut().set(UserAgent(user_agent)); - } - - match http_request.cache_mode.get() { - // Step 9 - CacheMode::Default if is_no_store_cache(&http_request.headers.borrow()) => { - http_request.cache_mode.set(CacheMode::NoStore); - }, - - // Step 10 - CacheMode::NoCache if !http_request.headers.borrow().has::<CacheControl>() => { - http_request.headers.borrow_mut().set(CacheControl(vec![CacheDirective::MaxAge(0)])); - }, - - // Step 11 - CacheMode::Reload => { - // Substep 1 - if !http_request.headers.borrow().has::<Pragma>() { - http_request.headers.borrow_mut().set(Pragma::NoCache); - } - - // Substep 2 - if !http_request.headers.borrow().has::<CacheControl>() { - http_request.headers.borrow_mut().set(CacheControl(vec![CacheDirective::NoCache])); - } - }, - - _ => {} - } - - let current_url = http_request.current_url(); - // Step 12 - // todo: pass referrer url and policy - // this can only be uncommented when the referrer header is set, else it crashes - // in the meantime, we manually set the headers in the block below - // modify_request_headers(&mut http_request.headers.borrow_mut(), ¤t_url, - // None, None, None); - { - let headers = &mut *http_request.headers.borrow_mut(); - let host = Host { - hostname: current_url.host_str().unwrap().to_owned(), - port: current_url.port_or_known_default() - }; - headers.set(host); - // unlike http_loader, we should not set the accept header - // here, according to the fetch spec - set_default_accept_encoding(headers); - } - - // Step 13 - // TODO some of this step can't be implemented yet - if credentials_flag { - // Substep 1 - // TODO http://mxr.mozilla.org/servo/source/components/net/http_loader.rs#504 - // XXXManishearth http_loader has block_cookies: support content blocking here too - set_request_cookies(¤t_url, - &mut *http_request.headers.borrow_mut(), - &context.state.cookie_jar); - // Substep 2 - if !http_request.headers.borrow().has::<Authorization<String>>() { - // Substep 3 - let mut authorization_value = None; - - // Substep 4 - if let Some(basic) = auth_from_cache(&context.state.auth_cache, ¤t_url.origin()) { - if !http_request.use_url_credentials || !has_credentials(¤t_url) { - authorization_value = Some(basic); - } - } - - // Substep 5 - if authentication_fetch_flag && authorization_value.is_none() { - if has_credentials(¤t_url) { - authorization_value = Some(Basic { - username: current_url.username().to_owned(), - password: current_url.password().map(str::to_owned) - }) - } - } - - // Substep 6 - if let Some(basic) = authorization_value { - http_request.headers.borrow_mut().set(Authorization(basic)); - } - } - } - - // Step 14 - // TODO this step can't be implemented yet - - // Step 15 - let mut response: Option<Response> = None; - - // Step 16 - // TODO have a HTTP cache to check for a completed response - let complete_http_response_from_cache: Option<Response> = None; - if http_request.cache_mode.get() != CacheMode::NoStore && - http_request.cache_mode.get() != CacheMode::Reload && - complete_http_response_from_cache.is_some() { - // Substep 1 - if http_request.cache_mode.get() == CacheMode::ForceCache { - // TODO pull response from HTTP cache - // response = http_request - } - - let revalidation_needed = match response { - Some(ref response) => response_needs_revalidation(&response), - _ => false - }; - - // Substep 2 - if !revalidation_needed && http_request.cache_mode.get() == CacheMode::Default { - // TODO pull response from HTTP cache - // response = http_request - // response.cache_state = CacheState::Local; - } - - // Substep 3 - if revalidation_needed && http_request.cache_mode.get() == CacheMode::Default || - http_request.cache_mode.get() == CacheMode::NoCache { - // TODO this substep - } - - // Step 17 - // TODO have a HTTP cache to check for a partial response - } else if http_request.cache_mode.get() == CacheMode::Default || - http_request.cache_mode.get() == CacheMode::ForceCache { - // TODO this substep - } - - // Step 18 - if response.is_none() { - response = Some(http_network_fetch(http_request.clone(), credentials_flag, - done_chan, context)); - } - let response = response.unwrap(); - - // Step 19 - if let Some(status) = response.status { - if status == StatusCode::NotModified && - (http_request.cache_mode.get() == CacheMode::Default || - http_request.cache_mode.get() == CacheMode::NoCache) { - // Substep 1 - // TODO this substep - // let cached_response: Option<Response> = None; - - // Substep 2 - // if cached_response.is_none() { - // return Response::network_error(); - // } - - // Substep 3 - - // Substep 4 - // response = cached_response; - - // Substep 5 - // TODO cache_state is immutable? - // response.cache_state = CacheState::Validated; - } - } - - // Step 20 - response -} - -/// [HTTP network fetch](https://fetch.spec.whatwg.org/#http-network-fetch) -fn http_network_fetch(request: Rc<Request>, - credentials_flag: bool, - done_chan: &mut DoneChannel, - context: &FetchContext) - -> Response { - // TODO: Implement HTTP network fetch spec - - // Step 1 - // nothing to do here, since credentials_flag is already a boolean - - // Step 2 - // TODO be able to create connection using current url's origin and credentials - let connection = create_http_connector(); - - // Step 3 - // TODO be able to tell if the connection is a failure - - // Step 4 - let factory = NetworkHttpRequestFactory { - connector: connection, - }; - let url = request.current_url(); - - let request_id = context.devtools_chan.as_ref().map(|_| { - uuid::Uuid::new_v4().simple().to_string() - }); - - // XHR uses the default destination; other kinds of fetches (which haven't been implemented yet) - // do not. Once we support other kinds of fetches we'll need to be more fine grained here - // since things like image fetches are classified differently by devtools - let is_xhr = request.destination == Destination::None; - let wrapped_response = obtain_response(&factory, &url, &request.method.borrow(), - &request.headers.borrow(), - &request.body.borrow(), &request.method.borrow(), - &request.pipeline_id.get(), request.redirect_count.get() + 1, - request_id.as_ref().map(Deref::deref), is_xhr); - - let pipeline_id = request.pipeline_id.get(); - let (res, msg) = match wrapped_response { - Ok(wrapped_response) => wrapped_response, - Err(error) => { - let error = match error.error { - LoadErrorType::ConnectionAborted { .. } => unreachable!(), - LoadErrorType::Ssl { reason } => NetworkError::SslValidation(error.url, reason), - e => NetworkError::Internal(e.description().to_owned()) - }; - return Response::network_error(error); - } - }; - - let mut response = Response::new(url.clone()); - response.status = Some(res.response.status); - response.raw_status = Some((res.response.status_raw().0, - res.response.status_raw().1.as_bytes().to_vec())); - response.headers = res.response.headers.clone(); - response.referrer = request.referrer.borrow().to_url().cloned(); - - let res_body = response.body.clone(); - - // We're about to spawn a thread to be waited on here - *done_chan = Some(channel()); - let meta = match response.metadata().expect("Response metadata should exist at this stage") { - FetchMetadata::Unfiltered(m) => m, - FetchMetadata::Filtered { unsafe_, .. } => unsafe_ - }; - let done_sender = done_chan.as_ref().map(|ch| ch.0.clone()); - let devtools_sender = context.devtools_chan.clone(); - let meta_status = meta.status.clone(); - let meta_headers = meta.headers.clone(); - spawn_named(format!("fetch worker thread"), move || { - match StreamedResponse::from_http_response(box res, meta) { - Ok(mut res) => { - *res_body.lock().unwrap() = ResponseBody::Receiving(vec![]); - - if let Some(ref sender) = devtools_sender { - if let Some(m) = msg { - send_request_to_devtools(m, &sender); - } - - // --- Tell devtools that we got a response - // Send an HttpResponse message to devtools with the corresponding request_id - if let Some(pipeline_id) = pipeline_id { - send_response_to_devtools( - &sender, request_id.unwrap(), - meta_headers.map(Serde::into_inner), - meta_status, - pipeline_id); - } - } - - loop { - match read_block(&mut res) { - Ok(Data::Payload(chunk)) => { - if let ResponseBody::Receiving(ref mut body) = *res_body.lock().unwrap() { - body.extend_from_slice(&chunk); - if let Some(ref sender) = done_sender { - let _ = sender.send(Data::Payload(chunk)); - } - } - }, - Ok(Data::Done) | Err(_) => { - let mut empty_vec = Vec::new(); - let completed_body = match *res_body.lock().unwrap() { - ResponseBody::Receiving(ref mut body) => { - // avoid cloning the body - swap(body, &mut empty_vec); - empty_vec - }, - _ => empty_vec, - }; - *res_body.lock().unwrap() = ResponseBody::Done(completed_body); - if let Some(ref sender) = done_sender { - let _ = sender.send(Data::Done); - } - break; - } - } - } - } - Err(_) => { - // XXXManishearth we should propagate this error somehow - *res_body.lock().unwrap() = ResponseBody::Done(vec![]); - if let Some(ref sender) = done_sender { - let _ = sender.send(Data::Done); - } - } - } - }); - - // TODO these substeps aren't possible yet - // Substep 1 - - // Substep 2 - - // TODO Determine if response was retrieved over HTTPS - // TODO Servo needs to decide what ciphers are to be treated as "deprecated" - response.https_state = HttpsState::None; - - // TODO Read request - - // Step 5-9 - // (needs stream bodies) - - // Step 10 - // TODO when https://bugzilla.mozilla.org/show_bug.cgi?id=1030660 - // is resolved, this step will become uneccesary - // TODO this step - if let Some(encoding) = response.headers.get::<ContentEncoding>() { - if encoding.contains(&Encoding::Gzip) { - } - - else if encoding.contains(&Encoding::Compress) { - } - }; - - // Step 11 - // TODO this step isn't possible yet (CSP) - - // Step 12 - if response.is_network_error() && request.cache_mode.get() == CacheMode::NoStore { - // TODO update response in the HTTP cache for request - } - - // TODO this step isn't possible yet - // Step 13 - - // Step 14. - if credentials_flag { - set_cookies_from_headers(&url, &response.headers, &context.state.cookie_jar); - } - - // TODO these steps - // Step 15 - // Substep 1 - // Substep 2 - // Sub-substep 1 - // Sub-substep 2 - // Sub-substep 3 - // Sub-substep 4 - // Substep 3 - - // Step 16 - response -} - -/// [CORS preflight fetch](https://fetch.spec.whatwg.org#cors-preflight-fetch) -fn cors_preflight_fetch(request: Rc<Request>, - cache: &mut CorsCache, - context: &FetchContext) - -> Response { - // Step 1 - let mut preflight = Request::new(request.current_url(), Some(request.origin.borrow().clone()), - request.is_service_worker_global_scope, request.pipeline_id.get()); - *preflight.method.borrow_mut() = Method::Options; - preflight.initiator = request.initiator.clone(); - preflight.type_ = request.type_.clone(); - preflight.destination = request.destination.clone(); - *preflight.referrer.borrow_mut() = request.referrer.borrow().clone(); - preflight.referrer_policy.set(request.referrer_policy.get()); - - // Step 2 - preflight.headers.borrow_mut().set::<AccessControlRequestMethod>( - AccessControlRequestMethod(request.method.borrow().clone())); - - // Step 3, 4 - let mut value = request.headers.borrow().iter() - .filter_map(|ref view| if is_simple_header(view) { - None - } else { - Some(UniCase(view.name().to_owned())) - }).collect::<Vec<UniCase<String>>>(); - value.sort(); - - // Step 5 - preflight.headers.borrow_mut().set::<AccessControlRequestHeaders>( - AccessControlRequestHeaders(value)); - - // Step 6 - let preflight = Rc::new(preflight); - let response = http_network_or_cache_fetch(preflight.clone(), false, false, &mut None, context); - - // Step 7 - if cors_check(request.clone(), &response).is_ok() && - response.status.map_or(false, |status| status.is_success()) { - // Substep 1 - let mut methods = if response.headers.has::<AccessControlAllowMethods>() { - match response.headers.get::<AccessControlAllowMethods>() { - Some(&AccessControlAllowMethods(ref m)) => m.clone(), - // Substep 3 - None => return Response::network_error(NetworkError::Internal("CORS ACAM check failed".into())) - } - } else { - vec![] - }; - - // Substep 2 - let header_names = if response.headers.has::<AccessControlAllowHeaders>() { - match response.headers.get::<AccessControlAllowHeaders>() { - Some(&AccessControlAllowHeaders(ref hn)) => hn.clone(), - // Substep 3 - None => return Response::network_error(NetworkError::Internal("CORS ACAH check failed".into())) - } - } else { - vec![] - }; - - // Substep 4 - if methods.is_empty() && request.use_cors_preflight { - methods = vec![request.method.borrow().clone()]; - } - - // Substep 5 - debug!("CORS check: Allowed methods: {:?}, current method: {:?}", - methods, request.method.borrow()); - if methods.iter().all(|method| *method != *request.method.borrow()) && - !is_simple_method(&*request.method.borrow()) { - return Response::network_error(NetworkError::Internal("CORS method check failed".into())); - } - - // Substep 6 - debug!("CORS check: Allowed headers: {:?}, current headers: {:?}", - header_names, request.headers.borrow()); - let set: HashSet<&UniCase<String>> = HashSet::from_iter(header_names.iter()); - if request.headers.borrow().iter().any(|ref hv| !set.contains(&UniCase(hv.name().to_owned())) && - !is_simple_header(hv)) { - return Response::network_error(NetworkError::Internal("CORS headers check failed".into())); - } - - // Substep 7, 8 - let max_age = response.headers.get::<AccessControlMaxAge>().map(|acma| acma.0).unwrap_or(0); - - // TODO: Substep 9 - Need to define what an imposed limit on max-age is - - // Substep 11, 12 - for method in &methods { - cache.match_method_and_update(&*request, method.clone(), max_age); - } - - // Substep 13, 14 - for header_name in &header_names { - cache.match_header_and_update(&*request, &*header_name, max_age); - } - - // Substep 15 - return response; - } - - // Step 8 - Response::network_error(NetworkError::Internal("CORS check failed".into())) -} - -/// [CORS check](https://fetch.spec.whatwg.org#concept-cors-check) -fn cors_check(request: Rc<Request>, response: &Response) -> Result<(), ()> { - // Step 1 - let origin = response.headers.get::<AccessControlAllowOrigin>().cloned(); - - // Step 2 - let origin = try!(origin.ok_or(())); - - // Step 3 - if request.credentials_mode != CredentialsMode::Include && - origin == AccessControlAllowOrigin::Any { - return Ok(()); - } - - // Step 4 - let origin = match origin { - AccessControlAllowOrigin::Value(origin) => origin, - // if it's Any or Null at this point, there's nothing to do but return Err(()) - _ => return Err(()) - }; - - match *request.origin.borrow() { - Origin::Origin(ref o) if o.ascii_serialization() == origin => {}, - _ => return Err(()) - } - - // Step 5 - if request.credentials_mode != CredentialsMode::Include { - return Ok(()); - } - - // Step 6 - let credentials = request.headers.borrow().get::<AccessControlAllowCredentials>().cloned(); - - // Step 7 - if credentials.is_some() { - return Ok(()); - } - - // Step 8 - Err(()) -} - -fn has_credentials(url: &ServoUrl) -> bool { - !url.username().is_empty() || url.password().is_some() -} - -fn is_no_store_cache(headers: &Headers) -> bool { - headers.has::<IfModifiedSince>() | headers.has::<IfNoneMatch>() | - headers.has::<IfUnmodifiedSince>() | headers.has::<IfMatch>() | - headers.has::<IfRange>() -} - /// https://fetch.spec.whatwg.org/#cors-safelisted-request-header -fn is_simple_header(h: &HeaderView) -> bool { +pub fn is_simple_header(h: &HeaderView) -> bool { if h.is::<ContentType>() { match h.value() { Some(&ContentType(Mime(TopLevel::Text, SubLevel::Plain, _))) | @@ -1328,18 +485,13 @@ fn is_simple_header(h: &HeaderView) -> bool { } } -fn is_simple_method(m: &Method) -> bool { +pub fn is_simple_method(m: &Method) -> bool { match *m { Method::Get | Method::Head | Method::Post => true, _ => false } } -fn response_needs_revalidation(_response: &Response) -> bool { - // TODO this function - false -} - // fn modify_request_headers(headers: &mut Headers) -> { // // TODO this function |