diff options
Diffstat (limited to 'components/net/fetch/methods.rs')
-rw-r--r-- | components/net/fetch/methods.rs | 200 |
1 files changed, 88 insertions, 112 deletions
diff --git a/components/net/fetch/methods.rs b/components/net/fetch/methods.rs index bec31fe81a2..edc6e3be45a 100644 --- a/components/net/fetch/methods.rs +++ b/components/net/fetch/methods.rs @@ -7,16 +7,15 @@ use data_loader::decode; use devtools_traits::DevtoolsControlMsg; use fetch::cors_cache::CorsCache; use filemanager_thread::FileManager; +use headers_core::HeaderMapExt; +use headers_ext::{AccessControlExposeHeaders, ContentType, Range}; +use http::header::{self, HeaderMap, HeaderName, HeaderValue}; 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; -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 hyper::Method; +use hyper::StatusCode; use ipc_channel::ipc::IpcReceiver; +use mime::{self, Mime}; use mime_guess::guess_mime_type; use net_traits::{FetchTaskTarget, NetworkError, ReferrerPolicy}; use net_traits::request::{CredentialsMode, Destination, Referrer, Request, RequestMode}; @@ -25,16 +24,20 @@ use net_traits::response::{Response, ResponseBody, ResponseType}; use servo_channel::{channel, Sender, Receiver}; use servo_url::ServoUrl; use std::borrow::Cow; -use std::fmt; use std::fs::File; use std::io::{BufReader, BufRead, Seek, SeekFrom}; use std::mem; +use std::ops::Bound; use std::str; use std::sync::{Arc, Mutex}; use std::sync::atomic::Ordering; use std::thread; use subresource_integrity::is_response_integrity_valid; +lazy_static! { + static ref X_CONTENT_TYPE_OPTIONS: HeaderName = HeaderName::from_static("x-content-type-options"); +} + const FILE_CHUNK_SIZE: usize = 32768; //32 KB pub type Target<'a> = &'a mut (FetchTaskTarget + Send); @@ -173,11 +176,11 @@ pub fn main_fetch(request: &mut Request, Referrer::Client => { // FIXME(#14507): We should never get this value here; it should // already have been handled in the script thread. - request.headers.remove::<RefererHeader>(); + request.headers.remove(header::REFERER); None }, Referrer::ReferrerUrl(url) => { - request.headers.remove::<RefererHeader>(); + request.headers.remove(header::REFERER); let current_url = request.current_url().clone(); determine_request_referrer(&mut request.headers, request.referrer_policy.unwrap(), @@ -238,7 +241,7 @@ pub fn main_fetch(request: &mut Request, } else if request.use_cors_preflight || (request.unsafe_request && (!is_cors_safelisted_method(&request.method) || - request.headers.iter().any(|h| !is_cors_safelisted_request_header(&h)))) { + request.headers.iter().any(|(name, value)| !is_cors_safelisted_request_header(&name, &value)))) { // Substep 1. request.response_tainting = ResponseTainting::CorsTainting; // Substep 2. @@ -269,18 +272,19 @@ pub fn main_fetch(request: &mut Request, // Substep 1. if request.response_tainting == ResponseTainting::CorsTainting { // Subsubstep 1. - let header_names = response.headers.get::<AccessControlExposeHeaders>(); + let header_names: Option<Vec<HeaderName>> = response.headers.typed_get::<AccessControlExposeHeaders>() + .map(|v| v.iter().collect()); match header_names { // Subsubstep 2. - Some(list) if request.credentials_mode != CredentialsMode::Include => { + Some(ref 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(); + response.headers.iter().map(|(name, _)| name.as_str().to_owned()).collect(); } }, // Subsubstep 3. Some(list) => { - response.cors_exposed_header_name_list = list.iter().map(|h| (**h).clone()).collect(); + response.cors_exposed_header_name_list = list.iter().map(|h| h.as_str().to_owned()).collect(); }, _ => (), } @@ -341,7 +345,7 @@ pub fn main_fetch(request: &mut Request, let not_network_error = !response_is_network_error && !internal_response.is_network_error(); if not_network_error && (is_null_body_status(&internal_response.status) || match request.method { - Method::Head | Method::Connect => true, + Method::HEAD | Method::CONNECT => true, _ => false }) { // when Fetch is used only asynchronously, we will need to make sure // that nothing tries to write to the body at this point @@ -463,7 +467,7 @@ fn scheme_fetch(request: &mut Request, match url.scheme() { "about" if url.path() == "blank" => { let mut response = Response::new(url); - response.headers.set(ContentType(mime!(Text / Html; Charset = Utf8))); + response.headers.typed_insert(ContentType::from(mime::TEXT_HTML_UTF_8)); *response.body.lock().unwrap() = ResponseBody::Done(vec![]); response }, @@ -477,7 +481,7 @@ fn scheme_fetch(request: &mut Request, Ok((mime, bytes)) => { let mut response = Response::new(url); *response.body.lock().unwrap() = ResponseBody::Done(bytes); - response.headers.set(ContentType(mime)); + response.headers.typed_insert(ContentType::from(mime)); response }, Err(_) => Response::network_error(NetworkError::Internal("Decoding data URL failed".into())) @@ -485,7 +489,7 @@ fn scheme_fetch(request: &mut Request, }, "file" => { - if request.method == Method::Get { + if request.method == Method::GET { match url.to_file_path() { Ok(file_path) => { match File::open(file_path.clone()) { @@ -493,7 +497,7 @@ fn scheme_fetch(request: &mut Request, let mime = guess_mime_type(file_path); let mut response = Response::new(url); - response.headers.set(ContentType(mime)); + response.headers.typed_insert(ContentType::from(mime)); let (done_sender, done_receiver) = channel(); *done_chan = Some((done_sender.clone(), done_receiver)); @@ -503,22 +507,22 @@ fn scheme_fetch(request: &mut Request, let cancellation_listener = context.cancellation_listener.clone(); - let range = request.headers.get::<header::Range>(); - let (start, end) = if let Some(&header::Range::Bytes(ref range)) = range { - match range.first().unwrap() { - &header::ByteRangeSpec::AllFrom(start) => (start, None), - &header::ByteRangeSpec::FromTo(start, end) => { + let (start, end) = if let Some(ref range) = request.headers.typed_get::<Range>() { + match range.iter().collect::<Vec<(Bound<u64>, Bound<u64>)>>().first() { + Some(&(Bound::Included(start), Bound::Unbounded)) => (start, None), + Some(&(Bound::Included(start), Bound::Included(end))) => { // `end` should be less or equal to `start`. (start, Some(u64::max(start, end))) }, - &header::ByteRangeSpec::Last(offset) => { + Some(&(Bound::Unbounded, Bound::Included(offset))) => { if let Ok(metadata) = file.metadata() { // `offset` cannot be bigger than the file size. (metadata.len() - u64::min(metadata.len(), offset), None) } else { (0, None) } - } + }, + _ => (0, None) } } else { (0, None) @@ -591,7 +595,7 @@ fn scheme_fetch(request: &mut Request, "blob" => { println!("Loading blob {}", url.as_str()); // Step 2. - if request.method != Method::Get { + if request.method != Method::GET { return Response::network_error(NetworkError::Internal("Unexpected method for blob".into())); } @@ -619,33 +623,33 @@ fn scheme_fetch(request: &mut Request, } /// <https://fetch.spec.whatwg.org/#cors-safelisted-request-header> -pub fn is_cors_safelisted_request_header(h: &HeaderView) -> bool { - if h.is::<ContentType>() { - match h.value() { - Some(&ContentType(Mime(TopLevel::Text, SubLevel::Plain, _))) | - Some(&ContentType(Mime(TopLevel::Application, SubLevel::WwwFormUrlEncoded, _))) | - Some(&ContentType(Mime(TopLevel::Multipart, SubLevel::FormData, _))) => true, - _ => false - +pub fn is_cors_safelisted_request_header(name: &HeaderName, value: &HeaderValue) -> bool { + if name == header::CONTENT_TYPE { + if let Some(m) = value.to_str().ok().and_then(|s| s.parse::<Mime>().ok()) { + m.type_() == mime::TEXT && m.subtype() == mime::PLAIN || + m.type_() == mime::APPLICATION && m.subtype() == mime::WWW_FORM_URLENCODED || + m.type_() == mime::MULTIPART && m.subtype() == mime::FORM_DATA + } else { + false } } else { - h.is::<Accept>() || h.is::<AcceptLanguage>() || h.is::<ContentLanguage>() + name == header::ACCEPT || name == header::ACCEPT_LANGUAGE || name == header::CONTENT_LANGUAGE } } /// <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, + Method::GET | Method::HEAD | Method::POST => true, _ => false } } -fn is_null_body_status(status: &Option<StatusCode>) -> bool { +fn is_null_body_status(status: &Option<(StatusCode, String)>) -> bool { match *status { - Some(status) => match status { - StatusCode::SwitchingProtocols | StatusCode::NoContent | - StatusCode::ResetContent | StatusCode::NotModified => true, + Some((status, _)) => match status { + StatusCode::SWITCHING_PROTOCOLS | StatusCode::NO_CONTENT | + StatusCode::RESET_CONTENT | StatusCode::NOT_MODIFIED => true, _ => false }, _ => false @@ -653,84 +657,56 @@ fn is_null_body_status(status: &Option<StatusCode>) -> bool { } /// <https://fetch.spec.whatwg.org/#should-response-to-request-be-blocked-due-to-nosniff?> -pub fn should_be_blocked_due_to_nosniff(destination: Destination, response_headers: &Headers) -> bool { - /// <https://fetch.spec.whatwg.org/#x-content-type-options-header> - /// This is needed to parse `X-Content-Type-Options` according to spec, - /// which requires that we inspect only the first value. - /// - /// A [unit-like struct](https://doc.rust-lang.org/book/structs.html#unit-like-structs) - /// is sufficient since a valid header implies that we use `nosniff`. - #[derive(Clone, Copy, Debug)] - struct XContentTypeOptions; - - impl Header for XContentTypeOptions { - fn header_name() -> &'static str { - "X-Content-Type-Options" - } - - /// https://fetch.spec.whatwg.org/#should-response-to-request-be-blocked-due-to-nosniff%3F #2 - fn parse_header(raw: &[Vec<u8>]) -> HyperResult<Self> { - raw.first() - .and_then(|v| str::from_utf8(v).ok()) - .and_then(|s| if s.trim().eq_ignore_ascii_case("nosniff") { - Some(XContentTypeOptions) - } else { - None - }) - .ok_or(Error::Header) - } - } - - impl HeaderFormat for XContentTypeOptions { - fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str("nosniff") - } - } - +pub fn should_be_blocked_due_to_nosniff(destination: Destination, response_headers: &HeaderMap) -> bool { // Steps 1-3. - if response_headers.get::<XContentTypeOptions>().is_none() { + // TODO(eijebong): Replace this once typed headers allow custom ones... + if response_headers.get("x-content-type-options") + .map_or(true, |val| val.to_str().unwrap_or("").to_lowercase() != "nosniff") + { return false; } // Step 4 // Note: an invalid MIME type will produce a `None`. - let content_type_header = response_headers.get::<ContentType>(); + let content_type_header = response_headers.typed_get::<ContentType>(); /// <https://html.spec.whatwg.org/multipage/#scriptingLanguages> #[inline] fn is_javascript_mime_type(mime_type: &Mime) -> bool { let javascript_mime_types: [Mime; 16] = [ - mime!(Application / ("ecmascript")), - mime!(Application / ("javascript")), - mime!(Application / ("x-ecmascript")), - mime!(Application / ("x-javascript")), - mime!(Text / ("ecmascript")), - mime!(Text / ("javascript")), - mime!(Text / ("javascript1.0")), - mime!(Text / ("javascript1.1")), - mime!(Text / ("javascript1.2")), - mime!(Text / ("javascript1.3")), - mime!(Text / ("javascript1.4")), - mime!(Text / ("javascript1.5")), - mime!(Text / ("jscript")), - mime!(Text / ("livescript")), - mime!(Text / ("x-ecmascript")), - mime!(Text / ("x-javascript")), + "application/ecmascript".parse().unwrap(), + "application/javascript".parse().unwrap(), + "application/x-ecmascript".parse().unwrap(), + "application/x-javascript".parse().unwrap(), + "text/ecmascript".parse().unwrap(), + "text/javascript".parse().unwrap(), + "text/javascript1.0".parse().unwrap(), + "text/javascript1.1".parse().unwrap(), + "text/javascript1.2".parse().unwrap(), + "text/javascript1.3".parse().unwrap(), + "text/javascript1.4".parse().unwrap(), + "text/javascript1.5".parse().unwrap(), + "text/jscript".parse().unwrap(), + "text/livescript".parse().unwrap(), + "text/x-ecmascript".parse().unwrap(), + "text/x-javascript".parse().unwrap(), ]; javascript_mime_types.iter() - .any(|mime| mime.0 == mime_type.0 && mime.1 == mime_type.1) + .any(|mime| mime.type_() == mime_type.type_() && mime.subtype() == mime_type.subtype()) } - // Assumes str::starts_with is equivalent to mime::TopLevel match content_type_header { // Step 6 - Some(&ContentType(ref mime_type)) if destination.is_script_like() - => !is_javascript_mime_type(mime_type), + Some(ref ct) if destination.is_script_like() + => !is_javascript_mime_type(&ct.clone().into()), // Step 7 - Some(&ContentType(Mime(ref tl, ref sl, _))) if destination == Destination::Style - => *tl != TopLevel::Text && *sl != SubLevel::Css, + Some(ref ct) if destination == Destination::Style + => { + let m: mime::Mime = ct.clone().into(); + m.type_() != mime::TEXT && m.subtype() != mime::CSS + }, None if destination == Destination::Style || destination.is_script_like() => true, // Step 8 @@ -739,23 +715,23 @@ pub fn should_be_blocked_due_to_nosniff(destination: Destination, response_heade } /// <https://fetch.spec.whatwg.org/#should-response-to-request-be-blocked-due-to-mime-type?> -fn should_be_blocked_due_to_mime_type(destination: Destination, response_headers: &Headers) -> bool { +fn should_be_blocked_due_to_mime_type(destination: Destination, response_headers: &HeaderMap) -> bool { // Step 1 - let mime_type = match response_headers.get::<ContentType>() { - Some(header) => header, + let mime_type: mime::Mime = match response_headers.typed_get::<ContentType>() { + Some(header) => header.into(), None => return false, }; // Step 2-3 - destination.is_script_like() && match *mime_type { - ContentType(Mime(TopLevel::Audio, _, _)) | - ContentType(Mime(TopLevel::Video, _, _)) | - ContentType(Mime(TopLevel::Image, _, _)) => true, - ContentType(Mime(TopLevel::Text, SubLevel::Ext(ref ext), _)) => ext == "csv", - - // Step 4 - _ => false, - } + destination.is_script_like() && + match mime_type.type_() { + mime::AUDIO | + mime::VIDEO | + mime::IMAGE => true, + mime::TEXT if mime_type.subtype() == mime::CSV => true, + // Step 4 + _ => false + } } /// <https://fetch.spec.whatwg.org/#block-bad-port> |