Rustfmt net crate

This commit is contained in:
Pyfisch 2018-11-03 15:28:48 +01:00
parent ba1ed11ced
commit 2481ad25f8
30 changed files with 4957 additions and 2870 deletions

View file

@ -19,16 +19,16 @@ use servo_url::ServoUrl;
/// https://fetch.spec.whatwg.org/#concept-basic-fetch (partial) /// https://fetch.spec.whatwg.org/#concept-basic-fetch (partial)
// TODO: make async. // TODO: make async.
pub fn load_blob_sync pub fn load_blob_sync(
(url: ServoUrl, url: ServoUrl,
filemanager: FileManager) filemanager: FileManager,
-> Result<(HeaderMap, Vec<u8>), NetworkError> { ) -> Result<(HeaderMap, Vec<u8>), NetworkError> {
let (id, origin) = match parse_blob_url(&url) { let (id, origin) = match parse_blob_url(&url) {
Ok((id, origin)) => (id, origin), Ok((id, origin)) => (id, origin),
Err(()) => { Err(()) => {
let e = format!("Invalid blob URL format {:?}", url); let e = format!("Invalid blob URL format {:?}", url);
return Err(NetworkError::Internal(e)); return Err(NetworkError::Internal(e));
} },
}; };
let (sender, receiver) = ipc::channel().unwrap(); let (sender, receiver) = ipc::channel().unwrap();
@ -38,11 +38,13 @@ pub fn load_blob_sync
let blob_buf = match receiver.recv().unwrap() { let blob_buf = match receiver.recv().unwrap() {
Ok(ReadFileProgress::Meta(blob_buf)) => blob_buf, Ok(ReadFileProgress::Meta(blob_buf)) => blob_buf,
Ok(_) => { Ok(_) => {
return Err(NetworkError::Internal("Invalid filemanager reply".to_string())); return Err(NetworkError::Internal(
} "Invalid filemanager reply".to_string(),
));
},
Err(e) => { Err(e) => {
return Err(NetworkError::Internal(format!("{:?}", e))); return Err(NetworkError::Internal(format!("{:?}", e)));
} },
}; };
let content_type: Mime = blob_buf.type_string.parse().unwrap_or(mime::TEXT_PLAIN); let content_type: Mime = blob_buf.type_string.parse().unwrap_or(mime::TEXT_PLAIN);
@ -51,19 +53,31 @@ pub fn load_blob_sync
let mut headers = HeaderMap::new(); let mut headers = HeaderMap::new();
if let Some(name) = blob_buf.filename { if let Some(name) = blob_buf.filename {
let charset = charset.map(|c| c.as_ref().into()).unwrap_or("us-ascii".to_owned()); 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 // TODO(eijebong): Replace this once the typed header is there
headers.insert( headers.insert(
header::CONTENT_DISPOSITION, header::CONTENT_DISPOSITION,
HeaderValue::from_bytes( HeaderValue::from_bytes(
format!("inline; {}", format!(
"inline; {}",
if charset.to_lowercase() == "utf-8" { if charset.to_lowercase() == "utf-8" {
format!("filename=\"{}\"", String::from_utf8(name.as_bytes().into()).unwrap()) format!(
"filename=\"{}\"",
String::from_utf8(name.as_bytes().into()).unwrap()
)
} else { } else {
format!("filename*=\"{}\"''{}", charset, http_percent_encode(name.as_bytes())) format!(
"filename*=\"{}\"''{}",
charset,
http_percent_encode(name.as_bytes())
)
} }
).as_bytes() )
).unwrap() .as_bytes(),
)
.unwrap(),
); );
} }
@ -77,16 +91,18 @@ pub fn load_blob_sync
match receiver.recv().unwrap() { match receiver.recv().unwrap() {
Ok(ReadFileProgress::Partial(ref mut new_bytes)) => { Ok(ReadFileProgress::Partial(ref mut new_bytes)) => {
bytes.append(new_bytes); bytes.append(new_bytes);
} },
Ok(ReadFileProgress::EOF) => { Ok(ReadFileProgress::EOF) => {
return Ok((headers, bytes)); return Ok((headers, bytes));
} },
Ok(_) => { Ok(_) => {
return Err(NetworkError::Internal("Invalid filemanager reply".to_string())); return Err(NetworkError::Internal(
} "Invalid filemanager reply".to_string(),
));
},
Err(e) => { Err(e) => {
return Err(NetworkError::Internal(format!("{:?}", e))); return Err(NetworkError::Internal(format!("{:?}", e)));
} },
} }
} }
} }

View file

@ -20,7 +20,7 @@ use tokio::prelude::future::Executor;
pub const BUF_SIZE: usize = 32768; pub const BUF_SIZE: usize = 32768;
pub struct HttpConnector { pub struct HttpConnector {
inner: HyperHttpConnector inner: HyperHttpConnector,
} }
impl HttpConnector { impl HttpConnector {
@ -28,9 +28,7 @@ impl HttpConnector {
let mut inner = HyperHttpConnector::new(4); let mut inner = HyperHttpConnector::new(4);
inner.enforce_http(false); inner.enforce_http(false);
inner.set_happy_eyeballs_timeout(None); inner.set_happy_eyeballs_timeout(None);
HttpConnector { HttpConnector { inner }
inner
}
} }
} }
@ -60,10 +58,7 @@ impl WrappedBody {
} }
pub fn new_with_decoder(body: Body, decoder: Decoder) -> Self { pub fn new_with_decoder(body: Body, decoder: Decoder) -> Self {
WrappedBody { WrappedBody { body, decoder }
body,
decoder,
}
} }
} }
@ -90,7 +85,7 @@ impl Stream for WrappedBody {
let len = decoder.read(&mut buf).ok()?; let len = decoder.read(&mut buf).ok()?;
buf.truncate(len); buf.truncate(len);
Some(buf.into()) Some(buf.into())
} },
Decoder::Gzip(None) => { Decoder::Gzip(None) => {
let mut buf = vec![0; BUF_SIZE]; let mut buf = vec![0; BUF_SIZE];
let mut decoder = GzDecoder::new(Cursor::new(chunk.into_bytes())); let mut decoder = GzDecoder::new(Cursor::new(chunk.into_bytes()));
@ -98,21 +93,21 @@ impl Stream for WrappedBody {
buf.truncate(len); buf.truncate(len);
self.decoder = Decoder::Gzip(Some(decoder)); self.decoder = Decoder::Gzip(Some(decoder));
Some(buf.into()) Some(buf.into())
} },
Decoder::Deflate(ref mut decoder) => { Decoder::Deflate(ref mut decoder) => {
let mut buf = vec![0; BUF_SIZE]; let mut buf = vec![0; BUF_SIZE];
*decoder.get_mut() = Cursor::new(chunk.into_bytes()); *decoder.get_mut() = Cursor::new(chunk.into_bytes());
let len = decoder.read(&mut buf).ok()?; let len = decoder.read(&mut buf).ok()?;
buf.truncate(len); buf.truncate(len);
Some(buf.into()) Some(buf.into())
} },
Decoder::Brotli(ref mut decoder) => { Decoder::Brotli(ref mut decoder) => {
let mut buf = vec![0; BUF_SIZE]; let mut buf = vec![0; BUF_SIZE];
decoder.get_mut().get_mut().extend(&chunk.into_bytes()); decoder.get_mut().get_mut().extend(&chunk.into_bytes());
let len = decoder.read(&mut buf).ok()?; let len = decoder.read(&mut buf).ok()?;
buf.truncate(len); buf.truncate(len);
Some(buf.into()) Some(buf.into())
} },
} }
} else { } else {
None None
@ -134,33 +129,46 @@ pub fn create_ssl_connector_builder(certs: &str) -> SslConnectorBuilder {
let (cert, rest) = certs.split_at(index + token.len()); let (cert, rest) = certs.split_at(index + token.len());
certs = rest; certs = rest;
let cert = x509::X509::from_pem(cert.as_bytes()).unwrap(); let cert = x509::X509::from_pem(cert.as_bytes()).unwrap();
ssl_connector_builder.cert_store_mut().add_cert(cert).or_else(|e| { ssl_connector_builder
let v: Option<Option<&str>> = e.errors().iter().nth(0).map(|e| e.reason()); .cert_store_mut()
if v == Some(Some("cert already in hash table")) { .add_cert(cert)
warn!("Cert already in hash table. Ignoring."); .or_else(|e| {
// Ignore error X509_R_CERT_ALREADY_IN_HASH_TABLE which means the let v: Option<Option<&str>> = e.errors().iter().nth(0).map(|e| e.reason());
// certificate is already in the store. if v == Some(Some("cert already in hash table")) {
Ok(()) warn!("Cert already in hash table. Ignoring.");
} else { // Ignore error X509_R_CERT_ALREADY_IN_HASH_TABLE which means the
Err(e) // certificate is already in the store.
} Ok(())
}).expect("could not set CA file"); } else {
Err(e)
}
})
.expect("could not set CA file");
} else { } else {
break; break;
} }
} }
ssl_connector_builder.set_cipher_list(DEFAULT_CIPHERS).expect("could not set ciphers"); ssl_connector_builder
ssl_connector_builder.set_options(SslOptions::NO_SSLV2 | SslOptions::NO_SSLV3 | SslOptions::NO_COMPRESSION); .set_cipher_list(DEFAULT_CIPHERS)
.expect("could not set ciphers");
ssl_connector_builder
.set_options(SslOptions::NO_SSLV2 | SslOptions::NO_SSLV3 | SslOptions::NO_COMPRESSION);
ssl_connector_builder ssl_connector_builder
} }
pub fn create_http_client<E>(ssl_connector_builder: SslConnectorBuilder, executor: E) pub fn create_http_client<E>(
-> Client<Connector, WrappedBody> ssl_connector_builder: SslConnectorBuilder,
where executor: E,
E: Executor<Box<Future<Error=(), Item=()> + Send + 'static>> + Sync + Send + 'static ) -> 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(); let connector =
Client::builder().http1_title_case_headers(true).executor(executor).build(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 // The basic logic here is to prefer ciphers with ECDSA certificates, Forward

View file

@ -19,23 +19,32 @@ use time::{Tm, now, at, Duration};
/// which cookie-rs and hyper's header parsing do not support. /// which cookie-rs and hyper's header parsing do not support.
#[derive(Clone, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Cookie { pub struct Cookie {
#[serde(deserialize_with = "hyper_serde::deserialize", #[serde(
serialize_with = "hyper_serde::serialize")] deserialize_with = "hyper_serde::deserialize",
serialize_with = "hyper_serde::serialize"
)]
pub cookie: cookie_rs::Cookie<'static>, pub cookie: cookie_rs::Cookie<'static>,
pub host_only: bool, pub host_only: bool,
pub persistent: bool, pub persistent: bool,
#[serde(deserialize_with = "hyper_serde::deserialize", #[serde(
serialize_with = "hyper_serde::serialize")] deserialize_with = "hyper_serde::deserialize",
serialize_with = "hyper_serde::serialize"
)]
pub creation_time: Tm, pub creation_time: Tm,
#[serde(deserialize_with = "hyper_serde::deserialize", #[serde(
serialize_with = "hyper_serde::serialize")] deserialize_with = "hyper_serde::deserialize",
serialize_with = "hyper_serde::serialize"
)]
pub last_access: Tm, pub last_access: Tm,
pub expiry_time: Option<Serde<Tm>>, pub expiry_time: Option<Serde<Tm>>,
} }
impl Cookie { impl Cookie {
pub fn from_cookie_string(cookie_str: String, request: &ServoUrl, pub fn from_cookie_string(
source: CookieSource) -> Option<Cookie> { cookie_str: String,
request: &ServoUrl,
source: CookieSource,
) -> Option<Cookie> {
cookie_rs::Cookie::parse(cookie_str) cookie_rs::Cookie::parse(cookie_str)
.ok() .ok()
.map(|cookie| Cookie::new_wrapped(cookie, request, source)) .map(|cookie| Cookie::new_wrapped(cookie, request, source))
@ -43,15 +52,21 @@ impl Cookie {
} }
/// <http://tools.ietf.org/html/rfc6265#section-5.3> /// <http://tools.ietf.org/html/rfc6265#section-5.3>
pub fn new_wrapped(mut cookie: cookie_rs::Cookie<'static>, request: &ServoUrl, source: CookieSource) pub fn new_wrapped(
-> Option<Cookie> { mut cookie: cookie_rs::Cookie<'static>,
request: &ServoUrl,
source: CookieSource,
) -> Option<Cookie> {
// Step 3 // Step 3
let (persistent, expiry_time) = match (cookie.max_age(), cookie.expires()) { let (persistent, expiry_time) = match (cookie.max_age(), cookie.expires()) {
(Some(max_age), _) => { (Some(max_age), _) => (
(true, Some(at(now().to_timespec() + Duration::seconds(max_age.num_seconds())))) true,
} Some(at(
now().to_timespec() + Duration::seconds(max_age.num_seconds())
)),
),
(_, Some(expires)) => (true, Some(expires)), (_, Some(expires)) => (true, Some(expires)),
_ => (false, None) _ => (false, None),
}; };
let url_host = request.host_str().unwrap_or("").to_owned(); let url_host = request.host_str().unwrap_or("").to_owned();
@ -64,7 +79,7 @@ impl Cookie {
if domain == url_host { if domain == url_host {
domain = "".to_string(); domain = "".to_string();
} else { } else {
return None return None;
} }
} }
@ -83,16 +98,18 @@ impl Cookie {
// Step 7 // Step 7
let mut has_path_specified = true; let mut has_path_specified = true;
let mut path = cookie.path().unwrap_or_else(|| { let mut path = cookie
has_path_specified = false; .path()
"" .unwrap_or_else(|| {
}).to_owned(); has_path_specified = false;
""
})
.to_owned();
if path.chars().next() != Some('/') { if path.chars().next() != Some('/') {
path = Cookie::default_path(&request.path().to_owned()).to_string(); path = Cookie::default_path(&request.path().to_owned()).to_string();
} }
cookie.set_path(path); cookie.set_path(path);
// Step 10 // Step 10
if cookie.http_only().unwrap_or(false) && source == CookieSource::NonHTTP { if cookie.http_only().unwrap_or(false) && source == CookieSource::NonHTTP {
return None; return None;
@ -101,14 +118,14 @@ impl Cookie {
// https://tools.ietf.org/html/draft-west-cookie-prefixes-04#section-4 // https://tools.ietf.org/html/draft-west-cookie-prefixes-04#section-4
// Step 1 of cookie prefixes // Step 1 of cookie prefixes
if (cookie.name().starts_with("__Secure-") || cookie.name().starts_with("__Host-")) && if (cookie.name().starts_with("__Secure-") || cookie.name().starts_with("__Host-")) &&
!(cookie.secure().unwrap_or(false) && request.is_secure_scheme()) !(cookie.secure().unwrap_or(false) && request.is_secure_scheme())
{ {
return None; return None;
} }
// Step 2 of cookie prefixes // Step 2 of cookie prefixes
if cookie.name().starts_with("__Host-") && if cookie.name().starts_with("__Host-") &&
!(host_only && has_path_specified && cookie.path().unwrap() == "/") !(host_only && has_path_specified && cookie.path().unwrap() == "/")
{ {
return None; return None;
} }
@ -152,16 +169,16 @@ impl Cookie {
// The cookie-path and the request-path are identical. // The cookie-path and the request-path are identical.
request_path == cookie_path || request_path == cookie_path ||
(request_path.starts_with(cookie_path) &&
(request_path.starts_with(cookie_path) && ( (
// The cookie-path is a prefix of the request-path, and the last // The cookie-path is a prefix of the request-path, and the last
// character of the cookie-path is %x2F ("/"). // character of the cookie-path is %x2F ("/").
cookie_path.ends_with("/") || cookie_path.ends_with("/") ||
// The cookie-path is a prefix of the request-path, and the first // The cookie-path is a prefix of the request-path, and the first
// character of the request-path that is not included in the cookie- // character of the request-path that is not included in the cookie-
// path is a %x2F ("/") character. // path is a %x2F ("/") character.
request_path[cookie_path.len()..].starts_with("/") request_path[cookie_path.len()..].starts_with("/")
)) ))
} }
// http://tools.ietf.org/html/rfc6265#section-5.1.3 // http://tools.ietf.org/html/rfc6265#section-5.1.3
@ -170,10 +187,10 @@ impl Cookie {
let domain_string = &domain_string.to_lowercase(); let domain_string = &domain_string.to_lowercase();
string == domain_string || string == domain_string ||
(string.ends_with(domain_string) && (string.ends_with(domain_string) &&
string.as_bytes()[string.len()-domain_string.len()-1] == b'.' && string.as_bytes()[string.len() - domain_string.len() - 1] == b'.' &&
string.parse::<Ipv4Addr>().is_err() && string.parse::<Ipv4Addr>().is_err() &&
string.parse::<Ipv6Addr>().is_err()) string.parse::<Ipv6Addr>().is_err())
} }
// http://tools.ietf.org/html/rfc6265#section-5.4 step 1 // http://tools.ietf.org/html/rfc6265#section-5.4 step 1

View file

@ -33,7 +33,12 @@ impl CookieStorage {
} }
// http://tools.ietf.org/html/rfc6265#section-5.3 // http://tools.ietf.org/html/rfc6265#section-5.3
pub fn remove(&mut self, cookie: &Cookie, url: &ServoUrl, source: CookieSource) -> Result<Option<Cookie>, ()> { pub fn remove(
&mut self,
cookie: &Cookie,
url: &ServoUrl,
source: CookieSource,
) -> Result<Option<Cookie>, ()> {
let domain = reg_host(cookie.cookie.domain().as_ref().unwrap_or(&"")); let domain = reg_host(cookie.cookie.domain().as_ref().unwrap_or(&""));
let cookies = self.cookies_map.entry(domain).or_insert(vec![]); let cookies = self.cookies_map.entry(domain).or_insert(vec![]);
@ -47,10 +52,10 @@ impl CookieStorage {
let existing_path = c.cookie.path().as_ref().unwrap().to_owned(); let existing_path = c.cookie.path().as_ref().unwrap().to_owned();
c.cookie.name() == cookie.cookie.name() && c.cookie.name() == cookie.cookie.name() &&
c.cookie.secure().unwrap_or(false) && c.cookie.secure().unwrap_or(false) &&
(Cookie::domain_match(new_domain, existing_domain) || (Cookie::domain_match(new_domain, existing_domain) ||
Cookie::domain_match(existing_domain, new_domain)) && Cookie::domain_match(existing_domain, new_domain)) &&
Cookie::path_match(new_path, existing_path) Cookie::path_match(new_path, existing_path)
}); });
if any_overlapping { if any_overlapping {
@ -61,8 +66,8 @@ impl CookieStorage {
// Step 11.1 // Step 11.1
let position = cookies.iter().position(|c| { let position = cookies.iter().position(|c| {
c.cookie.domain() == cookie.cookie.domain() && c.cookie.domain() == cookie.cookie.domain() &&
c.cookie.path() == cookie.cookie.path() && c.cookie.path() == cookie.cookie.path() &&
c.cookie.name() == cookie.cookie.name() c.cookie.name() == cookie.cookie.name()
}); });
if let Some(ind) = position { if let Some(ind) = position {
@ -111,7 +116,9 @@ impl CookieStorage {
let new_len = cookies.len(); let new_len = cookies.len();
// https://www.ietf.org/id/draft-ietf-httpbis-cookie-alone-01.txt // https://www.ietf.org/id/draft-ietf-httpbis-cookie-alone-01.txt
if new_len == old_len && !evict_one_cookie(cookie.cookie.secure().unwrap_or(false), cookies) { if new_len == old_len &&
!evict_one_cookie(cookie.cookie.secure().unwrap_or(false), cookies)
{
return; return;
} }
} }
@ -126,7 +133,7 @@ impl CookieStorage {
let a_creation_time = a.creation_time.to_timespec(); let a_creation_time = a.creation_time.to_timespec();
let b_creation_time = b.creation_time.to_timespec(); let b_creation_time = b.creation_time.to_timespec();
a_creation_time.cmp(&b_creation_time) a_creation_time.cmp(&b_creation_time)
} },
// Ensure that longer paths are sorted earlier than shorter paths // Ensure that longer paths are sorted earlier than shorter paths
Ordering::Greater => Ordering::Less, Ordering::Greater => Ordering::Less,
Ordering::Less => Ordering::Greater, Ordering::Less => Ordering::Greater,
@ -136,13 +143,17 @@ impl CookieStorage {
// http://tools.ietf.org/html/rfc6265#section-5.4 // http://tools.ietf.org/html/rfc6265#section-5.4
pub fn cookies_for_url(&mut self, url: &ServoUrl, source: CookieSource) -> Option<String> { pub fn cookies_for_url(&mut self, url: &ServoUrl, source: CookieSource) -> Option<String> {
let filterer = |c: &&mut Cookie| -> bool { let filterer = |c: &&mut Cookie| -> bool {
info!(" === SENT COOKIE : {} {} {:?} {:?}", info!(
c.cookie.name(), " === SENT COOKIE : {} {} {:?} {:?}",
c.cookie.value(), c.cookie.name(),
c.cookie.domain(), c.cookie.value(),
c.cookie.path()); c.cookie.domain(),
info!(" === SENT COOKIE RESULT {}", c.cookie.path()
c.appropriate_for_url(url, source)); );
info!(
" === SENT COOKIE RESULT {}",
c.appropriate_for_url(url, source)
);
// Step 1 // Step 1
c.appropriate_for_url(url, source) c.appropriate_for_url(url, source)
}; };
@ -161,7 +172,9 @@ impl CookieStorage {
(match acc.len() { (match acc.len() {
0 => acc, 0 => acc,
_ => acc + "; ", _ => acc + "; ",
}) + &c.cookie.name() + "=" + &c.cookie.value() }) + &c.cookie.name() +
"=" +
&c.cookie.value()
}; };
let result = url_cookies.iter_mut().fold("".to_owned(), reducer); let result = url_cookies.iter_mut().fold("".to_owned(), reducer);
@ -172,17 +185,21 @@ impl CookieStorage {
} }
} }
pub fn cookies_data_for_url<'a>(&'a mut self, pub fn cookies_data_for_url<'a>(
url: &'a ServoUrl, &'a mut self,
source: CookieSource) url: &'a ServoUrl,
-> impl Iterator<Item = cookie_rs::Cookie<'static>> + 'a { source: CookieSource,
) -> impl Iterator<Item = cookie_rs::Cookie<'static>> + 'a {
let domain = reg_host(url.host_str().unwrap_or("")); let domain = reg_host(url.host_str().unwrap_or(""));
let cookies = self.cookies_map.entry(domain).or_insert(vec![]); let cookies = self.cookies_map.entry(domain).or_insert(vec![]);
cookies.iter_mut().filter(move |c| c.appropriate_for_url(url, source)).map(|c| { cookies
c.touch(); .iter_mut()
c.cookie.clone() .filter(move |c| c.appropriate_for_url(url, source))
}) .map(|c| {
c.touch();
c.cookie.clone()
})
} }
} }
@ -220,7 +237,10 @@ fn get_oldest_accessed(is_secure_cookie: bool, cookies: &mut Vec<Cookie>) -> Opt
let mut oldest_accessed: Option<(usize, Tm)> = None; let mut oldest_accessed: Option<(usize, Tm)> = None;
for (i, c) in cookies.iter().enumerate() { for (i, c) in cookies.iter().enumerate() {
if (c.cookie.secure().unwrap_or(false) == 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
.as_ref()
.map_or(true, |a| c.last_access < a.1)
{
oldest_accessed = Some((i, c.last_access)); oldest_accessed = Some((i, c.last_access));
} }
} }

View file

@ -18,7 +18,9 @@ pub type DecodeData = (Mime, Vec<u8>);
pub fn decode(url: &ServoUrl) -> Result<DecodeData, DecodeError> { pub fn decode(url: &ServoUrl) -> Result<DecodeData, DecodeError> {
assert_eq!(url.scheme(), "data"); assert_eq!(url.scheme(), "data");
// Split out content type and data. // Split out content type and data.
let parts: Vec<&str> = url[Position::BeforePath..Position::AfterQuery].splitn(2, ',').collect(); let parts: Vec<&str> = url[Position::BeforePath..Position::AfterQuery]
.splitn(2, ',')
.collect();
if parts.len() != 2 { if parts.len() != 2 {
return Err(DecodeError::InvalidDataUri); return Err(DecodeError::InvalidDataUri);
} }
@ -36,15 +38,18 @@ pub fn decode(url: &ServoUrl) -> Result<DecodeData, DecodeError> {
ct_str.to_owned() ct_str.to_owned()
}; };
let content_type = ct_str.parse().unwrap_or_else(|_| { let content_type = ct_str
"text/plain; charset=US-ASCII".parse().unwrap() .parse()
}); .unwrap_or_else(|_| "text/plain; charset=US-ASCII".parse().unwrap());
let mut bytes = percent_decode(parts[1].as_bytes()).collect::<Vec<_>>(); let mut bytes = percent_decode(parts[1].as_bytes()).collect::<Vec<_>>();
if is_base64 { if is_base64 {
// FIXME(#2909): Its unclear what to do with non-alphabet characters, // FIXME(#2909): Its unclear what to do with non-alphabet characters,
// but Acid 3 apparently depends on spaces being ignored. // but Acid 3 apparently depends on spaces being ignored.
bytes = bytes.into_iter().filter(|&b| b != b' ').collect::<Vec<u8>>(); bytes = bytes
.into_iter()
.filter(|&b| b != b' ')
.collect::<Vec<u8>>();
match base64::decode(&bytes) { match base64::decode(&bytes) {
Err(..) => return Err(DecodeError::NonBase64DataUri), Err(..) => return Err(DecodeError::NonBase64DataUri),
Ok(data) => bytes = data, Ok(data) => bytes = data,

View file

@ -21,21 +21,21 @@ use time::{self, Timespec};
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum HeaderOrMethod { pub enum HeaderOrMethod {
HeaderData(HeaderName), HeaderData(HeaderName),
MethodData(Method) MethodData(Method),
} }
impl HeaderOrMethod { impl HeaderOrMethod {
fn match_header(&self, header_name: &HeaderName) -> bool { fn match_header(&self, header_name: &HeaderName) -> bool {
match *self { match *self {
HeaderOrMethod::HeaderData(ref n) => n == header_name, HeaderOrMethod::HeaderData(ref n) => n == header_name,
_ => false _ => false,
} }
} }
fn match_method(&self, method: &Method) -> bool { fn match_method(&self, method: &Method) -> bool {
match *self { match *self {
HeaderOrMethod::MethodData(ref m) => m == method, HeaderOrMethod::MethodData(ref m) => m == method,
_ => false _ => false,
} }
} }
} }
@ -48,19 +48,24 @@ pub struct CorsCacheEntry {
pub max_age: u32, pub max_age: u32,
pub credentials: bool, pub credentials: bool,
pub header_or_method: HeaderOrMethod, pub header_or_method: HeaderOrMethod,
created: Timespec created: Timespec,
} }
impl CorsCacheEntry { impl CorsCacheEntry {
fn new(origin: Origin, url: ServoUrl, max_age: u32, credentials: bool, fn new(
header_or_method: HeaderOrMethod) -> CorsCacheEntry { origin: Origin,
url: ServoUrl,
max_age: u32,
credentials: bool,
header_or_method: HeaderOrMethod,
) -> CorsCacheEntry {
CorsCacheEntry { CorsCacheEntry {
origin: origin, origin: origin,
url: url, url: url,
max_age: max_age, max_age: max_age,
credentials: credentials, credentials: credentials,
header_or_method: header_or_method, header_or_method: header_or_method,
created: time::now().to_timespec() created: time::now().to_timespec(),
} }
} }
} }
@ -80,25 +85,36 @@ impl CorsCache {
CorsCache(vec![]) CorsCache(vec![])
} }
fn find_entry_by_header<'a>(&'a mut self, request: &Request, fn find_entry_by_header<'a>(
header_name: &HeaderName) -> Option<&'a mut CorsCacheEntry> { &'a mut self,
request: &Request,
header_name: &HeaderName,
) -> Option<&'a mut CorsCacheEntry> {
self.cleanup(); self.cleanup();
self.0.iter_mut().find(|e| match_headers(e, request) && e.header_or_method.match_header(header_name)) self.0
.iter_mut()
.find(|e| match_headers(e, request) && e.header_or_method.match_header(header_name))
} }
fn find_entry_by_method<'a>(&'a mut self, request: &Request, fn find_entry_by_method<'a>(
method: Method) -> Option<&'a mut CorsCacheEntry> { &'a mut self,
request: &Request,
method: Method,
) -> Option<&'a mut CorsCacheEntry> {
// we can take the method from CorSRequest itself // we can take the method from CorSRequest itself
self.cleanup(); self.cleanup();
self.0.iter_mut().find(|e| match_headers(e, request) && e.header_or_method.match_method(&method)) self.0
.iter_mut()
.find(|e| match_headers(e, request) && e.header_or_method.match_method(&method))
} }
/// [Clear the cache](https://fetch.spec.whatwg.org/#concept-cache-clear) /// [Clear the cache](https://fetch.spec.whatwg.org/#concept-cache-clear)
pub fn clear(&mut self, request: &Request) { pub fn clear(&mut self, request: &Request) {
let CorsCache(buf) = self.clone(); let CorsCache(buf) = self.clone();
let new_buf: Vec<CorsCacheEntry> = let new_buf: Vec<CorsCacheEntry> = buf
buf.into_iter().filter(|e| e.origin == request.origin && .into_iter()
request.current_url() == e.url).collect(); .filter(|e| e.origin == request.origin && request.current_url() == e.url)
.collect();
*self = CorsCache(new_buf); *self = CorsCache(new_buf);
} }
@ -106,9 +122,10 @@ impl CorsCache {
pub fn cleanup(&mut self) { pub fn cleanup(&mut self) {
let CorsCache(buf) = self.clone(); let CorsCache(buf) = self.clone();
let now = time::now().to_timespec(); let now = time::now().to_timespec();
let new_buf: Vec<CorsCacheEntry> = buf.into_iter() let new_buf: Vec<CorsCacheEntry> = buf
.filter(|e| now.sec < e.created.sec + e.max_age as i64) .into_iter()
.collect(); .filter(|e| now.sec < e.created.sec + e.max_age as i64)
.collect();
*self = CorsCache(new_buf); *self = CorsCache(new_buf);
} }
@ -122,16 +139,27 @@ impl CorsCache {
/// [matching header](https://fetch.spec.whatwg.org/#concept-cache-match-header) is found. /// [matching header](https://fetch.spec.whatwg.org/#concept-cache-match-header) is found.
/// ///
/// If not, it will insert an equivalent entry /// If not, it will insert an equivalent entry
pub fn match_header_and_update(&mut self, request: &Request, pub fn match_header_and_update(
header_name: &HeaderName, new_max_age: u32) -> bool { &mut self,
match self.find_entry_by_header(&request, header_name).map(|e| e.max_age = new_max_age) { request: &Request,
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, Some(_) => true,
None => { None => {
self.insert(CorsCacheEntry::new(request.origin.clone(), request.current_url(), new_max_age, self.insert(CorsCacheEntry::new(
request.credentials_mode == CredentialsMode::Include, request.origin.clone(),
HeaderOrMethod::HeaderData(header_name.clone()))); request.current_url(),
new_max_age,
request.credentials_mode == CredentialsMode::Include,
HeaderOrMethod::HeaderData(header_name.clone()),
));
false false
} },
} }
} }
@ -145,15 +173,27 @@ impl CorsCache {
/// [a matching method](https://fetch.spec.whatwg.org/#concept-cache-match-method) is found. /// [a matching method](https://fetch.spec.whatwg.org/#concept-cache-match-method) is found.
/// ///
/// If not, it will insert an equivalent entry /// If not, it will insert an equivalent entry
pub fn match_method_and_update(&mut self, request: &Request, method: Method, new_max_age: u32) -> bool { pub fn match_method_and_update(
match self.find_entry_by_method(&request, method.clone()).map(|e| e.max_age = new_max_age) { &mut self,
request: &Request,
method: Method,
new_max_age: u32,
) -> bool {
match self
.find_entry_by_method(&request, method.clone())
.map(|e| e.max_age = new_max_age)
{
Some(_) => true, Some(_) => true,
None => { None => {
self.insert(CorsCacheEntry::new(request.origin.clone(), request.current_url(), new_max_age, self.insert(CorsCacheEntry::new(
request.credentials_mode == CredentialsMode::Include, request.origin.clone(),
HeaderOrMethod::MethodData(method))); request.current_url(),
new_max_age,
request.credentials_mode == CredentialsMode::Include,
HeaderOrMethod::MethodData(method),
));
false false
} },
} }
} }

View file

@ -35,7 +35,8 @@ use std::thread;
use subresource_integrity::is_response_integrity_valid; use subresource_integrity::is_response_integrity_valid;
lazy_static! { lazy_static! {
static ref X_CONTENT_TYPE_OPTIONS: HeaderName = HeaderName::from_static("x-content-type-options"); static ref X_CONTENT_TYPE_OPTIONS: HeaderName =
HeaderName::from_static("x-content-type-options");
} }
const FILE_CHUNK_SIZE: usize = 32768; //32 KB const FILE_CHUNK_SIZE: usize = 32768; //32 KB
@ -87,16 +88,16 @@ impl CancellationListener {
pub type DoneChannel = Option<(Sender<Data>, Receiver<Data>)>; pub type DoneChannel = Option<(Sender<Data>, Receiver<Data>)>;
/// [Fetch](https://fetch.spec.whatwg.org#concept-fetch) /// [Fetch](https://fetch.spec.whatwg.org#concept-fetch)
pub fn fetch(request: &mut Request, pub fn fetch(request: &mut Request, target: Target, context: &FetchContext) {
target: Target,
context: &FetchContext) {
fetch_with_cors_cache(request, &mut CorsCache::new(), target, context); fetch_with_cors_cache(request, &mut CorsCache::new(), target, context);
} }
pub fn fetch_with_cors_cache(request: &mut Request, pub fn fetch_with_cors_cache(
cache: &mut CorsCache, request: &mut Request,
target: Target, cache: &mut CorsCache,
context: &FetchContext) { target: Target,
context: &FetchContext,
) {
// Step 1. // Step 1.
if request.window == Window::Client { if request.window == Window::Client {
// TODO: Set window to request's client object if client is a Window object // TODO: Set window to request's client object if client is a Window object
@ -132,21 +133,27 @@ pub fn fetch_with_cors_cache(request: &mut Request,
} }
/// [Main fetch](https://fetch.spec.whatwg.org/#concept-main-fetch) /// [Main fetch](https://fetch.spec.whatwg.org/#concept-main-fetch)
pub fn main_fetch(request: &mut Request, pub fn main_fetch(
cache: &mut CorsCache, request: &mut Request,
cors_flag: bool, cache: &mut CorsCache,
recursive_flag: bool, cors_flag: bool,
target: Target, recursive_flag: bool,
done_chan: &mut DoneChannel, target: Target,
context: &FetchContext) done_chan: &mut DoneChannel,
-> Response { context: &FetchContext,
) -> Response {
// Step 1. // Step 1.
let mut response = None; let mut response = None;
// Step 2. // Step 2.
if request.local_urls_only { if request.local_urls_only {
if !matches!(request.current_url().scheme(), "about" | "blob" | "data" | "filesystem") { if !matches!(
response = Some(Response::network_error(NetworkError::Internal("Non-local scheme".into()))); request.current_url().scheme(),
"about" | "blob" | "data" | "filesystem"
) {
response = Some(Response::network_error(NetworkError::Internal(
"Non-local scheme".into(),
)));
} }
} }
@ -158,7 +165,9 @@ pub fn main_fetch(request: &mut Request,
// Step 5. // Step 5.
if should_be_blocked_due_to_bad_port(&request.current_url()) { if should_be_blocked_due_to_bad_port(&request.current_url()) {
response = Some(Response::network_error(NetworkError::Internal("Request attempted on bad port".into()))); response = Some(Response::network_error(NetworkError::Internal(
"Request attempted on bad port".into(),
)));
} }
// TODO: handle blocking as mixed content. // TODO: handle blocking as mixed content.
// TODO: handle blocking by content security policy. // TODO: handle blocking by content security policy.
@ -167,7 +176,9 @@ pub fn main_fetch(request: &mut Request,
// TODO: handle request's client's referrer policy. // TODO: handle request's client's referrer policy.
// Step 7. // Step 7.
request.referrer_policy = request.referrer_policy.or(Some(ReferrerPolicy::NoReferrerWhenDowngrade)); request.referrer_policy = request
.referrer_policy
.or(Some(ReferrerPolicy::NoReferrerWhenDowngrade));
// Step 8. // Step 8.
{ {
@ -182,11 +193,13 @@ pub fn main_fetch(request: &mut Request,
Referrer::ReferrerUrl(url) => { Referrer::ReferrerUrl(url) => {
request.headers.remove(header::REFERER); request.headers.remove(header::REFERER);
let current_url = request.current_url().clone(); let current_url = request.current_url().clone();
determine_request_referrer(&mut request.headers, determine_request_referrer(
request.referrer_policy.unwrap(), &mut request.headers,
url, request.referrer_policy.unwrap(),
current_url) url,
} current_url,
)
},
}; };
if let Some(referrer_url) = referrer_url { if let Some(referrer_url) = referrer_url {
request.referrer = Referrer::ReferrerUrl(referrer_url); request.referrer = Referrer::ReferrerUrl(referrer_url);
@ -197,8 +210,12 @@ pub fn main_fetch(request: &mut Request,
// TODO: handle FTP URLs. // TODO: handle FTP URLs.
// Step 10. // Step 10.
context.state.hsts_list.read().unwrap().switch_known_hsts_host_domain_url_to_https( context
request.current_url_mut()); .state
.hsts_list
.read()
.unwrap()
.switch_known_hsts_host_domain_url_to_https(request.current_url_mut());
// Step 11. // Step 11.
// Not applicable: see fetch_async. // Not applicable: see fetch_async.
@ -218,47 +235,48 @@ pub fn main_fetch(request: &mut Request,
// and about: schemes, but CSS tests will break on loading Ahem // and about: schemes, but CSS tests will break on loading Ahem
// since we load them through a file: URL. // since we load them through a file: URL.
current_url.scheme() == "about" || current_url.scheme() == "about" ||
request.mode == RequestMode::Navigate { request.mode == RequestMode::Navigate
{
// Substep 1. // Substep 1.
request.response_tainting = ResponseTainting::Basic; request.response_tainting = ResponseTainting::Basic;
// Substep 2. // Substep 2.
scheme_fetch(request, cache, target, done_chan, context) scheme_fetch(request, cache, target, done_chan, context)
} else if request.mode == RequestMode::SameOrigin { } else if request.mode == RequestMode::SameOrigin {
Response::network_error(NetworkError::Internal("Cross-origin response".into())) Response::network_error(NetworkError::Internal("Cross-origin response".into()))
} else if request.mode == RequestMode::NoCors { } else if request.mode == RequestMode::NoCors {
// Substep 1. // Substep 1.
request.response_tainting = ResponseTainting::Opaque; request.response_tainting = ResponseTainting::Opaque;
// Substep 2. // Substep 2.
scheme_fetch(request, cache, target, done_chan, context) scheme_fetch(request, cache, target, done_chan, context)
} else if !matches!(current_url.scheme(), "http" | "https") { } else if !matches!(current_url.scheme(), "http" | "https") {
Response::network_error(NetworkError::Internal("Non-http scheme".into())) Response::network_error(NetworkError::Internal("Non-http scheme".into()))
} else if request.use_cors_preflight || } else if request.use_cors_preflight ||
(request.unsafe_request && (request.unsafe_request &&
(!is_cors_safelisted_method(&request.method) || (!is_cors_safelisted_method(&request.method) ||
request.headers.iter().any(|(name, value)| !is_cors_safelisted_request_header(&name, &value)))) { request.headers.iter().any(|(name, value)| {
!is_cors_safelisted_request_header(&name, &value)
}))) {
// Substep 1. // Substep 1.
request.response_tainting = ResponseTainting::CorsTainting; request.response_tainting = ResponseTainting::CorsTainting;
// Substep 2. // Substep 2.
let response = http_fetch(request, cache, true, true, false, let response = http_fetch(
target, done_chan, context); request, cache, true, true, false, target, done_chan, context,
);
// Substep 3. // Substep 3.
if response.is_network_error() { if response.is_network_error() {
// TODO clear cache entries using request // TODO clear cache entries using request
} }
// Substep 4. // Substep 4.
response response
} else { } else {
// Substep 1. // Substep 1.
request.response_tainting = ResponseTainting::CorsTainting; request.response_tainting = ResponseTainting::CorsTainting;
// Substep 2. // Substep 2.
http_fetch(request, cache, true, false, false, target, done_chan, context) http_fetch(
request, cache, true, false, false, target, done_chan, context,
)
} }
}); });
@ -272,19 +290,25 @@ pub fn main_fetch(request: &mut Request,
// Substep 1. // Substep 1.
if request.response_tainting == ResponseTainting::CorsTainting { if request.response_tainting == ResponseTainting::CorsTainting {
// Subsubstep 1. // Subsubstep 1.
let header_names: Option<Vec<HeaderName>> = response.headers.typed_get::<AccessControlExposeHeaders>() let header_names: Option<Vec<HeaderName>> = response
.headers
.typed_get::<AccessControlExposeHeaders>()
.map(|v| v.iter().collect()); .map(|v| v.iter().collect());
match header_names { match header_names {
// Subsubstep 2. // Subsubstep 2.
Some(ref list) if request.credentials_mode != CredentialsMode::Include => { Some(ref list) if request.credentials_mode != CredentialsMode::Include => {
if list.len() == 1 && list[0] == "*" { if list.len() == 1 && list[0] == "*" {
response.cors_exposed_header_name_list = response.cors_exposed_header_name_list = response
response.headers.iter().map(|(name, _)| name.as_str().to_owned()).collect(); .headers
.iter()
.map(|(name, _)| name.as_str().to_owned())
.collect();
} }
}, },
// Subsubstep 3. // Subsubstep 3.
Some(list) => { Some(list) => {
response.cors_exposed_header_name_list = list.iter().map(|h| h.as_str().to_owned()).collect(); response.cors_exposed_header_name_list =
list.iter().map(|h| h.as_str().to_owned()).collect();
}, },
_ => (), _ => (),
} }
@ -304,13 +328,16 @@ pub fn main_fetch(request: &mut Request,
let internal_error = { let internal_error = {
// Tests for steps 17 and 18, before step 15 for borrowing concerns. // Tests for steps 17 and 18, before step 15 for borrowing concerns.
let response_is_network_error = response.is_network_error(); let response_is_network_error = response.is_network_error();
let should_replace_with_nosniff_error = let should_replace_with_nosniff_error = !response_is_network_error &&
!response_is_network_error && should_be_blocked_due_to_nosniff(request.destination, &response.headers); should_be_blocked_due_to_nosniff(request.destination, &response.headers);
let should_replace_with_mime_type_error = let should_replace_with_mime_type_error = !response_is_network_error &&
!response_is_network_error && should_be_blocked_due_to_mime_type(request.destination, &response.headers); should_be_blocked_due_to_mime_type(request.destination, &response.headers);
// Step 15. // Step 15.
let mut network_error_response = response.get_network_error().cloned().map(Response::network_error); let mut network_error_response = response
.get_network_error()
.cloned()
.map(Response::network_error);
let internal_response = if let Some(error_response) = network_error_response.as_mut() { let internal_response = if let Some(error_response) = network_error_response.as_mut() {
error_response error_response
} else { } else {
@ -326,27 +353,30 @@ pub fn main_fetch(request: &mut Request,
// TODO: handle blocking as mixed content. // TODO: handle blocking as mixed content.
// TODO: handle blocking by content security policy. // TODO: handle blocking by content security policy.
let blocked_error_response; let blocked_error_response;
let internal_response = let internal_response = if should_replace_with_nosniff_error {
if should_replace_with_nosniff_error { // Defer rebinding result
// Defer rebinding result blocked_error_response =
blocked_error_response = Response::network_error(NetworkError::Internal("Blocked by nosniff".into())); Response::network_error(NetworkError::Internal("Blocked by nosniff".into()));
&blocked_error_response &blocked_error_response
} else if should_replace_with_mime_type_error { } else if should_replace_with_mime_type_error {
// Defer rebinding result // Defer rebinding result
blocked_error_response = Response::network_error(NetworkError::Internal("Blocked by mime type".into())); blocked_error_response =
&blocked_error_response Response::network_error(NetworkError::Internal("Blocked by mime type".into()));
} else { &blocked_error_response
internal_response } else {
}; internal_response
};
// Step 18. // Step 18.
// We check `internal_response` since we did not mutate `response` // We check `internal_response` since we did not mutate `response`
// in the previous step. // in the previous step.
let not_network_error = !response_is_network_error && !internal_response.is_network_error(); let not_network_error = !response_is_network_error && !internal_response.is_network_error();
if not_network_error && (is_null_body_status(&internal_response.status) || if not_network_error &&
match request.method { (is_null_body_status(&internal_response.status) ||
Method::HEAD | Method::CONNECT => true, match request.method {
_ => false }) { Method::HEAD | Method::CONNECT => true,
_ => false,
}) {
// when Fetch is used only asynchronously, we will need to make sure // when Fetch is used only asynchronously, we will need to make sure
// that nothing tries to write to the body at this point // that nothing tries to write to the body at this point
let mut body = internal_response.body.lock().unwrap(); let mut body = internal_response.body.lock().unwrap();
@ -373,8 +403,11 @@ pub fn main_fetch(request: &mut Request,
// Step 19.2. // Step 19.2.
let ref integrity_metadata = &request.integrity_metadata; let ref integrity_metadata = &request.integrity_metadata;
if response.termination_reason.is_none() && if response.termination_reason.is_none() &&
!is_response_integrity_valid(integrity_metadata, &response) { !is_response_integrity_valid(integrity_metadata, &response)
Response::network_error(NetworkError::Internal("Subresource integrity validation failed".into())) {
Response::network_error(NetworkError::Internal(
"Subresource integrity validation failed".into(),
))
} else { } else {
response response
} }
@ -410,7 +443,7 @@ pub fn main_fetch(request: &mut Request,
// Step 23. // Step 23.
if !response_loaded { if !response_loaded {
wait_for_response(&mut response, target, done_chan); wait_for_response(&mut response, target, done_chan);
} }
// Step 24. // Step 24.
@ -430,8 +463,11 @@ pub fn main_fetch(request: &mut Request,
fn wait_for_response(response: &mut Response, target: Target, done_chan: &mut DoneChannel) { fn wait_for_response(response: &mut Response, target: Target, done_chan: &mut DoneChannel) {
if let Some(ref ch) = *done_chan { if let Some(ref ch) = *done_chan {
loop { loop {
match ch.1.recv() match ch
.expect("fetch worker should always send Done before terminating") { .1
.recv()
.expect("fetch worker should always send Done before terminating")
{
Data::Payload(vec) => { Data::Payload(vec) => {
target.process_response_chunk(vec); target.process_response_chunk(vec);
}, },
@ -439,7 +475,7 @@ fn wait_for_response(response: &mut Response, target: Target, done_chan: &mut Do
Data::Cancelled => { Data::Cancelled => {
response.aborted.store(true, Ordering::Relaxed); response.aborted.store(true, Ordering::Relaxed);
break; break;
} },
} }
} }
} else { } else {
@ -456,36 +492,39 @@ fn wait_for_response(response: &mut Response, target: Target, done_chan: &mut Do
} }
/// [Scheme fetch](https://fetch.spec.whatwg.org#scheme-fetch) /// [Scheme fetch](https://fetch.spec.whatwg.org#scheme-fetch)
fn scheme_fetch(request: &mut Request, fn scheme_fetch(
cache: &mut CorsCache, request: &mut Request,
target: Target, cache: &mut CorsCache,
done_chan: &mut DoneChannel, target: Target,
context: &FetchContext) done_chan: &mut DoneChannel,
-> Response { context: &FetchContext,
) -> Response {
let url = request.current_url(); let url = request.current_url();
match url.scheme() { match url.scheme() {
"about" if url.path() == "blank" => { "about" if url.path() == "blank" => {
let mut response = Response::new(url); let mut response = Response::new(url);
response.headers.typed_insert(ContentType::from(mime::TEXT_HTML_UTF_8)); response
.headers
.typed_insert(ContentType::from(mime::TEXT_HTML_UTF_8));
*response.body.lock().unwrap() = ResponseBody::Done(vec![]); *response.body.lock().unwrap() = ResponseBody::Done(vec![]);
response response
}, },
"http" | "https" => { "http" | "https" => http_fetch(
http_fetch(request, cache, false, false, false, target, done_chan, context) request, cache, false, false, false, target, done_chan, context,
}, ),
"data" => { "data" => match decode(&url) {
match decode(&url) { Ok((mime, bytes)) => {
Ok((mime, bytes)) => { let mut response = Response::new(url);
let mut response = Response::new(url); *response.body.lock().unwrap() = ResponseBody::Done(bytes);
*response.body.lock().unwrap() = ResponseBody::Done(bytes); response.headers.typed_insert(ContentType::from(mime));
response.headers.typed_insert(ContentType::from(mime)); response
response },
}, Err(_) => {
Err(_) => Response::network_error(NetworkError::Internal("Decoding data URL failed".into())) Response::network_error(NetworkError::Internal("Decoding data URL failed".into()))
} },
}, },
"file" => { "file" => {
@ -507,9 +546,17 @@ fn scheme_fetch(request: &mut Request,
let cancellation_listener = context.cancellation_listener.clone(); let cancellation_listener = context.cancellation_listener.clone();
let (start, end) = if let Some(ref range) = request.headers.typed_get::<Range>() { let (start, end) = if let Some(ref range) =
match range.iter().collect::<Vec<(Bound<u64>, Bound<u64>)>>().first() { request.headers.typed_get::<Range>()
Some(&(Bound::Included(start), Bound::Unbounded)) => (start, None), {
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))) => { Some(&(Bound::Included(start), Bound::Included(end))) => {
// `end` should be less or equal to `start`. // `end` should be less or equal to `start`.
(start, Some(u64::max(start, end))) (start, Some(u64::max(start, end)))
@ -517,75 +564,97 @@ fn scheme_fetch(request: &mut Request,
Some(&(Bound::Unbounded, Bound::Included(offset))) => { Some(&(Bound::Unbounded, Bound::Included(offset))) => {
if let Ok(metadata) = file.metadata() { if let Ok(metadata) = file.metadata() {
// `offset` cannot be bigger than the file size. // `offset` cannot be bigger than the file size.
(metadata.len() - u64::min(metadata.len(), offset), None) (
metadata.len() -
u64::min(metadata.len(), offset),
None,
)
} else { } else {
(0, None) (0, None)
} }
}, },
_ => (0, None) _ => (0, None),
} }
} else { } else {
(0, None) (0, None)
}; };
thread::Builder::new().name("fetch file worker thread".to_string()).spawn(move || { thread::Builder::new()
let mut reader = BufReader::with_capacity(FILE_CHUNK_SIZE, file); .name("fetch file worker thread".to_string())
if reader.seek(SeekFrom::Start(start)).is_err() { .spawn(move || {
warn!("Fetch - could not seek to {:?}", start); let mut reader =
} BufReader::with_capacity(FILE_CHUNK_SIZE, file);
if reader.seek(SeekFrom::Start(start)).is_err() {
warn!("Fetch - could not seek to {:?}", start);
}
loop { loop {
if cancellation_listener.lock().unwrap().cancelled() { if cancellation_listener.lock().unwrap().cancelled() {
*res_body.lock().unwrap() = ResponseBody::Done(vec![]); *res_body.lock().unwrap() =
let _ = done_sender.send(Data::Cancelled); ResponseBody::Done(vec![]);
return; let _ = done_sender.send(Data::Cancelled);
} return;
let length = {
let mut buffer = reader.fill_buf().unwrap().to_vec();
let mut buffer_len = buffer.len();
if let ResponseBody::Receiving(ref mut body) = *res_body.lock().unwrap() {
let offset = usize::min({
if let Some(end) = end {
let remaining_bytes =
end as usize - start as usize - body.len();
if remaining_bytes <= FILE_CHUNK_SIZE {
// This is the last chunk so we set buffer len to 0 to break
// the reading loop.
buffer_len = 0;
remaining_bytes
} else {
FILE_CHUNK_SIZE
}
} else {
FILE_CHUNK_SIZE
}
}, buffer.len());
body.extend_from_slice(&buffer[0..offset]);
let _ = done_sender.send(Data::Payload(buffer));
} }
buffer_len let length = {
}; let mut buffer =
if length == 0 { reader.fill_buf().unwrap().to_vec();
let mut body = res_body.lock().unwrap(); let mut buffer_len = buffer.len();
let completed_body = match *body { if let ResponseBody::Receiving(ref mut body) =
ResponseBody::Receiving(ref mut body) => { *res_body.lock().unwrap()
mem::replace(body, vec![]) {
}, let offset = usize::min(
_ => vec![], {
if let Some(end) = end {
let remaining_bytes = end as usize -
start as usize -
body.len();
if remaining_bytes <=
FILE_CHUNK_SIZE
{
// This is the last chunk so we set buffer len to 0 to break
// the reading loop.
buffer_len = 0;
remaining_bytes
} else {
FILE_CHUNK_SIZE
}
} else {
FILE_CHUNK_SIZE
}
},
buffer.len(),
);
body.extend_from_slice(&buffer[0..offset]);
let _ = done_sender.send(Data::Payload(buffer));
}
buffer_len
}; };
*body = ResponseBody::Done(completed_body); if length == 0 {
let _ = done_sender.send(Data::Done); let mut body = res_body.lock().unwrap();
break; 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;
}
reader.consume(length);
} }
reader.consume(length); })
} .expect("Failed to create fetch file worker thread");
}).expect("Failed to create fetch file worker thread");
response response
}, },
_ => Response::network_error(NetworkError::Internal("Opening file failed".into())), _ => Response::network_error(NetworkError::Internal(
"Opening file failed".into(),
)),
} }
}, },
_ => Response::network_error(NetworkError::Internal("Constructing file path failed".into())) _ => Response::network_error(NetworkError::Internal(
"Constructing file path failed".into(),
)),
} }
} else { } else {
Response::network_error(NetworkError::Internal("Unexpected method for file".into())) Response::network_error(NetworkError::Internal("Unexpected method for file".into()))
@ -596,7 +665,9 @@ fn scheme_fetch(request: &mut Request,
println!("Loading blob {}", url.as_str()); println!("Loading blob {}", url.as_str());
// Step 2. // Step 2.
if request.method != Method::GET { if request.method != Method::GET {
return Response::network_error(NetworkError::Internal("Unexpected method for blob".into())); return Response::network_error(NetworkError::Internal(
"Unexpected method for blob".into(),
));
} }
match load_blob_sync(url.clone(), context.filemanager.clone()) { match load_blob_sync(url.clone(), context.filemanager.clone()) {
@ -618,7 +689,7 @@ fn scheme_fetch(request: &mut Request,
Response::network_error(NetworkError::Internal("Unexpected scheme".into())) Response::network_error(NetworkError::Internal("Unexpected scheme".into()))
}, },
_ => Response::network_error(NetworkError::Internal("Unexpected scheme".into())) _ => Response::network_error(NetworkError::Internal("Unexpected scheme".into())),
} }
} }
@ -627,13 +698,15 @@ pub fn is_cors_safelisted_request_header(name: &HeaderName, value: &HeaderValue)
if name == header::CONTENT_TYPE { if name == header::CONTENT_TYPE {
if let Some(m) = value.to_str().ok().and_then(|s| s.parse::<Mime>().ok()) { 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::TEXT && m.subtype() == mime::PLAIN ||
m.type_() == mime::APPLICATION && m.subtype() == mime::WWW_FORM_URLENCODED || m.type_() == mime::APPLICATION && m.subtype() == mime::WWW_FORM_URLENCODED ||
m.type_() == mime::MULTIPART && m.subtype() == mime::FORM_DATA m.type_() == mime::MULTIPART && m.subtype() == mime::FORM_DATA
} else { } else {
false false
} }
} else { } else {
name == header::ACCEPT || name == header::ACCEPT_LANGUAGE || name == header::CONTENT_LANGUAGE name == header::ACCEPT ||
name == header::ACCEPT_LANGUAGE ||
name == header::CONTENT_LANGUAGE
} }
} }
@ -641,28 +714,35 @@ pub fn is_cors_safelisted_request_header(name: &HeaderName, value: &HeaderValue)
pub fn is_cors_safelisted_method(m: &Method) -> bool { pub fn is_cors_safelisted_method(m: &Method) -> bool {
match *m { match *m {
Method::GET | Method::HEAD | Method::POST => true, Method::GET | Method::HEAD | Method::POST => true,
_ => false _ => false,
} }
} }
fn is_null_body_status(status: &Option<(StatusCode, String)>) -> bool { fn is_null_body_status(status: &Option<(StatusCode, String)>) -> bool {
match *status { match *status {
Some((status, _)) => match status { Some((status, _)) => match status {
StatusCode::SWITCHING_PROTOCOLS | StatusCode::NO_CONTENT | StatusCode::SWITCHING_PROTOCOLS |
StatusCode::RESET_CONTENT | StatusCode::NOT_MODIFIED => true, StatusCode::NO_CONTENT |
_ => false StatusCode::RESET_CONTENT |
StatusCode::NOT_MODIFIED => true,
_ => false,
}, },
_ => false _ => false,
} }
} }
/// <https://fetch.spec.whatwg.org/#should-response-to-request-be-blocked-due-to-nosniff?> /// <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: &HeaderMap) -> bool { pub fn should_be_blocked_due_to_nosniff(
destination: Destination,
response_headers: &HeaderMap,
) -> bool {
// Steps 1-3. // Steps 1-3.
// TODO(eijebong): Replace this once typed headers allow custom ones... // TODO(eijebong): Replace this once typed headers allow custom ones...
if response_headers.get("x-content-type-options") if response_headers
.map_or(true, |val| val.to_str().unwrap_or("").to_lowercase() != "nosniff") .get("x-content-type-options")
{ .map_or(true, |val| {
val.to_str().unwrap_or("").to_lowercase() != "nosniff"
}) {
return false; return false;
} }
@ -692,30 +772,34 @@ pub fn should_be_blocked_due_to_nosniff(destination: Destination, response_heade
"text/x-javascript".parse().unwrap(), "text/x-javascript".parse().unwrap(),
]; ];
javascript_mime_types.iter() javascript_mime_types
.iter()
.any(|mime| mime.type_() == mime_type.type_() && mime.subtype() == mime_type.subtype()) .any(|mime| mime.type_() == mime_type.type_() && mime.subtype() == mime_type.subtype())
} }
match content_type_header { match content_type_header {
// Step 6 // Step 6
Some(ref ct) if destination.is_script_like() Some(ref ct) if destination.is_script_like() => {
=> !is_javascript_mime_type(&ct.clone().into()), !is_javascript_mime_type(&ct.clone().into())
},
// Step 7 // Step 7
Some(ref ct) if destination == Destination::Style Some(ref ct) if destination == Destination::Style => {
=> { let m: mime::Mime = ct.clone().into();
let m: mime::Mime = ct.clone().into(); m.type_() != mime::TEXT && m.subtype() != mime::CSS
m.type_() != mime::TEXT && m.subtype() != mime::CSS },
},
None if destination == Destination::Style || destination.is_script_like() => true, None if destination == Destination::Style || destination.is_script_like() => true,
// Step 8 // Step 8
_ => false _ => false,
} }
} }
/// <https://fetch.spec.whatwg.org/#should-response-to-request-be-blocked-due-to-mime-type?> /// <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: &HeaderMap) -> bool { fn should_be_blocked_due_to_mime_type(
destination: Destination,
response_headers: &HeaderMap,
) -> bool {
// Step 1 // Step 1
let mime_type: mime::Mime = match response_headers.typed_get::<ContentType>() { let mime_type: mime::Mime = match response_headers.typed_get::<ContentType>() {
Some(header) => header.into(), Some(header) => header.into(),
@ -725,12 +809,10 @@ fn should_be_blocked_due_to_mime_type(destination: Destination, response_headers
// Step 2-3 // Step 2-3
destination.is_script_like() && destination.is_script_like() &&
match mime_type.type_() { match mime_type.type_() {
mime::AUDIO | mime::AUDIO | mime::VIDEO | mime::IMAGE => true,
mime::VIDEO |
mime::IMAGE => true,
mime::TEXT if mime_type.subtype() == mime::CSV => true, mime::TEXT if mime_type.subtype() == mime::CSV => true,
// Step 4 // Step 4
_ => false _ => false,
} }
} }
@ -745,14 +827,17 @@ pub fn should_be_blocked_due_to_bad_port(url: &ServoUrl) -> bool {
// If there is no explicit port, this means the default one is used for // If there is no explicit port, this means the default one is used for
// the given scheme, and thus this means the request should not be blocked // the given scheme, and thus this means the request should not be blocked
// due to a bad port. // due to a bad port.
let port = if let Some(port) = url.port() { port } else { return false }; let port = if let Some(port) = url.port() {
port
} else {
return false;
};
// Step 4. // Step 4.
if scheme == "ftp" && (port == 20 || port == 21) { if scheme == "ftp" && (port == 20 || port == 21) {
return false; return false;
} }
// Step 5. // Step 5.
if is_network_scheme(scheme) && is_bad_port(port) { if is_network_scheme(scheme) && is_bad_port(port) {
return true; return true;
@ -770,12 +855,10 @@ fn is_network_scheme(scheme: &str) -> bool {
/// <https://fetch.spec.whatwg.org/#bad-port> /// <https://fetch.spec.whatwg.org/#bad-port>
fn is_bad_port(port: u16) -> bool { fn is_bad_port(port: u16) -> bool {
static BAD_PORTS: [u16; 64] = [ static BAD_PORTS: [u16; 64] = [
1, 7, 9, 11, 13, 15, 17, 19, 20, 21, 22, 23, 25, 37, 42, 1, 7, 9, 11, 13, 15, 17, 19, 20, 21, 22, 23, 25, 37, 42, 43, 53, 77, 79, 87, 95, 101, 102,
43, 53, 77, 79, 87, 95, 101, 102, 103, 104, 109, 110, 111, 103, 104, 109, 110, 111, 113, 115, 117, 119, 123, 135, 139, 143, 179, 389, 465, 512, 513,
113, 115, 117, 119, 123, 135, 139, 143, 179, 389, 465, 512, 514, 515, 526, 530, 531, 532, 540, 556, 563, 587, 601, 636, 993, 995, 2049, 3659, 4045,
513, 514, 515, 526, 530, 531, 532, 540, 556, 563, 587, 601, 6000, 6665, 6666, 6667, 6668, 6669,
636, 993, 995, 2049, 3659, 4045, 6000, 6665, 6666, 6667,
6668, 6669
]; ];
BAD_PORTS.binary_search(&port).is_ok() BAD_PORTS.binary_search(&port).is_ok()

View file

@ -34,7 +34,7 @@ struct FileStoreEntry {
/// UUIDs only become valid blob URIs when explicitly requested /// UUIDs only become valid blob URIs when explicitly requested
/// by the user with createObjectURL. Validity can be revoked as well. /// by the user with createObjectURL. Validity can be revoked as well.
/// (The UUID is the one that maps to this entry in `FileManagerStore`) /// (The UUID is the one that maps to this entry in `FileManagerStore`)
is_valid_url: AtomicBool is_valid_url: AtomicBool,
} }
#[derive(Clone)] #[derive(Clone)]
@ -71,29 +71,38 @@ impl FileManager {
} }
} }
pub fn read_file(&self, pub fn read_file(
sender: IpcSender<FileManagerResult<ReadFileProgress>>, &self,
id: Uuid, sender: IpcSender<FileManagerResult<ReadFileProgress>>,
check_url_validity: bool, id: Uuid,
origin: FileOrigin) { check_url_validity: bool,
origin: FileOrigin,
) {
let store = self.store.clone(); let store = self.store.clone();
thread::Builder::new().name("read file".to_owned()).spawn(move || { thread::Builder::new()
if let Err(e) = store.try_read_file(&sender, id, check_url_validity, .name("read file".to_owned())
origin) { .spawn(move || {
let _ = sender.send(Err(FileManagerThreadError::BlobURLStoreError(e))); if let Err(e) = store.try_read_file(&sender, id, check_url_validity, origin) {
} let _ = sender.send(Err(FileManagerThreadError::BlobURLStoreError(e)));
}).expect("Thread spawning failed"); }
})
.expect("Thread spawning failed");
} }
pub fn promote_memory(&self, pub fn promote_memory(
blob_buf: BlobBuf, &self,
set_valid: bool, blob_buf: BlobBuf,
sender: IpcSender<Result<Uuid, BlobURLStoreError>>, set_valid: bool,
origin: FileOrigin) { sender: IpcSender<Result<Uuid, BlobURLStoreError>>,
origin: FileOrigin,
) {
let store = self.store.clone(); let store = self.store.clone();
thread::Builder::new().name("transfer memory".to_owned()).spawn(move || { thread::Builder::new()
store.promote_memory(blob_buf, set_valid, sender, origin); .name("transfer memory".to_owned())
}).expect("Thread spawning failed"); .spawn(move || {
store.promote_memory(blob_buf, set_valid, sender, origin);
})
.expect("Thread spawning failed");
} }
/// Message handler /// Message handler
@ -102,35 +111,41 @@ impl FileManager {
FileManagerThreadMsg::SelectFile(filter, sender, origin, opt_test_path) => { FileManagerThreadMsg::SelectFile(filter, sender, origin, opt_test_path) => {
let store = self.store.clone(); let store = self.store.clone();
let embedder = self.embedder_proxy.clone(); let embedder = self.embedder_proxy.clone();
thread::Builder::new().name("select file".to_owned()).spawn(move || { thread::Builder::new()
store.select_file(filter, sender, origin, opt_test_path, embedder); .name("select file".to_owned())
}).expect("Thread spawning failed"); .spawn(move || {
} store.select_file(filter, sender, origin, opt_test_path, embedder);
})
.expect("Thread spawning failed");
},
FileManagerThreadMsg::SelectFiles(filter, sender, origin, opt_test_paths) => { FileManagerThreadMsg::SelectFiles(filter, sender, origin, opt_test_paths) => {
let store = self.store.clone(); let store = self.store.clone();
let embedder = self.embedder_proxy.clone(); let embedder = self.embedder_proxy.clone();
thread::Builder::new().name("select files".to_owned()).spawn(move || { thread::Builder::new()
store.select_files(filter, sender, origin, opt_test_paths, embedder); .name("select files".to_owned())
}).expect("Thread spawning failed"); .spawn(move || {
} store.select_files(filter, sender, origin, opt_test_paths, embedder);
})
.expect("Thread spawning failed");
},
FileManagerThreadMsg::ReadFile(sender, id, check_url_validity, origin) => { FileManagerThreadMsg::ReadFile(sender, id, check_url_validity, origin) => {
self.read_file(sender, id, check_url_validity, origin); self.read_file(sender, id, check_url_validity, origin);
} },
FileManagerThreadMsg::PromoteMemory(blob_buf, set_valid, sender, origin) => { FileManagerThreadMsg::PromoteMemory(blob_buf, set_valid, sender, origin) => {
self.promote_memory(blob_buf, set_valid, sender, origin); self.promote_memory(blob_buf, set_valid, sender, origin);
} },
FileManagerThreadMsg::AddSlicedURLEntry(id, rel_pos, sender, origin) =>{ FileManagerThreadMsg::AddSlicedURLEntry(id, rel_pos, sender, origin) => {
self.store.add_sliced_url_entry(id, rel_pos, sender, origin); self.store.add_sliced_url_entry(id, rel_pos, sender, origin);
} },
FileManagerThreadMsg::DecRef(id, origin, sender) => { FileManagerThreadMsg::DecRef(id, origin, sender) => {
let _ = sender.send(self.store.dec_ref(&id, &origin)); let _ = sender.send(self.store.dec_ref(&id, &origin));
} },
FileManagerThreadMsg::RevokeBlobURL(id, origin, sender) => { FileManagerThreadMsg::RevokeBlobURL(id, origin, sender) => {
let _ = sender.send(self.store.set_blob_url_validity(false, &id, &origin)); let _ = sender.send(self.store.set_blob_url_validity(false, &id, &origin));
} },
FileManagerThreadMsg::ActivateBlobURL(id, sender, origin) => { FileManagerThreadMsg::ActivateBlobURL(id, sender, origin) => {
let _ = sender.send(self.store.set_blob_url_validity(true, &id, &origin)); let _ = sender.send(self.store.set_blob_url_validity(true, &id, &origin));
} },
} }
} }
} }
@ -150,8 +165,12 @@ impl FileManagerStore {
} }
/// Copy out the file backend implementation content /// Copy out the file backend implementation content
fn get_impl(&self, id: &Uuid, origin_in: &FileOrigin, fn get_impl(
check_url_validity: bool) -> Result<FileImpl, BlobURLStoreError> { &self,
id: &Uuid,
origin_in: &FileOrigin,
check_url_validity: bool,
) -> Result<FileImpl, BlobURLStoreError> {
match self.entries.read().unwrap().get(id) { match self.entries.read().unwrap().get(id) {
Some(ref entry) => { Some(ref entry) => {
if *origin_in != *entry.origin { if *origin_in != *entry.origin {
@ -164,7 +183,7 @@ impl FileManagerStore {
Ok(entry.file_impl.clone()) Ok(entry.file_impl.clone())
} }
} }
} },
None => Err(BlobURLStoreError::InvalidFileID), None => Err(BlobURLStoreError::InvalidFileID),
} }
} }
@ -177,7 +196,7 @@ impl FileManagerStore {
self.entries.write().unwrap().remove(id); self.entries.write().unwrap().remove(id);
} }
fn inc_ref(&self, id: &Uuid, origin_in: &FileOrigin) -> Result<(), BlobURLStoreError>{ fn inc_ref(&self, id: &Uuid, origin_in: &FileOrigin) -> Result<(), BlobURLStoreError> {
match self.entries.read().unwrap().get(id) { match self.entries.read().unwrap().get(id) {
Some(entry) => { Some(entry) => {
if entry.origin == *origin_in { if entry.origin == *origin_in {
@ -186,41 +205,53 @@ impl FileManagerStore {
} else { } else {
Err(BlobURLStoreError::InvalidOrigin) Err(BlobURLStoreError::InvalidOrigin)
} }
} },
None => Err(BlobURLStoreError::InvalidFileID), None => Err(BlobURLStoreError::InvalidFileID),
} }
} }
fn add_sliced_url_entry(&self, parent_id: Uuid, rel_pos: RelativePos, fn add_sliced_url_entry(
sender: IpcSender<Result<Uuid, BlobURLStoreError>>, &self,
origin_in: FileOrigin) { parent_id: Uuid,
rel_pos: RelativePos,
sender: IpcSender<Result<Uuid, BlobURLStoreError>>,
origin_in: FileOrigin,
) {
match self.inc_ref(&parent_id, &origin_in) { match self.inc_ref(&parent_id, &origin_in) {
Ok(_) => { Ok(_) => {
let new_id = Uuid::new_v4(); let new_id = Uuid::new_v4();
self.insert(new_id, FileStoreEntry { self.insert(
origin: origin_in, new_id,
file_impl: FileImpl::Sliced(parent_id, rel_pos), FileStoreEntry {
refs: AtomicUsize::new(1), origin: origin_in,
// Valid here since AddSlicedURLEntry implies URL creation file_impl: FileImpl::Sliced(parent_id, rel_pos),
// from a BlobImpl::Sliced refs: AtomicUsize::new(1),
is_valid_url: AtomicBool::new(true), // Valid here since AddSlicedURLEntry implies URL creation
}); // from a BlobImpl::Sliced
is_valid_url: AtomicBool::new(true),
},
);
// We assume that the returned id will be held by BlobImpl::File // We assume that the returned id will be held by BlobImpl::File
let _ = sender.send(Ok(new_id)); let _ = sender.send(Ok(new_id));
} },
Err(e) => { Err(e) => {
let _ = sender.send(Err(e)); let _ = sender.send(Err(e));
} },
} }
} }
fn query_files_from_embedder(&self, fn query_files_from_embedder(
patterns: Vec<FilterPattern>, &self,
multiple_files: bool, patterns: Vec<FilterPattern>,
embedder_proxy: EmbedderProxy) -> Option<Vec<String>> { multiple_files: bool,
embedder_proxy: EmbedderProxy,
) -> Option<Vec<String>> {
let (ipc_sender, ipc_receiver) = ipc::channel().expect("Failed to create IPC channel!"); let (ipc_sender, ipc_receiver) = ipc::channel().expect("Failed to create IPC channel!");
let msg = (None, EmbedderMsg::SelectFiles(patterns, multiple_files, ipc_sender)); let msg = (
None,
EmbedderMsg::SelectFiles(patterns, multiple_files, ipc_sender),
);
embedder_proxy.send(msg); embedder_proxy.send(msg);
match ipc_receiver.recv() { match ipc_receiver.recv() {
@ -228,23 +259,26 @@ impl FileManagerStore {
Err(e) => { Err(e) => {
warn!("Failed to receive files from embedder ({}).", e); warn!("Failed to receive files from embedder ({}).", e);
None None
} },
} }
} }
fn select_file(&self, fn select_file(
patterns: Vec<FilterPattern>, &self,
sender: IpcSender<FileManagerResult<SelectedFile>>, patterns: Vec<FilterPattern>,
origin: FileOrigin, sender: IpcSender<FileManagerResult<SelectedFile>>,
opt_test_path: Option<String>, origin: FileOrigin,
embedder_proxy: EmbedderProxy) { opt_test_path: Option<String>,
embedder_proxy: EmbedderProxy,
) {
// Check if the select_files preference is enabled // Check if the select_files preference is enabled
// to ensure process-level security against compromised script; // to ensure process-level security against compromised script;
// Then try applying opt_test_path directly for testing convenience // Then try applying opt_test_path directly for testing convenience
let opt_s = if select_files_pref_enabled() { let opt_s = if select_files_pref_enabled() {
opt_test_path opt_test_path
} else { } else {
self.query_files_from_embedder(patterns, false, embedder_proxy).and_then(|mut x| x.pop()) self.query_files_from_embedder(patterns, false, embedder_proxy)
.and_then(|mut x| x.pop())
}; };
match opt_s { match opt_s {
@ -252,20 +286,22 @@ impl FileManagerStore {
let selected_path = Path::new(&s); let selected_path = Path::new(&s);
let result = self.create_entry(selected_path, &origin); let result = self.create_entry(selected_path, &origin);
let _ = sender.send(result); let _ = sender.send(result);
} },
None => { None => {
let _ = sender.send(Err(FileManagerThreadError::UserCancelled)); let _ = sender.send(Err(FileManagerThreadError::UserCancelled));
return; return;
} },
} }
} }
fn select_files(&self, fn select_files(
patterns: Vec<FilterPattern>, &self,
sender: IpcSender<FileManagerResult<Vec<SelectedFile>>>, patterns: Vec<FilterPattern>,
origin: FileOrigin, sender: IpcSender<FileManagerResult<Vec<SelectedFile>>>,
opt_test_paths: Option<Vec<String>>, origin: FileOrigin,
embedder_proxy: EmbedderProxy) { opt_test_paths: Option<Vec<String>>,
embedder_proxy: EmbedderProxy,
) {
// Check if the select_files preference is enabled // Check if the select_files preference is enabled
// to ensure process-level security against compromised script; // to ensure process-level security against compromised script;
// Then try applying opt_test_paths directly for testing convenience // Then try applying opt_test_paths directly for testing convenience
@ -291,30 +327,42 @@ impl FileManagerStore {
Err(e) => { Err(e) => {
let _ = sender.send(Err(e)); let _ = sender.send(Err(e));
return; return;
} },
}; };
} }
let _ = sender.send(Ok(replies)); let _ = sender.send(Ok(replies));
} },
None => { None => {
let _ = sender.send(Err(FileManagerThreadError::UserCancelled)); let _ = sender.send(Err(FileManagerThreadError::UserCancelled));
return; return;
} },
} }
} }
fn create_entry(&self, file_path: &Path, origin: &str) -> Result<SelectedFile, FileManagerThreadError> { fn create_entry(
&self,
file_path: &Path,
origin: &str,
) -> Result<SelectedFile, FileManagerThreadError> {
use net_traits::filemanager_thread::FileManagerThreadError::FileSystemError; use net_traits::filemanager_thread::FileManagerThreadError::FileSystemError;
let file = File::open(file_path).map_err(|e| FileSystemError(e.to_string()))?; let file = File::open(file_path).map_err(|e| FileSystemError(e.to_string()))?;
let metadata = file.metadata().map_err(|e| FileSystemError(e.to_string()))?; let metadata = file
let modified = metadata.modified().map_err(|e| FileSystemError(e.to_string()))?; .metadata()
let elapsed = modified.elapsed().map_err(|e| FileSystemError(e.to_string()))?; .map_err(|e| FileSystemError(e.to_string()))?;
let modified = metadata
.modified()
.map_err(|e| FileSystemError(e.to_string()))?;
let elapsed = modified
.elapsed()
.map_err(|e| FileSystemError(e.to_string()))?;
// Unix Epoch: https://doc.servo.org/std/time/constant.UNIX_EPOCH.html // Unix Epoch: https://doc.servo.org/std/time/constant.UNIX_EPOCH.html
let modified_epoch = elapsed.as_secs() * 1000 + elapsed.subsec_nanos() as u64 / 1000000; let modified_epoch = elapsed.as_secs() * 1000 + elapsed.subsec_nanos() as u64 / 1000000;
let file_size = metadata.len(); let file_size = metadata.len();
let file_name = file_path.file_name().ok_or(FileSystemError("Invalid filepath".to_string()))?; let file_name = file_path
.file_name()
.ok_or(FileSystemError("Invalid filepath".to_string()))?;
let file_impl = FileImpl::MetaDataOnly(FileMetaData { let file_impl = FileImpl::MetaDataOnly(FileMetaData {
path: file_path.to_path_buf(), path: file_path.to_path_buf(),
@ -324,18 +372,21 @@ impl FileManagerStore {
let id = Uuid::new_v4(); let id = Uuid::new_v4();
self.insert(id, FileStoreEntry { self.insert(
origin: origin.to_string(), id,
file_impl: file_impl, FileStoreEntry {
refs: AtomicUsize::new(1), origin: origin.to_string(),
// Invalid here since create_entry is called by file selection file_impl: file_impl,
is_valid_url: AtomicBool::new(false), refs: AtomicUsize::new(1),
}); // Invalid here since create_entry is called by file selection
is_valid_url: AtomicBool::new(false),
},
);
let filename_path = Path::new(file_name); let filename_path = Path::new(file_name);
let type_string = match guess_mime_type_opt(filename_path) { let type_string = match guess_mime_type_opt(filename_path) {
Some(x) => format!("{}", x), Some(x) => format!("{}", x),
None => "".to_string(), None => "".to_string(),
}; };
Ok(SelectedFile { Ok(SelectedFile {
@ -347,9 +398,14 @@ impl FileManagerStore {
}) })
} }
fn get_blob_buf(&self, sender: &IpcSender<FileManagerResult<ReadFileProgress>>, fn get_blob_buf(
id: &Uuid, origin_in: &FileOrigin, rel_pos: RelativePos, &self,
check_url_validity: bool) -> Result<(), BlobURLStoreError> { sender: &IpcSender<FileManagerResult<ReadFileProgress>>,
id: &Uuid,
origin_in: &FileOrigin,
rel_pos: RelativePos,
check_url_validity: bool,
) -> Result<(), BlobURLStoreError> {
let file_impl = self.get_impl(id, origin_in, check_url_validity)?; let file_impl = self.get_impl(id, origin_in, check_url_validity)?;
match file_impl { match file_impl {
FileImpl::Memory(buf) => { FileImpl::Memory(buf) => {
@ -365,7 +421,7 @@ impl FileManagerStore {
let _ = sender.send(Ok(ReadFileProgress::EOF)); let _ = sender.send(Ok(ReadFileProgress::EOF));
Ok(()) Ok(())
} },
FileImpl::MetaDataOnly(metadata) => { FileImpl::MetaDataOnly(metadata) => {
/* XXX: Snapshot state check (optional) https://w3c.github.io/FileAPI/#snapshot-state. /* XXX: Snapshot state check (optional) https://w3c.github.io/FileAPI/#snapshot-state.
Concretely, here we create another file, and this file might not Concretely, here we create another file, and this file might not
@ -373,45 +429,62 @@ impl FileManagerStore {
create_entry is called. create_entry is called.
*/ */
let opt_filename = metadata.path.file_name() let opt_filename = metadata
.and_then(|osstr| osstr.to_str()) .path
.map(|s| s.to_string()); .file_name()
.and_then(|osstr| osstr.to_str())
.map(|s| s.to_string());
let mime = guess_mime_type_opt(metadata.path.clone()); let mime = guess_mime_type_opt(metadata.path.clone());
let range = rel_pos.to_abs_range(metadata.size as usize); let range = rel_pos.to_abs_range(metadata.size as usize);
let mut file = File::open(&metadata.path) let mut file = File::open(&metadata.path)
.map_err(|e| BlobURLStoreError::External(e.to_string()))?; .map_err(|e| BlobURLStoreError::External(e.to_string()))?;
let seeked_start = file.seek(SeekFrom::Start(range.start as u64)) let seeked_start = file
.map_err(|e| BlobURLStoreError::External(e.to_string()))?; .seek(SeekFrom::Start(range.start as u64))
.map_err(|e| BlobURLStoreError::External(e.to_string()))?;
if seeked_start == (range.start as u64) { if seeked_start == (range.start as u64) {
let type_string = match mime { let type_string = match mime {
Some(x) => format!("{}", x), Some(x) => format!("{}", x),
None => "".to_string(), None => "".to_string(),
}; };
chunked_read(sender, &mut file, range.len(), opt_filename, chunked_read(sender, &mut file, range.len(), opt_filename, type_string);
type_string);
Ok(()) Ok(())
} else { } else {
Err(BlobURLStoreError::InvalidEntry) Err(BlobURLStoreError::InvalidEntry)
} }
} },
FileImpl::Sliced(parent_id, inner_rel_pos) => { FileImpl::Sliced(parent_id, inner_rel_pos) => {
// Next time we don't need to check validity since // Next time we don't need to check validity since
// we have already done that for requesting URL if necessary // we have already done that for requesting URL if necessary
self.get_blob_buf(sender, &parent_id, origin_in, self.get_blob_buf(
rel_pos.slice_inner(&inner_rel_pos), false) sender,
} &parent_id,
origin_in,
rel_pos.slice_inner(&inner_rel_pos),
false,
)
},
} }
} }
// Convenient wrapper over get_blob_buf // Convenient wrapper over get_blob_buf
fn try_read_file(&self, sender: &IpcSender<FileManagerResult<ReadFileProgress>>, fn try_read_file(
id: Uuid, check_url_validity: bool, origin_in: FileOrigin) &self,
-> Result<(), BlobURLStoreError> { sender: &IpcSender<FileManagerResult<ReadFileProgress>>,
self.get_blob_buf(sender, &id, &origin_in, RelativePos::full_range(), check_url_validity) id: Uuid,
check_url_validity: bool,
origin_in: FileOrigin,
) -> Result<(), BlobURLStoreError> {
self.get_blob_buf(
sender,
&id,
&origin_in,
RelativePos::full_range(),
check_url_validity,
)
} }
fn dec_ref(&self, id: &Uuid, origin_in: &FileOrigin) -> Result<(), BlobURLStoreError> { fn dec_ref(&self, id: &Uuid, origin_in: &FileOrigin) -> Result<(), BlobURLStoreError> {
@ -436,7 +509,7 @@ impl FileManagerStore {
} else { } else {
return Err(BlobURLStoreError::InvalidOrigin); return Err(BlobURLStoreError::InvalidOrigin);
} }
} },
None => return Err(BlobURLStoreError::InvalidFileID), None => return Err(BlobURLStoreError::InvalidFileID),
}; };
@ -454,28 +527,41 @@ impl FileManagerStore {
Ok(()) Ok(())
} }
fn promote_memory(&self, blob_buf: BlobBuf, set_valid: bool, fn promote_memory(
sender: IpcSender<Result<Uuid, BlobURLStoreError>>, origin: FileOrigin) { &self,
match Url::parse(&origin) { // parse to check sanity blob_buf: BlobBuf,
set_valid: bool,
sender: IpcSender<Result<Uuid, BlobURLStoreError>>,
origin: FileOrigin,
) {
match Url::parse(&origin) {
// parse to check sanity
Ok(_) => { Ok(_) => {
let id = Uuid::new_v4(); let id = Uuid::new_v4();
self.insert(id, FileStoreEntry { self.insert(
origin: origin.clone(), id,
file_impl: FileImpl::Memory(blob_buf), FileStoreEntry {
refs: AtomicUsize::new(1), origin: origin.clone(),
is_valid_url: AtomicBool::new(set_valid), file_impl: FileImpl::Memory(blob_buf),
}); refs: AtomicUsize::new(1),
is_valid_url: AtomicBool::new(set_valid),
},
);
let _ = sender.send(Ok(id)); let _ = sender.send(Ok(id));
} },
Err(_) => { Err(_) => {
let _ = sender.send(Err(BlobURLStoreError::InvalidOrigin)); let _ = sender.send(Err(BlobURLStoreError::InvalidOrigin));
} },
} }
} }
fn set_blob_url_validity(&self, validity: bool, id: &Uuid, fn set_blob_url_validity(
origin_in: &FileOrigin) -> Result<(), BlobURLStoreError> { &self,
validity: bool,
id: &Uuid,
origin_in: &FileOrigin,
) -> Result<(), BlobURLStoreError> {
let (do_remove, opt_parent_id, res) = match self.entries.read().unwrap().get(id) { let (do_remove, opt_parent_id, res) = match self.entries.read().unwrap().get(id) {
Some(entry) => { Some(entry) => {
if *entry.origin == *origin_in { if *entry.origin == *origin_in {
@ -485,7 +571,7 @@ impl FileManagerStore {
// Check if it is the last possible reference // Check if it is the last possible reference
// since refs only accounts for blob id holders // since refs only accounts for blob id holders
// and store entry id holders // and store entry id holders
let zero_refs = entry.refs.load(Ordering::Acquire) == 0; let zero_refs = entry.refs.load(Ordering::Acquire) == 0;
if let FileImpl::Sliced(ref parent_id, _) = entry.file_impl { if let FileImpl::Sliced(ref parent_id, _) = entry.file_impl {
(zero_refs, Some(parent_id.clone()), Ok(())) (zero_refs, Some(parent_id.clone()), Ok(()))
@ -498,8 +584,8 @@ impl FileManagerStore {
} else { } else {
(false, None, Err(BlobURLStoreError::InvalidOrigin)) (false, None, Err(BlobURLStoreError::InvalidOrigin))
} }
} },
None => (false, None, Err(BlobURLStoreError::InvalidFileID)) None => (false, None, Err(BlobURLStoreError::InvalidFileID)),
}; };
if do_remove { if do_remove {
@ -515,15 +601,21 @@ impl FileManagerStore {
} }
fn select_files_pref_enabled() -> bool { fn select_files_pref_enabled() -> bool {
PREFS.get("dom.testing.htmlinputelement.select_files.enabled") PREFS
.as_boolean().unwrap_or(false) .get("dom.testing.htmlinputelement.select_files.enabled")
.as_boolean()
.unwrap_or(false)
} }
const CHUNK_SIZE: usize = 8192; const CHUNK_SIZE: usize = 8192;
fn chunked_read(sender: &IpcSender<FileManagerResult<ReadFileProgress>>, fn chunked_read(
file: &mut File, size: usize, opt_filename: Option<String>, sender: &IpcSender<FileManagerResult<ReadFileProgress>>,
type_string: String) { file: &mut File,
size: usize,
opt_filename: Option<String>,
type_string: String,
) {
// First chunk // First chunk
let mut buf = vec![0; CHUNK_SIZE]; let mut buf = vec![0; CHUNK_SIZE];
match file.read(&mut buf) { match file.read(&mut buf) {
@ -536,11 +628,11 @@ fn chunked_read(sender: &IpcSender<FileManagerResult<ReadFileProgress>>,
bytes: buf, bytes: buf,
}; };
let _ = sender.send(Ok(ReadFileProgress::Meta(blob_buf))); let _ = sender.send(Ok(ReadFileProgress::Meta(blob_buf)));
} },
Err(e) => { Err(e) => {
let _ = sender.send(Err(FileManagerThreadError::FileSystemError(e.to_string()))); let _ = sender.send(Err(FileManagerThreadError::FileSystemError(e.to_string())));
return; return;
} },
} }
// Send the remaining chunks // Send the remaining chunks
@ -550,15 +642,15 @@ fn chunked_read(sender: &IpcSender<FileManagerResult<ReadFileProgress>>,
Ok(0) => { Ok(0) => {
let _ = sender.send(Ok(ReadFileProgress::EOF)); let _ = sender.send(Ok(ReadFileProgress::EOF));
return; return;
} },
Ok(n) => { Ok(n) => {
buf.truncate(n); buf.truncate(n);
let _ = sender.send(Ok(ReadFileProgress::Partial(buf))); let _ = sender.send(Ok(ReadFileProgress::Partial(buf)));
} },
Err(e) => { Err(e) => {
let _ = sender.send(Err(FileManagerThreadError::FileSystemError(e.to_string()))); let _ = sender.send(Err(FileManagerThreadError::FileSystemError(e.to_string())));
return; return;
} },
} }
} }
} }

View file

@ -31,21 +31,32 @@ pub fn replace_host_table(table: HashMap<String, IpAddr>) {
} }
pub fn parse_hostsfile(hostsfile_content: &str) -> HashMap<String, IpAddr> { pub fn parse_hostsfile(hostsfile_content: &str) -> HashMap<String, IpAddr> {
hostsfile_content.lines().filter_map(|line| { hostsfile_content
let mut iter = line.split('#').next().unwrap().split_whitespace(); .lines()
Some((iter.next()?.parse().ok()?, iter)) .filter_map(|line| {
}).flat_map(|(ip, hosts)| { let mut iter = line.split('#').next().unwrap().split_whitespace();
hosts.filter(|host| { Some((iter.next()?.parse().ok()?, iter))
let invalid = ['\0', '\t', '\n', '\r', ' ', '#', '%', '/', ':', '?', '@', '[', '\\', ']'];
host.parse::<Ipv4Addr>().is_err() && !host.contains(&invalid[..])
}).map(move |host| {
(host.to_owned(), ip)
}) })
}).collect() .flat_map(|(ip, hosts)| {
hosts
.filter(|host| {
let invalid = [
'\0', '\t', '\n', '\r', ' ', '#', '%', '/', ':', '?', '@', '[', '\\', ']',
];
host.parse::<Ipv4Addr>().is_err() && !host.contains(&invalid[..])
})
.map(move |host| (host.to_owned(), ip))
})
.collect()
} }
pub fn replace_host(host: &str) -> Cow<str> { pub fn replace_host(host: &str) -> Cow<str> {
HOST_TABLE.lock().unwrap().as_ref() HOST_TABLE
.lock()
.unwrap()
.as_ref()
.and_then(|table| table.get(host)) .and_then(|table| table.get(host))
.map_or(host.into(), |replaced_host| replaced_host.to_string().into()) .map_or(host.into(), |replaced_host| {
replaced_host.to_string().into()
})
} }

View file

@ -16,11 +16,15 @@ pub struct HstsEntry {
pub host: String, pub host: String,
pub include_subdomains: bool, pub include_subdomains: bool,
pub max_age: Option<u64>, pub max_age: Option<u64>,
pub timestamp: Option<u64> pub timestamp: Option<u64>,
} }
impl HstsEntry { impl HstsEntry {
pub fn new(host: String, subdomains: IncludeSubdomains, max_age: Option<u64>) -> Option<HstsEntry> { pub fn new(
host: String,
subdomains: IncludeSubdomains,
max_age: Option<u64>,
) -> Option<HstsEntry> {
if host.parse::<Ipv4Addr>().is_ok() || host.parse::<Ipv6Addr>().is_ok() { if host.parse::<Ipv4Addr>().is_ok() || host.parse::<Ipv6Addr>().is_ok() {
None None
} else { } else {
@ -28,7 +32,7 @@ impl HstsEntry {
host: host, host: host,
include_subdomains: (subdomains == IncludeSubdomains::Included), include_subdomains: (subdomains == IncludeSubdomains::Included),
max_age: max_age, max_age: max_age,
timestamp: Some(time::get_time().sec as u64) timestamp: Some(time::get_time().sec as u64),
}) })
} }
} }
@ -37,9 +41,9 @@ impl HstsEntry {
match (self.max_age, self.timestamp) { match (self.max_age, self.timestamp) {
(Some(max_age), Some(timestamp)) => { (Some(max_age), Some(timestamp)) => {
(time::get_time().sec as u64) - timestamp >= max_age (time::get_time().sec as u64) - timestamp >= max_age
} },
_ => false _ => false,
} }
} }
@ -59,7 +63,9 @@ pub struct HstsList {
impl HstsList { impl HstsList {
pub fn new() -> HstsList { pub fn new() -> HstsList {
HstsList { entries_map: HashMap::new() } HstsList {
entries_map: HashMap::new(),
}
} }
/// Create an `HstsList` from the bytes of a JSON preload file. /// Create an `HstsList` from the bytes of a JSON preload file.
@ -107,9 +113,9 @@ impl HstsList {
} }
fn has_subdomain(&self, host: &str, base_domain: &str) -> bool { fn has_subdomain(&self, host: &str, base_domain: &str) -> bool {
self.entries_map.get(base_domain).map_or(false, |entries| { self.entries_map.get(base_domain).map_or(false, |entries| {
entries.iter().any(|e| e.matches_subdomain(host)) entries.iter().any(|e| e.matches_subdomain(host))
}) })
} }
pub fn push(&mut self, entry: HstsEntry) { pub fn push(&mut self, entry: HstsEntry) {
@ -118,7 +124,10 @@ impl HstsList {
let have_domain = self.has_domain(&entry.host, base_domain); let have_domain = self.has_domain(&entry.host, base_domain);
let have_subdomain = self.has_subdomain(&entry.host, base_domain); let have_subdomain = self.has_subdomain(&entry.host, base_domain);
let entries = self.entries_map.entry(base_domain.to_owned()).or_insert(vec![]); let entries = self
.entries_map
.entry(base_domain.to_owned())
.or_insert(vec![]);
if !have_domain && !have_subdomain { if !have_domain && !have_subdomain {
entries.push(entry); entries.push(entry);
} else if !have_subdomain { } else if !have_subdomain {
@ -136,7 +145,10 @@ impl HstsList {
if url.scheme() != "http" { if url.scheme() != "http" {
return; return;
} }
if url.domain().map_or(false, |domain| self.is_host_secure(domain)) { if url
.domain()
.map_or(false, |domain| self.is_host_secure(domain))
{
url.as_mut_url().set_scheme("https").unwrap(); url.as_mut_url().set_scheme("https").unwrap();
} }
} }

View file

@ -30,23 +30,22 @@ use std::time::SystemTime;
use time; use time;
use time::{Duration, Timespec, Tm}; use time::{Duration, Timespec, Tm};
/// The key used to differentiate requests in the cache. /// The key used to differentiate requests in the cache.
#[derive(Clone, Eq, Hash, MallocSizeOf, PartialEq )] #[derive(Clone, Eq, Hash, MallocSizeOf, PartialEq)]
pub struct CacheKey { pub struct CacheKey {
url: ServoUrl url: ServoUrl,
} }
impl CacheKey { impl CacheKey {
fn new(request: Request) -> CacheKey { fn new(request: Request) -> CacheKey {
CacheKey { CacheKey {
url: request.current_url().clone() url: request.current_url().clone(),
} }
} }
fn from_servo_url(servo_url: &ServoUrl) -> CacheKey { fn from_servo_url(servo_url: &ServoUrl) -> CacheKey {
CacheKey { CacheKey {
url: servo_url.clone() url: servo_url.clone(),
} }
} }
@ -63,7 +62,7 @@ struct CachedResource {
body: Arc<Mutex<ResponseBody>>, body: Arc<Mutex<ResponseBody>>,
aborted: Arc<AtomicBool>, aborted: Arc<AtomicBool>,
awaiting_body: Arc<Mutex<Vec<Sender<Data>>>>, awaiting_body: Arc<Mutex<Vec<Sender<Data>>>>,
data: Measurable<MeasurableCachedResource> data: Measurable<MeasurableCachedResource>,
} }
#[derive(Clone, MallocSizeOf)] #[derive(Clone, MallocSizeOf)]
@ -82,9 +81,9 @@ impl MallocSizeOf for CachedResource {
fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
// TODO: self.request_headers.unconditional_size_of(ops) + // TODO: self.request_headers.unconditional_size_of(ops) +
self.body.unconditional_size_of(ops) + self.body.unconditional_size_of(ops) +
self.aborted.unconditional_size_of(ops) + self.aborted.unconditional_size_of(ops) +
self.awaiting_body.unconditional_size_of(ops) + self.awaiting_body.unconditional_size_of(ops) +
self.data.size_of(ops) self.data.size_of(ops)
} }
} }
@ -94,7 +93,7 @@ struct CachedMetadata {
/// Headers /// Headers
pub headers: Arc<Mutex<HeaderMap>>, pub headers: Arc<Mutex<HeaderMap>>,
/// Fields that implement MallocSizeOf /// Fields that implement MallocSizeOf
pub data: Measurable<MeasurableCachedMetadata> pub data: Measurable<MeasurableCachedMetadata>,
} }
#[derive(Clone, MallocSizeOf)] #[derive(Clone, MallocSizeOf)]
@ -106,7 +105,7 @@ struct MeasurableCachedMetadata {
/// Character set. /// Character set.
pub charset: Option<String>, pub charset: Option<String>,
/// HTTP Status /// HTTP Status
pub status: Option<(u16, Vec<u8>)> pub status: Option<(u16, Vec<u8>)>,
} }
impl MallocSizeOf for CachedMetadata { impl MallocSizeOf for CachedMetadata {
@ -122,7 +121,7 @@ pub struct CachedResponse {
/// The response constructed from the cached resource /// The response constructed from the cached resource
pub response: Response, pub response: Response,
/// The revalidation flag for the stored response /// The revalidation flag for the stored response
pub needs_validation: bool pub needs_validation: bool,
} }
/// A memory cache. /// A memory cache.
@ -132,7 +131,6 @@ pub struct HttpCache {
entries: HashMap<CacheKey, Vec<CachedResource>>, entries: HashMap<CacheKey, Vec<CachedResource>>,
} }
/// Determine if a given response is cacheable based on the initial metadata received. /// Determine if a given response is cacheable based on the initial metadata received.
/// Based on <https://tools.ietf.org/html/rfc7234#section-3> /// Based on <https://tools.ietf.org/html/rfc7234#section-3>
fn response_is_cacheable(metadata: &Metadata) -> bool { fn response_is_cacheable(metadata: &Metadata) -> bool {
@ -143,15 +141,18 @@ fn response_is_cacheable(metadata: &Metadata) -> bool {
let headers = metadata.headers.as_ref().unwrap(); let headers = metadata.headers.as_ref().unwrap();
if headers.contains_key(header::EXPIRES) || if headers.contains_key(header::EXPIRES) ||
headers.contains_key(header::LAST_MODIFIED) || headers.contains_key(header::LAST_MODIFIED) ||
headers.contains_key(header::ETAG) { headers.contains_key(header::ETAG)
{
is_cacheable = true; is_cacheable = true;
} }
if let Some(ref directive) = headers.typed_get::<CacheControl>() { if let Some(ref directive) = headers.typed_get::<CacheControl>() {
if directive.no_store() { if directive.no_store() {
return false return false;
} }
if directive.public() || directive.s_max_age().is_some() || if directive.public() ||
directive.max_age().is_some() || directive.no_cache() directive.s_max_age().is_some() ||
directive.max_age().is_some() ||
directive.no_cache()
{ {
is_cacheable = true; is_cacheable = true;
} }
@ -245,12 +246,11 @@ fn get_response_expiry(response: &Response) -> Duration {
match *code { match *code {
200 | 203 | 204 | 206 | 300 | 301 | 404 | 405 | 410 | 414 | 501 => { 200 | 203 | 204 | 206 | 300 | 301 | 404 | 405 | 410 | 414 | 501 => {
// Status codes that are cacheable by default <https://tools.ietf.org/html/rfc7231#section-6.1> // Status codes that are cacheable by default <https://tools.ietf.org/html/rfc7231#section-6.1>
return heuristic_freshness return heuristic_freshness;
}, },
_ => { _ => {
// Other status codes can only use heuristic freshness if the public cache directive is present. // Other status codes can only use heuristic freshness if the public cache directive is present.
if let Some(ref directives) = response.headers.typed_get::<CacheControl>() if let Some(ref directives) = response.headers.typed_get::<CacheControl>() {
{
if directives.public() { if directives.public() {
return heuristic_freshness; return heuristic_freshness;
} }
@ -288,25 +288,30 @@ fn get_expiry_adjustment_from_request_headers(request: &Request, expires: Durati
return expires - min_fresh; return expires - min_fresh;
} }
if directive.no_cache() || directive.no_store() { if directive.no_cache() || directive.no_store() {
return Duration::min_value() return Duration::min_value();
} }
expires expires
} }
/// Create a CachedResponse from a request and a CachedResource. /// Create a CachedResponse from a request and a CachedResource.
fn create_cached_response(request: &Request, fn create_cached_response(
request: &Request,
cached_resource: &CachedResource, cached_resource: &CachedResource,
cached_headers: &HeaderMap, cached_headers: &HeaderMap,
done_chan: &mut DoneChannel) done_chan: &mut DoneChannel,
-> CachedResponse { ) -> CachedResponse {
let mut response = Response::new(cached_resource.data.metadata.data.final_url.clone()); let mut response = Response::new(cached_resource.data.metadata.data.final_url.clone());
response.headers = cached_headers.clone(); response.headers = cached_headers.clone();
response.body = cached_resource.body.clone(); response.body = cached_resource.body.clone();
if let ResponseBody::Receiving(_) = *cached_resource.body.lock().unwrap() { if let ResponseBody::Receiving(_) = *cached_resource.body.lock().unwrap() {
let (done_sender, done_receiver) = channel(); let (done_sender, done_receiver) = channel();
*done_chan = Some((done_sender.clone(), done_receiver)); *done_chan = Some((done_sender.clone(), done_receiver));
cached_resource.awaiting_body.lock().unwrap().push(done_sender); cached_resource
.awaiting_body
.lock()
.unwrap()
.push(done_sender);
} }
response.location_url = cached_resource.data.location_url.clone(); response.location_url = cached_resource.data.location_url.clone();
response.status = cached_resource.data.status.clone(); response.status = cached_resource.data.status.clone();
@ -324,15 +329,20 @@ fn create_cached_response(request: &Request,
// TODO: take must-revalidate into account <https://tools.ietf.org/html/rfc7234#section-5.2.2.1> // TODO: take must-revalidate into account <https://tools.ietf.org/html/rfc7234#section-5.2.2.1>
// TODO: if this cache is to be considered shared, take proxy-revalidate into account // TODO: if this cache is to be considered shared, take proxy-revalidate into account
// <https://tools.ietf.org/html/rfc7234#section-5.2.2.7> // <https://tools.ietf.org/html/rfc7234#section-5.2.2.7>
let has_expired = (adjusted_expires < time_since_validated) || let has_expired =
(adjusted_expires == time_since_validated); (adjusted_expires < time_since_validated) || (adjusted_expires == time_since_validated);
CachedResponse { response: response, needs_validation: has_expired } CachedResponse {
response: response,
needs_validation: has_expired,
}
} }
/// Create a new resource, based on the bytes requested, and an existing resource, /// Create a new resource, based on the bytes requested, and an existing resource,
/// with a status-code of 206. /// with a status-code of 206.
fn create_resource_with_bytes_from_resource(bytes: &[u8], resource: &CachedResource) fn create_resource_with_bytes_from_resource(
-> CachedResource { bytes: &[u8],
resource: &CachedResource,
) -> CachedResource {
CachedResource { CachedResource {
request_headers: resource.request_headers.clone(), request_headers: resource.request_headers.clone(),
body: Arc::new(Mutex::new(ResponseBody::Done(bytes.to_owned()))), body: Arc::new(Mutex::new(ResponseBody::Done(bytes.to_owned()))),
@ -347,29 +357,35 @@ fn create_resource_with_bytes_from_resource(bytes: &[u8], resource: &CachedResou
url_list: resource.data.url_list.clone(), url_list: resource.data.url_list.clone(),
expires: resource.data.expires.clone(), expires: resource.data.expires.clone(),
last_validated: resource.data.last_validated.clone(), last_validated: resource.data.last_validated.clone(),
}) }),
} }
} }
/// Support for range requests <https://tools.ietf.org/html/rfc7233>. /// Support for range requests <https://tools.ietf.org/html/rfc7233>.
fn handle_range_request(request: &Request, fn handle_range_request(
request: &Request,
candidates: Vec<&CachedResource>, candidates: Vec<&CachedResource>,
range_spec: Vec<(Bound<u64>, Bound<u64>)>, range_spec: Vec<(Bound<u64>, Bound<u64>)>,
done_chan: &mut DoneChannel) done_chan: &mut DoneChannel,
-> Option<CachedResponse> { ) -> Option<CachedResponse> {
let mut complete_cached_resources = candidates.iter().filter(|resource| { let mut complete_cached_resources =
match resource.data.raw_status { candidates
Some((ref code, _)) => *code == 200, .iter()
None => false .filter(|resource| match resource.data.raw_status {
} Some((ref code, _)) => *code == 200,
}); None => false,
let partial_cached_resources = candidates.iter().filter(|resource| { });
match resource.data.raw_status { let partial_cached_resources =
Some((ref code, _)) => *code == 206, candidates
None => false .iter()
} .filter(|resource| match resource.data.raw_status {
}); Some((ref code, _)) => *code == 206,
match (range_spec.first().unwrap(), complete_cached_resources.next()) { None => false,
});
match (
range_spec.first().unwrap(),
complete_cached_resources.next(),
) {
// TODO: take the full range spec into account. // TODO: take the full range spec into account.
// If we have a complete resource, take the request range from the body. // If we have a complete resource, take the request range from the body.
// When there isn't a complete resource available, we loop over cached partials, // When there isn't a complete resource available, we loop over cached partials,
@ -384,9 +400,11 @@ fn handle_range_request(request: &Request,
let e = end as usize + 1; let e = end as usize + 1;
let requested = body.get(b..e); let requested = body.get(b..e);
if let Some(bytes) = requested { if let Some(bytes) = requested {
let new_resource = create_resource_with_bytes_from_resource(bytes, complete_resource); let new_resource =
create_resource_with_bytes_from_resource(bytes, complete_resource);
let cached_headers = new_resource.data.metadata.headers.lock().unwrap(); let cached_headers = new_resource.data.metadata.headers.lock().unwrap();
let cached_response = create_cached_response(request, &new_resource, &*cached_headers, done_chan); let cached_response =
create_cached_response(request, &new_resource, &*cached_headers, done_chan);
return Some(cached_response); return Some(cached_response);
} }
} }
@ -400,9 +418,9 @@ fn handle_range_request(request: &Request,
if let Some(bytes_range) = range.bytes_range() { if let Some(bytes_range) = range.bytes_range() {
bytes_range bytes_range
} else { } else {
continue continue;
} }
} },
_ => continue, _ => continue,
}; };
if res_beginning - 1 < beginning && res_end + 1 > end { if res_beginning - 1 < beginning && res_end + 1 > end {
@ -416,8 +434,10 @@ fn handle_range_request(request: &Request,
_ => continue, _ => continue,
}; };
if let Some(bytes) = requested { if let Some(bytes) = requested {
let new_resource = create_resource_with_bytes_from_resource(&bytes, partial_resource); let new_resource =
let cached_response = create_cached_response(request, &new_resource, &*headers, done_chan); create_resource_with_bytes_from_resource(&bytes, partial_resource);
let cached_response =
create_cached_response(request, &new_resource, &*headers, done_chan);
return Some(cached_response); return Some(cached_response);
} }
} }
@ -428,9 +448,11 @@ fn handle_range_request(request: &Request,
let b = beginning as usize; let b = beginning as usize;
let requested = body.get(b..); let requested = body.get(b..);
if let Some(bytes) = requested { if let Some(bytes) = requested {
let new_resource = create_resource_with_bytes_from_resource(bytes, complete_resource); let new_resource =
create_resource_with_bytes_from_resource(bytes, complete_resource);
let cached_headers = new_resource.data.metadata.headers.lock().unwrap(); let cached_headers = new_resource.data.metadata.headers.lock().unwrap();
let cached_response = create_cached_response(request, &new_resource, &*cached_headers, done_chan); let cached_response =
create_cached_response(request, &new_resource, &*cached_headers, done_chan);
return Some(cached_response); return Some(cached_response);
} }
} }
@ -457,8 +479,10 @@ fn handle_range_request(request: &Request,
_ => continue, _ => continue,
}; };
if let Some(bytes) = requested { if let Some(bytes) = requested {
let new_resource = create_resource_with_bytes_from_resource(&bytes, partial_resource); let new_resource =
let cached_response = create_cached_response(request, &new_resource, &*headers, done_chan); create_resource_with_bytes_from_resource(&bytes, partial_resource);
let cached_response =
create_cached_response(request, &new_resource, &*headers, done_chan);
return Some(cached_response); return Some(cached_response);
} }
} }
@ -469,9 +493,11 @@ fn handle_range_request(request: &Request,
let from_byte = body.len() - offset as usize; let from_byte = body.len() - offset as usize;
let requested = body.get(from_byte..); let requested = body.get(from_byte..);
if let Some(bytes) = requested { if let Some(bytes) = requested {
let new_resource = create_resource_with_bytes_from_resource(bytes, complete_resource); let new_resource =
create_resource_with_bytes_from_resource(bytes, complete_resource);
let cached_headers = new_resource.data.metadata.headers.lock().unwrap(); let cached_headers = new_resource.data.metadata.headers.lock().unwrap();
let cached_response = create_cached_response(request, &new_resource, &*cached_headers, done_chan); let cached_response =
create_cached_response(request, &new_resource, &*cached_headers, done_chan);
return Some(cached_response); return Some(cached_response);
} }
} }
@ -488,7 +514,7 @@ fn handle_range_request(request: &Request,
} else { } else {
continue; continue;
}; };
if (total - res_beginning) > (offset - 1 ) && (total - res_end) < offset + 1 { if (total - res_beginning) > (offset - 1) && (total - res_end) < offset + 1 {
let resource_body = &*partial_resource.body.lock().unwrap(); let resource_body = &*partial_resource.body.lock().unwrap();
let requested = match resource_body { let requested = match resource_body {
&ResponseBody::Done(ref body) => { &ResponseBody::Done(ref body) => {
@ -498,38 +524,47 @@ fn handle_range_request(request: &Request,
_ => continue, _ => continue,
}; };
if let Some(bytes) = requested { if let Some(bytes) = requested {
let new_resource = create_resource_with_bytes_from_resource(&bytes, partial_resource); let new_resource =
let cached_response = create_cached_response(request, &new_resource, &*headers, done_chan); create_resource_with_bytes_from_resource(&bytes, partial_resource);
let cached_response =
create_cached_response(request, &new_resource, &*headers, done_chan);
return Some(cached_response); return Some(cached_response);
} }
} }
} }
}, },
// All the cases with Bound::Excluded should be unreachable anyway // All the cases with Bound::Excluded should be unreachable anyway
_ => return None _ => return None,
} }
None None
} }
impl HttpCache { impl HttpCache {
/// Create a new memory cache instance. /// Create a new memory cache instance.
pub fn new() -> HttpCache { pub fn new() -> HttpCache {
HttpCache { HttpCache {
entries: HashMap::new() entries: HashMap::new(),
} }
} }
/// Constructing Responses from Caches. /// Constructing Responses from Caches.
/// <https://tools.ietf.org/html/rfc7234#section-4> /// <https://tools.ietf.org/html/rfc7234#section-4>
pub fn construct_response(&self, request: &Request, done_chan: &mut DoneChannel) -> Option<CachedResponse> { 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> // 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. // Only Get requests are cached, avoid a url based match for others.
return None; return None;
} }
let entry_key = CacheKey::new(request.clone()); let entry_key = CacheKey::new(request.clone());
let resources = self.entries.get(&entry_key)?.into_iter().filter(|r| { !r.aborted.load(Ordering::Relaxed) }); let resources = self
.entries
.get(&entry_key)?
.into_iter()
.filter(|r| !r.aborted.load(Ordering::Relaxed));
let mut candidates = vec![]; let mut candidates = vec![];
for cached_resource in resources { for cached_resource in resources {
let mut can_be_constructed = true; let mut can_be_constructed = true;
@ -545,7 +580,9 @@ impl HttpCache {
match request.headers.get(vary_val) { match request.headers.get(vary_val) {
Some(header_data) => { Some(header_data) => {
// If the header is present in the request. // If the header is present in the request.
if let Some(original_header_data) = original_request_headers.get(vary_val) { if let Some(original_header_data) =
original_request_headers.get(vary_val)
{
// Check that the value of the nominated header field, // Check that the value of the nominated header field,
// in the original request, matches the value in the current request. // in the original request, matches the value in the current request.
if original_header_data != header_data { if original_header_data != header_data {
@ -558,7 +595,8 @@ impl HttpCache {
// If a header field is absent from a request, // If a header field is absent from a request,
// it can only match a stored response if those headers, // it can only match a stored response if those headers,
// were also absent in the original request. // were also absent in the original request.
can_be_constructed = original_request_headers.get(vary_val).is_none(); can_be_constructed =
original_request_headers.get(vary_val).is_none();
}, },
} }
if !can_be_constructed { if !can_be_constructed {
@ -573,7 +611,12 @@ impl HttpCache {
} }
// Support for range requests // Support for range requests
if let Some(range_spec) = request.headers.typed_get::<Range>() { if let Some(range_spec) = request.headers.typed_get::<Range>() {
return handle_range_request(request, candidates, range_spec.iter().collect(), done_chan); return handle_range_request(
request,
candidates,
range_spec.iter().collect(),
done_chan,
);
} else { } else {
// Not a Range request. // Not a Range request.
if let Some(ref cached_resource) = candidates.first() { if let Some(ref cached_resource) = candidates.first() {
@ -581,7 +624,8 @@ impl HttpCache {
// TODO: select the most appropriate one, using a known mechanism from a selecting header field, // TODO: select the most appropriate one, using a known mechanism from a selecting header field,
// or using the Date header to return the most recent one. // or using the Date header to return the most recent one.
let cached_headers = cached_resource.data.metadata.headers.lock().unwrap(); let cached_headers = cached_resource.data.metadata.headers.lock().unwrap();
let cached_response = create_cached_response(request, cached_resource, &*cached_headers, done_chan); let cached_response =
create_cached_response(request, cached_resource, &*cached_headers, done_chan);
return Some(cached_response); return Some(cached_response);
} }
} }
@ -602,7 +646,7 @@ impl HttpCache {
let _ = done_sender.send(Data::Payload(completed_body.clone())); let _ = done_sender.send(Data::Payload(completed_body.clone()));
let _ = done_sender.send(Data::Done); let _ = done_sender.send(Data::Done);
} }
}; }
} }
} }
} }
@ -610,7 +654,12 @@ impl HttpCache {
/// Freshening Stored Responses upon Validation. /// Freshening Stored Responses upon Validation.
/// <https://tools.ietf.org/html/rfc7234#section-4.3.4> /// <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> { pub fn refresh(
&mut self,
request: &Request,
response: Response,
done_chan: &mut DoneChannel,
) -> Option<Response> {
assert_eq!(response.status.map(|s| s.0), Some(StatusCode::NOT_MODIFIED)); assert_eq!(response.status.map(|s| s.0), Some(StatusCode::NOT_MODIFIED));
let entry_key = CacheKey::new(request.clone()); let entry_key = CacheKey::new(request.clone());
if let Some(cached_resources) = self.entries.get_mut(&entry_key) { if let Some(cached_resources) = self.entries.get_mut(&entry_key) {
@ -620,22 +669,25 @@ impl HttpCache {
// Otherwise, create a new dedicated channel to update the consumer. // Otherwise, create a new dedicated channel to update the consumer.
// The response constructed here will replace the 304 one from the network. // The response constructed here will replace the 304 one from the network.
let in_progress_channel = match *cached_resource.body.lock().unwrap() { let in_progress_channel = match *cached_resource.body.lock().unwrap() {
ResponseBody::Receiving(..) => { ResponseBody::Receiving(..) => Some(channel()),
Some(channel()) ResponseBody::Empty | ResponseBody::Done(..) => None,
},
ResponseBody::Empty | ResponseBody::Done(..) => None
}; };
match in_progress_channel { match in_progress_channel {
Some((done_sender, done_receiver)) => { Some((done_sender, done_receiver)) => {
*done_chan = Some((done_sender.clone(), done_receiver)); *done_chan = Some((done_sender.clone(), done_receiver));
cached_resource.awaiting_body.lock().unwrap().push(done_sender); cached_resource
.awaiting_body
.lock()
.unwrap()
.push(done_sender);
}, },
None => *done_chan = None None => *done_chan = None,
} }
// Received a response with 304 status code, in response to a request that matches a cached resource. // Received a response with 304 status code, in response to a request that matches a cached resource.
// 1. update the headers of the cached resource. // 1. update the headers of the cached resource.
// 2. return a response, constructed from the cached resource. // 2. return a response, constructed from the cached resource.
let mut constructed_response = Response::new(cached_resource.data.metadata.data.final_url.clone()); let mut constructed_response =
Response::new(cached_resource.data.metadata.data.final_url.clone());
constructed_response.body = cached_resource.body.clone(); constructed_response.body = cached_resource.body.clone();
constructed_response.status = cached_resource.data.status.clone(); constructed_response.status = cached_resource.data.status.clone();
constructed_response.https_state = cached_resource.data.https_state.clone(); constructed_response.https_state = cached_resource.data.https_state.clone();
@ -666,12 +718,19 @@ impl HttpCache {
/// <https://tools.ietf.org/html/rfc7234#section-4.4> /// <https://tools.ietf.org/html/rfc7234#section-4.4>
pub fn invalidate(&mut self, request: &Request, response: &Response) { pub fn invalidate(&mut self, request: &Request, response: &Response) {
// TODO(eijebong): Once headers support typed_get, update this to use them // 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 Some(Ok(location)) = response
.headers
.get(header::LOCATION)
.map(HeaderValue::to_str)
{
if let Ok(url) = request.current_url().join(location) { if let Ok(url) = request.current_url().join(location) {
self.invalidate_for_url(&url); self.invalidate_for_url(&url);
} }
} }
if let Some(Ok(ref content_location)) = response.headers.get(header::CONTENT_LOCATION).map(HeaderValue::to_str) 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) { if let Ok(url) = request.current_url().join(&content_location) {
self.invalidate_for_url(&url); self.invalidate_for_url(&url);
@ -683,18 +742,23 @@ impl HttpCache {
/// Storing Responses in Caches. /// Storing Responses in Caches.
/// <https://tools.ietf.org/html/rfc7234#section-3> /// <https://tools.ietf.org/html/rfc7234#section-3>
pub fn store(&mut self, request: &Request, response: &Response) { pub fn store(&mut self, request: &Request, response: &Response) {
if PREFS.get("network.http-cache.disabled").as_boolean().unwrap_or(false) { if PREFS
return .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. // Only Get requests are cached.
return return;
} }
let entry_key = CacheKey::new(request.clone()); let entry_key = CacheKey::new(request.clone());
let metadata = match response.metadata() { let metadata = match response.metadata() {
Ok(FetchMetadata::Filtered { Ok(FetchMetadata::Filtered {
filtered: _, filtered: _,
unsafe_: metadata }) | unsafe_: metadata,
}) |
Ok(FetchMetadata::Unfiltered(metadata)) => metadata, Ok(FetchMetadata::Unfiltered(metadata)) => metadata,
_ => return, _ => return,
}; };
@ -708,8 +772,8 @@ impl HttpCache {
final_url: metadata.final_url, final_url: metadata.final_url,
content_type: metadata.content_type.map(|v| v.0.to_string()), content_type: metadata.content_type.map(|v| v.0.to_string()),
charset: metadata.charset, charset: metadata.charset,
status: metadata.status status: metadata.status,
}) }),
}; };
let entry_resource = CachedResource { let entry_resource = CachedResource {
request_headers: Arc::new(Mutex::new(request.headers.clone())), request_headers: Arc::new(Mutex::new(request.headers.clone())),
@ -724,11 +788,10 @@ impl HttpCache {
raw_status: response.raw_status.clone(), raw_status: response.raw_status.clone(),
url_list: response.url_list.clone(), url_list: response.url_list.clone(),
expires: expiry, expires: expiry,
last_validated: time::now() last_validated: time::now(),
}) }),
}; };
let entry = self.entries.entry(entry_key).or_insert(vec![]); let entry = self.entries.entry(entry_key).or_insert(vec![]);
entry.push(entry_resource); entry.push(entry_resource);
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -37,39 +37,39 @@ fn decode_bytes_sync(key: LoadKey, bytes: &[u8]) -> DecoderMsg {
let image = load_from_memory(bytes); let image = load_from_memory(bytes);
DecoderMsg { DecoderMsg {
key: key, key: key,
image: image image: image,
} }
} }
fn get_placeholder_image(webrender_api: &webrender_api::RenderApi, data: &[u8]) -> io::Result<Arc<Image>> { fn get_placeholder_image(
webrender_api: &webrender_api::RenderApi,
data: &[u8],
) -> io::Result<Arc<Image>> {
let mut image = load_from_memory(&data).unwrap(); let mut image = load_from_memory(&data).unwrap();
set_webrender_image_key(webrender_api, &mut image); set_webrender_image_key(webrender_api, &mut image);
Ok(Arc::new(image)) Ok(Arc::new(image))
} }
fn set_webrender_image_key(webrender_api: &webrender_api::RenderApi, image: &mut Image) { fn set_webrender_image_key(webrender_api: &webrender_api::RenderApi, image: &mut Image) {
if image.id.is_some() { return; } if image.id.is_some() {
return;
}
let mut bytes = Vec::new(); let mut bytes = Vec::new();
let is_opaque = match image.format { let is_opaque = match image.format {
PixelFormat::BGRA8 => { PixelFormat::BGRA8 => {
bytes.extend_from_slice(&*image.bytes); bytes.extend_from_slice(&*image.bytes);
pixels::premultiply_inplace(bytes.as_mut_slice()) pixels::premultiply_inplace(bytes.as_mut_slice())
} },
PixelFormat::RGB8 => { PixelFormat::RGB8 => {
for bgr in image.bytes.chunks(3) { for bgr in image.bytes.chunks(3) {
bytes.extend_from_slice(&[ bytes.extend_from_slice(&[bgr[2], bgr[1], bgr[0], 0xff]);
bgr[2],
bgr[1],
bgr[0],
0xff
]);
} }
true true
} },
PixelFormat::K8 | PixelFormat::KA8 => { PixelFormat::K8 | PixelFormat::KA8 => {
panic!("Not support by webrender yet"); panic!("Not support by webrender yet");
} },
}; };
let descriptor = webrender_api::ImageDescriptor { let descriptor = webrender_api::ImageDescriptor {
size: webrender_api::DeviceUintSize::new(image.width, image.height), size: webrender_api::DeviceUintSize::new(image.width, image.height),
@ -121,20 +121,22 @@ impl AllPendingLoads {
} }
fn remove(&mut self, key: &LoadKey) -> Option<PendingLoad> { fn remove(&mut self, key: &LoadKey) -> Option<PendingLoad> {
self.loads.remove(key). self.loads.remove(key).and_then(|pending_load| {
and_then(|pending_load| { self.url_to_load_key.remove(&pending_load.url).unwrap();
self.url_to_load_key.remove(&pending_load.url).unwrap(); Some(pending_load)
Some(pending_load) })
})
} }
fn get_cached<'a>(&'a mut self, url: ServoUrl, can_request: CanRequestImages) fn get_cached<'a>(
-> CacheResult<'a> { &'a mut self,
url: ServoUrl,
can_request: CanRequestImages,
) -> CacheResult<'a> {
match self.url_to_load_key.entry(url.clone()) { match self.url_to_load_key.entry(url.clone()) {
Occupied(url_entry) => { Occupied(url_entry) => {
let load_key = url_entry.get(); let load_key = url_entry.get();
CacheResult::Hit(*load_key, self.loads.get_mut(load_key).unwrap()) CacheResult::Hit(*load_key, self.loads.get_mut(load_key).unwrap())
} },
Vacant(url_entry) => { Vacant(url_entry) => {
if can_request == CanRequestImages::No { if can_request == CanRequestImages::No {
return CacheResult::Miss(None); return CacheResult::Miss(None);
@ -149,9 +151,9 @@ impl AllPendingLoads {
Vacant(load_entry) => { Vacant(load_entry) => {
let mut_load = load_entry.insert(pending_load); let mut_load = load_entry.insert(pending_load);
CacheResult::Miss(Some((load_key, mut_load))) CacheResult::Miss(Some((load_key, mut_load)))
} },
} }
} },
} }
} }
} }
@ -226,14 +228,12 @@ impl ImageBytes {
type LoadKey = PendingImageId; type LoadKey = PendingImageId;
struct LoadKeyGenerator { struct LoadKeyGenerator {
counter: u64 counter: u64,
} }
impl LoadKeyGenerator { impl LoadKeyGenerator {
fn new() -> LoadKeyGenerator { fn new() -> LoadKeyGenerator {
LoadKeyGenerator { LoadKeyGenerator { counter: 0 }
counter: 0
}
} }
fn next(&mut self) -> PendingImageId { fn next(&mut self) -> PendingImageId {
self.counter += 1; self.counter += 1;
@ -244,7 +244,7 @@ impl LoadKeyGenerator {
enum LoadResult { enum LoadResult {
Loaded(Image), Loaded(Image),
PlaceholderLoaded(Arc<Image>), PlaceholderLoaded(Arc<Image>),
None None,
} }
/// Represents an image that is either being loaded /// Represents an image that is either being loaded
@ -271,10 +271,10 @@ struct PendingLoad {
impl PendingLoad { impl PendingLoad {
fn new(url: ServoUrl) -> PendingLoad { fn new(url: ServoUrl) -> PendingLoad {
PendingLoad { PendingLoad {
bytes: ImageBytes::InProgress(vec!()), bytes: ImageBytes::InProgress(vec![]),
metadata: None, metadata: None,
result: None, result: None,
listeners: vec!(), listeners: vec![],
url: url, url: url,
final_url: None, final_url: None,
} }
@ -314,20 +314,24 @@ impl ImageCacheStore {
}; };
match load_result { match load_result {
LoadResult::Loaded(ref mut image) => set_webrender_image_key(&self.webrender_api, image), LoadResult::Loaded(ref mut image) => {
LoadResult::PlaceholderLoaded(..) | LoadResult::None => {} set_webrender_image_key(&self.webrender_api, image)
},
LoadResult::PlaceholderLoaded(..) | LoadResult::None => {},
} }
let url = pending_load.final_url.clone(); let url = pending_load.final_url.clone();
let image_response = match load_result { let image_response = match load_result {
LoadResult::Loaded(image) => ImageResponse::Loaded(Arc::new(image), url.unwrap()), LoadResult::Loaded(image) => ImageResponse::Loaded(Arc::new(image), url.unwrap()),
LoadResult::PlaceholderLoaded(image) => LoadResult::PlaceholderLoaded(image) => {
ImageResponse::PlaceholderLoaded(image, self.placeholder_url.clone()), ImageResponse::PlaceholderLoaded(image, self.placeholder_url.clone())
},
LoadResult::None => ImageResponse::None, LoadResult::None => ImageResponse::None,
}; };
let completed_load = CompletedLoad::new(image_response.clone(), key); let completed_load = CompletedLoad::new(image_response.clone(), key);
self.completed_loads.insert(pending_load.url.into(), completed_load); self.completed_loads
.insert(pending_load.url.into(), completed_load);
for listener in pending_load.listeners { for listener in pending_load.listeners {
listener.respond(image_response.clone()); listener.respond(image_response.clone());
@ -336,21 +340,20 @@ impl ImageCacheStore {
/// Return a completed image if it exists, or None if there is no complete load /// Return a completed image if it exists, or None if there is no complete load
/// or the complete load is not fully decoded or is unavailable. /// or the complete load is not fully decoded or is unavailable.
fn get_completed_image_if_available(&self, fn get_completed_image_if_available(
url: &ServoUrl, &self,
placeholder: UsePlaceholder) url: &ServoUrl,
-> Option<Result<ImageOrMetadataAvailable, ImageState>> { placeholder: UsePlaceholder,
) -> Option<Result<ImageOrMetadataAvailable, ImageState>> {
self.completed_loads.get(url).map(|completed_load| { self.completed_loads.get(url).map(|completed_load| {
match (&completed_load.image_response, placeholder) { match (&completed_load.image_response, placeholder) {
(&ImageResponse::Loaded(ref image, ref url), _) | (&ImageResponse::Loaded(ref image, ref url), _) |
(&ImageResponse::PlaceholderLoaded(ref image, ref url), UsePlaceholder::Yes) => { (&ImageResponse::PlaceholderLoaded(ref image, ref url), UsePlaceholder::Yes) => Ok(
Ok(ImageOrMetadataAvailable::ImageAvailable(image.clone(), url.clone())) ImageOrMetadataAvailable::ImageAvailable(image.clone(), url.clone()),
} ),
(&ImageResponse::PlaceholderLoaded(_, _), UsePlaceholder::No) | (&ImageResponse::PlaceholderLoaded(_, _), UsePlaceholder::No) |
(&ImageResponse::None, _) | (&ImageResponse::None, _) |
(&ImageResponse::MetadataLoaded(_), _) => { (&ImageResponse::MetadataLoaded(_), _) => Err(ImageState::LoadError),
Err(ImageState::LoadError)
}
} }
}) })
} }
@ -383,18 +386,19 @@ impl ImageCache for ImageCacheImpl {
placeholder_image: get_placeholder_image(&webrender_api, &rippy_data).ok(), placeholder_image: get_placeholder_image(&webrender_api, &rippy_data).ok(),
placeholder_url: ServoUrl::parse("chrome://resources/rippy.png").unwrap(), placeholder_url: ServoUrl::parse("chrome://resources/rippy.png").unwrap(),
webrender_api: webrender_api, webrender_api: webrender_api,
})) })),
} }
} }
/// Return any available metadata or image for the given URL, /// Return any available metadata or image for the given URL,
/// or an indication that the image is not yet available if it is in progress, /// or an indication that the image is not yet available if it is in progress,
/// or else reserve a slot in the cache for the URL if the consumer can request images. /// or else reserve a slot in the cache for the URL if the consumer can request images.
fn find_image_or_metadata(&self, fn find_image_or_metadata(
url: ServoUrl, &self,
use_placeholder: UsePlaceholder, url: ServoUrl,
can_request: CanRequestImages) use_placeholder: UsePlaceholder,
-> Result<ImageOrMetadataAvailable, ImageState> { can_request: CanRequestImages,
) -> Result<ImageOrMetadataAvailable, ImageState> {
debug!("Find image or metadata for {}", url); debug!("Find image or metadata for {}", url);
let mut store = self.store.lock().unwrap(); let mut store = self.store.lock().unwrap();
if let Some(result) = store.get_completed_image_if_available(&url, use_placeholder) { if let Some(result) = store.get_completed_image_if_available(&url, use_placeholder) {
@ -409,24 +413,24 @@ impl ImageCache for ImageCacheImpl {
(&Some(Ok(_)), _) => { (&Some(Ok(_)), _) => {
debug!("Sync decoding {} ({:?})", url, key); debug!("Sync decoding {} ({:?})", url, key);
decode_bytes_sync(key, &pl.bytes.as_slice()) decode_bytes_sync(key, &pl.bytes.as_slice())
} },
(&None, &Some(ref meta)) => { (&None, &Some(ref meta)) => {
debug!("Metadata available for {} ({:?})", url, key); debug!("Metadata available for {} ({:?})", url, key);
return Ok(ImageOrMetadataAvailable::MetadataAvailable(meta.clone())) return Ok(ImageOrMetadataAvailable::MetadataAvailable(meta.clone()));
} },
(&Some(Err(_)), _) | (&None, &None) => { (&Some(Err(_)), _) | (&None, &None) => {
debug!("{} ({:?}) is still pending", url, key); debug!("{} ({:?}) is still pending", url, key);
return Err(ImageState::Pending(key)); return Err(ImageState::Pending(key));
} },
}, },
CacheResult::Miss(Some((key, _pl))) => { CacheResult::Miss(Some((key, _pl))) => {
debug!("Should be requesting {} ({:?})", url, key); debug!("Should be requesting {} ({:?})", url, key);
return Err(ImageState::NotRequested(key)); return Err(ImageState::NotRequested(key));
} },
CacheResult::Miss(None) => { CacheResult::Miss(None) => {
debug!("Couldn't find an entry for {}", url); debug!("Couldn't find an entry for {}", url);
return Err(ImageState::LoadError); return Err(ImageState::LoadError);
} },
} }
}; };
@ -468,17 +472,15 @@ impl ImageCache for ImageCacheImpl {
let mut store = self.store.lock().unwrap(); let mut store = self.store.lock().unwrap();
let pending_load = store.pending_loads.get_by_key_mut(&id).unwrap(); let pending_load = store.pending_loads.get_by_key_mut(&id).unwrap();
let metadata = match response { let metadata = match response {
Ok(meta) => { Ok(meta) => Some(match meta {
Some(match meta { FetchMetadata::Unfiltered(m) => m,
FetchMetadata::Unfiltered(m) => m, FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
FetchMetadata::Filtered { unsafe_, .. } => unsafe_, }),
})
},
Err(_) => None, Err(_) => None,
}; };
let final_url = metadata.as_ref().map(|m| m.final_url.clone()); let final_url = metadata.as_ref().map(|m| m.final_url.clone());
pending_load.final_url = final_url; pending_load.final_url = final_url;
} },
(FetchResponseMsg::ProcessResponseChunk(data), _) => { (FetchResponseMsg::ProcessResponseChunk(data), _) => {
debug!("Got some data for {:?}", id); debug!("Got some data for {:?}", id);
let mut store = self.store.lock().unwrap(); let mut store = self.store.lock().unwrap();
@ -488,16 +490,17 @@ impl ImageCache for ImageCacheImpl {
if let None = pending_load.metadata { if let None = pending_load.metadata {
if let Ok(metadata) = load_from_buf(&pending_load.bytes.as_slice()) { if let Ok(metadata) = load_from_buf(&pending_load.bytes.as_slice()) {
let dimensions = metadata.dimensions(); let dimensions = metadata.dimensions();
let img_metadata = ImageMetadata { width: dimensions.width, let img_metadata = ImageMetadata {
height: dimensions.height }; width: dimensions.width,
height: dimensions.height,
};
for listener in &pending_load.listeners { for listener in &pending_load.listeners {
listener.respond( listener.respond(ImageResponse::MetadataLoaded(img_metadata.clone()));
ImageResponse::MetadataLoaded(img_metadata.clone()));
} }
pending_load.metadata = Some(img_metadata); pending_load.metadata = Some(img_metadata);
} }
} }
} },
(FetchResponseMsg::ProcessResponseEOF(result), key) => { (FetchResponseMsg::ProcessResponseEOF(result), key) => {
debug!("Received EOF for {:?}", key); debug!("Received EOF for {:?}", key);
match result { match result {
@ -516,20 +519,20 @@ impl ImageCache for ImageCacheImpl {
debug!("Image decoded"); debug!("Image decoded");
local_store.lock().unwrap().handle_decoder(msg); local_store.lock().unwrap().handle_decoder(msg);
}); });
} },
Err(_) => { Err(_) => {
debug!("Processing error for {:?}", key); debug!("Processing error for {:?}", key);
let mut store = self.store.lock().unwrap(); let mut store = self.store.lock().unwrap();
match store.placeholder_image.clone() { match store.placeholder_image.clone() {
Some(placeholder_image) => { Some(placeholder_image) => store.complete_load(
store.complete_load( id,
id, LoadResult::PlaceholderLoaded(placeholder_image)) LoadResult::PlaceholderLoaded(placeholder_image),
} ),
None => store.complete_load(id, LoadResult::None), None => store.complete_load(id, LoadResult::None),
} }
} },
} }
} },
} }
} }

View file

@ -21,10 +21,14 @@ extern crate immeta;
extern crate ipc_channel; extern crate ipc_channel;
#[macro_use] #[macro_use]
extern crate lazy_static; extern crate lazy_static;
#[macro_use] extern crate log; #[macro_use]
extern crate log;
extern crate malloc_size_of; extern crate malloc_size_of;
#[macro_use] extern crate malloc_size_of_derive; #[macro_use]
#[macro_use] #[no_link] extern crate matches; extern crate malloc_size_of_derive;
#[macro_use]
#[no_link]
extern crate matches;
extern crate mime; extern crate mime;
extern crate mime_guess; extern crate mime_guess;
extern crate msg; extern crate msg;
@ -33,7 +37,8 @@ extern crate openssl;
extern crate pixels; extern crate pixels;
#[macro_use] #[macro_use]
extern crate profile_traits; extern crate profile_traits;
#[macro_use] extern crate serde; #[macro_use]
extern crate serde;
extern crate serde_json; extern crate serde_json;
extern crate servo_allocator; extern crate servo_allocator;
extern crate servo_arc; extern crate servo_arc;

View file

@ -25,16 +25,17 @@ pub enum MediaType {
pub enum ApacheBugFlag { pub enum ApacheBugFlag {
On, On,
Off Off,
} }
impl ApacheBugFlag { impl ApacheBugFlag {
/// <https://mimesniff.spec.whatwg.org/#supplied-mime-type-detection-algorithm> /// <https://mimesniff.spec.whatwg.org/#supplied-mime-type-detection-algorithm>
pub fn from_content_type(last_raw_content_type: &[u8]) -> ApacheBugFlag { pub fn from_content_type(last_raw_content_type: &[u8]) -> ApacheBugFlag {
if last_raw_content_type == b"text/plain" if last_raw_content_type == b"text/plain" ||
|| last_raw_content_type == b"text/plain; charset=ISO-8859-1" last_raw_content_type == b"text/plain; charset=ISO-8859-1" ||
|| last_raw_content_type == b"text/plain; charset=iso-8859-1" last_raw_content_type == b"text/plain; charset=iso-8859-1" ||
|| last_raw_content_type == b"text/plain; charset=UTF-8" { last_raw_content_type == b"text/plain; charset=UTF-8"
{
ApacheBugFlag::On ApacheBugFlag::On
} else { } else {
ApacheBugFlag::Off ApacheBugFlag::Off
@ -45,19 +46,22 @@ impl ApacheBugFlag {
#[derive(PartialEq)] #[derive(PartialEq)]
pub enum NoSniffFlag { pub enum NoSniffFlag {
On, On,
Off Off,
} }
impl MimeClassifier { impl MimeClassifier {
//Performs MIME Type Sniffing Algorithm (sections 7 and 8) //Performs MIME Type Sniffing Algorithm (sections 7 and 8)
pub fn classify<'a>(&'a self, pub fn classify<'a>(
context: LoadContext, &'a self,
no_sniff_flag: NoSniffFlag, context: LoadContext,
apache_bug_flag: ApacheBugFlag, no_sniff_flag: NoSniffFlag,
supplied_type: &Option<Mime>, apache_bug_flag: ApacheBugFlag,
data: &'a [u8]) -> Mime { supplied_type: &Option<Mime>,
let supplied_type_or_octet_stream = supplied_type.clone().unwrap_or(mime::APPLICATION_OCTET_STREAM); data: &'a [u8],
) -> Mime {
let supplied_type_or_octet_stream = supplied_type
.clone()
.unwrap_or(mime::APPLICATION_OCTET_STREAM);
match context { match context {
LoadContext::Browsing => match *supplied_type { LoadContext::Browsing => match *supplied_type {
None => self.sniff_unknown_type(no_sniff_flag, data), None => self.sniff_unknown_type(no_sniff_flag, data),
@ -69,30 +73,41 @@ impl MimeClassifier {
NoSniffFlag::On => supplied_type.clone(), NoSniffFlag::On => supplied_type.clone(),
NoSniffFlag::Off => match apache_bug_flag { NoSniffFlag::Off => match apache_bug_flag {
ApacheBugFlag::On => self.sniff_text_or_data(data), ApacheBugFlag::On => self.sniff_text_or_data(data),
ApacheBugFlag::Off => match MimeClassifier::get_media_type(supplied_type) { ApacheBugFlag::Off => {
Some(MediaType::Html) => self.feeds_classifier.classify(data), match MimeClassifier::get_media_type(supplied_type) {
Some(MediaType::Image) => self.image_classifier.classify(data), Some(MediaType::Html) => {
Some(MediaType::AudioVideo) => self.audio_video_classifier.classify(data), self.feeds_classifier.classify(data)
Some(MediaType::Xml) | None => None, },
}.unwrap_or(supplied_type.clone()) Some(MediaType::Image) => {
} self.image_classifier.classify(data)
},
Some(MediaType::AudioVideo) => {
self.audio_video_classifier.classify(data)
},
Some(MediaType::Xml) | None => None,
}
.unwrap_or(supplied_type.clone())
},
},
} }
} }
} },
}, },
LoadContext::Image => { LoadContext::Image => {
// Section 8.2 Sniffing an image context // Section 8.2 Sniffing an image context
match MimeClassifier::maybe_get_media_type(supplied_type) { match MimeClassifier::maybe_get_media_type(supplied_type) {
Some(MediaType::Xml) => None, Some(MediaType::Xml) => None,
_ => self.image_classifier.classify(data), _ => self.image_classifier.classify(data),
}.unwrap_or(supplied_type_or_octet_stream) }
.unwrap_or(supplied_type_or_octet_stream)
}, },
LoadContext::AudioVideo => { LoadContext::AudioVideo => {
// Section 8.3 Sniffing an image context // Section 8.3 Sniffing an image context
match MimeClassifier::maybe_get_media_type(supplied_type) { match MimeClassifier::maybe_get_media_type(supplied_type) {
Some(MediaType::Xml) => None, Some(MediaType::Xml) => None,
_ => self.audio_video_classifier.classify(data), _ => self.audio_video_classifier.classify(data),
}.unwrap_or(supplied_type_or_octet_stream) }
.unwrap_or(supplied_type_or_octet_stream)
}, },
LoadContext::Plugin => { LoadContext::Plugin => {
// 8.4 Sniffing in a plugin context // 8.4 Sniffing in a plugin context
@ -129,7 +144,8 @@ impl MimeClassifier {
match MimeClassifier::maybe_get_media_type(supplied_type) { match MimeClassifier::maybe_get_media_type(supplied_type) {
Some(MediaType::Xml) => None, Some(MediaType::Xml) => None,
_ => self.font_classifier.classify(data), _ => self.font_classifier.classify(data),
}.unwrap_or(supplied_type_or_octet_stream) }
.unwrap_or(supplied_type_or_octet_stream)
}, },
LoadContext::TextTrack => { LoadContext::TextTrack => {
// 8.8 Sniffing in a text track context // 8.8 Sniffing in a text track context
@ -149,16 +165,16 @@ impl MimeClassifier {
} }
pub fn new() -> MimeClassifier { pub fn new() -> MimeClassifier {
MimeClassifier { MimeClassifier {
image_classifier: GroupedClassifier::image_classifer(), image_classifier: GroupedClassifier::image_classifer(),
audio_video_classifier: GroupedClassifier::audio_video_classifier(), audio_video_classifier: GroupedClassifier::audio_video_classifier(),
scriptable_classifier: GroupedClassifier::scriptable_classifier(), scriptable_classifier: GroupedClassifier::scriptable_classifier(),
plaintext_classifier: GroupedClassifier::plaintext_classifier(), plaintext_classifier: GroupedClassifier::plaintext_classifier(),
archive_classifier: GroupedClassifier::archive_classifier(), archive_classifier: GroupedClassifier::archive_classifier(),
binary_or_plaintext: BinaryOrPlaintextClassifier, binary_or_plaintext: BinaryOrPlaintextClassifier,
feeds_classifier: FeedsClassifier, feeds_classifier: FeedsClassifier,
font_classifier: GroupedClassifier::font_classifier() font_classifier: GroupedClassifier::font_classifier(),
} }
} }
pub fn validate(&self) -> Result<(), String> { pub fn validate(&self) -> Result<(), String> {
@ -182,7 +198,8 @@ impl MimeClassifier {
None None
}; };
sniffed.or_else(|| self.plaintext_classifier.classify(data)) sniffed
.or_else(|| self.plaintext_classifier.classify(data))
.or_else(|| self.image_classifier.classify(data)) .or_else(|| self.image_classifier.classify(data))
.or_else(|| self.audio_video_classifier.classify(data)) .or_else(|| self.audio_video_classifier.classify(data))
.or_else(|| self.archive_classifier.classify(data)) .or_else(|| self.archive_classifier.classify(data))
@ -191,13 +208,15 @@ impl MimeClassifier {
} }
fn sniff_text_or_data<'a>(&'a self, data: &'a [u8]) -> Mime { fn sniff_text_or_data<'a>(&'a self, data: &'a [u8]) -> Mime {
self.binary_or_plaintext.classify(data).expect("BinaryOrPlaintextClassifier always succeeds") self.binary_or_plaintext
.classify(data)
.expect("BinaryOrPlaintextClassifier always succeeds")
} }
fn is_xml(mt: &Mime) -> bool { fn is_xml(mt: &Mime) -> bool {
mt.suffix() == Some(mime::XML) || mt.suffix() == Some(mime::XML) ||
(mt.type_() == mime::APPLICATION && mt.subtype() == mime::XML) || (mt.type_() == mime::APPLICATION && mt.subtype() == mime::XML) ||
(mt.type_() == mime::TEXT && mt.subtype() == mime::XML) (mt.type_() == mime::TEXT && mt.subtype() == mime::XML)
} }
fn is_html(mt: &Mime) -> bool { fn is_html(mt: &Mime) -> bool {
@ -210,21 +229,21 @@ impl MimeClassifier {
fn is_audio_video(mt: &Mime) -> bool { fn is_audio_video(mt: &Mime) -> bool {
mt.type_() == mime::AUDIO || mt.type_() == mime::AUDIO ||
mt.type_() == mime::VIDEO || mt.type_() == mime::VIDEO ||
mt.type_() == mime::APPLICATION && mt.subtype() == mime::OGG mt.type_() == mime::APPLICATION && mt.subtype() == mime::OGG
} }
fn is_explicit_unknown(mt: &Mime) -> bool { fn is_explicit_unknown(mt: &Mime) -> bool {
mt.type_().as_str() == "unknown" && mt.subtype().as_str() == "unknown" || mt.type_().as_str() == "unknown" && mt.subtype().as_str() == "unknown" ||
mt.type_() == mime::APPLICATION && mt.subtype().as_str() == "unknown" || mt.type_() == mime::APPLICATION && mt.subtype().as_str() == "unknown" ||
mt.type_() == mime::STAR && mt.subtype() == mime::STAR mt.type_() == mime::STAR && mt.subtype() == mime::STAR
} }
fn get_media_type(mime: &Mime) -> Option<MediaType> { fn get_media_type(mime: &Mime) -> Option<MediaType> {
if MimeClassifier::is_xml(&mime) { if MimeClassifier::is_xml(&mime) {
Some(MediaType::Xml) Some(MediaType::Xml)
} else if MimeClassifier::is_html(&mime) { } else if MimeClassifier::is_html(&mime) {
Some(MediaType::Html) Some(MediaType::Html)
} else if MimeClassifier::is_image(&mime) { } else if MimeClassifier::is_image(&mime) {
Some(MediaType::Image) Some(MediaType::Image)
} else if MimeClassifier::is_audio_video(&mime) { } else if MimeClassifier::is_audio_video(&mime) {
@ -235,9 +254,9 @@ impl MimeClassifier {
} }
fn maybe_get_media_type(supplied_type: &Option<Mime>) -> Option<MediaType> { fn maybe_get_media_type(supplied_type: &Option<Mime>) -> Option<MediaType> {
supplied_type.as_ref().and_then(|ref mime| { supplied_type
MimeClassifier::get_media_type(mime) .as_ref()
}) .and_then(|ref mime| MimeClassifier::get_media_type(mime))
} }
} }
@ -252,7 +271,7 @@ trait Matches {
fn matches(&mut self, matches: &[u8]) -> bool; fn matches(&mut self, matches: &[u8]) -> bool;
} }
impl <'a, T: Iterator<Item=&'a u8> + Clone> Matches for T { impl<'a, T: Iterator<Item = &'a u8> + Clone> Matches for T {
// Matching function that works on an iterator. // Matching function that works on an iterator.
// see if the next matches.len() bytes in data_iterator equal matches // see if the next matches.len() bytes in data_iterator equal matches
// move iterator and return true or just return false // move iterator and return true or just return false
@ -270,7 +289,7 @@ impl <'a, T: Iterator<Item=&'a u8> + Clone> Matches for T {
fn matches(&mut self, matches: &[u8]) -> bool { fn matches(&mut self, matches: &[u8]) -> bool {
if self.clone().nth(matches.len()).is_none() { if self.clone().nth(matches.len()).is_none() {
// there are less than matches.len() elements in self // there are less than matches.len() elements in self
return false return false;
} }
let result = self.clone().zip(matches).all(|(s, m)| *s == *m); let result = self.clone().zip(matches).all(|(s, m)| *s == *m);
if result { if result {
@ -294,64 +313,68 @@ impl ByteMatcher {
} else if data == self.pattern { } else if data == self.pattern {
Some(self.pattern.len()) Some(self.pattern.len())
} else { } else {
data[..data.len() - self.pattern.len() + 1].iter() data[..data.len() - self.pattern.len() + 1]
.iter()
.position(|x| !self.leading_ignore.contains(x)) .position(|x| !self.leading_ignore.contains(x))
.and_then(|start| .and_then(|start| {
if data[start..].iter() if data[start..]
.zip(self.pattern.iter()).zip(self.mask.iter()) .iter()
.all(|((&data, &pattern), &mask)| (data & mask) == pattern) { .zip(self.pattern.iter())
.zip(self.mask.iter())
.all(|((&data, &pattern), &mask)| (data & mask) == pattern)
{
Some(start + self.pattern.len()) Some(start + self.pattern.len())
} else { } else {
None None
}) }
})
} }
} }
} }
impl MIMEChecker for ByteMatcher { impl MIMEChecker for ByteMatcher {
fn classify(&self, data: &[u8]) -> Option<Mime> { fn classify(&self, data: &[u8]) -> Option<Mime> {
self.matches(data).map(|_| { self.matches(data).map(|_| self.content_type.clone())
self.content_type.clone()
})
} }
fn validate(&self) -> Result<(), String> { fn validate(&self) -> Result<(), String> {
if self.pattern.len() == 0 { if self.pattern.len() == 0 {
return Err(format!( return Err(format!("Zero length pattern for {:?}", self.content_type));
"Zero length pattern for {:?}",
self.content_type
))
} }
if self.pattern.len() != self.mask.len() { if self.pattern.len() != self.mask.len() {
return Err(format!( return Err(format!(
"Unequal pattern and mask length for {:?}", "Unequal pattern and mask length for {:?}",
self.content_type self.content_type
)) ));
} }
if self.pattern.iter().zip(self.mask.iter()).any( if self
|(&pattern, &mask)| pattern & mask != pattern .pattern
) { .iter()
.zip(self.mask.iter())
.any(|(&pattern, &mask)| pattern & mask != pattern)
{
return Err(format!( return Err(format!(
"Pattern not pre-masked for {:?}", "Pattern not pre-masked for {:?}",
self.content_type self.content_type
)) ));
} }
Ok(()) Ok(())
} }
} }
struct TagTerminatedByteMatcher { struct TagTerminatedByteMatcher {
matcher: ByteMatcher matcher: ByteMatcher,
} }
impl MIMEChecker for TagTerminatedByteMatcher { impl MIMEChecker for TagTerminatedByteMatcher {
fn classify(&self, data: &[u8]) -> Option<Mime> { fn classify(&self, data: &[u8]) -> Option<Mime> {
self.matcher.matches(data).and_then(|j| self.matcher.matches(data).and_then(|j| {
if j < data.len() && (data[j] == b' ' || data[j] == b'>') { if j < data.len() && (data[j] == b' ' || data[j] == b'>') {
Some(self.matcher.content_type.clone()) Some(self.matcher.content_type.clone())
} else { } else {
None None
}) }
})
} }
fn validate(&self) -> Result<(), String> { fn validate(&self) -> Result<(), String> {
@ -367,8 +390,10 @@ impl Mp4Matcher {
return false; return false;
} }
let box_size = ((data[0] as u32) << 24 | (data[1] as u32) << 16 | let box_size = ((data[0] as u32) << 24 |
(data[2] as u32) << 8 | (data[3] as u32)) as usize; (data[1] as u32) << 16 |
(data[2] as u32) << 8 |
(data[3] as u32)) as usize;
if (data.len() < box_size) || (box_size % 4 != 0) { if (data.len() < box_size) || (box_size % 4 != 0) {
return false; return false;
} }
@ -380,9 +405,10 @@ impl Mp4Matcher {
let mp4 = [0x6D, 0x70, 0x34]; let mp4 = [0x6D, 0x70, 0x34];
data[8..].starts_with(&mp4) || data[8..].starts_with(&mp4) ||
data[16..box_size].chunks(4).any(|chunk| chunk.starts_with(&mp4)) data[16..box_size]
.chunks(4)
.any(|chunk| chunk.starts_with(&mp4))
} }
} }
impl MIMEChecker for Mp4Matcher { impl MIMEChecker for Mp4Matcher {
fn classify(&self, data: &[u8]) -> Option<Mime> { fn classify(&self, data: &[u8]) -> Option<Mime> {
@ -403,14 +429,16 @@ struct BinaryOrPlaintextClassifier;
impl BinaryOrPlaintextClassifier { impl BinaryOrPlaintextClassifier {
fn classify_impl(&self, data: &[u8]) -> Mime { fn classify_impl(&self, data: &[u8]) -> Mime {
if data.starts_with(&[0xFFu8, 0xFEu8]) || if data.starts_with(&[0xFFu8, 0xFEu8]) ||
data.starts_with(&[0xFEu8, 0xFFu8]) || data.starts_with(&[0xFEu8, 0xFFu8]) ||
data.starts_with(&[0xEFu8, 0xBBu8, 0xBFu8]) data.starts_with(&[0xEFu8, 0xBBu8, 0xBFu8])
{ {
mime::TEXT_PLAIN mime::TEXT_PLAIN
} else if data.iter().any(|&x| x <= 0x08u8 || } else if data.iter().any(|&x| {
x == 0x0Bu8 || x <= 0x08u8 ||
(x >= 0x0Eu8 && x <= 0x1Au8) || x == 0x0Bu8 ||
(x >= 0x1Cu8 && x <= 0x1Fu8)) { (x >= 0x0Eu8 && x <= 0x1Au8) ||
(x >= 0x1Cu8 && x <= 0x1Fu8)
}) {
mime::APPLICATION_OCTET_STREAM mime::APPLICATION_OCTET_STREAM
} else { } else {
mime::TEXT_PLAIN mime::TEXT_PLAIN
@ -425,7 +453,6 @@ impl MIMEChecker for BinaryOrPlaintextClassifier {
fn validate(&self) -> Result<(), String> { fn validate(&self) -> Result<(), String> {
Ok(()) Ok(())
} }
} }
struct GroupedClassifier { struct GroupedClassifier {
byte_matchers: Vec<Box<MIMEChecker + Send + Sync>>, byte_matchers: Vec<Box<MIMEChecker + Send + Sync>>,
@ -442,7 +469,7 @@ impl GroupedClassifier {
Box::new(ByteMatcher::image_webp()), Box::new(ByteMatcher::image_webp()),
Box::new(ByteMatcher::image_png()), Box::new(ByteMatcher::image_png()),
Box::new(ByteMatcher::image_jpeg()), Box::new(ByteMatcher::image_jpeg()),
] ],
} }
} }
fn audio_video_classifier() -> GroupedClassifier { fn audio_video_classifier() -> GroupedClassifier {
@ -456,8 +483,8 @@ impl GroupedClassifier {
Box::new(ByteMatcher::audio_midi()), Box::new(ByteMatcher::audio_midi()),
Box::new(ByteMatcher::video_avi()), Box::new(ByteMatcher::video_avi()),
Box::new(ByteMatcher::audio_wave()), Box::new(ByteMatcher::audio_wave()),
Box::new(Mp4Matcher) Box::new(Mp4Matcher),
] ],
} }
} }
fn scriptable_classifier() -> GroupedClassifier { fn scriptable_classifier() -> GroupedClassifier {
@ -481,8 +508,8 @@ impl GroupedClassifier {
Box::new(ByteMatcher::text_html_p()), Box::new(ByteMatcher::text_html_p()),
Box::new(ByteMatcher::text_html_comment()), Box::new(ByteMatcher::text_html_comment()),
Box::new(ByteMatcher::text_xml()), Box::new(ByteMatcher::text_xml()),
Box::new(ByteMatcher::application_pdf()) Box::new(ByteMatcher::application_pdf()),
] ],
} }
} }
fn plaintext_classifier() -> GroupedClassifier { fn plaintext_classifier() -> GroupedClassifier {
@ -491,8 +518,8 @@ impl GroupedClassifier {
Box::new(ByteMatcher::text_plain_utf_8_bom()), Box::new(ByteMatcher::text_plain_utf_8_bom()),
Box::new(ByteMatcher::text_plain_utf_16le_bom()), Box::new(ByteMatcher::text_plain_utf_16le_bom()),
Box::new(ByteMatcher::text_plain_utf_16be_bom()), Box::new(ByteMatcher::text_plain_utf_16be_bom()),
Box::new(ByteMatcher::application_postscript()) Box::new(ByteMatcher::application_postscript()),
] ],
} }
} }
fn archive_classifier() -> GroupedClassifier { fn archive_classifier() -> GroupedClassifier {
@ -500,8 +527,8 @@ impl GroupedClassifier {
byte_matchers: vec![ byte_matchers: vec![
Box::new(ByteMatcher::application_x_gzip()), Box::new(ByteMatcher::application_x_gzip()),
Box::new(ByteMatcher::application_zip()), Box::new(ByteMatcher::application_zip()),
Box::new(ByteMatcher::application_x_rar_compressed()) Box::new(ByteMatcher::application_x_rar_compressed()),
] ],
} }
} }
@ -513,7 +540,7 @@ impl GroupedClassifier {
Box::new(ByteMatcher::open_type()), Box::new(ByteMatcher::open_type()),
Box::new(ByteMatcher::true_type()), Box::new(ByteMatcher::true_type()),
Box::new(ByteMatcher::application_vnd_ms_font_object()), Box::new(ByteMatcher::application_vnd_ms_font_object()),
] ],
} }
} }
} }
@ -536,7 +563,7 @@ impl MIMEChecker for GroupedClassifier {
enum Match { enum Match {
Start, Start,
DidNotMatch, DidNotMatch,
StartAndEnd StartAndEnd,
} }
impl Match { impl Match {
@ -549,7 +576,9 @@ impl Match {
} }
fn eats_until<'a, T>(matcher: &mut T, start: &[u8], end: &[u8]) -> Match fn eats_until<'a, T>(matcher: &mut T, start: &[u8], end: &[u8]) -> Match
where T: Iterator<Item=&'a u8> + Clone { where
T: Iterator<Item = &'a u8> + Clone,
{
if !matcher.matches(start) { if !matcher.matches(start) {
Match::DidNotMatch Match::DidNotMatch
} else if end.len() == 1 { } else if end.len() == 1 {
@ -593,11 +622,12 @@ impl FeedsClassifier {
// Steps 5.2.1 to 5.2.4 // Steps 5.2.1 to 5.2.4
match eats_until(&mut matcher, b"?", b"?>") match eats_until(&mut matcher, b"?", b"?>")
.chain(|| eats_until(&mut matcher, b"!--", b"-->")) .chain(|| eats_until(&mut matcher, b"!--", b"-->"))
.chain(|| eats_until(&mut matcher, b"!", b">")) { .chain(|| eats_until(&mut matcher, b"!", b">"))
{
Match::StartAndEnd => continue, Match::StartAndEnd => continue,
Match::DidNotMatch => {}, Match::DidNotMatch => {},
Match::Start => return None Match::Start => return None,
} }
// Step 5.2.5 // Step 5.2.5
@ -611,15 +641,21 @@ impl FeedsClassifier {
// Step 5.2.7 // Step 5.2.7
if matcher.matches(b"rdf:RDF") { if matcher.matches(b"rdf:RDF") {
while matcher.next().is_some() { while matcher.next().is_some() {
match eats_until(&mut matcher, match eats_until(
b"http://purl.org/rss/1.0/", &mut matcher,
b"http://www.w3.org/1999/02/22-rdf-syntax-ns#") b"http://purl.org/rss/1.0/",
.chain(|| eats_until(&mut matcher, b"http://www.w3.org/1999/02/22-rdf-syntax-ns#",
b"http://www.w3.org/1999/02/22-rdf-syntax-ns#", )
b"http://purl.org/rss/1.0/")) { .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("application/rss+xml".parse().unwrap()), Match::StartAndEnd => return Some("application/rss+xml".parse().unwrap()),
Match::DidNotMatch => {}, Match::DidNotMatch => {},
Match::Start => return None Match::Start => return None,
} }
} }
return None; return None;
@ -630,7 +666,7 @@ impl FeedsClassifier {
impl MIMEChecker for FeedsClassifier { impl MIMEChecker for FeedsClassifier {
fn classify(&self, data: &[u8]) -> Option<Mime> { fn classify(&self, data: &[u8]) -> Option<Mime> {
self.classify_impl(data) self.classify_impl(data)
} }
fn validate(&self) -> Result<(), String> { fn validate(&self) -> Result<(), String> {
@ -647,7 +683,7 @@ impl ByteMatcher {
pattern: b"\x00\x00\x01\x00", pattern: b"\x00\x00\x01\x00",
mask: b"\xFF\xFF\xFF\xFF", mask: b"\xFF\xFF\xFF\xFF",
content_type: "image/x-icon".parse().unwrap(), content_type: "image/x-icon".parse().unwrap(),
leading_ignore: &[] leading_ignore: &[],
} }
} }
//A Windows Cursor signature. //A Windows Cursor signature.
@ -656,7 +692,7 @@ impl ByteMatcher {
pattern: b"\x00\x00\x02\x00", pattern: b"\x00\x00\x02\x00",
mask: b"\xFF\xFF\xFF\xFF", mask: b"\xFF\xFF\xFF\xFF",
content_type: "image/x-icon".parse().unwrap(), content_type: "image/x-icon".parse().unwrap(),
leading_ignore: &[] leading_ignore: &[],
} }
} }
//The string "BM", a BMP signature. //The string "BM", a BMP signature.
@ -665,7 +701,7 @@ impl ByteMatcher {
pattern: b"BM", pattern: b"BM",
mask: b"\xFF\xFF", mask: b"\xFF\xFF",
content_type: mime::IMAGE_BMP, content_type: mime::IMAGE_BMP,
leading_ignore: &[] leading_ignore: &[],
} }
} }
//The string "GIF89a", a GIF signature. //The string "GIF89a", a GIF signature.
@ -674,7 +710,7 @@ impl ByteMatcher {
pattern: b"GIF89a", pattern: b"GIF89a",
mask: b"\xFF\xFF\xFF\xFF\xFF\xFF", mask: b"\xFF\xFF\xFF\xFF\xFF\xFF",
content_type: mime::IMAGE_GIF, content_type: mime::IMAGE_GIF,
leading_ignore: &[] leading_ignore: &[],
} }
} }
//The string "GIF87a", a GIF signature. //The string "GIF87a", a GIF signature.
@ -683,7 +719,7 @@ impl ByteMatcher {
pattern: b"GIF87a", pattern: b"GIF87a",
mask: b"\xFF\xFF\xFF\xFF\xFF\xFF", mask: b"\xFF\xFF\xFF\xFF\xFF\xFF",
content_type: mime::IMAGE_GIF, content_type: mime::IMAGE_GIF,
leading_ignore: &[] leading_ignore: &[],
} }
} }
//The string "RIFF" followed by four bytes followed by the string "WEBPVP". //The string "RIFF" followed by four bytes followed by the string "WEBPVP".
@ -692,7 +728,7 @@ impl ByteMatcher {
pattern: b"RIFF\x00\x00\x00\x00WEBPVP", pattern: b"RIFF\x00\x00\x00\x00WEBPVP",
mask: b"\xFF\xFF\xFF\xFF\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF", mask: b"\xFF\xFF\xFF\xFF\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF",
content_type: "image/webp".parse().unwrap(), content_type: "image/webp".parse().unwrap(),
leading_ignore: &[] leading_ignore: &[],
} }
} }
//An error-checking byte followed by the string "PNG" followed by CR LF SUB LF, the PNG //An error-checking byte followed by the string "PNG" followed by CR LF SUB LF, the PNG
@ -702,7 +738,7 @@ impl ByteMatcher {
pattern: b"\x89PNG\r\n\x1A\n", pattern: b"\x89PNG\r\n\x1A\n",
mask: b"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", mask: b"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF",
content_type: mime::IMAGE_PNG, content_type: mime::IMAGE_PNG,
leading_ignore: &[] leading_ignore: &[],
} }
} }
// The JPEG Start of Image marker followed by the indicator byte of another marker. // The JPEG Start of Image marker followed by the indicator byte of another marker.
@ -711,7 +747,7 @@ impl ByteMatcher {
pattern: b"\xFF\xD8\xFF", pattern: b"\xFF\xD8\xFF",
mask: b"\xFF\xFF\xFF", mask: b"\xFF\xFF\xFF",
content_type: mime::IMAGE_JPEG, content_type: mime::IMAGE_JPEG,
leading_ignore: &[] leading_ignore: &[],
} }
} }
//The WebM signature. [TODO: Use more bytes?] //The WebM signature. [TODO: Use more bytes?]
@ -720,7 +756,7 @@ impl ByteMatcher {
pattern: b"\x1A\x45\xDF\xA3", pattern: b"\x1A\x45\xDF\xA3",
mask: b"\xFF\xFF\xFF\xFF", mask: b"\xFF\xFF\xFF\xFF",
content_type: "video/webm".parse().unwrap(), content_type: "video/webm".parse().unwrap(),
leading_ignore: &[] leading_ignore: &[],
} }
} }
//The string ".snd", the basic audio signature. //The string ".snd", the basic audio signature.
@ -729,16 +765,16 @@ impl ByteMatcher {
pattern: b".snd", pattern: b".snd",
mask: b"\xFF\xFF\xFF\xFF", mask: b"\xFF\xFF\xFF\xFF",
content_type: "audio/basic".parse().unwrap(), content_type: "audio/basic".parse().unwrap(),
leading_ignore: &[] leading_ignore: &[],
} }
} }
//The string "FORM" followed by four bytes followed by the string "AIFF", the AIFF signature. //The string "FORM" followed by four bytes followed by the string "AIFF", the AIFF signature.
fn audio_aiff() -> ByteMatcher { fn audio_aiff() -> ByteMatcher {
ByteMatcher { ByteMatcher {
pattern: b"FORM\x00\x00\x00\x00AIFF", pattern: b"FORM\x00\x00\x00\x00AIFF",
mask: b"\xFF\xFF\xFF\xFF\x00\x00\x00\x00\xFF\xFF\xFF\xFF", mask: b"\xFF\xFF\xFF\xFF\x00\x00\x00\x00\xFF\xFF\xFF\xFF",
content_type: "audio/aiff".parse().unwrap(), content_type: "audio/aiff".parse().unwrap(),
leading_ignore: &[] leading_ignore: &[],
} }
} }
//The string "ID3", the ID3v2-tagged MP3 signature. //The string "ID3", the ID3v2-tagged MP3 signature.
@ -747,7 +783,7 @@ impl ByteMatcher {
pattern: b"ID3", pattern: b"ID3",
mask: b"\xFF\xFF\xFF", mask: b"\xFF\xFF\xFF",
content_type: "audio/mpeg".parse().unwrap(), content_type: "audio/mpeg".parse().unwrap(),
leading_ignore: &[] leading_ignore: &[],
} }
} }
//The string "OggS" followed by NUL, the Ogg container signature. //The string "OggS" followed by NUL, the Ogg container signature.
@ -756,7 +792,7 @@ impl ByteMatcher {
pattern: b"OggS\x00", pattern: b"OggS\x00",
mask: b"\xFF\xFF\xFF\xFF\xFF", mask: b"\xFF\xFF\xFF\xFF\xFF",
content_type: "application/ogg".parse().unwrap(), content_type: "application/ogg".parse().unwrap(),
leading_ignore: &[] leading_ignore: &[],
} }
} }
//The string "MThd" followed by four bytes representing the number 6 in 32 bits (big-endian), //The string "MThd" followed by four bytes representing the number 6 in 32 bits (big-endian),
@ -766,7 +802,7 @@ impl ByteMatcher {
pattern: b"MThd\x00\x00\x00\x06", pattern: b"MThd\x00\x00\x00\x06",
mask: b"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", mask: b"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF",
content_type: "audio/midi".parse().unwrap(), content_type: "audio/midi".parse().unwrap(),
leading_ignore: &[] leading_ignore: &[],
} }
} }
//The string "RIFF" followed by four bytes followed by the string "AVI ", the AVI signature. //The string "RIFF" followed by four bytes followed by the string "AVI ", the AVI signature.
@ -775,7 +811,7 @@ impl ByteMatcher {
pattern: b"RIFF\x00\x00\x00\x00AVI ", pattern: b"RIFF\x00\x00\x00\x00AVI ",
mask: b"\xFF\xFF\xFF\xFF\x00\x00\x00\x00\xFF\xFF\xFF\xFF", mask: b"\xFF\xFF\xFF\xFF\x00\x00\x00\x00\xFF\xFF\xFF\xFF",
content_type: "video/avi".parse().unwrap(), content_type: "video/avi".parse().unwrap(),
leading_ignore: &[] leading_ignore: &[],
} }
} }
// The string "RIFF" followed by four bytes followed by the string "WAVE", the WAVE signature. // The string "RIFF" followed by four bytes followed by the string "WAVE", the WAVE signature.
@ -784,7 +820,7 @@ impl ByteMatcher {
pattern: b"RIFF\x00\x00\x00\x00WAVE", pattern: b"RIFF\x00\x00\x00\x00WAVE",
mask: b"\xFF\xFF\xFF\xFF\x00\x00\x00\x00\xFF\xFF\xFF\xFF", mask: b"\xFF\xFF\xFF\xFF\x00\x00\x00\x00\xFF\xFF\xFF\xFF",
content_type: "audio/wave".parse().unwrap(), content_type: "audio/wave".parse().unwrap(),
leading_ignore: &[] leading_ignore: &[],
} }
} }
// doctype terminated with Tag terminating (TT) Byte // doctype terminated with Tag terminating (TT) Byte
@ -794,8 +830,8 @@ impl ByteMatcher {
pattern: b"<!DOCTYPE HTML", pattern: b"<!DOCTYPE HTML",
mask: b"\xFF\xFF\xDF\xDF\xDF\xDF\xDF\xDF\xDF\xFF\xDF\xDF\xDF\xDF", mask: b"\xFF\xFF\xDF\xDF\xDF\xDF\xDF\xDF\xDF\xFF\xDF\xDF\xDF\xDF",
content_type: mime::TEXT_HTML, content_type: mime::TEXT_HTML,
leading_ignore: b"\t\n\x0C\r " leading_ignore: b"\t\n\x0C\r ",
} },
} }
} }
@ -806,20 +842,20 @@ impl ByteMatcher {
pattern: b"<HTML", pattern: b"<HTML",
mask: b"\xFF\xDF\xDF\xDF\xDF", mask: b"\xFF\xDF\xDF\xDF\xDF",
content_type: mime::TEXT_HTML, content_type: mime::TEXT_HTML,
leading_ignore: b"\t\n\x0C\r " leading_ignore: b"\t\n\x0C\r ",
} },
} }
} }
// head terminated with Tag Terminating (TT) Byte // head terminated with Tag Terminating (TT) Byte
fn text_html_head() -> TagTerminatedByteMatcher { fn text_html_head() -> TagTerminatedByteMatcher {
TagTerminatedByteMatcher { TagTerminatedByteMatcher {
matcher: ByteMatcher { matcher: ByteMatcher {
pattern: b"<HEAD", pattern: b"<HEAD",
mask: b"\xFF\xDF\xDF\xDF\xDF", mask: b"\xFF\xDF\xDF\xDF\xDF",
content_type: mime::TEXT_HTML, content_type: mime::TEXT_HTML,
leading_ignore: b"\t\n\x0C\r " leading_ignore: b"\t\n\x0C\r ",
} },
} }
} }
@ -830,8 +866,8 @@ impl ByteMatcher {
pattern: b"<SCRIPT", pattern: b"<SCRIPT",
mask: b"\xFF\xDF\xDF\xDF\xDF\xDF\xDF", mask: b"\xFF\xDF\xDF\xDF\xDF\xDF\xDF",
content_type: mime::TEXT_HTML, content_type: mime::TEXT_HTML,
leading_ignore: b"\t\n\x0C\r " leading_ignore: b"\t\n\x0C\r ",
} },
} }
} }
@ -842,8 +878,8 @@ impl ByteMatcher {
pattern: b"<IFRAME", pattern: b"<IFRAME",
mask: b"\xFF\xDF\xDF\xDF\xDF\xDF\xDF", mask: b"\xFF\xDF\xDF\xDF\xDF\xDF\xDF",
content_type: mime::TEXT_HTML, content_type: mime::TEXT_HTML,
leading_ignore: b"\t\n\x0C\r " leading_ignore: b"\t\n\x0C\r ",
} },
} }
} }
@ -854,8 +890,8 @@ impl ByteMatcher {
pattern: b"<H1", pattern: b"<H1",
mask: b"\xFF\xDF\xFF", mask: b"\xFF\xDF\xFF",
content_type: mime::TEXT_HTML, content_type: mime::TEXT_HTML,
leading_ignore: b"\t\n\x0C\r " leading_ignore: b"\t\n\x0C\r ",
} },
} }
} }
@ -866,8 +902,8 @@ impl ByteMatcher {
pattern: b"<DIV", pattern: b"<DIV",
mask: b"\xFF\xDF\xDF\xDF", mask: b"\xFF\xDF\xDF\xDF",
content_type: mime::TEXT_HTML, content_type: mime::TEXT_HTML,
leading_ignore: b"\t\n\x0C\r " leading_ignore: b"\t\n\x0C\r ",
} },
} }
} }
@ -878,8 +914,8 @@ impl ByteMatcher {
pattern: b"<FONT", pattern: b"<FONT",
mask: b"\xFF\xDF\xDF\xDF\xDF", mask: b"\xFF\xDF\xDF\xDF\xDF",
content_type: mime::TEXT_HTML, content_type: mime::TEXT_HTML,
leading_ignore: b"\t\n\x0C\r " leading_ignore: b"\t\n\x0C\r ",
} },
} }
} }
@ -887,11 +923,11 @@ impl ByteMatcher {
fn text_html_table() -> TagTerminatedByteMatcher { fn text_html_table() -> TagTerminatedByteMatcher {
TagTerminatedByteMatcher { TagTerminatedByteMatcher {
matcher: ByteMatcher { matcher: ByteMatcher {
pattern: b"<TABLE", pattern: b"<TABLE",
mask: b"\xFF\xDF\xDF\xDF\xDF\xDF", mask: b"\xFF\xDF\xDF\xDF\xDF\xDF",
content_type: mime::TEXT_HTML, content_type: mime::TEXT_HTML,
leading_ignore: b"\t\n\x0C\r " leading_ignore: b"\t\n\x0C\r ",
} },
} }
} }
@ -902,8 +938,8 @@ impl ByteMatcher {
pattern: b"<A", pattern: b"<A",
mask: b"\xFF\xDF", mask: b"\xFF\xDF",
content_type: mime::TEXT_HTML, content_type: mime::TEXT_HTML,
leading_ignore: b"\t\n\x0C\r " leading_ignore: b"\t\n\x0C\r ",
} },
} }
} }
@ -914,8 +950,8 @@ impl ByteMatcher {
pattern: b"<STYLE", pattern: b"<STYLE",
mask: b"\xFF\xDF\xDF\xDF\xDF\xDF", mask: b"\xFF\xDF\xDF\xDF\xDF\xDF",
content_type: mime::TEXT_HTML, content_type: mime::TEXT_HTML,
leading_ignore: b"\t\n\x0C\r " leading_ignore: b"\t\n\x0C\r ",
} },
} }
} }
@ -926,8 +962,8 @@ impl ByteMatcher {
pattern: b"<TITLE", pattern: b"<TITLE",
mask: b"\xFF\xDF\xDF\xDF\xDF\xDF", mask: b"\xFF\xDF\xDF\xDF\xDF\xDF",
content_type: mime::TEXT_HTML, content_type: mime::TEXT_HTML,
leading_ignore: b"\t\n\x0C\r " leading_ignore: b"\t\n\x0C\r ",
} },
} }
} }
@ -938,8 +974,8 @@ impl ByteMatcher {
pattern: b"<B", pattern: b"<B",
mask: b"\xFF\xDF", mask: b"\xFF\xDF",
content_type: mime::TEXT_HTML, content_type: mime::TEXT_HTML,
leading_ignore: b"\t\n\x0C\r " leading_ignore: b"\t\n\x0C\r ",
} },
} }
} }
@ -950,8 +986,8 @@ impl ByteMatcher {
pattern: b"<BODY", pattern: b"<BODY",
mask: b"\xFF\xDF\xDF\xDF\xDF", mask: b"\xFF\xDF\xDF\xDF\xDF",
content_type: mime::TEXT_HTML, content_type: mime::TEXT_HTML,
leading_ignore: b"\t\n\x0C\r " leading_ignore: b"\t\n\x0C\r ",
} },
} }
} }
@ -962,8 +998,8 @@ impl ByteMatcher {
pattern: b"<BR", pattern: b"<BR",
mask: b"\xFF\xDF\xDF", mask: b"\xFF\xDF\xDF",
content_type: mime::TEXT_HTML, content_type: mime::TEXT_HTML,
leading_ignore: b"\t\n\x0C\r " leading_ignore: b"\t\n\x0C\r ",
} },
} }
} }
@ -974,8 +1010,8 @@ impl ByteMatcher {
pattern: b"<P", pattern: b"<P",
mask: b"\xFF\xDF", mask: b"\xFF\xDF",
content_type: mime::TEXT_HTML, content_type: mime::TEXT_HTML,
leading_ignore: b"\t\n\x0C\r " leading_ignore: b"\t\n\x0C\r ",
} },
} }
} }
@ -986,8 +1022,8 @@ impl ByteMatcher {
pattern: b"<!--", pattern: b"<!--",
mask: b"\xFF\xFF\xFF\xFF", mask: b"\xFF\xFF\xFF\xFF",
content_type: mime::TEXT_HTML, content_type: mime::TEXT_HTML,
leading_ignore: b"\t\n\x0C\r " leading_ignore: b"\t\n\x0C\r ",
} },
} }
} }
@ -997,7 +1033,7 @@ impl ByteMatcher {
pattern: b"<?xml", pattern: b"<?xml",
mask: b"\xFF\xFF\xFF\xFF\xFF", mask: b"\xFF\xFF\xFF\xFF\xFF",
content_type: mime::TEXT_XML, content_type: mime::TEXT_XML,
leading_ignore: b"\t\n\x0C\r " leading_ignore: b"\t\n\x0C\r ",
} }
} }
//The string "%PDF-", the PDF signature. //The string "%PDF-", the PDF signature.
@ -1006,7 +1042,7 @@ impl ByteMatcher {
pattern: b"%PDF-", pattern: b"%PDF-",
mask: b"\xFF\xFF\xFF\xFF\xFF", mask: b"\xFF\xFF\xFF\xFF\xFF",
content_type: mime::APPLICATION_PDF, content_type: mime::APPLICATION_PDF,
leading_ignore: &[] leading_ignore: &[],
} }
} }
//34 bytes followed by the string "LP", the Embedded OpenType signature. //34 bytes followed by the string "LP", the Embedded OpenType signature.
@ -1019,7 +1055,7 @@ impl ByteMatcher {
\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", \x00\x00\xFF\xFF",
content_type: "application/vnd.ms-fontobject".parse().unwrap(), content_type: "application/vnd.ms-fontobject".parse().unwrap(),
leading_ignore: &[] leading_ignore: &[],
} }
} }
//4 bytes representing the version number 1.0, a TrueType signature. //4 bytes representing the version number 1.0, a TrueType signature.
@ -1028,7 +1064,7 @@ impl ByteMatcher {
pattern: b"\x00\x01\x00\x00", pattern: b"\x00\x01\x00\x00",
mask: b"\xFF\xFF\xFF\xFF", mask: b"\xFF\xFF\xFF\xFF",
content_type: "application/font-sfnt".parse().unwrap(), content_type: "application/font-sfnt".parse().unwrap(),
leading_ignore: &[] leading_ignore: &[],
} }
} }
//The string "OTTO", the OpenType signature. //The string "OTTO", the OpenType signature.
@ -1037,7 +1073,7 @@ impl ByteMatcher {
pattern: b"OTTO", pattern: b"OTTO",
mask: b"\xFF\xFF\xFF\xFF", mask: b"\xFF\xFF\xFF\xFF",
content_type: "application/font-sfnt".parse().unwrap(), content_type: "application/font-sfnt".parse().unwrap(),
leading_ignore: &[] leading_ignore: &[],
} }
} }
// The string "ttcf", the TrueType Collection signature. // The string "ttcf", the TrueType Collection signature.
@ -1046,7 +1082,7 @@ impl ByteMatcher {
pattern: b"ttcf", pattern: b"ttcf",
mask: b"\xFF\xFF\xFF\xFF", mask: b"\xFF\xFF\xFF\xFF",
content_type: "application/font-sfnt".parse().unwrap(), content_type: "application/font-sfnt".parse().unwrap(),
leading_ignore: &[] leading_ignore: &[],
} }
} }
// The string "wOFF", the Web Open Font Format signature. // The string "wOFF", the Web Open Font Format signature.
@ -1055,7 +1091,7 @@ impl ByteMatcher {
pattern: b"wOFF", pattern: b"wOFF",
mask: b"\xFF\xFF\xFF\xFF", mask: b"\xFF\xFF\xFF\xFF",
content_type: "application/font-woff".parse().unwrap(), content_type: "application/font-woff".parse().unwrap(),
leading_ignore: &[] leading_ignore: &[],
} }
} }
//The GZIP archive signature. //The GZIP archive signature.
@ -1064,7 +1100,7 @@ impl ByteMatcher {
pattern: b"\x1F\x8B\x08", pattern: b"\x1F\x8B\x08",
mask: b"\xFF\xFF\xFF", mask: b"\xFF\xFF\xFF",
content_type: "application/x-gzip".parse().unwrap(), content_type: "application/x-gzip".parse().unwrap(),
leading_ignore: &[] leading_ignore: &[],
} }
} }
//The string "PK" followed by ETX EOT, the ZIP archive signature. //The string "PK" followed by ETX EOT, the ZIP archive signature.
@ -1073,7 +1109,7 @@ impl ByteMatcher {
pattern: b"PK\x03\x04", pattern: b"PK\x03\x04",
mask: b"\xFF\xFF\xFF\xFF", mask: b"\xFF\xFF\xFF\xFF",
content_type: "application/zip".parse().unwrap(), content_type: "application/zip".parse().unwrap(),
leading_ignore: &[] leading_ignore: &[],
} }
} }
//The string "Rar " followed by SUB BEL NUL, the RAR archive signature. //The string "Rar " followed by SUB BEL NUL, the RAR archive signature.
@ -1082,16 +1118,16 @@ impl ByteMatcher {
pattern: b"Rar \x1A\x07\x00", pattern: b"Rar \x1A\x07\x00",
mask: b"\xFF\xFF\xFF\xFF\xFF\xFF\xFF", mask: b"\xFF\xFF\xFF\xFF\xFF\xFF\xFF",
content_type: "application/x-rar-compressed".parse().unwrap(), content_type: "application/x-rar-compressed".parse().unwrap(),
leading_ignore: &[] leading_ignore: &[],
} }
} }
// The string "%!PS-Adobe-", the PostScript signature. // The string "%!PS-Adobe-", the PostScript signature.
fn application_postscript() -> ByteMatcher { fn application_postscript() -> ByteMatcher {
ByteMatcher { ByteMatcher {
pattern: b"%!PS-Adobe-", pattern: b"%!PS-Adobe-",
mask: b"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", mask: b"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF",
content_type: "application/postscript".parse().unwrap(), content_type: "application/postscript".parse().unwrap(),
leading_ignore: &[] leading_ignore: &[],
} }
} }
// UTF-16BE BOM // UTF-16BE BOM
@ -1100,7 +1136,7 @@ impl ByteMatcher {
pattern: b"\xFE\xFF\x00\x00", pattern: b"\xFE\xFF\x00\x00",
mask: b"\xFF\xFF\x00\x00", mask: b"\xFF\xFF\x00\x00",
content_type: mime::TEXT_PLAIN, content_type: mime::TEXT_PLAIN,
leading_ignore: &[] leading_ignore: &[],
} }
} }
//UTF-16LE BOM //UTF-16LE BOM
@ -1109,7 +1145,7 @@ impl ByteMatcher {
pattern: b"\xFF\xFE\x00\x00", pattern: b"\xFF\xFE\x00\x00",
mask: b"\xFF\xFF\x00\x00", mask: b"\xFF\xFF\x00\x00",
content_type: mime::TEXT_PLAIN, content_type: mime::TEXT_PLAIN,
leading_ignore: &[] leading_ignore: &[],
} }
} }
//UTF-8 BOM //UTF-8 BOM
@ -1118,7 +1154,7 @@ impl ByteMatcher {
pattern: b"\xEF\xBB\xBF\x00", pattern: b"\xEF\xBB\xBF\x00",
mask: b"\xFF\xFF\xFF\x00", mask: b"\xFF\xFF\xFF\x00",
content_type: mime::TEXT_PLAIN, content_type: mime::TEXT_PLAIN,
leading_ignore: &[] leading_ignore: &[],
} }
} }
} }

View file

@ -48,59 +48,65 @@ use storage_thread::StorageThreadFactory;
use websocket_loader; use websocket_loader;
/// Returns a tuple of (public, private) senders to the new threads. /// Returns a tuple of (public, private) senders to the new threads.
pub fn new_resource_threads(user_agent: Cow<'static, str>, pub fn new_resource_threads(
devtools_chan: Option<Sender<DevtoolsControlMsg>>, user_agent: Cow<'static, str>,
time_profiler_chan: ProfilerChan, devtools_chan: Option<Sender<DevtoolsControlMsg>>,
mem_profiler_chan: MemProfilerChan, time_profiler_chan: ProfilerChan,
embedder_proxy: EmbedderProxy, mem_profiler_chan: MemProfilerChan,
config_dir: Option<PathBuf>) embedder_proxy: EmbedderProxy,
-> (ResourceThreads, ResourceThreads) { config_dir: Option<PathBuf>,
) -> (ResourceThreads, ResourceThreads) {
let (public_core, private_core) = new_core_resource_thread( let (public_core, private_core) = new_core_resource_thread(
user_agent, user_agent,
devtools_chan, devtools_chan,
time_profiler_chan, time_profiler_chan,
mem_profiler_chan, mem_profiler_chan,
embedder_proxy, embedder_proxy,
config_dir.clone()); config_dir.clone(),
);
let storage: IpcSender<StorageThreadMsg> = StorageThreadFactory::new(config_dir); let storage: IpcSender<StorageThreadMsg> = StorageThreadFactory::new(config_dir);
(ResourceThreads::new(public_core, storage.clone()), (
ResourceThreads::new(private_core, storage)) ResourceThreads::new(public_core, storage.clone()),
ResourceThreads::new(private_core, storage),
)
} }
/// Create a CoreResourceThread /// Create a CoreResourceThread
pub fn new_core_resource_thread(user_agent: Cow<'static, str>, pub fn new_core_resource_thread(
devtools_chan: Option<Sender<DevtoolsControlMsg>>, user_agent: Cow<'static, str>,
time_profiler_chan: ProfilerChan, devtools_chan: Option<Sender<DevtoolsControlMsg>>,
mem_profiler_chan: MemProfilerChan, time_profiler_chan: ProfilerChan,
embedder_proxy: EmbedderProxy, mem_profiler_chan: MemProfilerChan,
config_dir: Option<PathBuf>) embedder_proxy: EmbedderProxy,
-> (CoreResourceThread, CoreResourceThread) { config_dir: Option<PathBuf>,
) -> (CoreResourceThread, CoreResourceThread) {
let (public_setup_chan, public_setup_port) = ipc::channel().unwrap(); let (public_setup_chan, public_setup_port) = ipc::channel().unwrap();
let (private_setup_chan, private_setup_port) = ipc::channel().unwrap(); let (private_setup_chan, private_setup_port) = ipc::channel().unwrap();
let (report_chan, report_port) = ipc::channel().unwrap(); let (report_chan, report_port) = ipc::channel().unwrap();
thread::Builder::new().name("ResourceManager".to_owned()).spawn(move || { thread::Builder::new()
let resource_manager = CoreResourceManager::new( .name("ResourceManager".to_owned())
user_agent, devtools_chan, time_profiler_chan, embedder_proxy .spawn(move || {
); let resource_manager = CoreResourceManager::new(
user_agent,
devtools_chan,
time_profiler_chan,
embedder_proxy,
);
let mut channel_manager = ResourceChannelManager { let mut channel_manager = ResourceChannelManager {
resource_manager: resource_manager, resource_manager: resource_manager,
config_dir: config_dir, config_dir: config_dir,
}; };
mem_profiler_chan.run_with_memory_reporting(|| ( mem_profiler_chan.run_with_memory_reporting(
channel_manager.start( || (channel_manager.start(public_setup_port, private_setup_port, report_port)),
public_setup_port, String::from("network-cache-reporter"),
private_setup_port, report_chan,
report_port) |report_chan| report_chan,
), );
String::from("network-cache-reporter"), })
report_chan, .expect("Thread spawning failed");
|report_chan| report_chan);
}).expect("Thread spawning failed");
(public_setup_chan, private_setup_chan) (public_setup_chan, private_setup_chan)
} }
@ -121,12 +127,8 @@ fn create_http_states(config_dir: Option<&Path>) -> (Arc<HttpState>, Arc<HttpSta
} }
let certs = match opts::get().certificate_path { let certs = match opts::get().certificate_path {
Some(ref path) => { Some(ref path) => fs::read_to_string(path).expect("Couldn't not find certificate file"),
fs::read_to_string(path).expect("Couldn't not find certificate file") None => resources::read_string(Resource::SSLCertificates),
}
None => {
resources::read_string(Resource::SSLCertificates)
},
}; };
let ssl_connector_builder = create_ssl_connector_builder(&certs); let ssl_connector_builder = create_ssl_connector_builder(&certs);
@ -147,10 +149,12 @@ fn create_http_states(config_dir: Option<&Path>) -> (Arc<HttpState>, Arc<HttpSta
impl ResourceChannelManager { impl ResourceChannelManager {
#[allow(unsafe_code)] #[allow(unsafe_code)]
fn start(&mut self, fn start(
public_receiver: IpcReceiver<CoreResourceMsg>, &mut self,
private_receiver: IpcReceiver<CoreResourceMsg>, public_receiver: IpcReceiver<CoreResourceMsg>,
memory_reporter: IpcReceiver<ReportsChan>) { private_receiver: IpcReceiver<CoreResourceMsg>,
memory_reporter: IpcReceiver<ReportsChan>,
) {
let (public_http_state, private_http_state) = let (public_http_state, private_http_state) =
create_http_states(self.config_dir.as_ref().map(Deref::deref)); create_http_states(self.config_dir.as_ref().map(Deref::deref));
@ -164,7 +168,7 @@ impl ResourceChannelManager {
// Handles case where profiler thread shuts down before resource thread. // Handles case where profiler thread shuts down before resource thread.
match receiver { match receiver {
ipc::IpcSelectionResult::ChannelClosed(..) => continue, ipc::IpcSelectionResult::ChannelClosed(..) => continue,
_ => {} _ => {},
} }
let (id, data) = receiver.unwrap(); let (id, data) = receiver.unwrap();
// If message is memory report, get the size_of of public and private http caches // If message is memory report, get the size_of of public and private http caches
@ -190,10 +194,12 @@ impl ResourceChannelManager {
} }
} }
fn process_report(&mut self, fn process_report(
msg: ReportsChan, &mut self,
public_http_state: &Arc<HttpState>, msg: ReportsChan,
private_http_state: &Arc<HttpState>) { public_http_state: &Arc<HttpState>,
private_http_state: &Arc<HttpState>,
) {
let mut ops = MallocSizeOfOps::new(servo_allocator::usable_size, None, None); let mut ops = MallocSizeOfOps::new(servo_allocator::usable_size, None, None);
let public_cache = public_http_state.http_cache.read().unwrap(); let public_cache = public_http_state.http_cache.read().unwrap();
let private_cache = private_http_state.http_cache.read().unwrap(); let private_cache = private_http_state.http_cache.read().unwrap();
@ -201,74 +207,95 @@ impl ResourceChannelManager {
let public_report = Report { let public_report = Report {
path: path!["memory-cache", "public"], path: path!["memory-cache", "public"],
kind: ReportKind::ExplicitJemallocHeapSize, kind: ReportKind::ExplicitJemallocHeapSize,
size: public_cache.size_of(&mut ops) size: public_cache.size_of(&mut ops),
}; };
let private_report = Report { let private_report = Report {
path: path!["memory-cache", "private"], path: path!["memory-cache", "private"],
kind: ReportKind::ExplicitJemallocHeapSize, kind: ReportKind::ExplicitJemallocHeapSize,
size: private_cache.size_of(&mut ops) size: private_cache.size_of(&mut ops),
}; };
msg.send(vec!(public_report, private_report)); msg.send(vec![public_report, private_report]);
} }
/// Returns false if the thread should exit. /// Returns false if the thread should exit.
fn process_msg(&mut self, fn process_msg(&mut self, msg: CoreResourceMsg, http_state: &Arc<HttpState>) -> bool {
msg: CoreResourceMsg,
http_state: &Arc<HttpState>) -> bool {
match msg { match msg {
CoreResourceMsg::Fetch(req_init, channels) => { CoreResourceMsg::Fetch(req_init, channels) => match channels {
match channels { FetchChannels::ResponseMsg(sender, cancel_chan) => {
FetchChannels::ResponseMsg(sender, cancel_chan) => self.resource_manager
self.resource_manager.fetch(req_init, None, sender, http_state, cancel_chan), .fetch(req_init, None, sender, http_state, cancel_chan)
FetchChannels::WebSocket { event_sender, action_receiver } => },
self.resource_manager.websocket_connect(req_init, event_sender, action_receiver, http_state), FetchChannels::WebSocket {
} event_sender,
} action_receiver,
CoreResourceMsg::FetchRedirect(req_init, res_init, sender, cancel_chan) => } => self.resource_manager.websocket_connect(
self.resource_manager.fetch(req_init, Some(res_init), sender, http_state, cancel_chan), req_init,
CoreResourceMsg::SetCookieForUrl(request, cookie, source) => event_sender,
self.resource_manager.set_cookie_for_url(&request, cookie.into_inner(), source, http_state), action_receiver,
http_state,
),
},
CoreResourceMsg::FetchRedirect(req_init, res_init, sender, cancel_chan) => self
.resource_manager
.fetch(req_init, Some(res_init), sender, http_state, cancel_chan),
CoreResourceMsg::SetCookieForUrl(request, cookie, source) => self
.resource_manager
.set_cookie_for_url(&request, cookie.into_inner(), source, http_state),
CoreResourceMsg::SetCookiesForUrl(request, cookies, source) => { CoreResourceMsg::SetCookiesForUrl(request, cookies, source) => {
for cookie in cookies { for cookie in cookies {
self.resource_manager.set_cookie_for_url(&request, cookie.into_inner(), source, http_state); self.resource_manager.set_cookie_for_url(
&request,
cookie.into_inner(),
source,
http_state,
);
} }
} },
CoreResourceMsg::GetCookiesForUrl(url, consumer, source) => { CoreResourceMsg::GetCookiesForUrl(url, consumer, source) => {
let mut cookie_jar = http_state.cookie_jar.write().unwrap(); let mut cookie_jar = http_state.cookie_jar.write().unwrap();
consumer.send(cookie_jar.cookies_for_url(&url, source)).unwrap(); consumer
} .send(cookie_jar.cookies_for_url(&url, source))
.unwrap();
},
CoreResourceMsg::NetworkMediator(mediator_chan) => { CoreResourceMsg::NetworkMediator(mediator_chan) => {
self.resource_manager.swmanager_chan = Some(mediator_chan) self.resource_manager.swmanager_chan = Some(mediator_chan)
} },
CoreResourceMsg::GetCookiesDataForUrl(url, consumer, source) => { CoreResourceMsg::GetCookiesDataForUrl(url, consumer, source) => {
let mut cookie_jar = http_state.cookie_jar.write().unwrap(); let mut cookie_jar = http_state.cookie_jar.write().unwrap();
let cookies = cookie_jar.cookies_data_for_url(&url, source).map(Serde).collect(); let cookies = cookie_jar
.cookies_data_for_url(&url, source)
.map(Serde)
.collect();
consumer.send(cookies).unwrap(); consumer.send(cookies).unwrap();
} },
CoreResourceMsg::GetHistoryState(history_state_id, consumer) => { CoreResourceMsg::GetHistoryState(history_state_id, consumer) => {
let history_states = http_state.history_states.read().unwrap(); let history_states = http_state.history_states.read().unwrap();
consumer.send(history_states.get(&history_state_id).cloned()).unwrap(); consumer
} .send(history_states.get(&history_state_id).cloned())
.unwrap();
},
CoreResourceMsg::SetHistoryState(history_state_id, history_state) => { CoreResourceMsg::SetHistoryState(history_state_id, history_state) => {
let mut history_states = http_state.history_states.write().unwrap(); let mut history_states = http_state.history_states.write().unwrap();
history_states.insert(history_state_id, history_state); history_states.insert(history_state_id, history_state);
} },
CoreResourceMsg::RemoveHistoryStates(states_to_remove) => { CoreResourceMsg::RemoveHistoryStates(states_to_remove) => {
let mut history_states = http_state.history_states.write().unwrap(); let mut history_states = http_state.history_states.write().unwrap();
for history_state in states_to_remove { for history_state in states_to_remove {
history_states.remove(&history_state); history_states.remove(&history_state);
} }
} },
CoreResourceMsg::Synchronize(sender) => { CoreResourceMsg::Synchronize(sender) => {
let _ = sender.send(()); let _ = sender.send(());
} },
CoreResourceMsg::ToFileManager(msg) => self.resource_manager.filemanager.handle(msg), CoreResourceMsg::ToFileManager(msg) => self.resource_manager.filemanager.handle(msg),
CoreResourceMsg::Exit(sender) => { CoreResourceMsg::Exit(sender) => {
if let Some(ref config_dir) = self.config_dir { if let Some(ref config_dir) = self.config_dir {
match http_state.auth_cache.read() { match http_state.auth_cache.read() {
Ok(auth_cache) => write_json_to_file(&*auth_cache, config_dir, "auth_cache.json"), Ok(auth_cache) => {
write_json_to_file(&*auth_cache, config_dir, "auth_cache.json")
},
Err(_) => warn!("Error writing auth cache to disk"), Err(_) => warn!("Error writing auth cache to disk"),
} }
match http_state.cookie_jar.read() { match http_state.cookie_jar.read() {
@ -282,14 +309,15 @@ impl ResourceChannelManager {
} }
let _ = sender.send(()); let _ = sender.send(());
return false; return false;
} },
} }
true true
} }
} }
pub fn read_json_from_file<T>(data: &mut T, config_dir: &Path, filename: &str) pub fn read_json_from_file<T>(data: &mut T, config_dir: &Path, filename: &str)
where T: for<'de> Deserialize<'de> where
T: for<'de> Deserialize<'de>,
{ {
let path = config_dir.join(filename); let path = config_dir.join(filename);
let display = path.display(); let display = path.display();
@ -304,10 +332,11 @@ pub fn read_json_from_file<T>(data: &mut T, config_dir: &Path, filename: &str)
let mut string_buffer: String = String::new(); let mut string_buffer: String = String::new();
match file.read_to_string(&mut string_buffer) { match file.read_to_string(&mut string_buffer) {
Err(why) => { Err(why) => panic!(
panic!("couldn't read from {}: {}", display, "couldn't read from {}: {}",
Error::description(&why)) display,
}, Error::description(&why)
),
Ok(_) => println!("successfully read from {}", display), Ok(_) => println!("successfully read from {}", display),
} }
@ -318,7 +347,8 @@ pub fn read_json_from_file<T>(data: &mut T, config_dir: &Path, filename: &str)
} }
pub fn write_json_to_file<T>(data: &T, config_dir: &Path, filename: &str) pub fn write_json_to_file<T>(data: &T, config_dir: &Path, filename: &str)
where T: Serialize where
T: Serialize,
{ {
let json_encoded: String; let json_encoded: String;
match serde_json::to_string_pretty(&data) { match serde_json::to_string_pretty(&data) {
@ -329,17 +359,16 @@ pub fn write_json_to_file<T>(data: &T, config_dir: &Path, filename: &str)
let display = path.display(); let display = path.display();
let mut file = match File::create(&path) { let mut file = match File::create(&path) {
Err(why) => panic!("couldn't create {}: {}", Err(why) => panic!("couldn't create {}: {}", display, Error::description(&why)),
display,
Error::description(&why)),
Ok(file) => file, Ok(file) => file,
}; };
match file.write_all(json_encoded.as_bytes()) { match file.write_all(json_encoded.as_bytes()) {
Err(why) => { Err(why) => panic!(
panic!("couldn't write to {}: {}", display, "couldn't write to {}: {}",
Error::description(&why)) display,
}, Error::description(&why)
),
Ok(_) => println!("successfully wrote to {}", display), Ok(_) => println!("successfully wrote to {}", display),
} }
} }
@ -354,7 +383,7 @@ impl AuthCache {
pub fn new() -> AuthCache { pub fn new() -> AuthCache {
AuthCache { AuthCache {
version: 1, version: 1,
entries: HashMap::new() entries: HashMap::new(),
} }
} }
} }
@ -373,10 +402,12 @@ pub struct CoreResourceManager {
} }
impl CoreResourceManager { impl CoreResourceManager {
pub fn new(user_agent: Cow<'static, str>, pub fn new(
devtools_channel: Option<Sender<DevtoolsControlMsg>>, user_agent: Cow<'static, str>,
_profiler_chan: ProfilerChan, devtools_channel: Option<Sender<DevtoolsControlMsg>>,
embedder_proxy: EmbedderProxy) -> CoreResourceManager { _profiler_chan: ProfilerChan,
embedder_proxy: EmbedderProxy,
) -> CoreResourceManager {
CoreResourceManager { CoreResourceManager {
user_agent: user_agent, user_agent: user_agent,
devtools_chan: devtools_channel, devtools_chan: devtools_channel,
@ -385,55 +416,67 @@ impl CoreResourceManager {
} }
} }
fn set_cookie_for_url(&mut self, request: &ServoUrl, fn set_cookie_for_url(
cookie: cookie_rs::Cookie<'static>, &mut self,
source: CookieSource, request: &ServoUrl,
http_state: &Arc<HttpState>) { cookie: cookie_rs::Cookie<'static>,
source: CookieSource,
http_state: &Arc<HttpState>,
) {
if let Some(cookie) = cookie::Cookie::new_wrapped(cookie, request, source) { if let Some(cookie) = cookie::Cookie::new_wrapped(cookie, request, source) {
let mut cookie_jar = http_state.cookie_jar.write().unwrap(); let mut cookie_jar = http_state.cookie_jar.write().unwrap();
cookie_jar.push(cookie, request, source) cookie_jar.push(cookie, request, source)
} }
} }
fn fetch(&self, fn fetch(
req_init: RequestInit, &self,
res_init_: Option<ResponseInit>, req_init: RequestInit,
mut sender: IpcSender<FetchResponseMsg>, res_init_: Option<ResponseInit>,
http_state: &Arc<HttpState>, mut sender: IpcSender<FetchResponseMsg>,
cancel_chan: Option<IpcReceiver<()>>) { http_state: &Arc<HttpState>,
cancel_chan: Option<IpcReceiver<()>>,
) {
let http_state = http_state.clone(); let http_state = http_state.clone();
let ua = self.user_agent.clone(); let ua = self.user_agent.clone();
let dc = self.devtools_chan.clone(); let dc = self.devtools_chan.clone();
let filemanager = self.filemanager.clone(); let filemanager = self.filemanager.clone();
thread::Builder::new().name(format!("fetch thread for {}", req_init.url)).spawn(move || { thread::Builder::new()
let mut request = Request::from_init(req_init); .name(format!("fetch thread for {}", req_init.url))
// XXXManishearth: Check origin against pipeline id (also ensure that the mode is allowed) .spawn(move || {
// todo load context / mimesniff in fetch let mut request = Request::from_init(req_init);
// todo referrer policy? // XXXManishearth: Check origin against pipeline id (also ensure that the mode is allowed)
// todo service worker stuff // todo load context / mimesniff in fetch
let context = FetchContext { // todo referrer policy?
state: http_state, // todo service worker stuff
user_agent: ua, let context = FetchContext {
devtools_chan: dc, state: http_state,
filemanager: filemanager, user_agent: ua,
cancellation_listener: Arc::new(Mutex::new(CancellationListener::new(cancel_chan))), devtools_chan: dc,
}; filemanager: filemanager,
cancellation_listener: Arc::new(Mutex::new(CancellationListener::new(
cancel_chan,
))),
};
match res_init_ { match res_init_ {
Some(res_init) => { Some(res_init) => {
let response = Response::from_init(res_init); let response = Response::from_init(res_init);
http_redirect_fetch(&mut request, http_redirect_fetch(
&mut CorsCache::new(), &mut request,
response, &mut CorsCache::new(),
true, response,
&mut sender, true,
&mut None, &mut sender,
&context); &mut None,
}, &context,
None => fetch(&mut request, &mut sender, &context), );
}; },
}).expect("Thread spawning failed"); None => fetch(&mut request, &mut sender, &context),
};
})
.expect("Thread spawning failed");
} }
fn websocket_connect( fn websocket_connect(
@ -441,7 +484,7 @@ impl CoreResourceManager {
request: RequestInit, request: RequestInit,
event_sender: IpcSender<WebSocketNetworkEvent>, event_sender: IpcSender<WebSocketNetworkEvent>,
action_receiver: IpcReceiver<WebSocketDomAction>, action_receiver: IpcReceiver<WebSocketDomAction>,
http_state: &Arc<HttpState> http_state: &Arc<HttpState>,
) { ) {
websocket_loader::init(request, event_sender, action_receiver, http_state.clone()); websocket_loader::init(request, event_sender, action_receiver, http_state.clone());
} }

View file

@ -22,9 +22,12 @@ impl StorageThreadFactory for IpcSender<StorageThreadMsg> {
/// Create a storage thread /// Create a storage thread
fn new(config_dir: Option<PathBuf>) -> IpcSender<StorageThreadMsg> { fn new(config_dir: Option<PathBuf>) -> IpcSender<StorageThreadMsg> {
let (chan, port) = ipc::channel().unwrap(); let (chan, port) = ipc::channel().unwrap();
thread::Builder::new().name("StorageManager".to_owned()).spawn(move || { thread::Builder::new()
StorageManager::new(port, config_dir).start(); .name("StorageManager".to_owned())
}).expect("Thread spawning failed"); .spawn(move || {
StorageManager::new(port, config_dir).start();
})
.expect("Thread spawning failed");
chan chan
} }
} }
@ -37,9 +40,7 @@ struct StorageManager {
} }
impl StorageManager { impl StorageManager {
fn new(port: IpcReceiver<StorageThreadMsg>, fn new(port: IpcReceiver<StorageThreadMsg>, config_dir: Option<PathBuf>) -> StorageManager {
config_dir: Option<PathBuf>)
-> StorageManager {
let mut local_data = HashMap::new(); let mut local_data = HashMap::new();
if let Some(ref config_dir) = config_dir { if let Some(ref config_dir) = config_dir {
resource_thread::read_json_from_file(&mut local_data, config_dir, "local_data.json"); resource_thread::read_json_from_file(&mut local_data, config_dir, "local_data.json");
@ -59,33 +60,33 @@ impl StorageManager {
match self.port.recv().unwrap() { match self.port.recv().unwrap() {
StorageThreadMsg::Length(sender, url, storage_type) => { StorageThreadMsg::Length(sender, url, storage_type) => {
self.length(sender, url, storage_type) self.length(sender, url, storage_type)
} },
StorageThreadMsg::Key(sender, url, storage_type, index) => { StorageThreadMsg::Key(sender, url, storage_type, index) => {
self.key(sender, url, storage_type, index) self.key(sender, url, storage_type, index)
} },
StorageThreadMsg::Keys(sender, url, storage_type) => { StorageThreadMsg::Keys(sender, url, storage_type) => {
self.keys(sender, url, storage_type) self.keys(sender, url, storage_type)
} },
StorageThreadMsg::SetItem(sender, url, storage_type, name, value) => { StorageThreadMsg::SetItem(sender, url, storage_type, name, value) => {
self.set_item(sender, url, storage_type, name, value); self.set_item(sender, url, storage_type, name, value);
self.save_state() self.save_state()
} },
StorageThreadMsg::GetItem(sender, url, storage_type, name) => { StorageThreadMsg::GetItem(sender, url, storage_type, name) => {
self.request_item(sender, url, storage_type, name) self.request_item(sender, url, storage_type, name)
} },
StorageThreadMsg::RemoveItem(sender, url, storage_type, name) => { StorageThreadMsg::RemoveItem(sender, url, storage_type, name) => {
self.remove_item(sender, url, storage_type, name); self.remove_item(sender, url, storage_type, name);
self.save_state() self.save_state()
} },
StorageThreadMsg::Clear(sender, url, storage_type) => { StorageThreadMsg::Clear(sender, url, storage_type) => {
self.clear(sender, url, storage_type); self.clear(sender, url, storage_type);
self.save_state() self.save_state()
} },
StorageThreadMsg::Exit(sender) => { StorageThreadMsg::Exit(sender) => {
// Nothing to do since we save localstorage set eagerly. // Nothing to do since we save localstorage set eagerly.
let _ = sender.send(()); let _ = sender.send(());
break break;
} },
} }
} }
} }
@ -96,49 +97,56 @@ impl StorageManager {
} }
} }
fn select_data(&self, storage_type: StorageType) fn select_data(
-> &HashMap<String, (usize, BTreeMap<String, String>)> { &self,
storage_type: StorageType,
) -> &HashMap<String, (usize, BTreeMap<String, String>)> {
match storage_type { match storage_type {
StorageType::Session => &self.session_data, StorageType::Session => &self.session_data,
StorageType::Local => &self.local_data StorageType::Local => &self.local_data,
} }
} }
fn select_data_mut(&mut self, storage_type: StorageType) fn select_data_mut(
-> &mut HashMap<String, (usize, BTreeMap<String, String>)> { &mut self,
storage_type: StorageType,
) -> &mut HashMap<String, (usize, BTreeMap<String, String>)> {
match storage_type { match storage_type {
StorageType::Session => &mut self.session_data, StorageType::Session => &mut self.session_data,
StorageType::Local => &mut self.local_data StorageType::Local => &mut self.local_data,
} }
} }
fn length(&self, sender: IpcSender<usize>, url: ServoUrl, storage_type: StorageType) { fn length(&self, sender: IpcSender<usize>, url: ServoUrl, storage_type: StorageType) {
let origin = self.origin_as_string(url); let origin = self.origin_as_string(url);
let data = self.select_data(storage_type); let data = self.select_data(storage_type);
sender.send(data.get(&origin).map_or(0, |&(_, ref entry)| entry.len())).unwrap(); sender
.send(data.get(&origin).map_or(0, |&(_, ref entry)| entry.len()))
.unwrap();
} }
fn key(&self, fn key(
sender: IpcSender<Option<String>>, &self,
url: ServoUrl, sender: IpcSender<Option<String>>,
storage_type: StorageType, url: ServoUrl,
index: u32) { storage_type: StorageType,
index: u32,
) {
let origin = self.origin_as_string(url); let origin = self.origin_as_string(url);
let data = self.select_data(storage_type); let data = self.select_data(storage_type);
let key = data.get(&origin) let key = data
.and_then(|&(_, ref entry)| entry.keys().nth(index as usize)) .get(&origin)
.cloned(); .and_then(|&(_, ref entry)| entry.keys().nth(index as usize))
.cloned();
sender.send(key).unwrap(); sender.send(key).unwrap();
} }
fn keys(&self, fn keys(&self, sender: IpcSender<Vec<String>>, url: ServoUrl, storage_type: StorageType) {
sender: IpcSender<Vec<String>>,
url: ServoUrl,
storage_type: StorageType) {
let origin = self.origin_as_string(url); let origin = self.origin_as_string(url);
let data = self.select_data(storage_type); let data = self.select_data(storage_type);
let keys = data.get(&origin) let keys = data
.map_or(vec![], |&(_, ref entry)| entry.keys().cloned().collect()); .get(&origin)
.map_or(vec![], |&(_, ref entry)| entry.keys().cloned().collect());
sender.send(keys).unwrap(); sender.send(keys).unwrap();
} }
@ -147,12 +155,14 @@ impl StorageManager {
/// value with the same key name but with different value name /// value with the same key name but with different value name
/// otherwise sends Err(()) to indicate that the operation would result in /// otherwise sends Err(()) to indicate that the operation would result in
/// exceeding the quota limit /// exceeding the quota limit
fn set_item(&mut self, fn set_item(
sender: IpcSender<Result<(bool, Option<String>), ()>>, &mut self,
url: ServoUrl, sender: IpcSender<Result<(bool, Option<String>), ()>>,
storage_type: StorageType, url: ServoUrl,
name: String, storage_type: StorageType,
value: String) { name: String,
value: String,
) {
let origin = self.origin_as_string(url); let origin = self.origin_as_string(url);
let (this_storage_size, other_storage_size) = { let (this_storage_size, other_storage_size) = {
@ -171,64 +181,82 @@ impl StorageManager {
data.insert(origin.clone(), (0, BTreeMap::new())); data.insert(origin.clone(), (0, BTreeMap::new()));
} }
let message = data.get_mut(&origin).map(|&mut (ref mut total, ref mut entry)| { let message = data
let mut new_total_size = this_storage_size + value.as_bytes().len(); .get_mut(&origin)
if let Some(old_value) = entry.get(&name) { .map(|&mut (ref mut total, ref mut entry)| {
new_total_size -= old_value.as_bytes().len(); let mut new_total_size = this_storage_size + value.as_bytes().len();
} else { if let Some(old_value) = entry.get(&name) {
new_total_size += name.as_bytes().len(); new_total_size -= old_value.as_bytes().len();
}
if (new_total_size + other_storage_size) > QUOTA_SIZE_LIMIT {
return Err(());
}
let message = entry.insert(name.clone(), value.clone()).map_or(
Ok((true, None)),
|old| if old == value {
Ok((false, None))
} else { } else {
Ok((true, Some(old))) new_total_size += name.as_bytes().len();
}); }
*total = new_total_size;
message if (new_total_size + other_storage_size) > QUOTA_SIZE_LIMIT {
}).unwrap(); return Err(());
}
let message =
entry
.insert(name.clone(), value.clone())
.map_or(Ok((true, None)), |old| {
if old == value {
Ok((false, None))
} else {
Ok((true, Some(old)))
}
});
*total = new_total_size;
message
})
.unwrap();
sender.send(message).unwrap(); sender.send(message).unwrap();
} }
fn request_item(&self, fn request_item(
sender: IpcSender<Option<String>>, &self,
url: ServoUrl, sender: IpcSender<Option<String>>,
storage_type: StorageType, url: ServoUrl,
name: String) { storage_type: StorageType,
name: String,
) {
let origin = self.origin_as_string(url); let origin = self.origin_as_string(url);
let data = self.select_data(storage_type); let data = self.select_data(storage_type);
sender.send(data.get(&origin) sender
.send(
data.get(&origin)
.and_then(|&(_, ref entry)| entry.get(&name)) .and_then(|&(_, ref entry)| entry.get(&name))
.map(String::clone)).unwrap(); .map(String::clone),
)
.unwrap();
} }
/// Sends Some(old_value) in case there was a previous value with the key name, otherwise sends None /// Sends Some(old_value) in case there was a previous value with the key name, otherwise sends None
fn remove_item(&mut self, fn remove_item(
sender: IpcSender<Option<String>>, &mut self,
url: ServoUrl, sender: IpcSender<Option<String>>,
storage_type: StorageType, url: ServoUrl,
name: String) { storage_type: StorageType,
name: String,
) {
let origin = self.origin_as_string(url); let origin = self.origin_as_string(url);
let data = self.select_data_mut(storage_type); let data = self.select_data_mut(storage_type);
let old_value = data.get_mut(&origin).and_then(|&mut (ref mut total, ref mut entry)| { let old_value = data
entry.remove(&name).and_then(|old| { .get_mut(&origin)
*total -= name.as_bytes().len() + old.as_bytes().len(); .and_then(|&mut (ref mut total, ref mut entry)| {
Some(old) entry.remove(&name).and_then(|old| {
}) *total -= name.as_bytes().len() + old.as_bytes().len();
}); Some(old)
})
});
sender.send(old_value).unwrap(); sender.send(old_value).unwrap();
} }
fn clear(&mut self, sender: IpcSender<bool>, url: ServoUrl, storage_type: StorageType) { fn clear(&mut self, sender: IpcSender<bool>, url: ServoUrl, storage_type: StorageType) {
let origin = self.origin_as_string(url); let origin = self.origin_as_string(url);
let data = self.select_data_mut(storage_type); let data = self.select_data_mut(storage_type);
sender.send(data.get_mut(&origin) sender
.send(
data.get_mut(&origin)
.map_or(false, |&mut (ref mut total, ref mut entry)| { .map_or(false, |&mut (ref mut total, ref mut entry)| {
if !entry.is_empty() { if !entry.is_empty() {
entry.clear(); entry.clear();
@ -236,7 +264,10 @@ impl StorageManager {
true true
} else { } else {
false false
}})).unwrap(); }
}),
)
.unwrap();
} }
fn origin_as_string(&self, url: ServoUrl) -> String { fn origin_as_string(&self, url: ServoUrl) -> String {

View file

@ -9,22 +9,13 @@ use std::iter::Filter;
use std::str::Split; use std::str::Split;
use std::sync::MutexGuard; use std::sync::MutexGuard;
const SUPPORTED_ALGORITHM: &'static [&'static str] = &[ const SUPPORTED_ALGORITHM: &'static [&'static str] = &["sha256", "sha384", "sha512"];
"sha256",
"sha384",
"sha512",
];
pub type StaticCharVec = &'static [char]; pub type StaticCharVec = &'static [char];
/// A "space character" according to: /// A "space character" according to:
/// ///
/// <https://html.spec.whatwg.org/multipage/#space-character> /// <https://html.spec.whatwg.org/multipage/#space-character>
pub static HTML_SPACE_CHARACTERS: StaticCharVec = &[ pub static HTML_SPACE_CHARACTERS: StaticCharVec =
'\u{0020}', &['\u{0020}', '\u{0009}', '\u{000a}', '\u{000c}', '\u{000d}'];
'\u{0009}',
'\u{000a}',
'\u{000c}',
'\u{000d}',
];
#[derive(Clone)] #[derive(Clone)]
pub struct SriEntry { pub struct SriEntry {
pub alg: String, pub alg: String,
@ -79,9 +70,18 @@ pub fn parsed_metadata(integrity_metadata: &str) -> Vec<SriEntry> {
} }
/// <https://w3c.github.io/webappsec-subresource-integrity/#getprioritizedhashfunction> /// <https://w3c.github.io/webappsec-subresource-integrity/#getprioritizedhashfunction>
pub fn get_prioritized_hash_function(hash_func_left: &str, hash_func_right: &str) -> Option<String> { pub fn get_prioritized_hash_function(
let left_priority = SUPPORTED_ALGORITHM.iter().position(|s| s.to_owned() == hash_func_left).unwrap(); hash_func_left: &str,
let right_priority = SUPPORTED_ALGORITHM.iter().position(|s| s.to_owned() == hash_func_right).unwrap(); hash_func_right: &str,
) -> Option<String> {
let left_priority = SUPPORTED_ALGORITHM
.iter()
.position(|s| s.to_owned() == hash_func_left)
.unwrap();
let right_priority = SUPPORTED_ALGORITHM
.iter()
.position(|s| s.to_owned() == hash_func_right)
.unwrap();
if left_priority == right_priority { if left_priority == right_priority {
return None; return None;
@ -91,7 +91,6 @@ pub fn get_prioritized_hash_function(hash_func_left: &str, hash_func_right: &str
} else { } else {
Some(hash_func_right.to_owned()) Some(hash_func_right.to_owned())
} }
} }
/// <https://w3c.github.io/webappsec-subresource-integrity/#get-the-strongest-metadata> /// <https://w3c.github.io/webappsec-subresource-integrity/#get-the-strongest-metadata>
@ -100,8 +99,8 @@ pub fn get_strongest_metadata(integrity_metadata_list: Vec<SriEntry>) -> Vec<Sri
let mut current_algorithm = result[0].alg.clone(); let mut current_algorithm = result[0].alg.clone();
for integrity_metadata in &integrity_metadata_list[1..] { for integrity_metadata in &integrity_metadata_list[1..] {
let prioritized_hash = get_prioritized_hash_function(&integrity_metadata.alg, let prioritized_hash =
&*current_algorithm); get_prioritized_hash_function(&integrity_metadata.alg, &*current_algorithm);
if prioritized_hash.is_none() { if prioritized_hash.is_none() {
result.push(integrity_metadata.clone()); result.push(integrity_metadata.clone());
} else if let Some(algorithm) = prioritized_hash { } else if let Some(algorithm) = prioritized_hash {
@ -116,9 +115,10 @@ pub fn get_strongest_metadata(integrity_metadata_list: Vec<SriEntry>) -> Vec<Sri
} }
/// <https://w3c.github.io/webappsec-subresource-integrity/#apply-algorithm-to-response> /// <https://w3c.github.io/webappsec-subresource-integrity/#apply-algorithm-to-response>
fn apply_algorithm_to_response(body: MutexGuard<ResponseBody>, fn apply_algorithm_to_response(
message_digest: MessageDigest) body: MutexGuard<ResponseBody>,
-> String { message_digest: MessageDigest,
) -> String {
if let ResponseBody::Done(ref vec) = *body { if let ResponseBody::Done(ref vec) = *body {
let response_digest = hash(message_digest, vec).unwrap(); //Now hash let response_digest = hash(message_digest, vec).unwrap(); //Now hash
base64::encode(&response_digest) base64::encode(&response_digest)
@ -171,8 +171,12 @@ pub fn is_response_integrity_valid(integrity_metadata: &str, response: &Response
false false
} }
pub fn split_html_space_chars<'a>(s: &'a str) -> pub fn split_html_space_chars<'a>(
Filter<Split<'a, StaticCharVec>, fn(&&str) -> bool> { s: &'a str,
fn not_empty(&split: &&str) -> bool { !split.is_empty() } ) -> Filter<Split<'a, StaticCharVec>, fn(&&str) -> bool> {
s.split(HTML_SPACE_CHARACTERS).filter(not_empty as fn(&&str) -> bool) fn not_empty(&split: &&str) -> bool {
!split.is_empty()
}
s.split(HTML_SPACE_CHARACTERS)
.filter(not_empty as fn(&&str) -> bool)
} }

View file

@ -118,11 +118,13 @@ fn test_cookie_secure_prefix() {
assert!(Cookie::new_wrapped(cookie, url, CookieSource::HTTP).is_none()); assert!(Cookie::new_wrapped(cookie, url, CookieSource::HTTP).is_none());
let url = &ServoUrl::parse("http://example.com").unwrap(); let url = &ServoUrl::parse("http://example.com").unwrap();
let cookie = cookie_rs::Cookie::parse("__Secure-SID=12345; Secure; Domain=example.com").unwrap(); let cookie =
cookie_rs::Cookie::parse("__Secure-SID=12345; Secure; Domain=example.com").unwrap();
assert!(Cookie::new_wrapped(cookie, url, CookieSource::HTTP).is_none()); assert!(Cookie::new_wrapped(cookie, url, CookieSource::HTTP).is_none());
let url = &ServoUrl::parse("https://example.com").unwrap(); let url = &ServoUrl::parse("https://example.com").unwrap();
let cookie = cookie_rs::Cookie::parse("__Secure-SID=12345; Secure; Domain=example.com").unwrap(); let cookie =
cookie_rs::Cookie::parse("__Secure-SID=12345; Secure; Domain=example.com").unwrap();
assert!(Cookie::new_wrapped(cookie, url, CookieSource::HTTP).is_some()); assert!(Cookie::new_wrapped(cookie, url, CookieSource::HTTP).is_some());
} }
@ -157,7 +159,8 @@ fn test_cookie_host_prefix() {
assert!(Cookie::new_wrapped(cookie, url, CookieSource::HTTP).is_none()); assert!(Cookie::new_wrapped(cookie, url, CookieSource::HTTP).is_none());
let url = &ServoUrl::parse("https://example.com").unwrap(); let url = &ServoUrl::parse("https://example.com").unwrap();
let cookie = cookie_rs::Cookie::parse("__Host-SID=12345; Secure; Domain=example.com; Path=/").unwrap(); let cookie =
cookie_rs::Cookie::parse("__Host-SID=12345; Secure; Domain=example.com; Path=/").unwrap();
assert!(Cookie::new_wrapped(cookie, url, CookieSource::HTTP).is_none()); assert!(Cookie::new_wrapped(cookie, url, CookieSource::HTTP).is_none());
let url = &ServoUrl::parse("https://example.com").unwrap(); let url = &ServoUrl::parse("https://example.com").unwrap();
@ -193,13 +196,18 @@ fn test_sort_order() {
assert!(b.cookie.path().as_ref().unwrap().len() > a.cookie.path().as_ref().unwrap().len()); assert!(b.cookie.path().as_ref().unwrap().len() > a.cookie.path().as_ref().unwrap().len());
assert_eq!(CookieStorage::cookie_comparator(&a, &b), Ordering::Greater); assert_eq!(CookieStorage::cookie_comparator(&a, &b), Ordering::Greater);
assert_eq!(CookieStorage::cookie_comparator(&b, &a), Ordering::Less); assert_eq!(CookieStorage::cookie_comparator(&b, &a), Ordering::Less);
assert_eq!(CookieStorage::cookie_comparator(&a, &a_prime), Ordering::Less); assert_eq!(
assert_eq!(CookieStorage::cookie_comparator(&a_prime, &a), Ordering::Greater); CookieStorage::cookie_comparator(&a, &a_prime),
Ordering::Less
);
assert_eq!(
CookieStorage::cookie_comparator(&a_prime, &a),
Ordering::Greater
);
assert_eq!(CookieStorage::cookie_comparator(&a, &a), Ordering::Equal); assert_eq!(CookieStorage::cookie_comparator(&a, &a), Ordering::Equal);
} }
fn add_cookie_to_storage(storage: &mut CookieStorage, url: &ServoUrl, cookie_str: &str) fn add_cookie_to_storage(storage: &mut CookieStorage, url: &ServoUrl, cookie_str: &str) {
{
let source = CookieSource::HTTP; let source = CookieSource::HTTP;
let cookie = cookie_rs::Cookie::parse(cookie_str.to_owned()).unwrap(); let cookie = cookie_rs::Cookie::parse(cookie_str.to_owned()).unwrap();
let cookie = Cookie::new_wrapped(cookie, url, source).unwrap(); let cookie = Cookie::new_wrapped(cookie, url, source).unwrap();
@ -225,21 +233,40 @@ fn test_insecure_cookies_cannot_evict_secure_cookie() {
let insecure_url = ServoUrl::parse("http://home.example.org:8888/cookie-parser?0001").unwrap(); let insecure_url = ServoUrl::parse("http://home.example.org:8888/cookie-parser?0001").unwrap();
add_cookie_to_storage(&mut storage, &insecure_url, "foo=value; Domain=home.example.org"); add_cookie_to_storage(
add_cookie_to_storage(&mut storage, &insecure_url, "foo2=value; Domain=.example.org"); &mut storage,
&insecure_url,
"foo=value; Domain=home.example.org",
);
add_cookie_to_storage(
&mut storage,
&insecure_url,
"foo2=value; Domain=.example.org",
);
add_cookie_to_storage(&mut storage, &insecure_url, "foo3=value; Path=/foo/bar"); add_cookie_to_storage(&mut storage, &insecure_url, "foo3=value; Path=/foo/bar");
add_cookie_to_storage(&mut storage, &insecure_url, "foo4=value; Path=/foo"); add_cookie_to_storage(&mut storage, &insecure_url, "foo4=value; Path=/foo");
let source = CookieSource::HTTP; let source = CookieSource::HTTP;
assert_eq!(storage.cookies_for_url(&secure_url, source).unwrap(), "foo=bar; foo2=bar"); assert_eq!(
storage.cookies_for_url(&secure_url, source).unwrap(),
"foo=bar; foo2=bar"
);
let url = ServoUrl::parse("https://home.example.org:8888/foo/cookie-parser-result?0001").unwrap(); let url =
ServoUrl::parse("https://home.example.org:8888/foo/cookie-parser-result?0001").unwrap();
let source = CookieSource::HTTP; let source = CookieSource::HTTP;
assert_eq!(storage.cookies_for_url(&url, source).unwrap(), "foo3=bar; foo4=value; foo=bar; foo2=bar"); assert_eq!(
storage.cookies_for_url(&url, source).unwrap(),
"foo3=bar; foo4=value; foo=bar; foo2=bar"
);
let url = ServoUrl::parse("https://home.example.org:8888/foo/bar/cookie-parser-result?0001").unwrap(); let url =
ServoUrl::parse("https://home.example.org:8888/foo/bar/cookie-parser-result?0001").unwrap();
let source = CookieSource::HTTP; let source = CookieSource::HTTP;
assert_eq!(storage.cookies_for_url(&url, source).unwrap(), "foo4=bar; foo3=bar; foo4=value; foo=bar; foo2=bar"); assert_eq!(
storage.cookies_for_url(&url, source).unwrap(),
"foo4=bar; foo3=bar; foo4=value; foo=bar; foo2=bar"
);
} }
#[test] #[test]
@ -267,14 +294,21 @@ fn test_secure_cookies_eviction() {
let source = CookieSource::HTTP; let source = CookieSource::HTTP;
assert_eq!(storage.cookies_for_url(&url, source).unwrap(), "foo2=value"); assert_eq!(storage.cookies_for_url(&url, source).unwrap(), "foo2=value");
let url = ServoUrl::parse("https://home.example.org:8888/foo/cookie-parser-result?0001").unwrap(); let url =
ServoUrl::parse("https://home.example.org:8888/foo/cookie-parser-result?0001").unwrap();
let source = CookieSource::HTTP; let source = CookieSource::HTTP;
assert_eq!(storage.cookies_for_url(&url, source).unwrap(), "foo3=bar; foo4=value; foo2=value"); assert_eq!(
storage.cookies_for_url(&url, source).unwrap(),
"foo3=bar; foo4=value; foo2=value"
);
let url = ServoUrl::parse("https://home.example.org:8888/foo/bar/cookie-parser-result?0001").unwrap(); let url =
ServoUrl::parse("https://home.example.org:8888/foo/bar/cookie-parser-result?0001").unwrap();
let source = CookieSource::HTTP; let source = CookieSource::HTTP;
assert_eq!(storage.cookies_for_url(&url, source).unwrap(), assert_eq!(
"foo4=bar; foo3=value; foo3=bar; foo4=value; foo2=value"); storage.cookies_for_url(&url, source).unwrap(),
"foo4=bar; foo3=value; foo3=bar; foo4=value; foo2=value"
);
} }
#[test] #[test]
@ -302,21 +336,28 @@ fn test_secure_cookies_eviction_non_http_source() {
let source = CookieSource::HTTP; let source = CookieSource::HTTP;
assert_eq!(storage.cookies_for_url(&url, source).unwrap(), "foo2=value"); assert_eq!(storage.cookies_for_url(&url, source).unwrap(), "foo2=value");
let url = ServoUrl::parse("https://home.example.org:8888/foo/cookie-parser-result?0001").unwrap(); let url =
ServoUrl::parse("https://home.example.org:8888/foo/cookie-parser-result?0001").unwrap();
let source = CookieSource::HTTP; let source = CookieSource::HTTP;
assert_eq!(storage.cookies_for_url(&url, source).unwrap(), "foo3=bar; foo4=value; foo2=value"); assert_eq!(
storage.cookies_for_url(&url, source).unwrap(),
"foo3=bar; foo4=value; foo2=value"
);
let url = ServoUrl::parse("https://home.example.org:8888/foo/bar/cookie-parser-result?0001").unwrap(); let url =
ServoUrl::parse("https://home.example.org:8888/foo/bar/cookie-parser-result?0001").unwrap();
let source = CookieSource::HTTP; let source = CookieSource::HTTP;
assert_eq!(storage.cookies_for_url(&url, source).unwrap(), assert_eq!(
"foo4=bar; foo3=value; foo3=bar; foo4=value; foo2=value"); storage.cookies_for_url(&url, source).unwrap(),
"foo4=bar; foo3=value; foo3=bar; foo4=value; foo2=value"
);
} }
fn add_retrieve_cookies(
fn add_retrieve_cookies(set_location: &str, set_location: &str,
set_cookies: &[String], set_cookies: &[String],
final_location: &str) final_location: &str,
-> String { ) -> String {
let mut storage = CookieStorage::new(5); let mut storage = CookieStorage::new(5);
let url = ServoUrl::parse(set_location).unwrap(); let url = ServoUrl::parse(set_location).unwrap();
let source = CookieSource::HTTP; let source = CookieSource::HTTP;
@ -329,56 +370,75 @@ fn add_retrieve_cookies(set_location: &str,
// Get cookies for the test location // Get cookies for the test location
let url = ServoUrl::parse(final_location).unwrap(); let url = ServoUrl::parse(final_location).unwrap();
storage.cookies_for_url(&url, source).unwrap_or("".to_string()) storage
.cookies_for_url(&url, source)
.unwrap_or("".to_string())
} }
#[test] #[test]
fn test_cookie_eviction_expired() { fn test_cookie_eviction_expired() {
let mut vec = Vec::new(); let mut vec = Vec::new();
for i in 1..6 { for i in 1..6 {
let st = format!("extra{}=bar; Secure; expires=Sun, 18-Apr-2000 21:06:29 GMT", let st = format!(
i); "extra{}=bar; Secure; expires=Sun, 18-Apr-2000 21:06:29 GMT",
i
);
vec.push(st); vec.push(st);
} }
vec.push("foo=bar; Secure; expires=Sun, 18-Apr-2027 21:06:29 GMT".to_owned()); vec.push("foo=bar; Secure; expires=Sun, 18-Apr-2027 21:06:29 GMT".to_owned());
let r = add_retrieve_cookies("https://home.example.org:8888/cookie-parser?0001", let r = add_retrieve_cookies(
&vec, "https://home.example.org:8888/cookie-parser-result?0001"); "https://home.example.org:8888/cookie-parser?0001",
&vec,
"https://home.example.org:8888/cookie-parser-result?0001",
);
assert_eq!(&r, "foo=bar"); assert_eq!(&r, "foo=bar");
} }
#[test] #[test]
fn test_cookie_eviction_all_secure_one_nonsecure() { fn test_cookie_eviction_all_secure_one_nonsecure() {
let mut vec = Vec::new(); let mut vec = Vec::new();
for i in 1..5 { for i in 1..5 {
let st = format!("extra{}=bar; Secure; expires=Sun, 18-Apr-2026 21:06:29 GMT", let st = format!(
i); "extra{}=bar; Secure; expires=Sun, 18-Apr-2026 21:06:29 GMT",
i
);
vec.push(st); vec.push(st);
} }
vec.push("foo=bar; expires=Sun, 18-Apr-2026 21:06:29 GMT".to_owned()); vec.push("foo=bar; expires=Sun, 18-Apr-2026 21:06:29 GMT".to_owned());
vec.push("foo2=bar; Secure; expires=Sun, 18-Apr-2028 21:06:29 GMT".to_owned()); vec.push("foo2=bar; Secure; expires=Sun, 18-Apr-2028 21:06:29 GMT".to_owned());
let r = add_retrieve_cookies("https://home.example.org:8888/cookie-parser?0001", let r = add_retrieve_cookies(
&vec, "https://home.example.org:8888/cookie-parser-result?0001"); "https://home.example.org:8888/cookie-parser?0001",
assert_eq!(&r, "extra1=bar; extra2=bar; extra3=bar; extra4=bar; foo2=bar"); &vec,
"https://home.example.org:8888/cookie-parser-result?0001",
);
assert_eq!(
&r,
"extra1=bar; extra2=bar; extra3=bar; extra4=bar; foo2=bar"
);
} }
#[test] #[test]
fn test_cookie_eviction_all_secure_new_nonsecure() { fn test_cookie_eviction_all_secure_new_nonsecure() {
let mut vec = Vec::new(); let mut vec = Vec::new();
for i in 1..6 { for i in 1..6 {
let st = format!("extra{}=bar; Secure; expires=Sun, 18-Apr-2026 21:06:29 GMT", let st = format!(
i); "extra{}=bar; Secure; expires=Sun, 18-Apr-2026 21:06:29 GMT",
i
);
vec.push(st); vec.push(st);
} }
vec.push("foo=bar; expires=Sun, 18-Apr-2077 21:06:29 GMT".to_owned()); vec.push("foo=bar; expires=Sun, 18-Apr-2077 21:06:29 GMT".to_owned());
let r = add_retrieve_cookies("https://home.example.org:8888/cookie-parser?0001", let r = add_retrieve_cookies(
&vec, "https://home.example.org:8888/cookie-parser-result?0001"); "https://home.example.org:8888/cookie-parser?0001",
assert_eq!(&r, "extra1=bar; extra2=bar; extra3=bar; extra4=bar; extra5=bar"); &vec,
"https://home.example.org:8888/cookie-parser-result?0001",
);
assert_eq!(
&r,
"extra1=bar; extra2=bar; extra3=bar; extra4=bar; extra5=bar"
);
} }
#[test] #[test]
fn test_cookie_eviction_all_nonsecure_new_secure() { fn test_cookie_eviction_all_nonsecure_new_secure() {
let mut vec = Vec::new(); let mut vec = Vec::new();
@ -387,12 +447,17 @@ fn test_cookie_eviction_all_nonsecure_new_secure() {
vec.push(st); vec.push(st);
} }
vec.push("foo=bar; Secure; expires=Sun, 18-Apr-2077 21:06:29 GMT".to_owned()); vec.push("foo=bar; Secure; expires=Sun, 18-Apr-2077 21:06:29 GMT".to_owned());
let r = add_retrieve_cookies("https://home.example.org:8888/cookie-parser?0001", let r = add_retrieve_cookies(
&vec, "https://home.example.org:8888/cookie-parser-result?0001"); "https://home.example.org:8888/cookie-parser?0001",
assert_eq!(&r, "extra2=bar; extra3=bar; extra4=bar; extra5=bar; foo=bar"); &vec,
"https://home.example.org:8888/cookie-parser-result?0001",
);
assert_eq!(
&r,
"extra2=bar; extra3=bar; extra4=bar; extra5=bar; foo=bar"
);
} }
#[test] #[test]
fn test_cookie_eviction_all_nonsecure_new_nonsecure() { fn test_cookie_eviction_all_nonsecure_new_nonsecure() {
let mut vec = Vec::new(); let mut vec = Vec::new();
@ -401,7 +466,13 @@ fn test_cookie_eviction_all_nonsecure_new_nonsecure() {
vec.push(st); vec.push(st);
} }
vec.push("foo=bar; expires=Sun, 18-Apr-2077 21:06:29 GMT".to_owned()); vec.push("foo=bar; expires=Sun, 18-Apr-2077 21:06:29 GMT".to_owned());
let r = add_retrieve_cookies("https://home.example.org:8888/cookie-parser?0001", let r = add_retrieve_cookies(
&vec, "https://home.example.org:8888/cookie-parser-result?0001"); "https://home.example.org:8888/cookie-parser?0001",
assert_eq!(&r, "extra2=bar; extra3=bar; extra4=bar; extra5=bar; foo=bar"); &vec,
"https://home.example.org:8888/cookie-parser-result?0001",
);
assert_eq!(
&r,
"extra2=bar; extra3=bar; extra4=bar; extra5=bar; foo=bar"
);
} }

File diff suppressed because it is too large Load diff

View file

@ -14,10 +14,12 @@ use servo_url::ServoUrl;
use std::ops::Deref; use std::ops::Deref;
#[cfg(test)] #[cfg(test)]
fn assert_parse(url: &'static str, fn assert_parse(
content_type: Option<ContentType>, url: &'static str,
charset: Option<&str>, content_type: Option<ContentType>,
data: Option<&[u8]>) { charset: Option<&str>,
data: Option<&[u8]>,
) {
let url = ServoUrl::parse(url).unwrap(); let url = ServoUrl::parse(url).unwrap();
let origin = Origin::Origin(url.origin()); let origin = Origin::Origin(url.origin());
let mut request = Request::new(url, Some(origin), None); let mut request = Request::new(url, Some(origin), None);
@ -33,7 +35,10 @@ fn assert_parse(url: &'static str,
assert_eq!(header_content_type, content_type); assert_eq!(header_content_type, content_type);
let metadata = match response.metadata() { let metadata = match response.metadata() {
Ok(FetchMetadata::Filtered { filtered: FilteredMetadata::Basic(m), .. }) => m, Ok(FetchMetadata::Filtered {
filtered: FilteredMetadata::Basic(m),
..
}) => m,
result => panic!(result), result => panic!(result),
}; };
assert_eq!(metadata.content_type.map(Serde::into_inner), content_type); assert_eq!(metadata.content_type.map(Serde::into_inner), content_type);
@ -49,7 +54,12 @@ fn assert_parse(url: &'static str,
}, },
None => { None => {
assert!(response.is_network_error()); assert!(response.is_network_error());
assert_eq!(response.metadata().err(), Some(NetworkError::Internal("Decoding data URL failed".to_owned()))); assert_eq!(
response.metadata().err(),
Some(NetworkError::Internal(
"Decoding data URL failed".to_owned()
))
);
}, },
} }
} }
@ -63,9 +73,12 @@ fn empty_invalid() {
fn plain() { fn plain() {
assert_parse( assert_parse(
"data:,hello%20world", "data:,hello%20world",
Some(ContentType::from("text/plain; charset=US-ASCII".parse::<Mime>().unwrap())), Some(ContentType::from(
"text/plain; charset=US-ASCII".parse::<Mime>().unwrap(),
)),
Some("us-ascii"), Some("us-ascii"),
Some(b"hello world")); Some(b"hello world"),
);
} }
#[test] #[test]
@ -74,7 +87,8 @@ fn plain_ct() {
"data:text/plain,hello", "data:text/plain,hello",
Some(ContentType::from(mime::TEXT_PLAIN)), Some(ContentType::from(mime::TEXT_PLAIN)),
None, None,
Some(b"hello")); Some(b"hello"),
);
} }
#[test] #[test]
@ -83,16 +97,20 @@ fn plain_html() {
"data:text/html,<p>Servo</p>", "data:text/html,<p>Servo</p>",
Some(ContentType::from(mime::TEXT_HTML)), Some(ContentType::from(mime::TEXT_HTML)),
None, None,
Some(b"<p>Servo</p>")); Some(b"<p>Servo</p>"),
);
} }
#[test] #[test]
fn plain_charset() { fn plain_charset() {
assert_parse( assert_parse(
"data:text/plain;charset=latin1,hello", "data:text/plain;charset=latin1,hello",
Some(ContentType::from("text/plain; charset=latin1".parse::<Mime>().unwrap())), Some(ContentType::from(
"text/plain; charset=latin1".parse::<Mime>().unwrap(),
)),
Some("latin1"), Some("latin1"),
Some(b"hello")); Some(b"hello"),
);
} }
#[test] #[test]
@ -101,16 +119,20 @@ fn plain_only_charset() {
"data:;charset=utf-8,hello", "data:;charset=utf-8,hello",
Some(ContentType::from(mime::TEXT_PLAIN_UTF_8)), Some(ContentType::from(mime::TEXT_PLAIN_UTF_8)),
Some("utf-8"), Some("utf-8"),
Some(b"hello")); Some(b"hello"),
);
} }
#[test] #[test]
fn base64() { fn base64() {
assert_parse( assert_parse(
"data:;base64,C62+7w==", "data:;base64,C62+7w==",
Some(ContentType::from("text/plain; charset=US-ASCII".parse::<Mime>().unwrap())), Some(ContentType::from(
"text/plain; charset=US-ASCII".parse::<Mime>().unwrap(),
)),
Some("us-ascii"), Some("us-ascii"),
Some(&[0x0B, 0xAD, 0xBE, 0xEF])); Some(&[0x0B, 0xAD, 0xBE, 0xEF]),
);
} }
#[test] #[test]
@ -119,14 +141,20 @@ fn base64_ct() {
"data:application/octet-stream;base64,C62+7w==", "data:application/octet-stream;base64,C62+7w==",
Some(ContentType::from(mime::APPLICATION_OCTET_STREAM)), Some(ContentType::from(mime::APPLICATION_OCTET_STREAM)),
None, None,
Some(&[0x0B, 0xAD, 0xBE, 0xEF])); Some(&[0x0B, 0xAD, 0xBE, 0xEF]),
);
} }
#[test] #[test]
fn base64_charset() { fn base64_charset() {
assert_parse( assert_parse(
"data:text/plain;charset=koi8-r;base64,8PLl9+XkIO3l5Pfl5A==", "data:text/plain;charset=koi8-r;base64,8PLl9+XkIO3l5Pfl5A==",
Some(ContentType::from("text/plain; charset=koi8-r".parse::<Mime>().unwrap())), Some(ContentType::from(
"text/plain; charset=koi8-r".parse::<Mime>().unwrap(),
)),
Some("koi8-r"), Some("koi8-r"),
Some(&[0xF0, 0xF2, 0xE5, 0xF7, 0xE5, 0xE4, 0x20, 0xED, 0xE5, 0xE4, 0xF7, 0xE5, 0xE4])); Some(&[
0xF0, 0xF2, 0xE5, 0xF7, 0xE5, 0xE4, 0x20, 0xED, 0xE5, 0xE4, 0xF7, 0xE5, 0xE4,
]),
);
} }

View file

@ -70,7 +70,10 @@ fn test_fetch_on_bad_port_is_network_error() {
let fetch_response = fetch(&mut request, None); let fetch_response = fetch(&mut request, None);
assert!(fetch_response.is_network_error()); assert!(fetch_response.is_network_error());
let fetch_error = fetch_response.get_network_error().unwrap(); let fetch_error = fetch_response.get_network_error().unwrap();
assert_eq!(fetch_error, &NetworkError::Internal("Request attempted on bad port".into())) assert_eq!(
fetch_error,
&NetworkError::Internal("Request attempted on bad port".into())
)
} }
#[test] #[test]
@ -94,7 +97,7 @@ fn test_fetch_response_body_matches_const_message() {
ResponseBody::Done(ref body) => { ResponseBody::Done(ref body) => {
assert_eq!(&**body, MESSAGE); assert_eq!(&**body, MESSAGE);
}, },
_ => panic!() _ => panic!(),
}; };
} }
@ -106,7 +109,10 @@ fn test_fetch_aboutblank() {
request.referrer = Referrer::NoReferrer; request.referrer = Referrer::NoReferrer;
let fetch_response = fetch(&mut request, None); let fetch_response = fetch(&mut request, None);
assert!(!fetch_response.is_network_error()); assert!(!fetch_response.is_network_error());
assert_eq!(*fetch_response.body.lock().unwrap(), ResponseBody::Done(vec![])); assert_eq!(
*fetch_response.body.lock().unwrap(),
ResponseBody::Done(vec![])
);
} }
#[test] #[test]
@ -127,11 +133,12 @@ fn test_fetch_blob() {
let origin = ServoUrl::parse("http://www.example.org/").unwrap(); let origin = ServoUrl::parse("http://www.example.org/").unwrap();
let (sender, receiver) = ipc::channel().unwrap(); let (sender, receiver) = ipc::channel().unwrap();
context.filemanager.promote_memory(blob_buf, true, sender, "http://www.example.org".into()); context
.filemanager
.promote_memory(blob_buf, true, sender, "http://www.example.org".into());
let id = receiver.recv().unwrap().unwrap(); let id = receiver.recv().unwrap().unwrap();
let url = ServoUrl::parse(&format!("blob:{}{}", origin.as_str(), id.simple())).unwrap(); let url = ServoUrl::parse(&format!("blob:{}{}", origin.as_str(), id.simple())).unwrap();
let mut request = Request::new(url, Some(Origin::Origin(origin.origin())), None); let mut request = Request::new(url, Some(Origin::Origin(origin.origin())), None);
let fetch_response = fetch_with_context(&mut request, &context); let fetch_response = fetch_with_context(&mut request, &context);
@ -139,19 +146,27 @@ fn test_fetch_blob() {
assert_eq!(fetch_response.headers.len(), 2); assert_eq!(fetch_response.headers.len(), 2);
let content_type: Mime = fetch_response.headers.typed_get::<ContentType>().unwrap().into(); let content_type: Mime = fetch_response
.headers
.typed_get::<ContentType>()
.unwrap()
.into();
assert_eq!(content_type, mime::TEXT_PLAIN); assert_eq!(content_type, mime::TEXT_PLAIN);
let content_length: ContentLength = fetch_response.headers.typed_get().unwrap(); let content_length: ContentLength = fetch_response.headers.typed_get().unwrap();
assert_eq!(content_length.0, bytes.len() as u64); assert_eq!(content_length.0, bytes.len() as u64);
assert_eq!(*fetch_response.body.lock().unwrap(), assert_eq!(
ResponseBody::Done(bytes.to_vec())); *fetch_response.body.lock().unwrap(),
ResponseBody::Done(bytes.to_vec())
);
} }
#[test] #[test]
fn test_fetch_file() { fn test_fetch_file() {
let path = Path::new("../../resources/servo.css").canonicalize().unwrap(); let path = Path::new("../../resources/servo.css")
.canonicalize()
.unwrap();
let url = ServoUrl::from_file_path(path.clone()).unwrap(); let url = ServoUrl::from_file_path(path.clone()).unwrap();
let origin = Origin::Origin(url.origin()); let origin = Origin::Origin(url.origin());
let mut request = Request::new(url, Some(origin), None); let mut request = Request::new(url, Some(origin), None);
@ -159,7 +174,11 @@ fn test_fetch_file() {
let fetch_response = fetch(&mut request, None); let fetch_response = fetch(&mut request, None);
assert!(!fetch_response.is_network_error()); assert!(!fetch_response.is_network_error());
assert_eq!(fetch_response.headers.len(), 1); assert_eq!(fetch_response.headers.len(), 1);
let content_type: Mime = fetch_response.headers.typed_get::<ContentType>().unwrap().into(); let content_type: Mime = fetch_response
.headers
.typed_get::<ContentType>()
.unwrap()
.into();
assert_eq!(content_type, mime::TEXT_CSS); assert_eq!(content_type, mime::TEXT_CSS);
let resp_body = fetch_response.body.lock().unwrap(); let resp_body = fetch_response.body.lock().unwrap();
@ -171,7 +190,7 @@ fn test_fetch_file() {
ResponseBody::Done(ref val) => { ResponseBody::Done(ref val) => {
assert_eq!(val, &bytes); assert_eq!(val, &bytes);
}, },
_ => panic!() _ => panic!(),
} }
} }
@ -200,15 +219,34 @@ fn test_cors_preflight_fetch() {
static ACK: &'static [u8] = b"ACK"; static ACK: &'static [u8] = b"ACK";
let state = Arc::new(AtomicUsize::new(0)); let state = Arc::new(AtomicUsize::new(0));
let handler = move |request: HyperRequest<Body>, response: &mut HyperResponse<Body>| { let handler = move |request: HyperRequest<Body>, response: &mut HyperResponse<Body>| {
if request.method() == Method::OPTIONS && state.clone().fetch_add(1, Ordering::SeqCst) == 0 { 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
assert!(!request.headers().get(header::REFERER).unwrap().to_str().unwrap().contains("a.html")); .headers()
response.headers_mut().typed_insert(AccessControlAllowOrigin::ANY); .contains_key(header::ACCESS_CONTROL_REQUEST_METHOD));
response.headers_mut().typed_insert(AccessControlAllowCredentials); assert!(!request
response.headers_mut().typed_insert(AccessControlAllowMethods::from_iter(vec![Method::GET])); .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 { } else {
response.headers_mut().typed_insert(AccessControlAllowOrigin::ANY); response
.headers_mut()
.typed_insert(AccessControlAllowOrigin::ANY);
*response.body_mut() = ACK.to_vec().into(); *response.body_mut() = ACK.to_vec().into();
} }
}; };
@ -228,7 +266,7 @@ fn test_cors_preflight_fetch() {
assert!(!fetch_response.is_network_error()); assert!(!fetch_response.is_network_error());
match *fetch_response.body.lock().unwrap() { match *fetch_response.body.lock().unwrap() {
ResponseBody::Done(ref body) => assert_eq!(&**body, ACK), ResponseBody::Done(ref body) => assert_eq!(&**body, ACK),
_ => panic!() _ => panic!(),
}; };
} }
@ -239,15 +277,30 @@ fn test_cors_preflight_cache_fetch() {
let counter = state.clone(); let counter = state.clone();
let mut cache = CorsCache::new(); let mut cache = CorsCache::new();
let handler = move |request: HyperRequest<Body>, response: &mut HyperResponse<Body>| { let handler = move |request: HyperRequest<Body>, response: &mut HyperResponse<Body>| {
if request.method() == Method::OPTIONS && state.clone().fetch_add(1, Ordering::SeqCst) == 0 { 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
response.headers_mut().typed_insert(AccessControlAllowOrigin::ANY); .headers()
response.headers_mut().typed_insert(AccessControlAllowCredentials); .contains_key(header::ACCESS_CONTROL_REQUEST_METHOD));
response.headers_mut().typed_insert(AccessControlAllowMethods::from_iter(vec![Method::GET])); assert!(!request
response.headers_mut().typed_insert(AccessControlMaxAge::from(Duration::new(6000, 0))); .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 { } else {
response.headers_mut().typed_insert(AccessControlAllowOrigin::ANY); response
.headers_mut()
.typed_insert(AccessControlAllowOrigin::ANY);
*response.body_mut() = ACK.to_vec().into(); *response.body_mut() = ACK.to_vec().into();
} }
}; };
@ -276,11 +329,11 @@ fn test_cors_preflight_cache_fetch() {
match *fetch_response0.body.lock().unwrap() { match *fetch_response0.body.lock().unwrap() {
ResponseBody::Done(ref body) => assert_eq!(&**body, ACK), ResponseBody::Done(ref body) => assert_eq!(&**body, ACK),
_ => panic!() _ => panic!(),
}; };
match *fetch_response1.body.lock().unwrap() { match *fetch_response1.body.lock().unwrap() {
ResponseBody::Done(ref body) => assert_eq!(&**body, ACK), ResponseBody::Done(ref body) => assert_eq!(&**body, ACK),
_ => panic!() _ => panic!(),
}; };
} }
@ -289,14 +342,27 @@ fn test_cors_preflight_fetch_network_error() {
static ACK: &'static [u8] = b"ACK"; static ACK: &'static [u8] = b"ACK";
let state = Arc::new(AtomicUsize::new(0)); let state = Arc::new(AtomicUsize::new(0));
let handler = move |request: HyperRequest<Body>, response: &mut HyperResponse<Body>| { let handler = move |request: HyperRequest<Body>, response: &mut HyperResponse<Body>| {
if request.method() == Method::OPTIONS && state.clone().fetch_add(1, Ordering::SeqCst) == 0 { 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
response.headers_mut().typed_insert(AccessControlAllowOrigin::ANY); .headers()
response.headers_mut().typed_insert(AccessControlAllowCredentials); .contains_key(header::ACCESS_CONTROL_REQUEST_METHOD));
response.headers_mut().typed_insert(AccessControlAllowMethods::from_iter(vec![Method::GET])); 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 { } else {
response.headers_mut().typed_insert(AccessControlAllowOrigin::ANY); response
.headers_mut()
.typed_insert(AccessControlAllowOrigin::ANY);
*response.body_mut() = ACK.to_vec().into(); *response.body_mut() = ACK.to_vec().into();
} }
}; };
@ -318,11 +384,13 @@ fn test_cors_preflight_fetch_network_error() {
fn test_fetch_response_is_basic_filtered() { fn test_fetch_response_is_basic_filtered() {
static MESSAGE: &'static [u8] = b""; static MESSAGE: &'static [u8] = b"";
let handler = move |_: HyperRequest<Body>, response: &mut HyperResponse<Body>| { let handler = move |_: HyperRequest<Body>, response: &mut HyperResponse<Body>| {
response.headers_mut().insert(header::SET_COOKIE, HeaderValue::from_static("")); 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 // this header is obsoleted, so hyper doesn't implement it, but it's still covered by the spec
response.headers_mut().insert( response.headers_mut().insert(
HeaderName::from_static("set-cookie2"), HeaderName::from_static("set-cookie2"),
HeaderValue::from_bytes(&vec![]).unwrap() HeaderValue::from_bytes(&vec![]).unwrap(),
); );
*response.body_mut() = MESSAGE.to_vec().into(); *response.body_mut() = MESSAGE.to_vec().into();
@ -340,7 +408,9 @@ fn test_fetch_response_is_basic_filtered() {
let headers = fetch_response.headers; let headers = fetch_response.headers;
assert!(!headers.contains_key(header::SET_COOKIE)); assert!(!headers.contains_key(header::SET_COOKIE));
assert!(headers.get(HeaderName::from_static("set-cookie2")).is_none()); assert!(headers
.get(HeaderName::from_static("set-cookie2"))
.is_none());
} }
#[test] #[test]
@ -349,28 +419,41 @@ fn test_fetch_response_is_cors_filtered() {
let handler = move |_: HyperRequest<Body>, response: &mut HyperResponse<Body>| { let handler = move |_: HyperRequest<Body>, response: &mut HyperResponse<Body>| {
// this is mandatory for the Cors Check to pass // this is mandatory for the Cors Check to pass
// TODO test using different url encodings with this value ie. punycode // TODO test using different url encodings with this value ie. punycode
response.headers_mut().typed_insert(AccessControlAllowOrigin::ANY); response
.headers_mut()
.typed_insert(AccessControlAllowOrigin::ANY);
// these are the headers that should be kept after filtering // these are the headers that should be kept after filtering
response.headers_mut().typed_insert(CacheControl::new()); response.headers_mut().typed_insert(CacheControl::new());
response.headers_mut().insert(header::CONTENT_LANGUAGE, HeaderValue::from_bytes(&vec![]).unwrap()); response.headers_mut().insert(
response.headers_mut().typed_insert(ContentType::from(mime::TEXT_HTML)); header::CONTENT_LANGUAGE,
response.headers_mut().typed_insert(Expires::from(SystemTime::now() + Duration::new(86400, 0))); HeaderValue::from_bytes(&vec![]).unwrap(),
response.headers_mut().typed_insert(LastModified::from(SystemTime::now())); );
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()); response.headers_mut().typed_insert(Pragma::no_cache());
// these headers should not be kept after filtering, even though they are given a pass // these headers should not be kept after filtering, even though they are given a pass
response.headers_mut().insert(header::SET_COOKIE, HeaderValue::from_static("")); response
.headers_mut()
.insert(header::SET_COOKIE, HeaderValue::from_static(""));
response.headers_mut().insert( response.headers_mut().insert(
HeaderName::from_static("set-cookie2"), HeaderName::from_static("set-cookie2"),
HeaderValue::from_bytes(&vec![]).unwrap() HeaderValue::from_bytes(&vec![]).unwrap(),
); );
response.headers_mut().typed_insert( response
AccessControlAllowHeaders::from_iter(vec![ .headers_mut()
.typed_insert(AccessControlAllowHeaders::from_iter(vec![
HeaderName::from_static("set-cookie"), HeaderName::from_static("set-cookie"),
HeaderName::from_static("set-cookie2") HeaderName::from_static("set-cookie2"),
]) ]));
);
*response.body_mut() = MESSAGE.to_vec().into(); *response.body_mut() = MESSAGE.to_vec().into();
}; };
@ -397,7 +480,9 @@ fn test_fetch_response_is_cors_filtered() {
assert!(!headers.contains_key(header::ACCESS_CONTROL_ALLOW_ORIGIN)); assert!(!headers.contains_key(header::ACCESS_CONTROL_ALLOW_ORIGIN));
assert!(!headers.contains_key(header::SET_COOKIE)); assert!(!headers.contains_key(header::SET_COOKIE));
assert!(headers.get(HeaderName::from_static("set-cookie2")).is_none()); assert!(headers
.get(HeaderName::from_static("set-cookie2"))
.is_none());
} }
#[test] #[test]
@ -424,12 +509,12 @@ fn test_fetch_response_is_opaque_filtered() {
assert!(fetch_response.status.is_none()); assert!(fetch_response.status.is_none());
assert_eq!(fetch_response.headers, HeaderMap::new()); assert_eq!(fetch_response.headers, HeaderMap::new());
match *fetch_response.body.lock().unwrap() { match *fetch_response.body.lock().unwrap() {
ResponseBody::Empty => { }, ResponseBody::Empty => {},
_ => panic!() _ => panic!(),
} }
match fetch_response.cache_state { match fetch_response.cache_state {
CacheState::None => { }, CacheState::None => {},
_ => panic!() _ => panic!(),
} }
} }
@ -437,13 +522,21 @@ fn test_fetch_response_is_opaque_filtered() {
fn test_fetch_response_is_opaque_redirect_filtered() { fn test_fetch_response_is_opaque_redirect_filtered() {
static MESSAGE: &'static [u8] = b""; static MESSAGE: &'static [u8] = b"";
let handler = move |request: HyperRequest<Body>, response: &mut HyperResponse<Body>| { let handler = move |request: HyperRequest<Body>, response: &mut HyperResponse<Body>| {
let redirects = request.uri().path().split("/").collect::<String>().parse::<u32>().unwrap_or(0); let redirects = request
.uri()
.path()
.split("/")
.collect::<String>()
.parse::<u32>()
.unwrap_or(0);
if redirects == 1 { if redirects == 1 {
*response.body_mut() = MESSAGE.to_vec().into(); *response.body_mut() = MESSAGE.to_vec().into();
} else { } else {
*response.status_mut() = StatusCode::FOUND; *response.status_mut() = StatusCode::FOUND;
response.headers_mut().insert(header::LOCATION, HeaderValue::from_static("1")); response
.headers_mut()
.insert(header::LOCATION, HeaderValue::from_static("1"));
} }
}; };
@ -463,12 +556,12 @@ fn test_fetch_response_is_opaque_redirect_filtered() {
assert!(fetch_response.status.is_none()); assert!(fetch_response.status.is_none());
assert_eq!(fetch_response.headers, HeaderMap::new()); assert_eq!(fetch_response.headers, HeaderMap::new());
match *fetch_response.body.lock().unwrap() { match *fetch_response.body.lock().unwrap() {
ResponseBody::Empty => { }, ResponseBody::Empty => {},
_ => panic!() _ => panic!(),
} }
match fetch_response.cache_state { match fetch_response.cache_state {
CacheState::None => { }, CacheState::None => {},
_ => panic!() _ => panic!(),
} }
} }
@ -516,12 +609,19 @@ fn test_fetch_with_hsts() {
*response.body_mut() = MESSAGE.to_vec().into(); *response.body_mut() = MESSAGE.to_vec().into();
}; };
let cert_path = Path::new("../../resources/self_signed_certificate_for_testing.crt").canonicalize().unwrap(); let cert_path = Path::new("../../resources/self_signed_certificate_for_testing.crt")
let key_path = Path::new("../../resources/privatekey_for_testing.key").canonicalize().unwrap(); .canonicalize()
.unwrap();
let key_path = Path::new("../../resources/privatekey_for_testing.key")
.canonicalize()
.unwrap();
let (server, url) = make_ssl_server(handler, cert_path.clone(), key_path.clone()); let (server, url) = make_ssl_server(handler, cert_path.clone(), key_path.clone());
let mut ca_content = String::new(); let mut ca_content = String::new();
File::open(cert_path).unwrap().read_to_string(&mut ca_content).unwrap(); File::open(cert_path)
.unwrap()
.read_to_string(&mut ca_content)
.unwrap();
let ssl_client = create_ssl_connector_builder(&ca_content); let ssl_client = create_ssl_connector_builder(&ca_content);
let context = FetchContext { let context = FetchContext {
@ -534,8 +634,9 @@ fn test_fetch_with_hsts() {
{ {
let mut list = context.state.hsts_list.write().unwrap(); let mut list = context.state.hsts_list.write().unwrap();
list.push(HstsEntry::new("localhost".to_owned(), IncludeSubdomains::NotIncluded, None) list.push(
.unwrap()); HstsEntry::new("localhost".to_owned(), IncludeSubdomains::NotIncluded, None).unwrap(),
);
} }
let origin = Origin::Origin(url.origin()); let origin = Origin::Origin(url.origin());
let mut request = Request::new(url, Some(origin), None); let mut request = Request::new(url, Some(origin), None);
@ -544,8 +645,10 @@ fn test_fetch_with_hsts() {
request.local_urls_only = false; request.local_urls_only = false;
let response = fetch_with_context(&mut request, &context); let response = fetch_with_context(&mut request, &context);
server.close(); server.close();
assert_eq!(response.internal_response.unwrap().url().unwrap().scheme(), assert_eq!(
"https"); response.internal_response.unwrap().url().unwrap().scheme(),
"https"
);
} }
#[test] #[test]
@ -562,7 +665,7 @@ fn test_fetch_with_sri_network_error() {
// To calulate hash use : // To calulate hash use :
// echo -n "alert('Hello, Network Error');" | openssl dgst -sha384 -binary | openssl base64 -A // echo -n "alert('Hello, Network Error');" | openssl dgst -sha384 -binary | openssl base64 -A
request.integrity_metadata = request.integrity_metadata =
"sha384-H8BRh8j48O9oYatfu5AZzq6A9RINhZO5H16dQZngK7T62em8MUt1FLm52t+eX6xO".to_owned(); "sha384-H8BRh8j48O9oYatfu5AZzq6A9RINhZO5H16dQZngK7T62em8MUt1FLm52t+eX6xO".to_owned();
// Set the flag. // Set the flag.
request.local_urls_only = false; request.local_urls_only = false;
@ -586,7 +689,7 @@ fn test_fetch_with_sri_sucess() {
// To calulate hash use : // To calulate hash use :
// echo -n "alert('Hello, Network Error');" | openssl dgst -sha384 -binary | openssl base64 -A // echo -n "alert('Hello, Network Error');" | openssl dgst -sha384 -binary | openssl base64 -A
request.integrity_metadata = request.integrity_metadata =
"sha384-H8BRh8j48O9oYatfu5AZzq6A9RINhZO5H16dQZngK7T62em8MUt1FLm52t+eX6xO".to_owned(); "sha384-H8BRh8j48O9oYatfu5AZzq6A9RINhZO5H16dQZngK7T62em8MUt1FLm52t+eX6xO".to_owned();
// Set the flag. // Set the flag.
request.local_urls_only = false; request.local_urls_only = false;
@ -600,9 +703,7 @@ fn test_fetch_with_sri_sucess() {
#[test] #[test]
fn test_fetch_blocked_nosniff() { fn test_fetch_blocked_nosniff() {
#[inline] #[inline]
fn test_nosniff_request(destination: Destination, fn test_nosniff_request(destination: Destination, mime: Mime, should_error: bool) {
mime: Mime,
should_error: bool) {
const MESSAGE: &'static [u8] = b""; 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"; const VALUE: &'static [u8] = b"nosniff";
@ -612,7 +713,10 @@ fn test_fetch_blocked_nosniff() {
response.headers_mut().typed_insert(mime_header); response.headers_mut().typed_insert(mime_header);
assert!(response.headers().contains_key(header::CONTENT_TYPE)); assert!(response.headers().contains_key(header::CONTENT_TYPE));
// Add the nosniff header // Add the nosniff header
response.headers_mut().insert(HeaderName::from_static(HEADER), HeaderValue::from_bytes(VALUE).unwrap()); response.headers_mut().insert(
HeaderName::from_static(HEADER),
HeaderValue::from_bytes(VALUE).unwrap(),
);
*response.body_mut() = MESSAGE.to_vec().into(); *response.body_mut() = MESSAGE.to_vec().into();
}; };
@ -631,7 +735,7 @@ fn test_fetch_blocked_nosniff() {
let tests = vec![ let tests = vec![
(Destination::Script, mime::TEXT_JAVASCRIPT, false), (Destination::Script, mime::TEXT_JAVASCRIPT, false),
(Destination::Script, mime::TEXT_CSS, true), (Destination::Script, mime::TEXT_CSS, true),
(Destination::Style, mime::TEXT_CSS, false), (Destination::Style, mime::TEXT_CSS, false),
]; ];
for test in tests { for test in tests {
@ -642,14 +746,22 @@ fn test_fetch_blocked_nosniff() {
fn setup_server_and_fetch(message: &'static [u8], redirect_cap: u32) -> Response { fn setup_server_and_fetch(message: &'static [u8], redirect_cap: u32) -> Response {
let handler = move |request: HyperRequest<Body>, response: &mut HyperResponse<Body>| { let handler = move |request: HyperRequest<Body>, response: &mut HyperResponse<Body>| {
let redirects = request.uri().path().split("/").collect::<String>().parse::<u32>().unwrap_or(0); let redirects = request
.uri()
.path()
.split("/")
.collect::<String>()
.parse::<u32>()
.unwrap_or(0);
if redirects >= redirect_cap { if redirects >= redirect_cap {
*response.body_mut() = message.to_vec().into(); *response.body_mut() = message.to_vec().into();
} else { } else {
*response.status_mut() = StatusCode::FOUND; *response.status_mut() = StatusCode::FOUND;
let url = format!("{redirects}", redirects = redirects + 1); let url = format!("{redirects}", redirects = redirects + 1);
response.headers_mut().insert(header::LOCATION, HeaderValue::from_str(&url).unwrap()); response
.headers_mut()
.insert(header::LOCATION, HeaderValue::from_str(&url).unwrap());
} }
}; };
@ -678,7 +790,7 @@ fn test_fetch_redirect_count_ceiling() {
ResponseBody::Done(ref body) => { ResponseBody::Done(ref body) => {
assert_eq!(&**body, MESSAGE); assert_eq!(&**body, MESSAGE);
}, },
_ => panic!() _ => panic!(),
}; };
} }
@ -694,31 +806,43 @@ fn test_fetch_redirect_count_failure() {
match *fetch_response.body.lock().unwrap() { match *fetch_response.body.lock().unwrap() {
ResponseBody::Done(_) | ResponseBody::Receiving(_) => panic!(), ResponseBody::Done(_) | ResponseBody::Receiving(_) => panic!(),
_ => { } _ => {},
}; };
} }
fn test_fetch_redirect_updates_method_runner(tx: Sender<bool>, status_code: StatusCode, method: Method) { fn test_fetch_redirect_updates_method_runner(
tx: Sender<bool>,
status_code: StatusCode,
method: Method,
) {
let handler_method = method.clone(); let handler_method = method.clone();
let handler_tx = Arc::new(Mutex::new(tx)); let handler_tx = Arc::new(Mutex::new(tx));
let handler = move |request: HyperRequest<Body>, response: &mut HyperResponse<Body>| { let handler = move |request: HyperRequest<Body>, response: &mut HyperResponse<Body>| {
let redirects = request.uri().path().split("/").collect::<String>().parse::<u32>().unwrap_or(0); let redirects = request
.uri()
.path()
.split("/")
.collect::<String>()
.parse::<u32>()
.unwrap_or(0);
let mut test_pass = true; let mut test_pass = true;
if redirects == 0 { if redirects == 0 {
*response.status_mut() = StatusCode::TEMPORARY_REDIRECT; *response.status_mut() = StatusCode::TEMPORARY_REDIRECT;
response.headers_mut().insert(header::LOCATION, HeaderValue::from_static("1")); response
.headers_mut()
.insert(header::LOCATION, HeaderValue::from_static("1"));
} else if redirects == 1 { } else if redirects == 1 {
// this makes sure that the request method does't change from the wrong status code // 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; test_pass = false;
} }
*response.status_mut() = status_code; *response.status_mut() = status_code;
response.headers_mut().insert(header::LOCATION, HeaderValue::from_static("2")); 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; test_pass = false;
} }
@ -727,7 +851,6 @@ fn test_fetch_redirect_updates_method_runner(tx: Sender<bool>, status_code: Stat
if redirects > 0 { if redirects > 0 {
handler_tx.lock().unwrap().send(test_pass).unwrap(); handler_tx.lock().unwrap().send(test_pass).unwrap();
} }
}; };
let (server, url) = make_server(handler); let (server, url) = make_server(handler);
@ -745,7 +868,11 @@ fn test_fetch_redirect_updates_method_runner(tx: Sender<bool>, status_code: Stat
fn test_fetch_redirect_updates_method() { fn test_fetch_redirect_updates_method() {
let (tx, rx) = channel(); let (tx, rx) = channel();
test_fetch_redirect_updates_method_runner(tx.clone(), StatusCode::MOVED_PERMANENTLY, 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);
assert_eq!(rx.recv().unwrap(), true); assert_eq!(rx.recv().unwrap(), true);
// make sure the test doesn't send more data than expected // make sure the test doesn't send more data than expected
@ -763,7 +890,11 @@ fn test_fetch_redirect_updates_method() {
let extension = Method::from_bytes(b"FOO").unwrap(); let extension = Method::from_bytes(b"FOO").unwrap();
test_fetch_redirect_updates_method_runner(tx.clone(), StatusCode::MOVED_PERMANENTLY, extension.clone()); test_fetch_redirect_updates_method_runner(
tx.clone(),
StatusCode::MOVED_PERMANENTLY,
extension.clone(),
);
assert_eq!(rx.recv().unwrap(), true); assert_eq!(rx.recv().unwrap(), true);
// for MovedPermanently and Found, Method should only be changed if it was Post // for MovedPermanently and Found, Method should only be changed if it was Post
assert_eq!(rx.recv().unwrap(), false); assert_eq!(rx.recv().unwrap(), false);
@ -785,9 +916,9 @@ fn response_is_done(response: &Response) -> bool {
let response_complete = match response.response_type { let response_complete = match response.response_type {
ResponseType::Default | ResponseType::Basic | ResponseType::Cors => { ResponseType::Default | ResponseType::Basic | ResponseType::Cors => {
(*response.body.lock().unwrap()).is_done() (*response.body.lock().unwrap()).is_done()
} },
// if the internal response cannot have a body, it shouldn't block the "done" state // if the internal response cannot have a body, it shouldn't block the "done" state
ResponseType::Opaque | ResponseType::OpaqueRedirect | ResponseType::Error(..) => true ResponseType::Opaque | ResponseType::OpaqueRedirect | ResponseType::Error(..) => true,
}; };
let internal_complete = if let Some(ref res) = response.internal_response { let internal_complete = if let Some(ref res) = response.internal_response {
@ -842,13 +973,21 @@ fn test_opaque_filtered_fetch_async_returns_complete_response() {
fn test_opaque_redirect_filtered_fetch_async_returns_complete_response() { fn test_opaque_redirect_filtered_fetch_async_returns_complete_response() {
static MESSAGE: &'static [u8] = b""; static MESSAGE: &'static [u8] = b"";
let handler = move |request: HyperRequest<Body>, response: &mut HyperResponse<Body>| { let handler = move |request: HyperRequest<Body>, response: &mut HyperResponse<Body>| {
let redirects = request.uri().path().split("/").collect::<String>().parse::<u32>().unwrap_or(0); let redirects = request
.uri()
.path()
.split("/")
.collect::<String>()
.parse::<u32>()
.unwrap_or(0);
if redirects == 1 { if redirects == 1 {
*response.body_mut() = MESSAGE.to_vec().into(); *response.body_mut() = MESSAGE.to_vec().into();
} else { } else {
*response.status_mut() = StatusCode::FOUND; *response.status_mut() = StatusCode::FOUND;
response.headers_mut().insert(header::LOCATION, HeaderValue::from_static("1")); response
.headers_mut()
.insert(header::LOCATION, HeaderValue::from_static("1"));
} }
}; };
@ -892,13 +1031,22 @@ fn test_fetch_with_devtools() {
//Creating default headers for request //Creating default headers for request
let mut headers = HeaderMap::new(); let mut headers = HeaderMap::new();
headers.insert(header::ACCEPT_ENCODING, HeaderValue::from_static("gzip, deflate, br")); headers.insert(
headers.typed_insert( header::ACCEPT_ENCODING,
Host::from(format!("{}:{}", url.host_str().unwrap(), url.port().unwrap()).parse::<Authority>().unwrap())); 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("*/*")); headers.insert(header::ACCEPT, HeaderValue::from_static("*/*"));
headers.insert(header::ACCEPT_LANGUAGE, HeaderValue::from_static("en-US, en; q=0.5")); headers.insert(
header::ACCEPT_LANGUAGE,
HeaderValue::from_static("en-US, en; q=0.5"),
);
headers.typed_insert::<UserAgent>(DEFAULT_USER_AGENT.parse().unwrap()); headers.typed_insert::<UserAgent>(DEFAULT_USER_AGENT.parse().unwrap());
@ -918,7 +1066,11 @@ fn test_fetch_with_devtools() {
let content = "Yay!"; let content = "Yay!";
let mut response_headers = HeaderMap::new(); let mut response_headers = HeaderMap::new();
response_headers.typed_insert(ContentLength(content.len() as u64)); response_headers.typed_insert(ContentLength(content.len() as u64));
devhttpresponse.headers.as_mut().unwrap().remove(header::DATE); devhttpresponse
.headers
.as_mut()
.unwrap()
.remove(header::DATE);
let httpresponse = DevtoolsHttpResponse { let httpresponse = DevtoolsHttpResponse {
headers: Some(response_headers), headers: Some(response_headers),

View file

@ -16,14 +16,18 @@ use std::path::PathBuf;
#[test] #[test]
fn test_filemanager() { fn test_filemanager() {
let filemanager = FileManager::new(create_embedder_proxy()); let filemanager = FileManager::new(create_embedder_proxy());
PREFS.set("dom.testing.htmlinputelement.select_files.enabled", PrefValue::Boolean(true)); PREFS.set(
"dom.testing.htmlinputelement.select_files.enabled",
PrefValue::Boolean(true),
);
// Try to open a dummy file "components/net/tests/test.jpeg" in tree // Try to open a dummy file "components/net/tests/test.jpeg" in tree
let mut handler = File::open("tests/test.jpeg").expect("test.jpeg is stolen"); let mut handler = File::open("tests/test.jpeg").expect("test.jpeg is stolen");
let mut test_file_content = vec![]; let mut test_file_content = vec![];
handler.read_to_end(&mut test_file_content) handler
.expect("Read components/net/tests/test.jpeg error"); .read_to_end(&mut test_file_content)
.expect("Read components/net/tests/test.jpeg error");
let patterns = vec![FilterPattern(".txt".to_string())]; let patterns = vec![FilterPattern(".txt".to_string())];
let origin = "test.com".to_string(); let origin = "test.com".to_string();
@ -31,10 +35,16 @@ fn test_filemanager() {
{ {
// Try to select a dummy file "components/net/tests/test.jpeg" // Try to select a dummy file "components/net/tests/test.jpeg"
let (tx, rx) = ipc::channel().unwrap(); let (tx, rx) = ipc::channel().unwrap();
filemanager.handle(FileManagerThreadMsg::SelectFile(patterns.clone(), tx, origin.clone(), filemanager.handle(FileManagerThreadMsg::SelectFile(
Some("tests/test.jpeg".to_string()))); patterns.clone(),
let selected = rx.recv().expect("Broken channel") tx,
.expect("The file manager failed to find test.jpeg"); origin.clone(),
Some("tests/test.jpeg".to_string()),
));
let selected = rx
.recv()
.expect("Broken channel")
.expect("The file manager failed to find test.jpeg");
// Expecting attributes conforming the spec // Expecting attributes conforming the spec
assert_eq!(selected.filename, PathBuf::from("test.jpeg")); assert_eq!(selected.filename, PathBuf::from("test.jpeg"));
@ -43,24 +53,35 @@ fn test_filemanager() {
// Test by reading, expecting same content // Test by reading, expecting same content
{ {
let (tx2, rx2) = ipc::channel().unwrap(); let (tx2, rx2) = ipc::channel().unwrap();
filemanager.handle(FileManagerThreadMsg::ReadFile(tx2, selected.id.clone(), false, origin.clone())); filemanager.handle(FileManagerThreadMsg::ReadFile(
tx2,
selected.id.clone(),
false,
origin.clone(),
));
let msg = rx2.recv().expect("Broken channel"); let msg = rx2.recv().expect("Broken channel");
if let ReadFileProgress::Meta(blob_buf) = msg.expect("File manager reading failure is unexpected") { if let ReadFileProgress::Meta(blob_buf) =
msg.expect("File manager reading failure is unexpected")
{
let mut bytes = blob_buf.bytes; let mut bytes = blob_buf.bytes;
loop { loop {
match rx2.recv().expect("Broken channel").expect("File manager reading failure is unexpected") { match rx2
.recv()
.expect("Broken channel")
.expect("File manager reading failure is unexpected")
{
ReadFileProgress::Meta(_) => { ReadFileProgress::Meta(_) => {
panic!("Invalid FileManager reply"); panic!("Invalid FileManager reply");
} },
ReadFileProgress::Partial(mut bytes_in) => { ReadFileProgress::Partial(mut bytes_in) => {
bytes.append(&mut bytes_in); bytes.append(&mut bytes_in);
} },
ReadFileProgress::EOF => { ReadFileProgress::EOF => {
break; break;
} },
} }
} }
@ -73,7 +94,11 @@ fn test_filemanager() {
// Delete the id // Delete the id
{ {
let (tx2, rx2) = ipc::channel().unwrap(); let (tx2, rx2) = ipc::channel().unwrap();
filemanager.handle(FileManagerThreadMsg::DecRef(selected.id.clone(), origin.clone(), tx2)); filemanager.handle(FileManagerThreadMsg::DecRef(
selected.id.clone(),
origin.clone(),
tx2,
));
let ret = rx2.recv().expect("Broken channel"); let ret = rx2.recv().expect("Broken channel");
assert!(ret.is_ok(), "DecRef is not okay"); assert!(ret.is_ok(), "DecRef is not okay");
@ -82,15 +107,26 @@ fn test_filemanager() {
// Test by reading again, expecting read error because we invalidated the id // Test by reading again, expecting read error because we invalidated the id
{ {
let (tx2, rx2) = ipc::channel().unwrap(); let (tx2, rx2) = ipc::channel().unwrap();
filemanager.handle(FileManagerThreadMsg::ReadFile(tx2, selected.id.clone(), false, origin.clone())); filemanager.handle(FileManagerThreadMsg::ReadFile(
tx2,
selected.id.clone(),
false,
origin.clone(),
));
let msg = rx2.recv().expect("Broken channel"); let msg = rx2.recv().expect("Broken channel");
match msg { match msg {
Err(FileManagerThreadError::BlobURLStoreError(BlobURLStoreError::InvalidFileID)) => {}, Err(FileManagerThreadError::BlobURLStoreError(
BlobURLStoreError::InvalidFileID,
)) => {},
other => { other => {
assert!(false, "Get unexpected response after deleting the id: {:?}", other); assert!(
} false,
"Get unexpected response after deleting the id: {:?}",
other
);
},
} }
} }
} }

View file

@ -13,7 +13,7 @@ fn test_hsts_entry_is_not_expired_when_it_has_no_timestamp() {
host: "mozilla.org".to_owned(), host: "mozilla.org".to_owned(),
include_subdomains: false, include_subdomains: false,
max_age: Some(20), max_age: Some(20),
timestamp: None timestamp: None,
}; };
assert!(!entry.is_expired()); assert!(!entry.is_expired());
@ -25,7 +25,7 @@ fn test_hsts_entry_is_not_expired_when_it_has_no_max_age() {
host: "mozilla.org".to_owned(), host: "mozilla.org".to_owned(),
include_subdomains: false, include_subdomains: false,
max_age: None, max_age: None,
timestamp: Some(time::get_time().sec as u64) timestamp: Some(time::get_time().sec as u64),
}; };
assert!(!entry.is_expired()); assert!(!entry.is_expired());
@ -37,7 +37,7 @@ fn test_hsts_entry_is_expired_when_it_has_reached_its_max_age() {
host: "mozilla.org".to_owned(), host: "mozilla.org".to_owned(),
include_subdomains: false, include_subdomains: false,
max_age: Some(10), max_age: Some(10),
timestamp: Some(time::get_time().sec as u64 - 20u64) timestamp: Some(time::get_time().sec as u64 - 20u64),
}; };
assert!(entry.is_expired()); assert!(entry.is_expired());
@ -46,7 +46,9 @@ fn test_hsts_entry_is_expired_when_it_has_reached_its_max_age() {
#[test] #[test]
fn test_hsts_entry_cant_be_created_with_ipv6_address_as_host() { fn test_hsts_entry_cant_be_created_with_ipv6_address_as_host() {
let entry = HstsEntry::new( let entry = HstsEntry::new(
"2001:0db8:0000:0000:0000:ff00:0042:8329".to_owned(), IncludeSubdomains::NotIncluded, None "2001:0db8:0000:0000:0000:ff00:0042:8329".to_owned(),
IncludeSubdomains::NotIncluded,
None,
); );
assert!(entry.is_none(), "able to create HstsEntry with IPv6 host"); assert!(entry.is_none(), "able to create HstsEntry with IPv6 host");
@ -54,9 +56,7 @@ fn test_hsts_entry_cant_be_created_with_ipv6_address_as_host() {
#[test] #[test]
fn test_hsts_entry_cant_be_created_with_ipv4_address_as_host() { fn test_hsts_entry_cant_be_created_with_ipv4_address_as_host() {
let entry = HstsEntry::new( let entry = HstsEntry::new("4.4.4.4".to_owned(), IncludeSubdomains::NotIncluded, None);
"4.4.4.4".to_owned(), IncludeSubdomains::NotIncluded, None
);
assert!(entry.is_none(), "able to create HstsEntry with IPv4 host"); assert!(entry.is_none(), "able to create HstsEntry with IPv4 host");
} }
@ -66,15 +66,33 @@ fn test_base_domain_in_entries_map() {
let entries_map = HashMap::new(); let entries_map = HashMap::new();
let mut list = HstsList { let mut list = HstsList {
entries_map: entries_map entries_map: entries_map,
}; };
list.push(HstsEntry::new("servo.mozilla.org".to_owned(), list.push(
IncludeSubdomains::NotIncluded, None).unwrap()); HstsEntry::new(
list.push(HstsEntry::new("firefox.mozilla.org".to_owned(), "servo.mozilla.org".to_owned(),
IncludeSubdomains::NotIncluded, None).unwrap()); IncludeSubdomains::NotIncluded,
list.push(HstsEntry::new("bugzilla.org".to_owned(), None,
IncludeSubdomains::NotIncluded, None).unwrap()); )
.unwrap(),
);
list.push(
HstsEntry::new(
"firefox.mozilla.org".to_owned(),
IncludeSubdomains::NotIncluded,
None,
)
.unwrap(),
);
list.push(
HstsEntry::new(
"bugzilla.org".to_owned(),
IncludeSubdomains::NotIncluded,
None,
)
.unwrap(),
);
assert_eq!(list.entries_map.len(), 2); assert_eq!(list.entries_map.len(), 2);
assert_eq!(list.entries_map.get("mozilla.org").unwrap().len(), 2); assert_eq!(list.entries_map.get("mozilla.org").unwrap().len(), 2);
@ -83,14 +101,27 @@ fn test_base_domain_in_entries_map() {
#[test] #[test]
fn test_push_entry_with_0_max_age_evicts_entry_from_list() { fn test_push_entry_with_0_max_age_evicts_entry_from_list() {
let mut entries_map = HashMap::new(); let mut entries_map = HashMap::new();
entries_map.insert("mozilla.org".to_owned(), vec!(HstsEntry::new("mozilla.org".to_owned(), entries_map.insert(
IncludeSubdomains::NotIncluded, Some(500000u64)).unwrap())); "mozilla.org".to_owned(),
vec![HstsEntry::new(
"mozilla.org".to_owned(),
IncludeSubdomains::NotIncluded,
Some(500000u64),
)
.unwrap()],
);
let mut list = HstsList { let mut list = HstsList {
entries_map: entries_map entries_map: entries_map,
}; };
list.push(HstsEntry::new("mozilla.org".to_owned(), list.push(
IncludeSubdomains::NotIncluded, Some(0)).unwrap()); HstsEntry::new(
"mozilla.org".to_owned(),
IncludeSubdomains::NotIncluded,
Some(0),
)
.unwrap(),
);
assert_eq!(list.is_host_secure("mozilla.org"), false) assert_eq!(list.is_host_secure("mozilla.org"), false)
} }
@ -98,14 +129,22 @@ fn test_push_entry_with_0_max_age_evicts_entry_from_list() {
#[test] #[test]
fn test_push_entry_to_hsts_list_should_not_add_subdomains_whose_superdomain_is_already_matched() { fn test_push_entry_to_hsts_list_should_not_add_subdomains_whose_superdomain_is_already_matched() {
let mut entries_map = HashMap::new(); let mut entries_map = HashMap::new();
entries_map.insert("mozilla.org".to_owned(), vec!(HstsEntry::new("mozilla.org".to_owned(), entries_map.insert(
IncludeSubdomains::Included, None).unwrap())); "mozilla.org".to_owned(),
vec![HstsEntry::new("mozilla.org".to_owned(), IncludeSubdomains::Included, None).unwrap()],
);
let mut list = HstsList { let mut list = HstsList {
entries_map: entries_map entries_map: entries_map,
}; };
list.push(HstsEntry::new("servo.mozilla.org".to_owned(), list.push(
IncludeSubdomains::NotIncluded, None).unwrap()); HstsEntry::new(
"servo.mozilla.org".to_owned(),
IncludeSubdomains::NotIncluded,
None,
)
.unwrap(),
);
assert_eq!(list.entries_map.get("mozilla.org").unwrap().len(), 1) assert_eq!(list.entries_map.get("mozilla.org").unwrap().len(), 1)
} }
@ -113,16 +152,24 @@ fn test_push_entry_to_hsts_list_should_not_add_subdomains_whose_superdomain_is_a
#[test] #[test]
fn test_push_entry_to_hsts_list_should_update_existing_domain_entrys_include_subdomains() { fn test_push_entry_to_hsts_list_should_update_existing_domain_entrys_include_subdomains() {
let mut entries_map = HashMap::new(); let mut entries_map = HashMap::new();
entries_map.insert("mozilla.org".to_owned(), vec!(HstsEntry::new("mozilla.org".to_owned(), entries_map.insert(
IncludeSubdomains::Included, None).unwrap())); "mozilla.org".to_owned(),
vec![HstsEntry::new("mozilla.org".to_owned(), IncludeSubdomains::Included, None).unwrap()],
);
let mut list = HstsList { let mut list = HstsList {
entries_map: entries_map entries_map: entries_map,
}; };
assert!(list.is_host_secure("servo.mozilla.org")); assert!(list.is_host_secure("servo.mozilla.org"));
list.push(HstsEntry::new("mozilla.org".to_owned(), list.push(
IncludeSubdomains::NotIncluded, None).unwrap()); HstsEntry::new(
"mozilla.org".to_owned(),
IncludeSubdomains::NotIncluded,
None,
)
.unwrap(),
);
assert!(!list.is_host_secure("servo.mozilla.org")) assert!(!list.is_host_secure("servo.mozilla.org"))
} }
@ -130,14 +177,27 @@ fn test_push_entry_to_hsts_list_should_update_existing_domain_entrys_include_sub
#[test] #[test]
fn test_push_entry_to_hsts_list_should_not_create_duplicate_entry() { fn test_push_entry_to_hsts_list_should_not_create_duplicate_entry() {
let mut entries_map = HashMap::new(); let mut entries_map = HashMap::new();
entries_map.insert("mozilla.org".to_owned(), vec!(HstsEntry::new("mozilla.org".to_owned(), entries_map.insert(
IncludeSubdomains::NotIncluded, None).unwrap())); "mozilla.org".to_owned(),
vec![HstsEntry::new(
"mozilla.org".to_owned(),
IncludeSubdomains::NotIncluded,
None,
)
.unwrap()],
);
let mut list = HstsList { let mut list = HstsList {
entries_map: entries_map entries_map: entries_map,
}; };
list.push(HstsEntry::new("mozilla.org".to_owned(), list.push(
IncludeSubdomains::NotIncluded, None).unwrap()); HstsEntry::new(
"mozilla.org".to_owned(),
IncludeSubdomains::NotIncluded,
None,
)
.unwrap(),
);
assert_eq!(list.entries_map.get("mozilla.org").unwrap().len(), 1) assert_eq!(list.entries_map.get("mozilla.org").unwrap().len(), 1)
} }
@ -145,16 +205,16 @@ fn test_push_entry_to_hsts_list_should_not_create_duplicate_entry() {
#[test] #[test]
fn test_push_multiple_entrie_to_hsts_list_should_add_them_all() { fn test_push_multiple_entrie_to_hsts_list_should_add_them_all() {
let mut list = HstsList { let mut list = HstsList {
entries_map: HashMap::new() entries_map: HashMap::new(),
}; };
assert!(!list.is_host_secure("mozilla.org")); assert!(!list.is_host_secure("mozilla.org"));
assert!(!list.is_host_secure("bugzilla.org")); assert!(!list.is_host_secure("bugzilla.org"));
list.push(HstsEntry::new("mozilla.org".to_owned(), list.push(HstsEntry::new("mozilla.org".to_owned(), IncludeSubdomains::Included, None).unwrap());
IncludeSubdomains::Included, None).unwrap()); list.push(
list.push(HstsEntry::new("bugzilla.org".to_owned(), HstsEntry::new("bugzilla.org".to_owned(), IncludeSubdomains::Included, None).unwrap(),
IncludeSubdomains::Included, None).unwrap()); );
assert!(list.is_host_secure("mozilla.org")); assert!(list.is_host_secure("mozilla.org"));
assert!(list.is_host_secure("bugzilla.org")); assert!(list.is_host_secure("bugzilla.org"));
@ -163,13 +223,12 @@ fn test_push_multiple_entrie_to_hsts_list_should_add_them_all() {
#[test] #[test]
fn test_push_entry_to_hsts_list_should_add_an_entry() { fn test_push_entry_to_hsts_list_should_add_an_entry() {
let mut list = HstsList { let mut list = HstsList {
entries_map: HashMap::new() entries_map: HashMap::new(),
}; };
assert!(!list.is_host_secure("mozilla.org")); assert!(!list.is_host_secure("mozilla.org"));
list.push(HstsEntry::new("mozilla.org".to_owned(), list.push(HstsEntry::new("mozilla.org".to_owned(), IncludeSubdomains::Included, None).unwrap());
IncludeSubdomains::Included, None).unwrap());
assert!(list.is_host_secure("mozilla.org")); assert!(list.is_host_secure("mozilla.org"));
} }
@ -177,34 +236,43 @@ fn test_push_entry_to_hsts_list_should_add_an_entry() {
#[test] #[test]
fn test_parse_hsts_preload_should_return_none_when_json_invalid() { fn test_parse_hsts_preload_should_return_none_when_json_invalid() {
let mock_preload_content = "derp"; let mock_preload_content = "derp";
assert!(HstsList::from_preload(mock_preload_content).is_none(), "invalid preload list should not have parsed") assert!(
HstsList::from_preload(mock_preload_content).is_none(),
"invalid preload list should not have parsed"
)
} }
#[test] #[test]
fn test_parse_hsts_preload_should_return_none_when_json_contains_no_entries_map_key() { fn test_parse_hsts_preload_should_return_none_when_json_contains_no_entries_map_key() {
let mock_preload_content = "{\"nothing\": \"to see here\"}"; let mock_preload_content = "{\"nothing\": \"to see here\"}";
assert!(HstsList::from_preload(mock_preload_content).is_none(), "invalid preload list should not have parsed") assert!(
HstsList::from_preload(mock_preload_content).is_none(),
"invalid preload list should not have parsed"
)
} }
#[test] #[test]
fn test_parse_hsts_preload_should_decode_host_and_includes_subdomains() { fn test_parse_hsts_preload_should_decode_host_and_includes_subdomains() {
let mock_preload_content = "{\ let mock_preload_content = "{\
\"entries\": [\ \"entries\": [\
{\"host\": \"mozilla.org\",\ {\"host\": \"mozilla.org\",\
\"include_subdomains\": false}\ \"include_subdomains\": false}\
]\ ]\
}"; }";
let hsts_list = HstsList::from_preload(mock_preload_content); let hsts_list = HstsList::from_preload(mock_preload_content);
let entries_map = hsts_list.unwrap().entries_map; let entries_map = hsts_list.unwrap().entries_map;
assert_eq!(entries_map.get("mozilla.org").unwrap()[0].host, "mozilla.org"); assert_eq!(
entries_map.get("mozilla.org").unwrap()[0].host,
"mozilla.org"
);
assert!(!entries_map.get("mozilla.org").unwrap()[0].include_subdomains); assert!(!entries_map.get("mozilla.org").unwrap()[0].include_subdomains);
} }
#[test] #[test]
fn test_hsts_list_with_no_entries_map_does_not_is_host_secure() { fn test_hsts_list_with_no_entries_map_does_not_is_host_secure() {
let hsts_list = HstsList { let hsts_list = HstsList {
entries_map: HashMap::new() entries_map: HashMap::new(),
}; };
assert!(!hsts_list.is_host_secure("mozilla.org")); assert!(!hsts_list.is_host_secure("mozilla.org"));
@ -213,11 +281,18 @@ fn test_hsts_list_with_no_entries_map_does_not_is_host_secure() {
#[test] #[test]
fn test_hsts_list_with_exact_domain_entry_is_is_host_secure() { fn test_hsts_list_with_exact_domain_entry_is_is_host_secure() {
let mut entries_map = HashMap::new(); let mut entries_map = HashMap::new();
entries_map.insert("mozilla.org".to_owned(), vec![HstsEntry::new("mozilla.org".to_owned(), entries_map.insert(
IncludeSubdomains::NotIncluded, None).unwrap()]); "mozilla.org".to_owned(),
vec![HstsEntry::new(
"mozilla.org".to_owned(),
IncludeSubdomains::NotIncluded,
None,
)
.unwrap()],
);
let hsts_list = HstsList { let hsts_list = HstsList {
entries_map: entries_map entries_map: entries_map,
}; };
assert!(hsts_list.is_host_secure("mozilla.org")); assert!(hsts_list.is_host_secure("mozilla.org"));
@ -226,10 +301,12 @@ fn test_hsts_list_with_exact_domain_entry_is_is_host_secure() {
#[test] #[test]
fn test_hsts_list_with_subdomain_when_include_subdomains_is_true_is_is_host_secure() { fn test_hsts_list_with_subdomain_when_include_subdomains_is_true_is_is_host_secure() {
let mut entries_map = HashMap::new(); let mut entries_map = HashMap::new();
entries_map.insert("mozilla.org".to_owned(), vec![HstsEntry::new("mozilla.org".to_owned(), entries_map.insert(
IncludeSubdomains::Included, None).unwrap()]); "mozilla.org".to_owned(),
vec![HstsEntry::new("mozilla.org".to_owned(), IncludeSubdomains::Included, None).unwrap()],
);
let hsts_list = HstsList { let hsts_list = HstsList {
entries_map: entries_map entries_map: entries_map,
}; };
assert!(hsts_list.is_host_secure("servo.mozilla.org")); assert!(hsts_list.is_host_secure("servo.mozilla.org"));
@ -238,10 +315,17 @@ fn test_hsts_list_with_subdomain_when_include_subdomains_is_true_is_is_host_secu
#[test] #[test]
fn test_hsts_list_with_subdomain_when_include_subdomains_is_false_is_not_is_host_secure() { fn test_hsts_list_with_subdomain_when_include_subdomains_is_false_is_not_is_host_secure() {
let mut entries_map = HashMap::new(); let mut entries_map = HashMap::new();
entries_map.insert("mozilla.org".to_owned(), vec![HstsEntry::new("mozilla.org".to_owned(), entries_map.insert(
IncludeSubdomains::NotIncluded, None).unwrap()]); "mozilla.org".to_owned(),
vec![HstsEntry::new(
"mozilla.org".to_owned(),
IncludeSubdomains::NotIncluded,
None,
)
.unwrap()],
);
let hsts_list = HstsList { let hsts_list = HstsList {
entries_map: entries_map entries_map: entries_map,
}; };
assert!(!hsts_list.is_host_secure("servo.mozilla.org")); assert!(!hsts_list.is_host_secure("servo.mozilla.org"));
@ -250,10 +334,12 @@ fn test_hsts_list_with_subdomain_when_include_subdomains_is_false_is_not_is_host
#[test] #[test]
fn test_hsts_list_with_subdomain_when_host_is_not_a_subdomain_is_not_is_host_secure() { fn test_hsts_list_with_subdomain_when_host_is_not_a_subdomain_is_not_is_host_secure() {
let mut entries_map = HashMap::new(); let mut entries_map = HashMap::new();
entries_map.insert("mozilla.org".to_owned(), vec![HstsEntry::new("mozilla.org".to_owned(), entries_map.insert(
IncludeSubdomains::Included, None).unwrap()]); "mozilla.org".to_owned(),
vec![HstsEntry::new("mozilla.org".to_owned(), IncludeSubdomains::Included, None).unwrap()],
);
let hsts_list = HstsList { let hsts_list = HstsList {
entries_map: entries_map entries_map: entries_map,
}; };
assert!(!hsts_list.is_host_secure("servo-mozilla.org")); assert!(!hsts_list.is_host_secure("servo-mozilla.org"));
@ -262,10 +348,12 @@ fn test_hsts_list_with_subdomain_when_host_is_not_a_subdomain_is_not_is_host_sec
#[test] #[test]
fn test_hsts_list_with_subdomain_when_host_is_exact_match_is_is_host_secure() { fn test_hsts_list_with_subdomain_when_host_is_exact_match_is_is_host_secure() {
let mut entries_map = HashMap::new(); let mut entries_map = HashMap::new();
entries_map.insert("mozilla.org".to_owned(), vec![HstsEntry::new("mozilla.org".to_owned(), entries_map.insert(
IncludeSubdomains::Included, None).unwrap()]); "mozilla.org".to_owned(),
vec![HstsEntry::new("mozilla.org".to_owned(), IncludeSubdomains::Included, None).unwrap()],
);
let hsts_list = HstsList { let hsts_list = HstsList {
entries_map: entries_map entries_map: entries_map,
}; };
assert!(hsts_list.is_host_secure("mozilla.org")); assert!(hsts_list.is_host_secure("mozilla.org"));
@ -274,14 +362,17 @@ fn test_hsts_list_with_subdomain_when_host_is_exact_match_is_is_host_secure() {
#[test] #[test]
fn test_hsts_list_with_expired_entry_is_not_is_host_secure() { fn test_hsts_list_with_expired_entry_is_not_is_host_secure() {
let mut entries_map = HashMap::new(); let mut entries_map = HashMap::new();
entries_map.insert("mozilla.org".to_owned(), vec![HstsEntry { entries_map.insert(
"mozilla.org".to_owned(),
vec![HstsEntry {
host: "mozilla.org".to_owned(), host: "mozilla.org".to_owned(),
include_subdomains: false, include_subdomains: false,
max_age: Some(20), max_age: Some(20),
timestamp: Some(time::get_time().sec as u64 - 100u64) timestamp: Some(time::get_time().sec as u64 - 100u64),
}]); }],
);
let hsts_list = HstsList { let hsts_list = HstsList {
entries_map: entries_map entries_map: entries_map,
}; };
assert!(!hsts_list.is_host_secure("mozilla.org")); assert!(!hsts_list.is_host_secure("mozilla.org"));

File diff suppressed because it is too large Load diff

View file

@ -70,9 +70,7 @@ use tokio::runtime::Runtime;
use tokio_openssl::SslAcceptorExt; use tokio_openssl::SslAcceptorExt;
lazy_static! { lazy_static! {
pub static ref HANDLE: Mutex<Runtime> = { pub static ref HANDLE: Mutex<Runtime> = { Mutex::new(Runtime::new().unwrap()) };
Mutex::new(Runtime::new().unwrap())
};
} }
const DEFAULT_USER_AGENT: &'static str = "Such Browser. Very Layout. Wow."; const DEFAULT_USER_AGENT: &'static str = "Such Browser. Very Layout. Wow.";
@ -83,18 +81,17 @@ struct FetchResponseCollector {
fn create_embedder_proxy() -> EmbedderProxy { fn create_embedder_proxy() -> EmbedderProxy {
let (sender, _) = channel(); let (sender, _) = channel();
let event_loop_waker = | | { let event_loop_waker = || {
struct DummyEventLoopWaker { struct DummyEventLoopWaker {}
}
impl DummyEventLoopWaker { impl DummyEventLoopWaker {
fn new() -> DummyEventLoopWaker { fn new() -> DummyEventLoopWaker {
DummyEventLoopWaker { } DummyEventLoopWaker {}
} }
} }
impl EventLoopWaker for DummyEventLoopWaker { impl EventLoopWaker for DummyEventLoopWaker {
fn wake(&self) { } fn wake(&self) {}
fn clone(&self) -> Box<EventLoopWaker + Send> { fn clone(&self) -> Box<EventLoopWaker + Send> {
Box::new(DummyEventLoopWaker { }) Box::new(DummyEventLoopWaker {})
} }
} }
@ -103,12 +100,16 @@ fn create_embedder_proxy() -> EmbedderProxy {
EmbedderProxy { EmbedderProxy {
sender: sender, sender: sender,
event_loop_waker: event_loop_waker() event_loop_waker: event_loop_waker(),
} }
} }
fn new_fetch_context(dc: Option<Sender<DevtoolsControlMsg>>, fc: Option<EmbedderProxy>) -> FetchContext { fn new_fetch_context(
let ssl_connector = create_ssl_connector_builder(&resources::read_string(Resource::SSLCertificates)); dc: Option<Sender<DevtoolsControlMsg>>,
fc: Option<EmbedderProxy>,
) -> FetchContext {
let ssl_connector =
create_ssl_connector_builder(&resources::read_string(Resource::SSLCertificates));
let sender = fc.unwrap_or_else(|| create_embedder_proxy()); let sender = fc.unwrap_or_else(|| create_embedder_proxy());
FetchContext { FetchContext {
state: Arc::new(HttpState::new(ssl_connector)), state: Arc::new(HttpState::new(ssl_connector)),
@ -135,9 +136,7 @@ fn fetch(request: &mut Request, dc: Option<Sender<DevtoolsControlMsg>>) -> Respo
fn fetch_with_context(request: &mut Request, context: &FetchContext) -> Response { fn fetch_with_context(request: &mut Request, context: &FetchContext) -> Response {
let (sender, receiver) = channel(); let (sender, receiver) = channel();
let mut target = FetchResponseCollector { let mut target = FetchResponseCollector { sender: sender };
sender: sender,
};
methods::fetch(request, &mut target, context); methods::fetch(request, &mut target, context);
@ -146,9 +145,7 @@ fn fetch_with_context(request: &mut Request, context: &FetchContext) -> Response
fn fetch_with_cors_cache(request: &mut Request, cache: &mut CorsCache) -> Response { fn fetch_with_cors_cache(request: &mut Request, cache: &mut CorsCache) -> Response {
let (sender, receiver) = channel(); let (sender, receiver) = channel();
let mut target = FetchResponseCollector { let mut target = FetchResponseCollector { sender: sender };
sender: sender,
};
methods::fetch_with_cors_cache(request, cache, &mut target, &new_fetch_context(None, None)); methods::fetch_with_cors_cache(request, cache, &mut target, &new_fetch_context(None, None));
@ -166,7 +163,7 @@ impl Server {
} }
fn make_server<H>(handler: H) -> (Server, ServoUrl) fn make_server<H>(handler: H) -> (Server, ServoUrl)
where where
H: Fn(HyperRequest<Body>, &mut HyperResponse<Body>) + Send + Sync + 'static, H: Fn(HyperRequest<Body>, &mut HyperResponse<Body>) + Send + Sync + 'static,
{ {
let handler = Arc::new(handler); let handler = Arc::new(handler);
@ -174,18 +171,18 @@ fn make_server<H>(handler: H) -> (Server, ServoUrl)
let url_string = format!("http://localhost:{}", listener.local_addr().unwrap().port()); let url_string = format!("http://localhost:{}", listener.local_addr().unwrap().port());
let url = ServoUrl::parse(&url_string).unwrap(); let url = ServoUrl::parse(&url_string).unwrap();
let (tx, rx) = futures::sync::oneshot::channel::<()>(); let (tx, rx) = futures::sync::oneshot::channel::<()>();
let server = HyperServer::from_tcp(listener).unwrap().serve( let server = HyperServer::from_tcp(listener)
move || { .unwrap()
.serve(move || {
let handler = handler.clone(); let handler = handler.clone();
service_fn_ok(move |req: HyperRequest<Body>| { service_fn_ok(move |req: HyperRequest<Body>| {
let mut response = HyperResponse::new(Vec::<u8>::new().into()); let mut response = HyperResponse::new(Vec::<u8>::new().into());
handler(req, &mut response); handler(req, &mut response);
response response
}) })
} })
) .with_graceful_shutdown(rx)
.with_graceful_shutdown(rx) .map_err(|_| ());
.map_err(|_|());
HANDLE.lock().unwrap().spawn(server); HANDLE.lock().unwrap().spawn(server);
let server = Server { close_channel: tx }; let server = Server { close_channel: tx };
@ -193,7 +190,7 @@ fn make_server<H>(handler: H) -> (Server, ServoUrl)
} }
fn make_ssl_server<H>(handler: H, cert_path: PathBuf, key_path: PathBuf) -> (Server, ServoUrl) fn make_ssl_server<H>(handler: H, cert_path: PathBuf, key_path: PathBuf) -> (Server, ServoUrl)
where where
H: Fn(HyperRequest<Body>, &mut HyperResponse<Body>) + Send + Sync + 'static, H: Fn(HyperRequest<Body>, &mut HyperResponse<Body>) + Send + Sync + 'static,
{ {
let handler = Arc::new(handler); let handler = Arc::new(handler);
@ -202,28 +199,39 @@ fn make_ssl_server<H>(handler: H, cert_path: PathBuf, key_path: PathBuf) -> (Ser
let url_string = format!("http://localhost:{}", listener.local_addr().unwrap().port()); let url_string = format!("http://localhost:{}", listener.local_addr().unwrap().port());
let url = ServoUrl::parse(&url_string).unwrap(); let url = ServoUrl::parse(&url_string).unwrap();
let server = listener.incoming() let server = listener.incoming().map_err(|_| ()).for_each(move |sock| {
.map_err(|_| ()) let mut ssl_builder = SslAcceptor::mozilla_modern(SslMethod::tls()).unwrap();
.for_each(move |sock| { ssl_builder
let mut ssl_builder = SslAcceptor::mozilla_modern(SslMethod::tls()).unwrap(); .set_certificate_file(&cert_path, SslFiletype::PEM)
ssl_builder.set_certificate_file(&cert_path, SslFiletype::PEM).unwrap(); .unwrap();
ssl_builder.set_private_key_file(&key_path, SslFiletype::PEM).unwrap(); ssl_builder
.set_private_key_file(&key_path, SslFiletype::PEM)
.unwrap();
let handler = handler.clone(); let handler = handler.clone();
ssl_builder.build().accept_async(sock).map_err(|_| ()).and_then(move |ssl| { ssl_builder
Http::new().serve_connection(ssl, .build()
.accept_async(sock)
.map_err(|_| ())
.and_then(move |ssl| {
Http::new()
.serve_connection(
ssl,
service_fn_ok(move |req: HyperRequest<Body>| { service_fn_ok(move |req: HyperRequest<Body>| {
let mut response = HyperResponse::new(Vec::<u8>::new().into()); let mut response = HyperResponse::new(Vec::<u8>::new().into());
handler(req, &mut response); handler(req, &mut response);
response response
}) }),
) )
.map_err(|_|()) .map_err(|_| ())
}) })
}); });
let (tx, rx) = futures::sync::oneshot::channel::<()>(); let (tx, rx) = futures::sync::oneshot::channel::<()>();
let server = server.select(rx.map_err(|_| ())).map(|_| ()).map_err(|_| ()); let server = server
.select(rx.map_err(|_| ()))
.map(|_| ())
.map_err(|_| ());
HANDLE.lock().unwrap().spawn(server); HANDLE.lock().unwrap().spawn(server);

View file

@ -33,7 +33,7 @@ fn test_sniff_mp4_matcher() {
panic!("Didn't read mime type") panic!("Didn't read mime type")
} }
}, },
Err(e) => panic!("Couldn't read from file with error {}", e) Err(e) => panic!("Couldn't read from file with error {}", e),
} }
} }
@ -43,9 +43,9 @@ fn test_sniff_mp4_matcher_long() {
let matcher = Mp4Matcher; let matcher = Mp4Matcher;
let mut data: [u8; 260] = [0; 260]; let mut data: [u8; 260] = [0; 260];
&data[.. 11].clone_from_slice( &data[..11].clone_from_slice(&[
&[0x00, 0x00, 0x01, 0x04, 0x66, 0x74, 0x79, 0x70, 0x6D, 0x70, 0x34] 0x00, 0x00, 0x01, 0x04, 0x66, 0x74, 0x79, 0x70, 0x6D, 0x70, 0x34,
); ]);
assert!(matcher.matches(&data)); assert!(matcher.matches(&data));
} }
@ -57,13 +57,18 @@ fn test_validate_classifier() {
} }
#[cfg(test)] #[cfg(test)]
fn test_sniff_with_flags(filename_orig: &path::Path, fn test_sniff_with_flags(
expected_mime: Mime, filename_orig: &path::Path,
supplied_type: Option<Mime>, expected_mime: Mime,
no_sniff_flag: NoSniffFlag, supplied_type: Option<Mime>,
apache_bug_flag: ApacheBugFlag) { no_sniff_flag: NoSniffFlag,
apache_bug_flag: ApacheBugFlag,
) {
let current_working_directory = env::current_dir().unwrap(); let current_working_directory = env::current_dir().unwrap();
println!("The current directory is {}", current_working_directory.display()); println!(
"The current directory is {}",
current_working_directory.display()
);
let mut filename = PathBuf::from("tests/parsable_mime/"); let mut filename = PathBuf::from("tests/parsable_mime/");
filename.push(filename_orig); filename.push(filename_orig);
@ -74,30 +79,35 @@ fn test_sniff_with_flags(filename_orig: &path::Path,
match read_result { match read_result {
Ok(data) => { Ok(data) => {
let parsed_mime = classifier.classify(LoadContext::Browsing, let parsed_mime = classifier.classify(
no_sniff_flag, LoadContext::Browsing,
apache_bug_flag, no_sniff_flag,
&supplied_type, apache_bug_flag,
&data); &supplied_type,
&data,
);
if (parsed_mime.type_() != expected_mime.type_()) || if (parsed_mime.type_() != expected_mime.type_()) ||
(parsed_mime.subtype() != expected_mime.subtype()) { (parsed_mime.subtype() != expected_mime.subtype())
panic!("File {:?} parsed incorrectly should be {:?}, parsed as {:?}", {
filename, expected_mime, parsed_mime); panic!(
"File {:?} parsed incorrectly should be {:?}, parsed as {:?}",
filename, expected_mime, parsed_mime
);
} }
} },
Err(e) => panic!("Couldn't read from file {:?} with error {}", Err(e) => panic!("Couldn't read from file {:?} with error {}", filename, e),
filename, e),
} }
} }
#[cfg(test)] #[cfg(test)]
fn test_sniff_full(filename_orig: &path::Path, expected_mime: Mime, fn test_sniff_full(filename_orig: &path::Path, expected_mime: Mime, supplied_type: Option<Mime>) {
supplied_type: Option<Mime>) { test_sniff_with_flags(
test_sniff_with_flags(filename_orig, filename_orig,
expected_mime, expected_mime,
supplied_type, supplied_type,
NoSniffFlag::Off, NoSniffFlag::Off,
ApacheBugFlag::Off) ApacheBugFlag::Off,
)
} }
#[cfg(test)] #[cfg(test)]
@ -198,32 +208,50 @@ fn test_sniff_wave() {
#[test] #[test]
fn test_sniff_ogg() { fn test_sniff_ogg() {
test_sniff_classification("small.ogg", "application/ogg".parse().unwrap(), None); test_sniff_classification("small.ogg", "application/ogg".parse().unwrap(), None);
test_sniff_classification("small.ogg", "application/ogg".parse().unwrap(), Some("audio/".parse().unwrap())); test_sniff_classification(
"small.ogg",
"application/ogg".parse().unwrap(),
Some("audio/".parse().unwrap()),
);
} }
#[test] #[test]
#[should_panic] #[should_panic]
fn test_sniff_vsn_ms_fontobject() { fn test_sniff_vsn_ms_fontobject() {
test_sniff_classification_sup("vnd.ms-fontobject", "application/vnd.ms-fontobject".parse().unwrap()); test_sniff_classification_sup(
"vnd.ms-fontobject",
"application/vnd.ms-fontobject".parse().unwrap(),
);
} }
#[test] #[test]
#[should_panic] #[should_panic]
fn test_sniff_true_type() { fn test_sniff_true_type() {
test_sniff_full(&PathBuf::from("unknown/true_type.ttf"), "(TrueType)/".parse().unwrap(), None); test_sniff_full(
&PathBuf::from("unknown/true_type.ttf"),
"(TrueType)/".parse().unwrap(),
None,
);
} }
#[test] #[test]
#[should_panic] #[should_panic]
fn test_sniff_open_type() { fn test_sniff_open_type() {
test_sniff_full(&PathBuf::from("unknown/open_type"), "(OpenType)/".parse().unwrap(), None); test_sniff_full(
&PathBuf::from("unknown/open_type"),
"(OpenType)/".parse().unwrap(),
None,
);
} }
#[test] #[test]
#[should_panic] #[should_panic]
fn test_sniff_true_type_collection() { fn test_sniff_true_type_collection() {
test_sniff_full(&PathBuf::from("unknown/true_type_collection.ttc"), "(TrueType Collection)/".parse().unwrap(), test_sniff_full(
None); &PathBuf::from("unknown/true_type_collection.ttc"),
"(TrueType Collection)/".parse().unwrap(),
None,
);
} }
#[test] #[test]
@ -244,7 +272,11 @@ fn test_sniff_zip() {
#[test] #[test]
fn test_sniff_rar() { fn test_sniff_rar() {
test_sniff_classification("test.rar", "application/x-rar-compressed".parse().unwrap(), None); test_sniff_classification(
"test.rar",
"application/x-rar-compressed".parse().unwrap(),
None,
);
} }
#[test] #[test]
@ -466,86 +498,130 @@ fn test_sniff_utf_8_bom() {
#[test] #[test]
fn test_sniff_rss_feed() { fn test_sniff_rss_feed() {
// RSS feeds // RSS feeds
test_sniff_full(&PathBuf::from("text/xml/feed.rss"), "application/rss+xml".parse().unwrap(), Some(mime::TEXT_HTML)); test_sniff_full(
test_sniff_full(&PathBuf::from("text/xml/rdf_rss.xml"), "application/rss+xml".parse().unwrap(), &PathBuf::from("text/xml/feed.rss"),
Some(mime::TEXT_HTML)); "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 // Not RSS feeds
test_sniff_full(&PathBuf::from("text/xml/rdf_rss_ko_1.xml"), mime::TEXT_HTML, Some(mime::TEXT_HTML)); test_sniff_full(
test_sniff_full(&PathBuf::from("text/xml/rdf_rss_ko_2.xml"), mime::TEXT_HTML, Some(mime::TEXT_HTML)); &PathBuf::from("text/xml/rdf_rss_ko_1.xml"),
test_sniff_full(&PathBuf::from("text/xml/rdf_rss_ko_3.xml"), mime::TEXT_HTML, Some(mime::TEXT_HTML)); mime::TEXT_HTML,
test_sniff_full(&PathBuf::from("text/xml/rdf_rss_ko_4.xml"), mime::TEXT_HTML, Some(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] #[test]
fn test_sniff_atom_feed() { fn test_sniff_atom_feed() {
test_sniff_full(&PathBuf::from("text/xml/feed.atom"), "application/atom+xml".parse().unwrap(), test_sniff_full(
Some(mime::TEXT_HTML)); &PathBuf::from("text/xml/feed.atom"),
"application/atom+xml".parse().unwrap(),
Some(mime::TEXT_HTML),
);
} }
#[test] #[test]
fn test_sniff_binary_file() { fn test_sniff_binary_file() {
test_sniff_full(&PathBuf::from("unknown/binary_file"), mime::APPLICATION_OCTET_STREAM, None); test_sniff_full(
&PathBuf::from("unknown/binary_file"),
mime::APPLICATION_OCTET_STREAM,
None,
);
} }
#[test] #[test]
fn test_sniff_atom_feed_with_no_sniff_flag_on() { fn test_sniff_atom_feed_with_no_sniff_flag_on() {
test_sniff_with_flags(&PathBuf::from("text/xml/feed.atom"), test_sniff_with_flags(
mime::TEXT_HTML, &PathBuf::from("text/xml/feed.atom"),
Some(mime::TEXT_HTML), mime::TEXT_HTML,
NoSniffFlag::On, Some(mime::TEXT_HTML),
ApacheBugFlag::Off); NoSniffFlag::On,
ApacheBugFlag::Off,
);
} }
#[test] #[test]
fn test_sniff_with_no_sniff_flag_on_and_apache_flag_on() { fn test_sniff_with_no_sniff_flag_on_and_apache_flag_on() {
test_sniff_with_flags(&PathBuf::from("text/xml/feed.atom"), test_sniff_with_flags(
mime::TEXT_HTML, &PathBuf::from("text/xml/feed.atom"),
Some(mime::TEXT_HTML), mime::TEXT_HTML,
NoSniffFlag::On, Some(mime::TEXT_HTML),
ApacheBugFlag::On); NoSniffFlag::On,
ApacheBugFlag::On,
);
} }
#[test] #[test]
fn test_sniff_utf_8_bom_with_apache_flag_on() { fn test_sniff_utf_8_bom_with_apache_flag_on() {
test_sniff_with_flags(&PathBuf::from("text/plain/utf8bom.txt"), test_sniff_with_flags(
mime::TEXT_PLAIN, &PathBuf::from("text/plain/utf8bom.txt"),
Some("dummy/text".parse().unwrap()), mime::TEXT_PLAIN,
NoSniffFlag::Off, Some("dummy/text".parse().unwrap()),
ApacheBugFlag::On); NoSniffFlag::Off,
ApacheBugFlag::On,
);
} }
#[test] #[test]
fn test_sniff_utf_16be_bom_with_apache_flag_on() { fn test_sniff_utf_16be_bom_with_apache_flag_on() {
test_sniff_with_flags(&PathBuf::from("text/plain/utf16bebom.txt"), test_sniff_with_flags(
mime::TEXT_PLAIN, &PathBuf::from("text/plain/utf16bebom.txt"),
Some("dummy/text".parse().unwrap()), mime::TEXT_PLAIN,
NoSniffFlag::Off, Some("dummy/text".parse().unwrap()),
ApacheBugFlag::On); NoSniffFlag::Off,
ApacheBugFlag::On,
);
} }
#[test] #[test]
fn test_sniff_utf_16le_bom_with_apache_flag_on() { fn test_sniff_utf_16le_bom_with_apache_flag_on() {
test_sniff_with_flags(&PathBuf::from("text/plain/utf16lebom.txt"), test_sniff_with_flags(
mime::TEXT_PLAIN, &PathBuf::from("text/plain/utf16lebom.txt"),
Some("dummy/text".parse().unwrap()), mime::TEXT_PLAIN,
NoSniffFlag::Off, Some("dummy/text".parse().unwrap()),
ApacheBugFlag::On); NoSniffFlag::Off,
ApacheBugFlag::On,
);
} }
#[test] #[test]
fn test_sniff_octet_stream_apache_flag_on() { fn test_sniff_octet_stream_apache_flag_on() {
test_sniff_with_flags(&PathBuf::from("unknown/binary_file"), test_sniff_with_flags(
mime::APPLICATION_OCTET_STREAM, &PathBuf::from("unknown/binary_file"),
Some("dummy/binary".parse().unwrap()), mime::APPLICATION_OCTET_STREAM,
NoSniffFlag::Off, Some("dummy/binary".parse().unwrap()),
ApacheBugFlag::On); NoSniffFlag::Off,
ApacheBugFlag::On,
);
} }
#[test] #[test]
fn test_sniff_mp4_video_apache_flag_on() { fn test_sniff_mp4_video_apache_flag_on() {
test_sniff_with_flags(&PathBuf::from("video/mp4/test.mp4"), test_sniff_with_flags(
mime::APPLICATION_OCTET_STREAM, &PathBuf::from("video/mp4/test.mp4"),
Some("video/mp4".parse().unwrap()), mime::APPLICATION_OCTET_STREAM,
NoSniffFlag::Off, Some("video/mp4".parse().unwrap()),
ApacheBugFlag::On); NoSniffFlag::Off,
ApacheBugFlag::On,
);
} }

View file

@ -21,7 +21,13 @@ fn test_exit() {
let (mtx, _mrx) = ipc::channel().unwrap(); let (mtx, _mrx) = ipc::channel().unwrap();
let (sender, receiver) = ipc::channel().unwrap(); let (sender, receiver) = ipc::channel().unwrap();
let (resource_thread, _private_resource_thread) = new_core_resource_thread( let (resource_thread, _private_resource_thread) = new_core_resource_thread(
"".into(), None, ProfilerChan(tx), MemProfilerChan(mtx), create_embedder_proxy(), None); "".into(),
None,
ProfilerChan(tx),
MemProfilerChan(mtx),
create_embedder_proxy(),
None,
);
resource_thread.send(CoreResourceMsg::Exit(sender)).unwrap(); resource_thread.send(CoreResourceMsg::Exit(sender)).unwrap();
receiver.recv().unwrap(); receiver.recv().unwrap();
} }
@ -32,12 +38,16 @@ fn test_parse_hostsfile() {
let hosts_table = parse_hostsfile(mock_hosts_file_content); let hosts_table = parse_hostsfile(mock_hosts_file_content);
assert_eq!(2, hosts_table.len()); assert_eq!(2, hosts_table.len());
assert_eq!(ip("127.0.0.1"), *hosts_table.get("foo.bar.com").unwrap()); assert_eq!(ip("127.0.0.1"), *hosts_table.get("foo.bar.com").unwrap());
assert_eq!(ip("127.0.0.2"), *hosts_table.get("servo.test.server").unwrap()); assert_eq!(
ip("127.0.0.2"),
*hosts_table.get("servo.test.server").unwrap()
);
} }
#[test] #[test]
fn test_parse_malformed_hostsfile() { fn test_parse_malformed_hostsfile() {
let mock_hosts_file_content = "malformed file\n127.0.0.1 foo.bar.com\nservo.test.server 127.0.0.1"; let mock_hosts_file_content =
"malformed file\n127.0.0.1 foo.bar.com\nservo.test.server 127.0.0.1";
let hosts_table = parse_hostsfile(mock_hosts_file_content); let hosts_table = parse_hostsfile(mock_hosts_file_content);
assert_eq!(1, hosts_table.len()); assert_eq!(1, hosts_table.len());
assert_eq!(ip("127.0.0.1"), *hosts_table.get("foo.bar.com").unwrap()); assert_eq!(ip("127.0.0.1"), *hosts_table.get("foo.bar.com").unwrap());
@ -45,7 +55,8 @@ fn test_parse_malformed_hostsfile() {
#[test] #[test]
fn test_parse_hostsfile_with_line_comment() { fn test_parse_hostsfile_with_line_comment() {
let mock_hosts_file_content = "# this is a line comment\n127.0.0.1 foo.bar.com\n# anothercomment"; let mock_hosts_file_content =
"# this is a line comment\n127.0.0.1 foo.bar.com\n# anothercomment";
let hosts_table = parse_hostsfile(mock_hosts_file_content); let hosts_table = parse_hostsfile(mock_hosts_file_content);
assert_eq!(1, hosts_table.len()); assert_eq!(1, hosts_table.len());
assert_eq!(ip("127.0.0.1"), *hosts_table.get("foo.bar.com").unwrap()); assert_eq!(ip("127.0.0.1"), *hosts_table.get("foo.bar.com").unwrap());
@ -53,11 +64,15 @@ fn test_parse_hostsfile_with_line_comment() {
#[test] #[test]
fn test_parse_hostsfile_with_end_of_line_comment() { fn test_parse_hostsfile_with_end_of_line_comment() {
let mock_hosts_file_content = "127.0.0.1 foo.bar.com # line ending comment\n127.0.0.2 servo.test.server #comment"; let mock_hosts_file_content =
"127.0.0.1 foo.bar.com # line ending comment\n127.0.0.2 servo.test.server #comment";
let hosts_table = parse_hostsfile(mock_hosts_file_content); let hosts_table = parse_hostsfile(mock_hosts_file_content);
assert_eq!(2, hosts_table.len()); assert_eq!(2, hosts_table.len());
assert_eq!(ip("127.0.0.1"), *hosts_table.get("foo.bar.com").unwrap()); assert_eq!(ip("127.0.0.1"), *hosts_table.get("foo.bar.com").unwrap());
assert_eq!(ip("127.0.0.2"), *hosts_table.get("servo.test.server").unwrap()); assert_eq!(
ip("127.0.0.2"),
*hosts_table.get("servo.test.server").unwrap()
);
} }
#[test] #[test]
@ -86,12 +101,14 @@ fn test_parse_hostsfile_with_tabs_instead_spaces() {
let hosts_table = parse_hostsfile(mock_hosts_file_content); let hosts_table = parse_hostsfile(mock_hosts_file_content);
assert_eq!(2, hosts_table.len()); assert_eq!(2, hosts_table.len());
assert_eq!(ip("127.0.0.1"), *hosts_table.get("foo.bar.com").unwrap()); assert_eq!(ip("127.0.0.1"), *hosts_table.get("foo.bar.com").unwrap());
assert_eq!(ip("127.0.0.2"), *hosts_table.get("servo.test.server").unwrap()); assert_eq!(
ip("127.0.0.2"),
*hosts_table.get("servo.test.server").unwrap()
);
} }
#[test] #[test]
fn test_parse_hostsfile_with_valid_ipv4_addresses() fn test_parse_hostsfile_with_valid_ipv4_addresses() {
{
let mock_hosts_file_content = let mock_hosts_file_content =
"255.255.255.255 foo.bar.com\n169.0.1.201 servo.test.server\n192.168.5.0 servo.foo.com"; "255.255.255.255 foo.bar.com\n169.0.1.201 servo.test.server\n192.168.5.0 servo.foo.com";
let hosts_table = parse_hostsfile(mock_hosts_file_content); let hosts_table = parse_hostsfile(mock_hosts_file_content);
@ -99,8 +116,7 @@ fn test_parse_hostsfile_with_valid_ipv4_addresses()
} }
#[test] #[test]
fn test_parse_hostsfile_with_invalid_ipv4_addresses() fn test_parse_hostsfile_with_invalid_ipv4_addresses() {
{
let mock_hosts_file_content = "256.255.255.255 foo.bar.com\n169.0.1000.201 servo.test.server \ let mock_hosts_file_content = "256.255.255.255 foo.bar.com\n169.0.1000.201 servo.test.server \
\n192.168.5.500 servo.foo.com\n192.abc.100.2 test.servo.com"; \n192.168.5.500 servo.foo.com\n192.abc.100.2 test.servo.com";
let hosts_table = parse_hostsfile(mock_hosts_file_content); let hosts_table = parse_hostsfile(mock_hosts_file_content);
@ -108,8 +124,7 @@ fn test_parse_hostsfile_with_invalid_ipv4_addresses()
} }
#[test] #[test]
fn test_parse_hostsfile_with_valid_ipv6_addresses() fn test_parse_hostsfile_with_valid_ipv6_addresses() {
{
let mock_hosts_file_content = "2001:0db8:0000:0000:0000:ff00:0042:8329 foo.bar.com\n\ let mock_hosts_file_content = "2001:0db8:0000:0000:0000:ff00:0042:8329 foo.bar.com\n\
2001:db8:0:0:0:ff00:42:8329 moz.foo.com\n\ 2001:db8:0:0:0:ff00:42:8329 moz.foo.com\n\
2001:db8::ff00:42:8329 foo.moz.com moz.moz.com\n\ 2001:db8::ff00:42:8329 foo.moz.com moz.moz.com\n\
@ -122,8 +137,7 @@ fn test_parse_hostsfile_with_valid_ipv6_addresses()
} }
#[test] #[test]
fn test_parse_hostsfile_with_invalid_ipv6_addresses() fn test_parse_hostsfile_with_invalid_ipv6_addresses() {
{
let mock_hosts_file_content = "12001:0db8:0000:0000:0000:ff00:0042:8329 foo.bar.com\n\ let mock_hosts_file_content = "12001:0db8:0000:0000:0000:ff00:0042:8329 foo.bar.com\n\
2001:zdb8:0:0:0:gg00:42:t329 moz.foo.com\n\ 2001:zdb8:0:0:0:gg00:42:t329 moz.foo.com\n\
2002:0DB8:85A3:0042:1000:8A2E:0370:7334/1289 baz3.bar.moz"; 2002:0DB8:85A3:0042:1000:8A2E:0370:7334/1289 baz3.bar.moz";
@ -132,14 +146,19 @@ fn test_parse_hostsfile_with_invalid_ipv6_addresses()
} }
#[test] #[test]
fn test_parse_hostsfile_with_end_of_line_whitespace() fn test_parse_hostsfile_with_end_of_line_whitespace() {
{
let mock_hosts_file_content = "127.0.0.1 foo.bar.com \n\ let mock_hosts_file_content = "127.0.0.1 foo.bar.com \n\
2001:db8:0:0:0:ff00:42:8329 moz.foo.com\n \ 2001:db8:0:0:0:ff00:42:8329 moz.foo.com\n \
127.0.0.2 servo.test.server "; 127.0.0.2 servo.test.server ";
let hosts_table = parse_hostsfile(mock_hosts_file_content); let hosts_table = parse_hostsfile(mock_hosts_file_content);
assert_eq!(3, hosts_table.len()); assert_eq!(3, hosts_table.len());
assert_eq!(ip("127.0.0.1"), *hosts_table.get("foo.bar.com").unwrap()); assert_eq!(ip("127.0.0.1"), *hosts_table.get("foo.bar.com").unwrap());
assert_eq!(ip("2001:db8:0:0:0:ff00:42:8329"), *hosts_table.get("moz.foo.com").unwrap()); assert_eq!(
assert_eq!(ip("127.0.0.2"), *hosts_table.get("servo.test.server").unwrap()); ip("2001:db8:0:0:0:ff00:42:8329"),
*hosts_table.get("moz.foo.com").unwrap()
);
assert_eq!(
ip("127.0.0.2"),
*hosts_table.get("servo.test.server").unwrap()
);
} }

View file

@ -72,7 +72,8 @@ fn test_response_integrity_valid() {
let url: ServoUrl = ServoUrl::parse("http://servo.org").unwrap(); let url: ServoUrl = ServoUrl::parse("http://servo.org").unwrap();
let response: Response = Response::new(url); let response: Response = Response::new(url);
let integrity_metadata = "sha384-H8BRh8j48O9oYatfu5AZzq6A9RINhZO5H16dQZngK7T62em8MUt1FLm52t+eX6xO"; let integrity_metadata =
"sha384-H8BRh8j48O9oYatfu5AZzq6A9RINhZO5H16dQZngK7T62em8MUt1FLm52t+eX6xO";
let response_body = "alert('Hello, world.');".to_owned().into_bytes(); let response_body = "alert('Hello, world.');".to_owned().into_bytes();
*response.body.lock().unwrap() = ResponseBody::Done(response_body); *response.body.lock().unwrap() = ResponseBody::Done(response_body);
@ -84,7 +85,8 @@ fn test_response_integrity_invalid() {
let url: ServoUrl = ServoUrl::parse("http://servo.org").unwrap(); let url: ServoUrl = ServoUrl::parse("http://servo.org").unwrap();
let response: Response = Response::new(url); let response: Response = Response::new(url);
let integrity_metadata = "sha256-H8BRh8j48O9oYatfu5AZzq6A9RINhZO5H16dQZngK7T62em8MUt1FLm52t+eX6xO"; let integrity_metadata =
"sha256-H8BRh8j48O9oYatfu5AZzq6A9RINhZO5H16dQZngK7T62em8MUt1FLm52t+eX6xO";
let response_body = "alert('Hello, world.');".to_owned().into_bytes(); let response_body = "alert('Hello, world.');".to_owned().into_bytes();
*response.body.lock().unwrap() = ResponseBody::Done(response_body); *response.body.lock().unwrap() = ResponseBody::Done(response_body);

View file

@ -51,20 +51,25 @@ impl<'a> Factory for Client<'a> {
} }
} }
impl<'a> Handler for Client<'a> { impl<'a> Handler for Client<'a> {
fn build_request(&mut self, url: &Url) -> WebSocketResult<Request> { fn build_request(&mut self, url: &Url) -> WebSocketResult<Request> {
let mut req = Request::from_url(url)?; let mut req = Request::from_url(url)?;
req.headers_mut().push(("Origin".to_string(), self.origin.as_bytes().to_owned())); req.headers_mut()
req.headers_mut().push(("Host".to_string(), format!("{}", self.host).as_bytes().to_owned())); .push(("Origin".to_string(), self.origin.as_bytes().to_owned()));
req.headers_mut().push((
"Host".to_string(),
format!("{}", self.host).as_bytes().to_owned(),
));
for protocol in self.protocols { for protocol in self.protocols {
req.add_protocol(protocol); req.add_protocol(protocol);
}; }
let mut cookie_jar = self.http_state.cookie_jar.write().unwrap(); let mut cookie_jar = self.http_state.cookie_jar.write().unwrap();
if let Some(cookie_list) = cookie_jar.cookies_for_url(self.resource_url, CookieSource::HTTP) { if let Some(cookie_list) = cookie_jar.cookies_for_url(self.resource_url, CookieSource::HTTP)
req.headers_mut().push(("Cookie".into(), cookie_list.as_bytes().to_owned())) {
req.headers_mut()
.push(("Cookie".into(), cookie_list.as_bytes().to_owned()))
} }
Ok(req) Ok(req)
@ -83,41 +88,48 @@ impl<'a> Handler for Client<'a> {
// TODO(eijebong): Replace thise once typed headers settled on a cookie impl // TODO(eijebong): Replace thise once typed headers settled on a cookie impl
for cookie in headers.get_all(header::SET_COOKIE) { for cookie in headers.get_all(header::SET_COOKIE) {
if let Ok(s) = cookie.to_str() { if let Ok(s) = cookie.to_str() {
if let Some(cookie) = Cookie::from_cookie_string(s.into(), self.resource_url, CookieSource::HTTP) { if let Some(cookie) =
Cookie::from_cookie_string(s.into(), self.resource_url, CookieSource::HTTP)
{
jar.push(cookie, self.resource_url, CookieSource::HTTP); jar.push(cookie, self.resource_url, CookieSource::HTTP);
} }
} }
} }
let _ = self.event_sender.send( let _ = self
WebSocketNetworkEvent::ConnectionEstablished { protocol_in_use: self.protocol_in_use.clone() }); .event_sender
.send(WebSocketNetworkEvent::ConnectionEstablished {
protocol_in_use: self.protocol_in_use.clone(),
});
Ok(()) Ok(())
} }
fn on_message(&mut self, message: Message) -> WebSocketResult<()> { fn on_message(&mut self, message: Message) -> WebSocketResult<()> {
let message = match message { let message = match message {
Message::Text(message) => MessageData::Text(message), Message::Text(message) => MessageData::Text(message),
Message::Binary(message) => MessageData::Binary(message), Message::Binary(message) => MessageData::Binary(message),
}; };
let _ = self.event_sender.send(WebSocketNetworkEvent::MessageReceived(message)); let _ = self
.event_sender
.send(WebSocketNetworkEvent::MessageReceived(message));
Ok(()) Ok(())
} }
fn on_error(&mut self, err: WebSocketError) { fn on_error(&mut self, err: WebSocketError) {
debug!("Error in WebSocket communication: {:?}", err); debug!("Error in WebSocket communication: {:?}", err);
let _ = self.event_sender.send(WebSocketNetworkEvent::Fail); let _ = self.event_sender.send(WebSocketNetworkEvent::Fail);
} }
fn on_response(&mut self, res: &WsResponse) -> WebSocketResult<()> { fn on_response(&mut self, res: &WsResponse) -> WebSocketResult<()> {
let protocol_in_use = res.protocol()?; let protocol_in_use = res.protocol()?;
if let Some(protocol_name) = protocol_in_use { if let Some(protocol_name) = protocol_in_use {
if !self.protocols.is_empty() && !self.protocols.iter().any(|p| protocol_name == (*p)) { if !self.protocols.is_empty() && !self.protocols.iter().any(|p| protocol_name == (*p)) {
let error = WebSocketError::new(WebSocketErrorKind::Protocol, let error = WebSocketError::new(
"Protocol in Use not in client-supplied protocol list"); WebSocketErrorKind::Protocol,
"Protocol in Use not in client-supplied protocol list",
);
return Err(error); return Err(error);
} }
self.protocol_in_use = Some(protocol_name.into()); self.protocol_in_use = Some(protocol_name.into());
@ -127,7 +139,10 @@ impl<'a> Handler for Client<'a> {
fn on_close(&mut self, code: CloseCode, reason: &str) { fn on_close(&mut self, code: CloseCode, reason: &str) {
debug!("Connection closing due to ({:?}) {}", code, reason); debug!("Connection closing due to ({:?}) {}", code, reason);
let _ = self.event_sender.send(WebSocketNetworkEvent::Close(Some(code.into()), reason.to_owned())); let _ = self.event_sender.send(WebSocketNetworkEvent::Close(
Some(code.into()),
reason.to_owned(),
));
} }
fn upgrade_ssl_client( fn upgrade_ssl_client(
@ -136,106 +151,120 @@ impl<'a> Handler for Client<'a> {
url: &Url, url: &Url,
) -> WebSocketResult<SslStream<TcpStream>> { ) -> WebSocketResult<SslStream<TcpStream>> {
let certs = match opts::get().certificate_path { let certs = match opts::get().certificate_path {
Some(ref path) => { Some(ref path) => fs::read_to_string(path).expect("Couldn't not find certificate file"),
fs::read_to_string(path).expect("Couldn't not find certificate file") None => resources::read_string(Resource::SSLCertificates),
}
None => {
resources::read_string(Resource::SSLCertificates)
},
}; };
let domain = self.resource_url.as_url().domain().ok_or(WebSocketError::new( let domain = self
WebSocketErrorKind::Protocol, .resource_url
format!("Unable to parse domain from {}. Needed for SSL.", url), .as_url()
))?; .domain()
.ok_or(WebSocketError::new(
WebSocketErrorKind::Protocol,
format!("Unable to parse domain from {}. Needed for SSL.", url),
))?;
let connector = create_ssl_connector_builder(&certs).build(); let connector = create_ssl_connector_builder(&certs).build();
connector.connect(domain, stream).map_err(WebSocketError::from) connector
.connect(domain, stream)
.map_err(WebSocketError::from)
} }
} }
pub fn init( pub fn init(
req_init: RequestInit, req_init: RequestInit,
resource_event_sender: IpcSender<WebSocketNetworkEvent>, resource_event_sender: IpcSender<WebSocketNetworkEvent>,
dom_action_receiver: IpcReceiver<WebSocketDomAction>, dom_action_receiver: IpcReceiver<WebSocketDomAction>,
http_state: Arc<HttpState> http_state: Arc<HttpState>,
) { ) {
thread::Builder::new().name(format!("WebSocket connection to {}", req_init.url)).spawn(move || { thread::Builder::new()
let protocols = match req_init.mode { .name(format!("WebSocket connection to {}", req_init.url))
RequestMode::WebSocket { protocols } => protocols.clone(), .spawn(move || {
_ => panic!("Received a RequestInit with a non-websocket mode in websocket_loader"), let protocols = match req_init.mode {
}; RequestMode::WebSocket { protocols } => protocols.clone(),
_ => panic!("Received a RequestInit with a non-websocket mode in websocket_loader"),
};
let scheme = req_init.url.scheme(); let scheme = req_init.url.scheme();
let mut req_url = req_init.url.clone(); let mut req_url = req_init.url.clone();
if scheme == "ws" { if scheme == "ws" {
req_url.as_mut_url().set_scheme("http").unwrap(); req_url.as_mut_url().set_scheme("http").unwrap();
} else if scheme == "wss" { } else if scheme == "wss" {
req_url.as_mut_url().set_scheme("https").unwrap(); req_url.as_mut_url().set_scheme("https").unwrap();
}
if should_be_blocked_due_to_bad_port(&req_url) {
debug!("Failed to establish a WebSocket connection: port blocked");
let _ = resource_event_sender.send(WebSocketNetworkEvent::Fail);
return;
}
let host = replace_host(req_init.url.host_str().unwrap());
let mut net_url = req_init.url.clone().into_url();
net_url.set_host(Some(&host)).unwrap();
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(),
host: &host,
protocols: &protocols,
http_state: &http_state,
resource_url: &req_init.url,
event_sender: &resource_event_sender,
protocol_in_use: None,
};
let mut ws = WebSocket::new(client).unwrap();
if let Err(e) = ws.connect(net_url) {
debug!("Failed to establish a WebSocket connection: {:?}", e);
return;
};
let ws_sender = ws.broadcaster();
let initiated_close = Arc::new(AtomicBool::new(false));
thread::spawn(move || {
while let Ok(dom_action) = dom_action_receiver.recv() {
match dom_action {
WebSocketDomAction::SendMessage(MessageData::Text(data)) => {
ws_sender.send(Message::text(data)).unwrap();
},
WebSocketDomAction::SendMessage(MessageData::Binary(data)) => {
ws_sender.send(Message::binary(data)).unwrap();
},
WebSocketDomAction::Close(code, reason) => {
if !initiated_close.fetch_or(true, Ordering::SeqCst) {
match code {
Some(code) => {
ws_sender.close_with_reason(code.into(), reason.unwrap_or("".to_owned())).unwrap()
},
None => ws_sender.close(CloseCode::Status).unwrap(),
};
}
},
}
} }
});
if let Err(e) = ws.run() { if should_be_blocked_due_to_bad_port(&req_url) {
debug!("Failed to run WebSocket: {:?}", e); debug!("Failed to establish a WebSocket connection: port blocked");
let _ = resource_event_sender.send(WebSocketNetworkEvent::Fail); let _ = resource_event_sender.send(WebSocketNetworkEvent::Fail);
}; return;
}).expect("Thread spawning failed"); }
let host = replace_host(req_init.url.host_str().unwrap());
let mut net_url = req_init.url.clone().into_url();
net_url.set_host(Some(&host)).unwrap();
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(),
host: &host,
protocols: &protocols,
http_state: &http_state,
resource_url: &req_init.url,
event_sender: &resource_event_sender,
protocol_in_use: None,
};
let mut ws = WebSocket::new(client).unwrap();
if let Err(e) = ws.connect(net_url) {
debug!("Failed to establish a WebSocket connection: {:?}", e);
return;
};
let ws_sender = ws.broadcaster();
let initiated_close = Arc::new(AtomicBool::new(false));
thread::spawn(move || {
while let Ok(dom_action) = dom_action_receiver.recv() {
match dom_action {
WebSocketDomAction::SendMessage(MessageData::Text(data)) => {
ws_sender.send(Message::text(data)).unwrap();
},
WebSocketDomAction::SendMessage(MessageData::Binary(data)) => {
ws_sender.send(Message::binary(data)).unwrap();
},
WebSocketDomAction::Close(code, reason) => {
if !initiated_close.fetch_or(true, Ordering::SeqCst) {
match code {
Some(code) => ws_sender
.close_with_reason(
code.into(),
reason.unwrap_or("".to_owned()),
)
.unwrap(),
None => ws_sender.close(CloseCode::Status).unwrap(),
};
}
},
}
}
});
if let Err(e) = ws.run() {
debug!("Failed to run WebSocket: {:?}", e);
let _ = resource_event_sender.send(WebSocketNetworkEvent::Fail);
};
})
.expect("Thread spawning failed");
} }