diff options
Diffstat (limited to 'components')
63 files changed, 2477 insertions, 2549 deletions
diff --git a/components/constellation/Cargo.toml b/components/constellation/Cargo.toml index 7c1ee6b99af..a036f0036d2 100644 --- a/components/constellation/Cargo.toml +++ b/components/constellation/Cargo.toml @@ -22,7 +22,7 @@ euclid = "0.19" embedder_traits = { path = "../embedder_traits" } gfx = {path = "../gfx"} gfx_traits = {path = "../gfx_traits"} -hyper = "0.10" +http = "0.1" ipc-channel = "0.11" layout_traits = {path = "../layout_traits"} keyboard-types = {version = "0.4.2-servo", features = ["serde"]} diff --git a/components/constellation/lib.rs b/components/constellation/lib.rs index f5e23ba3c09..57b59960eb3 100644 --- a/components/constellation/lib.rs +++ b/components/constellation/lib.rs @@ -20,7 +20,7 @@ extern crate euclid; extern crate gaol; extern crate gfx; extern crate gfx_traits; -extern crate hyper; +extern crate http; extern crate ipc_channel; extern crate keyboard_types; extern crate layout_traits; diff --git a/components/constellation/network_listener.rs b/components/constellation/network_listener.rs index 3f58d029acf..457a2021ced 100644 --- a/components/constellation/network_listener.rs +++ b/components/constellation/network_listener.rs @@ -6,7 +6,7 @@ //! Any redirects that are encountered are followed. Whenever a non-redirect //! response is received, it is forwarded to the appropriate script thread. -use hyper::header::Location; +use http::header::LOCATION; use ipc_channel::ipc; use ipc_channel::router::ROUTER; use msg::constellation_msg::PipelineId; @@ -99,7 +99,7 @@ impl NetworkListener { }; match metadata.headers { - Some(ref headers) if headers.has::<Location>() => { + Some(ref headers) if headers.contains_key(LOCATION) => { if self.req_init.url_list.is_empty() { self.req_init.url_list.push(self.req_init.url.clone()); } diff --git a/components/devtools/Cargo.toml b/components/devtools/Cargo.toml index 1bcbc1290b2..ba0b4d172c6 100644 --- a/components/devtools/Cargo.toml +++ b/components/devtools/Cargo.toml @@ -11,8 +11,11 @@ path = "lib.rs" [dependencies] devtools_traits = {path = "../devtools_traits"} -hyper = "0.10" -hyper_serde = "0.8" +headers-core = "0.0.1" +headers-ext = "0.0.3" +http = "0.1" +hyper = "0.12" +hyper_serde = "0.9" ipc-channel = "0.11" log = "0.4" msg = {path = "../msg"} diff --git a/components/devtools/actors/network_event.rs b/components/devtools/actors/network_event.rs index 5e4fe01bcf3..8bc3cf9850a 100644 --- a/components/devtools/actors/network_event.rs +++ b/components/devtools/actors/network_event.rs @@ -9,13 +9,12 @@ use actor::{Actor, ActorMessageStatus, ActorRegistry}; use devtools_traits::HttpRequest as DevtoolsHttpRequest; use devtools_traits::HttpResponse as DevtoolsHttpResponse; -use hyper::header::{ContentType, Cookie}; -use hyper::header::Headers; -use hyper::http::RawStatus; -use hyper::method::Method; +use headers_core::HeaderMapExt; +use headers_ext::{ContentType, Cookie}; +use http::{header, HeaderMap}; +use hyper::{Method, StatusCode}; use protocol::JsonPacketStream; use serde_json::{Map, Value}; -use std::borrow::Cow; use std::net::TcpStream; use time; use time::Tm; @@ -23,7 +22,7 @@ use time::Tm; struct HttpRequest { url: String, method: Method, - headers: Headers, + headers: HeaderMap, body: Option<Vec<u8>>, startedDateTime: Tm, timeStamp: i64, @@ -32,9 +31,9 @@ struct HttpRequest { } struct HttpResponse { - headers: Option<Headers>, - status: Option<RawStatus>, - body: Option<Vec<u8>>, + headers: Option<HeaderMap>, + status: Option<(StatusCode, String)>, + body: Option<Vec<u8>> } pub struct NetworkEventActor { @@ -189,15 +188,11 @@ impl Actor for NetworkEventActor { let mut headers = Vec::new(); let mut rawHeadersString = "".to_owned(); let mut headersSize = 0; - for item in self.request.headers.iter() { - let name = item.name(); - let value = item.value_string(); - rawHeadersString = rawHeadersString + name + ":" + &value + "\r\n"; - headersSize += name.len() + value.len(); - headers.push(Header { - name: name.to_owned(), - value: value.to_owned(), - }); + for (name, value) in self.request.headers.iter() { + let value = &value.to_str().unwrap().to_string(); + rawHeadersString = rawHeadersString + name.as_str() + ":" + &value + "\r\n"; + headersSize += name.as_str().len() + value.len(); + headers.push(Header { name: name.as_str().to_owned(), value: value.to_owned() }); } let msg = GetRequestHeadersReply { from: self.name(), @@ -210,11 +205,10 @@ impl Actor for NetworkEventActor { }, "getRequestCookies" => { let mut cookies = Vec::new(); - if let Some(req_cookies) = self.request.headers.get_raw("Cookie") { - for cookie in &*req_cookies { - if let Ok(cookie_value) = String::from_utf8(cookie.clone()) { - cookies = cookie_value.into_bytes(); - } + + for cookie in self.request.headers.get_all(header::COOKIE) { + if let Ok(cookie_value) = String::from_utf8(cookie.as_bytes().to_vec()) { + cookies = cookie_value.into_bytes(); } } @@ -239,17 +233,15 @@ impl Actor for NetworkEventActor { let mut headers = vec![]; let mut rawHeadersString = "".to_owned(); let mut headersSize = 0; - for item in response_headers.iter() { - let name = item.name(); - let value = item.value_string(); + for (name, value) in response_headers.iter() { headers.push(Header { - name: name.to_owned(), - value: value.clone(), + name: name.as_str().to_owned(), + value: value.to_str().unwrap().to_owned(), }); - headersSize += name.len() + value.len(); - rawHeadersString.push_str(name); + headersSize += name.as_str().len() + value.len(); + rawHeadersString.push_str(name.as_str()); rawHeadersString.push_str(":"); - rawHeadersString.push_str(&value); + rawHeadersString.push_str(value.to_str().unwrap()); rawHeadersString.push_str("\r\n"); } let msg = GetResponseHeadersReply { @@ -264,11 +256,10 @@ impl Actor for NetworkEventActor { }, "getResponseCookies" => { let mut cookies = Vec::new(); - if let Some(res_cookies) = self.request.headers.get_raw("set-cookie") { - for cookie in &*res_cookies { - if let Ok(cookie_value) = String::from_utf8(cookie.clone()) { - cookies = cookie_value.into_bytes(); - } + // TODO: This seems quite broken + for cookie in self.request.headers.get_all(header::SET_COOKIE) { + if let Ok(cookie_value) = String::from_utf8(cookie.as_bytes().to_vec()) { + cookies = cookie_value.into_bytes(); } } @@ -330,8 +321,8 @@ impl NetworkEventActor { name: name, request: HttpRequest { url: String::new(), - method: Method::Get, - headers: Headers::new(), + method: Method::GET, + headers: HeaderMap::new(), body: None, startedDateTime: time::now(), timeStamp: time::get_time().sec, @@ -363,7 +354,7 @@ impl NetworkEventActor { self.response.headers = response.headers.clone(); self.response.status = response.status.as_ref().map(|&(s, ref st)| { let status_text = String::from_utf8_lossy(st).into_owned(); - RawStatus(s, Cow::from(status_text)) + (StatusCode::from_u16(s).unwrap(), status_text) }); self.response.body = response.body.clone(); } @@ -385,13 +376,8 @@ impl NetworkEventActor { // TODO: Send the correct values for all these fields. let hSizeOption = self.response.headers.as_ref().map(|headers| headers.len()); let hSize = hSizeOption.unwrap_or(0); - let (status_code, status_message) = self - .response - .status - .as_ref() - .map_or((0, "".to_owned()), |&RawStatus(ref code, ref text)| { - (*code, text.clone().into_owned()) - }); + let (status_code, status_message) = self.response.status.as_ref() + .map_or((0, "".to_owned()), |(code, text)| (code.as_u16(), text.clone())); // TODO: Send the correct values for remoteAddress and remotePort and http_version. ResponseStartMsg { httpVersion: "HTTP/1.1".to_owned(), @@ -407,9 +393,9 @@ impl NetworkEventActor { pub fn response_content(&self) -> ResponseContentMsg { let mut mString = "".to_owned(); if let Some(ref headers) = self.response.headers { - mString = match headers.get() { - Some(&ContentType(ref mime)) => mime.to_string(), - None => "".to_owned(), + mString = match headers.typed_get::<ContentType>() { + Some(ct) => ct.to_string(), + _ => "".to_owned() }; } // TODO: Set correct values when response's body is sent to the devtools in http_loader. @@ -424,9 +410,9 @@ impl NetworkEventActor { pub fn response_cookies(&self) -> ResponseCookiesMsg { let mut cookies_size = 0; if let Some(ref headers) = self.response.headers { - cookies_size = match headers.get() { - Some(&Cookie(ref cookie)) => cookie.len(), - None => 0, + cookies_size = match headers.typed_get::<Cookie>() { + Some(ref cookie) => cookie.len(), + _ => 0, }; } ResponseCookiesMsg { @@ -439,8 +425,8 @@ impl NetworkEventActor { let mut headers_byte_count = 0; if let Some(ref headers) = self.response.headers { headers_size = headers.len(); - for item in headers.iter() { - headers_byte_count += item.name().len() + item.value_string().len(); + for (name, value) in headers.iter() { + headers_byte_count += name.as_str().len() + value.len(); } } ResponseHeadersMsg { @@ -450,11 +436,10 @@ impl NetworkEventActor { } pub fn request_headers(&self) -> RequestHeadersMsg { - let size = self - .request - .headers - .iter() - .fold(0, |acc, h| acc + h.name().len() + h.value_string().len()); + let size = self.request + .headers + .iter() + .fold(0, |acc, (name, value)| acc + name.as_str().len() + value.len()); RequestHeadersMsg { headers: self.request.headers.len(), headersSize: size, @@ -462,9 +447,9 @@ impl NetworkEventActor { } pub fn request_cookies(&self) -> RequestCookiesMsg { - let cookies_size = match self.request.headers.get() { - Some(&Cookie(ref cookie)) => cookie.len(), - None => 0, + let cookies_size = match self.request.headers.typed_get::<Cookie>() { + Some(ref cookie) => cookie.len(), + _ => 0 }; RequestCookiesMsg { cookies: cookies_size, diff --git a/components/devtools/lib.rs b/components/devtools/lib.rs index 547f96bc445..9541d553de3 100644 --- a/components/devtools/lib.rs +++ b/components/devtools/lib.rs @@ -13,6 +13,9 @@ #![deny(unsafe_code)] extern crate devtools_traits; +extern crate headers_core; +extern crate headers_ext; +extern crate http; extern crate hyper; extern crate ipc_channel; #[macro_use] diff --git a/components/devtools_traits/Cargo.toml b/components/devtools_traits/Cargo.toml index 34359dadf41..26769dfba3d 100644 --- a/components/devtools_traits/Cargo.toml +++ b/components/devtools_traits/Cargo.toml @@ -11,8 +11,7 @@ path = "lib.rs" [dependencies] bitflags = "1.0" -hyper = "0.10" -hyper_serde = "0.8" +http = "0.1" ipc-channel = "0.11" malloc_size_of = { path = "../malloc_size_of" } malloc_size_of_derive = { path = "../malloc_size_of_derive" } diff --git a/components/devtools_traits/lib.rs b/components/devtools_traits/lib.rs index 764af5da0a2..9d95adb3d26 100644 --- a/components/devtools_traits/lib.rs +++ b/components/devtools_traits/lib.rs @@ -13,7 +13,7 @@ #[macro_use] extern crate bitflags; -extern crate hyper; +extern crate http; extern crate ipc_channel; extern crate malloc_size_of; #[macro_use] @@ -24,8 +24,8 @@ extern crate serde; extern crate servo_url; extern crate time; -use hyper::header::Headers; -use hyper::method::Method; +use http::HeaderMap; +use http::method::Method; use ipc_channel::ipc::IpcSender; use msg::constellation_msg::PipelineId; use servo_url::ServoUrl; @@ -301,7 +301,7 @@ pub enum CachedConsoleMessage { pub struct HttpRequest { pub url: ServoUrl, pub method: Method, - pub headers: Headers, + pub headers: HeaderMap, pub body: Option<Vec<u8>>, pub pipeline_id: PipelineId, pub startedDateTime: Tm, @@ -313,7 +313,7 @@ pub struct HttpRequest { #[derive(Debug, PartialEq)] pub struct HttpResponse { - pub headers: Option<Headers>, + pub headers: Option<HeaderMap>, pub status: Option<(u16, Vec<u8>)>, pub body: Option<Vec<u8>>, pub pipeline_id: PipelineId, diff --git a/components/malloc_size_of/Cargo.toml b/components/malloc_size_of/Cargo.toml index 04df21fd628..fff432e3fbc 100644 --- a/components/malloc_size_of/Cargo.toml +++ b/components/malloc_size_of/Cargo.toml @@ -29,8 +29,8 @@ app_units = "0.7" cssparser = "0.24.0" euclid = "0.19" hashglobe = { path = "../hashglobe" } -hyper = { version = "0.10", optional = true } -hyper_serde = { version = "0.8", optional = true } +hyper = { version = "0.12", optional = true } +hyper_serde = { version = "0.9", optional = true } keyboard-types = {version = "0.4.2-servo", features = ["serde"], optional = true} mozjs = { version = "0.9.3", optional = true } selectors = { path = "../selectors" } diff --git a/components/malloc_size_of/lib.rs b/components/malloc_size_of/lib.rs index 52b8412f64e..e0f638a843d 100644 --- a/components/malloc_size_of/lib.rs +++ b/components/malloc_size_of/lib.rs @@ -978,48 +978,6 @@ impl MallocSizeOf for xml5ever::QualName { } #[cfg(feature = "servo")] -impl MallocSizeOf for hyper::header::Headers { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - self.iter().fold(0, |acc, x| { - let name = x.name(); - let raw = self.get_raw(name); - acc + raw.size_of(ops) - }) - } -} - -#[cfg(feature = "servo")] -impl MallocSizeOf for hyper::header::ContentType { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - self.0.size_of(ops) - } -} - -#[cfg(feature = "servo")] -impl MallocSizeOf for hyper::mime::Mime { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - self.0.size_of(ops) + self.1.size_of(ops) + self.2.size_of(ops) - } -} - -#[cfg(feature = "servo")] -impl MallocSizeOf for hyper::mime::Attr { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - match *self { - hyper::mime::Attr::Ext(ref s) => s.size_of(ops), - _ => 0, - } - } -} - -#[cfg(feature = "servo")] -impl MallocSizeOf for hyper::mime::Value { - fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize { - self.len() // Length of string value in bytes (not the char length of a string)! - } -} - -#[cfg(feature = "servo")] malloc_size_of_is_0!(time::Duration); #[cfg(feature = "servo")] malloc_size_of_is_0!(time::Tm); @@ -1046,12 +1004,9 @@ impl<T> MallocSizeOf for servo_channel::Sender<T> { } #[cfg(feature = "servo")] -impl MallocSizeOf for hyper::status::StatusCode { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - match *self { - hyper::status::StatusCode::Unregistered(u) => u.size_of(ops), - _ => 0, - } +impl MallocSizeOf for hyper::StatusCode { + fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize { + 0 } } 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(), diff --git a/components/net_traits/Cargo.toml b/components/net_traits/Cargo.toml index f2046b9cfeb..4d89992ff11 100644 --- a/components/net_traits/Cargo.toml +++ b/components/net_traits/Cargo.toml @@ -12,16 +12,20 @@ test = false doctest = false [dependencies] -cookie = "0.10" +cookie = "0.11" embedder_traits = { path = "../embedder_traits" } -hyper = "0.10" -hyper_serde = "0.8" +headers-core = "0.0.1" +headers-ext = "0.0.3" +http = "0.1" +hyper = "0.12" +hyper_serde = "0.9" image = "0.19" ipc-channel = "0.11" lazy_static = "1" log = "0.4" malloc_size_of = { path = "../malloc_size_of" } malloc_size_of_derive = { path = "../malloc_size_of_derive" } +mime = "0.3" msg = {path = "../msg"} num-traits = "0.2" pixels = {path = "../pixels"} diff --git a/components/net_traits/lib.rs b/components/net_traits/lib.rs index 85862b99a11..5a6a1364fbf 100644 --- a/components/net_traits/lib.rs +++ b/components/net_traits/lib.rs @@ -7,6 +7,9 @@ extern crate cookie as cookie_rs; extern crate embedder_traits; +extern crate headers_core; +extern crate headers_ext; +extern crate http; extern crate hyper; extern crate hyper_serde; extern crate image as piston_image; @@ -15,38 +18,43 @@ extern crate ipc_channel; #[macro_use] extern crate log; #[macro_use] extern crate malloc_size_of; #[macro_use] extern crate malloc_size_of_derive; +extern crate mime; extern crate msg; extern crate num_traits; extern crate pixels; #[macro_use] extern crate serde; extern crate servo_arc; extern crate servo_url; -extern crate url; +#[macro_use] extern crate url; extern crate uuid; extern crate webrender_api; use cookie_rs::Cookie; use filemanager_thread::FileManagerThreadMsg; +use headers_core::HeaderMapExt; +use headers_ext::{ContentType, ReferrerPolicy as ReferrerPolicyHeader}; +use http::{Error as HttpError, HeaderMap}; use hyper::Error as HyperError; -use hyper::header::{ContentType, Headers, ReferrerPolicy as ReferrerPolicyHeader}; -use hyper::http::RawStatus; -use hyper::mime::{Attr, Mime}; +use hyper::StatusCode; use hyper_serde::Serde; use ipc_channel::Error as IpcError; use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; use ipc_channel::router::ROUTER; +use mime::Mime; use msg::constellation_msg::HistoryStateId; use request::{Request, RequestInit}; use response::{HttpsState, Response, ResponseInit}; use servo_url::ServoUrl; use std::error::Error; use storage_thread::StorageThreadMsg; +use url::percent_encoding; pub mod blob_url_store; pub mod filemanager_thread; pub mod image_cache; pub mod net_error_list; pub mod pub_domains; +pub mod quality; pub mod request; pub mod response; pub mod storage_thread; @@ -80,16 +88,16 @@ pub struct CustomResponse { #[ignore_malloc_size_of = "Defined in hyper"] #[serde(deserialize_with = "::hyper_serde::deserialize", serialize_with = "::hyper_serde::serialize")] - pub headers: Headers, + pub headers: HeaderMap, #[ignore_malloc_size_of = "Defined in hyper"] #[serde(deserialize_with = "::hyper_serde::deserialize", serialize_with = "::hyper_serde::serialize")] - pub raw_status: RawStatus, + pub raw_status: (StatusCode, String), pub body: Vec<u8>, } impl CustomResponse { - pub fn new(headers: Headers, raw_status: RawStatus, body: Vec<u8>) -> CustomResponse { + pub fn new(headers: HeaderMap, raw_status: (StatusCode, String), body: Vec<u8>) -> CustomResponse { CustomResponse { headers: headers, raw_status: raw_status, @@ -126,24 +134,24 @@ pub enum ReferrerPolicy { StrictOriginWhenCrossOrigin, } -impl<'a> From<&'a ReferrerPolicyHeader> for ReferrerPolicy { - fn from(policy: &'a ReferrerPolicyHeader) -> Self { - match *policy { - ReferrerPolicyHeader::NoReferrer => +impl From<ReferrerPolicyHeader> for ReferrerPolicy { + fn from(policy: ReferrerPolicyHeader) -> Self { + match policy { + ReferrerPolicyHeader::NO_REFERRER => ReferrerPolicy::NoReferrer, - ReferrerPolicyHeader::NoReferrerWhenDowngrade => + ReferrerPolicyHeader::NO_REFERRER_WHEN_DOWNGRADE => ReferrerPolicy::NoReferrerWhenDowngrade, - ReferrerPolicyHeader::SameOrigin => + ReferrerPolicyHeader::SAME_ORIGIN => ReferrerPolicy::SameOrigin, - ReferrerPolicyHeader::Origin => + ReferrerPolicyHeader::ORIGIN => ReferrerPolicy::Origin, - ReferrerPolicyHeader::OriginWhenCrossOrigin => + ReferrerPolicyHeader::ORIGIN_WHEN_CROSS_ORIGIN => ReferrerPolicy::OriginWhenCrossOrigin, - ReferrerPolicyHeader::UnsafeUrl => + ReferrerPolicyHeader::UNSAFE_URL => ReferrerPolicy::UnsafeUrl, - ReferrerPolicyHeader::StrictOrigin => + ReferrerPolicyHeader::STRICT_ORIGIN => ReferrerPolicy::StrictOrigin, - ReferrerPolicyHeader::StrictOriginWhenCrossOrigin => + ReferrerPolicyHeader::STRICT_ORIGIN_WHEN_CROSS_ORIGIN => ReferrerPolicy::StrictOriginWhenCrossOrigin, } } @@ -419,7 +427,7 @@ pub struct Metadata { #[ignore_malloc_size_of = "Defined in hyper"] /// Headers - pub headers: Option<Serde<Headers>>, + pub headers: Option<Serde<HeaderMap>>, /// HTTP Status pub status: Option<(u16, Vec<u8>)>, @@ -454,16 +462,15 @@ impl Metadata { /// Extract the parts of a Mime that we care about. pub fn set_content_type(&mut self, content_type: Option<&Mime>) { if self.headers.is_none() { - self.headers = Some(Serde(Headers::new())); + self.headers = Some(Serde(HeaderMap::new())); } if let Some(mime) = content_type { - self.headers.as_mut().unwrap().set(ContentType(mime.clone())); - self.content_type = Some(Serde(ContentType(mime.clone()))); - let Mime(_, _, ref parameters) = *mime; - for &(ref k, ref v) in parameters { - if Attr::Charset == *k { - self.charset = Some(v.to_string()); + self.headers.as_mut().unwrap().typed_insert(ContentType::from(mime.clone())); + self.content_type = Some(Serde(ContentType::from(mime.clone()))); + for (name, value) in mime.params() { + if mime::CHARSET == name { + self.charset = Some(value.to_string()); } } } @@ -518,18 +525,16 @@ pub enum NetworkError { } impl NetworkError { - pub fn from_hyper_error(url: &ServoUrl, error: HyperError) -> Self { - if let HyperError::Ssl(ref ssl_error) = error { - return NetworkError::from_ssl_error(url, &**ssl_error); - } + pub fn from_hyper_error(error: &HyperError) -> Self { NetworkError::Internal(error.description().to_owned()) } - pub fn from_ssl_error(url: &ServoUrl, error: &Error) -> Self { - NetworkError::SslValidation(url.clone(), error.description().to_owned()) + pub fn from_http_error(error: &HttpError) -> Self { + NetworkError::Internal(error.description().to_owned()) } } + /// Normalize `slice`, as defined by /// [the Fetch Spec](https://fetch.spec.whatwg.org/#concept-header-value-normalize). pub fn trim_http_whitespace(mut slice: &[u8]) -> &[u8] { @@ -551,3 +556,17 @@ pub fn trim_http_whitespace(mut slice: &[u8]) -> &[u8] { slice } + +pub fn http_percent_encode(bytes: &[u8]) -> String { + define_encode_set! { + // This encode set is used for HTTP header values and is defined at + // https://tools.ietf.org/html/rfc5987#section-3.2 + pub HTTP_VALUE = [percent_encoding::SIMPLE_ENCODE_SET] | { + ' ', '"', '%', '\'', '(', ')', '*', ',', '/', ':', ';', '<', '-', '>', '?', + '[', '\\', ']', '{', '}' + } + } + + url::percent_encoding::percent_encode(bytes, HTTP_VALUE).to_string() +} + diff --git a/components/net_traits/quality.rs b/components/net_traits/quality.rs new file mode 100644 index 00000000000..72fed2304ca --- /dev/null +++ b/components/net_traits/quality.rs @@ -0,0 +1,80 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//TODO(eijebong): Remove this once typed headers figure out quality +// This is copy pasted from the old hyper headers to avoid hardcoding everything +// (I would probably also make some silly mistakes while migrating...) + +use http::header::HeaderValue; +use mime::Mime; +use std::{fmt, str}; + +/// A quality value, as specified in [RFC7231]. +/// +/// Quality values are decimal numbers between 0 and 1 (inclusive) with up to 3 fractional digits of precision. +/// +/// [RFC7231]: https://tools.ietf.org/html/rfc7231#section-5.3.1 +#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] +pub struct Quality(u16); + +impl Quality { + /// Creates a quality value from a value between 0 and 1000 inclusive. + /// + /// This is semantically divided by 1000 to produce a value between 0 and 1. + /// + /// # Panics + /// + /// Panics if the value is greater than 1000. + pub fn from_u16(quality: u16) -> Quality { + assert!(quality <= 1000); + Quality(quality) + } +} + +/// A value paired with its "quality" as defined in [RFC7231]. +/// +/// Quality items are used in content negotiation headers such as `Accept` and `Accept-Encoding`. +/// +/// [RFC7231]: https://tools.ietf.org/html/rfc7231#section-5.3 +#[derive(Clone, Debug, PartialEq)] +pub struct QualityItem<T> { + pub item: T, + pub quality: Quality, +} + +impl<T> QualityItem<T> { + /// Creates a new quality item. + pub fn new(item: T, quality: Quality) -> QualityItem<T> { + QualityItem { item, quality } + } +} + +impl<T> fmt::Display for QualityItem<T> +where + T: fmt::Display, +{ + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&self.item, fmt)?; + match self.quality.0 { + 1000 => Ok(()), + 0 => fmt.write_str("; q=0"), + mut x => { + fmt.write_str("; q=0.")?; + let mut digits = *b"000"; + digits[2] = (x % 10) as u8 + b'0'; + x /= 10; + digits[1] = (x % 10) as u8 + b'0'; + x /= 10; + digits[0] = (x % 10) as u8 + b'0'; + + let s = str::from_utf8(&digits[..]).unwrap(); + fmt.write_str(s.trim_right_matches('0')) + } + } + } +} + +pub fn quality_to_value(q: Vec<QualityItem<Mime>>) -> HeaderValue { + HeaderValue::from_str(&q.iter().map(|q| q.to_string()).collect::<Vec<String>>().join(", ")).unwrap() +} diff --git a/components/net_traits/request.rs b/components/net_traits/request.rs index 822a39ee324..4113037a672 100644 --- a/components/net_traits/request.rs +++ b/components/net_traits/request.rs @@ -3,8 +3,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use ReferrerPolicy; -use hyper::header::Headers; -use hyper::method::Method; +use http::HeaderMap; +use hyper::Method; use msg::constellation_msg::PipelineId; use servo_url::{ImmutableOrigin, ServoUrl}; use std::default::Default; @@ -145,7 +145,7 @@ pub struct RequestInit { #[serde(deserialize_with = "::hyper_serde::deserialize", serialize_with = "::hyper_serde::serialize")] #[ignore_malloc_size_of = "Defined in hyper"] - pub headers: Headers, + pub headers: HeaderMap, pub unsafe_request: bool, pub body: Option<Vec<u8>>, pub service_workers_mode: ServiceWorkersMode, @@ -171,9 +171,9 @@ pub struct RequestInit { impl Default for RequestInit { fn default() -> RequestInit { RequestInit { - method: Method::Get, + method: Method::GET, url: ServoUrl::parse("about:blank").unwrap(), - headers: Headers::new(), + headers: HeaderMap::new(), unsafe_request: false, body: None, service_workers_mode: ServiceWorkersMode::All, @@ -208,7 +208,7 @@ pub struct Request { pub sandboxed_storage_area_urls: bool, /// <https://fetch.spec.whatwg.org/#concept-request-header-list> #[ignore_malloc_size_of = "Defined in hyper"] - pub headers: Headers, + pub headers: HeaderMap, /// <https://fetch.spec.whatwg.org/#unsafe-request-flag> pub unsafe_request: bool, /// <https://fetch.spec.whatwg.org/#concept-request-body> @@ -264,10 +264,10 @@ impl Request { pipeline_id: Option<PipelineId>) -> Request { Request { - method: Method::Get, + method: Method::GET, local_urls_only: false, sandboxed_storage_area_urls: false, - headers: Headers::new(), + headers: HeaderMap::new(), unsafe_request: false, body: None, window: Window::Client, diff --git a/components/net_traits/response.rs b/components/net_traits/response.rs index 4b0a64460ce..e7f39358c5a 100644 --- a/components/net_traits/response.rs +++ b/components/net_traits/response.rs @@ -5,8 +5,9 @@ //! The [Response](https://fetch.spec.whatwg.org/#responses) object //! resulting from a [fetch operation](https://fetch.spec.whatwg.org/#concept-fetch) use {FetchMetadata, FilteredMetadata, Metadata, NetworkError, ReferrerPolicy}; -use hyper::header::{AccessControlExposeHeaders, ContentType, Headers}; -use hyper::status::StatusCode; +use headers_core::HeaderMapExt; +use headers_ext::{AccessControlExposeHeaders, ContentType}; +use http::{HeaderMap, StatusCode}; use hyper_serde::Serde; use servo_arc::Arc; use servo_url::ServoUrl; @@ -81,7 +82,7 @@ pub struct ResponseInit { #[serde(deserialize_with = "::hyper_serde::deserialize", serialize_with = "::hyper_serde::serialize")] #[ignore_malloc_size_of = "Defined in hyper"] - pub headers: Headers, + pub headers: HeaderMap, pub status_code: u16, pub referrer: Option<ServoUrl>, pub location_url: Option<Result<ServoUrl, String>>, @@ -96,10 +97,10 @@ pub struct Response { pub url_list: Vec<ServoUrl>, /// `None` can be considered a StatusCode of `0`. #[ignore_malloc_size_of = "Defined in hyper"] - pub status: Option<StatusCode>, + pub status: Option<(StatusCode, String)>, pub raw_status: Option<(u16, Vec<u8>)>, #[ignore_malloc_size_of = "Defined in hyper"] - pub headers: Headers, + pub headers: HeaderMap, #[ignore_malloc_size_of = "Mutex heap size undefined"] pub body: Arc<Mutex<ResponseBody>>, pub cache_state: CacheState, @@ -127,9 +128,9 @@ impl Response { termination_reason: None, url: Some(url), url_list: vec![], - status: Some(StatusCode::Ok), + status: Some((StatusCode::OK, "OK".to_string())), raw_status: Some((200, b"OK".to_vec())), - headers: Headers::new(), + headers: HeaderMap::new(), body: Arc::new(Mutex::new(ResponseBody::Empty)), cache_state: CacheState::None, https_state: HttpsState::None, @@ -148,7 +149,7 @@ impl Response { res.location_url = init.location_url; res.headers = init.headers; res.referrer = init.referrer; - res.status = Some(StatusCode::from_u16(init.status_code)); + res.status = StatusCode::from_u16(init.status_code).map(|s| (s, s.to_string())).ok(); res } @@ -160,7 +161,7 @@ impl Response { url_list: vec![], status: None, raw_status: None, - headers: Headers::new(), + headers: HeaderMap::new(), body: Arc::new(Mutex::new(ResponseBody::Empty)), cache_state: CacheState::None, https_state: HttpsState::None, @@ -242,45 +243,43 @@ impl Response { ResponseType::Error(..) => unreachable!(), ResponseType::Basic => { - let headers = old_headers.iter().filter(|header| { - match &*header.name().to_ascii_lowercase() { + let headers = old_headers.iter().filter(|(name, _)| { + match &*name.as_str().to_ascii_lowercase() { "set-cookie" | "set-cookie2" => false, _ => true } - }).collect(); + }).map(|(n, v)| (n.clone(), v.clone())).collect(); response.headers = headers; }, ResponseType::Cors => { - let access = old_headers.get::<AccessControlExposeHeaders>(); - let allowed_headers = access.as_ref().map(|v| &v[..]).unwrap_or(&[]); - - let headers = old_headers.iter().filter(|header| { - match &*header.name().to_ascii_lowercase() { + let headers = old_headers.iter().filter(|(name, _)| { + match &*name.as_str().to_ascii_lowercase() { "cache-control" | "content-language" | "content-type" | "expires" | "last-modified" | "pragma" => true, "set-cookie" | "set-cookie2" => false, header => { - let result = - allowed_headers.iter().find(|h| *header == *h.to_ascii_lowercase()); + let access = old_headers.typed_get::<AccessControlExposeHeaders>(); + let result = access + .and_then(|v| v.iter().find(|h| *header == h.as_str().to_ascii_lowercase())); result.is_some() } } - }).collect(); + }).map(|(n, v)| (n.clone(), v.clone())).collect(); response.headers = headers; }, ResponseType::Opaque => { response.url_list = vec![]; response.url = None; - response.headers = Headers::new(); + response.headers = HeaderMap::new(); response.status = None; response.body = Arc::new(Mutex::new(ResponseBody::Empty)); response.cache_state = CacheState::None; }, ResponseType::OpaqueRedirect => { - response.headers = Headers::new(); + response.headers = HeaderMap::new(); response.status = None; response.body = Arc::new(Mutex::new(ResponseBody::Empty)); response.cache_state = CacheState::None; @@ -293,10 +292,7 @@ impl Response { pub fn metadata(&self) -> Result<FetchMetadata, NetworkError> { fn init_metadata(response: &Response, url: &ServoUrl) -> Metadata { let mut metadata = Metadata::default(url.clone()); - metadata.set_content_type(match response.headers.get() { - Some(&ContentType(ref mime)) => Some(mime), - None => None, - }); + metadata.set_content_type(response.headers.typed_get::<ContentType>().map(|v| v.into()).as_ref()); metadata.location_url = response.location_url.clone(); metadata.headers = Some(Serde(response.headers.clone())); metadata.status = response.raw_status.clone(); diff --git a/components/profile/Cargo.toml b/components/profile/Cargo.toml index 7b9d79a7a8f..890a9aa9336 100644 --- a/components/profile/Cargo.toml +++ b/components/profile/Cargo.toml @@ -14,7 +14,7 @@ unstable = ["jemalloc-sys"] [dependencies] profile_traits = {path = "../profile_traits"} -influent = "0.4" +influent = "0.5" ipc-channel = "0.11" heartbeats-simple = "0.4" log = "0.4" @@ -22,6 +22,7 @@ serde = "1.0" serde_json = "1.0" servo_config = {path = "../config"} time = "0.1.12" +tokio = "0.1" [target.'cfg(target_os = "macos")'.dependencies] task_info = {path = "../../support/rust-task_info"} diff --git a/components/profile/lib.rs b/components/profile/lib.rs index d01aad61cd0..248fd0ab19b 100644 --- a/components/profile/lib.rs +++ b/components/profile/lib.rs @@ -25,6 +25,7 @@ extern crate servo_config; #[cfg(target_os = "macos")] extern crate task_info; extern crate time as std_time; +extern crate tokio; #[allow(unsafe_code)] mod heartbeats; diff --git a/components/profile/time.rs b/components/profile/time.rs index 780a6e9da2f..e2d12ad5701 100644 --- a/components/profile/time.rs +++ b/components/profile/time.rs @@ -23,6 +23,8 @@ use std::io::{self, Write}; use std::path::Path; use std::time::Duration; use std_time::precise_time_ns; +use tokio; +use tokio::prelude::Future; use trace_dump::TraceDump; pub trait Formattable { @@ -470,9 +472,9 @@ impl Profiler { if let Some(ref meta) = *meta { measurement.add_tag("host", meta.url.as_str()); }; - if client.write_one(measurement, None).is_err() { - warn!("Could not write measurement to profiler db"); - } + + tokio::run(client.write_one(measurement, None) + .map_err(|e| warn!("Could not write measurement to profiler db: {:?}", e))); } } }, diff --git a/components/script/Cargo.toml b/components/script/Cargo.toml index 12cb7c6c667..614f9fcded1 100644 --- a/components/script/Cargo.toml +++ b/components/script/Cargo.toml @@ -31,13 +31,13 @@ tinyfiledialogs = "3.0" [dependencies] app_units = "0.7" backtrace = {version = "0.3", optional = true} -base64 = "0.6" +base64 = "0.9" bitflags = "1.0" bluetooth_traits = {path = "../bluetooth_traits"} byteorder = "1.0" canvas_traits = {path = "../canvas_traits"} caseless = "0.2" -cookie = "0.10" +cookie = "0.11" chrono = "0.4" cssparser = "0.24" deny_public_fields = {path = "../deny_public_fields"} @@ -51,9 +51,12 @@ euclid = "0.19" fnv = "1.0" gleam = "0.6" half = "1.0" +headers-core = "0.0.1" +headers-ext = "0.0.3" html5ever = "0.22" -hyper = "0.10" -hyper_serde = "0.8" +http = "0.1" +hyper = "0.12" +hyper_serde = "0.9" image = "0.19" ipc-channel = "0.11" itertools = "0.7.6" @@ -66,8 +69,8 @@ malloc_size_of = { path = "../malloc_size_of" } malloc_size_of_derive = { path = "../malloc_size_of_derive" } metrics = {path = "../metrics"} mitochondria = "1.1.2" -mime = "0.2.1" -mime_guess = "1.8.0" +mime = "0.3" +mime_guess = "2.0.0-alpha.6" mozjs = "0.9.3" msg = {path = "../msg"} net_traits = {path = "../net_traits"} diff --git a/components/script/body.rs b/components/script/body.rs index 9709043912b..cfc167d25b4 100644 --- a/components/script/body.rs +++ b/components/script/body.rs @@ -22,7 +22,7 @@ use js::jsval::UndefinedValue; use js::rust::wrappers::JS_GetPendingException; use js::rust::wrappers::JS_ParseJSON; use js::typedarray::{ArrayBuffer, CreateWith}; -use mime::{Mime, TopLevel, SubLevel}; +use mime::{self, Mime}; use std::cell::Ref; use std::ptr; use std::rc::Rc; @@ -175,23 +175,22 @@ fn run_form_data_algorithm( } else { "" }; - let mime: Mime = mime_str - .parse() - .map_err(|_| Error::Type("Inappropriate MIME-type for Body".to_string()))?; - match mime { - // TODO - // ... Parser for Mime(TopLevel::Multipart, SubLevel::FormData, _) - // ... is not fully determined yet. - Mime(TopLevel::Application, SubLevel::WwwFormUrlEncoded, _) => { - let entries = form_urlencoded::parse(&bytes); - let formdata = FormData::new(None, root); - for (k, e) in entries { - formdata.Append(USVString(k.into_owned()), USVString(e.into_owned())); - } - return Ok(FetchedData::FormData(formdata)); - }, - _ => return Err(Error::Type("Inappropriate MIME-type for Body".to_string())), + let mime: Mime = mime_str.parse().map_err( + |_| Error::Type("Inappropriate MIME-type for Body".to_string()))?; + + // TODO + // ... Parser for Mime(TopLevel::Multipart, SubLevel::FormData, _) + // ... is not fully determined yet. + if mime.type_() == mime::APPLICATION && mime.subtype() == mime::WWW_FORM_URLENCODED { + let entries = form_urlencoded::parse(&bytes); + let formdata = FormData::new(None, root); + for (k, e) in entries { + formdata.Append(USVString(k.into_owned()), USVString(e.into_owned())); + } + return Ok(FetchedData::FormData(formdata)); } + + Err(Error::Type("Inappropriate MIME-type for Body".to_string())) } #[allow(unsafe_code)] diff --git a/components/script/dom/bindings/trace.rs b/components/script/dom/bindings/trace.rs index 8e720f28e25..75fcfb42c16 100644 --- a/components/script/dom/bindings/trace.rs +++ b/components/script/dom/bindings/trace.rs @@ -55,10 +55,9 @@ use euclid::Length as EuclidLength; use html5ever::{Prefix, LocalName, Namespace, QualName}; use html5ever::buffer_queue::BufferQueue; use html5ever::tendril::IncompleteUtf8; -use hyper::header::Headers; -use hyper::method::Method; -use hyper::mime::Mime; -use hyper::status::StatusCode; +use http::header::HeaderMap; +use hyper::Method; +use hyper::StatusCode; use ipc_channel::ipc::{IpcReceiver, IpcSender}; use js::glue::{CallObjectTracer, CallValueTracer}; use js::jsapi::{GCTraceKindToAscii, Heap, JSObject, JSTracer, TraceKind}; @@ -67,6 +66,7 @@ use js::rust::{GCMethods, Handle, Runtime}; use js::typedarray::TypedArray; use js::typedarray::TypedArrayElement; use metrics::{InteractiveMetrics, InteractiveWindow}; +use mime::Mime; use msg::constellation_msg::{BrowsingContextId, HistoryStateId, PipelineId, TopLevelBrowsingContextId}; use net_traits::{Metadata, NetworkError, ReferrerPolicy, ResourceThreads}; use net_traits::filemanager_thread::RelativePos; @@ -391,7 +391,7 @@ unsafe_no_jsmanaged_fields!(TimelineMarkerType); unsafe_no_jsmanaged_fields!(WorkerId); unsafe_no_jsmanaged_fields!(BufferQueue, QuirksMode, IncompleteUtf8); unsafe_no_jsmanaged_fields!(Runtime); -unsafe_no_jsmanaged_fields!(Headers, Method); +unsafe_no_jsmanaged_fields!(HeaderMap, Method); unsafe_no_jsmanaged_fields!(WindowProxyHandler); unsafe_no_jsmanaged_fields!(UntrustedNodeAddress); unsafe_no_jsmanaged_fields!(LengthOrPercentageOrAuto); diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index 9b52ea86b08..75527a90efc 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -95,14 +95,13 @@ use encoding_rs::{Encoding, UTF_8}; use euclid::Point2D; use fetch::FetchCanceller; use html5ever::{LocalName, Namespace, QualName}; -use hyper::header::{Header, SetCookie}; use hyper_serde::Serde; use ipc_channel::ipc::{self, IpcSender}; use js::jsapi::{JSContext, JSObject, JSRuntime}; use js::jsapi::JS_GetRuntime; use keyboard_types::{Key, KeyState, Modifiers}; use metrics::{InteractiveFlag, InteractiveMetrics, InteractiveWindow, ProfilerMetadataFactory, ProgressiveWebMetric}; -use mime::{Mime, TopLevel, SubLevel}; +use mime::{self, Mime}; use msg::constellation_msg::BrowsingContextId; use net_traits::{FetchResponseMsg, IpcSend, ReferrerPolicy}; use net_traits::CookieSource::NonHTTP; @@ -2530,14 +2529,12 @@ impl Document { implementation: Default::default(), content_type: match content_type { Some(mime_data) => mime_data, - None => Mime::from(match is_html_document { + None => match is_html_document { // https://dom.spec.whatwg.org/#dom-domimplementation-createhtmldocument - IsHTMLDocument::HTMLDocument => Mime(TopLevel::Text, SubLevel::Html, vec![]), + IsHTMLDocument::HTMLDocument => mime::TEXT_HTML, // https://dom.spec.whatwg.org/#concept-document-content-type - IsHTMLDocument::NonHTMLDocument => { - Mime(TopLevel::Application, SubLevel::Xml, vec![]) - }, - }), + IsHTMLDocument::NonHTMLDocument => "application/xml".parse().unwrap(), + }, }, last_modified: last_modified, url: DomRefCell::new(url), @@ -3345,8 +3342,10 @@ impl DocumentMethods for Document { local_name.make_ascii_lowercase(); } - let is_xhtml = self.content_type.0 == TopLevel::Application && - self.content_type.1.as_str() == "xhtml+xml"; + let is_xhtml = self.content_type.type_() == mime::APPLICATION && + self.content_type.subtype().as_str() == "xhtml" && + self.content_type.suffix() == Some(mime::XML); + let ns = if self.is_html_document || is_xhtml { ns!(html) } else { @@ -3947,18 +3946,16 @@ impl DocumentMethods for Document { return Err(Error::Security); } - if let Ok(cookie_header) = SetCookie::parse_header(&vec![cookie.to_string().into_bytes()]) { - let cookies = cookie_header - .0 - .into_iter() - .filter_map(|cookie| cookie_rs::Cookie::parse(cookie).ok().map(Serde)) - .collect(); - let _ = self - .window + let cookies = if let Some(cookie) = cookie_rs::Cookie::parse(cookie.to_string()).ok().map(Serde) { + vec![cookie] + } else { + vec![] + }; + + let _ = self.window .upcast::<GlobalScope>() .resource_threads() .send(SetCookiesForUrl(self.url(), cookies, NonHTTP)); - } Ok(()) } diff --git a/components/script/dom/domimplementation.rs b/components/script/dom/domimplementation.rs index f20cc61f3df..8936cc8b391 100644 --- a/components/script/dom/domimplementation.rs +++ b/components/script/dom/domimplementation.rs @@ -24,7 +24,7 @@ use dom::node::Node; use dom::text::Text; use dom::xmldocument::XMLDocument; use dom_struct::dom_struct; -use mime::{Mime, TopLevel, SubLevel}; +use mime; use script_traits::DocumentActivity; // https://dom.spec.whatwg.org/#domimplementation @@ -82,17 +82,9 @@ impl DOMImplementationMethods for DOMImplementation { let namespace = namespace_from_domstring(maybe_namespace.to_owned()); let content_type = match namespace { - ns!(html) => Mime( - TopLevel::Application, - SubLevel::Ext("xhtml+xml".to_string()), - vec![], - ), - ns!(svg) => Mime( - TopLevel::Image, - SubLevel::Ext("svg+xml".to_string()), - vec![], - ), - _ => Mime(TopLevel::Application, SubLevel::Xml, vec![]), + ns!(html) => "application/xhtml+xml".parse().unwrap(), + ns!(svg) => mime::IMAGE_SVG, + _ => "application/xml".parse().unwrap(), }; // Step 1. diff --git a/components/script/dom/eventsource.rs b/components/script/dom/eventsource.rs index 7aad98406a4..208d8aee8ed 100644 --- a/components/script/dom/eventsource.rs +++ b/components/script/dom/eventsource.rs @@ -17,13 +17,14 @@ use dom::messageevent::MessageEvent; use dom_struct::dom_struct; use euclid::Length; use fetch::FetchCanceller; -use hyper::header::{Accept, qitem}; +use headers_ext::ContentType; +use http::header::{self, HeaderName, HeaderValue}; use ipc_channel::ipc; use ipc_channel::router::ROUTER; use js::conversions::ToJSValConvertible; use js::jsapi::JSAutoCompartment; use js::jsval::UndefinedValue; -use mime::{Mime, TopLevel, SubLevel}; +use mime::{self, Mime}; use net_traits::{CoreResourceMsg, FetchChannels, FetchMetadata}; use net_traits::{FetchResponseMsg, FetchResponseListener, NetworkError}; use net_traits::request::{CacheMode, CorsSettings, CredentialsMode}; @@ -39,7 +40,6 @@ use task_source::{TaskSource, TaskSourceName}; use timers::OneshotTimerCallback; use utf8; -header! { (LastEventId, "Last-Event-ID") => [String] } const DEFAULT_RECONNECTION_TIME: u64 = 5000; @@ -338,13 +338,14 @@ impl FetchResponseListener for EventSourceContext { }; match meta.content_type { None => self.fail_the_connection(), - Some(ct) => match ct.into_inner().0 { - Mime(TopLevel::Text, SubLevel::EventStream, _) => { + Some(ct) => { + if <ContentType as Into<Mime>>::into(ct.into_inner()) == mime::TEXT_EVENT_STREAM { self.origin = meta.final_url.origin().ascii_serialization(); self.announce_the_connection(); - }, - _ => self.fail_the_connection(), - }, + } else { + self.fail_the_connection() + } + } } }, Err(_) => { @@ -501,9 +502,8 @@ impl EventSource { ..RequestInit::default() }; // Step 10 - request - .headers - .set(Accept(vec![qitem(mime!(Text / EventStream))])); + // TODO(eijebong): Replace once typed headers allow it + request.headers.insert(header::ACCEPT, HeaderValue::from_static("text/event-stream")); // Step 11 request.cache_mode = CacheMode::NoStore; // Step 12 @@ -613,9 +613,9 @@ impl EventSourceTimeoutCallback { let mut request = event_source.request(); // Step 5.3 if !event_source.last_event_id.borrow().is_empty() { - request.headers.set(LastEventId(String::from( - event_source.last_event_id.borrow().clone(), - ))); + //TODO(eijebong): Change this once typed header support custom values + request.headers.insert(HeaderName::from_static("last-event-id"), + HeaderValue::from_str(&String::from(event_source.last_event_id.borrow().clone())).unwrap()); } // Step 5.4 global diff --git a/components/script/dom/filereader.rs b/components/script/dom/filereader.rs index c816c943303..69ca537a75b 100644 --- a/components/script/dom/filereader.rs +++ b/components/script/dom/filereader.rs @@ -22,13 +22,13 @@ use dom::globalscope::GlobalScope; use dom::progressevent::ProgressEvent; use dom_struct::dom_struct; use encoding_rs::{Encoding, UTF_8}; -use hyper::mime::{Attr, Mime}; use js::jsapi::Heap; use js::jsapi::JSAutoCompartment; use js::jsapi::JSContext; use js::jsapi::JSObject; use js::jsval::{self, JSVal}; use js::typedarray::{ArrayBuffer, CreateWith}; +use mime::{self, Mime}; use servo_atoms::Atom; use std::cell::Cell; use std::ptr; @@ -115,11 +115,10 @@ impl FileReaderSharedFunctionality { // Step 4 & 5 encoding = encoding.or_else(|| { let resultmime = blob_type.parse::<Mime>().ok(); - resultmime.and_then(|Mime(_, _, ref parameters)| { - parameters - .iter() - .find(|&&(ref k, _)| &Attr::Charset == k) - .and_then(|&(_, ref v)| Encoding::for_label(v.as_str().as_bytes())) + resultmime.and_then(|mime| { + mime.params() + .find(|(ref k, _)| &mime::CHARSET == k) + .and_then(|(_, ref v)| Encoding::for_label(v.as_ref().as_bytes())) }) }); diff --git a/components/script/dom/headers.rs b/components/script/dom/headers.rs index 9ff08e7c07a..2cdb955b43f 100644 --- a/components/script/dom/headers.rs +++ b/components/script/dom/headers.rs @@ -11,11 +11,11 @@ use dom::bindings::root::DomRoot; use dom::bindings::str::{ByteString, is_token}; use dom::globalscope::GlobalScope; use dom_struct::dom_struct; -use hyper::header::Headers as HyperHeaders; -use mime::{Mime, TopLevel, SubLevel}; +use http::header::{self, HeaderMap as HyperHeaders, HeaderName, HeaderValue}; +use mime::{self, Mime}; use std::cell::Cell; use std::result::Result; -use std::str; +use std::str::{self, FromStr}; #[dom_struct] pub struct Headers { @@ -87,14 +87,14 @@ impl HeadersMethods for Headers { } // Step 7 let mut combined_value: Vec<u8> = vec![]; - if let Some(v) = self.header_list.borrow().get_raw(&valid_name) { - combined_value = v[0].clone(); + if let Some(v) = self.header_list.borrow().get(HeaderName::from_str(&valid_name).unwrap()) { + combined_value = v.as_bytes().to_vec(); combined_value.push(b','); } combined_value.extend(valid_value.iter().cloned()); self.header_list .borrow_mut() - .set_raw(valid_name, vec![combined_value]); + .insert(HeaderName::from_str(&valid_name).unwrap(), HeaderValue::from_bytes(&combined_value).unwrap()); Ok(()) } @@ -121,19 +121,17 @@ impl HeadersMethods for Headers { return Ok(()); } // Step 6 - self.header_list.borrow_mut().remove_raw(&valid_name); + self.header_list.borrow_mut().remove(&valid_name); Ok(()) } // https://fetch.spec.whatwg.org/#dom-headers-get fn Get(&self, name: ByteString) -> Fallible<Option<ByteString>> { // Step 1 - let valid_name = &validate_name(name)?; - Ok(self - .header_list - .borrow() - .get_raw(&valid_name) - .map(|v| ByteString::new(v[0].clone()))) + let valid_name = validate_name(name)?; + Ok(self.header_list.borrow().get(HeaderName::from_str(&valid_name).unwrap()).map(|v| { + ByteString::new(v.as_bytes().to_vec()) + })) } // https://fetch.spec.whatwg.org/#dom-headers-has @@ -141,7 +139,7 @@ impl HeadersMethods for Headers { // Step 1 let valid_name = validate_name(name)?; // Step 2 - Ok(self.header_list.borrow_mut().get_raw(&valid_name).is_some()) + Ok(self.header_list.borrow_mut().get(&valid_name).is_some()) } // https://fetch.spec.whatwg.org/#dom-headers-set @@ -173,7 +171,7 @@ impl HeadersMethods for Headers { // https://fetch.spec.whatwg.org/#concept-header-list-set self.header_list .borrow_mut() - .set_raw(valid_name, vec![valid_value]); + .insert(HeaderName::from_str(&valid_name).unwrap(), HeaderValue::from_bytes(&valid_value).unwrap()); Ok(()) } } @@ -184,10 +182,10 @@ impl Headers { match filler { // Step 1 Some(HeadersInit::Headers(h)) => { - for header in h.header_list.borrow().iter() { + for (name, value) in h.header_list.borrow().iter() { self.Append( - ByteString::new(Vec::from(header.name())), - ByteString::new(Vec::from(header.value_string().into_bytes())), + ByteString::new(Vec::from(name.as_str())), + ByteString::new(Vec::from(value.to_str().unwrap().as_bytes())) )?; } Ok(()) @@ -248,26 +246,21 @@ impl Headers { } pub fn get_headers_list(&self) -> HyperHeaders { - let mut headers = HyperHeaders::new(); - headers.extend(self.header_list.borrow_mut().iter()); - headers + self.header_list.borrow_mut().clone() } // https://fetch.spec.whatwg.org/#concept-header-extract-mime-type pub fn extract_mime_type(&self) -> Vec<u8> { - self.header_list - .borrow() - .get_raw("content-type") - .map_or(vec![], |v| v[0].clone()) + self.header_list.borrow().get(header::CONTENT_TYPE).map_or(vec![], |v| v.as_bytes().to_owned()) } pub fn sort_header_list(&self) -> Vec<(String, String)> { let borrowed_header_list = self.header_list.borrow(); let headers_iter = borrowed_header_list.iter(); let mut header_vec = vec![]; - for header in headers_iter { - let name = header.name().to_string(); - let value = header.value_string(); + for (name, value) in headers_iter { + let name = name.as_str().to_owned(); + let value = value.to_str().unwrap().to_owned(); let name_value = (name, value); header_vec.push(name_value); } @@ -306,12 +299,14 @@ fn is_cors_safelisted_request_content_type(value: &[u8]) -> bool { let value_mime_result: Result<Mime, _> = value_string.parse(); match value_mime_result { Err(_) => false, - Ok(value_mime) => match value_mime { - Mime(TopLevel::Application, SubLevel::WwwFormUrlEncoded, _) | - Mime(TopLevel::Multipart, SubLevel::FormData, _) | - Mime(TopLevel::Text, SubLevel::Plain, _) => true, - _ => false, - }, + Ok(value_mime) => { + match (value_mime.type_(), value_mime.subtype()) { + (mime::APPLICATION, mime::WWW_FORM_URLENCODED) | + (mime::MULTIPART, mime::FORM_DATA) | + (mime::TEXT, mime::PLAIN) => true, + _ => false, + } + } } } diff --git a/components/script/dom/htmlformelement.rs b/components/script/dom/htmlformelement.rs index 96f011bbb8e..c1c3e0e4446 100755 --- a/components/script/dom/htmlformelement.rs +++ b/components/script/dom/htmlformelement.rs @@ -44,9 +44,12 @@ use dom::virtualmethods::VirtualMethods; use dom::window::Window; use dom_struct::dom_struct; use encoding_rs::{Encoding, UTF_8}; +use headers_core::HeaderMapExt; +use headers_ext::ContentType; use html5ever::{LocalName, Prefix}; -use hyper::header::{Charset, ContentDisposition, ContentType, DispositionParam, DispositionType}; -use hyper::method::Method; +use hyper::Method; +use mime::{self, Mime}; +use net_traits::http_percent_encode; use script_thread::MainThreadScriptMsg; use script_traits::LoadData; use servo_rand::random; @@ -379,23 +382,15 @@ impl HTMLFormElement { // https://html.spec.whatwg.org/multipage/#submit-dialog }, // https://html.spec.whatwg.org/multipage/#submit-mutate-action - ("http", FormMethod::FormGet) | - ("https", FormMethod::FormGet) | - ("data", FormMethod::FormGet) => { - load_data.headers.set(ContentType::form_url_encoded()); + ("http", FormMethod::FormGet) | ("https", FormMethod::FormGet) | ("data", FormMethod::FormGet) => { + load_data.headers.typed_insert(ContentType::from(mime::APPLICATION_WWW_FORM_URLENCODED)); self.mutate_action_url(&mut form_data, load_data, encoding, &target_window); }, // https://html.spec.whatwg.org/multipage/#submit-body ("http", FormMethod::FormPost) | ("https", FormMethod::FormPost) => { - load_data.method = Method::Post; - self.submit_entity_body( - &mut form_data, - load_data, - enctype, - encoding, - &target_window, - ); - }, + load_data.method = Method::POST; + self.submit_entity_body(&mut form_data, load_data, enctype, encoding, &target_window); + } // https://html.spec.whatwg.org/multipage/#submit-get-action ("file", _) | ("about", _) | @@ -450,7 +445,7 @@ impl HTMLFormElement { let bytes = match enctype { FormEncType::UrlEncoded => { let charset = encoding.name(); - load_data.headers.set(ContentType::form_url_encoded()); + load_data.headers.typed_insert(ContentType::from(mime::APPLICATION_WWW_FORM_URLENCODED)); self.set_encoding_override(load_data.url.as_mut_url().query_pairs_mut()) .clear() @@ -463,12 +458,12 @@ impl HTMLFormElement { load_data.url.query().unwrap_or("").to_string().into_bytes() }, FormEncType::FormDataEncoded => { - let mime = mime!(Multipart / FormData; Boundary =(&boundary)); - load_data.headers.set(ContentType(mime)); + let mime: Mime = format!("multipart/form-data; boundary={}", boundary).parse().unwrap(); + load_data.headers.typed_insert(ContentType::from(mime)); encode_multipart_form_data(form_data, boundary, encoding) }, FormEncType::TextPlainEncoded => { - load_data.headers.set(ContentType(mime!(Text / Plain))); + load_data.headers.typed_insert(ContentType::from(mime::TEXT_PLAIN)); self.encode_plaintext(form_data).into_bytes() }, }; @@ -1231,40 +1226,30 @@ pub fn encode_multipart_form_data( // what spec says (that it should start with a '\r\n'). let mut boundary_bytes = format!("--{}\r\n", boundary).into_bytes(); result.append(&mut boundary_bytes); - let mut content_disposition = ContentDisposition { - disposition: DispositionType::Ext("form-data".to_owned()), - parameters: vec![DispositionParam::Ext( - "name".to_owned(), - String::from(entry.name.clone()), - )], - }; + // TODO(eijebong): Everthing related to content-disposition it to redo once typed headers + // are capable of it. match entry.value { FormDatumValue::String(ref s) => { + let content_disposition = format!("form-data; name=\"{}\"", entry.name); let mut bytes = format!("Content-Disposition: {}\r\n\r\n{}", content_disposition, s) .into_bytes(); result.append(&mut bytes); }, FormDatumValue::File(ref f) => { - content_disposition - .parameters - .push(DispositionParam::Filename( - Charset::Ext(String::from(charset.clone())), - None, - f.name().clone().into(), - )); + let extra = if charset.to_lowercase() == "utf-8" { + format!("filename=\"{}\"", String::from_utf8(f.name().as_bytes().into()).unwrap()) + } else { + format!("filename*=\"{}\"''{}", charset, http_percent_encode(f.name().as_bytes())) + }; + + let content_disposition = format!("form-data; name=\"{}\"; {}", entry.name, extra); // https://tools.ietf.org/html/rfc7578#section-4.4 - let content_type = ContentType( - f.upcast::<Blob>() - .Type() - .parse() - .unwrap_or(mime!(Text / Plain)), - ); - let mut type_bytes = format!( - "Content-Disposition: {}\r\ncontent-type: {}\r\n\r\n", - content_disposition, content_type - ).into_bytes(); + let content_type: Mime = f.upcast::<Blob>().Type().parse().unwrap_or(mime::TEXT_PLAIN); + let mut type_bytes = format!("Content-Disposition: {}\r\ncontent-type: {}\r\n\r\n", + content_disposition, + content_type).into_bytes(); result.append(&mut type_bytes); let mut bytes = f.upcast::<Blob>().get_bytes().unwrap_or(vec![]); diff --git a/components/script/dom/htmlimageelement.rs b/components/script/dom/htmlimageelement.rs index b31c5e886af..c9de1995230 100644 --- a/components/script/dom/htmlimageelement.rs +++ b/components/script/dom/htmlimageelement.rs @@ -44,7 +44,7 @@ use html5ever::{LocalName, Prefix}; use ipc_channel::ipc; use ipc_channel::router::ROUTER; use microtask::{Microtask, MicrotaskRunnable}; -use mime::{Mime, TopLevel, SubLevel}; +use mime::{self, Mime}; use net_traits::{FetchResponseListener, FetchMetadata, NetworkError, FetchResponseMsg}; use net_traits::image::base::{Image, ImageMetadata}; use net_traits::image_cache::{CanRequestImages, ImageCache, ImageOrMetadataAvailable}; @@ -180,13 +180,9 @@ impl FetchResponseListener for ImageContext { // Step 14.5 of https://html.spec.whatwg.org/multipage/#img-environment-changes if let Some(metadata) = metadata.as_ref() { if let Some(ref content_type) = metadata.content_type { - match content_type.clone().into_inner().0 { - Mime(TopLevel::Multipart, SubLevel::Ext(s), _) => { - if s == "x-mixed-replace" { - self.aborted.set(true); - } - }, - _ => (), + let mime: Mime = content_type.clone().into_inner().into(); + if mime.type_() == mime::MULTIPART && mime.subtype().as_str() == "x-mixed-replace" { + self.aborted.set(true); } } } @@ -570,11 +566,12 @@ impl HTMLImageElement { // TODO Handle unsupported mime type let mime = x.value().parse::<Mime>(); match mime { - Ok(m) => match m { - Mime(TopLevel::Image, _, _) => (), - _ => continue, - }, - _ => continue, + Ok(m) => + match m.type_() { + mime::IMAGE => (), + _ => continue + }, + _ => continue } } diff --git a/components/script/dom/htmlmediaelement.rs b/components/script/dom/htmlmediaelement.rs index 04754712887..1cd0b5e825a 100644 --- a/components/script/dom/htmlmediaelement.rs +++ b/components/script/dom/htmlmediaelement.rs @@ -34,12 +34,14 @@ use dom::promise::Promise; use dom::virtualmethods::VirtualMethods; use dom_struct::dom_struct; use fetch::FetchCanceller; +use headers_core::HeaderMapExt; +use headers_ext::ContentLength; use html5ever::{LocalName, Prefix}; -use hyper::header::{ByteRangeSpec, ContentLength, Headers, Range as HyperRange}; +use http::header::{self, HeaderMap, HeaderValue}; use ipc_channel::ipc; use ipc_channel::router::ROUTER; use microtask::{Microtask, MicrotaskRunnable}; -use mime::{Mime, SubLevel, TopLevel}; +use mime::{self, Mime}; use net_traits::{CoreResourceMsg, FetchChannels, FetchResponseListener, FetchMetadata, Metadata}; use net_traits::NetworkError; use net_traits::request::{CredentialsMode, Destination, RequestInit}; @@ -686,10 +688,9 @@ impl HTMLMediaElement { HTMLMediaElementTypeId::HTMLAudioElement => Destination::Audio, HTMLMediaElementTypeId::HTMLVideoElement => Destination::Video, }; - let mut headers = Headers::new(); - headers.set(HyperRange::Bytes(vec![ByteRangeSpec::AllFrom( - offset.unwrap_or(0), - )])); + let mut headers = HeaderMap::new(); + // FIXME(eijebong): Use typed headers once we have a constructor for the range header + headers.insert(header::RANGE, HeaderValue::from_str(&format!("bytes={}-", offset.unwrap_or(0))).unwrap()); let request = RequestInit { url: self.resource_url.borrow().as_ref().unwrap().clone(), headers, @@ -1271,7 +1272,10 @@ impl HTMLMediaElementMethods for HTMLMediaElement { // https://html.spec.whatwg.org/multipage/#dom-navigator-canplaytype fn CanPlayType(&self, type_: DOMString) -> CanPlayTypeResult { match type_.parse::<Mime>() { - Ok(Mime(TopLevel::Application, SubLevel::OctetStream, _)) | Err(_) => { + Ok(ref mime) if (mime.type_() == mime::APPLICATION && mime.subtype() == mime::OCTET_STREAM) => { + CanPlayTypeResult::_empty + }, + Err(_) => { CanPlayTypeResult::_empty }, _ => CanPlayTypeResult::Maybe, @@ -1464,8 +1468,8 @@ impl FetchResponseListener for HTMLMediaElementContext { if let Some(metadata) = self.metadata.as_ref() { if let Some(headers) = metadata.headers.as_ref() { - if let Some(content_length) = headers.get::<ContentLength>() { - if let Err(e) = self.elem.root().player.set_input_size(**content_length) { + if let Some(content_length) = headers.typed_get::<ContentLength>() { + if let Err(e) = self.elem.root().player.set_input_size(content_length.0) { eprintln!("Could not set player input size {:?}", e); } } diff --git a/components/script/dom/request.rs b/components/script/dom/request.rs index 0415aba9203..ceb64ec45ce 100644 --- a/components/script/dom/request.rs +++ b/components/script/dom/request.rs @@ -25,7 +25,8 @@ use dom::headers::{Guard, Headers}; use dom::promise::Promise; use dom::xmlhttprequest::Extractable; use dom_struct::dom_struct; -use hyper::method::Method as HttpMethod; +use http::Method as HttpMethod; +use http::method::InvalidMethod; use net_traits::ReferrerPolicy as MsgReferrerPolicy; use net_traits::request::{Origin, Window}; use net_traits::request::CacheMode as NetTraitsRequestCache; @@ -38,6 +39,7 @@ use net_traits::request::RequestMode as NetTraitsRequestMode; use servo_url::ServoUrl; use std::cell::{Cell, Ref}; use std::rc::Rc; +use std::str::FromStr; #[dom_struct] pub struct Request { @@ -283,7 +285,7 @@ impl Request { } // Step 25.2 let method = match init_method.as_str() { - Some(s) => normalize_method(s), + Some(s) => normalize_method(s).map_err(|e| Error::Type(format!("Method is not valid: {:?}", e)))?, None => return Err(Error::Type("Method is not a valid UTF8".to_string())), }; // Step 25.3 @@ -373,16 +375,10 @@ impl Request { let req = r.request.borrow(); let req_method = &req.method; match *req_method { - HttpMethod::Get => { - return Err(Error::Type( - "Init's body is non-null, and request method is GET".to_string(), - )) - }, - HttpMethod::Head => { - return Err(Error::Type( - "Init's body is non-null, and request method is HEAD".to_string(), - )) - }, + HttpMethod::GET => return Err(Error::Type( + "Init's body is non-null, and request method is GET".to_string())), + HttpMethod::HEAD => return Err(Error::Type( + "Init's body is non-null, and request method is HEAD".to_string())), _ => {}, } } @@ -473,17 +469,18 @@ fn net_request_from_global(global: &GlobalScope, url: ServoUrl) -> NetTraitsRequ } // https://fetch.spec.whatwg.org/#concept-method-normalize -fn normalize_method(m: &str) -> HttpMethod { +fn normalize_method(m: &str) -> Result<HttpMethod, InvalidMethod> { match_ignore_ascii_case! { m, - "delete" => return HttpMethod::Delete, - "get" => return HttpMethod::Get, - "head" => return HttpMethod::Head, - "options" => return HttpMethod::Options, - "post" => return HttpMethod::Post, - "put" => return HttpMethod::Put, + "delete" => return Ok(HttpMethod::DELETE), + "get" => return Ok(HttpMethod::GET), + "head" => return Ok(HttpMethod::HEAD), + "options" => return Ok(HttpMethod::OPTIONS), + "post" => return Ok(HttpMethod::POST), + "put" => return Ok(HttpMethod::PUT), _ => (), } - HttpMethod::Extension(m.to_string()) + debug!("Method: {:?}", m); + HttpMethod::from_str(m) } // https://fetch.spec.whatwg.org/#concept-method @@ -503,7 +500,9 @@ fn is_forbidden_method(m: &ByteString) -> bool { // https://fetch.spec.whatwg.org/#cors-safelisted-method fn is_cors_safelisted_method(m: &HttpMethod) -> bool { - m == &HttpMethod::Get || m == &HttpMethod::Head || m == &HttpMethod::Post + m == &HttpMethod::GET || + m == &HttpMethod::HEAD || + m == &HttpMethod::POST } // https://url.spec.whatwg.org/#include-credentials diff --git a/components/script/dom/response.rs b/components/script/dom/response.rs index 613145a370a..828dfc56515 100644 --- a/components/script/dom/response.rs +++ b/components/script/dom/response.rs @@ -18,8 +18,8 @@ use dom::headers::{is_vchar, is_obs_text}; use dom::promise::Promise; use dom::xmlhttprequest::Extractable; use dom_struct::dom_struct; -use hyper::header::Headers as HyperHeaders; -use hyper::status::StatusCode; +use http::header::HeaderMap as HyperHeaders; +use hyper::StatusCode; use hyper_serde::Serde; use net_traits::response::{ResponseBody as NetTraitsResponseBody}; use servo_url::ServoUrl; @@ -55,7 +55,7 @@ impl Response { headers_reflector: Default::default(), mime_type: DomRefCell::new("".to_string().into_bytes()), body_used: Cell::new(false), - status: DomRefCell::new(Some(StatusCode::Ok)), + status: DomRefCell::new(Some(StatusCode::OK)), raw_status: DomRefCell::new(Some((200, b"OK".to_vec()))), response_type: DomRefCell::new(DOMResponseType::Default), url: DomRefCell::new(None), @@ -99,7 +99,7 @@ impl Response { let r = Response::new(global); // Step 4 - *r.status.borrow_mut() = Some(StatusCode::from_u16(init.status)); + *r.status.borrow_mut() = Some(StatusCode::from_u16(init.status).unwrap()); // Step 5 *r.raw_status.borrow_mut() = Some((init.status, init.statusText.clone().into())); @@ -189,7 +189,7 @@ impl Response { let r = Response::new(global); // Step 5 - *r.status.borrow_mut() = Some(StatusCode::from_u16(status)); + *r.status.borrow_mut() = Some(StatusCode::from_u16(status).unwrap()); *r.raw_status.borrow_mut() = Some((status, b"".to_vec())); // Step 6 @@ -300,7 +300,7 @@ impl ResponseMethods for Response { fn Ok(&self) -> bool { match *self.status.borrow() { Some(s) => { - let status_num = s.to_u16(); + let status_num = s.as_u16(); return status_num >= 200 && status_num <= 299; }, None => false, diff --git a/components/script/dom/servoparser/mod.rs b/components/script/dom/servoparser/mod.rs index e64580f68c1..cacd54d2f1a 100644 --- a/components/script/dom/servoparser/mod.rs +++ b/components/script/dom/servoparser/mod.rs @@ -35,9 +35,8 @@ use html5ever::{Attribute, ExpandedName, LocalName, QualName}; use html5ever::buffer_queue::BufferQueue; use html5ever::tendril::{StrTendril, ByteTendril, IncompleteUtf8}; use html5ever::tree_builder::{NodeOrText, TreeSink, NextParserState, QuirksMode, ElementFlags}; -use hyper::header::ContentType; -use hyper::mime::{Mime, SubLevel, TopLevel}; use hyper_serde::Serde; +use mime::{self, Mime}; use msg::constellation_msg::PipelineId; use net_traits::{FetchMetadata, FetchResponseListener, Metadata, NetworkError}; use network_listener::PreInvoke; @@ -697,10 +696,11 @@ impl FetchResponseListener for ParserContext { }, Err(_) => None, }; - let content_type = metadata + let content_type: Option<Mime> = metadata .clone() .and_then(|meta| meta.content_type) - .map(Serde::into_inner); + .map(Serde::into_inner) + .map(Into::into); let parser = match ScriptThread::page_headers_available(&self.id, metadata) { Some(parser) => parser, None => return, @@ -712,7 +712,7 @@ impl FetchResponseListener for ParserContext { self.parser = Some(Trusted::new(&*parser)); match content_type { - Some(ContentType(Mime(TopLevel::Image, _, _))) => { + Some(ref mime) if mime.type_() == mime::IMAGE => { self.is_synthesized_document = true; let page = "<html><body></body></html>".into(); parser.push_string_input_chunk(page); @@ -725,14 +725,14 @@ impl FetchResponseListener for ParserContext { doc_body.AppendChild(&DomRoot::upcast::<Node>(img)).expect("Appending failed"); }, - Some(ContentType(Mime(TopLevel::Text, SubLevel::Plain, _))) => { + Some(ref mime) if mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN => { // https://html.spec.whatwg.org/multipage/#read-text let page = "<pre>\n".into(); parser.push_string_input_chunk(page); parser.parse_sync(); parser.tokenizer.borrow_mut().set_plaintext_state(); }, - Some(ContentType(Mime(TopLevel::Text, SubLevel::Html, _))) => { + Some(ref mime) if mime.type_() == mime::TEXT && mime.subtype() == mime::HTML => { // Handle text/html if let Some(reason) = ssl_error { self.is_synthesized_document = true; @@ -749,15 +749,18 @@ impl FetchResponseListener for ParserContext { parser.parse_sync(); } }, - Some(ContentType(Mime(TopLevel::Text, SubLevel::Xml, _))) | // Handle text/xml, application/xml - Some(ContentType(Mime(TopLevel::Application, SubLevel::Xml, _))) => {}, - Some(ContentType(Mime(TopLevel::Application, SubLevel::Ext(ref sub), _))) - if sub.as_str() == "xhtml+xml".to_owned() => {}, // Handle xhtml (application/xhtml+xml) - Some(ContentType(Mime(toplevel, sublevel, _))) => { + // Handle text/xml, application/xml + Some(ref mime) if (mime.type_() == mime::TEXT && mime.subtype() == mime::XML) || + (mime.type_() == mime::APPLICATION && mime.subtype() == mime::XML) => {}, + Some(ref mime) if mime.type_() == mime::APPLICATION && + mime.subtype().as_str() == "xhtml" && + mime.suffix() == Some(mime::XML) + => {}, // Handle xhtml (application/xhtml+xml) + Some(ref mime) => { // Show warning page for unknown mime types. let page = format!("<html><body><p>Unknown content type ({}/{}).</p></body></html>", - toplevel.as_str(), - sublevel.as_str()); + mime.type_().as_str(), + mime.subtype().as_str()); self.is_synthesized_document = true; parser.push_string_input_chunk(page); parser.parse_sync(); diff --git a/components/script/dom/xmlhttprequest.rs b/components/script/dom/xmlhttprequest.rs index 38d206b3a1b..6ed6a7ffab4 100644 --- a/components/script/dom/xmlhttprequest.rs +++ b/components/script/dom/xmlhttprequest.rs @@ -39,12 +39,12 @@ use dom_struct::dom_struct; use encoding_rs::{Encoding, UTF_8}; use euclid::Length; use fetch::FetchCanceller; +use headers_core::HeaderMapExt; +use headers_ext::{ContentLength, ContentType}; use html5ever::serialize; use html5ever::serialize::SerializeOpts; -use hyper::header::{ContentLength, ContentType, ContentEncoding}; -use hyper::header::Headers; -use hyper::method::Method; -use hyper::mime::{self, Attr as MimeAttr, Mime, Value as MimeValue}; +use http::header::{self, HeaderMap, HeaderName, HeaderValue}; +use hyper::Method; use hyper_serde::Serde; use ipc_channel::ipc; use ipc_channel::router::ROUTER; @@ -53,6 +53,7 @@ use js::jsapi::JS_ClearPendingException; use js::jsval::{JSVal, NullValue, UndefinedValue}; use js::rust::wrappers::JS_ParseJSON; use js::typedarray::{ArrayBuffer, CreateWith}; +use mime::{self, Mime, Name}; use net_traits::{FetchChannels, FetchMetadata, FilteredMetadata}; use net_traits::{FetchResponseListener, NetworkError, ReferrerPolicy}; use net_traits::CoreResourceMsg::Fetch; @@ -68,7 +69,7 @@ use std::default::Default; use std::ptr; use std::ptr::NonNull; use std::slice; -use std::str; +use std::str::{self, FromStr}; use std::sync::{Arc, Mutex}; use task_source::TaskSourceName; use task_source::networking::NetworkingTaskSource; @@ -100,7 +101,7 @@ struct XHRContext { #[derive(Clone)] pub enum XHRProgress { /// Notify that headers have been received - HeadersReceived(GenerationId, Option<Headers>, Option<(u16, Vec<u8>)>), + HeadersReceived(GenerationId, Option<HeaderMap>, Option<(u16, Vec<u8>)>), /// Partial progress (after receiving headers), containing portion of the response Loading(GenerationId, ByteString), /// Loading is done @@ -138,7 +139,7 @@ pub struct XMLHttpRequest { #[ignore_malloc_size_of = "Defined in rust-mozjs"] response_json: Heap<JSVal>, #[ignore_malloc_size_of = "Defined in hyper"] - response_headers: DomRefCell<Headers>, + response_headers: DomRefCell<HeaderMap>, #[ignore_malloc_size_of = "Defined in hyper"] override_mime_type: DomRefCell<Option<Mime>>, override_charset: DomRefCell<Option<&'static Encoding>>, @@ -148,7 +149,7 @@ pub struct XMLHttpRequest { request_method: DomRefCell<Method>, request_url: DomRefCell<Option<ServoUrl>>, #[ignore_malloc_size_of = "Defined in hyper"] - request_headers: DomRefCell<Headers>, + request_headers: DomRefCell<HeaderMap>, request_body_len: Cell<usize>, sync: Cell<bool>, upload_complete: Cell<bool>, @@ -188,13 +189,13 @@ impl XMLHttpRequest { response_blob: Default::default(), response_arraybuffer: Heap::default(), response_json: Heap::default(), - response_headers: DomRefCell::new(Headers::new()), + response_headers: DomRefCell::new(HeaderMap::new()), override_mime_type: DomRefCell::new(None), override_charset: DomRefCell::new(None), - request_method: DomRefCell::new(Method::Get), + request_method: DomRefCell::new(Method::GET), request_url: DomRefCell::new(None), - request_headers: DomRefCell::new(Headers::new()), + request_headers: DomRefCell::new(HeaderMap::new()), request_body_len: Cell::new(0), sync: Cell::new(false), upload_complete: Cell::new(false), @@ -347,8 +348,8 @@ impl XMLHttpRequestMethods for XMLHttpRequest { match maybe_method { // Step 4 - Some(Method::Connect) | Some(Method::Trace) => Err(Error::Security), - Some(Method::Extension(ref t)) if &**t == "TRACK" => Err(Error::Security), + Some(Method::CONNECT) | Some(Method::TRACE) => Err(Error::Security), + Some(ref t) if t.as_str() == "TRACK" => Err(Error::Security), Some(parsed_method) => { // Step 3 if !is_token(&method) { @@ -395,7 +396,7 @@ impl XMLHttpRequestMethods for XMLHttpRequest { *self.request_method.borrow_mut() = parsed_method; *self.request_url.borrow_mut() = Some(parsed_url); self.sync.set(!async); - *self.request_headers.borrow_mut() = Headers::new(); + *self.request_headers.borrow_mut() = HeaderMap::new(); self.send_flag.set(false); *self.status_text.borrow_mut() = ByteString::new(vec![]); self.status.set(0); @@ -450,19 +451,17 @@ impl XMLHttpRequestMethods for XMLHttpRequest { let mut headers = self.request_headers.borrow_mut(); // Step 6 - let value = match headers.get_raw(name_str) { + let value = match headers.get(name_str).map(HeaderValue::as_bytes) { Some(raw) => { - debug!("SetRequestHeader: old value = {:?}", raw[0]); - let mut buf = raw[0].clone(); + let mut buf = raw.to_vec(); buf.extend_from_slice(b", "); buf.extend_from_slice(value); - debug!("SetRequestHeader: new value = {:?}", buf); buf }, None => value.into(), }; - headers.set_raw(name_str.to_owned(), vec![value]); + headers.insert(HeaderName::from_str(name_str).unwrap(), HeaderValue::from_bytes(&value).unwrap()); Ok(()) } @@ -532,8 +531,8 @@ impl XMLHttpRequestMethods for XMLHttpRequest { // Step 3 let data = match *self.request_method.borrow() { - Method::Get | Method::Head => None, - _ => data, + Method::GET | Method::HEAD => None, + _ => data }; // Step 4 (first half) let extracted_or_serialized = match data { @@ -638,30 +637,46 @@ impl XMLHttpRequestMethods for XMLHttpRequest { // XHR spec differs from http, and says UTF-8 should be in capitals, // instead of "utf-8", which is what Hyper defaults to. So not // using content types provided by Hyper. - { - Some(MimeValue::Ext("UTF-8".to_string())) - }, + Some("UTF-8"), _ => None, }; let mut content_type_set = false; if let Some(ref ct) = *content_type { - if !request.headers.has::<ContentType>() { - request - .headers - .set_raw("content-type", vec![ct.bytes().collect()]); + if !request.headers.contains_key(header::CONTENT_TYPE) { + request.headers.insert(header::CONTENT_TYPE, HeaderValue::from_str(ct).unwrap()); content_type_set = true; } } if !content_type_set { - let ct = request.headers.get_mut::<ContentType>(); + let ct = request.headers.typed_get::<ContentType>(); if let Some(ct) = ct { if let Some(encoding) = encoding { - for param in &mut (ct.0).2 { - if param.0 == MimeAttr::Charset { - if !param.0.as_str().eq_ignore_ascii_case(encoding.as_str()) { - *param = (MimeAttr::Charset, encoding.clone()); + let mime: Mime = ct.into(); + for param in mime.params() { + if param.0 == mime::CHARSET { + if !param.1.as_ref().eq_ignore_ascii_case(encoding) { + let new_params: Vec<(Name, Name)> = + mime.params() + .filter(|p| p.0 != mime::CHARSET) + .map(|p| (p.0, p.1)) + .collect(); + + let new_mime = + format!("{}/{}; charset={}{}{}", + mime.type_().as_ref(), + mime.subtype().as_ref(), + encoding, + if new_params.is_empty() { "" } else { "; " }, + new_params + .iter() + .map(|p| format!("{}={}", p.0, p.1)) + .collect::<Vec<String>>() + .join("; ") + ); + let new_mime: Mime = new_mime.parse().unwrap(); + request.headers.typed_insert(ContentType::from(new_mime)) } } } @@ -672,8 +687,6 @@ impl XMLHttpRequestMethods for XMLHttpRequest { _ => (), } - debug!("request.headers = {:?}", request.headers); - self.fetch_time.set(time::now().to_timespec().sec); let rv = self.fetch(request, &self.global()); @@ -728,15 +741,48 @@ impl XMLHttpRequestMethods for XMLHttpRequest { // https://xhr.spec.whatwg.org/#the-getresponseheader()-method fn GetResponseHeader(&self, name: ByteString) -> Option<ByteString> { - self.filter_response_headers() - .iter() - .find(|h| name.eq_ignore_case(&h.name().parse().unwrap())) - .map(|h| ByteString::new(h.value_string().into_bytes())) + let headers = self.filter_response_headers(); + let headers = headers.get_all(HeaderName::from_str(&name.as_str()?.to_lowercase()).ok()?); + let mut first = true; + let s = headers.iter() + .fold(Vec::new(), |mut vec, value| { + if !first { + vec.extend(", ".as_bytes()); + } + first = false; + vec.extend(value.as_bytes()); + vec + }); + + // There was no header with that name so we never got to change that value + if first { + None + } else { + Some(ByteString::new(s)) + } } // https://xhr.spec.whatwg.org/#the-getallresponseheaders()-method fn GetAllResponseHeaders(&self) -> ByteString { - ByteString::new(self.filter_response_headers().to_string().into_bytes()) + let headers = self.filter_response_headers(); + let keys = headers.keys(); + let v = keys.fold(Vec::new(), |mut vec, k| { + let values = headers.get_all(k); + vec.extend(k.as_str().as_bytes()); + vec.extend(": ".as_bytes()); + let mut first = true; + for value in values { + if !first { + vec.extend(", ".as_bytes()); + first = false; + } + vec.extend(value.as_bytes()); + } + vec.extend("\r\n".as_bytes()); + vec + }); + + ByteString::new(v) } // https://xhr.spec.whatwg.org/#the-overridemimetype()-method @@ -751,12 +797,20 @@ impl XMLHttpRequestMethods for XMLHttpRequest { // Step 2 let override_mime = mime.parse::<Mime>().map_err(|_| Error::Syntax)?; // Step 3 - let mime_no_params = Mime(override_mime.clone().0, override_mime.clone().1, vec![]); + let mime_str = override_mime.as_ref(); + let mime_parts: Vec<&str> = mime_str.split(";").collect(); + let mime_no_params = if mime_parts.len() > 1 { + mime_parts[0].parse().unwrap() + } else { + override_mime.clone() + }; + *self.override_mime_type.borrow_mut() = Some(mime_no_params); // Step 4 - let value = override_mime.get_param(mime::Attr::Charset); - *self.override_charset.borrow_mut() = - value.and_then(|value| Encoding::for_label(value.as_bytes())); + let value = override_mime.get_param(mime::CHARSET); + *self.override_charset.borrow_mut() = value.and_then(|value| { + Encoding::for_label(value.as_ref().as_bytes()) + }); Ok(()) } @@ -1077,20 +1131,17 @@ impl XMLHttpRequest { fn dispatch_progress_event(&self, upload: bool, type_: Atom, loaded: u64, total: Option<u64>) { let (total_length, length_computable) = - if self.response_headers.borrow().has::<ContentEncoding>() { + if self.response_headers.borrow().contains_key(header::CONTENT_ENCODING) { (0, false) } else { (total.unwrap_or(0), total.is_some()) }; - let progressevent = ProgressEvent::new( - &self.global(), - type_, - EventBubbles::DoesNotBubble, - EventCancelable::NotCancelable, - length_computable, - loaded, - total_length, - ); + let progressevent = ProgressEvent::new(&self.global(), + type_, + EventBubbles::DoesNotBubble, + EventCancelable::NotCancelable, + length_computable, loaded, + total_length); let target = if upload { self.upload.upcast() } else { @@ -1108,13 +1159,10 @@ impl XMLHttpRequest { fn dispatch_response_progress_event(&self, type_: Atom) { let len = self.response.borrow().len() as u64; - let total = self - .response_headers - .borrow() - .get::<ContentLength>() - .map(|x| **x as u64); + let total = self.response_headers.borrow().typed_get::<ContentLength>().map(|v| v.0); self.dispatch_progress_event(false, type_, len, total); } + fn set_timeout(&self, duration_ms: u32) { // Sets up the object to timeout in a given number of milliseconds // This will cancel all previous timeouts @@ -1154,11 +1202,7 @@ impl XMLHttpRequest { return response; } // Step 2 - let mime = self - .final_mime_type() - .as_ref() - .map(Mime::to_string) - .unwrap_or("".to_owned()); + let mime = self.final_mime_type().as_ref().map(|m| m.to_string()).unwrap_or("".to_owned()); // Step 3, 4 let bytes = self.response.borrow().to_vec(); @@ -1200,7 +1244,7 @@ impl XMLHttpRequest { let charset = self.final_charset().unwrap_or(UTF_8); let temp_doc: DomRoot<Document>; match mime_type { - Some(Mime(mime::TopLevel::Text, mime::SubLevel::Html, _)) => { + Some(ref mime) if mime.type_() == mime::TEXT && mime.subtype() == mime::HTML => { // Step 5 if self.response_type.get() == XMLHttpRequestResponseType::_empty { return None; @@ -1210,17 +1254,15 @@ impl XMLHttpRequest { } }, // Step 7 - Some(Mime(mime::TopLevel::Text, mime::SubLevel::Xml, _)) | - Some(Mime(mime::TopLevel::Application, mime::SubLevel::Xml, _)) | + Some(ref mime) if (mime.type_() == mime::TEXT && mime.subtype() == mime::XML) || + (mime.type_() == mime::APPLICATION && mime.subtype() == mime::XML) => { + temp_doc = self.handle_xml(); + }, None => { temp_doc = self.handle_xml(); }, - Some(Mime(_, mime::SubLevel::Ext(sub), _)) => { - if sub.ends_with("+xml") { - temp_doc = self.handle_xml(); - } else { - return None; - } + Some(ref mime) if mime.suffix() == Some(mime::XML) => { + temp_doc = self.handle_xml(); }, // Step 4 _ => { @@ -1336,34 +1378,13 @@ impl XMLHttpRequest { ) } - fn filter_response_headers(&self) -> Headers { + fn filter_response_headers(&self) -> HeaderMap { // https://fetch.spec.whatwg.org/#concept-response-header-list - use hyper::error::Result; - use hyper::header::{Header, HeaderFormat}; - use hyper::header::SetCookie; - use std::fmt; - - // a dummy header so we can use headers.remove::<SetCookie2>() - #[derive(Clone, Debug, MallocSizeOf)] - struct SetCookie2; - impl Header for SetCookie2 { - fn header_name() -> &'static str { - "set-cookie2" - } - - fn parse_header(_: &[Vec<u8>]) -> Result<SetCookie2> { - unimplemented!() - } - } - impl HeaderFormat for SetCookie2 { - fn fmt_header(&self, _f: &mut fmt::Formatter) -> fmt::Result { - unimplemented!() - } - } + use http::header::{self, HeaderName}; let mut headers = self.response_headers.borrow().clone(); - headers.remove::<SetCookie>(); - headers.remove::<SetCookie2>(); + headers.remove(header::SET_COOKIE); + headers.remove(HeaderName::from_static("set-cookie2")); // XXXManishearth additional CORS filtering goes here headers } @@ -1416,12 +1437,15 @@ impl XMLHttpRequest { if self.override_charset.borrow().is_some() { self.override_charset.borrow().clone() } else { - match self.response_headers.borrow().get() { - Some(&ContentType(ref mime)) => { - let value = mime.get_param(mime::Attr::Charset); - value.and_then(|value| Encoding::for_label(value.as_bytes())) - }, - None => None, + match self.response_headers.borrow().typed_get::<ContentType>() { + Some(ct) => { + let mime: Mime = ct.into(); + let value = mime.get_param(mime::CHARSET); + value.and_then(|value|{ + Encoding::for_label(value.as_ref().as_bytes()) + }) + } + None => { None } } } } @@ -1430,9 +1454,9 @@ impl XMLHttpRequest { if self.override_mime_type.borrow().is_some() { self.override_mime_type.borrow().clone() } else { - match self.response_headers.borrow().get() { - Some(&ContentType(ref mime)) => Some(mime.clone()), - None => None, + match self.response_headers.borrow().typed_get::<ContentType>() { + Some(ct) => { Some(ct.into()) }, + None => { None } } } } diff --git a/components/script/lib.rs b/components/script/lib.rs index a402bec7cf3..ef0205b68b6 100644 --- a/components/script/lib.rs +++ b/components/script/lib.rs @@ -45,9 +45,11 @@ extern crate euclid; extern crate fnv; extern crate gleam; extern crate half; +extern crate headers_core; +extern crate headers_ext; #[macro_use] extern crate html5ever; -#[macro_use] +extern crate http; extern crate hyper; extern crate hyper_serde; extern crate image; @@ -65,7 +67,6 @@ extern crate malloc_size_of; #[macro_use] extern crate malloc_size_of_derive; extern crate metrics; -#[macro_use] extern crate mime; extern crate mime_guess; extern crate mitochondria; diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index 8795a65cd13..e1d9fd4c0e4 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -65,9 +65,9 @@ use dom::workletglobalscope::WorkletGlobalScopeInit; use embedder_traits::EmbedderMsg; use euclid::{Point2D, Vector2D, Rect}; use fetch::FetchCanceller; -use hyper::header::{ContentType, HttpDate, Headers, LastModified}; -use hyper::header::ReferrerPolicy as ReferrerPolicyHeader; -use hyper::mime::{Mime, SubLevel, TopLevel}; +use headers_core::HeaderMapExt; +use headers_ext::LastModified; +use headers_ext::ReferrerPolicy as ReferrerPolicyHeader; use hyper_serde::Serde; use ipc_channel::ipc::{self, IpcSender}; use js::glue::GetWindowProxyClass; @@ -76,6 +76,7 @@ use js::jsapi::{JSTracer, SetWindowProxyClass}; use js::jsval::UndefinedValue; use metrics::{MAX_TASK_NS, PaintTimeMetrics}; use microtask::{MicrotaskQueue, Microtask}; +use mime::{self, Mime}; use msg::constellation_msg::{BrowsingContextId, HistoryStateId, PipelineId}; use msg::constellation_msg::{PipelineNamespace, TopLevelBrowsingContextId}; use net_traits::{FetchMetadata, FetchResponseListener, FetchResponseMsg}; @@ -115,6 +116,7 @@ use std::rc::Rc; use std::result::Result; use std::sync::Arc; use std::thread; +use std::time::SystemTime; use style::thread_state::{self, ThreadState}; use task_queue::{QueuedTask, QueuedTaskConversion, TaskQueue}; use task_source::TaskSourceName; @@ -127,7 +129,7 @@ use task_source::performance_timeline::PerformanceTimelineTaskSource; use task_source::remote_event::RemoteEventTaskSource; use task_source::user_interaction::UserInteractionTaskSource; use task_source::websocket::WebsocketTaskSource; -use time::{get_time, precise_time_ns, Tm}; +use time::{at_utc, get_time, precise_time_ns, Timespec}; use url::Position; use url::percent_encoding::percent_decode; use webdriver_handlers; @@ -2611,37 +2613,27 @@ impl ScriptThread { window.init_window_proxy(&window_proxy); let last_modified = metadata.headers.as_ref().and_then(|headers| { - headers - .get() - .map(|&LastModified(HttpDate(ref tm))| dom_last_modified(tm)) + headers.typed_get::<LastModified>() + .map(|tm| dom_last_modified(&tm.into())) }); - let content_type = metadata - .content_type - .as_ref() - .map(|&Serde(ContentType(ref mimetype))| mimetype.clone()); - let loader = DocumentLoader::new_with_threads( self.resource_threads.clone(), Some(final_url.clone()), ); - let is_html_document = match metadata.content_type { - Some(Serde(ContentType(Mime( - TopLevel::Application, - SubLevel::Ext(ref sub_level), - _, - )))) - if sub_level.ends_with("+xml") => - { - IsHTMLDocument::NonHTMLDocument - }, + let content_type: Option<Mime> = metadata.content_type + .map(Serde::into_inner) + .map(Into::into); - Some(Serde(ContentType(Mime(TopLevel::Application, SubLevel::Xml, _)))) | - Some(Serde(ContentType(Mime(TopLevel::Text, SubLevel::Xml, _)))) => { - IsHTMLDocument::NonHTMLDocument - }, + let is_html_document = match content_type { + Some(ref mime) if mime.type_() == mime::APPLICATION && + mime.suffix() == Some(mime::XML) => IsHTMLDocument::NonHTMLDocument, + Some(ref mime) if + (mime.type_() == mime::TEXT && mime.subtype() == mime::XML) || + (mime.type_() == mime::APPLICATION && mime.subtype() == mime::XML) + => IsHTMLDocument::NonHTMLDocument, _ => IsHTMLDocument::HTMLDocument, }; @@ -2650,28 +2642,25 @@ impl ScriptThread { None => None, }; - let referrer_policy = metadata - .headers - .as_ref() - .map(Serde::deref) - .and_then(Headers::get::<ReferrerPolicyHeader>) - .map(ReferrerPolicy::from); - - let document = Document::new( - &window, - HasBrowsingContext::Yes, - Some(final_url.clone()), - incomplete.origin, - is_html_document, - content_type, - last_modified, - incomplete.activity, - DocumentSource::FromParser, - loader, - referrer, - referrer_policy, - incomplete.canceller, - ); + let referrer_policy = metadata.headers + .as_ref() + .map(Serde::deref) + .and_then(|h| h.typed_get::<ReferrerPolicyHeader>()) + .map(ReferrerPolicy::from); + + let document = Document::new(&window, + HasBrowsingContext::Yes, + Some(final_url.clone()), + incomplete.origin, + is_html_document, + content_type, + last_modified, + incomplete.activity, + DocumentSource::FromParser, + loader, + referrer, + referrer_policy, + incomplete.canceller); document.set_ready_state(DocumentReadyState::Loading); self.documents @@ -3112,7 +3101,7 @@ impl ScriptThread { let mut context = ParserContext::new(id, url.clone()); let mut meta = Metadata::default(url); - meta.set_content_type(Some(&mime!(Text / Html))); + meta.set_content_type(Some(&mime::TEXT_HTML)); // If this page load is the result of a javascript scheme url, map // the evaluation result into a response. @@ -3221,7 +3210,10 @@ impl Drop for ScriptThread { } } -fn dom_last_modified(tm: &Tm) -> String { +fn dom_last_modified(tm: &SystemTime) -> String { + let tm = tm.duration_since(SystemTime::UNIX_EPOCH).unwrap(); + let tm = Timespec::new(tm.as_secs() as i64, 0); + let tm = at_utc(tm); tm.to_local() .strftime("%m/%d/%Y %H:%M:%S") .unwrap() diff --git a/components/script/stylesheet_loader.rs b/components/script/stylesheet_loader.rs index 8ae75a6e35b..6562dcf7c2a 100644 --- a/components/script/stylesheet_loader.rs +++ b/components/script/stylesheet_loader.rs @@ -14,11 +14,9 @@ use dom::htmlelement::HTMLElement; use dom::htmllinkelement::{RequestGenerationId, HTMLLinkElement}; use dom::node::{document_from_node, window_from_node}; use encoding_rs::UTF_8; -use hyper::header::ContentType; -use hyper::mime::{Mime, TopLevel, SubLevel}; -use hyper_serde::Serde; use ipc_channel::ipc; use ipc_channel::router::ROUTER; +use mime::{self, Mime}; use net_traits::{FetchResponseListener, FetchMetadata, FilteredMetadata, Metadata, NetworkError, ReferrerPolicy}; use net_traits::request::{CorsSettings, CredentialsMode, Destination, RequestInit, RequestMode}; use network_listener::{NetworkListener, PreInvoke}; @@ -117,18 +115,12 @@ impl FetchResponseListener for StylesheetContext { Some(meta) => meta, None => return, }; - let is_css = - metadata - .content_type - .map_or(false, |Serde(ContentType(Mime(top, sub, _)))| { - top == TopLevel::Text && sub == SubLevel::Css - }); - - let data = if is_css { - mem::replace(&mut self.data, vec![]) - } else { - vec![] - }; + let is_css = metadata.content_type.map_or(false, |ct| { + let mime: Mime = ct.into_inner().into(); + mime.type_() == mime::TEXT && mime.subtype() == mime::CSS + }); + + let data = if is_css { mem::replace(&mut self.data, vec![]) } else { vec![] }; // TODO: Get the actual value. http://dev.w3.org/csswg/css-syntax/#environment-encoding let environment_encoding = UTF_8; diff --git a/components/script/webdriver_handlers.rs b/components/script/webdriver_handlers.rs index 6c3e7deb910..3ac4362f8bd 100644 --- a/components/script/webdriver_handlers.rs +++ b/components/script/webdriver_handlers.rs @@ -299,7 +299,11 @@ pub fn handle_add_cookie( }, }; let url = document.url(); - let method = if cookie.http_only() { HTTP } else { NonHTTP }; + let method = if cookie.http_only().unwrap_or(false) { + HTTP + } else { + NonHTTP + }; let domain = cookie.domain().map(ToOwned::to_owned); reply diff --git a/components/script_traits/Cargo.toml b/components/script_traits/Cargo.toml index 580897fcc58..3656990aafe 100644 --- a/components/script_traits/Cargo.toml +++ b/components/script_traits/Cargo.toml @@ -12,13 +12,14 @@ path = "lib.rs" [dependencies] bluetooth_traits = {path = "../bluetooth_traits"} canvas_traits = {path = "../canvas_traits"} -cookie = "0.10" +cookie = "0.11" devtools_traits = {path = "../devtools_traits"} embedder_traits = {path = "../embedder_traits"} euclid = "0.19" gfx_traits = {path = "../gfx_traits"} -hyper = "0.10" -hyper_serde = "0.8" +http = "0.1" +hyper = "0.12" +hyper_serde = "0.9" ipc-channel = "0.11" keyboard-types = {version = "0.4.2-servo", features = ["serde"]} libc = "0.2" diff --git a/components/script_traits/lib.rs b/components/script_traits/lib.rs index cc9787d4721..6ac224d0b51 100644 --- a/components/script_traits/lib.rs +++ b/components/script_traits/lib.rs @@ -16,6 +16,7 @@ extern crate devtools_traits; extern crate embedder_traits; extern crate euclid; extern crate gfx_traits; +extern crate http; extern crate hyper; extern crate hyper_serde; extern crate ipc_channel; @@ -47,8 +48,8 @@ use canvas_traits::webgl::WebGLPipeline; use devtools_traits::{DevtoolScriptControlMsg, ScriptToDevtoolsControlMsg, WorkerId}; use euclid::{Length, Point2D, Vector2D, Rect, TypedSize2D, TypedScale}; use gfx_traits::Epoch; -use hyper::header::Headers; -use hyper::method::Method; +use http::HeaderMap; +use hyper::Method; use ipc_channel::{Error as IpcError}; use ipc_channel::ipc::{IpcReceiver, IpcSender}; use keyboard_types::KeyboardEvent; @@ -148,7 +149,7 @@ pub struct LoadData { deserialize_with = "::hyper_serde::deserialize", serialize_with = "::hyper_serde::serialize" )] - pub headers: Headers, + pub headers: HeaderMap, /// The data. pub data: Option<Vec<u8>>, /// The result of evaluating a javascript scheme url. @@ -180,8 +181,8 @@ impl LoadData { LoadData { url: url, creator_pipeline_id: creator_pipeline_id, - method: Method::Get, - headers: Headers::new(), + method: Method::GET, + headers: HeaderMap::new(), data: None, js_eval_result: None, referrer_policy: referrer_policy, diff --git a/components/webdriver_server/Cargo.toml b/components/webdriver_server/Cargo.toml index d5f4c6fd76c..18d13d309df 100644 --- a/components/webdriver_server/Cargo.toml +++ b/components/webdriver_server/Cargo.toml @@ -10,10 +10,10 @@ name = "webdriver_server" path = "lib.rs" [dependencies] -base64 = "0.6" -cookie = "0.10" +base64 = "0.9" +cookie = "0.11" euclid = "0.19" -hyper = "0.10" +hyper = "0.12" image = "0.19" ipc-channel = "0.11" keyboard-types = {version = "0.4.2-servo", features = ["serde"]} @@ -21,11 +21,12 @@ log = "0.4" msg = {path = "../msg"} net_traits = {path = "../net_traits"} regex = "1.0" -rustc-serialize = "0.3.4" +serde = "1" +serde_json = "1" script_traits = {path = "../script_traits"} servo_channel = {path = "../channel"} servo_config = {path = "../config"} servo_url = {path = "../url"} url = "1.2" uuid = {version = "0.6", features = ["v4"]} -webdriver = "0.35" +webdriver = "0.37" diff --git a/components/webdriver_server/lib.rs b/components/webdriver_server/lib.rs index 300096061d0..6c1bbbf8094 100644 --- a/components/webdriver_server/lib.rs +++ b/components/webdriver_server/lib.rs @@ -18,8 +18,10 @@ extern crate log; extern crate msg; extern crate net_traits; extern crate regex; -extern crate rustc_serialize; extern crate script_traits; +#[macro_use] +extern crate serde; +extern crate serde_json; extern crate servo_channel; extern crate servo_config; extern crate servo_url; @@ -29,77 +31,55 @@ extern crate webdriver; mod keys; use euclid::TypedSize2D; -use hyper::method::Method::{self, Post}; +use hyper::Method; use image::{DynamicImage, ImageFormat, RgbImage}; use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; use keys::keycodes_to_keys; use msg::constellation_msg::{BrowsingContextId, TopLevelBrowsingContextId, TraversalDirection}; use net_traits::image::base::PixelFormat; use regex::Captures; -use rustc_serialize::json::{Json, ToJson}; use script_traits::{ConstellationMsg, LoadData, WebDriverCommandMsg}; use script_traits::webdriver_msg::{LoadStatus, WebDriverCookieError, WebDriverFrameId}; -use script_traits::webdriver_msg::{WebDriverJSError, WebDriverJSResult, WebDriverScriptCommand}; +use script_traits::webdriver_msg::{WebDriverJSError, WebDriverJSResult, WebDriverScriptCommand, WebDriverJSValue}; +use serde::de::{Deserialize, Deserializer, MapAccess, Visitor}; +use serde::ser::{Serialize, Serializer}; +use serde_json::Value; use servo_channel::Sender; use servo_config::prefs::{PREFS, PrefValue}; use servo_url::ServoUrl; use std::borrow::ToOwned; use std::collections::BTreeMap; +use std::fmt; use std::net::{SocketAddr, SocketAddrV4}; use std::thread; use std::time::Duration; use uuid::Uuid; -use webdriver::command::{AddCookieParameters, GetParameters, JavascriptCommandParameters}; -use webdriver::command::{LocatorParameters, Parameters}; +use webdriver::command::{AddCookieParameters, GetParameters, JavascriptCommandParameters, LocatorParameters}; use webdriver::command::{SendKeysParameters, SwitchToFrameParameters, TimeoutsParameters}; -use webdriver::command::{WebDriverCommand, WebDriverExtensionCommand, WebDriverMessage}; -use webdriver::command::WindowRectParameters; -use webdriver::common::{Date, LocatorStrategy, Nullable, WebElement}; +use webdriver::command::{WebDriverCommand, WebDriverExtensionCommand, WebDriverMessage, WindowRectParameters}; +use webdriver::common::{Cookie, Date, LocatorStrategy, WebElement}; use webdriver::error::{ErrorStatus, WebDriverError, WebDriverResult}; use webdriver::httpapi::WebDriverExtensionRoute; -use webdriver::response::{Cookie, CookieResponse, CookiesResponse}; +use webdriver::response::{CookieResponse, CookiesResponse}; use webdriver::response::{ElementRectResponse, NewSessionResponse, ValueResponse}; use webdriver::response::{WebDriverResponse, WindowRectResponse}; use webdriver::server::{self, Session, WebDriverHandler}; fn extension_routes() -> Vec<(Method, &'static str, ServoExtensionRoute)> { - return vec![ - ( - Post, - "/session/{sessionId}/servo/prefs/get", - ServoExtensionRoute::GetPrefs, - ), - ( - Post, - "/session/{sessionId}/servo/prefs/set", - ServoExtensionRoute::SetPrefs, - ), - ( - Post, - "/session/{sessionId}/servo/prefs/reset", - ServoExtensionRoute::ResetPrefs, - ), - ]; + return vec![(Method::POST, "/session/{sessionId}/servo/prefs/get", ServoExtensionRoute::GetPrefs), + (Method::POST, "/session/{sessionId}/servo/prefs/set", ServoExtensionRoute::SetPrefs), + (Method::POST, "/session/{sessionId}/servo/prefs/reset", ServoExtensionRoute::ResetPrefs)] } fn cookie_msg_to_cookie(cookie: cookie_rs::Cookie) -> Cookie { Cookie { name: cookie.name().to_owned(), value: cookie.value().to_owned(), - path: match cookie.path() { - Some(path) => Nullable::Value(path.to_string()), - None => Nullable::Null, - }, - domain: match cookie.domain() { - Some(domain) => Nullable::Value(domain.to_string()), - None => Nullable::Null, - }, - expiry: match cookie.expires() { - Some(time) => Nullable::Value(Date::new(time.to_timespec().sec as u64)), - None => Nullable::Null, - }, - secure: cookie.secure(), - httpOnly: cookie.http_only(), + path: cookie.path().map(|s| s.to_owned()), + domain: cookie.domain().map(|s| s.to_owned()), + expiry: cookie.expires().map(|time| Date(time.to_timespec().sec as u64)), + secure: cookie.secure().unwrap_or(false), + httpOnly: cookie.http_only().unwrap_or(false), } } @@ -167,22 +147,20 @@ enum ServoExtensionRoute { impl WebDriverExtensionRoute for ServoExtensionRoute { type Command = ServoExtensionCommand; - fn command( - &self, - _captures: &Captures, - body_data: &Json, - ) -> WebDriverResult<WebDriverCommand<ServoExtensionCommand>> { + fn command(&self, + _captures: &Captures, + body_data: &Value) -> WebDriverResult<WebDriverCommand<ServoExtensionCommand>> { let command = match *self { ServoExtensionRoute::GetPrefs => { - let parameters: GetPrefsParameters = Parameters::from_json(&body_data)?; + let parameters: GetPrefsParameters = serde_json::from_value(body_data.clone())?; ServoExtensionCommand::GetPrefs(parameters) }, ServoExtensionRoute::SetPrefs => { - let parameters: SetPrefsParameters = Parameters::from_json(&body_data)?; + let parameters: SetPrefsParameters = serde_json::from_value(body_data.clone())?; ServoExtensionCommand::SetPrefs(parameters) }, ServoExtensionRoute::ResetPrefs => { - let parameters: GetPrefsParameters = Parameters::from_json(&body_data)?; + let parameters: GetPrefsParameters = serde_json::from_value(body_data.clone())?; ServoExtensionCommand::ResetPrefs(parameters) }, }; @@ -198,95 +176,148 @@ enum ServoExtensionCommand { } impl WebDriverExtensionCommand for ServoExtensionCommand { - fn parameters_json(&self) -> Option<Json> { + fn parameters_json(&self) -> Option<Value> { match *self { - ServoExtensionCommand::GetPrefs(ref x) => Some(x.to_json()), - ServoExtensionCommand::SetPrefs(ref x) => Some(x.to_json()), - ServoExtensionCommand::ResetPrefs(ref x) => Some(x.to_json()), + ServoExtensionCommand::GetPrefs(ref x) => serde_json::to_value(x).ok(), + ServoExtensionCommand::SetPrefs(ref x) => serde_json::to_value(x).ok(), + ServoExtensionCommand::ResetPrefs(ref x) => serde_json::to_value(x).ok(), } } } -#[derive(Clone, Debug, PartialEq)] -struct GetPrefsParameters { - prefs: Vec<String>, +struct SendableWebDriverJSValue(pub WebDriverJSValue); + +impl Serialize for SendableWebDriverJSValue { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + match self.0 { + WebDriverJSValue::Undefined => serializer.serialize_unit(), + WebDriverJSValue::Null => serializer.serialize_unit(), + WebDriverJSValue::Boolean(x) => serializer.serialize_bool(x), + WebDriverJSValue::Number(x) => serializer.serialize_f64(x), + WebDriverJSValue::String(ref x) => serializer.serialize_str(&x), + } + } } -impl Parameters for GetPrefsParameters { - fn from_json(body: &Json) -> WebDriverResult<GetPrefsParameters> { - let data = body.as_object().ok_or(WebDriverError::new( - ErrorStatus::InvalidArgument, - "Message body was not an object", - ))?; - let prefs_value = data.get("prefs").ok_or(WebDriverError::new( - ErrorStatus::InvalidArgument, - "Missing prefs key", - ))?; - let items = prefs_value.as_array().ok_or(WebDriverError::new( - ErrorStatus::InvalidArgument, - "prefs was not an array", - ))?; - let params = items - .iter() - .map(|x| { - x.as_string() - .map(|y| y.to_owned()) - .ok_or(WebDriverError::new( - ErrorStatus::InvalidArgument, - "Pref is not a string", - )) - }).collect::<Result<Vec<_>, _>>()?; - Ok(GetPrefsParameters { prefs: params }) +#[derive(Clone, Debug, PartialEq)] +struct WebDriverPrefValue(pub PrefValue); + +impl Serialize for WebDriverPrefValue { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + match self.0 { + PrefValue::Boolean(b) => serializer.serialize_bool(b), + PrefValue::String(ref s) => serializer.serialize_str(&s), + PrefValue::Number(f) => serializer.serialize_f64(f), + PrefValue::Missing => serializer.serialize_unit(), + } } } -impl ToJson for GetPrefsParameters { - fn to_json(&self) -> Json { - let mut data = BTreeMap::new(); - data.insert("prefs".to_owned(), self.prefs.to_json()); - Json::Object(data) +impl<'de> Deserialize<'de> for WebDriverPrefValue { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: Deserializer<'de>, + { + struct Visitor; + + + impl<'de> ::serde::de::Visitor<'de> for Visitor { + type Value = WebDriverPrefValue; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("preference value") + } + + fn visit_f64<E>(self, value: f64) -> Result<Self::Value, E> + where + E: ::serde::de::Error, + { + Ok(WebDriverPrefValue(PrefValue::Number(value))) + } + + fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E> + where + E: ::serde::de::Error, + { + Ok(WebDriverPrefValue(PrefValue::Number(value as f64))) + } + + fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E> + where + E: ::serde::de::Error, + { + Ok(WebDriverPrefValue(PrefValue::Number(value as f64))) + } + + fn visit_str<E>(self, value: &str) -> Result<Self::Value, E> + where + E: ::serde::de::Error, + { + Ok(WebDriverPrefValue(PrefValue::String(value.to_owned()))) + } + + fn visit_bool<E>(self, value: bool) -> Result<Self::Value, E> + where + E: ::serde::de::Error, + { + Ok(WebDriverPrefValue(PrefValue::Boolean(value))) + } + } + + deserializer.deserialize_any(Visitor) } } -#[derive(Clone, Debug, PartialEq)] + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +struct GetPrefsParameters { + prefs: Vec<String>, +} + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] struct SetPrefsParameters { - prefs: Vec<(String, PrefValue)>, + #[serde(deserialize_with = "map_to_vec")] + prefs: Vec<(String, WebDriverPrefValue)>, } -impl Parameters for SetPrefsParameters { - fn from_json(body: &Json) -> WebDriverResult<SetPrefsParameters> { - let data = body.as_object().ok_or(WebDriverError::new( - ErrorStatus::InvalidArgument, - "Message body was not an object", - ))?; - let items = data - .get("prefs") - .ok_or(WebDriverError::new( - ErrorStatus::InvalidArgument, - "Missing prefs key", - ))?.as_object() - .ok_or(WebDriverError::new( - ErrorStatus::InvalidArgument, - "prefs was not an array", - ))?; - let mut params = Vec::with_capacity(items.len()); - for (name, val) in items.iter() { - let value = PrefValue::from_json(val.clone()).or(Err(WebDriverError::new( - ErrorStatus::InvalidArgument, - "Pref is not a boolean or string", - )))?; - let key = name.to_owned(); - params.push((key, value)); - } - Ok(SetPrefsParameters { prefs: params }) - } +fn map_to_vec<'de, D>(de: D) -> Result<Vec<(String, WebDriverPrefValue)>, D::Error> + where D: Deserializer<'de> { + de.deserialize_map(TupleVecMapVisitor) } -impl ToJson for SetPrefsParameters { - fn to_json(&self) -> Json { - let mut data = BTreeMap::new(); - data.insert("prefs".to_owned(), self.prefs.to_json()); - Json::Object(data) +struct TupleVecMapVisitor; + +impl<'de> Visitor<'de> for TupleVecMapVisitor +{ + type Value = Vec<(String, WebDriverPrefValue)>; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a map") + } + + #[inline] + fn visit_unit<E>(self) -> Result<Self::Value, E> { + Ok(Vec::new()) + } + + #[inline] + fn visit_map<T>(self, mut access: T) -> Result<Self::Value, T::Error> + where + T: MapAccess<'de>, + { + let mut values = Vec::new(); + + while let Some((key, value)) = access.next_entry()? { + values.push((key, value)); + } + + Ok(values) } } @@ -349,12 +380,11 @@ impl Handler { let top_level_browsing_context_id = self.focus_top_level_browsing_context_id()?; let browsing_context_id = BrowsingContextId::from(top_level_browsing_context_id); let session = WebDriverSession::new(browsing_context_id, top_level_browsing_context_id); - let mut capabilities = BTreeMap::new(); - capabilities.insert("browserName".to_owned(), "servo".to_json()); - capabilities.insert("browserVersion".to_owned(), "0.0.1".to_json()); - capabilities.insert("acceptInsecureCerts".to_owned(), false.to_json()); - let response = - NewSessionResponse::new(session.id.to_string(), Json::Object(capabilities)); + let mut capabilities = serde_json::Map::new(); + capabilities.insert("browserName".to_owned(), serde_json::to_value("servo")?); + capabilities.insert("browserVersion".to_owned(), serde_json::to_value("0.0.1")?); + capabilities.insert("acceptInsecureCerts".to_owned(), serde_json::to_value(false)?); + let response = NewSessionResponse::new(session.id.to_string(), Value::Object(capabilities)); debug!("new session created {}.", session.id); self.session = Some(session); Ok(WebDriverResponse::NewSession(response)) @@ -447,9 +477,7 @@ impl Handler { let url = receiver.recv().unwrap(); - Ok(WebDriverResponse::Generic(ValueResponse::new( - url.as_str().to_json(), - ))) + Ok(WebDriverResponse::Generic(ValueResponse(serde_json::to_value(url.as_str())?))) } fn handle_window_size(&self) -> WebDriverResult<WebDriverResponse> { @@ -478,12 +506,12 @@ impl Handler { ) -> WebDriverResult<WebDriverResponse> { let (sender, receiver) = ipc::channel().unwrap(); let width = match params.width { - Nullable::Value(v) => v, - Nullable::Null => 0, + Some(v) => v, + None => 0, }; let height = match params.height { - Nullable::Value(v) => v, - Nullable::Null => 0, + Some(v) => v, + None => 0, }; let size = TypedSize2D::new(width as u32, height as u32); let top_level_browsing_context_id = self.session()?.top_level_browsing_context_id; @@ -526,13 +554,8 @@ impl Handler { ))?; match receiver.recv().unwrap() { - Ok(is_enabled) => Ok(WebDriverResponse::Generic(ValueResponse::new( - is_enabled.to_json(), - ))), - Err(_) => Err(WebDriverError::new( - ErrorStatus::StaleElementReference, - "Element not found", - )), + Ok(is_enabled) => Ok(WebDriverResponse::Generic(ValueResponse(serde_json::to_value(is_enabled)?))), + Err(_) => Err(WebDriverError::new(ErrorStatus::StaleElementReference, "Element not found")) } } @@ -545,13 +568,8 @@ impl Handler { ))?; match receiver.recv().unwrap() { - Ok(is_selected) => Ok(WebDriverResponse::Generic(ValueResponse::new( - is_selected.to_json(), - ))), - Err(_) => Err(WebDriverError::new( - ErrorStatus::StaleElementReference, - "Element not found", - )), + Ok(is_selected) => Ok(WebDriverResponse::Generic(ValueResponse(serde_json::to_value(is_selected)?))), + Err(_) => Err(WebDriverError::new(ErrorStatus::StaleElementReference, "Element not found")) } } @@ -590,27 +608,21 @@ impl Handler { self.top_level_script_command(WebDriverScriptCommand::GetTitle(sender))?; let value = receiver.recv().unwrap(); - Ok(WebDriverResponse::Generic(ValueResponse::new( - value.to_json(), - ))) + Ok(WebDriverResponse::Generic(ValueResponse(serde_json::to_value(value)?))) } fn handle_window_handle(&self) -> WebDriverResult<WebDriverResponse> { // For now we assume there's only one window so just use the session // id as the window id let handle = self.session.as_ref().unwrap().id.to_string(); - Ok(WebDriverResponse::Generic(ValueResponse::new( - handle.to_json(), - ))) + Ok(WebDriverResponse::Generic(ValueResponse(serde_json::to_value(handle)?))) } fn handle_window_handles(&self) -> WebDriverResult<WebDriverResponse> { // For now we assume there's only one window so just use the session // id as the window id - let handles = vec![self.session.as_ref().unwrap().id.to_string().to_json()]; - Ok(WebDriverResponse::Generic(ValueResponse::new( - handles.to_json(), - ))) + let handles = vec![serde_json::to_value(self.session.as_ref().unwrap().id.to_string())?]; + Ok(WebDriverResponse::Generic(ValueResponse(serde_json::to_value(handles)?))) } fn handle_find_element( @@ -631,13 +643,13 @@ impl Handler { match receiver.recv().unwrap() { Ok(value) => { - let value_resp = value.map(|x| WebElement::new(x).to_json()).to_json(); - Ok(WebDriverResponse::Generic(ValueResponse::new(value_resp))) - }, - Err(_) => Err(WebDriverError::new( - ErrorStatus::InvalidSelector, - "Invalid selector", - )), + let value_resp = serde_json::to_value( + value.map(|x| serde_json::to_value(WebElement::new(x)).unwrap()) + )?; + Ok(WebDriverResponse::Generic(ValueResponse(value_resp))) + } + Err(_) => Err(WebDriverError::new(ErrorStatus::InvalidSelector, + "Invalid selector")) } } @@ -647,14 +659,14 @@ impl Handler { ) -> WebDriverResult<WebDriverResponse> { use webdriver::common::FrameId; let frame_id = match parameters.id { - FrameId::Null => { + None => { let session = self.session_mut()?; session.browsing_context_id = BrowsingContextId::from(session.top_level_browsing_context_id); return Ok(WebDriverResponse::Void); }, - FrameId::Short(ref x) => WebDriverFrameId::Short(*x), - FrameId::Element(ref x) => WebDriverFrameId::Element(x.id.clone()), + Some(FrameId::Short(ref x)) => WebDriverFrameId::Short(*x), + Some(FrameId::Element(ref x)) => WebDriverFrameId::Element(x.id.clone()) }; self.switch_to_frame(frame_id) @@ -704,18 +716,12 @@ impl Handler { self.browsing_context_script_command(cmd)?; match receiver.recv().unwrap() { Ok(value) => { - let resp_value: Vec<Json> = value - .into_iter() - .map(|x| WebElement::new(x).to_json()) - .collect(); - Ok(WebDriverResponse::Generic(ValueResponse::new( - resp_value.to_json(), - ))) - }, - Err(_) => Err(WebDriverError::new( - ErrorStatus::InvalidSelector, - "Invalid selector", - )), + let resp_value: Vec<Value> = value.into_iter().map( + |x| serde_json::to_value(WebElement::new(x)).unwrap()).collect(); + Ok(WebDriverResponse::Generic(ValueResponse(serde_json::to_value(resp_value)?))) + } + Err(_) => Err(WebDriverError::new(ErrorStatus::InvalidSelector, + "Invalid selector")) } } @@ -746,13 +752,9 @@ impl Handler { let cmd = WebDriverScriptCommand::GetElementText(element.id.clone(), sender); self.browsing_context_script_command(cmd)?; match receiver.recv().unwrap() { - Ok(value) => Ok(WebDriverResponse::Generic(ValueResponse::new( - value.to_json(), - ))), - Err(_) => Err(WebDriverError::new( - ErrorStatus::StaleElementReference, - "Unable to find element in document", - )), + Ok(value) => Ok(WebDriverResponse::Generic(ValueResponse(serde_json::to_value(value)?))), + Err(_) => Err(WebDriverError::new(ErrorStatus::StaleElementReference, + "Unable to find element in document")) } } @@ -760,13 +762,8 @@ impl Handler { let (sender, receiver) = ipc::channel().unwrap(); let cmd = WebDriverScriptCommand::GetActiveElement(sender); self.browsing_context_script_command(cmd)?; - let value = receiver - .recv() - .unwrap() - .map(|x| WebElement::new(x).to_json()); - Ok(WebDriverResponse::Generic(ValueResponse::new( - value.to_json(), - ))) + let value = receiver.recv().unwrap().map(|x| serde_json::to_value(WebElement::new(x)).unwrap()); + Ok(WebDriverResponse::Generic(ValueResponse(serde_json::to_value(value)?))) } fn handle_element_tag_name(&self, element: &WebElement) -> WebDriverResult<WebDriverResponse> { @@ -774,13 +771,9 @@ impl Handler { let cmd = WebDriverScriptCommand::GetElementTagName(element.id.clone(), sender); self.browsing_context_script_command(cmd)?; match receiver.recv().unwrap() { - Ok(value) => Ok(WebDriverResponse::Generic(ValueResponse::new( - value.to_json(), - ))), - Err(_) => Err(WebDriverError::new( - ErrorStatus::StaleElementReference, - "Unable to find element in document", - )), + Ok(value) => Ok(WebDriverResponse::Generic(ValueResponse(serde_json::to_value(value)?))), + Err(_) => Err(WebDriverError::new(ErrorStatus::StaleElementReference, + "Unable to find element in document")) } } @@ -797,13 +790,9 @@ impl Handler { ); self.browsing_context_script_command(cmd)?; match receiver.recv().unwrap() { - Ok(value) => Ok(WebDriverResponse::Generic(ValueResponse::new( - value.to_json(), - ))), - Err(_) => Err(WebDriverError::new( - ErrorStatus::StaleElementReference, - "Unable to find element in document", - )), + Ok(value) => Ok(WebDriverResponse::Generic(ValueResponse(serde_json::to_value(value)?))), + Err(_) => Err(WebDriverError::new(ErrorStatus::StaleElementReference, + "Unable to find element in document")) } } @@ -817,13 +806,9 @@ impl Handler { WebDriverScriptCommand::GetElementCSS(element.id.clone(), name.to_owned(), sender); self.browsing_context_script_command(cmd)?; match receiver.recv().unwrap() { - Ok(value) => Ok(WebDriverResponse::Generic(ValueResponse::new( - value.to_json(), - ))), - Err(_) => Err(WebDriverError::new( - ErrorStatus::StaleElementReference, - "Unable to find element in document", - )), + Ok(value) => Ok(WebDriverResponse::Generic(ValueResponse(serde_json::to_value(value)?))), + Err(_) => Err(WebDriverError::new(ErrorStatus::StaleElementReference, + "Unable to find element in document")) } } @@ -832,13 +817,10 @@ impl Handler { let cmd = WebDriverScriptCommand::GetCookies(sender); self.browsing_context_script_command(cmd)?; let cookies = receiver.recv().unwrap(); - let response = cookies - .into_iter() - .map(|cookie| cookie_msg_to_cookie(cookie.into_inner())) - .collect::<Vec<Cookie>>(); - Ok(WebDriverResponse::Cookies(CookiesResponse { - value: response, - })) + let response = cookies.into_iter().map(|cookie| { + cookie_msg_to_cookie(cookie.into_inner()) + }).collect::<Vec<Cookie>>(); + Ok(WebDriverResponse::Cookies(CookiesResponse(response))) } fn handle_get_cookie(&self, name: &str) -> WebDriverResult<WebDriverResponse> { @@ -846,14 +828,10 @@ impl Handler { let cmd = WebDriverScriptCommand::GetCookie(name.to_owned(), sender); self.browsing_context_script_command(cmd)?; let cookies = receiver.recv().unwrap(); - let response = cookies - .into_iter() - .map(|cookie| cookie_msg_to_cookie(cookie.into_inner())) - .next() - .unwrap(); - Ok(WebDriverResponse::Cookie(CookieResponse { - value: response, - })) + let response = cookies.into_iter().map(|cookie| { + cookie_msg_to_cookie(cookie.into_inner()) + }).next().unwrap(); + Ok(WebDriverResponse::Cookie(CookieResponse(response))) } fn handle_add_cookie( @@ -866,11 +844,11 @@ impl Handler { .secure(params.secure) .http_only(params.httpOnly); let cookie = match params.domain { - Nullable::Value(ref domain) => cookie.domain(domain.to_owned()), + Some(ref domain) => cookie.domain(domain.to_owned()), _ => cookie, }; let cookie = match params.path { - Nullable::Value(ref path) => cookie.path(path.to_owned()).finish(), + Some(ref path) => cookie.path(path.to_owned()).finish(), _ => cookie.finish(), }; @@ -958,9 +936,8 @@ impl Handler { result: WebDriverJSResult, ) -> WebDriverResult<WebDriverResponse> { match result { - Ok(value) => Ok(WebDriverResponse::Generic(ValueResponse::new( - value.to_json(), - ))), + Ok(value) => + Ok(WebDriverResponse::Generic(ValueResponse(serde_json::to_value(SendableWebDriverJSValue(value))?))), Err(WebDriverJSError::Timeout) => Err(WebDriverError::new(ErrorStatus::Timeout, "")), Err(WebDriverJSError::UnknownType) => Err(WebDriverError::new( ErrorStatus::UnsupportedOperation, @@ -1055,9 +1032,7 @@ impl Handler { .unwrap(); let encoded = base64::encode(&png_data); - Ok(WebDriverResponse::Generic(ValueResponse::new( - encoded.to_json(), - ))) + Ok(WebDriverResponse::Generic(ValueResponse(serde_json::to_value(encoded)?))) } fn handle_get_prefs( @@ -1067,12 +1042,10 @@ impl Handler { let prefs = parameters .prefs .iter() - .map(|item| (item.clone(), PREFS.get(item).to_json())) + .map(|item| (item.clone(), serde_json::to_value(PREFS.get(item)).unwrap())) .collect::<BTreeMap<_, _>>(); - Ok(WebDriverResponse::Generic(ValueResponse::new( - prefs.to_json(), - ))) + Ok(WebDriverResponse::Generic(ValueResponse(serde_json::to_value(prefs)?))) } fn handle_set_prefs( @@ -1080,7 +1053,7 @@ impl Handler { parameters: &SetPrefsParameters, ) -> WebDriverResult<WebDriverResponse> { for &(ref key, ref value) in parameters.prefs.iter() { - PREFS.set(key, value.clone()); + PREFS.set(key, value.0.clone()); } Ok(WebDriverResponse::Void) } @@ -1096,12 +1069,10 @@ impl Handler { parameters .prefs .iter() - .map(|item| (item.clone(), PREFS.reset(item).to_json())) + .map(|item| (item.clone(), serde_json::to_value(PREFS.reset(item)).unwrap())) .collect::<BTreeMap<_, _>>() }; - Ok(WebDriverResponse::Generic(ValueResponse::new( - prefs.to_json(), - ))) + Ok(WebDriverResponse::Generic(ValueResponse(serde_json::to_value(prefs)?))) } } |