diff --git a/components/net/fetch/methods.rs b/components/net/fetch/methods.rs index 16d64a86a7c..b357edae05e 100644 --- a/components/net/fetch/methods.rs +++ b/components/net/fetch/methods.rs @@ -5,20 +5,24 @@ use connector::create_http_connector; use data_loader::decode; use fetch::cors_cache::CORSCache; -use http_loader::{NetworkHttpRequestFactory, ReadResult, obtain_response, read_block}; +use http_loader::{HttpState, set_default_accept_encoding, set_request_cookies}; +use http_loader::{NetworkHttpRequestFactory, ReadResult, StreamedResponse, obtain_response, read_block}; +use http_loader::{auth_from_cache, determine_request_referrer}; 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, IfMatch, IfRange, IfUnmodifiedSince, IfModifiedSince}; +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 hyper::method::Method; use hyper::mime::{Mime, SubLevel, TopLevel}; use hyper::status::StatusCode; use mime_guess::guess_mime_type; -use net_traits::AsyncFetchListener; -use net_traits::request::{CacheMode, CredentialsMode, Type, Origin, Window}; +use msg::constellation_msg::ReferrerPolicy; +use net_traits::FetchTaskTarget; +use net_traits::request::{CacheMode, CredentialsMode}; use net_traits::request::{RedirectMode, Referer, Request, RequestMode, ResponseTainting}; +use net_traits::request::{Type, Origin, Window}; use net_traits::response::{HttpsState, TerminationReason}; use net_traits::response::{Response, ResponseBody, ResponseType}; use resource_thread::CancellationListener; @@ -26,27 +30,36 @@ use std::collections::HashSet; use std::fs::File; use std::io::Read; use std::iter::FromIterator; +use std::mem::swap; use std::rc::Rc; -use std::thread; +use std::sync::mpsc::{channel, Sender, Receiver}; use unicase::UniCase; use url::{Origin as UrlOrigin, Url}; use util::thread::spawn_named; -pub fn fetch_async(request: Request, listener: Box) { - spawn_named(format!("fetch for {:?}", request.current_url_string()), move || { - let request = Rc::new(request); - let fetch_response = fetch(request); - fetch_response.wait_until_done(); - listener.response_available(fetch_response); - }) +pub type Target = Option>; + +enum Data { + Payload(Vec), + Done, } +pub struct FetchContext { + pub state: HttpState, + pub user_agent: String, +} + +type DoneChannel = Option<(Sender, Receiver)>; + /// [Fetch](https://fetch.spec.whatwg.org#concept-fetch) -pub fn fetch(request: Rc) -> Response { - fetch_with_cors_cache(request, &mut CORSCache::new()) +pub fn fetch(request: Rc, target: &mut Target, context: FetchContext) -> Response { + fetch_with_cors_cache(request, &mut CORSCache::new(), target, context) } -pub fn fetch_with_cors_cache(request: Rc, cache: &mut CORSCache) -> Response { +pub fn fetch_with_cors_cache(request: Rc, + cache: &mut CORSCache, + target: &mut Target, + context: FetchContext) -> Response { // Step 1 if request.window.get() == Window::Client { // TODO: Set window to request's client object if client is a Window object @@ -105,12 +118,15 @@ pub fn fetch_with_cors_cache(request: Rc, cache: &mut CORSCache) -> Res if request.is_subresource_request() { // TODO: create a fetch record and append it to request's client's fetch group list } + // Step 7 - main_fetch(request, cache, false, false) + main_fetch(request, cache, false, false, target, &mut None, &context) } /// [Main fetch](https://fetch.spec.whatwg.org/#concept-main-fetch) -fn main_fetch(request: Rc, cache: &mut CORSCache, cors_flag: bool, recursive_flag: bool) -> Response { +fn main_fetch(request: Rc, 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 @@ -128,23 +144,39 @@ fn main_fetch(request: Rc, cache: &mut CORSCache, cors_flag: bool, recu // TODO be able to execute report CSP // Step 4 - // TODO this step, based off of http_loader.rs + // TODO this step, based off of http_loader.rs (upgrade) // Step 5 - // TODO this step + // TODO this step (CSP port/content blocking) // Step 6 - if request.referer != Referer::NoReferer { - // TODO be able to invoke "determine request's referer" - } + // TODO this step (referer policy) + // currently the clients themselves set referer policy in RequestInit // Step 7 - // TODO this step + if request.referrer_policy.get().is_none() { + request.referrer_policy.set(Some(ReferrerPolicy::NoRefWhenDowngrade)); + } // Step 8 - // this step is obsoleted by fetch_async + if *request.referer.borrow() != Referer::NoReferer { + // remove Referer headers set in past redirects/preflights + // this stops the assertion in determine_request_referrer from failing + request.headers.borrow_mut().remove::(); + let referrer_url = determine_request_referrer(&mut *request.headers.borrow_mut(), + request.referrer_policy.get(), + request.referer.borrow_mut().take(), + request.current_url().clone()); + *request.referer.borrow_mut() = Referer::from_url(referrer_url); + } // Step 9 + // TODO this step (HSTS) + + // Step 10 + // this step is obsoleted by fetch_async + + // Step 11 let response = match response { Some(response) => response, None => { @@ -160,14 +192,14 @@ fn main_fetch(request: Rc, cache: &mut CORSCache, cors_flag: bool, recu (current_url.scheme() == "file" && request.same_origin_data.get()) || current_url.scheme() == "about" || request.mode == RequestMode::Navigate { - basic_fetch(request.clone(), cache) + basic_fetch(request.clone(), cache, target, done_chan, context) } else if request.mode == RequestMode::SameOrigin { Response::network_error() } else if request.mode == RequestMode::NoCORS { request.response_tainting.set(ResponseTainting::Opaque); - basic_fetch(request.clone(), cache) + basic_fetch(request.clone(), cache, target, done_chan, context) } else if !matches!(current_url.scheme(), "http" | "https") { Response::network_error() @@ -178,7 +210,7 @@ fn main_fetch(request: Rc, cache: &mut CORSCache, cors_flag: bool, recu request.headers.borrow().iter().any(|h| !is_simple_header(&h)))) { request.response_tainting.set(ResponseTainting::CORSTainting); request.redirect_mode.set(RedirectMode::Error); - let response = http_fetch(request.clone(), cache, true, true, false); + let response = http_fetch(request.clone(), cache, true, true, false, target, done_chan, context); if response.is_network_error() { // TODO clear cache entries using request } @@ -186,17 +218,17 @@ fn main_fetch(request: Rc, cache: &mut CORSCache, cors_flag: bool, recu } else { request.response_tainting.set(ResponseTainting::CORSTainting); - http_fetch(request.clone(), cache, true, false, false) + http_fetch(request.clone(), cache, true, false, false, target, done_chan, context) } } }; - // Step 10 + // Step 12 if recursive_flag { return response; } - // Step 11 + // Step 13 // no need to check if response is a network error, since the type would not be `Default` let response = if response.response_type == ResponseType::Default { let response_type = match request.response_tainting.get() { @@ -210,7 +242,7 @@ fn main_fetch(request: Rc, cache: &mut CORSCache, cors_flag: bool, recu }; { - // Step 12 + // Step 14 let network_error_res = Response::network_error(); let internal_response = if response.is_network_error() { &network_error_res @@ -218,10 +250,15 @@ fn main_fetch(request: Rc, cache: &mut CORSCache, cors_flag: bool, recu response.actual_response() }; - // Step 13 - // TODO this step + // Step 15 + if internal_response.url_list.borrow().is_empty() { + *internal_response.url_list.borrow_mut() = request.url_list.borrow().clone(); + } - // Step 14 + // Step 16 + // TODO this step (CSP/blocking) + + // Step 17 if !response.is_network_error() && (is_null_body_status(&internal_response.status) || match *request.method.borrow() { Method::Head | Method::Connect => true, @@ -233,7 +270,7 @@ fn main_fetch(request: Rc, cache: &mut CORSCache, cors_flag: bool, recu *body = ResponseBody::Empty; } - // Step 15 + // Step 18 // TODO be able to compare response integrity against request integrity metadata // if !response.is_network_error() { @@ -248,34 +285,91 @@ fn main_fetch(request: Rc, cache: &mut CORSCache, cors_flag: bool, recu // } } - // Step 16 + // Step 19 if request.synchronous { - response.actual_response().wait_until_done(); + if let Some(ref mut target) = *target { + // process_response is not supposed to be used + // by sync fetch, but we overload it here for simplicity + target.process_response(&response); + } + + if let Some(ref ch) = *done_chan { + loop { + match ch.1.recv() + .expect("fetch worker should always send Done before terminating") { + Data::Payload(vec) => { + if let Some(ref mut target) = *target { + target.process_response_chunk(vec); + } + } + Data::Done => break, + } + } + } else if let ResponseBody::Done(ref vec) = *response.body.lock().unwrap() { + // in case there was no channel to wait for, the body was + // obtained synchronously via basic_fetch for data/file/about/etc + // We should still send the body across as a chunk + if let Some(ref mut target) = *target { + target.process_response_chunk(vec.clone()); + } + } else { + assert!(*response.body.lock().unwrap() == ResponseBody::Empty) + } + + // overloaded similarly to process_response + if let Some(ref mut target) = *target { + target.process_response_eof(&response); + } return response; } - // Step 17 + // Step 20 if request.body.borrow().is_some() && matches!(request.current_url().scheme(), "http" | "https") { - // TODO queue a fetch task on request to process end-of-file + if let Some(ref mut target) = *target { + // XXXManishearth: We actually should be calling process_request + // in http_network_fetch. However, we can't yet follow the request + // upload progress, so I'm keeping it here for now and pretending + // the body got sent in one chunk + target.process_request_body(&request); + target.process_request_eof(&request); + } } - { - // Step 12 repeated to use internal_response - let network_error_res = Response::network_error(); - let internal_response = if response.is_network_error() { - &network_error_res + // Step 21 + if let Some(ref mut target) = *target { + target.process_response(&response); + } + + // Step 22 + if let Some(ref ch) = *done_chan { + loop { + match ch.1.recv() + .expect("fetch worker should always send Done before terminating") { + Data::Payload(vec) => { + if let Some(ref mut target) = *target { + target.process_response_chunk(vec); + } + } + Data::Done => break, + } + } + } else if let Some(ref mut target) = *target { + if let ResponseBody::Done(ref vec) = *response.body.lock().unwrap() { + // in case there was no channel to wait for, the body was + // obtained synchronously via basic_fetch for data/file/about/etc + // We should still send the body across as a chunk + target.process_response_chunk(vec.clone()); } else { - response.actual_response() - }; + assert!(*response.body.lock().unwrap() == ResponseBody::Empty) + } + } - // Step 18 - // TODO this step + // Step 23 + request.done.set(true); - // Step 19 - internal_response.wait_until_done(); - - // Step 20 - // TODO this step + // Step 24 + if let Some(ref mut target) = *target { + target.process_response_eof(&response); } // TODO remove this line when only asynchronous fetches are used @@ -283,19 +377,23 @@ fn main_fetch(request: Rc, cache: &mut CORSCache, cors_flag: bool, recu } /// [Basic fetch](https://fetch.spec.whatwg.org#basic-fetch) -fn basic_fetch(request: Rc, cache: &mut CORSCache) -> Response { +fn basic_fetch(request: Rc, cache: &mut CORSCache, + target: &mut Target, done_chan: &mut DoneChannel, + context: &FetchContext) -> Response { let url = request.current_url(); match url.scheme() { "about" if url.path() == "blank" => { let mut response = Response::new(); + // https://github.com/whatwg/fetch/issues/312 + response.url = Some(url); response.headers.set(ContentType(mime!(Text / Html; Charset = Utf8))); *response.body.lock().unwrap() = ResponseBody::Done(vec![]); response }, "http" | "https" => { - http_fetch(request.clone(), cache, false, false, false) + http_fetch(request.clone(), cache, false, false, false, target, done_chan, context) }, "data" => { @@ -303,6 +401,8 @@ fn basic_fetch(request: Rc, cache: &mut CORSCache) -> Response { match decode(&url) { Ok((mime, bytes)) => { let mut response = Response::new(); + // https://github.com/whatwg/fetch/issues/312 + response.url = Some(url.clone()); *response.body.lock().unwrap() = ResponseBody::Done(bytes); response.headers.set(ContentType(mime)); response @@ -324,6 +424,8 @@ fn basic_fetch(request: Rc, cache: &mut CORSCache) -> Response { let mime = guess_mime_type(file_path); let mut response = Response::new(); + // https://github.com/whatwg/fetch/issues/312 + response.url = Some(url.clone()); *response.body.lock().unwrap() = ResponseBody::Done(bytes); response.headers.set(ContentType(mime)); response @@ -350,7 +452,12 @@ fn http_fetch(request: Rc, cache: &mut CORSCache, cors_flag: bool, cors_preflight_flag: bool, - authentication_fetch_flag: bool) -> Response { + 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 = None; @@ -378,17 +485,18 @@ fn http_fetch(request: Rc, } // Substep 4 - let actual_response = res.actual_response(); - if actual_response.url_list.borrow().is_empty() { - *actual_response.url_list.borrow_mut() = request.url_list.borrow().clone(); - } - - // Substep 5 // 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 { @@ -403,7 +511,7 @@ fn http_fetch(request: Rc, // Sub-substep 1 if method_mismatch || header_mismatch { - let preflight_result = cors_preflight_fetch(request.clone(), cache); + let preflight_result = cors_preflight_fetch(request.clone(), cache, context); // Sub-substep 2 if preflight_result.response_type == ResponseType::Error { return Response::network_error(); @@ -415,17 +523,10 @@ fn http_fetch(request: Rc, request.skip_service_worker.set(true); // Substep 3 - let credentials = match request.credentials_mode { - CredentialsMode::Include => true, - CredentialsMode::CredentialsSameOrigin if request.response_tainting.get() == ResponseTainting::Basic - => true, - _ => false - }; + let fetch_result = http_network_or_cache_fetch(request.clone(), credentials, authentication_fetch_flag, + done_chan, context); // Substep 4 - let fetch_result = http_network_or_cache_fetch(request.clone(), credentials, authentication_fetch_flag); - - // Substep 5 if cors_flag && cors_check(request.clone(), &fetch_result).is_err() { return Response::network_error(); } @@ -450,7 +551,8 @@ fn http_fetch(request: Rc, RedirectMode::Follow => { // set back to default response.return_internal.set(true); - http_redirect_fetch(request, cache, Rc::new(response), cors_flag) + http_redirect_fetch(request, cache, Rc::new(response), + cors_flag, target, done_chan, context) } } }, @@ -459,7 +561,7 @@ fn http_fetch(request: Rc, StatusCode::Unauthorized => { // Step 1 // FIXME: Figure out what to do with request window objects - if cors_flag || request.credentials_mode != CredentialsMode::Include { + if cors_flag || !credentials { return response; } @@ -469,10 +571,15 @@ fn http_fetch(request: Rc, // 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); + return http_fetch(request, cache, cors_flag, cors_preflight_flag, + true, target, done_chan, context); } // Code 407 @@ -485,11 +592,16 @@ fn http_fetch(request: Rc, // 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); + // return http_fetch(request, cache, + // cors_flag, cors_preflight_flag, + // authentication_fetch_flag, target, + // done_chan, context); } _ => { } @@ -510,7 +622,10 @@ fn http_fetch(request: Rc, fn http_redirect_fetch(request: Rc, cache: &mut CORSCache, response: Rc, - cors_flag: bool) -> Response { + cors_flag: bool, + target: &mut Target, + done_chan: &mut DoneChannel, + context: &FetchContext) -> Response { // Step 1 assert_eq!(response.return_internal.get(), true); @@ -584,20 +699,22 @@ fn http_redirect_fetch(request: Rc, request.url_list.borrow_mut().push(location_url); // Step 15 - main_fetch(request, cache, cors_flag, true) + 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, credentials_flag: bool, - authentication_fetch_flag: bool) -> Response { + 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::Follow { - request.clone() + request.redirect_mode.get() == RedirectMode::Error { + request } else { Rc::new((*request).clone()) }; @@ -621,9 +738,8 @@ fn http_network_or_cache_fetch(request: Rc, } // Step 6 - match http_request.referer { - Referer::NoReferer => - http_request.headers.borrow_mut().set(RefererHeader("".to_owned())), + match *http_request.referer.borrow() { + Referer::NoReferer => (), Referer::RefererUrl(ref http_request_referer) => http_request.headers.borrow_mut().set(RefererHeader(http_request_referer.to_string())), Referer::Client => @@ -640,7 +756,7 @@ fn http_network_or_cache_fetch(request: Rc, // Step 8 if !http_request.headers.borrow().has::() { - http_request.headers.borrow_mut().set(UserAgent(global_user_agent().to_owned())); + http_request.headers.borrow_mut().set(UserAgent(context.user_agent.clone())); } match http_request.cache_mode.get() { @@ -670,34 +786,53 @@ fn http_network_or_cache_fetch(request: Rc, _ => {} } + let current_url = http_request.current_url(); // Step 12 - // modify_request_headers(http_request.headers.borrow()); + // todo: pass referrer url and policy + // this can only be uncommented when the referer 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::>() { // Substep 3 let mut authorization_value = None; // Substep 4 - // TODO be able to retrieve https://fetch.spec.whatwg.org/#authentication-entry + if let Some(basic) = auth_from_cache(&context.state.auth_cache, ¤t_url) { + if !http_request.use_url_credentials || !has_credentials(¤t_url) { + authorization_value = Some(basic); + } + } // Substep 5 - if authentication_fetch_flag { - let current_url = http_request.current_url(); - - authorization_value = if has_credentials(¤t_url) { - Some(Basic { + 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) }) - } else { - None } } @@ -753,7 +888,7 @@ fn http_network_or_cache_fetch(request: Rc, // Step 18 if response.is_none() { - response = Some(http_network_fetch(request.clone(), http_request.clone(), credentials_flag)); + response = Some(http_network_fetch(http_request.clone(), credentials_flag, done_chan)); } let response = response.unwrap(); @@ -788,8 +923,8 @@ fn http_network_or_cache_fetch(request: Rc, /// [HTTP network fetch](https://fetch.spec.whatwg.org/#http-network-fetch) fn http_network_fetch(request: Rc, - _http_request: Rc, - _credentials_flag: bool) -> Response { + _credentials_flag: bool, + done_chan: &mut DoneChannel) -> Response { // TODO: Implement HTTP network fetch spec // Step 1 @@ -811,42 +946,70 @@ fn http_network_fetch(request: Rc, let wrapped_response = obtain_response(&factory, &url, &request.method.borrow(), &request.headers.borrow(), - &cancellation_listener, &None, &request.method.borrow(), - &None, request.redirect_count.get(), &None, ""); + &cancellation_listener, &request.body.borrow(), &request.method.borrow(), + &None, request.redirect_count.get() + 1, &None, ""); let mut response = Response::new(); match wrapped_response { - Ok((mut res, _)) => { - response.url = Some(res.response.url.clone()); + Ok((res, _)) => { + response.url = Some(url.clone()); response.status = Some(res.response.status); + response.raw_status = Some(res.response.status_raw().clone()); response.headers = res.response.headers.clone(); let res_body = response.body.clone(); - thread::spawn(move || { - *res_body.lock().unwrap() = ResponseBody::Receiving(vec![]); - loop { - match read_block(&mut res.response) { - Ok(ReadResult::Payload(ref mut chunk)) => { - if let ResponseBody::Receiving(ref mut body) = *res_body.lock().unwrap() { - body.append(chunk); + // We're about to spawn a thread to be waited on here + *done_chan = Some(channel()); + let meta = response.metadata().expect("Response metadata should exist at this stage"); + let done_sender = done_chan.as_ref().map(|ch| ch.0.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![]); + loop { + match read_block(&mut res) { + Ok(ReadResult::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(ReadResult::EOF) | 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; + } } - }, - Ok(ReadResult::EOF) | Err(_) => { - let completed_body = match *res_body.lock().unwrap() { - ResponseBody::Receiving(ref body) => (*body).clone(), - _ => vec![] - }; - *res_body.lock().unwrap() = ResponseBody::Done(completed_body); - 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); } } - } }); }, - Err(_) => - response.termination_reason = Some(TerminationReason::Fatal) + Err(_) => { + response.termination_reason = Some(TerminationReason::Fatal); + } }; // TODO these substeps aren't possible yet @@ -860,7 +1023,10 @@ fn http_network_fetch(request: Rc, // TODO Read request - // Step 5 + // 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 @@ -872,22 +1038,19 @@ fn http_network_fetch(request: Rc, } }; - // Step 6 - *response.url_list.borrow_mut() = request.url_list.borrow().clone(); + // Step 11 + // TODO this step isn't possible yet (CSP) - // Step 7 - // TODO this step isn't possible yet - - // Step 8 + // 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 9 + // Step 13 // TODO these steps - // Step 10 + // Step 14 // Substep 1 // Substep 2 // Sub-substep 1 @@ -896,19 +1059,20 @@ fn http_network_fetch(request: Rc, // Sub-substep 4 // Substep 3 - // Step 11 + // Step 15 response } /// [CORS preflight fetch](https://fetch.spec.whatwg.org#cors-preflight-fetch) -fn cors_preflight_fetch(request: Rc, cache: &mut CORSCache) -> Response { +fn cors_preflight_fetch(request: Rc, cache: &mut CORSCache, context: &FetchContext) -> Response { // Step 1 let mut preflight = Request::new(request.current_url(), Some(request.origin.borrow().clone()), false); *preflight.method.borrow_mut() = Method::Options; preflight.initiator = request.initiator.clone(); preflight.type_ = request.type_.clone(); preflight.destination = request.destination.clone(); - preflight.referer = request.referer.clone(); + *preflight.referer.borrow_mut() = request.referer.borrow().clone(); + preflight.referrer_policy.set(preflight.referrer_policy.get()); // Step 2 preflight.headers.borrow_mut().set::( @@ -929,7 +1093,7 @@ fn cors_preflight_fetch(request: Rc, cache: &mut CORSCache) -> Response // Step 6 let preflight = Rc::new(preflight); - let response = http_network_or_cache_fetch(preflight.clone(), false, false); + let response = http_network_or_cache_fetch(preflight.clone(), false, false, &mut None, context); // Step 7 if cors_check(request.clone(), &response).is_ok() && @@ -962,12 +1126,16 @@ fn cors_preflight_fetch(request: Rc, cache: &mut CORSCache) -> Response } // 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(); } // Substep 6 + debug!("CORS check: Allowed headers: {:?}, current headers: {:?}", + header_names, request.headers.borrow()); let set: HashSet<&UniCase> = 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)) { @@ -1040,12 +1208,6 @@ fn cors_check(request: Rc, response: &Response) -> Result<(), ()> { Err(()) } -fn global_user_agent() -> String { - // TODO have a better useragent string - const USER_AGENT_STRING: &'static str = "Servo"; - USER_AGENT_STRING.to_owned() -} - fn has_credentials(url: &Url) -> bool { !url.username().is_empty() || url.password().is_some() } @@ -1056,6 +1218,7 @@ fn is_no_store_cache(headers: &Headers) -> bool { headers.has::() } +/// https://fetch.spec.whatwg.org/#cors-safelisted-request-header fn is_simple_header(h: &HeaderView) -> bool { if h.is::() { match h.value() { diff --git a/components/net/http_loader.rs b/components/net/http_loader.rs index f94b6ffce32..2855e38bf45 100644 --- a/components/net/http_loader.rs +++ b/components/net/http_loader.rs @@ -370,7 +370,7 @@ impl Error for LoadErrorType { } } -fn set_default_accept_encoding(headers: &mut Headers) { +pub fn set_default_accept_encoding(headers: &mut Headers) { if headers.has::() { return } @@ -434,10 +434,10 @@ fn strip_url(mut referrer_url: Url, origin_only: bool) -> Option { } /// https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer -fn determine_request_referrer(headers: &mut Headers, - referrer_policy: Option, - referrer_url: Option, - url: Url) -> Option { +pub fn determine_request_referrer(headers: &mut Headers, + referrer_policy: Option, + referrer_url: Option, + url: Url) -> Option { //TODO - algorithm step 2 not addressed assert!(!headers.has::()); if let Some(ref_url) = referrer_url { @@ -453,9 +453,9 @@ fn determine_request_referrer(headers: &mut Headers, return None; } -pub fn set_request_cookies(url: Url, headers: &mut Headers, cookie_jar: &Arc>) { +pub fn set_request_cookies(url: &Url, headers: &mut Headers, cookie_jar: &Arc>) { let mut cookie_jar = cookie_jar.write().unwrap(); - if let Some(cookie_list) = cookie_jar.cookies_for_url(&url, CookieSource::HTTP) { + if let Some(cookie_list) = cookie_jar.cookies_for_url(url, CookieSource::HTTP) { let mut v = Vec::new(); v.push(cookie_list.into_bytes()); headers.set_raw("Cookie".to_owned(), v); @@ -540,7 +540,7 @@ impl StreamedResponse { StreamedResponse { metadata: m, decoder: d } } - fn from_http_response(response: Box, m: Metadata) -> Result { + pub fn from_http_response(response: Box, m: Metadata) -> Result { let decoder = match response.content_encoding() { Some(Encoding::Gzip) => { let result = GzDecoder::new(response); @@ -627,10 +627,7 @@ fn request_must_be_secured(url: &Url, hsts_list: &Arc>) -> bool pub fn modify_request_headers(headers: &mut Headers, url: &Url, user_agent: &str, - cookie_jar: &Arc>, - auth_cache: &Arc>, - load_data: &LoadData, - block_cookies: bool, + referrer_policy: Option, referrer_url: &mut Option) { // Ensure that the host header is set from the original url let host = Host { @@ -654,23 +651,13 @@ pub fn modify_request_headers(headers: &mut Headers, set_default_accept_encoding(headers); *referrer_url = determine_request_referrer(headers, - load_data.referrer_policy.clone(), + referrer_policy.clone(), referrer_url.clone(), url.clone()); if let Some(referer_val) = referrer_url.clone() { headers.set(Referer(referer_val.into_string())); } - - // https://fetch.spec.whatwg.org/#concept-http-network-or-cache-fetch step 11 - if load_data.credentials_flag { - if !block_cookies { - set_request_cookies(url.clone(), headers, cookie_jar); - } - - // https://fetch.spec.whatwg.org/#http-network-or-cache-fetch step 12 - set_auth_header(headers, url, auth_cache); - } } fn set_auth_header(headers: &mut Headers, @@ -680,18 +667,21 @@ fn set_auth_header(headers: &mut Headers, if let Some(auth) = auth_from_url(url) { headers.set(auth); } else { - if let Some(ref auth_entry) = auth_cache.read().unwrap().entries.get(url) { - auth_from_entry(&auth_entry, headers); + if let Some(basic) = auth_from_cache(auth_cache, url) { + headers.set(Authorization(basic)); } } } } -fn auth_from_entry(auth_entry: &AuthCacheEntry, headers: &mut Headers) { - let user_name = auth_entry.user_name.clone(); - let password = Some(auth_entry.password.clone()); - - headers.set(Authorization(Basic { username: user_name, password: password })); +pub fn auth_from_cache(auth_cache: &Arc>, url: &Url) -> Option { + if let Some(ref auth_entry) = auth_cache.read().unwrap().entries.get(url) { + let user_name = auth_entry.user_name.clone(); + let password = Some(auth_entry.password.clone()); + Some(Basic { username: user_name, password: password }) + } else { + None + } } fn auth_from_url(doc_url: &Url) -> Option> { @@ -956,9 +946,18 @@ pub fn load(load_data: &LoadData, let request_id = uuid::Uuid::new_v4().simple().to_string(); modify_request_headers(&mut request_headers, &doc_url, - &user_agent, &http_state.cookie_jar, - &http_state.auth_cache, &load_data, - block_cookies, &mut referrer_url); + &user_agent, load_data.referrer_policy, + &mut referrer_url); + + // https://fetch.spec.whatwg.org/#concept-http-network-or-cache-fetch step 11 + if load_data.credentials_flag { + if !block_cookies { + set_request_cookies(&doc_url, &mut request_headers, &http_state.cookie_jar); + } + + // https://fetch.spec.whatwg.org/#http-network-or-cache-fetch step 12 + set_auth_header(&mut request_headers, &doc_url, &http_state.auth_cache); + } //if there is a new auth header then set the request headers with it if let Some(ref auth_header) = new_auth_header { diff --git a/components/net/resource_thread.rs b/components/net/resource_thread.rs index fecbff3e077..6e94fd1cf03 100644 --- a/components/net/resource_thread.rs +++ b/components/net/resource_thread.rs @@ -11,6 +11,7 @@ use cookie; use cookie_storage::CookieStorage; use data_loader; use devtools_traits::DevtoolsControlMsg; +use fetch::methods::{fetch, FetchContext}; use file_loader; use filemanager_thread::FileManagerThreadFactory; use hsts::HstsList; @@ -22,9 +23,11 @@ use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; use mime_classifier::{ApacheBugFlag, MIMEClassifier, NoSniffFlag}; use net_traits::LoadContext; use net_traits::ProgressMsg::Done; +use net_traits::request::{Request, RequestInit}; use net_traits::{AsyncResponseTarget, Metadata, ProgressMsg, ResponseAction, CoreResourceThread}; -use net_traits::{CoreResourceMsg, CookieSource, LoadConsumer, LoadData, LoadResponse, ResourceId}; -use net_traits::{NetworkError, WebSocketCommunicate, WebSocketConnectData, ResourceThreads}; +use net_traits::{CoreResourceMsg, CookieSource, FetchResponseMsg, FetchTaskTarget, LoadConsumer}; +use net_traits::{LoadData, LoadResponse, NetworkError, ResourceId}; +use net_traits::{WebSocketCommunicate, WebSocketConnectData, ResourceThreads}; use profile_traits::time::ProfilerChan; use rustc_serialize::json; use rustc_serialize::{Decodable, Encodable}; @@ -36,6 +39,7 @@ use std::error::Error; use std::fs::File; use std::io::prelude::*; use std::path::Path; +use std::rc::Rc; use std::sync::mpsc::{Receiver, Sender, channel}; use std::sync::{Arc, RwLock}; use storage_thread::StorageThreadFactory; @@ -193,6 +197,8 @@ impl ResourceChannelManager { match self.from_client.recv().unwrap() { CoreResourceMsg::Load(load_data, consumer, id_sender) => self.resource_manager.load(load_data, consumer, id_sender, control_sender.clone()), + CoreResourceMsg::Fetch(init, sender) => + self.resource_manager.fetch(init, sender), CoreResourceMsg::WebsocketConnect(connect, connect_data) => self.resource_manager.websocket_connect(connect, connect_data), CoreResourceMsg::SetCookiesForUrl(request, cookie_list, source) => @@ -480,6 +486,26 @@ impl CoreResourceManager { cancel_listener)); } + fn fetch(&self, init: RequestInit, sender: IpcSender) { + let http_state = HttpState { + hsts_list: self.hsts_list.clone(), + cookie_jar: self.cookie_jar.clone(), + auth_cache: self.auth_cache.clone(), + blocked_content: BLOCKED_CONTENT_RULES.clone(), + }; + let ua = self.user_agent.clone(); + spawn_named(format!("fetch thread for {}", init.url), move || { + let request = Request::from_init(init); + // XXXManishearth: Check origin against pipeline id (also ensure that the mode is allowed) + // todo load context / mimesniff in fetch + // todo referrer policy? + // todo service worker stuff + let mut target = Some(Box::new(sender) as Box); + let context = FetchContext { state: http_state, user_agent: ua }; + fetch(Rc::new(request), &mut target, context); + }) + } + fn websocket_connect(&self, connect: WebSocketCommunicate, connect_data: WebSocketConnectData) { diff --git a/components/net/websocket_loader.rs b/components/net/websocket_loader.rs index 60d6c8932f6..d748e309d20 100644 --- a/components/net/websocket_loader.rs +++ b/components/net/websocket_loader.rs @@ -43,7 +43,7 @@ fn establish_a_websocket_connection(resource_url: &Url, net_url: (Host, String, request.headers.set(WebSocketProtocol(protocols.clone())); }; - http_loader::set_request_cookies(resource_url.clone(), &mut request.headers, &cookie_jar); + http_loader::set_request_cookies(&resource_url, &mut request.headers, &cookie_jar); let response = try!(request.send()); try!(response.validate()); diff --git a/components/net_traits/lib.rs b/components/net_traits/lib.rs index 4af820b0ebd..bb8638d7c3c 100644 --- a/components/net_traits/lib.rs +++ b/components/net_traits/lib.rs @@ -36,6 +36,8 @@ use hyper::method::Method; use hyper::mime::{Attr, Mime}; use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; use msg::constellation_msg::{PipelineId, ReferrerPolicy}; +use request::{Request, RequestInit}; +use response::{HttpsState, Response}; use std::io::Error as IOError; use std::sync::mpsc::Sender; use std::thread; @@ -112,6 +114,7 @@ pub struct LoadData { pub headers: Headers, #[ignore_heap_size_of = "Defined in hyper"] /// Headers that will apply to the initial request and any redirects + /// Unused in fetch pub preserved_headers: Headers, pub data: Option>, pub cors: Option, @@ -154,9 +157,81 @@ pub trait LoadOrigin { fn pipeline_id(&self) -> Option; } -/// Interface for observing the final response for an asynchronous fetch operation. -pub trait AsyncFetchListener { - fn response_available(&self, response: response::Response); +#[derive(Deserialize, Serialize)] +pub enum FetchResponseMsg { + // todo: should have fields for transmitted/total bytes + ProcessRequestBody, + ProcessRequestEOF, + // todo: send more info about the response (or perhaps the entire Response) + ProcessResponse(Result), + ProcessResponseChunk(Vec), + ProcessResponseEOF(Result<(), NetworkError>), +} + +pub trait FetchTaskTarget { + /// https://fetch.spec.whatwg.org/#process-request-body + /// + /// Fired when a chunk of the request body is transmitted + fn process_request_body(&mut self, request: &Request); + + /// https://fetch.spec.whatwg.org/#process-request-end-of-file + /// + /// Fired when the entire request finishes being transmitted + fn process_request_eof(&mut self, request: &Request); + + /// https://fetch.spec.whatwg.org/#process-response + /// + /// Fired when headers are received + fn process_response(&mut self, response: &Response); + + /// Fired when a chunk of response content is received + fn process_response_chunk(&mut self, chunk: Vec); + + /// https://fetch.spec.whatwg.org/#process-response-end-of-file + /// + /// Fired when the response is fully fetched + fn process_response_eof(&mut self, response: &Response); +} + +pub trait FetchResponseListener { + fn process_request_body(&mut self); + fn process_request_eof(&mut self); + fn process_response(&mut self, metadata: Result); + fn process_response_chunk(&mut self, chunk: Vec); + fn process_response_eof(&mut self, response: Result<(), NetworkError>); +} + +impl FetchTaskTarget for IpcSender { + fn process_request_body(&mut self, _: &Request) { + let _ = self.send(FetchResponseMsg::ProcessRequestBody); + } + + fn process_request_eof(&mut self, _: &Request) { + let _ = self.send(FetchResponseMsg::ProcessRequestEOF); + } + + fn process_response(&mut self, response: &Response) { + let _ = self.send(FetchResponseMsg::ProcessResponse(response.metadata())); + } + + fn process_response_chunk(&mut self, chunk: Vec) { + let _ = self.send(FetchResponseMsg::ProcessResponseChunk(chunk)); + } + + fn process_response_eof(&mut self, response: &Response) { + if response.is_network_error() { + // todo: finer grained errors + let _ = self.send(FetchResponseMsg::ProcessResponseEOF( + Err(NetworkError::Internal("Network error".into())))); + } else { + let _ = self.send(FetchResponseMsg::ProcessResponseEOF(Ok(()))); + } + } +} + + +pub trait Action { + fn process(self, listener: &mut Listener); } /// A listener for asynchronous network events. Cancelling the underlying request is unsupported. @@ -183,9 +258,9 @@ pub enum ResponseAction { ResponseComplete(Result<(), NetworkError>) } -impl ResponseAction { +impl Action for ResponseAction { /// Execute the default action on a provided listener. - pub fn process(self, listener: &mut AsyncResponseListener) { + fn process(self, listener: &mut T) { match self { ResponseAction::HeadersAvailable(m) => listener.headers_available(m), ResponseAction::DataAvailable(d) => listener.data_available(d), @@ -194,6 +269,19 @@ impl ResponseAction { } } +impl Action for FetchResponseMsg { + /// Execute the default action on a provided listener. + fn process(self, listener: &mut T) { + match self { + FetchResponseMsg::ProcessRequestBody => listener.process_request_body(), + FetchResponseMsg::ProcessRequestEOF => listener.process_request_eof(), + FetchResponseMsg::ProcessResponse(meta) => listener.process_response(meta), + FetchResponseMsg::ProcessResponseChunk(data) => listener.process_response_chunk(data), + FetchResponseMsg::ProcessResponseEOF(data) => listener.process_response_eof(data), + } + } +} + /// A target for async networking events. Commonly used to dispatch a runnable event to another /// thread storing the wrapped closure for later execution. #[derive(Deserialize, Serialize)] @@ -331,6 +419,7 @@ pub struct WebSocketConnectData { pub enum CoreResourceMsg { /// Request the data associated with a particular URL Load(LoadData, LoadConsumer, Option>), + Fetch(RequestInit, IpcSender), /// Try to make a websocket connection to a URL. WebsocketConnect(WebSocketCommunicate, WebSocketConnectData), /// Store a set of cookies for a given originating URL @@ -469,7 +558,7 @@ pub struct Metadata { pub status: Option, /// Is successful HTTPS connection - pub https_state: response::HttpsState, + pub https_state: HttpsState, } impl Metadata { @@ -482,7 +571,7 @@ impl Metadata { headers: None, // https://fetch.spec.whatwg.org/#concept-response-status-message status: Some(RawStatus(200, "OK".into())), - https_state: response::HttpsState::None, + https_state: HttpsState::None, } } diff --git a/components/net_traits/request.rs b/components/net_traits/request.rs index 675a78fd788..1fbcdabe868 100644 --- a/components/net_traits/request.rs +++ b/components/net_traits/request.rs @@ -4,7 +4,9 @@ use hyper::header::Headers; use hyper::method::Method; +use msg::constellation_msg::ReferrerPolicy; use std::cell::{Cell, RefCell}; +use std::mem::swap; use url::{Origin as UrlOrigin, Url}; /// An [initiator](https://fetch.spec.whatwg.org/#concept-request-initiator) @@ -25,7 +27,7 @@ pub enum Type { } /// A request [destination](https://fetch.spec.whatwg.org/#concept-request-destination) -#[derive(Copy, Clone, PartialEq)] +#[derive(Copy, Clone, PartialEq, Serialize, Deserialize)] pub enum Destination { None, Document, Embed, Font, Image, Manifest, Media, Object, Report, Script, ServiceWorker, @@ -43,12 +45,13 @@ pub enum Origin { #[derive(Clone, PartialEq)] pub enum Referer { NoReferer, + /// Default referer if nothing is specified Client, RefererUrl(Url) } /// A [request mode](https://fetch.spec.whatwg.org/#concept-request-mode) -#[derive(Copy, Clone, PartialEq)] +#[derive(Copy, Clone, PartialEq, Serialize, Deserialize)] pub enum RequestMode { Navigate, SameOrigin, @@ -57,7 +60,7 @@ pub enum RequestMode { } /// Request [credentials mode](https://fetch.spec.whatwg.org/#concept-request-credentials-mode) -#[derive(Copy, Clone, PartialEq)] +#[derive(Copy, Clone, PartialEq, Serialize, Deserialize)] pub enum CredentialsMode { Omit, CredentialsSameOrigin, @@ -106,6 +109,29 @@ pub enum CORSSettings { UseCredentials } +#[derive(Serialize, Deserialize, Clone)] +pub struct RequestInit { + pub method: Method, + pub url: Url, + pub headers: Headers, + pub unsafe_request: bool, + pub same_origin_data: bool, + pub body: Option>, + // TODO: client object + pub destination: Destination, + pub synchronous: bool, + pub mode: RequestMode, + pub use_cors_preflight: bool, + pub credentials_mode: CredentialsMode, + pub use_url_credentials: bool, + // this should actually be set by fetch, but fetch + // doesn't have info about the client right now + pub origin: Url, + // XXXManishearth these should be part of the client object + pub referer_url: Option, + pub referrer_policy: Option, +} + /// A [Request](https://fetch.spec.whatwg.org/#requests) as defined by the Fetch spec #[derive(Clone)] pub struct Request { @@ -130,8 +156,9 @@ pub struct Request { pub origin: RefCell, pub omit_origin_header: Cell, pub same_origin_data: Cell, - pub referer: Referer, - // TODO: referrer policy + /// https://fetch.spec.whatwg.org/#concept-request-referrer + pub referer: RefCell, + pub referrer_policy: Cell>, pub synchronous: bool, pub mode: RequestMode, pub use_cors_preflight: bool, @@ -145,7 +172,7 @@ pub struct Request { pub url_list: RefCell>, pub redirect_count: Cell, pub response_tainting: Cell, - pub done: Cell + pub done: Cell, } impl Request { @@ -169,7 +196,8 @@ impl Request { origin: RefCell::new(origin.unwrap_or(Origin::Client)), omit_origin_header: Cell::new(false), same_origin_data: Cell::new(false), - referer: Referer::Client, + referer: RefCell::new(Referer::Client), + referrer_policy: Cell::new(None), synchronous: false, mode: RequestMode::NoCORS, use_cors_preflight: false, @@ -185,6 +213,30 @@ impl Request { } } + pub fn from_init(init: RequestInit) -> Request { + let mut req = Request::new(init.url, + Some(Origin::Origin(init.origin.origin())), + false); + *req.method.borrow_mut() = init.method; + *req.headers.borrow_mut() = init.headers; + req.unsafe_request = init.unsafe_request; + req.same_origin_data.set(init.same_origin_data); + *req.body.borrow_mut() = init.body; + req.destination = init.destination; + req.synchronous = init.synchronous; + req.mode = init.mode; + req.use_cors_preflight = init.use_cors_preflight; + req.credentials_mode = init.credentials_mode; + req.use_url_credentials = init.use_url_credentials; + *req.referer.borrow_mut() = if let Some(url) = init.referer_url { + Referer::RefererUrl(url) + } else { + Referer::NoReferer + }; + req.referrer_policy.set(init.referrer_policy); + req + } + /// https://html.spec.whatwg.org/multipage/#create-a-potential-cors-request pub fn potential_cors_request(url: Url, cors_attribute_state: Option, @@ -207,7 +259,8 @@ impl Request { origin: RefCell::new(Origin::Client), omit_origin_header: Cell::new(false), same_origin_data: Cell::new(false), - referer: Referer::Client, + referer: RefCell::new(Referer::Client), + referrer_policy: Cell::new(None), synchronous: false, // Step 1-2 mode: match cors_attribute_state { @@ -258,3 +311,27 @@ impl Request { } } } + +impl Referer { + pub fn to_url(&self) -> Option<&Url> { + match *self { + Referer::NoReferer | Referer::Client => None, + Referer::RefererUrl(ref url) => Some(url) + } + } + pub fn from_url(url: Option) -> Self { + if let Some(url) = url { + Referer::RefererUrl(url) + } else { + Referer::NoReferer + } + } + pub fn take(&mut self) -> Option { + let mut new = Referer::Client; + swap(self, &mut new); + match new { + Referer::NoReferer | Referer::Client => None, + Referer::RefererUrl(url) => Some(url) + } + } +} diff --git a/components/net_traits/response.rs b/components/net_traits/response.rs index d107e219988..ab1d5b1c8f5 100644 --- a/components/net_traits/response.rs +++ b/components/net_traits/response.rs @@ -4,15 +4,17 @@ //! The [Response](https://fetch.spec.whatwg.org/#responses) object //! resulting from a [fetch operation](https://fetch.spec.whatwg.org/#concept-fetch) -use hyper::header::{AccessControlExposeHeaders, Headers}; +use hyper::header::{AccessControlExposeHeaders, ContentType, Headers}; +use hyper::http::RawStatus; use hyper::status::StatusCode; use std::ascii::AsciiExt; use std::cell::{Cell, RefCell}; use std::sync::{Arc, Mutex}; use url::Url; +use {Metadata, NetworkError}; /// [Response type](https://fetch.spec.whatwg.org/#concept-response-type) -#[derive(Clone, PartialEq, Copy, Debug)] +#[derive(Clone, PartialEq, Copy, Debug, Deserialize, Serialize)] pub enum ResponseType { Basic, CORS, @@ -23,7 +25,7 @@ pub enum ResponseType { } /// [Response termination reason](https://fetch.spec.whatwg.org/#concept-response-termination-reason) -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Deserialize, Serialize)] pub enum TerminationReason { EndUserAbort, Fatal, @@ -50,7 +52,7 @@ impl ResponseBody { /// [Cache state](https://fetch.spec.whatwg.org/#concept-response-cache-state) -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Deserialize, Serialize)] pub enum CacheState { None, Local, @@ -81,6 +83,7 @@ pub struct Response { pub url_list: RefCell>, /// `None` can be considered a StatusCode of `0`. pub status: Option, + pub raw_status: Option, pub headers: Headers, pub body: Arc>, pub cache_state: CacheState, @@ -100,6 +103,7 @@ impl Response { url: None, url_list: RefCell::new(Vec::new()), status: Some(StatusCode::Ok), + raw_status: Some(RawStatus(200, "OK".into())), headers: Headers::new(), body: Arc::new(Mutex::new(ResponseBody::Empty)), cache_state: CacheState::None, @@ -116,6 +120,7 @@ impl Response { url: None, url_list: RefCell::new(vec![]), status: None, + raw_status: None, headers: Headers::new(), body: Arc::new(Mutex::new(ResponseBody::Empty)), cache_state: CacheState::None, @@ -132,18 +137,6 @@ impl Response { } } - pub fn wait_until_done(&self) { - match self.response_type { - // since these response types can't hold a body, they should be considered done - ResponseType::Error | ResponseType::Opaque | ResponseType::OpaqueRedirect => {}, - _ => { - while !self.body.lock().unwrap().is_done() && !self.is_network_error() { - // loop until done - } - } - } - } - pub fn actual_response(&self) -> &Response { if self.return_internal.get() && self.internal_response.is_some() { &**self.internal_response.as_ref().unwrap() @@ -228,4 +221,25 @@ impl Response { response } + + pub fn metadata(&self) -> Result { + let mut metadata = if let Some(ref url) = self.url { + Metadata::default(url.clone()) + } else { + return Err(NetworkError::Internal("No url found in response".to_string())); + }; + + if self.is_network_error() { + return Err(NetworkError::Internal("Cannot extract metadata from network error".to_string())); + } + + metadata.set_content_type(match self.headers.get() { + Some(&ContentType(ref mime)) => Some(mime), + None => None + }); + metadata.headers = Some(self.headers.clone()); + metadata.status = self.raw_status.clone(); + metadata.https_state = self.https_state; + return Ok(metadata); + } } diff --git a/components/script/Cargo.toml b/components/script/Cargo.toml index 056bb6f72bb..95f1044fd36 100644 --- a/components/script/Cargo.toml +++ b/components/script/Cargo.toml @@ -60,7 +60,6 @@ smallvec = "0.1" string_cache = {version = "0.2.18", features = ["heap_size", "unstable"]} style = {path = "../style"} time = "0.1.12" -unicase = "1.0" url = {version = "1.0.0", features = ["heap_size", "query_encoding"]} util = {path = "../util"} uuid = {version = "0.2", features = ["v4"]} diff --git a/components/script/cors.rs b/components/script/cors.rs deleted file mode 100644 index f9d0bbbd264..00000000000 --- a/components/script/cors.rs +++ /dev/null @@ -1,490 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -//! A partial implementation of CORS -//! For now this library is XHR-specific. -//! For stuff involving ``, `