diff options
Diffstat (limited to 'components/net')
-rw-r--r-- | components/net/Cargo.toml | 27 | ||||
-rw-r--r-- | components/net/blob_loader.rs | 44 | ||||
-rw-r--r-- | components/net/connector.rs | 157 | ||||
-rw-r--r-- | components/net/cookie.rs | 8 | ||||
-rw-r--r-- | components/net/cookie_storage.rs | 12 | ||||
-rw-r--r-- | components/net/data_loader.rs | 5 | ||||
-rw-r--r-- | components/net/fetch/cors_cache.rs | 17 | ||||
-rw-r--r-- | components/net/fetch/methods.rs | 200 | ||||
-rw-r--r-- | components/net/http_cache.rs | 336 | ||||
-rw-r--r-- | components/net/http_loader.rs | 692 | ||||
-rw-r--r-- | components/net/lib.rs | 7 | ||||
-rw-r--r-- | components/net/mime_classifier.rs | 244 | ||||
-rw-r--r-- | components/net/resource_thread.rs | 11 | ||||
-rw-r--r-- | components/net/subresource_integrity.rs | 5 | ||||
-rw-r--r-- | components/net/tests/cookie.rs | 12 | ||||
-rw-r--r-- | components/net/tests/cookie_http_state.rs | 12 | ||||
-rw-r--r-- | components/net/tests/data_loader.rs | 37 | ||||
-rw-r--r-- | components/net/tests/fetch.rs | 412 | ||||
-rw-r--r-- | components/net/tests/http_loader.rs | 626 | ||||
-rw-r--r-- | components/net/tests/main.rs | 112 | ||||
-rw-r--r-- | components/net/tests/mime_classifier.rs | 305 | ||||
-rw-r--r-- | components/net/websocket_loader.rs | 35 |
22 files changed, 1618 insertions, 1698 deletions
diff --git a/components/net/Cargo.toml b/components/net/Cargo.toml index 402044c1ecd..099f2c17f71 100644 --- a/components/net/Cargo.toml +++ b/components/net/Cargo.toml @@ -13,15 +13,19 @@ test = false doctest = false [dependencies] -base64 = "0.6" -brotli = "1.0.6" -cookie = "0.10" +base64 = "0.9" +brotli = "2.5" +bytes = "0.4" +cookie = "0.11" devtools_traits = {path = "../devtools_traits"} embedder_traits = { path = "../embedder_traits" } flate2 = "1" -hyper = "0.10" -hyper_serde = "0.8" -hyper-openssl = "0.2.2" +headers-core = "0.0.1" +headers-ext = "0.0.3" +http = "0.1" +hyper = "0.12" +hyper_serde = "0.9" +hyper-openssl = "0.6" immeta = "0.4" ipc-channel = "0.11" lazy_static = "1" @@ -29,11 +33,11 @@ log = "0.4" malloc_size_of = { path = "../malloc_size_of" } malloc_size_of_derive = { path = "../malloc_size_of_derive" } matches = "0.1" -mime = "0.2.1" -mime_guess = "1.8.0" +mime = "0.3" +mime_guess = "2.0.0-alpha.6" msg = {path = "../msg"} net_traits = {path = "../net_traits"} -openssl = "0.9" +openssl = "0.10" pixels = {path = "../pixels"} profile_traits = {path = "../profile_traits"} serde = "1.0" @@ -43,9 +47,10 @@ servo_arc = {path = "../servo_arc"} servo_channel = {path = "../channel"} servo_config = {path = "../config"} servo_url = {path = "../url"} +tokio = "0.1" +tokio-timer = "0.2" threadpool = "1.0" time = "0.1.17" -unicase = "1.4.0" url = "1.2" uuid = {version = "0.6", features = ["v4"]} webrender_api = {git = "https://github.com/servo/webrender", features = ["ipc"]} @@ -53,6 +58,8 @@ ws = { version = "0.7", features = ["ssl"] } [dev-dependencies] std_test_override = { path = "../std_test_override" } +futures = "0.1" +tokio-openssl = "0.2" [[test]] name = "main" diff --git a/components/net/blob_loader.rs b/components/net/blob_loader.rs index 1fe05cc1c20..3af4d531549 100644 --- a/components/net/blob_loader.rs +++ b/components/net/blob_loader.rs @@ -3,11 +3,13 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use filemanager_thread::FileManager; -use hyper::header::{Charset, ContentLength, ContentType, Headers}; -use hyper::header::{ContentDisposition, DispositionParam, DispositionType}; +use headers_core::HeaderMapExt; +use headers_ext::{ContentLength, ContentType}; +use http::HeaderMap; +use http::header::{self, HeaderValue}; use ipc_channel::ipc; -use mime::{Attr, Mime}; -use net_traits::NetworkError; +use mime::{self, Mime}; +use net_traits::{http_percent_encode, NetworkError}; use net_traits::blob_url_store::parse_blob_url; use net_traits::filemanager_thread::ReadFileProgress; use servo_url::ServoUrl; @@ -20,7 +22,7 @@ use servo_url::ServoUrl; pub fn load_blob_sync (url: ServoUrl, filemanager: FileManager) - -> Result<(Headers, Vec<u8>), NetworkError> { + -> Result<(HeaderMap, Vec<u8>), NetworkError> { let (id, origin) = match parse_blob_url(&url) { Ok((id, origin)) => (id, origin), Err(()) => { @@ -43,26 +45,32 @@ pub fn load_blob_sync } }; - let content_type: Mime = blob_buf.type_string.parse().unwrap_or(mime!(Text / Plain)); - let charset = content_type.get_param(Attr::Charset); + let content_type: Mime = blob_buf.type_string.parse().unwrap_or(mime::TEXT_PLAIN); + let charset = content_type.get_param(mime::CHARSET); - let mut headers = Headers::new(); + let mut headers = HeaderMap::new(); if let Some(name) = blob_buf.filename { - let charset = charset.and_then(|c| c.as_str().parse().ok()); - headers.set(ContentDisposition { - disposition: DispositionType::Inline, - parameters: vec![ - DispositionParam::Filename(charset.unwrap_or(Charset::Us_Ascii), - None, name.as_bytes().to_vec()) - ] - }); + let charset = charset.map(|c| c.as_ref().into()).unwrap_or("us-ascii".to_owned()); + // TODO(eijebong): Replace this once the typed header is there + headers.insert( + header::CONTENT_DISPOSITION, + HeaderValue::from_bytes( + format!("inline; {}", + if charset.to_lowercase() == "utf-8" { + format!("filename=\"{}\"", String::from_utf8(name.as_bytes().into()).unwrap()) + } else { + format!("filename*=\"{}\"''{}", charset, http_percent_encode(name.as_bytes())) + } + ).as_bytes() + ).unwrap() + ); } // Basic fetch, Step 4. - headers.set(ContentLength(blob_buf.size as u64)); + headers.typed_insert(ContentLength(blob_buf.size as u64)); // Basic fetch, Step 5. - headers.set(ContentType(content_type.clone())); + headers.typed_insert(ContentType::from(content_type.clone())); let mut bytes = blob_buf.bytes; loop { diff --git a/components/net/connector.rs b/components/net/connector.rs index 03ee7e5459d..0311b05b125 100644 --- a/components/net/connector.rs +++ b/components/net/connector.rs @@ -2,60 +2,132 @@ * 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/. */ +use flate2::read::GzDecoder; use hosts::replace_host; -use hyper::client::Pool; -use hyper::error::{Result as HyperResult, Error as HyperError}; -use hyper::net::{NetworkConnector, HttpsStream, HttpStream, SslClient}; -use hyper_openssl::OpensslClient; -use openssl::ssl::{SSL_OP_NO_COMPRESSION, SSL_OP_NO_SSLV2, SSL_OP_NO_SSLV3}; -use openssl::ssl::{SslConnector, SslConnectorBuilder, SslMethod}; +use http_loader::Decoder; +use hyper::{Body, Client}; +use hyper::body::Payload; +use hyper::client::HttpConnector as HyperHttpConnector; +use hyper::client::connect::{Connect, Destination}; +use hyper::rt::Future; +use hyper_openssl::HttpsConnector; +use openssl::ssl::{SslConnector, SslConnectorBuilder, SslMethod, SslOptions}; use openssl::x509; -use std::io; -use std::net::TcpStream; +use std::io::{Cursor, Read}; +use tokio::prelude::{Async, Stream}; +use tokio::prelude::future::Executor; -pub struct HttpsConnector { - ssl: OpensslClient, +pub const BUF_SIZE: usize = 32768; + +pub struct HttpConnector { + inner: HyperHttpConnector } -impl HttpsConnector { - fn new(ssl: OpensslClient) -> HttpsConnector { - HttpsConnector { - ssl: ssl, +impl HttpConnector { + fn new() -> HttpConnector { + let mut inner = HyperHttpConnector::new(4); + inner.enforce_http(false); + inner.set_happy_eyeballs_timeout(None); + HttpConnector { + inner } } } -impl NetworkConnector for HttpsConnector { - type Stream = HttpsStream<<OpensslClient as SslClient>::Stream>; - - fn connect(&self, host: &str, port: u16, scheme: &str) -> HyperResult<Self::Stream> { - if scheme != "http" && scheme != "https" { - return Err(HyperError::Io(io::Error::new(io::ErrorKind::InvalidInput, - "Invalid scheme for Http"))); - } +impl Connect for HttpConnector { + type Transport = <HyperHttpConnector as Connect>::Transport; + type Error = <HyperHttpConnector as Connect>::Error; + type Future = <HyperHttpConnector as Connect>::Future; + fn connect(&self, dest: Destination) -> Self::Future { // Perform host replacement when making the actual TCP connection. - let addr = &(&*replace_host(host), port); - let stream = HttpStream(TcpStream::connect(addr)?); + let mut new_dest = dest.clone(); + let addr = replace_host(dest.host()); + new_dest.set_host(&*addr).unwrap(); + self.inner.connect(new_dest) + } +} - if scheme == "http" { - Ok(HttpsStream::Http(stream)) - } else { - // Do not perform host replacement on the host that is used - // for verifying any SSL certificate encountered. - self.ssl.wrap_client(stream, host).map(HttpsStream::Https) +pub type Connector = HttpsConnector<HttpConnector>; +pub struct WrappedBody { + pub body: Body, + pub decoder: Decoder, +} + +impl WrappedBody { + pub fn new(body: Body) -> Self { + Self::new_with_decoder(body, Decoder::Plain) + } + + pub fn new_with_decoder(body: Body, decoder: Decoder) -> Self { + WrappedBody { + body, + decoder, } } } -pub type Connector = HttpsConnector; +impl Payload for WrappedBody { + type Data = <Body as Payload>::Data; + type Error = <Body as Payload>::Error; + fn poll_data(&mut self) -> Result<Async<Option<Self::Data>>, Self::Error> { + self.body.poll_data() + } +} -pub fn create_ssl_connector(certs: &str) -> SslConnector { +impl Stream for WrappedBody { + type Item = <Body as Stream>::Item; + type Error = <Body as Stream>::Error; + fn poll(&mut self) -> Result<Async<Option<Self::Item>>, Self::Error> { + self.body.poll().map(|res| { + res.map(|maybe_chunk| { + if let Some(chunk) = maybe_chunk { + match self.decoder { + Decoder::Plain => Some(chunk), + Decoder::Gzip(Some(ref mut decoder)) => { + let mut buf = vec![0; BUF_SIZE]; + *decoder.get_mut() = Cursor::new(chunk.into_bytes()); + let len = decoder.read(&mut buf).ok()?; + buf.truncate(len); + Some(buf.into()) + } + Decoder::Gzip(None) => { + let mut buf = vec![0; BUF_SIZE]; + let mut decoder = GzDecoder::new(Cursor::new(chunk.into_bytes())); + let len = decoder.read(&mut buf).ok()?; + buf.truncate(len); + self.decoder = Decoder::Gzip(Some(decoder)); + Some(buf.into()) + } + Decoder::Deflate(ref mut decoder) => { + let mut buf = vec![0; BUF_SIZE]; + *decoder.get_mut() = Cursor::new(chunk.into_bytes()); + let len = decoder.read(&mut buf).ok()?; + buf.truncate(len); + Some(buf.into()) + } + Decoder::Brotli(ref mut decoder) => { + let mut buf = vec![0; BUF_SIZE]; + decoder.get_mut().get_mut().extend(&chunk.into_bytes()); + let len = decoder.read(&mut buf).ok()?; + buf.truncate(len); + Some(buf.into()) + } + } + } else { + None + } + }) + }) + } +} + +pub fn create_ssl_connector_builder(certs: &str) -> SslConnectorBuilder { // certs include multiple certificates. We could add all of them at once, // but if any of them were already added, openssl would fail to insert all // of them. let mut certs = certs; - let mut ssl_connector_builder = SslConnectorBuilder::new(SslMethod::tls()).unwrap(); + let mut ssl_connector_builder = SslConnector::builder(SslMethod::tls()).unwrap(); loop { let token = "-----END CERTIFICATE-----"; if let Some(index) = certs.find(token) { @@ -78,18 +150,17 @@ pub fn create_ssl_connector(certs: &str) -> SslConnector { } } ssl_connector_builder.set_cipher_list(DEFAULT_CIPHERS).expect("could not set ciphers"); - ssl_connector_builder.set_options(SSL_OP_NO_SSLV2 | SSL_OP_NO_SSLV3 | SSL_OP_NO_COMPRESSION); - ssl_connector_builder.build() -} - -pub fn create_ssl_client(certs: &str) -> OpensslClient { - let ssl_connector = create_ssl_connector(certs); - OpensslClient::from(ssl_connector) + ssl_connector_builder.set_options(SslOptions::NO_SSLV2 | SslOptions::NO_SSLV3 | SslOptions::NO_COMPRESSION); + ssl_connector_builder } -pub fn create_http_connector(ssl_client: OpensslClient) -> Pool<Connector> { - let https_connector = HttpsConnector::new(ssl_client); - Pool::with_connector(Default::default(), https_connector) +pub fn create_http_client<E>(ssl_connector_builder: SslConnectorBuilder, executor: E) + -> Client<Connector, WrappedBody> + where + E: Executor<Box<Future<Error=(), Item=()> + Send + 'static>> + Sync + Send + 'static +{ + let connector = HttpsConnector::with_connector(HttpConnector::new(), ssl_connector_builder).unwrap(); + Client::builder().http1_title_case_headers(true).executor(executor).build(connector) } // The basic logic here is to prefer ciphers with ECDSA certificates, Forward diff --git a/components/net/cookie.rs b/components/net/cookie.rs index e8e7be6733d..fb403628107 100644 --- a/components/net/cookie.rs +++ b/components/net/cookie.rs @@ -94,14 +94,14 @@ impl Cookie { // Step 10 - if cookie.http_only() && source == CookieSource::NonHTTP { + if cookie.http_only().unwrap_or(false) && source == CookieSource::NonHTTP { return None; } // https://tools.ietf.org/html/draft-west-cookie-prefixes-04#section-4 // Step 1 of cookie prefixes if (cookie.name().starts_with("__Secure-") || cookie.name().starts_with("__Host-")) && - !(cookie.secure() && request.is_secure_scheme()) + !(cookie.secure().unwrap_or(false) && request.is_secure_scheme()) { return None; } @@ -197,10 +197,10 @@ impl Cookie { } } - if self.cookie.secure() && !url.is_secure_scheme() { + if self.cookie.secure().unwrap_or(false) && !url.is_secure_scheme() { return false; } - if self.cookie.http_only() && source == CookieSource::NonHTTP { + if self.cookie.http_only().unwrap_or(false) && source == CookieSource::NonHTTP { return false; } diff --git a/components/net/cookie_storage.rs b/components/net/cookie_storage.rs index 4dc7075a9fd..cb066d416fe 100644 --- a/components/net/cookie_storage.rs +++ b/components/net/cookie_storage.rs @@ -38,7 +38,7 @@ impl CookieStorage { let cookies = self.cookies_map.entry(domain).or_insert(vec![]); // https://www.ietf.org/id/draft-ietf-httpbis-cookie-alone-01.txt Step 2 - if !cookie.cookie.secure() && !url.is_secure_scheme() { + if !cookie.cookie.secure().unwrap_or(false) && !url.is_secure_scheme() { let new_domain = cookie.cookie.domain().as_ref().unwrap().to_owned(); let new_path = cookie.cookie.path().as_ref().unwrap().to_owned(); @@ -47,7 +47,7 @@ impl CookieStorage { let existing_path = c.cookie.path().as_ref().unwrap().to_owned(); c.cookie.name() == cookie.cookie.name() && - c.cookie.secure() && + c.cookie.secure().unwrap_or(false) && (Cookie::domain_match(new_domain, existing_domain) || Cookie::domain_match(existing_domain, new_domain)) && Cookie::path_match(new_path, existing_path) @@ -70,7 +70,7 @@ impl CookieStorage { let c = cookies.remove(ind); // http://tools.ietf.org/html/rfc6265#section-5.3 step 11.2 - if c.cookie.http_only() && source == CookieSource::NonHTTP { + if c.cookie.http_only().unwrap_or(false) && source == CookieSource::NonHTTP { // Undo the removal. cookies.push(c); Err(()) @@ -85,7 +85,7 @@ impl CookieStorage { // http://tools.ietf.org/html/rfc6265#section-5.3 pub fn push(&mut self, mut cookie: Cookie, url: &ServoUrl, source: CookieSource) { // https://www.ietf.org/id/draft-ietf-httpbis-cookie-alone-01.txt Step 1 - if cookie.cookie.secure() && !url.is_secure_scheme() { + if cookie.cookie.secure().unwrap_or(false) && !url.is_secure_scheme() { return; } @@ -111,7 +111,7 @@ impl CookieStorage { let new_len = cookies.len(); // https://www.ietf.org/id/draft-ietf-httpbis-cookie-alone-01.txt - if new_len == old_len && !evict_one_cookie(cookie.cookie.secure(), cookies) { + if new_len == old_len && !evict_one_cookie(cookie.cookie.secure().unwrap_or(false), cookies) { return; } } @@ -219,7 +219,7 @@ fn evict_one_cookie(is_secure_cookie: bool, cookies: &mut Vec<Cookie>) -> bool { fn get_oldest_accessed(is_secure_cookie: bool, cookies: &mut Vec<Cookie>) -> Option<(usize, Tm)> { let mut oldest_accessed: Option<(usize, Tm)> = None; for (i, c) in cookies.iter().enumerate() { - if (c.cookie.secure() == is_secure_cookie) && + if (c.cookie.secure().unwrap_or(false) == is_secure_cookie) && oldest_accessed.as_ref().map_or(true, |a| c.last_access < a.1) { oldest_accessed = Some((i, c.last_access)); } diff --git a/components/net/data_loader.rs b/components/net/data_loader.rs index b95a2ab3412..16ffb75a323 100644 --- a/components/net/data_loader.rs +++ b/components/net/data_loader.rs @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use base64; -use hyper::mime::{Attr, Mime, SubLevel, TopLevel, Value}; +use mime::Mime; use servo_url::ServoUrl; use url::Position; use url::percent_encoding::percent_decode; @@ -37,8 +37,7 @@ pub fn decode(url: &ServoUrl) -> Result<DecodeData, DecodeError> { }; let content_type = ct_str.parse().unwrap_or_else(|_| { - Mime(TopLevel::Text, SubLevel::Plain, - vec![(Attr::Charset, Value::Ext("US-ASCII".to_owned()))]) + "text/plain; charset=US-ASCII".parse().unwrap() }); let mut bytes = percent_decode(parts[1].as_bytes()).collect::<Vec<_>>(); diff --git a/components/net/fetch/cors_cache.rs b/components/net/fetch/cors_cache.rs index 8962dd2c998..95e070cfc8e 100644 --- a/components/net/fetch/cors_cache.rs +++ b/components/net/fetch/cors_cache.rs @@ -9,7 +9,8 @@ //! This library will eventually become the core of the Fetch crate //! with CORSRequest being expanded into FetchRequest (etc) -use hyper::method::Method; +use http::header::HeaderName; +use hyper::Method; use net_traits::request::{CredentialsMode, Origin, Request}; use servo_url::ServoUrl; use time::{self, Timespec}; @@ -19,14 +20,14 @@ use time::{self, Timespec}; /// Each entry might pertain to a header or method #[derive(Clone, Debug)] pub enum HeaderOrMethod { - HeaderData(String), + HeaderData(HeaderName), MethodData(Method) } impl HeaderOrMethod { - fn match_header(&self, header_name: &str) -> bool { + fn match_header(&self, header_name: &HeaderName) -> bool { match *self { - HeaderOrMethod::HeaderData(ref s) => (&**s).eq_ignore_ascii_case(header_name), + HeaderOrMethod::HeaderData(ref n) => n == header_name, _ => false } } @@ -80,7 +81,7 @@ impl CorsCache { } fn find_entry_by_header<'a>(&'a mut self, request: &Request, - header_name: &str) -> Option<&'a mut CorsCacheEntry> { + header_name: &HeaderName) -> Option<&'a mut CorsCacheEntry> { self.cleanup(); self.0.iter_mut().find(|e| match_headers(e, request) && e.header_or_method.match_header(header_name)) } @@ -113,7 +114,7 @@ impl CorsCache { /// Returns true if an entry with a /// [matching header](https://fetch.spec.whatwg.org/#concept-cache-match-header) is found - pub fn match_header(&mut self, request: &Request, header_name: &str) -> bool { + pub fn match_header(&mut self, request: &Request, header_name: &HeaderName) -> bool { self.find_entry_by_header(&request, header_name).is_some() } @@ -122,13 +123,13 @@ impl CorsCache { /// /// If not, it will insert an equivalent entry pub fn match_header_and_update(&mut self, request: &Request, - header_name: &str, new_max_age: u32) -> bool { + header_name: &HeaderName, new_max_age: u32) -> bool { match self.find_entry_by_header(&request, header_name).map(|e| e.max_age = new_max_age) { Some(_) => true, None => { self.insert(CorsCacheEntry::new(request.origin.clone(), request.current_url(), new_max_age, request.credentials_mode == CredentialsMode::Include, - HeaderOrMethod::HeaderData(header_name.to_owned()))); + HeaderOrMethod::HeaderData(header_name.clone()))); false } } 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> diff --git a/components/net/http_cache.rs b/components/net/http_cache.rs index 1fcafc92952..dbb777eb103 100644 --- a/components/net/http_cache.rs +++ b/components/net/http_cache.rs @@ -8,12 +8,11 @@ //! and <http://tools.ietf.org/html/rfc7232>. use fetch::methods::{Data, DoneChannel}; -use hyper::header; -use hyper::header::ContentType; -use hyper::header::Headers; -use hyper::method::Method; -use hyper::status::StatusCode; -use hyper_serde::Serde; +use headers_core::HeaderMapExt; +use headers_ext::{CacheControl, ContentRange, Expires, LastModified, Pragma, Range, Vary}; +use http::{header, HeaderMap}; +use http::header::HeaderValue; +use hyper::{Method, StatusCode}; use malloc_size_of::{MallocSizeOf, MallocSizeOfOps, MallocUnconditionalSizeOf, MallocUnconditionalShallowSizeOf}; use malloc_size_of::Measurable; use net_traits::{Metadata, FetchMetadata}; @@ -24,11 +23,12 @@ use servo_channel::{Sender, channel}; use servo_config::prefs::PREFS; use servo_url::ServoUrl; use std::collections::HashMap; -use std::str; +use std::ops::Bound; use std::sync::Mutex; use std::sync::atomic::{AtomicBool, Ordering}; +use std::time::SystemTime; use time; -use time::{Duration, Tm}; +use time::{Duration, Timespec, Tm}; /// The key used to differentiate requests in the cache. @@ -59,7 +59,7 @@ impl CacheKey { /// A complete cached resource. #[derive(Clone)] struct CachedResource { - request_headers: Arc<Mutex<Headers>>, + request_headers: Arc<Mutex<HeaderMap>>, body: Arc<Mutex<ResponseBody>>, aborted: Arc<AtomicBool>, awaiting_body: Arc<Mutex<Vec<Sender<Data>>>>, @@ -71,7 +71,7 @@ struct MeasurableCachedResource { metadata: CachedMetadata, location_url: Option<Result<ServoUrl, String>>, https_state: HttpsState, - status: Option<StatusCode>, + status: Option<(StatusCode, String)>, raw_status: Option<(u16, Vec<u8>)>, url_list: Vec<ServoUrl>, expires: Duration, @@ -80,7 +80,7 @@ struct MeasurableCachedResource { impl MallocSizeOf for CachedResource { fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - self.request_headers.unconditional_size_of(ops) + + // TODO: self.request_headers.unconditional_size_of(ops) + self.body.unconditional_size_of(ops) + self.aborted.unconditional_size_of(ops) + self.awaiting_body.unconditional_size_of(ops) + @@ -92,7 +92,7 @@ impl MallocSizeOf for CachedResource { #[derive(Clone)] struct CachedMetadata { /// Headers - pub headers: Arc<Mutex<Headers>>, + pub headers: Arc<Mutex<HeaderMap>>, /// Fields that implement MallocSizeOf pub data: Measurable<MeasurableCachedMetadata> } @@ -102,7 +102,7 @@ struct MeasurableCachedMetadata { /// Final URL after redirects. pub final_url: ServoUrl, /// MIME type / subtype. - pub content_type: Option<Serde<ContentType>>, + pub content_type: Option<String>, /// Character set. pub charset: Option<String>, /// HTTP Status @@ -112,7 +112,7 @@ struct MeasurableCachedMetadata { impl MallocSizeOf for CachedMetadata { fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { self.headers.unconditional_shallow_size_of(ops) + - self.headers.size_of(ops) + + // TODO: self.headers.size_of(ops) + self.data.size_of(ops) } } @@ -141,23 +141,25 @@ fn response_is_cacheable(metadata: &Metadata) -> bool { // 2. check for absence of the Authorization header field. let mut is_cacheable = false; let headers = metadata.headers.as_ref().unwrap(); - if headers.has::<header::Expires>() || - headers.has::<header::LastModified>() || - headers.has::<header::ETag>() { + if headers.contains_key(header::EXPIRES) || + headers.contains_key(header::LAST_MODIFIED) || + headers.contains_key(header::ETAG) { is_cacheable = true; } - if let Some(&header::CacheControl(ref directive)) = headers.get::<header::CacheControl>() { - for directive in directive.iter() { - match *directive { - header::CacheDirective::NoStore => return false, - header::CacheDirective::Public | header::CacheDirective::SMaxAge(_) - | header::CacheDirective::MaxAge(_) | header::CacheDirective::NoCache => is_cacheable = true, - _ => {}, - } + if let Some(ref directive) = headers.typed_get::<CacheControl>() { + if directive.no_store() { + return false + } + if directive.public() || directive.s_max_age().is_some() || + directive.max_age().is_some() || directive.no_cache() + { + is_cacheable = true; } } - if let Some(&header::Pragma::NoCache) = headers.get::<header::Pragma>() { - return false; + if let Some(pragma) = headers.typed_get::<Pragma>() { + if pragma.is_no_cache() { + return false; + } } is_cacheable } @@ -166,10 +168,11 @@ fn response_is_cacheable(metadata: &Metadata) -> bool { /// <https://tools.ietf.org/html/rfc7234#section-4.2.3> fn calculate_response_age(response: &Response) -> Duration { // TODO: follow the spec more closely (Date headers, request/response lag, ...) - if let Some(secs) = response.headers.get_raw("Age") { - let seconds_string = String::from_utf8_lossy(&secs[0]); - if let Ok(secs) = seconds_string.parse::<i64>() { - return Duration::seconds(secs); + if let Some(secs) = response.headers.get(header::AGE) { + if let Ok(seconds_string) = secs.to_str() { + if let Ok(secs) = seconds_string.parse::<i64>() { + return Duration::seconds(secs); + } } } Duration::seconds(0i64) @@ -180,42 +183,37 @@ fn calculate_response_age(response: &Response) -> Duration { fn get_response_expiry(response: &Response) -> Duration { // Calculating Freshness Lifetime <https://tools.ietf.org/html/rfc7234#section-4.2.1> let age = calculate_response_age(&response); - if let Some(&header::CacheControl(ref directives)) = response.headers.get::<header::CacheControl>() { - let has_no_cache_directive = directives.iter().any(|directive| { - header::CacheDirective::NoCache == *directive - }); - if has_no_cache_directive { + if let Some(directives) = response.headers.typed_get::<CacheControl>() { + if directives.no_cache() { // Requires validation on first use. return Duration::seconds(0i64); } else { - for directive in directives { - match *directive { - header::CacheDirective::SMaxAge(secs) | header::CacheDirective::MaxAge(secs) => { - let max_age = Duration::seconds(secs as i64); - if max_age < age { - return Duration::seconds(0i64); - } - return max_age - age; - }, - _ => (), + if let Some(secs) = directives.max_age().or(directives.s_max_age()) { + let max_age = Duration::from_std(secs).unwrap(); + if max_age < age { + return Duration::seconds(0i64); } + return max_age - age; } } } - if let Some(&header::Expires(header::HttpDate(t))) = response.headers.get::<header::Expires>() { - // store the period of time from now until expiry - let desired = t.to_timespec(); - let current = time::now().to_timespec(); - if desired > current { - return desired - current; - } else { - return Duration::seconds(0i64); - } - } else { - if let Some(_) = response.headers.get_raw("Expires") { - // Malformed Expires header, shouldn't be used to construct a valid response. - return Duration::seconds(0i64); - } + match response.headers.typed_get::<Expires>() { + Some(t) => { + // store the period of time from now until expiry + let t: SystemTime = t.into(); + let t = t.duration_since(SystemTime::UNIX_EPOCH).unwrap(); + let desired = Timespec::new(t.as_secs() as i64, 0); + let current = time::now().to_timespec(); + + if desired > current { + return desired - current; + } else { + return Duration::seconds(0i64); + } + }, + // Malformed Expires header, shouldn't be used to construct a valid response. + None if response.headers.contains_key(header::EXPIRES) => return Duration::seconds(0i64), + _ => {}, } // Calculating Heuristic Freshness // <https://tools.ietf.org/html/rfc7234#section-4.2.2> @@ -224,13 +222,15 @@ fn get_response_expiry(response: &Response) -> Duration { // Since presently we do not generate a Warning header field with a 113 warn-code, // 24 hours minus response age is the max for heuristic calculation. let max_heuristic = Duration::hours(24) - age; - let heuristic_freshness = if let Some(&header::LastModified(header::HttpDate(t))) = + let heuristic_freshness = if let Some(last_modified) = // If the response has a Last-Modified header field, // caches are encouraged to use a heuristic expiration value // that is no more than some fraction of the interval since that time. - response.headers.get::<header::LastModified>() { - let last_modified = t.to_timespec(); + response.headers.typed_get::<LastModified>() { let current = time::now().to_timespec(); + let last_modified: SystemTime = last_modified.into(); + let last_modified = last_modified.duration_since(SystemTime::UNIX_EPOCH).unwrap(); + let last_modified = Timespec::new(last_modified.as_secs() as i64, 0); // A typical setting of this fraction might be 10%. let raw_heuristic_calc = (current - last_modified) / 10; let result = if raw_heuristic_calc < max_heuristic { @@ -249,11 +249,9 @@ fn get_response_expiry(response: &Response) -> Duration { }, _ => { // Other status codes can only use heuristic freshness if the public cache directive is present. - if let Some(&header::CacheControl(ref directives)) = response.headers.get::<header::CacheControl>() { - let has_public_directive = directives.iter().any(|directive| { - header::CacheDirective::Public == *directive - }); - if has_public_directive { + if let Some(ref directives) = response.headers.typed_get::<CacheControl>() + { + if directives.public() { return heuristic_freshness; } } @@ -267,48 +265,39 @@ fn get_response_expiry(response: &Response) -> Duration { /// Request Cache-Control Directives /// <https://tools.ietf.org/html/rfc7234#section-5.2.1> fn get_expiry_adjustment_from_request_headers(request: &Request, expires: Duration) -> Duration { - let directive_data = match request.headers.get_raw("cache-control") { + let directive = match request.headers.typed_get::<CacheControl>() { Some(data) => data, None => return expires, }; - let directives_string = String::from_utf8_lossy(&directive_data[0]); - for directive in directives_string.split(",") { - let mut directive_info = directive.split("="); - match (directive_info.next(), directive_info.next()) { - (Some("max-stale"), Some(sec_str)) => { - if let Ok(secs) = sec_str.parse::<i64>() { - return expires + Duration::seconds(secs); - } - }, - (Some("max-age"), Some(sec_str)) => { - if let Ok(secs) = sec_str.parse::<i64>() { - let max_age = Duration::seconds(secs); - if expires > max_age { - return Duration::min_value(); - } - return expires - max_age; - } - }, - (Some("min-fresh"), Some(sec_str)) => { - if let Ok(secs) = sec_str.parse::<i64>() { - let min_fresh = Duration::seconds(secs); - if expires < min_fresh { - return Duration::min_value(); - } - return expires - min_fresh; - } - }, - (Some("no-cache"), _) | (Some("no-store"), _) => return Duration::min_value(), - _ => {} + + if let Some(max_age) = directive.max_stale() { + return expires + Duration::from_std(max_age).unwrap(); + } + if let Some(max_age) = directive.max_age() { + let max_age = Duration::from_std(max_age).unwrap(); + if expires > max_age { + return Duration::min_value(); } + return expires - max_age; } + if let Some(min_fresh) = directive.min_fresh() { + let min_fresh = Duration::from_std(min_fresh).unwrap(); + if expires < min_fresh { + return Duration::min_value(); + } + return expires - min_fresh; + } + if directive.no_cache() || directive.no_store() { + return Duration::min_value() + } + expires } /// Create a CachedResponse from a request and a CachedResource. fn create_cached_response(request: &Request, cached_resource: &CachedResource, - cached_headers: &Headers, + cached_headers: &HeaderMap, done_chan: &mut DoneChannel) -> CachedResponse { let mut response = Response::new(cached_resource.data.metadata.data.final_url.clone()); @@ -353,7 +342,7 @@ fn create_resource_with_bytes_from_resource(bytes: &[u8], resource: &CachedResou metadata: resource.data.metadata.clone(), location_url: resource.data.location_url.clone(), https_state: resource.data.https_state.clone(), - status: Some(StatusCode::PartialContent), + status: Some((StatusCode::PARTIAL_CONTENT, "Partial Content".into())), raw_status: Some((206, b"Partial Content".to_vec())), url_list: resource.data.url_list.clone(), expires: resource.data.expires.clone(), @@ -365,7 +354,7 @@ fn create_resource_with_bytes_from_resource(bytes: &[u8], resource: &CachedResou /// Support for range requests <https://tools.ietf.org/html/rfc7233>. fn handle_range_request(request: &Request, candidates: Vec<&CachedResource>, - range_spec: &[header::ByteRangeSpec], + range_spec: Vec<(Bound<u64>, Bound<u64>)>, done_chan: &mut DoneChannel) -> Option<CachedResponse> { let mut complete_cached_resources = candidates.iter().filter(|resource| { @@ -389,7 +378,7 @@ fn handle_range_request(request: &Request, // see <https://tools.ietf.org/html/rfc7233#section-4.3>. // TODO: add support for complete and partial resources, // whose body is in the ResponseBody::Receiving state. - (&header::ByteRangeSpec::FromTo(beginning, end), Some(ref complete_resource)) => { + (&(Bound::Included(beginning), Bound::Included(end)), Some(ref complete_resource)) => { if let ResponseBody::Done(ref body) = *complete_resource.body.lock().unwrap() { let b = beginning as usize; let e = end as usize + 1; @@ -402,14 +391,18 @@ fn handle_range_request(request: &Request, } } }, - (&header::ByteRangeSpec::FromTo(beginning, end), None) => { + (&(Bound::Included(beginning), Bound::Included(end)), None) => { for partial_resource in partial_cached_resources { let headers = partial_resource.data.metadata.headers.lock().unwrap(); - let content_range = headers.get::<header::ContentRange>(); + let content_range = headers.typed_get::<ContentRange>(); let (res_beginning, res_end) = match content_range { - Some(&header::ContentRange( - header::ContentRangeSpec::Bytes { - range: Some((res_beginning, res_end)), .. })) => (res_beginning, res_end), + Some(range) => { + if let Some(bytes_range) = range.bytes_range() { + bytes_range + } else { + continue + } + } _ => continue, }; if res_beginning - 1 < beginning && res_end + 1 > end { @@ -430,7 +423,7 @@ fn handle_range_request(request: &Request, } } }, - (&header::ByteRangeSpec::AllFrom(beginning), Some(ref complete_resource)) => { + (&(Bound::Included(beginning), Bound::Unbounded), Some(ref complete_resource)) => { if let ResponseBody::Done(ref body) = *complete_resource.body.lock().unwrap() { let b = beginning as usize; let requested = body.get(b..); @@ -442,16 +435,17 @@ fn handle_range_request(request: &Request, } } }, - (&header::ByteRangeSpec::AllFrom(beginning), None) => { + (&(Bound::Included(beginning), Bound::Unbounded), None) => { for partial_resource in partial_cached_resources { let headers = partial_resource.data.metadata.headers.lock().unwrap(); - let content_range = headers.get::<header::ContentRange>(); - let (res_beginning, res_end, total) = match content_range { - Some(&header::ContentRange( - header::ContentRangeSpec::Bytes { - range: Some((res_beginning, res_end)), - instance_length: Some(total) })) => (res_beginning, res_end, total), - _ => continue, + let content_range = headers.typed_get::<ContentRange>(); + let (res_beginning, res_end, total) = if let Some(range) = content_range { + match (range.bytes_range(), range.bytes_len()) { + (Some(bytes_range), Some(total)) => (bytes_range.0, bytes_range.1, total), + _ => continue, + } + } else { + continue; }; if res_beginning < beginning && res_end == total - 1 { let resource_body = &*partial_resource.body.lock().unwrap(); @@ -470,7 +464,7 @@ fn handle_range_request(request: &Request, } } }, - (&header::ByteRangeSpec::Last(offset), Some(ref complete_resource)) => { + (&(Bound::Unbounded, Bound::Included(offset)), Some(ref complete_resource)) => { if let ResponseBody::Done(ref body) = *complete_resource.body.lock().unwrap() { let from_byte = body.len() - offset as usize; let requested = body.get(from_byte..); @@ -482,16 +476,17 @@ fn handle_range_request(request: &Request, } } }, - (&header::ByteRangeSpec::Last(offset), None) => { + (&(Bound::Unbounded, Bound::Included(offset)), None) => { for partial_resource in partial_cached_resources { let headers = partial_resource.data.metadata.headers.lock().unwrap(); - let content_range = headers.get::<header::ContentRange>(); - let (res_beginning, res_end, total) = match content_range { - Some(&header::ContentRange( - header::ContentRangeSpec::Bytes { - range: Some((res_beginning, res_end)), - instance_length: Some(total) })) => (res_beginning, res_end, total), - _ => continue, + let content_range = headers.typed_get::<ContentRange>(); + let (res_beginning, res_end, total) = if let Some(range) = content_range { + match (range.bytes_range(), range.bytes_len()) { + (Some(bytes_range), Some(total)) => (bytes_range.0, bytes_range.1, total), + _ => continue, + } + } else { + continue; }; if (total - res_beginning) > (offset - 1 ) && (total - res_end) < offset + 1 { let resource_body = &*partial_resource.body.lock().unwrap(); @@ -509,7 +504,9 @@ fn handle_range_request(request: &Request, } } } - } + }, + // All the cases with Bound::Excluded should be unreachable anyway + _ => return None } None } @@ -527,7 +524,7 @@ impl HttpCache { /// <https://tools.ietf.org/html/rfc7234#section-4> pub fn construct_response(&self, request: &Request, done_chan: &mut DoneChannel) -> Option<CachedResponse> { // TODO: generate warning headers as appropriate <https://tools.ietf.org/html/rfc7234#section-5.5> - if request.method != Method::Get { + if request.method != Method::GET { // Only Get requests are cached, avoid a url based match for others. return None; } @@ -538,41 +535,35 @@ impl HttpCache { let mut can_be_constructed = true; let cached_headers = cached_resource.data.metadata.headers.lock().unwrap(); let original_request_headers = cached_resource.request_headers.lock().unwrap(); - if let Some(vary_data) = cached_headers.get_raw("Vary") { - // Calculating Secondary Keys with Vary <https://tools.ietf.org/html/rfc7234#section-4.1> - let vary_data_string = String::from_utf8_lossy(&vary_data[0]); - let vary_values = vary_data_string.split(",").map(|val| val.trim()); - for vary_val in vary_values { + if let Some(vary_value) = cached_headers.typed_get::<Vary>() { + if vary_value.is_any() { + can_be_constructed = false + } else { // For every header name found in the Vary header of the stored response. - if vary_val == "*" { - // A Vary header field-value of "*" always fails to match. - can_be_constructed = false; - break; - } - match request.headers.get_raw(vary_val) { - Some(header_data) => { - // If the header is present in the request. - let request_header_data_string = String::from_utf8_lossy(&header_data[0]); - if let Some(original_header_data) = original_request_headers.get_raw(vary_val) { - // Check that the value of the nominated header field, - // in the original request, matches the value in the current request. - let original_request_header_data_string = - String::from_utf8_lossy(&original_header_data[0]); - if original_request_header_data_string != request_header_data_string { - can_be_constructed = false; - break; + // Calculating Secondary Keys with Vary <https://tools.ietf.org/html/rfc7234#section-4.1> + for vary_val in vary_value.iter_strs() { + match request.headers.get(vary_val) { + Some(header_data) => { + // If the header is present in the request. + if let Some(original_header_data) = original_request_headers.get(vary_val) { + // Check that the value of the nominated header field, + // in the original request, matches the value in the current request. + if original_header_data != header_data { + can_be_constructed = false; + break; + } } - } - }, - None => { - // If a header field is absent from a request, - // it can only match a stored response if those headers, - // were also absent in the original request. - can_be_constructed = original_request_headers.get_raw(vary_val).is_none(); - }, - } - if !can_be_constructed { - break; + }, + None => { + // If a header field is absent from a request, + // it can only match a stored response if those headers, + // were also absent in the original request. + can_be_constructed = original_request_headers.get(vary_val).is_none(); + }, + } + if !can_be_constructed { + break; + } } } } @@ -581,8 +572,8 @@ impl HttpCache { } } // Support for range requests - if let Some(&header::Range::Bytes(ref range_spec)) = request.headers.get::<header::Range>() { - return handle_range_request(request, candidates, &range_spec, done_chan); + if let Some(range_spec) = request.headers.typed_get::<Range>() { + return handle_range_request(request, candidates, range_spec.iter().collect(), done_chan); } else { // Not a Range request. if let Some(ref cached_resource) = candidates.first() { @@ -620,7 +611,7 @@ impl HttpCache { /// Freshening Stored Responses upon Validation. /// <https://tools.ietf.org/html/rfc7234#section-4.3.4> pub fn refresh(&mut self, request: &Request, response: Response, done_chan: &mut DoneChannel) -> Option<Response> { - assert_eq!(response.status, Some(StatusCode::NotModified)); + assert_eq!(response.status.map(|s| s.0), Some(StatusCode::NOT_MODIFIED)); let entry_key = CacheKey::new(request.clone()); if let Some(cached_resources) = self.entries.get_mut(&entry_key) { for cached_resource in cached_resources.iter_mut() { @@ -654,7 +645,7 @@ impl HttpCache { constructed_response.url_list = cached_resource.data.url_list.clone(); cached_resource.data.expires = get_response_expiry(&constructed_response); let mut stored_headers = cached_resource.data.metadata.headers.lock().unwrap(); - stored_headers.extend(response.headers.iter()); + stored_headers.extend(response.headers); constructed_response.headers = stored_headers.clone(); return Some(constructed_response); } @@ -674,17 +665,16 @@ impl HttpCache { /// Invalidation. /// <https://tools.ietf.org/html/rfc7234#section-4.4> pub fn invalidate(&mut self, request: &Request, response: &Response) { - if let Some(&header::Location(ref location)) = response.headers.get::<header::Location>() { + // TODO(eijebong): Once headers support typed_get, update this to use them + if let Some(Ok(location)) = response.headers.get(header::LOCATION).map(HeaderValue::to_str) { if let Ok(url) = request.current_url().join(location) { self.invalidate_for_url(&url); } } - // TODO: update hyper to use typed getter. - if let Some(url_data) = response.headers.get_raw("Content-Location") { - if let Ok(content_location) = str::from_utf8(&url_data[0]) { - if let Ok(url) = request.current_url().join(content_location) { - self.invalidate_for_url(&url); - } + if let Some(Ok(ref content_location)) = response.headers.get(header::CONTENT_LOCATION).map(HeaderValue::to_str) + { + if let Ok(url) = request.current_url().join(&content_location) { + self.invalidate_for_url(&url); } } self.invalidate_for_url(&request.url()); @@ -696,7 +686,7 @@ impl HttpCache { if PREFS.get("network.http-cache.disabled").as_boolean().unwrap_or(false) { return } - if request.method != Method::Get { + if request.method != Method::GET { // Only Get requests are cached. return } @@ -716,7 +706,7 @@ impl HttpCache { headers: Arc::new(Mutex::new(response.headers.clone())), data: Measurable(MeasurableCachedMetadata { final_url: metadata.final_url, - content_type: metadata.content_type, + content_type: metadata.content_type.map(|v| v.0.to_string()), charset: metadata.charset, status: metadata.status }) diff --git a/components/net/http_loader.rs b/components/net/http_loader.rs index fe85fc5a6f7..20aaaffc237 100644 --- a/components/net/http_loader.rs +++ b/components/net/http_loader.rs @@ -3,7 +3,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use brotli::Decompressor; -use connector::{Connector, create_http_connector}; +use bytes::Bytes; +use connector::{BUF_SIZE, Connector, create_http_client, WrappedBody}; use cookie; use cookie_storage::CookieStorage; use devtools_traits::{ChromeToDevtoolsControlMsg, DevtoolsControlMsg, HttpRequest as DevtoolsHttpRequest}; @@ -12,60 +13,51 @@ use fetch::cors_cache::CorsCache; use fetch::methods::{Data, DoneChannel, FetchContext, Target}; use fetch::methods::{is_cors_safelisted_request_header, is_cors_safelisted_method, main_fetch}; use flate2::read::{DeflateDecoder, GzDecoder}; +use headers_core::HeaderMapExt; +use headers_ext::{AccessControlAllowCredentials, AccessControlAllowHeaders}; +use headers_ext::{AccessControlAllowMethods, AccessControlRequestHeaders, AccessControlRequestMethod, Authorization}; +use headers_ext::{AccessControlAllowOrigin, AccessControlMaxAge, Basic}; +use headers_ext::{CacheControl, ContentEncoding, ContentLength}; +use headers_ext::{Host, IfModifiedSince, LastModified, Origin as HyperOrigin, Pragma, Referer, UserAgent}; use hsts::HstsList; +use http::{HeaderMap, Request as HyperRequest}; +use http::header::{self, HeaderName, HeaderValue}; +use http::uri::Authority; use http_cache::HttpCache; -use hyper::Error as HttpError; -use hyper::LanguageTag; -use hyper::client::{Pool, Request as HyperRequest, Response as HyperResponse}; -use hyper::header::{Accept, AccessControlAllowCredentials, AccessControlAllowHeaders}; -use hyper::header::{AccessControlAllowMethods, AccessControlAllowOrigin}; -use hyper::header::{AccessControlMaxAge, AccessControlRequestHeaders}; -use hyper::header::{AccessControlRequestMethod, AcceptEncoding, AcceptLanguage}; -use hyper::header::{Authorization, Basic, CacheControl, CacheDirective}; -use hyper::header::{ContentEncoding, ContentLength, Encoding, Header, Headers}; -use hyper::header::{Host, HttpDate, Origin as HyperOrigin, IfMatch, IfRange}; -use hyper::header::{IfUnmodifiedSince, IfModifiedSince, IfNoneMatch, Location}; -use hyper::header::{Pragma, Quality, QualityItem, Referer, SetCookie}; -use hyper::header::{UserAgent, q, qitem}; -use hyper::method::Method; -use hyper::status::StatusCode; -use hyper_openssl::OpensslClient; +use hyper::{Body, Client, Method, StatusCode, Response as HyperResponse}; use hyper_serde::Serde; use log; +use mime; use msg::constellation_msg::{HistoryStateId, PipelineId}; use net_traits::{CookieSource, FetchMetadata, NetworkError, ReferrerPolicy}; +use net_traits::quality::{quality_to_value, Quality, QualityItem}; use net_traits::request::{CacheMode, CredentialsMode, Destination, Origin}; use net_traits::request::{RedirectMode, Referrer, Request, RequestMode}; use net_traits::request::{ResponseTainting, ServiceWorkersMode}; use net_traits::response::{HttpsState, Response, ResponseBody, ResponseType}; +use openssl::ssl::SslConnectorBuilder; use resource_thread::AuthCache; use servo_channel::{channel, Sender}; use servo_url::{ImmutableOrigin, ServoUrl}; use std::collections::{HashMap, HashSet}; use std::error::Error; -use std::io::{self, Read, Write}; +use std::io::Cursor; use std::iter::FromIterator; use std::mem; use std::ops::Deref; use std::str::FromStr; +use std::sync::Mutex; use std::sync::RwLock; -use std::thread; -use time; -use time::Tm; -use unicase::UniCase; +use std::time::{Duration, SystemTime}; +use time::{self, Tm}; +use tokio::prelude::{Future, future, Stream}; +use tokio::runtime::Runtime; use uuid; -fn read_block<R: Read>(reader: &mut R) -> Result<Data, ()> { - let mut buf = vec![0; 32768]; - - match reader.read(&mut buf) { - Ok(len) if len > 0 => { - buf.truncate(len); - Ok(Data::Payload(buf)) - } - Ok(_) => Ok(Data::Done), - Err(_) => Err(()), - } +lazy_static! { + pub static ref HANDLE: Mutex<Runtime> = { + Mutex::new(Runtime::new().unwrap()) + }; } pub struct HttpState { @@ -74,20 +66,18 @@ pub struct HttpState { pub http_cache: RwLock<HttpCache>, pub auth_cache: RwLock<AuthCache>, pub history_states: RwLock<HashMap<HistoryStateId, Vec<u8>>>, - pub ssl_client: OpensslClient, - pub connector: Pool<Connector>, + pub client: Client<Connector, WrappedBody>, } impl HttpState { - pub fn new(ssl_client: OpensslClient) -> HttpState { + pub fn new(ssl_connector_builder: SslConnectorBuilder) -> HttpState { HttpState { hsts_list: RwLock::new(HstsList::new()), cookie_jar: RwLock::new(CookieStorage::new(150)), auth_cache: RwLock::new(AuthCache::new()), history_states: RwLock::new(HashMap::new()), http_cache: RwLock::new(HttpCache::new()), - ssl_client: ssl_client.clone(), - connector: create_http_connector(ssl_client), + client: create_http_client(ssl_connector_builder, HANDLE.lock().unwrap().executor()), } } } @@ -97,72 +87,63 @@ fn precise_time_ms() -> u64 { } // Step 3 of https://fetch.spec.whatwg.org/#concept-fetch. -pub fn set_default_accept(destination: Destination, headers: &mut Headers) { - if headers.has::<Accept>() { +pub fn set_default_accept(destination: Destination, headers: &mut HeaderMap) { + if headers.contains_key(header::ACCEPT) { return; } let value = match destination { // Step 3.2. Destination::Document => { vec![ - qitem(mime!(Text / Html)), - qitem(mime!(Application / ("xhtml+xml"))), - QualityItem::new(mime!(Application / Xml), q(0.9)), - QualityItem::new(mime!(_ / _), q(0.8)), + QualityItem::new(mime::TEXT_HTML, Quality::from_u16(1000)), + QualityItem::new("application/xhtml+xml".parse().unwrap(), Quality::from_u16(1000)), + QualityItem::new("application/xml".parse().unwrap(), Quality::from_u16(900)), + QualityItem::new(mime::STAR_STAR, Quality::from_u16(800)) ] }, // Step 3.3. Destination::Image => { vec![ - qitem(mime!(Image / Png)), - qitem(mime!(Image / ("svg+xml") )), - QualityItem::new(mime!(Image / _), q(0.8)), - QualityItem::new(mime!(_ / _), q(0.5)), + QualityItem::new(mime::IMAGE_PNG, Quality::from_u16(1000)), + QualityItem::new(mime::IMAGE_SVG, Quality::from_u16(1000)), + QualityItem::new(mime::IMAGE_STAR, Quality::from_u16(800)), + QualityItem::new(mime::STAR_STAR, Quality::from_u16(500)) ] }, // Step 3.3. Destination::Style => { vec![ - qitem(mime!(Text / Css)), - QualityItem::new(mime!(_ / _), q(0.1)) + QualityItem::new(mime::TEXT_CSS, Quality::from_u16(1000)), + QualityItem::new(mime::STAR_STAR, Quality::from_u16(100)) ] }, // Step 3.1. _ => { - vec![qitem(mime!(_ / _))] + vec![QualityItem::new(mime::STAR_STAR, Quality::from_u16(1000))] }, }; // Step 3.4. - headers.set(Accept(value)); + // TODO(eijebong): Change this once typed headers are done + headers.insert(header::ACCEPT, quality_to_value(value)); } -fn set_default_accept_encoding(headers: &mut Headers) { - if headers.has::<AcceptEncoding>() { +fn set_default_accept_encoding(headers: &mut HeaderMap) { + if headers.contains_key(header::ACCEPT_ENCODING) { return } - headers.set(AcceptEncoding(vec![ - qitem(Encoding::Gzip), - qitem(Encoding::Deflate), - qitem(Encoding::EncodingExt("br".to_owned())) - ])); + // TODO(eijebong): Change this once typed headers are done + headers.insert(header::ACCEPT_ENCODING, HeaderValue::from_static("gzip, deflate, br")); } -pub fn set_default_accept_language(headers: &mut Headers) { - if headers.has::<AcceptLanguage>() { +pub fn set_default_accept_language(headers: &mut HeaderMap) { + if headers.contains_key(header::ACCEPT_LANGUAGE) { return; } - let mut en_us: LanguageTag = Default::default(); - en_us.language = Some("en".to_owned()); - en_us.region = Some("US".to_owned()); - let mut en: LanguageTag = Default::default(); - en.language = Some("en".to_owned()); - headers.set(AcceptLanguage(vec![ - qitem(en_us), - QualityItem::new(en, Quality(500)), - ])); + // TODO(eijebong): Change this once typed headers are done + headers.insert(header::ACCEPT_LANGUAGE, HeaderValue::from_static("en-US, en; q=0.5")); } /// <https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-state-no-referrer-when-downgrade> @@ -210,12 +191,12 @@ fn strip_url(mut referrer_url: ServoUrl, origin_only: bool) -> Option<ServoUrl> /// <https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer> /// Steps 4-6. -pub fn determine_request_referrer(headers: &mut Headers, +pub fn determine_request_referrer(headers: &mut HeaderMap, referrer_policy: ReferrerPolicy, referrer_source: ServoUrl, current_url: ServoUrl) -> Option<ServoUrl> { - assert!(!headers.has::<Referer>()); + assert!(!headers.contains_key(header::REFERER)); // FIXME(#14505): this does not seem to be the correct way of checking for // same-origin requests. let cross_origin = referrer_source.origin() != current_url.origin(); @@ -233,94 +214,65 @@ pub fn determine_request_referrer(headers: &mut Headers, } } -pub fn set_request_cookies(url: &ServoUrl, headers: &mut Headers, cookie_jar: &RwLock<CookieStorage>) { +pub fn set_request_cookies(url: &ServoUrl, headers: &mut HeaderMap, cookie_jar: &RwLock<CookieStorage>) { let mut cookie_jar = cookie_jar.write().unwrap(); 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); + headers.insert(header::COOKIE, HeaderValue::from_bytes(cookie_list.as_bytes()).unwrap()); } } fn set_cookie_for_url(cookie_jar: &RwLock<CookieStorage>, request: &ServoUrl, - cookie_val: String) { + cookie_val: &str) { let mut cookie_jar = cookie_jar.write().unwrap(); let source = CookieSource::HTTP; - let header = Header::parse_header(&[cookie_val.into_bytes()]); - if let Ok(SetCookie(cookies)) = header { - for cookie in cookies { - if let Some(cookie) = cookie::Cookie::from_cookie_string(cookie, request, source) { - cookie_jar.push(cookie, request, source); - } - } + if let Some(cookie) = cookie::Cookie::from_cookie_string(cookie_val.into(), request, source) { + cookie_jar.push(cookie, request, source); } } -fn set_cookies_from_headers(url: &ServoUrl, headers: &Headers, cookie_jar: &RwLock<CookieStorage>) { - if let Some(cookies) = headers.get_raw("set-cookie") { - for cookie in cookies.iter() { - if let Ok(cookie_value) = String::from_utf8(cookie.clone()) { - set_cookie_for_url(&cookie_jar, - &url, - cookie_value); - } +fn set_cookies_from_headers(url: &ServoUrl, headers: &HeaderMap, cookie_jar: &RwLock<CookieStorage>) { + for cookie in headers.get_all(header::SET_COOKIE) { + if let Ok(cookie_str) = cookie.to_str() { + set_cookie_for_url(&cookie_jar, + &url, + &cookie_str); } } } -struct StreamedResponse { - decoder: Decoder, -} - - -impl Read for StreamedResponse { - #[inline] - fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { - match self.decoder { - Decoder::Gzip(ref mut d) => d.read(buf), - Decoder::Deflate(ref mut d) => d.read(buf), - Decoder::Brotli(ref mut d) => d.read(buf), - Decoder::Plain(ref mut d) => d.read(buf) - } - } -} - -impl StreamedResponse { - fn from_http_response(response: HyperResponse) -> io::Result<StreamedResponse> { - let decoder = { - if let Some(ref encoding) = response.headers.get::<ContentEncoding>().cloned() { - if encoding.contains(&Encoding::Gzip) { - Decoder::Gzip(GzDecoder::new(response)) - } - else if encoding.contains(&Encoding::Deflate) { - Decoder::Deflate(DeflateDecoder::new(response)) - } - else if encoding.contains(&Encoding::EncodingExt("br".to_owned())) { - Decoder::Brotli(Decompressor::new(response, 1024)) - } else { - Decoder::Plain(response) - } +impl Decoder { + fn from_http_response(response: &HyperResponse<Body>) -> Decoder { + if let Some(encoding) = response.headers().typed_get::<ContentEncoding>() { + if encoding.contains("gzip") { + Decoder::Gzip(None) + } + else if encoding.contains("deflate") { + Decoder::Deflate(DeflateDecoder::new(Cursor::new(Bytes::new()))) + } + else if encoding.contains("br") { + Decoder::Brotli(Decompressor::new(Cursor::new(Bytes::new()), BUF_SIZE)) } else { - Decoder::Plain(response) + Decoder::Plain } - }; - Ok(StreamedResponse { decoder: decoder }) + } else { + Decoder::Plain + } } } -enum Decoder { - Gzip(GzDecoder<HyperResponse>), - Deflate(DeflateDecoder<HyperResponse>), - Brotli(Decompressor<HyperResponse>), - Plain(HyperResponse) +pub enum Decoder { + Gzip(Option<GzDecoder<Cursor<Bytes>>>), + Deflate(DeflateDecoder<Cursor<Bytes>>), + Brotli(Decompressor<Cursor<Bytes>>), + Plain, } fn prepare_devtools_request(request_id: String, url: ServoUrl, method: Method, - headers: Headers, + headers: HeaderMap, body: Option<Vec<u8>>, pipeline_id: PipelineId, now: Tm, @@ -351,7 +303,7 @@ fn send_request_to_devtools(msg: ChromeToDevtoolsControlMsg, fn send_response_to_devtools(devtools_chan: &Sender<DevtoolsControlMsg>, request_id: String, - headers: Option<Headers>, + headers: Option<HeaderMap>, status: Option<(u16, Vec<u8>)>, pipeline_id: PipelineId) { let response = DevtoolsHttpResponse { headers: headers, status: status, body: None, pipeline_id: pipeline_id }; @@ -361,111 +313,93 @@ fn send_response_to_devtools(devtools_chan: &Sender<DevtoolsControlMsg>, let _ = devtools_chan.send(DevtoolsControlMsg::FromChrome(msg)); } -fn auth_from_cache(auth_cache: &RwLock<AuthCache>, origin: &ImmutableOrigin) -> Option<Basic> { +fn auth_from_cache(auth_cache: &RwLock<AuthCache>, origin: &ImmutableOrigin) -> Option<Authorization<Basic>> { if let Some(ref auth_entry) = auth_cache.read().unwrap().entries.get(&origin.ascii_serialization()) { - let user_name = auth_entry.user_name.clone(); - let password = Some(auth_entry.password.clone()); - Some(Basic { username: user_name, password: password }) + let user_name = &auth_entry.user_name; + let password = &auth_entry.password; + Some(Authorization::basic(user_name, password)) } else { None } } -fn obtain_response(connector: &Pool<Connector>, +fn obtain_response(client: &Client<Connector, WrappedBody>, url: &ServoUrl, method: &Method, - request_headers: &Headers, + request_headers: &HeaderMap, data: &Option<Vec<u8>>, load_data_method: &Method, pipeline_id: &Option<PipelineId>, iters: u32, request_id: Option<&str>, is_xhr: bool) - -> Result<(HyperResponse, Option<ChromeToDevtoolsControlMsg>), NetworkError> { - let null_data = None; - - // loop trying connections in connection pool - // they may have grown stale (disconnected), in which case we'll get - // a ConnectionAborted error. this loop tries again with a new - // connection. - loop { - let mut headers = request_headers.clone(); - - // Avoid automatically sending request body if a redirect has occurred. - // - // TODO - This is the wrong behaviour according to the RFC. However, I'm not - // sure how much "correctness" vs. real-world is important in this case. - // - // https://tools.ietf.org/html/rfc7231#section-6.4 - let is_redirected_request = iters != 1; - let request_body; - match data { - &Some(ref d) if !is_redirected_request => { - headers.set(ContentLength(d.len() as u64)); - request_body = data; - } - _ => { - if *load_data_method != Method::Get && *load_data_method != Method::Head { - headers.set(ContentLength(0)) - } - request_body = &null_data; - } + -> Box<Future<Item=(HyperResponse<WrappedBody>, Option<ChromeToDevtoolsControlMsg>), + Error = NetworkError>> { + let mut headers = request_headers.clone(); + + // Avoid automatically sending request body if a redirect has occurred. + // + // TODO - This is the wrong behaviour according to the RFC. However, I'm not + // sure how much "correctness" vs. real-world is important in this case. + // + // https://tools.ietf.org/html/rfc7231#section-6.4 + let is_redirected_request = iters != 1; + let request_body; + match data { + &Some(ref d) if !is_redirected_request => { + headers.typed_insert(ContentLength(d.len() as u64)); + request_body = d.clone(); } - - if log_enabled!(log::Level::Info) { - info!("{} {}", method, url); - for header in headers.iter() { - info!(" - {}", header); + _ => { + if *load_data_method != Method::GET && *load_data_method != Method::HEAD { + headers.typed_insert(ContentLength(0)) } - info!("{:?}", data); + request_body = vec![]; } + } - let connect_start = precise_time_ms(); - - let request = HyperRequest::with_connector(method.clone(), - url.clone().into_url(), - &*connector); - let mut request = match request { - Ok(request) => request, - Err(e) => return Err(NetworkError::from_hyper_error(&url, e)), - }; - *request.headers_mut() = headers.clone(); - - let connect_end = precise_time_ms(); - - let send_start = precise_time_ms(); + if log_enabled!(log::Level::Info) { + info!("{} {}", method, url); + for header in headers.iter() { + info!(" - {:?}", header); + } + info!("{:?}", data); + } - let mut request_writer = match request.start() { - Ok(streaming) => streaming, - Err(e) => return Err(NetworkError::Internal(e.description().to_owned())), - }; + let connect_start = precise_time_ms(); + // https://url.spec.whatwg.org/#percent-encoded-bytes + let request = HyperRequest::builder() + .method(method) + .uri(url.clone().into_url().as_ref().replace("|", "%7C").replace("{", "%7B").replace("}", "%7D")) + .body(WrappedBody::new(request_body.clone().into())); - if let Some(ref data) = *request_body { - if let Err(e) = request_writer.write_all(&data) { - return Err(NetworkError::Internal(e.description().to_owned())) - } - } + let mut request = match request { + Ok(request) => request, + Err(e) => return Box::new(future::result(Err(NetworkError::from_http_error(&e)))), + }; + *request.headers_mut() = headers.clone(); + let connect_end = precise_time_ms(); - let response = match request_writer.send() { - Ok(w) => w, - Err(HttpError::Io(ref io_error)) - if io_error.kind() == io::ErrorKind::ConnectionAborted || - io_error.kind() == io::ErrorKind::ConnectionReset => { - debug!("connection aborted ({:?}), possibly stale, trying new connection", io_error.description()); - continue; - }, - Err(e) => return Err(NetworkError::Internal(e.description().to_owned())), - }; + let request_id = request_id.map(|v| v.to_owned()); + let pipeline_id = pipeline_id.clone(); + let closure_url = url.clone(); + let method = method.clone(); + let send_start = precise_time_ms(); + Box::new(client.request(request).and_then(move |res| { let send_end = precise_time_ms(); let msg = if let Some(request_id) = request_id { - if let Some(pipeline_id) = *pipeline_id { + if let Some(pipeline_id) = pipeline_id { Some(prepare_devtools_request( - request_id.into(), - url.clone(), method.clone(), headers, - request_body.clone(), pipeline_id, time::now(), + request_id, + closure_url, method.clone(), headers, + Some(request_body.clone()), pipeline_id, time::now(), connect_end - connect_start, send_end - send_start, is_xhr)) + // TODO: ^This is not right, connect_start is taken before contructing the + // request and connect_end at the end of it. send_start is takend before the + // connection too. I'm not sure it's currently possible to get the time at the + // point between the connection and the start of a request. } else { debug!("Not notifying devtools (no pipeline_id)"); None @@ -474,8 +408,9 @@ fn obtain_response(connector: &Pool<Connector>, debug!("Not notifying devtools (no request_id)"); None }; - return Ok((response, msg)); - } + let decoder = Decoder::from_http_response(&res); + Ok((res.map(move |r| WrappedBody::new_with_decoder(r, decoder)), msg)) + }).map_err(move |e| NetworkError::from_hyper_error(&e))) } /// [HTTP fetch](https://fetch.spec.whatwg.org#http-fetch) @@ -543,8 +478,8 @@ pub fn http_fetch(request: &mut Request, let method_mismatch = !method_cache_match && (!is_cors_safelisted_method(&request.method) || request.use_cors_preflight); - let header_mismatch = request.headers.iter().any(|view| - !cache.match_header(&*request, view.name()) && !is_cors_safelisted_request_header(&view) + let header_mismatch = request.headers.iter().any(|(name, value)| + !cache.match_header(&*request, &name) && !is_cors_safelisted_request_header(&name, &value) ); // Sub-substep 1 @@ -579,16 +514,19 @@ pub fn http_fetch(request: &mut Request, let mut response = response.unwrap(); // Step 5 - if response.actual_response().status.map_or(false, is_redirect_status) { + if response.actual_response().status.as_ref().map_or(false, is_redirect_status) { // Substep 1. - if response.actual_response().status.map_or(true, |s| s != StatusCode::SeeOther) { + if response.actual_response().status.as_ref().map_or(true, |s| s.0 != StatusCode::SEE_OTHER) { // TODO: send RST_STREAM frame } // Substep 2-3. - let location = response.actual_response().headers.get::<Location>().map( - |l| ServoUrl::parse_with_base(response.actual_response().url(), l) - .map_err(|err| err.description().into())); + let location = response.actual_response().headers.get(header::LOCATION) + .and_then(|v| HeaderValue::to_str(v).map(|l| { + ServoUrl::parse_with_base(response.actual_response().url(), &l) + .map_err(|err| err.description().into()) + }).ok() + ); // Substep 4. response.actual_response_mut().location_url = location; @@ -664,7 +602,7 @@ pub fn http_redirect_fetch(request: &mut Request, } // Step 9 - if response.actual_response().status.map_or(true, |s| s != StatusCode::SeeOther) && + if response.actual_response().status.as_ref().map_or(true, |s| s.0 != StatusCode::SEE_OTHER) && request.body.as_ref().map_or(false, |b| b.is_empty()) { return Response::network_error(NetworkError::Internal("Request body is not done".into())); } @@ -675,10 +613,10 @@ pub fn http_redirect_fetch(request: &mut Request, } // Step 11 - if response.actual_response().status.map_or(false, |code| - ((code == StatusCode::MovedPermanently || code == StatusCode::Found) && request.method == Method::Post) || - (code == StatusCode::SeeOther && request.method != Method::Head)) { - request.method = Method::Get; + if response.actual_response().status.as_ref().map_or(false, |(code, _)| + ((*code == StatusCode::MOVED_PERMANENTLY || *code == StatusCode::FOUND) && request.method == Method::POST) || + (*code == StatusCode::SEE_OTHER && request.method != Method::HEAD)) { + request.method = Method::GET; request.body = None; } @@ -701,10 +639,9 @@ pub fn http_redirect_fetch(request: &mut Request, fn try_immutable_origin_to_hyper_origin(url_origin: &ImmutableOrigin) -> Option<HyperOrigin> { match *url_origin { - // TODO (servo/servo#15569) Set "Origin: null" when hyper supports it - ImmutableOrigin::Opaque(_) => None, + ImmutableOrigin::Opaque(_) => Some(HyperOrigin::NULL), ImmutableOrigin::Tuple(ref scheme, ref host, ref port) => - Some(HyperOrigin::new(scheme.clone(), host.to_string(), Some(port.clone()))) + HyperOrigin::try_from_parts(&scheme, &host.to_string(), Some(port.clone())).ok() } } @@ -742,7 +679,7 @@ fn http_network_or_cache_fetch(request: &mut Request, None => match http_request.method { // Step 6 - Method::Post | Method::Put => + Method::POST | Method::PUT => Some(0), // Step 5 _ => None @@ -753,7 +690,7 @@ fn http_network_or_cache_fetch(request: &mut Request, // Step 8 if let Some(content_length_value) = content_length_value { - http_request.headers.set(ContentLength(content_length_value)); + http_request.headers.typed_insert(ContentLength(content_length_value)); if http_request.keep_alive { // Step 9 TODO: needs request's client object } @@ -764,7 +701,7 @@ fn http_network_or_cache_fetch(request: &mut Request, match http_request.referrer { Referrer::NoReferrer => (), Referrer::ReferrerUrl(ref http_request_referrer) => - http_request.headers.set(Referer(http_request_referrer.to_string())), + http_request.headers.typed_insert::<Referer>(http_request_referrer.to_string().parse().unwrap()), Referrer::Client => // it should be impossible for referrer to be anything else during fetching // https://fetch.spec.whatwg.org/#concept-request-referrer @@ -772,19 +709,19 @@ fn http_network_or_cache_fetch(request: &mut Request, }; // Step 11 - if cors_flag || (http_request.method != Method::Get && http_request.method != Method::Head) { + if cors_flag || (http_request.method != Method::GET && http_request.method != Method::HEAD) { debug_assert_ne!(http_request.origin, Origin::Client); if let Origin::Origin(ref url_origin) = http_request.origin { if let Some(hyper_origin) = try_immutable_origin_to_hyper_origin(url_origin) { - http_request.headers.set(hyper_origin) + http_request.headers.typed_insert(hyper_origin) } } } // Step 12 - if !http_request.headers.has::<UserAgent>() { + if !http_request.headers.contains_key(header::USER_AGENT) { let user_agent = context.user_agent.clone().into_owned(); - http_request.headers.set(UserAgent(user_agent)); + http_request.headers.typed_insert::<UserAgent>(user_agent.parse().unwrap()); } match http_request.cache_mode { @@ -794,20 +731,20 @@ fn http_network_or_cache_fetch(request: &mut Request, }, // Step 14 - CacheMode::NoCache if !http_request.headers.has::<CacheControl>() => { - http_request.headers.set(CacheControl(vec![CacheDirective::MaxAge(0)])); + CacheMode::NoCache if !http_request.headers.contains_key(header::CACHE_CONTROL) => { + http_request.headers.typed_insert(CacheControl::new().with_max_age(Duration::from_secs(0))); }, // Step 15 CacheMode::Reload | CacheMode::NoStore => { // Substep 1 - if !http_request.headers.has::<Pragma>() { - http_request.headers.set(Pragma::NoCache); + if !http_request.headers.contains_key(header::PRAGMA) { + http_request.headers.typed_insert(Pragma::no_cache()); } // Substep 2 - if !http_request.headers.has::<CacheControl>() { - http_request.headers.set(CacheControl(vec![CacheDirective::NoCache])); + if !http_request.headers.contains_key(header::CACHE_CONTROL) { + http_request.headers.typed_insert(CacheControl::new().with_no_cache()); } }, @@ -816,11 +753,13 @@ fn http_network_or_cache_fetch(request: &mut Request, // Step 16 let current_url = http_request.current_url(); - let host = Host { - hostname: current_url.host_str().unwrap().to_owned(), - port: current_url.port() - }; - http_request.headers.set(host); + let host = Host::from( + format!("{}{}", current_url.host_str().unwrap(), + current_url.port().map(|v| format!(":{}", v)).unwrap_or("".into()) + ).parse::<Authority>().unwrap() + ); + + http_request.headers.typed_insert(host); // unlike http_loader, we should not set the accept header // here, according to the fetch spec set_default_accept_encoding(&mut http_request.headers); @@ -835,7 +774,7 @@ fn http_network_or_cache_fetch(request: &mut Request, &mut http_request.headers, &context.state.cookie_jar); // Substep 2 - if !http_request.headers.has::<Authorization<String>>() { + if !http_request.headers.contains_key(header::AUTHORIZATION) { // Substep 3 let mut authorization_value = None; @@ -849,16 +788,16 @@ fn http_network_or_cache_fetch(request: &mut Request, // 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) - }) + authorization_value = Some(Authorization::basic( + current_url.username(), + current_url.password().unwrap_or("") + )); } } // Substep 6 if let Some(basic) = authorization_value { - http_request.headers.set(Authorization(basic)); + http_request.headers.typed_insert(basic); } } } @@ -886,17 +825,12 @@ fn http_network_or_cache_fetch(request: &mut Request, if needs_revalidation { revalidating_flag = true; // Substep 5 - // TODO: find out why the typed header getter return None from the headers of cached responses. - if let Some(date_slice) = response_headers.get_raw("Last-Modified") { - let date_string = String::from_utf8_lossy(&date_slice[0]); - if let Ok(http_date) = HttpDate::from_str(&date_string) { - http_request.headers.set(IfModifiedSince(http_date)); - } + if let Some(http_date) = response_headers.typed_get::<LastModified>() { + let http_date: SystemTime = http_date.into(); + http_request.headers.typed_insert(IfModifiedSince::from(http_date)); } - if let Some(entity_tag) = - response_headers.get_raw("ETag") { - http_request.headers.set_raw("If-None-Match", entity_tag.to_vec()); - + if let Some(entity_tag) = response_headers.get(header::ETAG) { + http_request.headers.insert(header::IF_NONE_MATCH, entity_tag.clone()); } } else { // Substep 6 @@ -945,14 +879,14 @@ fn http_network_or_cache_fetch(request: &mut Request, done_chan, context); // Substep 3 if let Some((200...399, _)) = forward_response.raw_status { - if !http_request.method.safe() { + if !http_request.method.is_safe() { if let Ok(mut http_cache) = context.state.http_cache.write() { http_cache.invalidate(&http_request, &forward_response); } } } // Substep 4 - if revalidating_flag && forward_response.status.map_or(false, |s| s == StatusCode::NotModified) { + if revalidating_flag && forward_response.status.as_ref().map_or(false, |s| s.0 == StatusCode::NOT_MODIFIED) { if let Ok(mut http_cache) = context.state.http_cache.write() { response = http_cache.refresh(&http_request, forward_response.clone(), done_chan); wait_for_cached_response(done_chan, &mut response); @@ -976,7 +910,9 @@ fn http_network_or_cache_fetch(request: &mut Request, // Step 23 // FIXME: Figure out what to do with request window objects - if let (Some(StatusCode::Unauthorized), false, true) = (response.status, cors_flag, credentials_flag) { + if let (Some((StatusCode::UNAUTHORIZED, _)), false, true) = + (response.status.as_ref(), cors_flag, credentials_flag) + { // Substep 1 // TODO: Spec says requires testing on multiple WWW-Authenticate headers @@ -1002,7 +938,7 @@ fn http_network_or_cache_fetch(request: &mut Request, } // Step 24 - if let Some(StatusCode::ProxyAuthenticationRequired) = response.status { + if let Some((StatusCode::PROXY_AUTHENTICATION_REQUIRED, _)) = response.status.as_ref() { // Step 1 if request_has_no_window { return Response::network_error(NetworkError::Internal("Can't find Window object".into())); @@ -1062,38 +998,38 @@ fn http_network_fetch(request: &Request, // 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(&context.state.connector, - &url, - &request.method, - &request.headers, - &request.body, &request.method, - &request.pipeline_id, request.redirect_count + 1, - request_id.as_ref().map(Deref::deref), is_xhr); + let response_future = obtain_response(&context.state.client, + &url, + &request.method, + &request.headers, + &request.body, &request.method, + &request.pipeline_id, request.redirect_count + 1, + request_id.as_ref().map(Deref::deref), is_xhr); let pipeline_id = request.pipeline_id; - let (res, msg) = match wrapped_response { + // This will only get the headers, the body is read later + let (res, msg) = match response_future.wait() { Ok(wrapped_response) => wrapped_response, Err(error) => return Response::network_error(error), }; if log_enabled!(log::Level::Info) { info!("response for {}", url); - for header in res.headers.iter() { - info!(" - {}", header); + for header in res.headers().iter() { + info!(" - {:?}", header); } } let mut response = Response::new(url.clone()); - response.status = Some(res.status); - response.raw_status = Some((res.status_raw().0, - res.status_raw().1.as_bytes().to_vec())); - response.headers = res.headers.clone(); + response.status = Some((res.status(), res.status().canonical_reason().unwrap_or("").into())); + response.raw_status = Some((res.status().as_u16(), res.status().canonical_reason().unwrap_or("").into())); + response.headers = res.headers().clone(); response.referrer = request.referrer.to_url().cloned(); response.referrer_policy = request.referrer_policy.clone(); let res_body = response.body.clone(); - // We're about to spawn a thread to be waited on here + // We're about to spawn a future to be waited on here let (done_sender, done_receiver) = channel(); *done_chan = Some((done_sender.clone(), done_receiver)); let meta = match response.metadata().expect("Response metadata should exist at this stage") { @@ -1107,68 +1043,56 @@ fn http_network_fetch(request: &Request, if cancellation_listener.lock().unwrap().cancelled() { return Response::network_error(NetworkError::Internal("Fetch aborted".into())) } - thread::Builder::new().name(format!("fetch worker thread")).spawn(move || { - match StreamedResponse::from_http_response(res) { - 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); - } - } + *res_body.lock().unwrap() = ResponseBody::Receiving(vec![]); - loop { - if cancellation_listener.lock().unwrap().cancelled() { - *res_body.lock().unwrap() = ResponseBody::Done(vec![]); - let _ = done_sender.send(Data::Cancelled); - return; - } - 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); - let _ = done_sender.send(Data::Payload(chunk)); - } - }, - Ok(Data::Done) | Err(_) => { - let mut body = res_body.lock().unwrap(); - let completed_body = match *body { - ResponseBody::Receiving(ref mut body) => { - mem::replace(body, vec![]) - }, - _ => vec![], - }; - *body = ResponseBody::Done(completed_body); - let _ = done_sender.send(Data::Done); - break; - } - Ok(Data::Cancelled) => unreachable!() // read_block doesn't return Data::Cancelled - } - } - } - Err(_) => { - // XXXManishearth we should propagate this error somehow - *res_body.lock().unwrap() = ResponseBody::Done(vec![]); - let _ = done_sender.send(Data::Done); - } + if let Some(ref sender) = devtools_sender { + if let Some(m) = msg { + send_request_to_devtools(m, &sender); } - }).expect("Thread spawning failed"); - // TODO these substeps aren't possible yet - // Substep 1 + // --- 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); + } + } - // Substep 2 + let done_sender = done_sender.clone(); + let done_sender2 = done_sender.clone(); + HANDLE.lock().unwrap().spawn(res.into_body().map_err(|_|()).fold(res_body, move |res_body, chunk| { + if cancellation_listener.lock().unwrap().cancelled() { + *res_body.lock().unwrap() = ResponseBody::Done(vec![]); + let _ = done_sender.send(Data::Cancelled); + return future::failed(()); + } + if let ResponseBody::Receiving(ref mut body) = *res_body.lock().unwrap() { + let bytes = chunk.into_bytes(); + body.extend_from_slice(&*bytes); + let _ = done_sender.send(Data::Payload(bytes.to_vec())); + } + future::ok(res_body) + }).and_then(move |res_body| { + let mut body = res_body.lock().unwrap(); + let completed_body = match *body { + ResponseBody::Receiving(ref mut body) => { + mem::replace(body, vec![]) + }, + _ => vec![], + }; + *body = ResponseBody::Done(completed_body); + let _ = done_sender2.send(Data::Done); + future::ok(()) + }).map_err(|_| ())); + + // 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" @@ -1183,11 +1107,11 @@ fn http_network_fetch(request: &Request, // 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) { + if let Some(encoding) = response.headers.typed_get::<ContentEncoding>() { + if encoding.contains("gzip") { } - else if encoding.contains(&Encoding::Compress) { + else if encoding.contains("compress") { } }; @@ -1228,7 +1152,7 @@ fn cors_preflight_fetch(request: &Request, -> Response { // Step 1 let mut preflight = Request::new(request.current_url(), Some(request.origin.clone()), request.pipeline_id); - preflight.method = Method::Options; + preflight.method = Method::OPTIONS; preflight.initiator = request.initiator.clone(); preflight.destination = request.destination.clone(); preflight.origin = request.origin.clone(); @@ -1236,20 +1160,24 @@ fn cors_preflight_fetch(request: &Request, preflight.referrer_policy = request.referrer_policy; // Step 2 - preflight.headers.set::<AccessControlRequestMethod>( - AccessControlRequestMethod(request.method.clone())); + preflight.headers.typed_insert::<AccessControlRequestMethod>( + AccessControlRequestMethod::from(request.method.clone())); // Step 3 let mut headers = request.headers .iter() - .filter(|view| !is_cors_safelisted_request_header(view)) - .map(|view| UniCase(view.name().to_ascii_lowercase().to_owned())) - .collect::<Vec<UniCase<String>>>(); + .filter(|(name, value)| !is_cors_safelisted_request_header(&name, &value)) + .map(|(name, _)| name.as_str()) + .collect::<Vec<&str>>(); headers.sort(); + let headers = headers + .iter() + .map(|name| HeaderName::from_str(name).unwrap()) + .collect::<Vec<HeaderName>>(); // Step 4 if !headers.is_empty() { - preflight.headers.set::<AccessControlRequestHeaders>(AccessControlRequestHeaders(headers)); + preflight.headers.typed_insert(AccessControlRequestHeaders::from_iter(headers)); } // Step 5 @@ -1257,11 +1185,11 @@ fn cors_preflight_fetch(request: &Request, // Step 6 if cors_check(&request, &response).is_ok() && - response.status.map_or(false, |status| status.is_success()) { + response.status.as_ref().map_or(false, |(status, _)| status.is_success()) { // Substep 1, 2 - let mut methods = if response.headers.has::<AccessControlAllowMethods>() { - match response.headers.get::<AccessControlAllowMethods>() { - Some(&AccessControlAllowMethods(ref m)) => m.clone(), + let mut methods = if response.headers.contains_key(header::ACCESS_CONTROL_ALLOW_METHODS) { + match response.headers.typed_get::<AccessControlAllowMethods>() { + Some(methods) => methods.iter().collect(), // Substep 4 None => return Response::network_error(NetworkError::Internal("CORS ACAM check failed".into())) } @@ -1270,9 +1198,9 @@ fn cors_preflight_fetch(request: &Request, }; // Substep 3 - let header_names = if response.headers.has::<AccessControlAllowHeaders>() { - match response.headers.get::<AccessControlAllowHeaders>() { - Some(&AccessControlAllowHeaders(ref hn)) => hn.clone(), + let header_names = if response.headers.contains_key(header::ACCESS_CONTROL_ALLOW_HEADERS) { + match response.headers.typed_get::<AccessControlAllowHeaders>() { + Some(names) => names.iter().collect(), // Substep 4 None => return Response::network_error(NetworkError::Internal("CORS ACAH check failed".into())) } @@ -1282,7 +1210,7 @@ fn cors_preflight_fetch(request: &Request, // Substep 5 if (methods.iter().any(|m| m.as_ref() == "*") || - header_names.iter().any(|hn| &**hn == "*")) && + header_names.iter().any(|hn| hn.as_str() == "*")) && request.credentials_mode == CredentialsMode::Include { return Response::network_error( NetworkError::Internal("CORS ACAH/ACAM and request credentials mode mismatch".into())); @@ -1304,22 +1232,24 @@ fn cors_preflight_fetch(request: &Request, // Substep 8 if request.headers.iter().any( - |header| header.name() == "authorization" && - header_names.iter().all(|hn| *hn != UniCase(header.name()))) { + |(name, _)| name == header::AUTHORIZATION && + header_names.iter().all(|hn| hn != name)) { return Response::network_error(NetworkError::Internal("CORS authorization check failed".into())); } // Substep 9 debug!("CORS check: Allowed headers: {:?}, current headers: {:?}", header_names, request.headers); - let set: HashSet<&UniCase<String>> = HashSet::from_iter(header_names.iter()); + let set: HashSet<&HeaderName> = HashSet::from_iter(header_names.iter()); if request.headers.iter().any( - |ref hv| !set.contains(&UniCase(hv.name().to_owned())) && !is_cors_safelisted_request_header(hv)) { + |(name, value)| !set.contains(name) && !is_cors_safelisted_request_header(&name, &value)) { return Response::network_error(NetworkError::Internal("CORS headers check failed".into())); } // Substep 10, 11 - let max_age = response.headers.get::<AccessControlMaxAge>().map(|acma| acma.0).unwrap_or(0); - + let max_age: Duration = response.headers.typed_get::<AccessControlMaxAge>() + .map(|acma| acma.into()) + .unwrap_or(Duration::from_secs(0)); + let max_age = max_age.as_secs() as u32; // Substep 12 // TODO: Need to define what an imposed limit on max-age is @@ -1346,26 +1276,26 @@ fn cors_preflight_fetch(request: &Request, /// [CORS check](https://fetch.spec.whatwg.org#concept-cors-check) fn cors_check(request: &Request, response: &Response) -> Result<(), ()> { // Step 1 - let origin = response.headers.get::<AccessControlAllowOrigin>().cloned(); + let origin = response.headers.typed_get::<AccessControlAllowOrigin>(); // Step 2 let origin = origin.ok_or(())?; // Step 3 if request.credentials_mode != CredentialsMode::Include && - origin == AccessControlAllowOrigin::Any { + origin == AccessControlAllowOrigin::ANY { return Ok(()); } // Step 4 - let origin = match origin { - AccessControlAllowOrigin::Value(origin) => origin, + let origin = match origin.origin() { + Some(origin) => origin, // if it's Any or Null at this point, there's nothing to do but return Err(()) - _ => return Err(()) + None => return Err(()) }; match request.origin { - Origin::Origin(ref o) if o.ascii_serialization() == origin.trim() => {}, + Origin::Origin(ref o) if o.ascii_serialization() == origin.to_string().trim() => {}, _ => return Err(()) } @@ -1375,7 +1305,7 @@ fn cors_check(request: &Request, response: &Response) -> Result<(), ()> { } // Step 6 - let credentials = response.headers.get::<AccessControlAllowCredentials>().cloned(); + let credentials = response.headers.typed_get::<AccessControlAllowCredentials>(); // Step 7 if credentials.is_some() { @@ -1390,20 +1320,20 @@ 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>() +fn is_no_store_cache(headers: &HeaderMap) -> bool { + headers.contains_key(header::IF_MODIFIED_SINCE) | headers.contains_key(header::IF_NONE_MATCH) | + headers.contains_key(header::IF_UNMODIFIED_SINCE) | headers.contains_key(header::IF_MATCH) | + headers.contains_key(header::IF_RANGE) } /// <https://fetch.spec.whatwg.org/#redirect-status> -pub fn is_redirect_status(status: StatusCode) -> bool { - match status { - StatusCode::MovedPermanently | - StatusCode::Found | - StatusCode::SeeOther | - StatusCode::TemporaryRedirect | - StatusCode::PermanentRedirect => true, +pub fn is_redirect_status(status: &(StatusCode, String)) -> bool { + match status.0 { + StatusCode::MOVED_PERMANENTLY | + StatusCode::FOUND | + StatusCode::SEE_OTHER | + StatusCode::TEMPORARY_REDIRECT | + StatusCode::PERMANENT_REDIRECT => true, _ => false, } } diff --git a/components/net/lib.rs b/components/net/lib.rs index 285a755a9e9..cebeb447aef 100644 --- a/components/net/lib.rs +++ b/components/net/lib.rs @@ -6,10 +6,14 @@ extern crate base64; extern crate brotli; +extern crate bytes; extern crate cookie as cookie_rs; extern crate devtools_traits; extern crate embedder_traits; extern crate flate2; +extern crate headers_core; +extern crate headers_ext; +extern crate http; extern crate hyper; extern crate hyper_openssl; extern crate hyper_serde; @@ -21,7 +25,6 @@ extern crate lazy_static; extern crate malloc_size_of; #[macro_use] extern crate malloc_size_of_derive; #[macro_use] #[no_link] extern crate matches; -#[macro_use] extern crate mime; extern crate mime_guess; extern crate msg; @@ -38,7 +41,7 @@ extern crate servo_channel; extern crate servo_config; extern crate servo_url; extern crate time; -extern crate unicase; +extern crate tokio; extern crate url; extern crate uuid; extern crate webrender_api; diff --git a/components/net/mime_classifier.rs b/components/net/mime_classifier.rs index f59dc2b925d..801f5bce9b3 100644 --- a/components/net/mime_classifier.rs +++ b/components/net/mime_classifier.rs @@ -2,9 +2,8 @@ * 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/. */ -use hyper::mime::TopLevel; +use mime::{self, Mime}; use net_traits::LoadContext; -use std::borrow::ToOwned; pub struct MimeClassifier { image_classifier: GroupedClassifier, @@ -49,34 +48,28 @@ pub enum NoSniffFlag { Off } -pub type MimeType = (TopLevel, String); - impl MimeClassifier { //Performs MIME Type Sniffing Algorithm (sections 7 and 8) - pub fn classify(&self, + pub fn classify<'a>(&'a self, context: LoadContext, no_sniff_flag: NoSniffFlag, apache_bug_flag: ApacheBugFlag, - supplied_type: &Option<MimeType>, - data: &[u8]) -> MimeType { - let supplied_type_or_octet_stream = supplied_type.clone() - .unwrap_or((TopLevel::Application, - "octet-stream".to_owned())); + supplied_type: &Option<Mime>, + data: &'a [u8]) -> Mime { + let supplied_type_or_octet_stream = supplied_type.clone().unwrap_or(mime::APPLICATION_OCTET_STREAM); match context { LoadContext::Browsing => match *supplied_type { None => self.sniff_unknown_type(no_sniff_flag, data), Some(ref supplied_type) => { - let &(ref media_type, ref media_subtype) = supplied_type; - if MimeClassifier::is_explicit_unknown(media_type, media_subtype) { + if MimeClassifier::is_explicit_unknown(supplied_type) { self.sniff_unknown_type(no_sniff_flag, data) } else { match no_sniff_flag { NoSniffFlag::On => supplied_type.clone(), NoSniffFlag::Off => match apache_bug_flag { ApacheBugFlag::On => self.sniff_text_or_data(data), - ApacheBugFlag::Off => match MimeClassifier::get_media_type(media_type, - media_subtype) { + ApacheBugFlag::Off => match MimeClassifier::get_media_type(supplied_type) { Some(MediaType::Html) => self.feeds_classifier.classify(data), Some(MediaType::Image) => self.image_classifier.classify(data), Some(MediaType::AudioVideo) => self.audio_video_classifier.classify(data), @@ -107,7 +100,7 @@ impl MimeClassifier { // This section was *not* finalized in the specs at the time // of this implementation. match *supplied_type { - None => (TopLevel::Application, "octet-stream".to_owned()), + None => mime::APPLICATION_OCTET_STREAM, _ => supplied_type_or_octet_stream, } }, @@ -117,7 +110,7 @@ impl MimeClassifier { // This section was *not* finalized in the specs at the time // of this implementation. match *supplied_type { - None => (TopLevel::Text, "css".to_owned()), + None => mime::TEXT_CSS, _ => supplied_type_or_octet_stream, } }, @@ -127,7 +120,7 @@ impl MimeClassifier { // This section was *not* finalized in the specs at the time // of this implementation. match *supplied_type { - None => (TopLevel::Text, "javascript".to_owned()), + None => mime::TEXT_JAVASCRIPT, _ => supplied_type_or_octet_stream, } }, @@ -143,14 +136,14 @@ impl MimeClassifier { // // This section was *not* finalized in the specs at the time // of this implementation. - (TopLevel::Text, "vtt".to_owned()) + "text/vtt".parse().unwrap() }, LoadContext::CacheManifest => { // 8.9 Sniffing in a cache manifest context // // This section was *not* finalized in the specs at the time // of this implementation. - (TopLevel::Text, "cache-manifest".to_owned()) + "text/cache-manifest".parse().unwrap() }, } } @@ -181,7 +174,7 @@ impl MimeClassifier { } //some sort of iterator over the classifiers might be better? - fn sniff_unknown_type(&self, no_sniff_flag: NoSniffFlag, data: &[u8]) -> MimeType { + fn sniff_unknown_type(&self, no_sniff_flag: NoSniffFlag, data: &[u8]) -> Mime { let should_sniff_scriptable = no_sniff_flag == NoSniffFlag::Off; let sniffed = if should_sniff_scriptable { self.scriptable_classifier.classify(data) @@ -197,72 +190,60 @@ impl MimeClassifier { .expect("BinaryOrPlaintextClassifier always succeeds") } - fn sniff_text_or_data(&self, data: &[u8]) -> MimeType { + fn sniff_text_or_data<'a>(&'a self, data: &'a [u8]) -> Mime { self.binary_or_plaintext.classify(data).expect("BinaryOrPlaintextClassifier always succeeds") } - fn is_xml(tp: &TopLevel, sub_tp: &str) -> bool { - sub_tp.ends_with("+xml") || - match (tp, sub_tp) { - (&TopLevel::Application, "xml") | (&TopLevel::Text, "xml") => true, - _ => false - } + fn is_xml(mt: &Mime) -> bool { + mt.suffix() == Some(mime::XML) || + (mt.type_() == mime::APPLICATION && mt.subtype() == mime::XML) || + (mt.type_() == mime::TEXT && mt.subtype() == mime::XML) } - fn is_html(tp: &TopLevel, sub_tp: &str) -> bool { - *tp == TopLevel::Text && sub_tp == "html" + fn is_html(mt: &Mime) -> bool { + mt.type_() == mime::TEXT && mt.subtype() == mime::HTML } - fn is_image(tp: &TopLevel) -> bool { - *tp == TopLevel::Image + fn is_image(mt: &Mime) -> bool { + mt.type_() == mime::IMAGE } - fn is_audio_video(tp: &TopLevel, sub_tp: &str) -> bool { - *tp == TopLevel::Audio || - *tp == TopLevel::Video || - (*tp == TopLevel::Application && sub_tp == "ogg") + fn is_audio_video(mt: &Mime) -> bool { + mt.type_() == mime::AUDIO || + mt.type_() == mime::VIDEO || + mt.type_() == mime::APPLICATION && mt.subtype() == mime::OGG } - fn is_explicit_unknown(tp: &TopLevel, sub_tp: &str) -> bool { - if let TopLevel::Ext(ref e) = *tp { - return e == "unknown" && sub_tp == "unknown"; - } - match (tp, sub_tp) { - (&TopLevel::Application, "unknown") | - (&TopLevel::Star, "*") => true, - _ => false - } + fn is_explicit_unknown(mt: &Mime) -> bool { + mt.type_().as_str() == "unknown" && mt.subtype().as_str() == "unknown" || + mt.type_() == mime::APPLICATION && mt.subtype().as_str() == "unknown" || + mt.type_() == mime::STAR && mt.subtype() == mime::STAR } - fn get_media_type(media_type: &TopLevel, - media_subtype: &str) -> Option<MediaType> { - if MimeClassifier::is_xml(media_type, media_subtype) { + fn get_media_type(mime: &Mime) -> Option<MediaType> { + if MimeClassifier::is_xml(&mime) { Some(MediaType::Xml) - } else if MimeClassifier::is_html(media_type, media_subtype) { + } else if MimeClassifier::is_html(&mime) { Some(MediaType::Html) - } else if MimeClassifier::is_image(media_type) { + } else if MimeClassifier::is_image(&mime) { Some(MediaType::Image) - } else if MimeClassifier::is_audio_video(media_type, media_subtype) { + } else if MimeClassifier::is_audio_video(&mime) { Some(MediaType::AudioVideo) } else { None } } - fn maybe_get_media_type(supplied_type: &Option<MimeType>) -> Option<MediaType> { - supplied_type.as_ref().and_then(|&(ref media_type, ref media_subtype)| { - MimeClassifier::get_media_type(media_type, media_subtype) + fn maybe_get_media_type(supplied_type: &Option<Mime>) -> Option<MediaType> { + supplied_type.as_ref().and_then(|ref mime| { + MimeClassifier::get_media_type(mime) }) } } -pub fn as_string_option(tup: Option<(TopLevel, &'static str)>) -> Option<MimeType> { - tup.map(|(a, b)| (a.to_owned(), b.to_owned())) -} - //Interface used for composite types trait MIMEChecker { - fn classify(&self, data: &[u8]) -> Option<MimeType>; + fn classify(&self, data: &[u8]) -> Option<Mime>; /// Validate the MIME checker configuration fn validate(&self) -> Result<(), String>; } @@ -303,7 +284,7 @@ struct ByteMatcher { pattern: &'static [u8], mask: &'static [u8], leading_ignore: &'static [u8], - content_type: (TopLevel, &'static str) + content_type: Mime, } impl ByteMatcher { @@ -328,31 +309,31 @@ impl ByteMatcher { } impl MIMEChecker for ByteMatcher { - fn classify(&self, data: &[u8]) -> Option<MimeType> { + fn classify(&self, data: &[u8]) -> Option<Mime> { self.matches(data).map(|_| { - (self.content_type.0.to_owned(), self.content_type.1.to_owned()) + self.content_type.clone() }) } fn validate(&self) -> Result<(), String> { if self.pattern.len() == 0 { return Err(format!( - "Zero length pattern for {}/{}", - self.content_type.0, self.content_type.1 + "Zero length pattern for {:?}", + self.content_type )) } if self.pattern.len() != self.mask.len() { return Err(format!( - "Unequal pattern and mask length for {}/{}", - self.content_type.0, self.content_type.1 + "Unequal pattern and mask length for {:?}", + self.content_type )) } if self.pattern.iter().zip(self.mask.iter()).any( |(&pattern, &mask)| pattern & mask != pattern ) { return Err(format!( - "Pattern not pre-masked for {}/{}", - self.content_type.0, self.content_type.1 + "Pattern not pre-masked for {:?}", + self.content_type )) } Ok(()) @@ -364,11 +345,10 @@ struct TagTerminatedByteMatcher { } impl MIMEChecker for TagTerminatedByteMatcher { - fn classify(&self, data: &[u8]) -> Option<MimeType> { + fn classify(&self, data: &[u8]) -> Option<Mime> { self.matcher.matches(data).and_then(|j| if j < data.len() && (data[j] == b' ' || data[j] == b'>') { - Some((self.matcher.content_type.0.to_owned(), - self.matcher.content_type.1.to_owned())) + Some(self.matcher.content_type.clone()) } else { None }) @@ -405,9 +385,9 @@ impl Mp4Matcher { } impl MIMEChecker for Mp4Matcher { - fn classify(&self, data: &[u8]) -> Option<MimeType> { + fn classify(&self, data: &[u8]) -> Option<Mime> { if self.matches(data) { - Some((TopLevel::Video, "mp4".to_owned())) + Some("video/mp4".parse().unwrap()) } else { None } @@ -421,25 +401,25 @@ impl MIMEChecker for Mp4Matcher { struct BinaryOrPlaintextClassifier; impl BinaryOrPlaintextClassifier { - fn classify_impl(&self, data: &[u8]) -> (TopLevel, &'static str) { + fn classify_impl(&self, data: &[u8]) -> Mime { if data.starts_with(&[0xFFu8, 0xFEu8]) || data.starts_with(&[0xFEu8, 0xFFu8]) || data.starts_with(&[0xEFu8, 0xBBu8, 0xBFu8]) { - (TopLevel::Text, "plain") + mime::TEXT_PLAIN } else if data.iter().any(|&x| x <= 0x08u8 || x == 0x0Bu8 || (x >= 0x0Eu8 && x <= 0x1Au8) || (x >= 0x1Cu8 && x <= 0x1Fu8)) { - (TopLevel::Application, "octet-stream") + mime::APPLICATION_OCTET_STREAM } else { - (TopLevel::Text, "plain") + mime::TEXT_PLAIN } } } impl MIMEChecker for BinaryOrPlaintextClassifier { - fn classify(&self, data: &[u8]) -> Option<MimeType> { - as_string_option(Some(self.classify_impl(data))) + fn classify(&self, data: &[u8]) -> Option<Mime> { + Some(self.classify_impl(data)) } fn validate(&self) -> Result<(), String> { @@ -538,7 +518,7 @@ impl GroupedClassifier { } } impl MIMEChecker for GroupedClassifier { - fn classify(&self, data: &[u8]) -> Option<MimeType> { + fn classify(&self, data: &[u8]) -> Option<Mime> { self.byte_matchers .iter() .filter_map(|matcher| matcher.classify(data)) @@ -591,7 +571,7 @@ where T: Iterator<Item=&'a u8> + Clone { struct FeedsClassifier; impl FeedsClassifier { // Implements sniffing for mislabeled feeds (https://mimesniff.spec.whatwg.org/#sniffing-a-mislabeled-feed) - fn classify_impl(&self, data: &[u8]) -> Option<(TopLevel, &'static str)> { + fn classify_impl(&self, data: &[u8]) -> Option<Mime> { // Step 4: can not be feed unless length is > 3 if data.len() < 3 { return None; @@ -622,11 +602,11 @@ impl FeedsClassifier { // Step 5.2.5 if matcher.matches(b"rss") { - return Some((TopLevel::Application, "rss+xml")); + return Some("application/rss+xml".parse().unwrap()); } // Step 5.2.6 if matcher.matches(b"feed") { - return Some((TopLevel::Application, "atom+xml")); + return Some("application/atom+xml".parse().unwrap()); } // Step 5.2.7 if matcher.matches(b"rdf:RDF") { @@ -637,7 +617,7 @@ impl FeedsClassifier { .chain(|| eats_until(&mut matcher, b"http://www.w3.org/1999/02/22-rdf-syntax-ns#", b"http://purl.org/rss/1.0/")) { - Match::StartAndEnd => return Some((TopLevel::Application, "rss+xml")), + Match::StartAndEnd => return Some("application/rss+xml".parse().unwrap()), Match::DidNotMatch => {}, Match::Start => return None } @@ -649,8 +629,8 @@ impl FeedsClassifier { } impl MIMEChecker for FeedsClassifier { - fn classify(&self, data: &[u8]) -> Option<MimeType> { - as_string_option(self.classify_impl(data)) + fn classify(&self, data: &[u8]) -> Option<Mime> { + self.classify_impl(data) } fn validate(&self) -> Result<(), String> { @@ -666,7 +646,7 @@ impl ByteMatcher { ByteMatcher { pattern: b"\x00\x00\x01\x00", mask: b"\xFF\xFF\xFF\xFF", - content_type: (TopLevel::Image, "x-icon"), + content_type: "image/x-icon".parse().unwrap(), leading_ignore: &[] } } @@ -675,7 +655,7 @@ impl ByteMatcher { ByteMatcher { pattern: b"\x00\x00\x02\x00", mask: b"\xFF\xFF\xFF\xFF", - content_type: (TopLevel::Image, "x-icon"), + content_type: "image/x-icon".parse().unwrap(), leading_ignore: &[] } } @@ -684,7 +664,7 @@ impl ByteMatcher { ByteMatcher { pattern: b"BM", mask: b"\xFF\xFF", - content_type: (TopLevel::Image, "bmp"), + content_type: mime::IMAGE_BMP, leading_ignore: &[] } } @@ -693,7 +673,7 @@ impl ByteMatcher { ByteMatcher { pattern: b"GIF89a", mask: b"\xFF\xFF\xFF\xFF\xFF\xFF", - content_type: (TopLevel::Image, "gif"), + content_type: mime::IMAGE_GIF, leading_ignore: &[] } } @@ -702,7 +682,7 @@ impl ByteMatcher { ByteMatcher { pattern: b"GIF87a", mask: b"\xFF\xFF\xFF\xFF\xFF\xFF", - content_type: (TopLevel::Image, "gif"), + content_type: mime::IMAGE_GIF, leading_ignore: &[] } } @@ -711,7 +691,7 @@ impl ByteMatcher { ByteMatcher { pattern: b"RIFF\x00\x00\x00\x00WEBPVP", mask: b"\xFF\xFF\xFF\xFF\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF", - content_type: (TopLevel::Image, "webp"), + content_type: "image/webp".parse().unwrap(), leading_ignore: &[] } } @@ -721,7 +701,7 @@ impl ByteMatcher { ByteMatcher { pattern: b"\x89PNG\r\n\x1A\n", mask: b"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", - content_type: (TopLevel::Image, "png"), + content_type: mime::IMAGE_PNG, leading_ignore: &[] } } @@ -730,7 +710,7 @@ impl ByteMatcher { ByteMatcher { pattern: b"\xFF\xD8\xFF", mask: b"\xFF\xFF\xFF", - content_type: (TopLevel::Image, "jpeg"), + content_type: mime::IMAGE_JPEG, leading_ignore: &[] } } @@ -739,7 +719,7 @@ impl ByteMatcher { ByteMatcher { pattern: b"\x1A\x45\xDF\xA3", mask: b"\xFF\xFF\xFF\xFF", - content_type: (TopLevel::Video, "webm"), + content_type: "video/webm".parse().unwrap(), leading_ignore: &[] } } @@ -748,7 +728,7 @@ impl ByteMatcher { ByteMatcher { pattern: b".snd", mask: b"\xFF\xFF\xFF\xFF", - content_type: (TopLevel::Audio, "basic"), + content_type: "audio/basic".parse().unwrap(), leading_ignore: &[] } } @@ -757,7 +737,7 @@ impl ByteMatcher { ByteMatcher { pattern: b"FORM\x00\x00\x00\x00AIFF", mask: b"\xFF\xFF\xFF\xFF\x00\x00\x00\x00\xFF\xFF\xFF\xFF", - content_type: (TopLevel::Audio, "aiff"), + content_type: "audio/aiff".parse().unwrap(), leading_ignore: &[] } } @@ -766,7 +746,7 @@ impl ByteMatcher { ByteMatcher { pattern: b"ID3", mask: b"\xFF\xFF\xFF", - content_type: (TopLevel::Audio, "mpeg"), + content_type: "audio/mpeg".parse().unwrap(), leading_ignore: &[] } } @@ -775,7 +755,7 @@ impl ByteMatcher { ByteMatcher { pattern: b"OggS\x00", mask: b"\xFF\xFF\xFF\xFF\xFF", - content_type: (TopLevel::Application, "ogg"), + content_type: "application/ogg".parse().unwrap(), leading_ignore: &[] } } @@ -785,7 +765,7 @@ impl ByteMatcher { ByteMatcher { pattern: b"MThd\x00\x00\x00\x06", mask: b"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", - content_type: (TopLevel::Audio, "midi"), + content_type: "audio/midi".parse().unwrap(), leading_ignore: &[] } } @@ -794,7 +774,7 @@ impl ByteMatcher { ByteMatcher { pattern: b"RIFF\x00\x00\x00\x00AVI ", mask: b"\xFF\xFF\xFF\xFF\x00\x00\x00\x00\xFF\xFF\xFF\xFF", - content_type: (TopLevel::Video, "avi"), + content_type: "video/avi".parse().unwrap(), leading_ignore: &[] } } @@ -803,7 +783,7 @@ impl ByteMatcher { ByteMatcher { pattern: b"RIFF\x00\x00\x00\x00WAVE", mask: b"\xFF\xFF\xFF\xFF\x00\x00\x00\x00\xFF\xFF\xFF\xFF", - content_type: (TopLevel::Audio, "wave"), + content_type: "audio/wave".parse().unwrap(), leading_ignore: &[] } } @@ -813,7 +793,7 @@ impl ByteMatcher { matcher: ByteMatcher { pattern: b"<!DOCTYPE HTML", mask: b"\xFF\xFF\xDF\xDF\xDF\xDF\xDF\xDF\xDF\xFF\xDF\xDF\xDF\xDF", - content_type: (TopLevel::Text, "html"), + content_type: mime::TEXT_HTML, leading_ignore: b"\t\n\x0C\r " } } @@ -825,7 +805,7 @@ impl ByteMatcher { matcher: ByteMatcher { pattern: b"<HTML", mask: b"\xFF\xDF\xDF\xDF\xDF", - content_type: (TopLevel::Text, "html"), + content_type: mime::TEXT_HTML, leading_ignore: b"\t\n\x0C\r " } } @@ -837,7 +817,7 @@ impl ByteMatcher { matcher: ByteMatcher { pattern: b"<HEAD", mask: b"\xFF\xDF\xDF\xDF\xDF", - content_type: (TopLevel::Text, "html"), + content_type: mime::TEXT_HTML, leading_ignore: b"\t\n\x0C\r " } } @@ -849,7 +829,7 @@ impl ByteMatcher { matcher: ByteMatcher { pattern: b"<SCRIPT", mask: b"\xFF\xDF\xDF\xDF\xDF\xDF\xDF", - content_type: (TopLevel::Text, "html"), + content_type: mime::TEXT_HTML, leading_ignore: b"\t\n\x0C\r " } } @@ -861,7 +841,7 @@ impl ByteMatcher { matcher: ByteMatcher { pattern: b"<IFRAME", mask: b"\xFF\xDF\xDF\xDF\xDF\xDF\xDF", - content_type: (TopLevel::Text, "html"), + content_type: mime::TEXT_HTML, leading_ignore: b"\t\n\x0C\r " } } @@ -873,7 +853,7 @@ impl ByteMatcher { matcher: ByteMatcher { pattern: b"<H1", mask: b"\xFF\xDF\xFF", - content_type: (TopLevel::Text, "html"), + content_type: mime::TEXT_HTML, leading_ignore: b"\t\n\x0C\r " } } @@ -885,7 +865,7 @@ impl ByteMatcher { matcher: ByteMatcher { pattern: b"<DIV", mask: b"\xFF\xDF\xDF\xDF", - content_type: (TopLevel::Text, "html"), + content_type: mime::TEXT_HTML, leading_ignore: b"\t\n\x0C\r " } } @@ -897,7 +877,7 @@ impl ByteMatcher { matcher: ByteMatcher { pattern: b"<FONT", mask: b"\xFF\xDF\xDF\xDF\xDF", - content_type: (TopLevel::Text, "html"), + content_type: mime::TEXT_HTML, leading_ignore: b"\t\n\x0C\r " } } @@ -909,7 +889,7 @@ impl ByteMatcher { matcher: ByteMatcher { pattern: b"<TABLE", mask: b"\xFF\xDF\xDF\xDF\xDF\xDF", - content_type: (TopLevel::Text, "html"), + content_type: mime::TEXT_HTML, leading_ignore: b"\t\n\x0C\r " } } @@ -921,7 +901,7 @@ impl ByteMatcher { matcher: ByteMatcher { pattern: b"<A", mask: b"\xFF\xDF", - content_type: (TopLevel::Text, "html"), + content_type: mime::TEXT_HTML, leading_ignore: b"\t\n\x0C\r " } } @@ -933,7 +913,7 @@ impl ByteMatcher { matcher: ByteMatcher { pattern: b"<STYLE", mask: b"\xFF\xDF\xDF\xDF\xDF\xDF", - content_type: (TopLevel::Text, "html"), + content_type: mime::TEXT_HTML, leading_ignore: b"\t\n\x0C\r " } } @@ -945,7 +925,7 @@ impl ByteMatcher { matcher: ByteMatcher { pattern: b"<TITLE", mask: b"\xFF\xDF\xDF\xDF\xDF\xDF", - content_type: (TopLevel::Text, "html"), + content_type: mime::TEXT_HTML, leading_ignore: b"\t\n\x0C\r " } } @@ -957,7 +937,7 @@ impl ByteMatcher { matcher: ByteMatcher { pattern: b"<B", mask: b"\xFF\xDF", - content_type: (TopLevel::Text, "html"), + content_type: mime::TEXT_HTML, leading_ignore: b"\t\n\x0C\r " } } @@ -969,7 +949,7 @@ impl ByteMatcher { matcher: ByteMatcher { pattern: b"<BODY", mask: b"\xFF\xDF\xDF\xDF\xDF", - content_type: (TopLevel::Text, "html"), + content_type: mime::TEXT_HTML, leading_ignore: b"\t\n\x0C\r " } } @@ -981,7 +961,7 @@ impl ByteMatcher { matcher: ByteMatcher { pattern: b"<BR", mask: b"\xFF\xDF\xDF", - content_type: (TopLevel::Text, "html"), + content_type: mime::TEXT_HTML, leading_ignore: b"\t\n\x0C\r " } } @@ -993,7 +973,7 @@ impl ByteMatcher { matcher: ByteMatcher { pattern: b"<P", mask: b"\xFF\xDF", - content_type: (TopLevel::Text, "html"), + content_type: mime::TEXT_HTML, leading_ignore: b"\t\n\x0C\r " } } @@ -1005,7 +985,7 @@ impl ByteMatcher { matcher: ByteMatcher { pattern: b"<!--", mask: b"\xFF\xFF\xFF\xFF", - content_type: (TopLevel::Text, "html"), + content_type: mime::TEXT_HTML, leading_ignore: b"\t\n\x0C\r " } } @@ -1016,7 +996,7 @@ impl ByteMatcher { ByteMatcher { pattern: b"<?xml", mask: b"\xFF\xFF\xFF\xFF\xFF", - content_type: (TopLevel::Text, "xml"), + content_type: mime::TEXT_XML, leading_ignore: b"\t\n\x0C\r " } } @@ -1025,7 +1005,7 @@ impl ByteMatcher { ByteMatcher { pattern: b"%PDF-", mask: b"\xFF\xFF\xFF\xFF\xFF", - content_type: (TopLevel::Application, "pdf"), + content_type: mime::APPLICATION_PDF, leading_ignore: &[] } } @@ -1038,7 +1018,7 @@ impl ByteMatcher { mask: b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\xFF\xFF", - content_type: (TopLevel::Application, "vnd.ms-fontobject"), + content_type: "application/vnd.ms-fontobject".parse().unwrap(), leading_ignore: &[] } } @@ -1047,7 +1027,7 @@ impl ByteMatcher { ByteMatcher { pattern: b"\x00\x01\x00\x00", mask: b"\xFF\xFF\xFF\xFF", - content_type: (TopLevel::Application, "font-sfnt"), + content_type: "application/font-sfnt".parse().unwrap(), leading_ignore: &[] } } @@ -1056,7 +1036,7 @@ impl ByteMatcher { ByteMatcher { pattern: b"OTTO", mask: b"\xFF\xFF\xFF\xFF", - content_type: (TopLevel::Application, "font-sfnt"), + content_type: "application/font-sfnt".parse().unwrap(), leading_ignore: &[] } } @@ -1065,7 +1045,7 @@ impl ByteMatcher { ByteMatcher { pattern: b"ttcf", mask: b"\xFF\xFF\xFF\xFF", - content_type: (TopLevel::Application, "font-sfnt"), + content_type: "application/font-sfnt".parse().unwrap(), leading_ignore: &[] } } @@ -1074,7 +1054,7 @@ impl ByteMatcher { ByteMatcher { pattern: b"wOFF", mask: b"\xFF\xFF\xFF\xFF", - content_type: (TopLevel::Application, "font-woff"), + content_type: "application/font-woff".parse().unwrap(), leading_ignore: &[] } } @@ -1083,7 +1063,7 @@ impl ByteMatcher { ByteMatcher { pattern: b"\x1F\x8B\x08", mask: b"\xFF\xFF\xFF", - content_type: (TopLevel::Application, "x-gzip"), + content_type: "application/x-gzip".parse().unwrap(), leading_ignore: &[] } } @@ -1092,7 +1072,7 @@ impl ByteMatcher { ByteMatcher { pattern: b"PK\x03\x04", mask: b"\xFF\xFF\xFF\xFF", - content_type: (TopLevel::Application, "zip"), + content_type: "application/zip".parse().unwrap(), leading_ignore: &[] } } @@ -1101,7 +1081,7 @@ impl ByteMatcher { ByteMatcher { pattern: b"Rar \x1A\x07\x00", mask: b"\xFF\xFF\xFF\xFF\xFF\xFF\xFF", - content_type: (TopLevel::Application, "x-rar-compressed"), + content_type: "application/x-rar-compressed".parse().unwrap(), leading_ignore: &[] } } @@ -1110,7 +1090,7 @@ impl ByteMatcher { ByteMatcher { pattern: b"%!PS-Adobe-", mask: b"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", - content_type: (TopLevel::Application, "postscript"), + content_type: "application/postscript".parse().unwrap(), leading_ignore: &[] } } @@ -1119,7 +1099,7 @@ impl ByteMatcher { ByteMatcher { pattern: b"\xFE\xFF\x00\x00", mask: b"\xFF\xFF\x00\x00", - content_type: (TopLevel::Text, "plain"), + content_type: mime::TEXT_PLAIN, leading_ignore: &[] } } @@ -1128,7 +1108,7 @@ impl ByteMatcher { ByteMatcher { pattern: b"\xFF\xFE\x00\x00", mask: b"\xFF\xFF\x00\x00", - content_type: (TopLevel::Text, "plain"), + content_type: mime::TEXT_PLAIN, leading_ignore: &[] } } @@ -1137,7 +1117,7 @@ impl ByteMatcher { ByteMatcher { pattern: b"\xEF\xBB\xBF\x00", mask: b"\xFF\xFF\xFF\x00", - content_type: (TopLevel::Text, "plain"), + content_type: mime::TEXT_PLAIN, leading_ignore: &[] } } diff --git a/components/net/resource_thread.rs b/components/net/resource_thread.rs index 0754750a952..74a18760cb0 100644 --- a/components/net/resource_thread.rs +++ b/components/net/resource_thread.rs @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ //! A thread that takes a URL and streams back the binary data. -use connector::{create_http_connector, create_ssl_client}; +use connector::{create_http_client, create_ssl_connector_builder}; use cookie; use cookie_rs; use cookie_storage::CookieStorage; @@ -15,7 +15,7 @@ use fetch::methods::{CancellationListener, FetchContext, fetch}; use filemanager_thread::FileManager; use hsts::HstsList; use http_cache::HttpCache; -use http_loader::{HttpState, http_redirect_fetch}; +use http_loader::{HANDLE, HttpState, http_redirect_fetch}; use hyper_serde::Serde; use ipc_channel::ipc::{self, IpcReceiver, IpcReceiverSet, IpcSender}; use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; @@ -129,18 +129,17 @@ fn create_http_states(config_dir: Option<&Path>) -> (Arc<HttpState>, Arc<HttpSta }, }; - let ssl_client = create_ssl_client(&certs); + let ssl_connector_builder = create_ssl_connector_builder(&certs); let http_state = HttpState { cookie_jar: RwLock::new(cookie_jar), auth_cache: RwLock::new(auth_cache), http_cache: RwLock::new(http_cache), hsts_list: RwLock::new(hsts_list), history_states: RwLock::new(HashMap::new()), - ssl_client: ssl_client.clone(), - connector: create_http_connector(ssl_client), + client: create_http_client(ssl_connector_builder, HANDLE.lock().unwrap().executor()), }; - let private_ssl_client = create_ssl_client(&certs); + let private_ssl_client = create_ssl_connector_builder(&certs); let private_http_state = HttpState::new(private_ssl_client); (Arc::new(http_state), Arc::new(private_http_state)) diff --git a/components/net/subresource_integrity.rs b/components/net/subresource_integrity.rs index 5088cc114b2..e0a7168de97 100644 --- a/components/net/subresource_integrity.rs +++ b/components/net/subresource_integrity.rs @@ -4,10 +4,11 @@ use base64; use net_traits::response::{Response, ResponseBody, ResponseType}; -use openssl::hash::{MessageDigest, hash2}; +use openssl::hash::{MessageDigest, hash}; use std::iter::Filter; use std::str::Split; use std::sync::MutexGuard; + const SUPPORTED_ALGORITHM: &'static [&'static str] = &[ "sha256", "sha384", @@ -119,7 +120,7 @@ fn apply_algorithm_to_response(body: MutexGuard<ResponseBody>, message_digest: MessageDigest) -> String { if let ResponseBody::Done(ref vec) = *body { - let response_digest = hash2(message_digest, vec).unwrap(); //Now hash2 + let response_digest = hash(message_digest, vec).unwrap(); //Now hash base64::encode(&response_digest) } else { unreachable!("Tried to calculate digest of incomplete response body") diff --git a/components/net/tests/cookie.rs b/components/net/tests/cookie.rs index 68149736680..3702f93fee2 100644 --- a/components/net/tests/cookie.rs +++ b/components/net/tests/cookie.rs @@ -3,7 +3,6 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use cookie_rs; -use hyper::header::{Header, SetCookie}; use net::cookie::Cookie; use net::cookie_storage::CookieStorage; use net_traits::CookieSource; @@ -90,7 +89,7 @@ fn fn_cookie_constructor() { let cookie = Cookie::new_wrapped(cookie, url, CookieSource::HTTP).unwrap(); assert_eq!(cookie.cookie.value(), "bar"); assert_eq!(cookie.cookie.name(), "baz"); - assert!(cookie.cookie.secure()); + assert!(cookie.cookie.secure().unwrap_or(false)); assert_eq!(&cookie.cookie.path().as_ref().unwrap()[..], "/foo/bar/"); assert_eq!(&cookie.cookie.domain().as_ref().unwrap()[..], "example.com"); assert!(cookie.host_only); @@ -324,13 +323,8 @@ fn add_retrieve_cookies(set_location: &str, // Add all cookies to the store for str_cookie in set_cookies { - let bytes = str_cookie.to_string().into_bytes(); - let header = Header::parse_header(&[bytes]).unwrap(); - let SetCookie(cookies) = header; - for bare_cookie in cookies { - let cookie = Cookie::from_cookie_string(bare_cookie, &url, source).unwrap(); - storage.push(cookie, &url, source); - } + let cookie = Cookie::from_cookie_string(str_cookie.to_owned(), &url, source).unwrap(); + storage.push(cookie, &url, source); } // Get cookies for the test location diff --git a/components/net/tests/cookie_http_state.rs b/components/net/tests/cookie_http_state.rs index 30cec8b26b2..ab310f6a35e 100644 --- a/components/net/tests/cookie_http_state.rs +++ b/components/net/tests/cookie_http_state.rs @@ -2,13 +2,11 @@ * 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/. */ -use hyper::header::{Header, SetCookie}; use net::cookie::Cookie; use net::cookie_storage::CookieStorage; use net_traits::CookieSource; use servo_url::ServoUrl; - fn run(set_location: &str, set_cookies: &[&str], final_location: &str) -> String { let mut storage = CookieStorage::new(150); let url = ServoUrl::parse(set_location).unwrap(); @@ -16,14 +14,8 @@ fn run(set_location: &str, set_cookies: &[&str], final_location: &str) -> String // Add all cookies to the store for str_cookie in set_cookies { - let bytes = str_cookie.to_string().into_bytes(); - let header = Header::parse_header(&[bytes]); - if let Ok(SetCookie(cookies)) = header { - for bare_cookie in cookies { - if let Some(cookie) = Cookie::from_cookie_string(bare_cookie, &url, source) { - storage.push(cookie, &url, source); - } - } + if let Some(cookie) = Cookie::from_cookie_string(str_cookie.to_owned().into(), &url, source) { + storage.push(cookie, &url, source); } } diff --git a/components/net/tests/data_loader.rs b/components/net/tests/data_loader.rs index 4ad73dee080..56b70af9e99 100644 --- a/components/net/tests/data_loader.rs +++ b/components/net/tests/data_loader.rs @@ -3,9 +3,10 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use fetch; -use hyper::header::ContentType; -use hyper::mime::{Attr, Mime, SubLevel, TopLevel, Value}; +use headers_core::HeaderMapExt; +use headers_ext::ContentType; use hyper_serde::Serde; +use mime::{self, Mime}; use net_traits::{FetchMetadata, FilteredMetadata, NetworkError}; use net_traits::request::{Origin, Request}; use net_traits::response::ResponseBody; @@ -28,8 +29,8 @@ fn assert_parse(url: &'static str, assert!(!response.is_network_error()); assert_eq!(response.headers.len(), 1); - let header_content_type = response.headers.get::<ContentType>(); - assert_eq!(header_content_type, content_type.as_ref()); + let header_content_type = response.headers.typed_get::<ContentType>(); + assert_eq!(header_content_type, content_type); let metadata = match response.metadata() { Ok(FetchMetadata::Filtered { filtered: FilteredMetadata::Basic(m), .. }) => m, @@ -62,9 +63,8 @@ fn empty_invalid() { fn plain() { assert_parse( "data:,hello%20world", - Some(ContentType(Mime(TopLevel::Text, SubLevel::Plain, - vec!((Attr::Charset, Value::Ext("US-ASCII".to_owned())))))), - Some("US-ASCII"), + Some(ContentType::from("text/plain; charset=US-ASCII".parse::<Mime>().unwrap())), + Some("us-ascii"), Some(b"hello world")); } @@ -72,7 +72,7 @@ fn plain() { fn plain_ct() { assert_parse( "data:text/plain,hello", - Some(ContentType(Mime(TopLevel::Text, SubLevel::Plain, vec!()))), + Some(ContentType::from(mime::TEXT_PLAIN)), None, Some(b"hello")); } @@ -81,7 +81,7 @@ fn plain_ct() { fn plain_html() { assert_parse( "data:text/html,<p>Servo</p>", - Some(ContentType(Mime(TopLevel::Text, SubLevel::Html, vec!()))), + Some(ContentType::from(mime::TEXT_HTML)), None, Some(b"<p>Servo</p>")); } @@ -90,9 +90,7 @@ fn plain_html() { fn plain_charset() { assert_parse( "data:text/plain;charset=latin1,hello", - Some(ContentType(Mime(TopLevel::Text, - SubLevel::Plain, - vec!((Attr::Charset, Value::Ext("latin1".to_owned())))))), + Some(ContentType::from("text/plain; charset=latin1".parse::<Mime>().unwrap())), Some("latin1"), Some(b"hello")); } @@ -101,9 +99,7 @@ fn plain_charset() { fn plain_only_charset() { assert_parse( "data:;charset=utf-8,hello", - Some(ContentType(Mime(TopLevel::Text, - SubLevel::Plain, - vec!((Attr::Charset, Value::Utf8))))), + Some(ContentType::from(mime::TEXT_PLAIN_UTF_8)), Some("utf-8"), Some(b"hello")); } @@ -112,10 +108,8 @@ fn plain_only_charset() { fn base64() { assert_parse( "data:;base64,C62+7w==", - Some(ContentType(Mime(TopLevel::Text, - SubLevel::Plain, - vec!((Attr::Charset, Value::Ext("US-ASCII".to_owned())))))), - Some("US-ASCII"), + Some(ContentType::from("text/plain; charset=US-ASCII".parse::<Mime>().unwrap())), + Some("us-ascii"), Some(&[0x0B, 0xAD, 0xBE, 0xEF])); } @@ -123,7 +117,7 @@ fn base64() { fn base64_ct() { assert_parse( "data:application/octet-stream;base64,C62+7w==", - Some(ContentType(Mime(TopLevel::Application, SubLevel::Ext("octet-stream".to_owned()), vec!()))), + Some(ContentType::from(mime::APPLICATION_OCTET_STREAM)), None, Some(&[0x0B, 0xAD, 0xBE, 0xEF])); } @@ -132,8 +126,7 @@ fn base64_ct() { fn base64_charset() { assert_parse( "data:text/plain;charset=koi8-r;base64,8PLl9+XkIO3l5Pfl5A==", - Some(ContentType(Mime(TopLevel::Text, SubLevel::Plain, - vec!((Attr::Charset, Value::Ext("koi8-r".to_owned())))))), + Some(ContentType::from("text/plain; charset=koi8-r".parse::<Mime>().unwrap())), Some("koi8-r"), Some(&[0xF0, 0xF2, 0xE5, 0xF7, 0xE5, 0xE4, 0x20, 0xED, 0xE5, 0xE4, 0xF7, 0xE5, 0xE4])); } diff --git a/components/net/tests/fetch.rs b/components/net/tests/fetch.rs index 2e33727e37f..d0d472614f9 100644 --- a/components/net/tests/fetch.rs +++ b/components/net/tests/fetch.rs @@ -2,27 +2,24 @@ * 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/. */ -use {DEFAULT_USER_AGENT, new_fetch_context, create_embedder_proxy, fetch, make_server}; +use {DEFAULT_USER_AGENT, new_fetch_context, create_embedder_proxy, fetch, make_server, make_ssl_server}; use devtools_traits::HttpRequest as DevtoolsHttpRequest; use devtools_traits::HttpResponse as DevtoolsHttpResponse; use fetch_with_context; use fetch_with_cors_cache; +use headers_core::HeaderMapExt; +use headers_ext::{AccessControlAllowCredentials, AccessControlAllowHeaders, AccessControlAllowOrigin}; +use headers_ext::{AccessControlAllowMethods, AccessControlMaxAge}; +use headers_ext::{CacheControl, ContentLength, ContentType, Expires, Host, LastModified, Pragma, UserAgent}; +use http::{Method, StatusCode}; +use http::header::{self, HeaderMap, HeaderName, HeaderValue}; +use http::uri::Authority; use http_loader::{expect_devtools_http_request, expect_devtools_http_response}; -use hyper::LanguageTag; -use hyper::header::{Accept, AccessControlAllowCredentials, AccessControlAllowHeaders, AccessControlAllowOrigin}; -use hyper::header::{AcceptEncoding, AcceptLanguage, AccessControlAllowMethods, AccessControlMaxAge}; -use hyper::header::{AccessControlRequestHeaders, AccessControlRequestMethod, Date, UserAgent}; -use hyper::header::{CacheControl, ContentLanguage, ContentLength, ContentType, Expires, LastModified}; -use hyper::header::{Encoding, Location, Pragma, Quality, QualityItem, SetCookie, qitem}; -use hyper::header::{Headers, Host, HttpDate, Referer as HyperReferer}; -use hyper::method::Method; -use hyper::mime::{Mime, SubLevel, TopLevel}; -use hyper::server::{Request as HyperRequest, Response as HyperResponse, Server}; -use hyper::status::StatusCode; -use hyper::uri::RequestUri; -use hyper_openssl; +use hyper::{Request as HyperRequest, Response as HyperResponse}; +use hyper::body::Body; +use mime::{self, Mime}; use msg::constellation_msg::TEST_PIPELINE_ID; -use net::connector::create_ssl_client; +use net::connector::create_ssl_connector_builder; use net::fetch::cors_cache::CorsCache; use net::fetch::methods::{CancellationListener, FetchContext}; use net::filemanager_thread::FileManager; @@ -37,21 +34,21 @@ use servo_channel::{channel, Sender}; use servo_url::{ImmutableOrigin, ServoUrl}; use std::fs::File; use std::io::Read; +use std::iter::FromIterator; use std::path::Path; use std::sync::{Arc, Mutex}; use std::sync::atomic::{AtomicUsize, Ordering}; -use time::{self, Duration}; -use unicase::UniCase; +use std::time::{SystemTime, Duration}; // TODO write a struct that impls Handler for storing test values #[test] fn test_fetch_response_is_not_network_error() { static MESSAGE: &'static [u8] = b""; - let handler = move |_: HyperRequest, response: HyperResponse| { - response.send(MESSAGE).unwrap(); + let handler = move |_: HyperRequest<Body>, response: &mut HyperResponse<Body>| { + *response.body_mut() = MESSAGE.to_vec().into(); }; - let (mut server, url) = make_server(handler); + let (server, url) = make_server(handler); let origin = Origin::Origin(url.origin()); let mut request = Request::new(url, Some(origin), None); @@ -79,10 +76,10 @@ fn test_fetch_on_bad_port_is_network_error() { #[test] fn test_fetch_response_body_matches_const_message() { static MESSAGE: &'static [u8] = b"Hello World!"; - let handler = move |_: HyperRequest, response: HyperResponse| { - response.send(MESSAGE).unwrap(); + let handler = move |_: HyperRequest<Body>, response: &mut HyperResponse<Body>| { + *response.body_mut() = MESSAGE.to_vec().into(); }; - let (mut server, url) = make_server(handler); + let (server, url) = make_server(handler); let origin = Origin::Origin(url.origin()); let mut request = Request::new(url, Some(origin), None); @@ -142,11 +139,11 @@ fn test_fetch_blob() { assert_eq!(fetch_response.headers.len(), 2); - let content_type: &ContentType = fetch_response.headers.get().unwrap(); - assert_eq!(**content_type, Mime(TopLevel::Text, SubLevel::Plain, vec![])); + let content_type: Mime = fetch_response.headers.typed_get::<ContentType>().unwrap().into(); + assert_eq!(content_type, mime::TEXT_PLAIN); - let content_length: &ContentLength = fetch_response.headers.get().unwrap(); - assert_eq!(**content_length, bytes.len() as u64); + let content_length: ContentLength = fetch_response.headers.typed_get().unwrap(); + assert_eq!(content_length.0, bytes.len() as u64); assert_eq!(*fetch_response.body.lock().unwrap(), ResponseBody::Done(bytes.to_vec())); @@ -162,8 +159,8 @@ fn test_fetch_file() { let fetch_response = fetch(&mut request, None); assert!(!fetch_response.is_network_error()); assert_eq!(fetch_response.headers.len(), 1); - let content_type: &ContentType = fetch_response.headers.get().unwrap(); - assert_eq!(**content_type, Mime(TopLevel::Text, SubLevel::Css, vec![])); + let content_type: Mime = fetch_response.headers.typed_get::<ContentType>().unwrap().into(); + assert_eq!(content_type, mime::TEXT_CSS); let resp_body = fetch_response.body.lock().unwrap(); let mut file = File::open(path).unwrap(); @@ -202,20 +199,20 @@ fn test_fetch_bogus_scheme() { fn test_cors_preflight_fetch() { static ACK: &'static [u8] = b"ACK"; let state = Arc::new(AtomicUsize::new(0)); - let handler = move |request: HyperRequest, mut response: HyperResponse| { - if request.method == Method::Options && state.clone().fetch_add(1, Ordering::SeqCst) == 0 { - assert!(request.headers.has::<AccessControlRequestMethod>()); - assert!(!request.headers.has::<AccessControlRequestHeaders>()); - assert!(!request.headers.get::<HyperReferer>().unwrap().contains("a.html")); - response.headers_mut().set(AccessControlAllowOrigin::Any); - response.headers_mut().set(AccessControlAllowCredentials); - response.headers_mut().set(AccessControlAllowMethods(vec![Method::Get])); + let handler = move |request: HyperRequest<Body>, response: &mut HyperResponse<Body>| { + if request.method() == Method::OPTIONS && state.clone().fetch_add(1, Ordering::SeqCst) == 0 { + assert!(request.headers().contains_key(header::ACCESS_CONTROL_REQUEST_METHOD)); + assert!(!request.headers().contains_key(header::ACCESS_CONTROL_REQUEST_HEADERS)); + assert!(!request.headers().get(header::REFERER).unwrap().to_str().unwrap().contains("a.html")); + response.headers_mut().typed_insert(AccessControlAllowOrigin::ANY); + response.headers_mut().typed_insert(AccessControlAllowCredentials); + response.headers_mut().typed_insert(AccessControlAllowMethods::from_iter(vec![Method::GET])); } else { - response.headers_mut().set(AccessControlAllowOrigin::Any); - response.send(ACK).unwrap(); + response.headers_mut().typed_insert(AccessControlAllowOrigin::ANY); + *response.body_mut() = ACK.to_vec().into(); } }; - let (mut server, url) = make_server(handler); + let (server, url) = make_server(handler); let target_url = url.clone().join("a.html").unwrap(); @@ -241,20 +238,20 @@ fn test_cors_preflight_cache_fetch() { let state = Arc::new(AtomicUsize::new(0)); let counter = state.clone(); let mut cache = CorsCache::new(); - let handler = move |request: HyperRequest, mut response: HyperResponse| { - if request.method == Method::Options && state.clone().fetch_add(1, Ordering::SeqCst) == 0 { - assert!(request.headers.has::<AccessControlRequestMethod>()); - assert!(!request.headers.has::<AccessControlRequestHeaders>()); - response.headers_mut().set(AccessControlAllowOrigin::Any); - response.headers_mut().set(AccessControlAllowCredentials); - response.headers_mut().set(AccessControlAllowMethods(vec![Method::Get])); - response.headers_mut().set(AccessControlMaxAge(6000)); + let handler = move |request: HyperRequest<Body>, response: &mut HyperResponse<Body>| { + if request.method() == Method::OPTIONS && state.clone().fetch_add(1, Ordering::SeqCst) == 0 { + assert!(request.headers().contains_key(header::ACCESS_CONTROL_REQUEST_METHOD)); + assert!(!request.headers().contains_key(header::ACCESS_CONTROL_REQUEST_HEADERS)); + response.headers_mut().typed_insert(AccessControlAllowOrigin::ANY); + response.headers_mut().typed_insert(AccessControlAllowCredentials); + response.headers_mut().typed_insert(AccessControlAllowMethods::from_iter(vec![Method::GET])); + response.headers_mut().typed_insert(AccessControlMaxAge::from(Duration::new(6000, 0))); } else { - response.headers_mut().set(AccessControlAllowOrigin::Any); - response.send(ACK).unwrap(); + response.headers_mut().typed_insert(AccessControlAllowOrigin::ANY); + *response.body_mut() = ACK.to_vec().into(); } }; - let (mut server, url) = make_server(handler); + let (server, url) = make_server(handler); let origin = Origin::Origin(ImmutableOrigin::new_opaque()); let mut request = Request::new(url.clone(), Some(origin.clone()), None); @@ -274,8 +271,8 @@ fn test_cors_preflight_cache_fetch() { assert_eq!(1, counter.load(Ordering::SeqCst)); // The entry exists in the CORS-preflight cache - assert_eq!(true, cache.match_method(&wrapped_request0, Method::Get)); - assert_eq!(true, cache.match_method(&wrapped_request1, Method::Get)); + assert_eq!(true, cache.match_method(&wrapped_request0, Method::GET)); + assert_eq!(true, cache.match_method(&wrapped_request1, Method::GET)); match *fetch_response0.body.lock().unwrap() { ResponseBody::Done(ref body) => assert_eq!(&**body, ACK), @@ -291,23 +288,23 @@ fn test_cors_preflight_cache_fetch() { fn test_cors_preflight_fetch_network_error() { static ACK: &'static [u8] = b"ACK"; let state = Arc::new(AtomicUsize::new(0)); - let handler = move |request: HyperRequest, mut response: HyperResponse| { - if request.method == Method::Options && state.clone().fetch_add(1, Ordering::SeqCst) == 0 { - assert!(request.headers.has::<AccessControlRequestMethod>()); - assert!(!request.headers.has::<AccessControlRequestHeaders>()); - response.headers_mut().set(AccessControlAllowOrigin::Any); - response.headers_mut().set(AccessControlAllowCredentials); - response.headers_mut().set(AccessControlAllowMethods(vec![Method::Get])); + let handler = move |request: HyperRequest<Body>, response: &mut HyperResponse<Body>| { + if request.method() == Method::OPTIONS && state.clone().fetch_add(1, Ordering::SeqCst) == 0 { + assert!(request.headers().contains_key(header::ACCESS_CONTROL_REQUEST_METHOD)); + assert!(!request.headers().contains_key(header::ACCESS_CONTROL_REQUEST_HEADERS)); + response.headers_mut().typed_insert(AccessControlAllowOrigin::ANY); + response.headers_mut().typed_insert(AccessControlAllowCredentials); + response.headers_mut().typed_insert(AccessControlAllowMethods::from_iter(vec![Method::GET])); } else { - response.headers_mut().set(AccessControlAllowOrigin::Any); - response.send(ACK).unwrap(); + response.headers_mut().typed_insert(AccessControlAllowOrigin::ANY); + *response.body_mut() = ACK.to_vec().into(); } }; - let (mut server, url) = make_server(handler); + let (server, url) = make_server(handler); let origin = Origin::Origin(ImmutableOrigin::new_opaque()); let mut request = Request::new(url, Some(origin), None); - request.method = Method::Extension("CHICKEN".to_owned()); + request.method = Method::from_bytes(b"CHICKEN").unwrap(); request.referrer = Referrer::NoReferrer; request.use_cors_preflight = true; request.mode = RequestMode::CorsMode; @@ -320,14 +317,17 @@ fn test_cors_preflight_fetch_network_error() { #[test] fn test_fetch_response_is_basic_filtered() { static MESSAGE: &'static [u8] = b""; - let handler = move |_: HyperRequest, mut response: HyperResponse| { - response.headers_mut().set(SetCookie(vec![])); + let handler = move |_: HyperRequest<Body>, response: &mut HyperResponse<Body>| { + response.headers_mut().insert(header::SET_COOKIE, HeaderValue::from_static("")); // this header is obsoleted, so hyper doesn't implement it, but it's still covered by the spec - response.headers_mut().set_raw("Set-Cookie2", vec![]); + response.headers_mut().insert( + HeaderName::from_static("set-cookie2"), + HeaderValue::from_bytes(&vec![]).unwrap() + ); - response.send(MESSAGE).unwrap(); + *response.body_mut() = MESSAGE.to_vec().into(); }; - let (mut server, url) = make_server(handler); + let (server, url) = make_server(handler); let origin = Origin::Origin(url.origin()); let mut request = Request::new(url, Some(origin), None); @@ -339,39 +339,42 @@ fn test_fetch_response_is_basic_filtered() { assert_eq!(fetch_response.response_type, ResponseType::Basic); let headers = fetch_response.headers; - assert!(!headers.has::<SetCookie>()); - assert!(headers.get_raw("Set-Cookie2").is_none()); + assert!(!headers.contains_key(header::SET_COOKIE)); + assert!(headers.get(HeaderName::from_static("set-cookie2")).is_none()); } #[test] fn test_fetch_response_is_cors_filtered() { static MESSAGE: &'static [u8] = b""; - let handler = move |_: HyperRequest, mut response: HyperResponse| { + let handler = move |_: HyperRequest<Body>, response: &mut HyperResponse<Body>| { // this is mandatory for the Cors Check to pass // TODO test using different url encodings with this value ie. punycode - response.headers_mut().set(AccessControlAllowOrigin::Any); + response.headers_mut().typed_insert(AccessControlAllowOrigin::ANY); // these are the headers that should be kept after filtering - response.headers_mut().set(CacheControl(vec![])); - response.headers_mut().set(ContentLanguage(vec![])); - response.headers_mut().set(ContentType::html()); - response.headers_mut().set(Expires(HttpDate(time::now() + Duration::days(1)))); - response.headers_mut().set(LastModified(HttpDate(time::now()))); - response.headers_mut().set(Pragma::NoCache); + response.headers_mut().typed_insert(CacheControl::new()); + response.headers_mut().insert(header::CONTENT_LANGUAGE, HeaderValue::from_bytes(&vec![]).unwrap()); + response.headers_mut().typed_insert(ContentType::from(mime::TEXT_HTML)); + response.headers_mut().typed_insert(Expires::from(SystemTime::now() + Duration::new(86400, 0))); + response.headers_mut().typed_insert(LastModified::from(SystemTime::now())); + response.headers_mut().typed_insert(Pragma::no_cache()); // these headers should not be kept after filtering, even though they are given a pass - response.headers_mut().set(SetCookie(vec![])); - response.headers_mut().set_raw("Set-Cookie2", vec![]); - response.headers_mut().set( - AccessControlAllowHeaders(vec![ - UniCase("set-cookie".to_owned()), - UniCase("set-cookie2".to_owned()) + response.headers_mut().insert(header::SET_COOKIE, HeaderValue::from_static("")); + response.headers_mut().insert( + HeaderName::from_static("set-cookie2"), + HeaderValue::from_bytes(&vec![]).unwrap() + ); + response.headers_mut().typed_insert( + AccessControlAllowHeaders::from_iter(vec![ + HeaderName::from_static("set-cookie"), + HeaderName::from_static("set-cookie2") ]) ); - response.send(MESSAGE).unwrap(); + *response.body_mut() = MESSAGE.to_vec().into(); }; - let (mut server, url) = make_server(handler); + let (server, url) = make_server(handler); // an origin mis-match will stop it from defaulting to a basic filtered response let origin = Origin::Origin(ImmutableOrigin::new_opaque()); @@ -385,25 +388,25 @@ fn test_fetch_response_is_cors_filtered() { assert_eq!(fetch_response.response_type, ResponseType::Cors); let headers = fetch_response.headers; - assert!(headers.has::<CacheControl>()); - assert!(headers.has::<ContentLanguage>()); - assert!(headers.has::<ContentType>()); - assert!(headers.has::<Expires>()); - assert!(headers.has::<LastModified>()); - assert!(headers.has::<Pragma>()); - - assert!(!headers.has::<AccessControlAllowOrigin>()); - assert!(!headers.has::<SetCookie>()); - assert!(headers.get_raw("Set-Cookie2").is_none()); + assert!(headers.contains_key(header::CACHE_CONTROL)); + assert!(headers.contains_key(header::CONTENT_LANGUAGE)); + assert!(headers.contains_key(header::CONTENT_TYPE)); + assert!(headers.contains_key(header::EXPIRES)); + assert!(headers.contains_key(header::LAST_MODIFIED)); + assert!(headers.contains_key(header::PRAGMA)); + + assert!(!headers.contains_key(header::ACCESS_CONTROL_ALLOW_ORIGIN)); + assert!(!headers.contains_key(header::SET_COOKIE)); + assert!(headers.get(HeaderName::from_static("set-cookie2")).is_none()); } #[test] fn test_fetch_response_is_opaque_filtered() { static MESSAGE: &'static [u8] = b""; - let handler = move |_: HyperRequest, response: HyperResponse| { - response.send(MESSAGE).unwrap(); + let handler = move |_: HyperRequest<Body>, response: &mut HyperResponse<Body>| { + *response.body_mut() = MESSAGE.to_vec().into(); }; - let (mut server, url) = make_server(handler); + let (server, url) = make_server(handler); // an origin mis-match will fall through to an Opaque filtered response let origin = Origin::Origin(ImmutableOrigin::new_opaque()); @@ -419,7 +422,7 @@ fn test_fetch_response_is_opaque_filtered() { assert!(fetch_response.url_list.is_empty()); // this also asserts that status message is "the empty byte sequence" assert!(fetch_response.status.is_none()); - assert_eq!(fetch_response.headers, Headers::new()); + assert_eq!(fetch_response.headers, HeaderMap::new()); match *fetch_response.body.lock().unwrap() { ResponseBody::Empty => { }, _ => panic!() @@ -433,25 +436,18 @@ fn test_fetch_response_is_opaque_filtered() { #[test] fn test_fetch_response_is_opaque_redirect_filtered() { static MESSAGE: &'static [u8] = b""; - let handler = move |request: HyperRequest, mut response: HyperResponse| { - let redirects = match request.uri { - RequestUri::AbsolutePath(url) => - url.split("/").collect::<String>().parse::<u32>().unwrap_or(0), - RequestUri::AbsoluteUri(url) => - url.path_segments().unwrap().next_back().unwrap().parse::<u32>().unwrap_or(0), - _ => panic!() - }; + let handler = move |request: HyperRequest<Body>, response: &mut HyperResponse<Body>| { + let redirects = request.uri().path().split("/").collect::<String>().parse::<u32>().unwrap_or(0); if redirects == 1 { - response.send(MESSAGE).unwrap(); + *response.body_mut() = MESSAGE.to_vec().into(); } else { - *response.status_mut() = StatusCode::Found; - let url = format!("{}", 1); - response.headers_mut().set(Location(url.to_owned())); + *response.status_mut() = StatusCode::FOUND; + response.headers_mut().insert(header::LOCATION, HeaderValue::from_static("1")); } }; - let (mut server, url) = make_server(handler); + let (server, url) = make_server(handler); let origin = Origin::Origin(url.origin()); let mut request = Request::new(url, Some(origin), None); @@ -465,7 +461,7 @@ fn test_fetch_response_is_opaque_redirect_filtered() { // this also asserts that status message is "the empty byte sequence" assert!(fetch_response.status.is_none()); - assert_eq!(fetch_response.headers, Headers::new()); + assert_eq!(fetch_response.headers, HeaderMap::new()); match *fetch_response.body.lock().unwrap() { ResponseBody::Empty => { }, _ => panic!() @@ -481,10 +477,10 @@ fn test_fetch_with_local_urls_only() { // If flag `local_urls_only` is set, fetching a non-local URL must result in network error. static MESSAGE: &'static [u8] = b""; - let handler = move |_: HyperRequest, response: HyperResponse| { - response.send(MESSAGE).unwrap(); + let handler = move |_: HyperRequest<Body>, response: &mut HyperResponse<Body>| { + *response.body_mut() = MESSAGE.to_vec().into(); }; - let (mut server, server_url) = make_server(handler); + let (server, server_url) = make_server(handler); let do_fetch = |url: ServoUrl| { let origin = Origin::Origin(url.origin()); @@ -506,7 +502,6 @@ fn test_fetch_with_local_urls_only() { assert!(!local_response.is_network_error()); assert!(server_response.is_network_error()); } - // NOTE(emilio): If this test starts failing: // // openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \ @@ -517,22 +512,17 @@ fn test_fetch_with_local_urls_only() { #[test] fn test_fetch_with_hsts() { static MESSAGE: &'static [u8] = b""; - let handler = move |_: HyperRequest, response: HyperResponse| { - response.send(MESSAGE).unwrap(); + let handler = move |_: HyperRequest<Body>, response: &mut HyperResponse<Body>| { + *response.body_mut() = MESSAGE.to_vec().into(); }; let cert_path = Path::new("../../resources/self_signed_certificate_for_testing.crt").canonicalize().unwrap(); let key_path = Path::new("../../resources/privatekey_for_testing.key").canonicalize().unwrap(); - - let ssl = hyper_openssl::OpensslServer::from_files(key_path, cert_path.clone()) - .unwrap(); - - //takes an address and something that implements hyper::net::Ssl - let mut server = Server::https("0.0.0.0:0", ssl).unwrap().handle_threads(handler, 1).unwrap(); + let (server, url) = make_ssl_server(handler, cert_path.clone(), key_path.clone()); let mut ca_content = String::new(); File::open(cert_path).unwrap().read_to_string(&mut ca_content).unwrap(); - let ssl_client = create_ssl_client(&ca_content); + let ssl_client = create_ssl_connector_builder(&ca_content); let context = FetchContext { state: Arc::new(HttpState::new(ssl_client)), @@ -547,15 +537,13 @@ fn test_fetch_with_hsts() { list.push(HstsEntry::new("localhost".to_owned(), IncludeSubdomains::NotIncluded, None) .unwrap()); } - let url_string = format!("http://localhost:{}", server.socket.port()); - let url = ServoUrl::parse(&url_string).unwrap(); let origin = Origin::Origin(url.origin()); let mut request = Request::new(url, Some(origin), None); request.referrer = Referrer::NoReferrer; // Set the flag. request.local_urls_only = false; let response = fetch_with_context(&mut request, &context); - let _ = server.close(); + server.close(); assert_eq!(response.internal_response.unwrap().url().unwrap().scheme(), "https"); } @@ -563,10 +551,10 @@ fn test_fetch_with_hsts() { #[test] fn test_fetch_with_sri_network_error() { static MESSAGE: &'static [u8] = b"alert('Hello, Network Error');"; - let handler = move |_: HyperRequest, response: HyperResponse| { - response.send(MESSAGE).unwrap(); + let handler = move |_: HyperRequest<Body>, response: &mut HyperResponse<Body>| { + *response.body_mut() = MESSAGE.to_vec().into(); }; - let (mut server, url) = make_server(handler); + let (server, url) = make_server(handler); let origin = Origin::Origin(url.origin()); let mut request = Request::new(url, Some(origin), None); @@ -587,10 +575,10 @@ fn test_fetch_with_sri_network_error() { #[test] fn test_fetch_with_sri_sucess() { static MESSAGE: &'static [u8] = b"alert('Hello, world.');"; - let handler = move |_: HyperRequest, response: HyperResponse| { - response.send(MESSAGE).unwrap(); + let handler = move |_: HyperRequest<Body>, response: &mut HyperResponse<Body>| { + *response.body_mut() = MESSAGE.to_vec().into(); }; - let (mut server, url) = make_server(handler); + let (server, url) = make_server(handler); let origin = Origin::Origin(url.origin()); let mut request = Request::new(url, Some(origin), None); @@ -616,20 +604,20 @@ fn test_fetch_blocked_nosniff() { mime: Mime, should_error: bool) { const MESSAGE: &'static [u8] = b""; - const HEADER: &'static str = "X-Content-Type-Options"; + const HEADER: &'static str = "x-content-type-options"; const VALUE: &'static [u8] = b"nosniff"; - let handler = move |_: HyperRequest, mut response: HyperResponse| { - let mime_header = ContentType(mime.clone()); - response.headers_mut().set(mime_header); - assert!(response.headers().has::<ContentType>()); + let handler = move |_: HyperRequest<Body>, response: &mut HyperResponse<Body>| { + let mime_header = ContentType::from(mime.clone()); + response.headers_mut().typed_insert(mime_header); + assert!(response.headers().contains_key(header::CONTENT_TYPE)); // Add the nosniff header - response.headers_mut().set_raw(HEADER, vec![VALUE.to_vec()]); + response.headers_mut().insert(HeaderName::from_static(HEADER), HeaderValue::from_bytes(VALUE).unwrap()); - response.send(MESSAGE).unwrap(); + *response.body_mut() = MESSAGE.to_vec().into(); }; - let (mut server, url) = make_server(handler); + let (server, url) = make_server(handler); let origin = Origin::Origin(url.origin()); let mut request = Request::new(url, Some(origin), None); @@ -641,9 +629,9 @@ fn test_fetch_blocked_nosniff() { } let tests = vec![ - (Destination::Script, Mime(TopLevel::Text, SubLevel::Javascript, vec![]), false), - (Destination::Script, Mime(TopLevel::Text, SubLevel::Css, vec![]), true), - (Destination::Style, Mime(TopLevel::Text, SubLevel::Css, vec![]), false), + (Destination::Script, mime::TEXT_JAVASCRIPT, false), + (Destination::Script, mime::TEXT_CSS, true), + (Destination::Style, mime::TEXT_CSS, false), ]; for test in tests { @@ -653,25 +641,19 @@ fn test_fetch_blocked_nosniff() { } fn setup_server_and_fetch(message: &'static [u8], redirect_cap: u32) -> Response { - let handler = move |request: HyperRequest, mut response: HyperResponse| { - let redirects = match request.uri { - RequestUri::AbsolutePath(url) => - url.split("/").collect::<String>().parse::<u32>().unwrap_or(0), - RequestUri::AbsoluteUri(url) => - url.path_segments().unwrap().next_back().unwrap().parse::<u32>().unwrap_or(0), - _ => panic!() - }; + let handler = move |request: HyperRequest<Body>, response: &mut HyperResponse<Body>| { + let redirects = request.uri().path().split("/").collect::<String>().parse::<u32>().unwrap_or(0); if redirects >= redirect_cap { - response.send(message).unwrap(); + *response.body_mut() = message.to_vec().into(); } else { - *response.status_mut() = StatusCode::Found; + *response.status_mut() = StatusCode::FOUND; let url = format!("{redirects}", redirects = redirects + 1); - response.headers_mut().set(Location(url.to_owned())); + response.headers_mut().insert(header::LOCATION, HeaderValue::from_str(&url).unwrap()); } }; - let (mut server, url) = make_server(handler); + let (server, url) = make_server(handler); let origin = Origin::Origin(url.origin()); let mut request = Request::new(url, Some(origin), None); @@ -720,30 +702,24 @@ fn test_fetch_redirect_updates_method_runner(tx: Sender<bool>, status_code: Stat let handler_method = method.clone(); let handler_tx = Arc::new(Mutex::new(tx)); - let handler = move |request: HyperRequest, mut response: HyperResponse| { - let redirects = match request.uri { - RequestUri::AbsolutePath(url) => - url.split("/").collect::<String>().parse::<u32>().unwrap_or(0), - RequestUri::AbsoluteUri(url) => - url.path_segments().unwrap().next_back().unwrap().parse::<u32>().unwrap_or(0), - _ => panic!() - }; + let handler = move |request: HyperRequest<Body>, response: &mut HyperResponse<Body>| { + let redirects = request.uri().path().split("/").collect::<String>().parse::<u32>().unwrap_or(0); let mut test_pass = true; if redirects == 0 { - *response.status_mut() = StatusCode::TemporaryRedirect; - response.headers_mut().set(Location("1".to_owned())); + *response.status_mut() = StatusCode::TEMPORARY_REDIRECT; + response.headers_mut().insert(header::LOCATION, HeaderValue::from_static("1")); } else if redirects == 1 { // this makes sure that the request method does't change from the wrong status code - if handler_method != Method::Get && request.method == Method::Get { + if handler_method != Method::GET && request.method() == Method::GET { test_pass = false; } *response.status_mut() = status_code; - response.headers_mut().set(Location("2".to_owned())); + response.headers_mut().insert(header::LOCATION, HeaderValue::from_static("2")); - } else if request.method != Method::Get { + } else if request.method() != Method::GET { test_pass = false; } @@ -754,7 +730,7 @@ fn test_fetch_redirect_updates_method_runner(tx: Sender<bool>, status_code: Stat }; - let (mut server, url) = make_server(handler); + let (server, url) = make_server(handler); let origin = Origin::Origin(url.origin()); let mut request = Request::new(url, Some(origin), None); @@ -769,36 +745,36 @@ fn test_fetch_redirect_updates_method_runner(tx: Sender<bool>, status_code: Stat fn test_fetch_redirect_updates_method() { let (tx, rx) = channel(); - test_fetch_redirect_updates_method_runner(tx.clone(), StatusCode::MovedPermanently, Method::Post); + test_fetch_redirect_updates_method_runner(tx.clone(), StatusCode::MOVED_PERMANENTLY, Method::POST); assert_eq!(rx.recv().unwrap(), true); assert_eq!(rx.recv().unwrap(), true); // make sure the test doesn't send more data than expected assert_eq!(rx.try_recv().is_none(), true); - test_fetch_redirect_updates_method_runner(tx.clone(), StatusCode::Found, Method::Post); + test_fetch_redirect_updates_method_runner(tx.clone(), StatusCode::FOUND, Method::POST); assert_eq!(rx.recv().unwrap(), true); assert_eq!(rx.recv().unwrap(), true); assert_eq!(rx.try_recv().is_none(), true); - test_fetch_redirect_updates_method_runner(tx.clone(), StatusCode::SeeOther, Method::Get); + test_fetch_redirect_updates_method_runner(tx.clone(), StatusCode::SEE_OTHER, Method::GET); assert_eq!(rx.recv().unwrap(), true); assert_eq!(rx.recv().unwrap(), true); assert_eq!(rx.try_recv().is_none(), true); - let extension = Method::Extension("FOO".to_owned()); + let extension = Method::from_bytes(b"FOO").unwrap(); - test_fetch_redirect_updates_method_runner(tx.clone(), StatusCode::MovedPermanently, extension.clone()); + test_fetch_redirect_updates_method_runner(tx.clone(), StatusCode::MOVED_PERMANENTLY, extension.clone()); assert_eq!(rx.recv().unwrap(), true); // for MovedPermanently and Found, Method should only be changed if it was Post assert_eq!(rx.recv().unwrap(), false); assert_eq!(rx.try_recv().is_none(), true); - test_fetch_redirect_updates_method_runner(tx.clone(), StatusCode::Found, extension.clone()); + test_fetch_redirect_updates_method_runner(tx.clone(), StatusCode::FOUND, extension.clone()); assert_eq!(rx.recv().unwrap(), true); assert_eq!(rx.recv().unwrap(), false); assert_eq!(rx.try_recv().is_none(), true); - test_fetch_redirect_updates_method_runner(tx.clone(), StatusCode::SeeOther, extension.clone()); + test_fetch_redirect_updates_method_runner(tx.clone(), StatusCode::SEE_OTHER, extension.clone()); assert_eq!(rx.recv().unwrap(), true); // for SeeOther, Method should always be changed, so this should be true assert_eq!(rx.recv().unwrap(), true); @@ -826,10 +802,10 @@ fn response_is_done(response: &Response) -> bool { #[test] fn test_fetch_async_returns_complete_response() { static MESSAGE: &'static [u8] = b"this message should be retrieved in full"; - let handler = move |_: HyperRequest, response: HyperResponse| { - response.send(MESSAGE).unwrap(); + let handler = move |_: HyperRequest<Body>, response: &mut HyperResponse<Body>| { + *response.body_mut() = MESSAGE.to_vec().into(); }; - let (mut server, url) = make_server(handler); + let (server, url) = make_server(handler); let origin = Origin::Origin(url.origin()); let mut request = Request::new(url, Some(origin), None); @@ -844,10 +820,10 @@ fn test_fetch_async_returns_complete_response() { #[test] fn test_opaque_filtered_fetch_async_returns_complete_response() { static MESSAGE: &'static [u8] = b""; - let handler = move |_: HyperRequest, response: HyperResponse| { - response.send(MESSAGE).unwrap(); + let handler = move |_: HyperRequest<Body>, response: &mut HyperResponse<Body>| { + *response.body_mut() = MESSAGE.to_vec().into(); }; - let (mut server, url) = make_server(handler); + let (server, url) = make_server(handler); // an origin mis-match will fall through to an Opaque filtered response let origin = Origin::Origin(ImmutableOrigin::new_opaque()); @@ -865,25 +841,18 @@ fn test_opaque_filtered_fetch_async_returns_complete_response() { #[test] fn test_opaque_redirect_filtered_fetch_async_returns_complete_response() { static MESSAGE: &'static [u8] = b""; - let handler = move |request: HyperRequest, mut response: HyperResponse| { - let redirects = match request.uri { - RequestUri::AbsolutePath(url) => - url.split("/").collect::<String>().parse::<u32>().unwrap_or(0), - RequestUri::AbsoluteUri(url) => - url.path_segments().unwrap().last().unwrap().parse::<u32>().unwrap_or(0), - _ => panic!() - }; + let handler = move |request: HyperRequest<Body>, response: &mut HyperResponse<Body>| { + let redirects = request.uri().path().split("/").collect::<String>().parse::<u32>().unwrap_or(0); if redirects == 1 { - response.send(MESSAGE).unwrap(); + *response.body_mut() = MESSAGE.to_vec().into(); } else { - *response.status_mut() = StatusCode::Found; - let url = format!("{}", 1); - response.headers_mut().set(Location(url.to_owned())); + *response.status_mut() = StatusCode::FOUND; + response.headers_mut().insert(header::LOCATION, HeaderValue::from_static("1")); } }; - let (mut server, url) = make_server(handler); + let (server, url) = make_server(handler); let origin = Origin::Origin(url.origin()); let mut request = Request::new(url, Some(origin), None); @@ -901,11 +870,11 @@ fn test_opaque_redirect_filtered_fetch_async_returns_complete_response() { #[test] fn test_fetch_with_devtools() { static MESSAGE: &'static [u8] = b"Yay!"; - let handler = move |_: HyperRequest, response: HyperResponse| { - response.send(MESSAGE).unwrap(); + let handler = move |_: HyperRequest<Body>, response: &mut HyperResponse<Body>| { + *response.body_mut() = MESSAGE.to_vec().into(); }; - let (mut server, url) = make_server(handler); + let (server, url) = make_server(handler); let origin = Origin::Origin(url.origin()); let mut request = Request::new(url.clone(), Some(origin), Some(TEST_PIPELINE_ID)); @@ -921,36 +890,23 @@ fn test_fetch_with_devtools() { let mut devhttpresponse = expect_devtools_http_response(&devtools_port); //Creating default headers for request - let mut headers = Headers::new(); - - headers.set(AcceptEncoding(vec![ - qitem(Encoding::Gzip), - qitem(Encoding::Deflate), - qitem(Encoding::EncodingExt("br".to_owned())) - ])); + let mut headers = HeaderMap::new(); - headers.set(Host { hostname: url.host_str().unwrap().to_owned() , port: url.port().to_owned() }); + headers.insert(header::ACCEPT_ENCODING, HeaderValue::from_static("gzip, deflate, br")); + headers.typed_insert( + Host::from(format!("{}:{}", url.host_str().unwrap(), url.port().unwrap()).parse::<Authority>().unwrap())); - let accept = Accept(vec![qitem(Mime(TopLevel::Star, SubLevel::Star, vec![]))]); - headers.set(accept); + headers.insert(header::ACCEPT, HeaderValue::from_static("*/*")); - let mut en_us: LanguageTag = Default::default(); - en_us.language = Some("en".to_owned()); - en_us.region = Some("US".to_owned()); - let mut en: LanguageTag = Default::default(); - en.language = Some("en".to_owned()); - headers.set(AcceptLanguage(vec![ - qitem(en_us), - QualityItem::new(en, Quality(500)), - ])); + headers.insert(header::ACCEPT_LANGUAGE, HeaderValue::from_static("en-US, en; q=0.5")); - headers.set(UserAgent(DEFAULT_USER_AGENT.to_owned())); + headers.typed_insert::<UserAgent>(DEFAULT_USER_AGENT.parse().unwrap()); let httprequest = DevtoolsHttpRequest { url: url, - method: Method::Get, + method: Method::GET, headers: headers, - body: None, + body: Some(vec![]), pipeline_id: TEST_PIPELINE_ID, startedDateTime: devhttprequest.startedDateTime, timeStamp: devhttprequest.timeStamp, @@ -960,9 +916,9 @@ fn test_fetch_with_devtools() { }; let content = "Yay!"; - let mut response_headers = Headers::new(); - response_headers.set(ContentLength(content.len() as u64)); - devhttpresponse.headers.as_mut().unwrap().remove::<Date>(); + let mut response_headers = HeaderMap::new(); + response_headers.typed_insert(ContentLength(content.len() as u64)); + devhttpresponse.headers.as_mut().unwrap().remove(header::DATE); let httpresponse = DevtoolsHttpResponse { headers: Some(response_headers), diff --git a/components/net/tests/http_loader.rs b/components/net/tests/http_loader.rs index 96c424469d1..4d327029ccd 100644 --- a/components/net/tests/http_loader.rs +++ b/components/net/tests/http_loader.rs @@ -10,16 +10,15 @@ use fetch; use fetch_with_context; use flate2::Compression; use flate2::write::{DeflateEncoder, GzEncoder}; -use hyper::LanguageTag; -use hyper::header::{Accept, AcceptEncoding, ContentEncoding, ContentLength, Cookie as CookieHeader}; -use hyper::header::{AcceptLanguage, AccessControlAllowOrigin, Authorization, Basic, Date}; -use hyper::header::{Encoding, Headers, Host, Location, Origin, Quality, QualityItem, SetCookie, qitem}; -use hyper::header::{StrictTransportSecurity, UserAgent}; -use hyper::method::Method; -use hyper::mime::{Mime, SubLevel, TopLevel}; -use hyper::server::{Request as HyperRequest, Response as HyperResponse}; -use hyper::status::StatusCode; -use hyper::uri::RequestUri; +use futures::{self, Future, Stream}; +use headers_core::HeaderMapExt; +use headers_ext::{Authorization, Basic, AccessControlAllowOrigin, ContentLength, Date, Host, Origin}; +use headers_ext::{StrictTransportSecurity, UserAgent}; +use http::{Method, StatusCode}; +use http::header::{self, HeaderMap, HeaderValue}; +use http::uri::Authority; +use hyper::{Request as HyperRequest, Response as HyperResponse}; +use hyper::body::Body; use make_server; use msg::constellation_msg::TEST_PIPELINE_ID; use net::cookie::Cookie; @@ -33,25 +32,21 @@ use new_fetch_context; use servo_channel::{channel, Receiver}; use servo_url::{ServoUrl, ImmutableOrigin}; use std::collections::HashMap; -use std::io::{Read, Write}; -use std::str::FromStr; +use std::io::Write; +use std::str; use std::sync::{Arc, Mutex, RwLock}; use std::sync::atomic::{AtomicBool, Ordering}; +use std::time::Duration; fn mock_origin() -> ImmutableOrigin { ServoUrl::parse("http://servo.org").unwrap().origin() } -fn read_response(reader: &mut Read) -> String { - let mut buf = vec![0; 1024]; - match reader.read(&mut buf) { - Ok(len) if len > 0 => { - unsafe { buf.set_len(len); } - String::from_utf8(buf).unwrap() - }, - Ok(_) => "".to_owned(), - Err(e) => panic!("problem reading response {}", e) - } +fn read_response(req: HyperRequest<Body>) -> impl Future<Item=String, Error=()> { + req.into_body() + .concat2() + .and_then(|body| futures::future::ok(str::from_utf8(&body).unwrap().to_owned())) + .map_err(|_| ()) } fn assert_cookie_for_domain(cookie_jar: &RwLock<CookieStorage>, domain: &str, cookie: Option<&str>) { @@ -97,90 +92,71 @@ pub fn expect_devtools_http_response(devtools_port: &Receiver<DevtoolsControlMsg fn test_check_default_headers_loaded_in_every_request() { let expected_headers = Arc::new(Mutex::new(None)); let expected_headers_clone = expected_headers.clone(); - let handler = move |request: HyperRequest, _: HyperResponse| { - assert_eq!(request.headers, expected_headers_clone.lock().unwrap().take().unwrap()); + let handler = move |request: HyperRequest<Body>, _: &mut HyperResponse<Body>| { + assert_eq!(request.headers().clone(), expected_headers_clone.lock().unwrap().take().unwrap()); }; - let (mut server, url) = make_server(handler); + let (server, url) = make_server(handler); - let mut headers = Headers::new(); + let mut headers = HeaderMap::new(); - headers.set(AcceptEncoding(vec![qitem(Encoding::Gzip), - qitem(Encoding::Deflate), - qitem(Encoding::EncodingExt("br".to_owned()))])); - - let hostname = match url.host_str() { - Some(hostname) => hostname.to_owned(), - _ => panic!() - }; + headers.insert(header::ACCEPT_ENCODING, HeaderValue::from_static("gzip, deflate, br")); - headers.set(Host { hostname: hostname, port: url.port() }); + headers.typed_insert( + Host::from(format!("{}:{}", url.host_str().unwrap(), url.port().unwrap()).parse::<Authority>().unwrap())); - let accept = Accept(vec![ - qitem(Mime(TopLevel::Text, SubLevel::Html, vec![])), - qitem(Mime(TopLevel::Application, SubLevel::Ext("xhtml+xml".to_owned()), vec![])), - QualityItem::new(Mime(TopLevel::Application, SubLevel::Xml, vec![]), Quality(900u16)), - QualityItem::new(Mime(TopLevel::Star, SubLevel::Star, vec![]), Quality(800u16)), - ]); - headers.set(accept); + headers.insert(header::ACCEPT, + HeaderValue::from_static("text/html, application/xhtml+xml, application/xml; q=0.9, */*; q=0.8")); - let mut en_us: LanguageTag = Default::default(); - en_us.language = Some("en".to_owned()); - en_us.region = Some("US".to_owned()); - let mut en: LanguageTag = Default::default(); - en.language = Some("en".to_owned()); - headers.set(AcceptLanguage(vec![ - qitem(en_us), - QualityItem::new(en, Quality(500)), - ])); + headers.insert(header::ACCEPT_LANGUAGE, HeaderValue::from_static("en-US, en; q=0.5")); - headers.set(UserAgent(::DEFAULT_USER_AGENT.to_owned())); + headers.typed_insert::<UserAgent>(::DEFAULT_USER_AGENT.parse().unwrap()); *expected_headers.lock().unwrap() = Some(headers.clone()); // Testing for method.GET let mut request = Request::from_init(RequestInit { url: url.clone(), - method: Method::Get, + method: Method::GET, destination: Destination::Document, origin: url.clone().origin(), pipeline_id: Some(TEST_PIPELINE_ID), .. RequestInit::default() }); let response = fetch(&mut request, None); - assert!(response.internal_response.unwrap().status.unwrap().is_success()); + assert!(response.internal_response.unwrap().status.unwrap().0.is_success()); // Testing for method.POST let mut post_headers = headers.clone(); - post_headers.set(ContentLength(0 as u64)); + post_headers.typed_insert(ContentLength(0 as u64)); let url_str = url.as_str(); // request gets header "Origin: http://example.com" but expected_headers has // "Origin: http://example.com/" which do not match for equality so strip trailing '/' - post_headers.set(Origin::from_str(&url_str[..url_str.len()-1]).unwrap()); + post_headers.insert(header::ORIGIN, HeaderValue::from_str(&url_str[..url_str.len()-1]).unwrap()); *expected_headers.lock().unwrap() = Some(post_headers); let mut request = Request::from_init(RequestInit { url: url.clone(), - method: Method::Post, + method: Method::POST, destination: Destination::Document, origin: url.clone().origin(), pipeline_id: Some(TEST_PIPELINE_ID), .. RequestInit::default() }); let response = fetch(&mut request, None); - assert!(response.internal_response.unwrap().status.unwrap().is_success()); + assert!(response.internal_response.unwrap().status.unwrap().0.is_success()); let _ = server.close(); } #[test] fn test_load_when_request_is_not_get_or_head_and_there_is_no_body_content_length_should_be_set_to_0() { - let handler = move |request: HyperRequest, _: HyperResponse| { - assert_eq!(request.headers.get::<ContentLength>(), Some(&ContentLength(0))); + let handler = move |request: HyperRequest<Body>, _: &mut HyperResponse<Body>| { + assert_eq!(request.headers().typed_get::<ContentLength>(), Some(ContentLength(0))); }; - let (mut server, url) = make_server(handler); + let (server, url) = make_server(handler); let mut request = Request::from_init(RequestInit { url: url.clone(), - method: Method::Post, + method: Method::POST, body: None, destination: Destination::Document, origin: mock_origin(), @@ -188,24 +164,24 @@ fn test_load_when_request_is_not_get_or_head_and_there_is_no_body_content_length .. RequestInit::default() }); let response = fetch(&mut request, None); - assert!(response.internal_response.unwrap().status.unwrap().is_success()); + assert!(response.internal_response.unwrap().status.unwrap().0.is_success()); let _ = server.close(); } #[test] fn test_request_and_response_data_with_network_messages() { - let handler = move |_: HyperRequest, mut response: HyperResponse| { - response.headers_mut().set(Host { hostname: "foo.bar".to_owned(), port: None }); - response.send(b"Yay!").unwrap(); + let handler = move |_: HyperRequest<Body>, response: &mut HyperResponse<Body>| { + response.headers_mut().typed_insert(Host::from("foo.bar".parse::<Authority>().unwrap())); + *response.body_mut() = b"Yay!".to_vec().into(); }; - let (mut server, url) = make_server(handler); + let (server, url) = make_server(handler); - let mut request_headers = Headers::new(); - request_headers.set(Host { hostname: "bar.foo".to_owned(), port: None }); + let mut request_headers = HeaderMap::new(); + request_headers.typed_insert(Host::from("bar.foo".parse::<Authority>().unwrap())); let mut request = Request::from_init(RequestInit { url: url.clone(), - method: Method::Get, + method: Method::GET, headers: request_headers, body: None, destination: Destination::Document, @@ -215,7 +191,7 @@ fn test_request_and_response_data_with_network_messages() { }); let (devtools_chan, devtools_port) = channel(); let response = fetch(&mut request, Some(devtools_chan)); - assert!(response.internal_response.unwrap().status.unwrap().is_success()); + assert!(response.internal_response.unwrap().status.unwrap().0.is_success()); let _ = server.close(); @@ -224,41 +200,23 @@ fn test_request_and_response_data_with_network_messages() { let devhttpresponse = expect_devtools_http_response(&devtools_port); //Creating default headers for request - let mut headers = Headers::new(); - - headers.set(AcceptEncoding(vec![ - qitem(Encoding::Gzip), - qitem(Encoding::Deflate), - qitem(Encoding::EncodingExt("br".to_owned())) - ])); - - headers.set(Host { hostname: url.host_str().unwrap().to_owned() , port: url.port() }); - - let accept = Accept(vec![ - qitem(Mime(TopLevel::Text, SubLevel::Html, vec![])), - qitem(Mime(TopLevel::Application, SubLevel::Ext("xhtml+xml".to_owned()), vec![])), - QualityItem::new(Mime(TopLevel::Application, SubLevel::Xml, vec![]), Quality(900u16)), - QualityItem::new(Mime(TopLevel::Star, SubLevel::Star, vec![]), Quality(800u16)), - ]); - headers.set(accept); - - let mut en_us: LanguageTag = Default::default(); - en_us.language = Some("en".to_owned()); - en_us.region = Some("US".to_owned()); - let mut en: LanguageTag = Default::default(); - en.language = Some("en".to_owned()); - headers.set(AcceptLanguage(vec![ - qitem(en_us), - QualityItem::new(en, Quality(500)), - ])); - - headers.set(UserAgent(::DEFAULT_USER_AGENT.to_owned())); + let mut headers = HeaderMap::new(); + + headers.insert(header::ACCEPT_ENCODING, HeaderValue::from_static("gzip, deflate, br")); + headers.typed_insert( + Host::from(format!("{}:{}", url.host_str().unwrap(), url.port().unwrap()).parse::<Authority>().unwrap())); + + headers.insert(header::ACCEPT, + HeaderValue::from_static("text/html, application/xhtml+xml, application/xml; q=0.9, */*; q=0.8")); + + headers.insert(header::ACCEPT_LANGUAGE, HeaderValue::from_static("en-US, en; q=0.5")); + headers.typed_insert::<UserAgent>(::DEFAULT_USER_AGENT.parse().unwrap()); let httprequest = DevtoolsHttpRequest { url: url, - method: Method::Get, + method: Method::GET, headers: headers, - body: None, + body: Some(b"".to_vec()), pipeline_id: TEST_PIPELINE_ID, startedDateTime: devhttprequest.startedDateTime, timeStamp: devhttprequest.timeStamp, @@ -268,10 +226,10 @@ fn test_request_and_response_data_with_network_messages() { }; let content = "Yay!"; - let mut response_headers = Headers::new(); - response_headers.set(ContentLength(content.len() as u64)); - response_headers.set(Host { hostname: "foo.bar".to_owned(), port: None }); - response_headers.set(devhttpresponse.headers.as_ref().unwrap().get::<Date>().unwrap().clone()); + let mut response_headers = HeaderMap::new(); + response_headers.typed_insert(ContentLength(content.len() as u64)); + response_headers.typed_insert(Host::from("foo.bar".parse::<Authority>().unwrap())); + response_headers.typed_insert(devhttpresponse.headers.as_ref().unwrap().typed_get::<Date>().unwrap().clone()); let httpresponse = DevtoolsHttpResponse { headers: Some(response_headers), @@ -286,15 +244,15 @@ fn test_request_and_response_data_with_network_messages() { #[test] fn test_request_and_response_message_from_devtool_without_pipeline_id() { - let handler = move |_: HyperRequest, mut response: HyperResponse| { - response.headers_mut().set(Host { hostname: "foo.bar".to_owned(), port: None }); - response.send(b"Yay!").unwrap(); + let handler = move |_: HyperRequest<Body>, response: &mut HyperResponse<Body>| { + response.headers_mut().typed_insert(Host::from("foo.bar".parse::<Authority>().unwrap())); + *response.body_mut() = b"Yay!".to_vec().into(); }; - let (mut server, url) = make_server(handler); + let (server, url) = make_server(handler); let mut request = Request::from_init(RequestInit { url: url.clone(), - method: Method::Get, + method: Method::GET, destination: Destination::Document, origin: mock_origin(), pipeline_id: None, @@ -302,7 +260,7 @@ fn test_request_and_response_message_from_devtool_without_pipeline_id() { }); let (devtools_chan, devtools_port) = channel(); let response = fetch(&mut request, Some(devtools_chan)); - assert!(response.internal_response.unwrap().status.unwrap().is_success()); + assert!(response.internal_response.unwrap().status.unwrap().0.is_success()); let _ = server.close(); @@ -312,24 +270,23 @@ fn test_request_and_response_message_from_devtool_without_pipeline_id() { #[test] fn test_redirected_request_to_devtools() { - let post_handler = move |request: HyperRequest, response: HyperResponse| { - assert_eq!(request.method, Method::Get); - response.send(b"Yay!").unwrap(); + let post_handler = move |request: HyperRequest<Body>, response: &mut HyperResponse<Body>| { + assert_eq!(request.method(), Method::GET); + *response.body_mut() = b"Yay!".to_vec().into(); }; - let (mut post_server, post_url) = make_server(post_handler); + let (post_server, post_url) = make_server(post_handler); let post_redirect_url = post_url.clone(); - let pre_handler = move |request: HyperRequest, mut response: HyperResponse| { - assert_eq!(request.method, Method::Post); - response.headers_mut().set(Location(post_redirect_url.to_string())); - *response.status_mut() = StatusCode::MovedPermanently; - response.send(b"").unwrap(); + let pre_handler = move |request: HyperRequest<Body>, response: &mut HyperResponse<Body>| { + assert_eq!(request.method(), Method::POST); + response.headers_mut().insert(header::LOCATION, HeaderValue::from_str(&post_redirect_url.to_string()).unwrap()); + *response.status_mut() = StatusCode::MOVED_PERMANENTLY; }; - let (mut pre_server, pre_url) = make_server(pre_handler); + let (pre_server, pre_url) = make_server(pre_handler); let mut request = Request::from_init(RequestInit { url: pre_url.clone(), - method: Method::Post, + method: Method::POST, destination: Destination::Document, pipeline_id: Some(TEST_PIPELINE_ID), .. RequestInit::default() @@ -343,14 +300,14 @@ fn test_redirected_request_to_devtools() { let devhttprequest = expect_devtools_http_request(&devtools_port); let devhttpresponse = expect_devtools_http_response(&devtools_port); - assert_eq!(devhttprequest.method, Method::Post); + assert_eq!(devhttprequest.method, Method::POST); assert_eq!(devhttprequest.url, pre_url); assert_eq!(devhttpresponse.status, Some((301, b"Moved Permanently".to_vec()))); let devhttprequest = expect_devtools_http_request(&devtools_port); let devhttpresponse = expect_devtools_http_response(&devtools_port); - assert_eq!(devhttprequest.method, Method::Get); + assert_eq!(devhttprequest.method, Method::GET); assert_eq!(devhttprequest.url, post_url); assert_eq!(devhttpresponse.status, Some((200, b"OK".to_vec()))); } @@ -359,24 +316,23 @@ fn test_redirected_request_to_devtools() { #[test] fn test_load_when_redirecting_from_a_post_should_rewrite_next_request_as_get() { - let post_handler = move |request: HyperRequest, response: HyperResponse| { - assert_eq!(request.method, Method::Get); - response.send(b"Yay!").unwrap(); + let post_handler = move |request: HyperRequest<Body>, response: &mut HyperResponse<Body>| { + assert_eq!(request.method(), Method::GET); + *response.body_mut() = b"Yay!".to_vec().into(); }; - let (mut post_server, post_url) = make_server(post_handler); + let (post_server, post_url) = make_server(post_handler); let post_redirect_url = post_url.clone(); - let pre_handler = move |request: HyperRequest, mut response: HyperResponse| { - assert_eq!(request.method, Method::Post); - response.headers_mut().set(Location(post_redirect_url.to_string())); - *response.status_mut() = StatusCode::MovedPermanently; - response.send(b"").unwrap(); + let pre_handler = move |request: HyperRequest<Body>, response: &mut HyperResponse<Body>| { + assert_eq!(request.method(), Method::POST); + response.headers_mut().insert(header::LOCATION, HeaderValue::from_str(&post_redirect_url.to_string()).unwrap()); + *response.status_mut() = StatusCode::MOVED_PERMANENTLY; }; - let (mut pre_server, pre_url) = make_server(pre_handler); + let (pre_server, pre_url) = make_server(pre_handler); let mut request = Request::from_init(RequestInit { url: pre_url.clone(), - method: Method::Post, + method: Method::POST, destination: Destination::Document, origin: mock_origin(), pipeline_id: Some(TEST_PIPELINE_ID), @@ -387,23 +343,23 @@ fn test_load_when_redirecting_from_a_post_should_rewrite_next_request_as_get() { let _ = pre_server.close(); let _ = post_server.close(); - assert!(response.to_actual().status.unwrap().is_success()); + assert!(response.to_actual().status.unwrap().0.is_success()); } #[test] fn test_load_should_decode_the_response_as_deflate_when_response_headers_have_content_encoding_deflate() { - let handler = move |_: HyperRequest, mut response: HyperResponse| { - response.headers_mut().set(ContentEncoding(vec![Encoding::Deflate])); + let handler = move |_: HyperRequest<Body>, response: &mut HyperResponse<Body>| { + response.headers_mut().insert(header::CONTENT_ENCODING, HeaderValue::from_static("deflate")); let mut e = DeflateEncoder::new(Vec::new(), Compression::default()); e.write(b"Yay!").unwrap(); let encoded_content = e.finish().unwrap(); - response.send(&encoded_content).unwrap(); + *response.body_mut() = encoded_content.into(); }; - let (mut server, url) = make_server(handler); + let (server, url) = make_server(handler); let mut request = Request::from_init(RequestInit { url: url.clone(), - method: Method::Get, + method: Method::GET, body: None, destination: Destination::Document, origin: mock_origin(), @@ -415,25 +371,25 @@ fn test_load_should_decode_the_response_as_deflate_when_response_headers_have_co let _ = server.close(); let internal_response = response.internal_response.unwrap(); - assert!(internal_response.status.unwrap().is_success()); + assert!(internal_response.status.clone().unwrap().0.is_success()); assert_eq!(*internal_response.body.lock().unwrap(), ResponseBody::Done(b"Yay!".to_vec())); } #[test] fn test_load_should_decode_the_response_as_gzip_when_response_headers_have_content_encoding_gzip() { - let handler = move |_: HyperRequest, mut response: HyperResponse| { - response.headers_mut().set(ContentEncoding(vec![Encoding::Gzip])); + let handler = move |_: HyperRequest<Body>, response: &mut HyperResponse<Body>| { + response.headers_mut().insert(header::CONTENT_ENCODING, HeaderValue::from_static("gzip")); let mut e = GzEncoder::new(Vec::new(), Compression::default()); e.write(b"Yay!").unwrap(); let encoded_content = e.finish().unwrap(); - response.send(&encoded_content).unwrap(); + *response.body_mut() = encoded_content.into(); }; - let (mut server, url) = make_server(handler); + let (server, url) = make_server(handler); let mut request = Request::from_init(RequestInit { url: url.clone(), - method: Method::Get, + method: Method::GET, body: None, destination: Destination::Document, origin: mock_origin(), @@ -445,35 +401,36 @@ fn test_load_should_decode_the_response_as_gzip_when_response_headers_have_conte let _ = server.close(); let internal_response = response.internal_response.unwrap(); - assert!(internal_response.status.unwrap().is_success()); + assert!(internal_response.status.clone().unwrap().0.is_success()); assert_eq!(*internal_response.body.lock().unwrap(), ResponseBody::Done(b"Yay!".to_vec())); } #[test] fn test_load_doesnt_send_request_body_on_any_redirect() { - let post_handler = move |mut request: HyperRequest, response: HyperResponse| { - assert_eq!(request.method, Method::Get); - let data = read_response(&mut request); - assert_eq!(data, ""); - response.send(b"Yay!").unwrap(); + let post_handler = move |request: HyperRequest<Body>, response: &mut HyperResponse<Body>| { + assert_eq!(request.method(), Method::GET); + read_response(request).and_then(|data| { + assert_eq!(data, ""); futures::future::ok(()) + }).poll().unwrap(); + *response.body_mut() = b"Yay!".to_vec().into(); }; - let (mut post_server, post_url) = make_server(post_handler); + let (post_server, post_url) = make_server(post_handler); let post_redirect_url = post_url.clone(); - let pre_handler = move |mut request: HyperRequest, mut response: HyperResponse| { - let data = read_response(&mut request); - assert_eq!(data, "Body on POST!"); - response.headers_mut().set(Location(post_redirect_url.to_string())); - *response.status_mut() = StatusCode::MovedPermanently; - response.send(b"").unwrap(); + let pre_handler = move |request: HyperRequest<Body>, response: &mut HyperResponse<Body>| { + read_response(request).and_then(|data| { + assert_eq!(data, "Body on POST"); futures::future::ok(()) + }).poll().unwrap(); + response.headers_mut().insert(header::LOCATION, HeaderValue::from_str(&post_redirect_url.to_string()).unwrap()); + *response.status_mut() = StatusCode::MOVED_PERMANENTLY; }; - let (mut pre_server, pre_url) = make_server(pre_handler); + let (pre_server, pre_url) = make_server(pre_handler); let mut request = Request::from_init(RequestInit { url: pre_url.clone(), body: Some(b"Body on POST!".to_vec()), - method: Method::Post, + method: Method::POST, destination: Destination::Document, origin: mock_origin(), pipeline_id: Some(TEST_PIPELINE_ID), @@ -484,20 +441,21 @@ fn test_load_doesnt_send_request_body_on_any_redirect() { let _ = pre_server.close(); let _ = post_server.close(); - assert!(response.to_actual().status.unwrap().is_success()); + assert!(response.to_actual().status.unwrap().0.is_success()); } #[test] fn test_load_doesnt_add_host_to_sts_list_when_url_is_http_even_if_sts_headers_are_present() { - let handler = move |_: HyperRequest, mut response: HyperResponse| { - response.headers_mut().set(StrictTransportSecurity::excluding_subdomains(31536000)); - response.send(b"Yay!").unwrap(); + let handler = move |_: HyperRequest<Body>, response: &mut HyperResponse<Body>| { + response.headers_mut().typed_insert( + StrictTransportSecurity::excluding_subdomains(Duration::from_secs(31536000))); + *response.body_mut() = b"Yay!".to_vec().into(); }; - let (mut server, url) = make_server(handler); + let (server, url) = make_server(handler); let mut request = Request::from_init(RequestInit { url: url.clone(), - method: Method::Get, + method: Method::GET, body: None, destination: Destination::Document, origin: mock_origin(), @@ -509,17 +467,17 @@ fn test_load_doesnt_add_host_to_sts_list_when_url_is_http_even_if_sts_headers_ar let _ = server.close(); - assert!(response.internal_response.unwrap().status.unwrap().is_success()); + assert!(response.internal_response.unwrap().status.unwrap().0.is_success()); assert_eq!(context.state.hsts_list.read().unwrap().is_host_secure(url.host_str().unwrap()), false); } #[test] fn test_load_sets_cookies_in_the_resource_manager_when_it_get_set_cookie_header_in_response() { - let handler = move |_: HyperRequest, mut response: HyperResponse| { - response.headers_mut().set(SetCookie(vec!["mozillaIs=theBest".to_owned()])); - response.send(b"Yay!").unwrap(); + let handler = move |_: HyperRequest<Body>, response: &mut HyperResponse<Body>| { + response.headers_mut().insert(header::SET_COOKIE, HeaderValue::from_static("mozillaIs=theBest")); + *response.body_mut() = b"Yay!".to_vec().into(); }; - let (mut server, url) = make_server(handler); + let (server, url) = make_server(handler); let context = new_fetch_context(None, None); @@ -527,7 +485,7 @@ fn test_load_sets_cookies_in_the_resource_manager_when_it_get_set_cookie_header_ let mut request = Request::from_init(RequestInit { url: url.clone(), - method: Method::Get, + method: Method::GET, body: None, destination: Destination::Document, origin: mock_origin(), @@ -539,19 +497,18 @@ fn test_load_sets_cookies_in_the_resource_manager_when_it_get_set_cookie_header_ let _ = server.close(); - assert!(response.internal_response.unwrap().status.unwrap().is_success()); + assert!(response.internal_response.unwrap().status.unwrap().0.is_success()); assert_cookie_for_domain(&context.state.cookie_jar, url.as_str(), Some("mozillaIs=theBest")); } #[test] fn test_load_sets_requests_cookies_header_for_url_by_getting_cookies_from_the_resource_manager() { - let handler = move |request: HyperRequest, response: HyperResponse| { - assert_eq!(request.headers.get::<CookieHeader>(), - Some(&CookieHeader(vec!["mozillaIs=theBest".to_owned()]))); - response.send(b"Yay!").unwrap(); + let handler = move |request: HyperRequest<Body>, response: &mut HyperResponse<Body>| { + assert_eq!(request.headers().get(header::COOKIE).unwrap().as_bytes(), b"mozillaIs=theBest"); + *response.body_mut() = b"Yay!".to_vec().into(); }; - let (mut server, url) = make_server(handler); + let (server, url) = make_server(handler); let context = new_fetch_context(None, None); @@ -567,7 +524,7 @@ fn test_load_sets_requests_cookies_header_for_url_by_getting_cookies_from_the_re let mut request = Request::from_init(RequestInit { url: url.clone(), - method: Method::Get, + method: Method::GET, body: None, destination: Destination::Document, origin: mock_origin(), @@ -579,17 +536,16 @@ fn test_load_sets_requests_cookies_header_for_url_by_getting_cookies_from_the_re let _ = server.close(); - assert!(response.internal_response.unwrap().status.unwrap().is_success()); + assert!(response.internal_response.unwrap().status.unwrap().0.is_success()); } #[test] fn test_load_sends_cookie_if_nonhttp() { - let handler = move |request: HyperRequest, response: HyperResponse| { - assert_eq!(request.headers.get::<CookieHeader>(), - Some(&CookieHeader(vec!["mozillaIs=theBest".to_owned()]))); - response.send(b"Yay!").unwrap(); + let handler = move |request: HyperRequest<Body>, response: &mut HyperResponse<Body>| { + assert_eq!(request.headers().get(header::COOKIE).unwrap().as_bytes(), b"mozillaIs=theBest"); + *response.body_mut() = b"Yay!".to_vec().into(); }; - let (mut server, url) = make_server(handler); + let (server, url) = make_server(handler); let context = new_fetch_context(None, None); @@ -605,7 +561,7 @@ fn test_load_sends_cookie_if_nonhttp() { let mut request = Request::from_init(RequestInit { url: url.clone(), - method: Method::Get, + method: Method::GET, body: None, destination: Destination::Document, origin: mock_origin(), @@ -617,17 +573,16 @@ fn test_load_sends_cookie_if_nonhttp() { let _ = server.close(); - assert!(response.internal_response.unwrap().status.unwrap().is_success()); + assert!(response.internal_response.unwrap().status.unwrap().0.is_success()); } #[test] fn test_cookie_set_with_httponly_should_not_be_available_using_getcookiesforurl() { - let handler = move |_: HyperRequest, mut response: HyperResponse| { - let pair = vec!["mozillaIs=theBest; HttpOnly".to_owned()]; - response.headers_mut().set(SetCookie(pair)); - response.send(b"Yay!").unwrap(); + let handler = move |_: HyperRequest<Body>, response: &mut HyperResponse<Body>| { + response.headers_mut().insert(header::SET_COOKIE, HeaderValue::from_static("mozillaIs=theBest; HttpOnly")); + *response.body_mut() = b"Yay!".to_vec().into(); }; - let (mut server, url) = make_server(handler); + let (server, url) = make_server(handler); let context = new_fetch_context(None, None); @@ -635,7 +590,7 @@ fn test_cookie_set_with_httponly_should_not_be_available_using_getcookiesforurl( let mut request = Request::from_init(RequestInit { url: url.clone(), - method: Method::Get, + method: Method::GET, body: None, destination: Destination::Document, origin: mock_origin(), @@ -647,7 +602,7 @@ fn test_cookie_set_with_httponly_should_not_be_available_using_getcookiesforurl( let _ = server.close(); - assert!(response.internal_response.unwrap().status.unwrap().is_success()); + assert!(response.internal_response.unwrap().status.unwrap().0.is_success()); assert_cookie_for_domain(&context.state.cookie_jar, url.as_str(), Some("mozillaIs=theBest")); let mut cookie_jar = context.state.cookie_jar.write().unwrap(); @@ -656,12 +611,11 @@ fn test_cookie_set_with_httponly_should_not_be_available_using_getcookiesforurl( #[test] fn test_when_cookie_received_marked_secure_is_ignored_for_http() { - let handler = move |_: HyperRequest, mut response: HyperResponse| { - let pair = vec!["mozillaIs=theBest; Secure".to_owned()]; - response.headers_mut().set(SetCookie(pair)); - response.send(b"Yay!").unwrap(); + let handler = move |_: HyperRequest<Body>, response: &mut HyperResponse<Body>| { + response.headers_mut().insert(header::SET_COOKIE, HeaderValue::from_static("mozillaIs=theBest; Secure")); + *response.body_mut() = b"Yay!".to_vec().into(); }; - let (mut server, url) = make_server(handler); + let (server, url) = make_server(handler); let context = new_fetch_context(None, None); @@ -669,7 +623,7 @@ fn test_when_cookie_received_marked_secure_is_ignored_for_http() { let mut request = Request::from_init(RequestInit { url: url.clone(), - method: Method::Get, + method: Method::GET, body: None, destination: Destination::Document, origin: mock_origin(), @@ -681,7 +635,7 @@ fn test_when_cookie_received_marked_secure_is_ignored_for_http() { let _ = server.close(); - assert!(response.internal_response.unwrap().status.unwrap().is_success()); + assert!(response.internal_response.unwrap().status.unwrap().0.is_success()); assert_cookie_for_domain(&context.state.cookie_jar, url.as_str(), None); } @@ -689,16 +643,16 @@ fn test_when_cookie_received_marked_secure_is_ignored_for_http() { #[test] fn test_load_sets_content_length_to_length_of_request_body() { let content = b"This is a request body"; - let content_length = ContentLength(content.len() as u64); - let handler = move |request: HyperRequest, response: HyperResponse| { - assert_eq!(request.headers.get::<ContentLength>(), Some(&content_length)); - response.send(content).unwrap(); + let handler = move |request: HyperRequest<Body>, response: &mut HyperResponse<Body>| { + let content_length = ContentLength(content.len() as u64); + assert_eq!(request.headers().typed_get::<ContentLength>(), Some(content_length)); + *response.body_mut() = content.to_vec().into(); }; - let (mut server, url) = make_server(handler); + let (server, url) = make_server(handler); let mut request = Request::from_init(RequestInit { url: url.clone(), - method: Method::Post, + method: Method::POST, body: Some(content.to_vec()), destination: Destination::Document, origin: mock_origin(), @@ -709,24 +663,22 @@ fn test_load_sets_content_length_to_length_of_request_body() { let _ = server.close(); - assert!(response.internal_response.unwrap().status.unwrap().is_success()); + assert!(response.internal_response.unwrap().status.unwrap().0.is_success()); } #[test] fn test_load_uses_explicit_accept_from_headers_in_load_data() { - let accept = Accept(vec![qitem(Mime(TopLevel::Text, SubLevel::Html, vec![]))]); - let expected_accept = accept.clone(); - let handler = move |request: HyperRequest, response: HyperResponse| { - assert_eq!(request.headers.get::<Accept>(), Some(&expected_accept)); - response.send(b"Yay!").unwrap(); + let handler = move |request: HyperRequest<Body>, response: &mut HyperResponse<Body>| { + assert_eq!(request.headers().get(header::ACCEPT).unwrap().to_str().unwrap(), "text/html"); + *response.body_mut() = b"Yay!".to_vec().into(); }; - let (mut server, url) = make_server(handler); + let (server, url) = make_server(handler); - let mut accept_headers = Headers::new(); - accept_headers.set(accept); + let mut accept_headers = HeaderMap::new(); + accept_headers.insert(header::ACCEPT, HeaderValue::from_static("text/html")); let mut request = Request::from_init(RequestInit { url: url.clone(), - method: Method::Get, + method: Method::GET, headers: accept_headers, destination: Destination::Document, origin: mock_origin(), @@ -737,25 +689,21 @@ fn test_load_uses_explicit_accept_from_headers_in_load_data() { let _ = server.close(); - assert!(response.internal_response.unwrap().status.unwrap().is_success()); + assert!(response.internal_response.unwrap().status.unwrap().0.is_success()); } #[test] fn test_load_sets_default_accept_to_html_xhtml_xml_and_then_anything_else() { - let handler = move |request: HyperRequest, response: HyperResponse| { - assert_eq!(request.headers.get::<Accept>(), Some(&Accept(vec![ - qitem(Mime(TopLevel::Text, SubLevel::Html, vec![])), - qitem(Mime(TopLevel::Application, SubLevel::Ext("xhtml+xml".to_owned()), vec![])), - QualityItem::new(Mime(TopLevel::Application, SubLevel::Xml, vec![]), Quality(900)), - QualityItem::new(Mime(TopLevel::Star, SubLevel::Star, vec![]), Quality(800)), - ]))); - response.send(b"Yay!").unwrap(); + let handler = move |request: HyperRequest<Body>, response: &mut HyperResponse<Body>| { + assert_eq!(request.headers().get(header::ACCEPT).unwrap().to_str().unwrap(), + "text/html, application/xhtml+xml, application/xml; q=0.9, */*; q=0.8"); + *response.body_mut() = b"Yay!".to_vec().into(); }; - let (mut server, url) = make_server(handler); + let (server, url) = make_server(handler); let mut request = Request::from_init(RequestInit { url: url.clone(), - method: Method::Get, + method: Method::GET, destination: Destination::Document, origin: mock_origin(), pipeline_id: Some(TEST_PIPELINE_ID), @@ -765,24 +713,22 @@ fn test_load_sets_default_accept_to_html_xhtml_xml_and_then_anything_else() { let _ = server.close(); - assert!(response.internal_response.unwrap().status.unwrap().is_success()); + assert!(response.internal_response.unwrap().status.unwrap().0.is_success()); } #[test] fn test_load_uses_explicit_accept_encoding_from_load_data_headers() { - let accept_encoding = AcceptEncoding(vec![qitem(Encoding::Chunked)]); - let expected_accept_encoding = accept_encoding.clone(); - let handler = move |request: HyperRequest, response: HyperResponse| { - assert_eq!(request.headers.get::<AcceptEncoding>(), Some(&expected_accept_encoding)); - response.send(b"Yay!").unwrap(); + let handler = move |request: HyperRequest<Body>, response: &mut HyperResponse<Body>| { + assert_eq!(request.headers().get(header::ACCEPT_ENCODING).unwrap().to_str().unwrap(), "chunked"); + *response.body_mut() = b"Yay!".to_vec().into(); }; - let (mut server, url) = make_server(handler); + let (server, url) = make_server(handler); - let mut accept_encoding_headers = Headers::new(); - accept_encoding_headers.set(accept_encoding); + let mut accept_encoding_headers = HeaderMap::new(); + accept_encoding_headers.insert(header::ACCEPT_ENCODING, HeaderValue::from_static("chunked")); let mut request = Request::from_init(RequestInit { url: url.clone(), - method: Method::Get, + method: Method::GET, headers: accept_encoding_headers, destination: Destination::Document, origin: mock_origin(), @@ -793,24 +739,20 @@ fn test_load_uses_explicit_accept_encoding_from_load_data_headers() { let _ = server.close(); - assert!(response.internal_response.unwrap().status.unwrap().is_success()); + assert!(response.internal_response.unwrap().status.unwrap().0.is_success()); } #[test] fn test_load_sets_default_accept_encoding_to_gzip_and_deflate() { - let handler = move |request: HyperRequest, response: HyperResponse| { - assert_eq!(request.headers.get::<AcceptEncoding>(), Some(&AcceptEncoding(vec![ - qitem(Encoding::Gzip), - qitem(Encoding::Deflate), - qitem(Encoding::EncodingExt("br".to_owned())) - ]))); - response.send(b"Yay!").unwrap(); + let handler = move |request: HyperRequest<Body>, response: &mut HyperResponse<Body>| { + assert_eq!(request.headers().get(header::ACCEPT_ENCODING).unwrap().to_str().unwrap(), "gzip, deflate, br"); + *response.body_mut() = b"Yay!".to_vec().into(); }; - let (mut server, url) = make_server(handler); + let (server, url) = make_server(handler); let mut request = Request::from_init(RequestInit { url: url.clone(), - method: Method::Get, + method: Method::GET, destination: Destination::Document, origin: mock_origin(), pipeline_id: Some(TEST_PIPELINE_ID), @@ -820,33 +762,32 @@ fn test_load_sets_default_accept_encoding_to_gzip_and_deflate() { let _ = server.close(); - assert!(response.internal_response.unwrap().status.unwrap().is_success()); + assert!(response.internal_response.unwrap().status.unwrap().0.is_success()); } #[test] fn test_load_errors_when_there_a_redirect_loop() { let url_b_for_a = Arc::new(Mutex::new(None::<ServoUrl>)); let url_b_for_a_clone = url_b_for_a.clone(); - let handler_a = move |_: HyperRequest, mut response: HyperResponse| { - response.headers_mut().set(Location(url_b_for_a_clone.lock().unwrap().as_ref().unwrap().to_string())); - *response.status_mut() = StatusCode::MovedPermanently; - response.send(b"").unwrap(); + let handler_a = move |_: HyperRequest<Body>, response: &mut HyperResponse<Body>| { + response.headers_mut().insert(header::LOCATION, + HeaderValue::from_str(&url_b_for_a_clone.lock().unwrap().as_ref().unwrap().to_string()).unwrap()); + *response.status_mut() = StatusCode::MOVED_PERMANENTLY; }; - let (mut server_a, url_a) = make_server(handler_a); + let (server_a, url_a) = make_server(handler_a); let url_a_for_b = url_a.clone(); - let handler_b = move |_: HyperRequest, mut response: HyperResponse| { - response.headers_mut().set(Location(url_a_for_b.to_string())); - *response.status_mut() = StatusCode::MovedPermanently; - response.send(b"").unwrap(); + let handler_b = move |_: HyperRequest<Body>, response: &mut HyperResponse<Body>| { + response.headers_mut().insert(header::LOCATION, HeaderValue::from_str(&url_a_for_b.to_string()).unwrap()); + *response.status_mut() = StatusCode::MOVED_PERMANENTLY; }; - let (mut server_b, url_b) = make_server(handler_b); + let (server_b, url_b) = make_server(handler_b); *url_b_for_a.lock().unwrap() = Some(url_b.clone()); let mut request = Request::from_init(RequestInit { url: url_a.clone(), - method: Method::Get, + method: Method::GET, destination: Destination::Document, origin: mock_origin(), pipeline_id: Some(TEST_PIPELINE_ID), @@ -866,30 +807,29 @@ fn test_load_succeeds_with_a_redirect_loop() { let url_b_for_a = Arc::new(Mutex::new(None::<ServoUrl>)); let url_b_for_a_clone = url_b_for_a.clone(); let handled_a = AtomicBool::new(false); - let handler_a = move |_: HyperRequest, mut response: HyperResponse| { + let handler_a = move |_: HyperRequest<Body>, response: &mut HyperResponse<Body>| { if !handled_a.swap(true, Ordering::SeqCst) { - response.headers_mut().set(Location(url_b_for_a_clone.lock().unwrap().as_ref().unwrap().to_string())); - *response.status_mut() = StatusCode::MovedPermanently; - response.send(b"").unwrap(); + response.headers_mut().insert(header::LOCATION, + HeaderValue::from_str(&url_b_for_a_clone.lock().unwrap().as_ref().unwrap().to_string()).unwrap()); + *response.status_mut() = StatusCode::MOVED_PERMANENTLY; } else { - response.send(b"Success").unwrap(); + *response.body_mut() = b"Success".to_vec().into() } }; - let (mut server_a, url_a) = make_server(handler_a); + let (server_a, url_a) = make_server(handler_a); let url_a_for_b = url_a.clone(); - let handler_b = move |_: HyperRequest, mut response: HyperResponse| { - response.headers_mut().set(Location(url_a_for_b.to_string())); - *response.status_mut() = StatusCode::MovedPermanently; - response.send(b"").unwrap(); + let handler_b = move |_: HyperRequest<Body>, response: &mut HyperResponse<Body>| { + response.headers_mut().insert(header::LOCATION, HeaderValue::from_str(&url_a_for_b.to_string()).unwrap()); + *response.status_mut() = StatusCode::MOVED_PERMANENTLY; }; - let (mut server_b, url_b) = make_server(handler_b); + let (server_b, url_b) = make_server(handler_b); *url_b_for_a.lock().unwrap() = Some(url_b.clone()); let mut request = Request::from_init(RequestInit { url: url_a.clone(), - method: Method::Get, + method: Method::GET, destination: Destination::Document, origin: mock_origin(), pipeline_id: Some(TEST_PIPELINE_ID), @@ -908,24 +848,23 @@ fn test_load_succeeds_with_a_redirect_loop() { #[test] fn test_load_follows_a_redirect() { - let post_handler = move |request: HyperRequest, response: HyperResponse| { - assert_eq!(request.method, Method::Get); - response.send(b"Yay!").unwrap(); + let post_handler = move |request: HyperRequest<Body>, response: &mut HyperResponse<Body>| { + assert_eq!(request.method(), Method::GET); + *response.body_mut() = b"Yay!".to_vec().into(); }; - let (mut post_server, post_url) = make_server(post_handler); + let (post_server, post_url) = make_server(post_handler); let post_redirect_url = post_url.clone(); - let pre_handler = move |request: HyperRequest, mut response: HyperResponse| { - assert_eq!(request.method, Method::Get); - response.headers_mut().set(Location(post_redirect_url.to_string())); - *response.status_mut() = StatusCode::MovedPermanently; - response.send(b"").unwrap(); + let pre_handler = move |request: HyperRequest<Body>, response: &mut HyperResponse<Body>| { + assert_eq!(request.method(), Method::GET); + response.headers_mut().insert(header::LOCATION, HeaderValue::from_str(&post_redirect_url.to_string()).unwrap()); + *response.status_mut() = StatusCode::MOVED_PERMANENTLY; }; - let (mut pre_server, pre_url) = make_server(pre_handler); + let (pre_server, pre_url) = make_server(pre_handler); let mut request = Request::from_init(RequestInit { url: pre_url.clone(), - method: Method::Get, + method: Method::GET, destination: Destination::Document, origin: mock_origin(), pipeline_id: Some(TEST_PIPELINE_ID), @@ -937,7 +876,7 @@ fn test_load_follows_a_redirect() { let _ = post_server.close(); let internal_response = response.internal_response.unwrap(); - assert!(internal_response.status.unwrap().is_success()); + assert!(internal_response.status.clone().unwrap().0.is_success()); assert_eq!(*internal_response.body.lock().unwrap(), ResponseBody::Done(b"Yay!".to_vec())); } @@ -946,27 +885,21 @@ fn test_load_follows_a_redirect() { fn test_redirect_from_x_to_y_provides_y_cookies_from_y() { let shared_url_y = Arc::new(Mutex::new(None::<ServoUrl>)); let shared_url_y_clone = shared_url_y.clone(); - let handler = move |request: HyperRequest, mut response: HyperResponse| { - let path = match request.uri { - RequestUri::AbsolutePath(path) => path, - uri => panic!("Unexpected uri: {:?}", uri), - }; + let handler = move |request: HyperRequest<Body>, response: &mut HyperResponse<Body>| { + let path = request.uri().path(); if path == "/com/" { - assert_eq!(request.headers.get(), - Some(&CookieHeader(vec!["mozillaIsNot=dotOrg".to_owned()]))); + assert_eq!(request.headers().get(header::COOKIE).unwrap().as_bytes(), b"mozillaIsNot=dotOrg"); let location = shared_url_y.lock().unwrap().as_ref().unwrap().to_string(); - response.headers_mut().set(Location(location)); - *response.status_mut() = StatusCode::MovedPermanently; - response.send(b"").unwrap(); + response.headers_mut().insert(header::LOCATION, HeaderValue::from_str(&location.to_string()).unwrap()); + *response.status_mut() = StatusCode::MOVED_PERMANENTLY; } else if path == "/org/" { - assert_eq!(request.headers.get(), - Some(&CookieHeader(vec!["mozillaIs=theBest".to_owned()]))); - response.send(b"Yay!").unwrap(); + assert_eq!(request.headers().get(header::COOKIE).unwrap().as_bytes(), b"mozillaIs=theBest"); + *response.body_mut() = b"Yay!".to_vec().into(); } else { panic!("unexpected path {:?}", path) } }; - let (mut server, url) = make_server(handler); + let (server, url) = make_server(handler); let port = url.port().unwrap(); assert_eq!(url.host_str(), Some("localhost")); @@ -1002,7 +935,7 @@ fn test_redirect_from_x_to_y_provides_y_cookies_from_y() { let mut request = Request::from_init(RequestInit { url: url_x.clone(), - method: Method::Get, + method: Method::GET, destination: Destination::Document, origin: mock_origin(), pipeline_id: Some(TEST_PIPELINE_ID), @@ -1014,39 +947,34 @@ fn test_redirect_from_x_to_y_provides_y_cookies_from_y() { let _ = server.close(); let internal_response = response.internal_response.unwrap(); - assert!(internal_response.status.unwrap().is_success()); + assert!(internal_response.status.clone().unwrap().0.is_success()); assert_eq!(*internal_response.body.lock().unwrap(), ResponseBody::Done(b"Yay!".to_vec())); } #[test] fn test_redirect_from_x_to_x_provides_x_with_cookie_from_first_response() { - let handler = move |request: HyperRequest, mut response: HyperResponse| { - let path = match request.uri { - ::hyper::uri::RequestUri::AbsolutePath(path) => path, - uri => panic!("Unexpected uri: {:?}", uri), - }; + let handler = move |request: HyperRequest<Body>, response: &mut HyperResponse<Body>| { + let path = request.uri().path(); if path == "/initial/" { - response.headers_mut().set_raw("set-cookie", vec![b"mozillaIs=theBest; path=/;".to_vec()]); + response.headers_mut().insert(header::SET_COOKIE, HeaderValue::from_static("mozillaIs=theBest; path=/;")); let location = "/subsequent/".to_string(); - response.headers_mut().set(Location(location)); - *response.status_mut() = StatusCode::MovedPermanently; - response.send(b"").unwrap(); + response.headers_mut().insert(header::LOCATION, HeaderValue::from_str(&location.to_string()).unwrap()); + *response.status_mut() = StatusCode::MOVED_PERMANENTLY; } else if path == "/subsequent/" { - assert_eq!(request.headers.get(), - Some(&CookieHeader(vec!["mozillaIs=theBest".to_owned()]))); - response.send(b"Yay!").unwrap(); + assert_eq!(request.headers().get(header::COOKIE).unwrap().as_bytes(), b"mozillaIs=theBest"); + *response.body_mut() = b"Yay!".to_vec().into(); } else { panic!("unexpected path {:?}", path) } }; - let (mut server, url) = make_server(handler); + let (server, url) = make_server(handler); let url = url.join("/initial/").unwrap(); let mut request = Request::from_init(RequestInit { url: url.clone(), - method: Method::Get, + method: Method::GET, destination: Destination::Document, origin: mock_origin(), pipeline_id: Some(TEST_PIPELINE_ID), @@ -1058,26 +986,22 @@ fn test_redirect_from_x_to_x_provides_x_with_cookie_from_first_response() { let _ = server.close(); let internal_response = response.internal_response.unwrap(); - assert!(internal_response.status.unwrap().is_success()); + assert!(internal_response.status.clone().unwrap().0.is_success()); assert_eq!(*internal_response.body.lock().unwrap(), ResponseBody::Done(b"Yay!".to_vec())); } #[test] fn test_if_auth_creds_not_in_url_but_in_cache_it_sets_it() { - let handler = move |request: HyperRequest, response: HyperResponse| { - let expected = Authorization(Basic { - username: "username".to_owned(), - password: Some("test".to_owned()) - }); - assert_eq!(request.headers.get(), Some(&expected)); - response.send(b"").unwrap(); + let handler = move |request: HyperRequest<Body>, _response: &mut HyperResponse<Body>| { + let expected = Authorization::basic("username", "test"); + assert_eq!(request.headers().typed_get::<Authorization<Basic>>(), Some(expected)); }; - let (mut server, url) = make_server(handler); + let (server, url) = make_server(handler); let mut request = Request::from_init(RequestInit { url: url.clone(), - method: Method::Get, + method: Method::GET, body: None, destination: Destination::Document, origin: mock_origin(), @@ -1098,20 +1022,19 @@ fn test_if_auth_creds_not_in_url_but_in_cache_it_sets_it() { let _ = server.close(); - assert!(response.internal_response.unwrap().status.unwrap().is_success()); + assert!(response.internal_response.unwrap().status.unwrap().0.is_success()); } #[test] fn test_auth_ui_needs_www_auth() { - let handler = move |_: HyperRequest, mut response: HyperResponse| { - *response.status_mut() = StatusCode::Unauthorized; - response.send(b"").unwrap(); + let handler = move |_: HyperRequest<Body>, response: &mut HyperResponse<Body>| { + *response.status_mut() = StatusCode::UNAUTHORIZED; }; - let (mut server, url) = make_server(handler); + let (server, url) = make_server(handler); let mut request = Request::from_init(RequestInit { url: url.clone(), - method: Method::Get, + method: Method::GET, body: None, destination: Destination::Document, origin: mock_origin(), @@ -1124,41 +1047,50 @@ fn test_auth_ui_needs_www_auth() { let _ = server.close(); - assert_eq!(response.internal_response.unwrap().status.unwrap(), StatusCode::Unauthorized); + assert_eq!(response.internal_response.unwrap().status.unwrap().0, StatusCode::UNAUTHORIZED); } #[test] fn test_origin_set() { let origin_header = Arc::new(Mutex::new(None)); let origin_header_clone = origin_header.clone(); - let handler = move |request: HyperRequest, mut resp: HyperResponse| { + let handler = move |request: HyperRequest<Body>, resp: &mut HyperResponse<Body>| { let origin_header_clone = origin_header.clone(); - resp.headers_mut().set(AccessControlAllowOrigin::Any); - match request.headers.get::<Origin>() { + resp.headers_mut().typed_insert(AccessControlAllowOrigin::ANY); + match request.headers().typed_get::<Origin>() { None => assert_eq!(origin_header_clone.lock().unwrap().take(), None), - Some(h) => assert_eq!(*h, origin_header_clone.lock().unwrap().take().unwrap()), + Some(h) => assert_eq!(h, origin_header_clone.lock().unwrap().take().unwrap()), } }; - let (mut server, url) = make_server(handler); + let (server, url) = make_server(handler); - let mut origin = Origin::new(url.scheme(), url.host_str().unwrap(), url.port()); + let mut origin = Origin::try_from_parts( + url.scheme(), + url.host_str().unwrap(), + url.port() + ).unwrap(); *origin_header_clone.lock().unwrap() = Some(origin.clone()); let mut request = Request::from_init(RequestInit { url: url.clone(), - method: Method::Post, + method: Method::POST, body: None, origin: url.clone().origin(), .. RequestInit::default() }); let response = fetch(&mut request, None); - assert!(response.internal_response.unwrap().status.unwrap().is_success()); + assert!(response.internal_response.unwrap().status.unwrap().0.is_success()); let origin_url = ServoUrl::parse("http://example.com").unwrap(); - origin = Origin::new(origin_url.scheme(), origin_url.host_str().unwrap(), origin_url.port()); + // XXX: Not sure about the Some(80) here. origin_url.origin() returns 80 for the port but origin_url returns None. + origin = Origin::try_from_parts( + origin_url.scheme(), + origin_url.host_str().unwrap(), + Some(80) + ).unwrap(); // Test Origin header is set on Get request with CORS mode let mut request = Request::from_init(RequestInit { url: url.clone(), - method: Method::Get, + method: Method::GET, mode: RequestMode::CorsMode, body: None, origin: origin_url.clone().origin(), @@ -1167,12 +1099,12 @@ fn test_origin_set() { *origin_header_clone.lock().unwrap() = Some(origin.clone()); let response = fetch(&mut request, None); - assert!(response.internal_response.unwrap().status.unwrap().is_success()); + assert!(response.internal_response.unwrap().status.unwrap().0.is_success()); // Test Origin header is not set on method Head let mut request = Request::from_init(RequestInit { url: url.clone(), - method: Method::Head, + method: Method::HEAD, body: None, origin: url.clone().origin(), .. RequestInit::default() @@ -1180,7 +1112,7 @@ fn test_origin_set() { *origin_header_clone.lock().unwrap() = None; let response = fetch(&mut request, None); - assert!(response.internal_response.unwrap().status.unwrap().is_success()); + assert!(response.internal_response.unwrap().status.unwrap().0.is_success()); let _ = server.close(); } diff --git a/components/net/tests/main.rs b/components/net/tests/main.rs index aeddb5105d1..ad666198a07 100644 --- a/components/net/tests/main.rs +++ b/components/net/tests/main.rs @@ -8,19 +8,27 @@ extern crate cookie as cookie_rs; extern crate devtools_traits; extern crate embedder_traits; extern crate flate2; +extern crate futures; +extern crate headers_core; +extern crate headers_ext; +extern crate http; extern crate hyper; -extern crate hyper_openssl; extern crate hyper_serde; extern crate ipc_channel; +#[macro_use] +extern crate lazy_static; +extern crate mime; extern crate msg; extern crate net; extern crate net_traits; +extern crate openssl; extern crate profile_traits; extern crate servo_channel; extern crate servo_config; extern crate servo_url; extern crate time; -extern crate unicase; +extern crate tokio; +extern crate tokio_openssl; extern crate url; mod cookie; @@ -38,8 +46,12 @@ mod subresource_integrity; use devtools_traits::DevtoolsControlMsg; use embedder_traits::{EmbedderProxy, EventLoopWaker}; use embedder_traits::resources::{self, Resource}; -use hyper::server::{Handler, Listening, Server}; -use net::connector::create_ssl_client; +use futures::{Future, Stream}; +use hyper::{Body, Request as HyperRequest, Response as HyperResponse}; +use hyper::server::Server as HyperServer; +use hyper::server::conn::Http; +use hyper::service::service_fn_ok; +use net::connector::create_ssl_connector_builder; use net::fetch::cors_cache::CorsCache; use net::fetch::methods::{self, CancellationListener, FetchContext}; use net::filemanager_thread::FileManager; @@ -47,9 +59,21 @@ use net::test::HttpState; use net_traits::FetchTaskTarget; use net_traits::request::Request; use net_traits::response::Response; +use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; use servo_channel::{channel, Sender}; use servo_url::ServoUrl; +use std::net::TcpListener as StdTcpListener; +use std::path::PathBuf; use std::sync::{Arc, Mutex}; +use tokio::net::TcpListener; +use tokio::runtime::Runtime; +use tokio_openssl::SslAcceptorExt; + +lazy_static! { + pub static ref HANDLE: Mutex<Runtime> = { + Mutex::new(Runtime::new().unwrap()) + }; +} const DEFAULT_USER_AGENT: &'static str = "Such Browser. Very Layout. Wow."; @@ -84,10 +108,10 @@ fn create_embedder_proxy() -> EmbedderProxy { } fn new_fetch_context(dc: Option<Sender<DevtoolsControlMsg>>, fc: Option<EmbedderProxy>) -> FetchContext { - let ssl_client = create_ssl_client(&resources::read_string(Resource::SSLCertificates)); + let ssl_connector = create_ssl_connector_builder(&resources::read_string(Resource::SSLCertificates)); let sender = fc.unwrap_or_else(|| create_embedder_proxy()); FetchContext { - state: Arc::new(HttpState::new(ssl_client)), + state: Arc::new(HttpState::new(ssl_connector)), user_agent: DEFAULT_USER_AGENT.into(), devtools_chan: dc, filemanager: FileManager::new(sender), @@ -131,10 +155,78 @@ fn fetch_with_cors_cache(request: &mut Request, cache: &mut CorsCache) -> Respon receiver.recv().unwrap() } -fn make_server<H: Handler + 'static>(handler: H) -> (Listening, ServoUrl) { - // this is a Listening server because of handle_threads() - let server = Server::http("0.0.0.0:0").unwrap().handle_threads(handler, 2).unwrap(); - let url_string = format!("http://localhost:{}", server.socket.port()); +pub(crate) struct Server { + pub close_channel: futures::sync::oneshot::Sender<()>, +} + +impl Server { + fn close(self) { + self.close_channel.send(()).unwrap(); + } +} + +fn make_server<H>(handler: H) -> (Server, ServoUrl) + where + H: Fn(HyperRequest<Body>, &mut HyperResponse<Body>) + Send + Sync + 'static, +{ + let handler = Arc::new(handler); + let listener = StdTcpListener::bind("0.0.0.0:0").unwrap(); + let url_string = format!("http://localhost:{}", listener.local_addr().unwrap().port()); let url = ServoUrl::parse(&url_string).unwrap(); + let (tx, rx) = futures::sync::oneshot::channel::<()>(); + let server = HyperServer::from_tcp(listener).unwrap().serve( + move || { + let handler = handler.clone(); + service_fn_ok(move |req: HyperRequest<Body>| { + let mut response = HyperResponse::new(Vec::<u8>::new().into()); + handler(req, &mut response); + response + }) + } + ) + .with_graceful_shutdown(rx) + .map_err(|_|()); + + HANDLE.lock().unwrap().spawn(server); + let server = Server { close_channel: tx }; + (server, url) +} + +fn make_ssl_server<H>(handler: H, cert_path: PathBuf, key_path: PathBuf) -> (Server, ServoUrl) + where + H: Fn(HyperRequest<Body>, &mut HyperResponse<Body>) + Send + Sync + 'static, +{ + let handler = Arc::new(handler); + let listener = StdTcpListener::bind("[::0]:0").unwrap(); + let listener = TcpListener::from_std(listener, &HANDLE.lock().unwrap().reactor()).unwrap(); + let url_string = format!("http://localhost:{}", listener.local_addr().unwrap().port()); + let url = ServoUrl::parse(&url_string).unwrap(); + + let server = listener.incoming() + .map_err(|_| ()) + .for_each(move |sock| { + let mut ssl_builder = SslAcceptor::mozilla_modern(SslMethod::tls()).unwrap(); + ssl_builder.set_certificate_file(&cert_path, SslFiletype::PEM).unwrap(); + ssl_builder.set_private_key_file(&key_path, SslFiletype::PEM).unwrap(); + + let handler = handler.clone(); + ssl_builder.build().accept_async(sock).map_err(|_| ()).and_then(move |ssl| { + Http::new().serve_connection(ssl, + service_fn_ok(move |req: HyperRequest<Body>| { + let mut response = HyperResponse::new(Vec::<u8>::new().into()); + handler(req, &mut response); + response + }) + ) + .map_err(|_|()) + }) + }); + + let (tx, rx) = futures::sync::oneshot::channel::<()>(); + let server = server.select(rx.map_err(|_| ())).map(|_| ()).map_err(|_| ()); + + HANDLE.lock().unwrap().spawn(server); + + let server = Server { close_channel: tx }; (server, url) } diff --git a/components/net/tests/mime_classifier.rs b/components/net/tests/mime_classifier.rs index 1a4757ded5f..593017c7f2c 100644 --- a/components/net/tests/mime_classifier.rs +++ b/components/net/tests/mime_classifier.rs @@ -2,8 +2,8 @@ * 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/. */ +use mime::{self, Mime}; use net::mime_classifier::{ApacheBugFlag, MimeClassifier, Mp4Matcher, NoSniffFlag}; -use net::mime_classifier::as_string_option; use net_traits::LoadContext; use std::env; use std::fs::File; @@ -58,11 +58,10 @@ fn test_validate_classifier() { #[cfg(test)] fn test_sniff_with_flags(filename_orig: &path::Path, - type_string: &str, - subtype_string: &str, - supplied_type: Option<(&'static str, &'static str)>, - no_sniff_flag: NoSniffFlag, - apache_bug_flag: ApacheBugFlag) { + expected_mime: Mime, + supplied_type: Option<Mime>, + no_sniff_flag: NoSniffFlag, + apache_bug_flag: ApacheBugFlag) { let current_working_directory = env::current_dir().unwrap(); println!("The current directory is {}", current_working_directory.display()); @@ -75,17 +74,15 @@ fn test_sniff_with_flags(filename_orig: &path::Path, match read_result { Ok(data) => { - let supplied_type = supplied_type.map(|(x, y)| (x.parse().unwrap(), y)); - let (parsed_type, parsed_subtp) = classifier.classify(LoadContext::Browsing, - no_sniff_flag, - apache_bug_flag, - &as_string_option(supplied_type), - &data); - if (&parsed_type[..] != type_string) || - (&parsed_subtp[..] != subtype_string) { - panic!("File {:?} parsed incorrectly should be {}/{}, parsed as {}/{}", - filename, type_string, subtype_string, - parsed_type, parsed_subtp); + let parsed_mime = classifier.classify(LoadContext::Browsing, + no_sniff_flag, + apache_bug_flag, + &supplied_type, + &data); + if (parsed_mime.type_() != expected_mime.type_()) || + (parsed_mime.subtype() != expected_mime.subtype()) { + panic!("File {:?} parsed incorrectly should be {:?}, parsed as {:?}", + filename, expected_mime, parsed_mime); } } Err(e) => panic!("Couldn't read from file {:?} with error {}", @@ -94,407 +91,407 @@ fn test_sniff_with_flags(filename_orig: &path::Path, } #[cfg(test)] -fn test_sniff_full(filename_orig: &path::Path, type_string: &str, subtype_string: &str, - supplied_type: Option<(&'static str, &'static str)>) { +fn test_sniff_full(filename_orig: &path::Path, expected_mime: Mime, + supplied_type: Option<Mime>) { test_sniff_with_flags(filename_orig, - type_string, - subtype_string, + expected_mime, supplied_type, NoSniffFlag::Off, ApacheBugFlag::Off) } #[cfg(test)] -fn test_sniff_classification(file: &str, type_string: &str, subtype_string: &str, - supplied_type: Option<(&'static str, &'static str)>) { +fn test_sniff_classification(file: &str, expected_mime: Mime, supplied_type: Option<Mime>) { let mut x = PathBuf::from("./"); - x.push(type_string); - x.push(subtype_string); + x.push(expected_mime.type_().as_str()); + x.push(expected_mime.subtype().as_str()); x.push(file); - test_sniff_full(&x, type_string, subtype_string, supplied_type); + test_sniff_full(&x, expected_mime, supplied_type); } #[cfg(test)] -fn test_sniff_classification_sup(file: &str, type_string: &'static str, subtype_string: &str) { - test_sniff_classification(file, type_string, subtype_string, None); - let class_type = Some((type_string, "")); - test_sniff_classification(file, type_string, subtype_string, class_type); +fn test_sniff_classification_sup(file: &str, expected_mime: Mime) { + test_sniff_classification(file, expected_mime.clone(), None); + let no_sub = format!("{}/", expected_mime.type_()).parse().unwrap(); + test_sniff_classification(file, expected_mime, Some(no_sub)); } #[test] fn test_sniff_x_icon() { - test_sniff_classification_sup("test.ico", "image", "x-icon"); + test_sniff_classification_sup("test.ico", "image/x-icon".parse().unwrap()); } #[test] fn test_sniff_x_icon_cursor() { - test_sniff_classification_sup("test_cursor.ico", "image", "x-icon"); + test_sniff_classification_sup("test_cursor.ico", "image/x-icon".parse().unwrap()); } #[test] fn test_sniff_bmp() { - test_sniff_classification_sup("test.bmp", "image", "bmp"); + test_sniff_classification_sup("test.bmp", mime::IMAGE_BMP); } #[test] fn test_sniff_gif87a() { - test_sniff_classification_sup("test87a", "image", "gif"); + test_sniff_classification_sup("test87a", mime::IMAGE_GIF); } #[test] fn test_sniff_gif89a() { - test_sniff_classification_sup("test89a.gif", "image", "gif"); + test_sniff_classification_sup("test89a.gif", mime::IMAGE_GIF); } #[test] fn test_sniff_webp() { - test_sniff_classification_sup("test.webp", "image", "webp"); + test_sniff_classification_sup("test.webp", "image/webp".parse().unwrap()); } #[test] fn test_sniff_png() { - test_sniff_classification_sup("test.png", "image", "png"); + test_sniff_classification_sup("test.png", mime::IMAGE_PNG); } #[test] fn test_sniff_jpg() { - test_sniff_classification_sup("test.jpg", "image", "jpeg"); + test_sniff_classification_sup("test.jpg", mime::IMAGE_JPEG); } #[test] fn test_sniff_webm() { - test_sniff_classification_sup("test.webm", "video", "webm"); + test_sniff_classification_sup("test.webm", "video/webm".parse().unwrap()); } #[test] fn test_sniff_mp4() { - test_sniff_classification_sup("test.mp4", "video", "mp4"); + test_sniff_classification_sup("test.mp4", "video/mp4".parse().unwrap()); } #[test] fn test_sniff_avi() { - test_sniff_classification_sup("test.avi", "video", "avi"); + test_sniff_classification_sup("test.avi", "video/avi".parse().unwrap()); } #[test] fn test_sniff_basic() { - test_sniff_classification_sup("test.au", "audio", "basic"); + test_sniff_classification_sup("test.au", "audio/basic".parse().unwrap()); } #[test] fn test_sniff_aiff() { - test_sniff_classification_sup("test.aif", "audio", "aiff"); + test_sniff_classification_sup("test.aif", "audio/aiff".parse().unwrap()); } #[test] fn test_sniff_mpeg() { - test_sniff_classification_sup("test.mp3", "audio", "mpeg"); + test_sniff_classification_sup("test.mp3", "audio/mpeg".parse().unwrap()); } #[test] fn test_sniff_midi() { - test_sniff_classification_sup("test.mid", "audio", "midi"); + test_sniff_classification_sup("test.mid", "audio/midi".parse().unwrap()); } #[test] fn test_sniff_wave() { - test_sniff_classification_sup("test.wav", "audio", "wave"); + test_sniff_classification_sup("test.wav", "audio/wave".parse().unwrap()); } #[test] fn test_sniff_ogg() { - test_sniff_classification("small.ogg", "application", "ogg", None); - test_sniff_classification("small.ogg", "application", "ogg", Some(("audio", ""))); + test_sniff_classification("small.ogg", "application/ogg".parse().unwrap(), None); + test_sniff_classification("small.ogg", "application/ogg".parse().unwrap(), Some("audio/".parse().unwrap())); } #[test] #[should_panic] fn test_sniff_vsn_ms_fontobject() { - test_sniff_classification_sup("vnd.ms-fontobject", "application", "vnd.ms-fontobject"); + test_sniff_classification_sup("vnd.ms-fontobject", "application/vnd.ms-fontobject".parse().unwrap()); } #[test] #[should_panic] fn test_sniff_true_type() { - test_sniff_full(&PathBuf::from("unknown/true_type.ttf"), "(TrueType)", "", None); + test_sniff_full(&PathBuf::from("unknown/true_type.ttf"), "(TrueType)/".parse().unwrap(), None); } #[test] #[should_panic] fn test_sniff_open_type() { - test_sniff_full(&PathBuf::from("unknown/open_type"), "(OpenType)", "", None); + test_sniff_full(&PathBuf::from("unknown/open_type"), "(OpenType)/".parse().unwrap(), None); } #[test] #[should_panic] fn test_sniff_true_type_collection() { - test_sniff_full(&PathBuf::from("unknown/true_type_collection.ttc"), "(TrueType Collection)", "", None); + test_sniff_full(&PathBuf::from("unknown/true_type_collection.ttc"), "(TrueType Collection)/".parse().unwrap(), + None); } #[test] #[should_panic] fn test_sniff_woff() { - test_sniff_classification_sup("test.wof", "application", "font-woff"); + test_sniff_classification_sup("test.wof", "application/font-woff".parse().unwrap()); } #[test] fn test_sniff_gzip() { - test_sniff_classification("test.gz", "application", "x-gzip", None); + test_sniff_classification("test.gz", "application/x-gzip".parse().unwrap(), None); } #[test] fn test_sniff_zip() { - test_sniff_classification("test.zip", "application", "zip", None); + test_sniff_classification("test.zip", "application/zip".parse().unwrap(), None); } #[test] fn test_sniff_rar() { - test_sniff_classification("test.rar", "application", "x-rar-compressed", None); + test_sniff_classification("test.rar", "application/x-rar-compressed".parse().unwrap(), None); } #[test] fn test_sniff_text_html_doctype_20() { - test_sniff_classification("text_html_doctype_20.html", "text", "html", None); - test_sniff_classification("text_html_doctype_20_u.html", "text", "html", None); + test_sniff_classification("text_html_doctype_20.html", mime::TEXT_HTML, None); + test_sniff_classification("text_html_doctype_20_u.html", mime::TEXT_HTML, None); } #[test] fn test_sniff_text_html_doctype_3e() { - test_sniff_classification("text_html_doctype_3e.html", "text", "html", None); - test_sniff_classification("text_html_doctype_3e_u.html", "text", "html", None); + test_sniff_classification("text_html_doctype_3e.html", mime::TEXT_HTML, None); + test_sniff_classification("text_html_doctype_3e_u.html", mime::TEXT_HTML, None); } #[test] fn test_sniff_text_html_page_20() { - test_sniff_classification("text_html_page_20.html", "text", "html", None); - test_sniff_classification("text_html_page_20_u.html", "text", "html", None); + test_sniff_classification("text_html_page_20.html", mime::TEXT_HTML, None); + test_sniff_classification("text_html_page_20_u.html", mime::TEXT_HTML, None); } #[test] fn test_sniff_text_html_page_3e() { - test_sniff_classification("text_html_page_3e.html", "text", "html", None); - test_sniff_classification("text_html_page_3e_u.html", "text", "html", None); + test_sniff_classification("text_html_page_3e.html", mime::TEXT_HTML, None); + test_sniff_classification("text_html_page_3e_u.html", mime::TEXT_HTML, None); } #[test] fn test_sniff_text_html_head_20() { - test_sniff_classification("text_html_head_20.html", "text", "html", None); - test_sniff_classification("text_html_head_20_u.html", "text", "html", None); + test_sniff_classification("text_html_head_20.html", mime::TEXT_HTML, None); + test_sniff_classification("text_html_head_20_u.html", mime::TEXT_HTML, None); } #[test] fn test_sniff_text_html_head_3e() { - test_sniff_classification("text_html_head_3e.html", "text", "html", None); - test_sniff_classification("text_html_head_3e_u.html", "text", "html", None); + test_sniff_classification("text_html_head_3e.html", mime::TEXT_HTML, None); + test_sniff_classification("text_html_head_3e_u.html", mime::TEXT_HTML, None); } #[test] fn test_sniff_text_html_script_20() { - test_sniff_classification("text_html_script_20.html", "text", "html", None); - test_sniff_classification("text_html_script_20_u.html", "text", "html", None); + test_sniff_classification("text_html_script_20.html", mime::TEXT_HTML, None); + test_sniff_classification("text_html_script_20_u.html", mime::TEXT_HTML, None); } #[test] fn test_sniff_text_html_script_3e() { - test_sniff_classification("text_html_script_3e.html", "text", "html", None); - test_sniff_classification("text_html_script_3e_u.html", "text", "html", None); + test_sniff_classification("text_html_script_3e.html", mime::TEXT_HTML, None); + test_sniff_classification("text_html_script_3e_u.html", mime::TEXT_HTML, None); } #[test] fn test_sniff_text_html_iframe_20() { - test_sniff_classification("text_html_iframe_20.html", "text", "html", None); - test_sniff_classification("text_html_iframe_20_u.html", "text", "html", None); + test_sniff_classification("text_html_iframe_20.html", mime::TEXT_HTML, None); + test_sniff_classification("text_html_iframe_20_u.html", mime::TEXT_HTML, None); } #[test] fn test_sniff_text_html_iframe_3e() { - test_sniff_classification("text_html_iframe_3e.html", "text", "html", None); - test_sniff_classification("text_html_iframe_3e_u.html", "text", "html", None); + test_sniff_classification("text_html_iframe_3e.html", mime::TEXT_HTML, None); + test_sniff_classification("text_html_iframe_3e_u.html", mime::TEXT_HTML, None); } #[test] fn test_sniff_text_html_h1_20() { - test_sniff_classification("text_html_h1_20.html", "text", "html", None); - test_sniff_classification("text_html_h1_20_u.html", "text", "html", None); + test_sniff_classification("text_html_h1_20.html", mime::TEXT_HTML, None); + test_sniff_classification("text_html_h1_20_u.html", mime::TEXT_HTML, None); } #[test] fn test_sniff_text_html_h1_3e() { - test_sniff_classification("text_html_h1_3e.html", "text", "html", None); - test_sniff_classification("text_html_h1_3e_u.html", "text", "html", None); + test_sniff_classification("text_html_h1_3e.html", mime::TEXT_HTML, None); + test_sniff_classification("text_html_h1_3e_u.html", mime::TEXT_HTML, None); } #[test] fn test_sniff_text_html_div_20() { - test_sniff_classification("text_html_div_20.html", "text", "html", None); - test_sniff_classification("text_html_div_20_u.html", "text", "html", None); + test_sniff_classification("text_html_div_20.html", mime::TEXT_HTML, None); + test_sniff_classification("text_html_div_20_u.html", mime::TEXT_HTML, None); } #[test] fn test_sniff_text_html_div_3e() { - test_sniff_classification("text_html_div_3e.html", "text", "html", None); - test_sniff_classification("text_html_div_3e_u.html", "text", "html", None); + test_sniff_classification("text_html_div_3e.html", mime::TEXT_HTML, None); + test_sniff_classification("text_html_div_3e_u.html", mime::TEXT_HTML, None); } #[test] fn test_sniff_text_html_font_20() { - test_sniff_classification("text_html_font_20.html", "text", "html", None); - test_sniff_classification("text_html_font_20_u.html", "text", "html", None); + test_sniff_classification("text_html_font_20.html", mime::TEXT_HTML, None); + test_sniff_classification("text_html_font_20_u.html", mime::TEXT_HTML, None); } #[test] fn test_sniff_text_html_font_3e() { - test_sniff_classification("text_html_font_3e.html", "text", "html", None); - test_sniff_classification("text_html_font_3e_u.html", "text", "html", None); + test_sniff_classification("text_html_font_3e.html", mime::TEXT_HTML, None); + test_sniff_classification("text_html_font_3e_u.html", mime::TEXT_HTML, None); } #[test] fn test_sniff_text_html_table_20() { - test_sniff_classification("text_html_table_20.html", "text", "html", None); - test_sniff_classification("text_html_table_20_u.html", "text", "html", None); + test_sniff_classification("text_html_table_20.html", mime::TEXT_HTML, None); + test_sniff_classification("text_html_table_20_u.html", mime::TEXT_HTML, None); } #[test] fn test_sniff_text_html_table_3e() { - test_sniff_classification("text_html_table_3e.html", "text", "html", None); - test_sniff_classification("text_html_table_3e_u.html", "text", "html", None); + test_sniff_classification("text_html_table_3e.html", mime::TEXT_HTML, None); + test_sniff_classification("text_html_table_3e_u.html", mime::TEXT_HTML, None); } #[test] fn test_sniff_text_html_a_20() { - test_sniff_classification("text_html_a_20.html", "text", "html", None); - test_sniff_classification("text_html_a_20_u.html", "text", "html", None); + test_sniff_classification("text_html_a_20.html", mime::TEXT_HTML, None); + test_sniff_classification("text_html_a_20_u.html", mime::TEXT_HTML, None); } #[test] fn test_sniff_text_html_a_3e() { - test_sniff_classification("text_html_a_3e.html", "text", "html", None); - test_sniff_classification("text_html_a_3e_u.html", "text", "html", None); + test_sniff_classification("text_html_a_3e.html", mime::TEXT_HTML, None); + test_sniff_classification("text_html_a_3e_u.html", mime::TEXT_HTML, None); } #[test] fn test_sniff_text_html_style_20() { - test_sniff_classification("text_html_style_20.html", "text", "html", None); - test_sniff_classification("text_html_style_20_u.html", "text", "html", None); + test_sniff_classification("text_html_style_20.html", mime::TEXT_HTML, None); + test_sniff_classification("text_html_style_20_u.html", mime::TEXT_HTML, None); } #[test] fn test_sniff_text_html_style_3e() { - test_sniff_classification("text_html_style_3e.html", "text", "html", None); - test_sniff_classification("text_html_style_3e_u.html", "text", "html", None); + test_sniff_classification("text_html_style_3e.html", mime::TEXT_HTML, None); + test_sniff_classification("text_html_style_3e_u.html", mime::TEXT_HTML, None); } #[test] fn test_sniff_text_html_title_20() { - test_sniff_classification("text_html_title_20.html", "text", "html", None); - test_sniff_classification("text_html_title_20_u.html", "text", "html", None); + test_sniff_classification("text_html_title_20.html", mime::TEXT_HTML, None); + test_sniff_classification("text_html_title_20_u.html", mime::TEXT_HTML, None); } #[test] fn test_sniff_text_html_title_3e() { - test_sniff_classification("text_html_title_3e.html", "text", "html", None); - test_sniff_classification("text_html_title_3e_u.html", "text", "html", None); + test_sniff_classification("text_html_title_3e.html", mime::TEXT_HTML, None); + test_sniff_classification("text_html_title_3e_u.html", mime::TEXT_HTML, None); } #[test] fn test_sniff_text_html_b_20() { - test_sniff_classification("text_html_b_20.html", "text", "html", None); - test_sniff_classification("text_html_b_20_u.html", "text", "html", None); + test_sniff_classification("text_html_b_20.html", mime::TEXT_HTML, None); + test_sniff_classification("text_html_b_20_u.html", mime::TEXT_HTML, None); } #[test] fn test_sniff_text_html_b_3e() { - test_sniff_classification("text_html_b_3e.html", "text", "html", None); - test_sniff_classification("text_html_b_3e_u.html", "text", "html", None); + test_sniff_classification("text_html_b_3e.html", mime::TEXT_HTML, None); + test_sniff_classification("text_html_b_3e_u.html", mime::TEXT_HTML, None); } #[test] fn test_sniff_text_html_body_20() { - test_sniff_classification("text_html_body_20.html", "text", "html", None); - test_sniff_classification("text_html_body_20_u.html", "text", "html", None); + test_sniff_classification("text_html_body_20.html", mime::TEXT_HTML, None); + test_sniff_classification("text_html_body_20_u.html", mime::TEXT_HTML, None); } #[test] fn test_sniff_text_html_body_3e() { - test_sniff_classification("text_html_body_3e.html", "text", "html", None); - test_sniff_classification("text_html_body_3e_u.html", "text", "html", None); + test_sniff_classification("text_html_body_3e.html", mime::TEXT_HTML, None); + test_sniff_classification("text_html_body_3e_u.html", mime::TEXT_HTML, None); } #[test] fn test_sniff_text_html_br_20() { - test_sniff_classification("text_html_br_20.html", "text", "html", None); - test_sniff_classification("text_html_br_20_u.html", "text", "html", None); + test_sniff_classification("text_html_br_20.html", mime::TEXT_HTML, None); + test_sniff_classification("text_html_br_20_u.html", mime::TEXT_HTML, None); } #[test] fn test_sniff_text_html_br_3e() { - test_sniff_classification("text_html_br_3e.html", "text", "html", None); - test_sniff_classification("text_html_br_3e_u.html", "text", "html", None); + test_sniff_classification("text_html_br_3e.html", mime::TEXT_HTML, None); + test_sniff_classification("text_html_br_3e_u.html", mime::TEXT_HTML, None); } #[test] fn test_sniff_text_html_p_20() { - test_sniff_classification("text_html_p_20.html", "text", "html", None); - test_sniff_classification("text_html_p_20_u.html", "text", "html", None); + test_sniff_classification("text_html_p_20.html", mime::TEXT_HTML, None); + test_sniff_classification("text_html_p_20_u.html", mime::TEXT_HTML, None); } #[test] fn test_sniff_text_html_p_3e() { - test_sniff_classification("text_html_p_3e.html", "text", "html", None); - test_sniff_classification("text_html_p_3e_u.html", "text", "html", None); + test_sniff_classification("text_html_p_3e.html", mime::TEXT_HTML, None); + test_sniff_classification("text_html_p_3e_u.html", mime::TEXT_HTML, None); } #[test] fn test_sniff_text_html_comment_20() { - test_sniff_classification("text_html_comment_20.html", "text", "html", None); + test_sniff_classification("text_html_comment_20.html", mime::TEXT_HTML, None); } #[test] fn test_sniff_text_html_comment_3e() { - test_sniff_classification("text_html_comment_3e.html", "text", "html", None); + test_sniff_classification("text_html_comment_3e.html", mime::TEXT_HTML, None); } #[test] fn test_sniff_xml() { - test_sniff_classification("test.xml", "text", "xml", None); + test_sniff_classification("test.xml", mime::TEXT_XML, None); } #[test] fn test_sniff_pdf() { - test_sniff_classification("test.pdf", "application", "pdf", None); + test_sniff_classification("test.pdf", mime::APPLICATION_PDF, None); } #[test] fn test_sniff_postscript() { - test_sniff_classification("test.ps", "application", "postscript", None); + test_sniff_classification("test.ps", "application/postscript".parse().unwrap(), None); } #[test] fn test_sniff_utf_16be_bom() { - test_sniff_classification("utf16bebom.txt", "text", "plain", None); + test_sniff_classification("utf16bebom.txt", mime::TEXT_PLAIN, None); } #[test] fn test_sniff_utf_16le_bom() { - test_sniff_classification("utf16lebom.txt", "text", "plain", None); + test_sniff_classification("utf16lebom.txt", mime::TEXT_PLAIN, None); } #[test] fn test_sniff_utf_8_bom() { - test_sniff_classification("utf8bom.txt", "text", "plain", None); + test_sniff_classification("utf8bom.txt", mime::TEXT_PLAIN, None); } #[test] fn test_sniff_rss_feed() { // RSS feeds - test_sniff_full(&PathBuf::from("text/xml/feed.rss"), "application", "rss+xml", Some(("text", "html"))); - test_sniff_full(&PathBuf::from("text/xml/rdf_rss.xml"), "application", "rss+xml", Some(("text", "html"))); + test_sniff_full(&PathBuf::from("text/xml/feed.rss"), "application/rss+xml".parse().unwrap(), Some(mime::TEXT_HTML)); + test_sniff_full(&PathBuf::from("text/xml/rdf_rss.xml"), "application/rss+xml".parse().unwrap(), + Some(mime::TEXT_HTML)); // Not RSS feeds - test_sniff_full(&PathBuf::from("text/xml/rdf_rss_ko_1.xml"), "text", "html", Some(("text", "html"))); - test_sniff_full(&PathBuf::from("text/xml/rdf_rss_ko_2.xml"), "text", "html", Some(("text", "html"))); - test_sniff_full(&PathBuf::from("text/xml/rdf_rss_ko_3.xml"), "text", "html", Some(("text", "html"))); - test_sniff_full(&PathBuf::from("text/xml/rdf_rss_ko_4.xml"), "text", "html", Some(("text", "html"))); + test_sniff_full(&PathBuf::from("text/xml/rdf_rss_ko_1.xml"), mime::TEXT_HTML, Some(mime::TEXT_HTML)); + test_sniff_full(&PathBuf::from("text/xml/rdf_rss_ko_2.xml"), mime::TEXT_HTML, Some(mime::TEXT_HTML)); + test_sniff_full(&PathBuf::from("text/xml/rdf_rss_ko_3.xml"), mime::TEXT_HTML, Some(mime::TEXT_HTML)); + test_sniff_full(&PathBuf::from("text/xml/rdf_rss_ko_4.xml"), mime::TEXT_HTML, Some(mime::TEXT_HTML)); } #[test] fn test_sniff_atom_feed() { - test_sniff_full(&PathBuf::from("text/xml/feed.atom"), "application", "atom+xml", Some(("text", "html"))); + test_sniff_full(&PathBuf::from("text/xml/feed.atom"), "application/atom+xml".parse().unwrap(), + Some(mime::TEXT_HTML)); } #[test] fn test_sniff_binary_file() { - test_sniff_full(&PathBuf::from("unknown/binary_file"), "application", "octet-stream", None); + test_sniff_full(&PathBuf::from("unknown/binary_file"), mime::APPLICATION_OCTET_STREAM, None); } #[test] fn test_sniff_atom_feed_with_no_sniff_flag_on() { test_sniff_with_flags(&PathBuf::from("text/xml/feed.atom"), - "text", - "html", - Some(("text", "html")), + mime::TEXT_HTML, + Some(mime::TEXT_HTML), NoSniffFlag::On, ApacheBugFlag::Off); } @@ -502,9 +499,8 @@ fn test_sniff_atom_feed_with_no_sniff_flag_on() { #[test] fn test_sniff_with_no_sniff_flag_on_and_apache_flag_on() { test_sniff_with_flags(&PathBuf::from("text/xml/feed.atom"), - "text", - "html", - Some(("text", "html")), + mime::TEXT_HTML, + Some(mime::TEXT_HTML), NoSniffFlag::On, ApacheBugFlag::On); } @@ -512,9 +508,8 @@ fn test_sniff_with_no_sniff_flag_on_and_apache_flag_on() { #[test] fn test_sniff_utf_8_bom_with_apache_flag_on() { test_sniff_with_flags(&PathBuf::from("text/plain/utf8bom.txt"), - "text", - "plain", - Some(("dummy", "text")), + mime::TEXT_PLAIN, + Some("dummy/text".parse().unwrap()), NoSniffFlag::Off, ApacheBugFlag::On); } @@ -522,9 +517,8 @@ fn test_sniff_utf_8_bom_with_apache_flag_on() { #[test] fn test_sniff_utf_16be_bom_with_apache_flag_on() { test_sniff_with_flags(&PathBuf::from("text/plain/utf16bebom.txt"), - "text", - "plain", - Some(("dummy", "text")), + mime::TEXT_PLAIN, + Some("dummy/text".parse().unwrap()), NoSniffFlag::Off, ApacheBugFlag::On); } @@ -532,9 +526,8 @@ fn test_sniff_utf_16be_bom_with_apache_flag_on() { #[test] fn test_sniff_utf_16le_bom_with_apache_flag_on() { test_sniff_with_flags(&PathBuf::from("text/plain/utf16lebom.txt"), - "text", - "plain", - Some(("dummy", "text")), + mime::TEXT_PLAIN, + Some("dummy/text".parse().unwrap()), NoSniffFlag::Off, ApacheBugFlag::On); } @@ -542,9 +535,8 @@ fn test_sniff_utf_16le_bom_with_apache_flag_on() { #[test] fn test_sniff_octet_stream_apache_flag_on() { test_sniff_with_flags(&PathBuf::from("unknown/binary_file"), - "application", - "octet-stream", - Some(("dummy", "binary")), + mime::APPLICATION_OCTET_STREAM, + Some("dummy/binary".parse().unwrap()), NoSniffFlag::Off, ApacheBugFlag::On); } @@ -552,9 +544,8 @@ fn test_sniff_octet_stream_apache_flag_on() { #[test] fn test_sniff_mp4_video_apache_flag_on() { test_sniff_with_flags(&PathBuf::from("video/mp4/test.mp4"), - "application", - "octet-stream", - Some(("video", "mp4")), + mime::APPLICATION_OCTET_STREAM, + Some("video/mp4".parse().unwrap()), NoSniffFlag::Off, ApacheBugFlag::On); } diff --git a/components/net/websocket_loader.rs b/components/net/websocket_loader.rs index 5a676c904ad..8440e0b3c63 100644 --- a/components/net/websocket_loader.rs +++ b/components/net/websocket_loader.rs @@ -2,13 +2,15 @@ * 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/. */ -use connector::create_ssl_connector; +use connector::create_ssl_connector_builder; use cookie::Cookie; use embedder_traits::resources::{self, Resource}; use fetch::methods::should_be_blocked_due_to_bad_port; +use headers_ext::Host; use hosts::replace_host; +use http::header::{self, HeaderMap, HeaderName, HeaderValue}; +use http::uri::Authority; use http_loader::HttpState; -use hyper::header::{Headers, Host, SetCookie}; use ipc_channel::ipc::{IpcReceiver, IpcSender}; use net_traits::{CookieSource, MessageData}; use net_traits::{WebSocketDomAction, WebSocketNetworkEvent}; @@ -69,17 +71,19 @@ impl<'a> Handler for Client<'a> { } fn on_open(&mut self, shake: Handshake) -> WebSocketResult<()> { - let mut headers = Headers::new(); + let mut headers = HeaderMap::new(); for &(ref name, ref value) in shake.response.headers().iter() { - headers.set_raw(name.clone(), vec![value.clone()]); + let name = HeaderName::from_bytes(name.as_bytes()).unwrap(); + let value = HeaderValue::from_bytes(&value).unwrap(); + + headers.insert(name, value); } - if let Some(cookies) = headers.get::<SetCookie>() { - let mut jar = self.http_state.cookie_jar.write().unwrap(); - for cookie in &**cookies { - if let Some(cookie) = - Cookie::from_cookie_string(cookie.clone(), self.resource_url, CookieSource::HTTP) - { + let mut jar = self.http_state.cookie_jar.write().unwrap(); + // TODO(eijebong): Replace thise once typed headers settled on a cookie impl + for cookie in headers.get_all(header::SET_COOKIE) { + if let Ok(s) = cookie.to_str() { + if let Some(cookie) = Cookie::from_cookie_string(s.into(), self.resource_url, CookieSource::HTTP) { jar.push(cookie, self.resource_url, CookieSource::HTTP); } } @@ -144,7 +148,7 @@ impl<'a> Handler for Client<'a> { WebSocketErrorKind::Protocol, format!("Unable to parse domain from {}. Needed for SSL.", url), ))?; - let connector = create_ssl_connector(&certs); + let connector = create_ssl_connector_builder(&certs).build(); connector.connect(domain, stream).map_err(WebSocketError::from) } @@ -180,10 +184,11 @@ pub fn init( let mut net_url = req_init.url.clone().into_url(); net_url.set_host(Some(&host)).unwrap(); - let host = Host { - hostname: req_init.url.host_str().unwrap().to_owned(), - port: req_init.url.port_or_known_default(), - }; + let host = Host::from( + format!("{}{}", req_init.url.host_str().unwrap(), + req_init.url.port_or_known_default().map(|v| format!(":{}", v)).unwrap_or("".into()) + ).parse::<Authority>().unwrap() + ); let client = Client { origin: &req_init.origin.ascii_serialization(), |