mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
Rustfmt net crate
This commit is contained in:
parent
ba1ed11ced
commit
2481ad25f8
30 changed files with 4957 additions and 2870 deletions
|
@ -19,16 +19,16 @@ use servo_url::ServoUrl;
|
|||
|
||||
/// https://fetch.spec.whatwg.org/#concept-basic-fetch (partial)
|
||||
// TODO: make async.
|
||||
pub fn load_blob_sync
|
||||
(url: ServoUrl,
|
||||
filemanager: FileManager)
|
||||
-> Result<(HeaderMap, Vec<u8>), NetworkError> {
|
||||
pub fn load_blob_sync(
|
||||
url: ServoUrl,
|
||||
filemanager: FileManager,
|
||||
) -> Result<(HeaderMap, Vec<u8>), NetworkError> {
|
||||
let (id, origin) = match parse_blob_url(&url) {
|
||||
Ok((id, origin)) => (id, origin),
|
||||
Err(()) => {
|
||||
let e = format!("Invalid blob URL format {:?}", url);
|
||||
return Err(NetworkError::Internal(e));
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let (sender, receiver) = ipc::channel().unwrap();
|
||||
|
@ -38,11 +38,13 @@ pub fn load_blob_sync
|
|||
let blob_buf = match receiver.recv().unwrap() {
|
||||
Ok(ReadFileProgress::Meta(blob_buf)) => blob_buf,
|
||||
Ok(_) => {
|
||||
return Err(NetworkError::Internal("Invalid filemanager reply".to_string()));
|
||||
}
|
||||
return Err(NetworkError::Internal(
|
||||
"Invalid filemanager reply".to_string(),
|
||||
));
|
||||
},
|
||||
Err(e) => {
|
||||
return Err(NetworkError::Internal(format!("{:?}", e)));
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
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();
|
||||
|
||||
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
|
||||
headers.insert(
|
||||
header::CONTENT_DISPOSITION,
|
||||
HeaderValue::from_bytes(
|
||||
format!("inline; {}",
|
||||
format!(
|
||||
"inline; {}",
|
||||
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 {
|
||||
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() {
|
||||
Ok(ReadFileProgress::Partial(ref mut new_bytes)) => {
|
||||
bytes.append(new_bytes);
|
||||
}
|
||||
},
|
||||
Ok(ReadFileProgress::EOF) => {
|
||||
return Ok((headers, bytes));
|
||||
}
|
||||
},
|
||||
Ok(_) => {
|
||||
return Err(NetworkError::Internal("Invalid filemanager reply".to_string()));
|
||||
}
|
||||
return Err(NetworkError::Internal(
|
||||
"Invalid filemanager reply".to_string(),
|
||||
));
|
||||
},
|
||||
Err(e) => {
|
||||
return Err(NetworkError::Internal(format!("{:?}", e)));
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ use tokio::prelude::future::Executor;
|
|||
pub const BUF_SIZE: usize = 32768;
|
||||
|
||||
pub struct HttpConnector {
|
||||
inner: HyperHttpConnector
|
||||
inner: HyperHttpConnector,
|
||||
}
|
||||
|
||||
impl HttpConnector {
|
||||
|
@ -28,9 +28,7 @@ impl HttpConnector {
|
|||
let mut inner = HyperHttpConnector::new(4);
|
||||
inner.enforce_http(false);
|
||||
inner.set_happy_eyeballs_timeout(None);
|
||||
HttpConnector {
|
||||
inner
|
||||
}
|
||||
HttpConnector { inner }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -60,10 +58,7 @@ impl WrappedBody {
|
|||
}
|
||||
|
||||
pub fn new_with_decoder(body: Body, decoder: Decoder) -> Self {
|
||||
WrappedBody {
|
||||
body,
|
||||
decoder,
|
||||
}
|
||||
WrappedBody { body, decoder }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -90,7 +85,7 @@ impl Stream for WrappedBody {
|
|||
let len = decoder.read(&mut buf).ok()?;
|
||||
buf.truncate(len);
|
||||
Some(buf.into())
|
||||
}
|
||||
},
|
||||
Decoder::Gzip(None) => {
|
||||
let mut buf = vec![0; BUF_SIZE];
|
||||
let mut decoder = GzDecoder::new(Cursor::new(chunk.into_bytes()));
|
||||
|
@ -98,21 +93,21 @@ impl Stream for WrappedBody {
|
|||
buf.truncate(len);
|
||||
self.decoder = Decoder::Gzip(Some(decoder));
|
||||
Some(buf.into())
|
||||
}
|
||||
},
|
||||
Decoder::Deflate(ref mut decoder) => {
|
||||
let mut buf = vec![0; BUF_SIZE];
|
||||
*decoder.get_mut() = Cursor::new(chunk.into_bytes());
|
||||
let len = decoder.read(&mut buf).ok()?;
|
||||
buf.truncate(len);
|
||||
Some(buf.into())
|
||||
}
|
||||
},
|
||||
Decoder::Brotli(ref mut decoder) => {
|
||||
let mut buf = vec![0; BUF_SIZE];
|
||||
decoder.get_mut().get_mut().extend(&chunk.into_bytes());
|
||||
let len = decoder.read(&mut buf).ok()?;
|
||||
buf.truncate(len);
|
||||
Some(buf.into())
|
||||
}
|
||||
},
|
||||
}
|
||||
} else {
|
||||
None
|
||||
|
@ -134,33 +129,46 @@ pub fn create_ssl_connector_builder(certs: &str) -> SslConnectorBuilder {
|
|||
let (cert, rest) = certs.split_at(index + token.len());
|
||||
certs = rest;
|
||||
let cert = x509::X509::from_pem(cert.as_bytes()).unwrap();
|
||||
ssl_connector_builder.cert_store_mut().add_cert(cert).or_else(|e| {
|
||||
let v: Option<Option<&str>> = e.errors().iter().nth(0).map(|e| e.reason());
|
||||
if v == Some(Some("cert already in hash table")) {
|
||||
warn!("Cert already in hash table. Ignoring.");
|
||||
// Ignore error X509_R_CERT_ALREADY_IN_HASH_TABLE which means the
|
||||
// certificate is already in the store.
|
||||
Ok(())
|
||||
} else {
|
||||
Err(e)
|
||||
}
|
||||
}).expect("could not set CA file");
|
||||
ssl_connector_builder
|
||||
.cert_store_mut()
|
||||
.add_cert(cert)
|
||||
.or_else(|e| {
|
||||
let v: Option<Option<&str>> = e.errors().iter().nth(0).map(|e| e.reason());
|
||||
if v == Some(Some("cert already in hash table")) {
|
||||
warn!("Cert already in hash table. Ignoring.");
|
||||
// Ignore error X509_R_CERT_ALREADY_IN_HASH_TABLE which means the
|
||||
// certificate is already in the store.
|
||||
Ok(())
|
||||
} else {
|
||||
Err(e)
|
||||
}
|
||||
})
|
||||
.expect("could not set CA file");
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
ssl_connector_builder.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
|
||||
.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
|
||||
}
|
||||
|
||||
pub fn create_http_client<E>(ssl_connector_builder: SslConnectorBuilder, executor: E)
|
||||
-> Client<Connector, WrappedBody>
|
||||
where
|
||||
E: Executor<Box<Future<Error=(), Item=()> + Send + 'static>> + Sync + Send + 'static
|
||||
pub fn create_http_client<E>(
|
||||
ssl_connector_builder: SslConnectorBuilder,
|
||||
executor: E,
|
||||
) -> Client<Connector, WrappedBody>
|
||||
where
|
||||
E: Executor<Box<Future<Error = (), Item = ()> + Send + 'static>> + Sync + Send + 'static,
|
||||
{
|
||||
let connector = HttpsConnector::with_connector(HttpConnector::new(), ssl_connector_builder).unwrap();
|
||||
Client::builder().http1_title_case_headers(true).executor(executor).build(connector)
|
||||
let connector =
|
||||
HttpsConnector::with_connector(HttpConnector::new(), ssl_connector_builder).unwrap();
|
||||
Client::builder()
|
||||
.http1_title_case_headers(true)
|
||||
.executor(executor)
|
||||
.build(connector)
|
||||
}
|
||||
|
||||
// The basic logic here is to prefer ciphers with ECDSA certificates, Forward
|
||||
|
|
|
@ -19,23 +19,32 @@ use time::{Tm, now, at, Duration};
|
|||
/// which cookie-rs and hyper's header parsing do not support.
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct Cookie {
|
||||
#[serde(deserialize_with = "hyper_serde::deserialize",
|
||||
serialize_with = "hyper_serde::serialize")]
|
||||
#[serde(
|
||||
deserialize_with = "hyper_serde::deserialize",
|
||||
serialize_with = "hyper_serde::serialize"
|
||||
)]
|
||||
pub cookie: cookie_rs::Cookie<'static>,
|
||||
pub host_only: bool,
|
||||
pub persistent: bool,
|
||||
#[serde(deserialize_with = "hyper_serde::deserialize",
|
||||
serialize_with = "hyper_serde::serialize")]
|
||||
#[serde(
|
||||
deserialize_with = "hyper_serde::deserialize",
|
||||
serialize_with = "hyper_serde::serialize"
|
||||
)]
|
||||
pub creation_time: Tm,
|
||||
#[serde(deserialize_with = "hyper_serde::deserialize",
|
||||
serialize_with = "hyper_serde::serialize")]
|
||||
#[serde(
|
||||
deserialize_with = "hyper_serde::deserialize",
|
||||
serialize_with = "hyper_serde::serialize"
|
||||
)]
|
||||
pub last_access: Tm,
|
||||
pub expiry_time: Option<Serde<Tm>>,
|
||||
}
|
||||
|
||||
impl Cookie {
|
||||
pub fn from_cookie_string(cookie_str: String, request: &ServoUrl,
|
||||
source: CookieSource) -> Option<Cookie> {
|
||||
pub fn from_cookie_string(
|
||||
cookie_str: String,
|
||||
request: &ServoUrl,
|
||||
source: CookieSource,
|
||||
) -> Option<Cookie> {
|
||||
cookie_rs::Cookie::parse(cookie_str)
|
||||
.ok()
|
||||
.map(|cookie| Cookie::new_wrapped(cookie, request, source))
|
||||
|
@ -43,15 +52,21 @@ impl Cookie {
|
|||
}
|
||||
|
||||
/// <http://tools.ietf.org/html/rfc6265#section-5.3>
|
||||
pub fn new_wrapped(mut cookie: cookie_rs::Cookie<'static>, request: &ServoUrl, source: CookieSource)
|
||||
-> Option<Cookie> {
|
||||
pub fn new_wrapped(
|
||||
mut cookie: cookie_rs::Cookie<'static>,
|
||||
request: &ServoUrl,
|
||||
source: CookieSource,
|
||||
) -> Option<Cookie> {
|
||||
// Step 3
|
||||
let (persistent, expiry_time) = match (cookie.max_age(), cookie.expires()) {
|
||||
(Some(max_age), _) => {
|
||||
(true, Some(at(now().to_timespec() + Duration::seconds(max_age.num_seconds()))))
|
||||
}
|
||||
(Some(max_age), _) => (
|
||||
true,
|
||||
Some(at(
|
||||
now().to_timespec() + Duration::seconds(max_age.num_seconds())
|
||||
)),
|
||||
),
|
||||
(_, Some(expires)) => (true, Some(expires)),
|
||||
_ => (false, None)
|
||||
_ => (false, None),
|
||||
};
|
||||
|
||||
let url_host = request.host_str().unwrap_or("").to_owned();
|
||||
|
@ -64,7 +79,7 @@ impl Cookie {
|
|||
if domain == url_host {
|
||||
domain = "".to_string();
|
||||
} else {
|
||||
return None
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -83,16 +98,18 @@ impl Cookie {
|
|||
|
||||
// Step 7
|
||||
let mut has_path_specified = true;
|
||||
let mut path = cookie.path().unwrap_or_else(|| {
|
||||
has_path_specified = false;
|
||||
""
|
||||
}).to_owned();
|
||||
let mut path = cookie
|
||||
.path()
|
||||
.unwrap_or_else(|| {
|
||||
has_path_specified = false;
|
||||
""
|
||||
})
|
||||
.to_owned();
|
||||
if path.chars().next() != Some('/') {
|
||||
path = Cookie::default_path(&request.path().to_owned()).to_string();
|
||||
}
|
||||
cookie.set_path(path);
|
||||
|
||||
|
||||
// Step 10
|
||||
if cookie.http_only().unwrap_or(false) && source == CookieSource::NonHTTP {
|
||||
return None;
|
||||
|
@ -101,14 +118,14 @@ impl Cookie {
|
|||
// https://tools.ietf.org/html/draft-west-cookie-prefixes-04#section-4
|
||||
// Step 1 of cookie prefixes
|
||||
if (cookie.name().starts_with("__Secure-") || cookie.name().starts_with("__Host-")) &&
|
||||
!(cookie.secure().unwrap_or(false) && request.is_secure_scheme())
|
||||
!(cookie.secure().unwrap_or(false) && request.is_secure_scheme())
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
// Step 2 of cookie prefixes
|
||||
if cookie.name().starts_with("__Host-") &&
|
||||
!(host_only && has_path_specified && cookie.path().unwrap() == "/")
|
||||
!(host_only && has_path_specified && cookie.path().unwrap() == "/")
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
@ -152,16 +169,16 @@ impl Cookie {
|
|||
|
||||
// The cookie-path and the request-path are identical.
|
||||
request_path == cookie_path ||
|
||||
|
||||
(request_path.starts_with(cookie_path) && (
|
||||
// The cookie-path is a prefix of the request-path, and the last
|
||||
// character of the cookie-path is %x2F ("/").
|
||||
cookie_path.ends_with("/") ||
|
||||
(request_path.starts_with(cookie_path) &&
|
||||
(
|
||||
// The cookie-path is a prefix of the request-path, and the last
|
||||
// character of the cookie-path is %x2F ("/").
|
||||
cookie_path.ends_with("/") ||
|
||||
// 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-
|
||||
// path is a %x2F ("/") character.
|
||||
request_path[cookie_path.len()..].starts_with("/")
|
||||
))
|
||||
))
|
||||
}
|
||||
|
||||
// http://tools.ietf.org/html/rfc6265#section-5.1.3
|
||||
|
@ -170,10 +187,10 @@ impl Cookie {
|
|||
let domain_string = &domain_string.to_lowercase();
|
||||
|
||||
string == domain_string ||
|
||||
(string.ends_with(domain_string) &&
|
||||
string.as_bytes()[string.len()-domain_string.len()-1] == b'.' &&
|
||||
string.parse::<Ipv4Addr>().is_err() &&
|
||||
string.parse::<Ipv6Addr>().is_err())
|
||||
(string.ends_with(domain_string) &&
|
||||
string.as_bytes()[string.len() - domain_string.len() - 1] == b'.' &&
|
||||
string.parse::<Ipv4Addr>().is_err() &&
|
||||
string.parse::<Ipv6Addr>().is_err())
|
||||
}
|
||||
|
||||
// http://tools.ietf.org/html/rfc6265#section-5.4 step 1
|
||||
|
|
|
@ -33,7 +33,12 @@ impl CookieStorage {
|
|||
}
|
||||
|
||||
// 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 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();
|
||||
|
||||
c.cookie.name() == cookie.cookie.name() &&
|
||||
c.cookie.secure().unwrap_or(false) &&
|
||||
(Cookie::domain_match(new_domain, existing_domain) ||
|
||||
Cookie::domain_match(existing_domain, new_domain)) &&
|
||||
Cookie::path_match(new_path, existing_path)
|
||||
c.cookie.secure().unwrap_or(false) &&
|
||||
(Cookie::domain_match(new_domain, existing_domain) ||
|
||||
Cookie::domain_match(existing_domain, new_domain)) &&
|
||||
Cookie::path_match(new_path, existing_path)
|
||||
});
|
||||
|
||||
if any_overlapping {
|
||||
|
@ -61,8 +66,8 @@ impl CookieStorage {
|
|||
// Step 11.1
|
||||
let position = cookies.iter().position(|c| {
|
||||
c.cookie.domain() == cookie.cookie.domain() &&
|
||||
c.cookie.path() == cookie.cookie.path() &&
|
||||
c.cookie.name() == cookie.cookie.name()
|
||||
c.cookie.path() == cookie.cookie.path() &&
|
||||
c.cookie.name() == cookie.cookie.name()
|
||||
});
|
||||
|
||||
if let Some(ind) = position {
|
||||
|
@ -111,7 +116,9 @@ impl CookieStorage {
|
|||
let new_len = cookies.len();
|
||||
|
||||
// https://www.ietf.org/id/draft-ietf-httpbis-cookie-alone-01.txt
|
||||
if new_len == old_len && !evict_one_cookie(cookie.cookie.secure().unwrap_or(false), cookies) {
|
||||
if new_len == old_len &&
|
||||
!evict_one_cookie(cookie.cookie.secure().unwrap_or(false), cookies)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -126,7 +133,7 @@ impl CookieStorage {
|
|||
let a_creation_time = a.creation_time.to_timespec();
|
||||
let b_creation_time = b.creation_time.to_timespec();
|
||||
a_creation_time.cmp(&b_creation_time)
|
||||
}
|
||||
},
|
||||
// Ensure that longer paths are sorted earlier than shorter paths
|
||||
Ordering::Greater => Ordering::Less,
|
||||
Ordering::Less => Ordering::Greater,
|
||||
|
@ -136,13 +143,17 @@ impl CookieStorage {
|
|||
// http://tools.ietf.org/html/rfc6265#section-5.4
|
||||
pub fn cookies_for_url(&mut self, url: &ServoUrl, source: CookieSource) -> Option<String> {
|
||||
let filterer = |c: &&mut Cookie| -> bool {
|
||||
info!(" === SENT COOKIE : {} {} {:?} {:?}",
|
||||
c.cookie.name(),
|
||||
c.cookie.value(),
|
||||
c.cookie.domain(),
|
||||
c.cookie.path());
|
||||
info!(" === SENT COOKIE RESULT {}",
|
||||
c.appropriate_for_url(url, source));
|
||||
info!(
|
||||
" === SENT COOKIE : {} {} {:?} {:?}",
|
||||
c.cookie.name(),
|
||||
c.cookie.value(),
|
||||
c.cookie.domain(),
|
||||
c.cookie.path()
|
||||
);
|
||||
info!(
|
||||
" === SENT COOKIE RESULT {}",
|
||||
c.appropriate_for_url(url, source)
|
||||
);
|
||||
// Step 1
|
||||
c.appropriate_for_url(url, source)
|
||||
};
|
||||
|
@ -161,7 +172,9 @@ impl CookieStorage {
|
|||
(match acc.len() {
|
||||
0 => acc,
|
||||
_ => acc + "; ",
|
||||
}) + &c.cookie.name() + "=" + &c.cookie.value()
|
||||
}) + &c.cookie.name() +
|
||||
"=" +
|
||||
&c.cookie.value()
|
||||
};
|
||||
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,
|
||||
url: &'a ServoUrl,
|
||||
source: CookieSource)
|
||||
-> impl Iterator<Item = cookie_rs::Cookie<'static>> + 'a {
|
||||
pub fn cookies_data_for_url<'a>(
|
||||
&'a mut self,
|
||||
url: &'a ServoUrl,
|
||||
source: CookieSource,
|
||||
) -> impl Iterator<Item = cookie_rs::Cookie<'static>> + 'a {
|
||||
let domain = reg_host(url.host_str().unwrap_or(""));
|
||||
let cookies = self.cookies_map.entry(domain).or_insert(vec![]);
|
||||
|
||||
cookies.iter_mut().filter(move |c| c.appropriate_for_url(url, source)).map(|c| {
|
||||
c.touch();
|
||||
c.cookie.clone()
|
||||
})
|
||||
cookies
|
||||
.iter_mut()
|
||||
.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;
|
||||
for (i, c) in cookies.iter().enumerate() {
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,9 @@ pub type DecodeData = (Mime, Vec<u8>);
|
|||
pub fn decode(url: &ServoUrl) -> Result<DecodeData, DecodeError> {
|
||||
assert_eq!(url.scheme(), "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 {
|
||||
return Err(DecodeError::InvalidDataUri);
|
||||
}
|
||||
|
@ -36,15 +38,18 @@ pub fn decode(url: &ServoUrl) -> Result<DecodeData, DecodeError> {
|
|||
ct_str.to_owned()
|
||||
};
|
||||
|
||||
let content_type = ct_str.parse().unwrap_or_else(|_| {
|
||||
"text/plain; charset=US-ASCII".parse().unwrap()
|
||||
});
|
||||
let content_type = ct_str
|
||||
.parse()
|
||||
.unwrap_or_else(|_| "text/plain; charset=US-ASCII".parse().unwrap());
|
||||
|
||||
let mut bytes = percent_decode(parts[1].as_bytes()).collect::<Vec<_>>();
|
||||
if is_base64 {
|
||||
// FIXME(#2909): It’s unclear what to do with non-alphabet characters,
|
||||
// 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) {
|
||||
Err(..) => return Err(DecodeError::NonBase64DataUri),
|
||||
Ok(data) => bytes = data,
|
||||
|
|
|
@ -21,21 +21,21 @@ use time::{self, Timespec};
|
|||
#[derive(Clone, Debug)]
|
||||
pub enum HeaderOrMethod {
|
||||
HeaderData(HeaderName),
|
||||
MethodData(Method)
|
||||
MethodData(Method),
|
||||
}
|
||||
|
||||
impl HeaderOrMethod {
|
||||
fn match_header(&self, header_name: &HeaderName) -> bool {
|
||||
match *self {
|
||||
HeaderOrMethod::HeaderData(ref n) => n == header_name,
|
||||
_ => false
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn match_method(&self, method: &Method) -> bool {
|
||||
match *self {
|
||||
HeaderOrMethod::MethodData(ref m) => m == method,
|
||||
_ => false
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -48,19 +48,24 @@ pub struct CorsCacheEntry {
|
|||
pub max_age: u32,
|
||||
pub credentials: bool,
|
||||
pub header_or_method: HeaderOrMethod,
|
||||
created: Timespec
|
||||
created: Timespec,
|
||||
}
|
||||
|
||||
impl CorsCacheEntry {
|
||||
fn new(origin: Origin, url: ServoUrl, max_age: u32, credentials: bool,
|
||||
header_or_method: HeaderOrMethod) -> CorsCacheEntry {
|
||||
fn new(
|
||||
origin: Origin,
|
||||
url: ServoUrl,
|
||||
max_age: u32,
|
||||
credentials: bool,
|
||||
header_or_method: HeaderOrMethod,
|
||||
) -> CorsCacheEntry {
|
||||
CorsCacheEntry {
|
||||
origin: origin,
|
||||
url: url,
|
||||
max_age: max_age,
|
||||
credentials: credentials,
|
||||
header_or_method: header_or_method,
|
||||
created: time::now().to_timespec()
|
||||
created: time::now().to_timespec(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -80,25 +85,36 @@ impl CorsCache {
|
|||
CorsCache(vec![])
|
||||
}
|
||||
|
||||
fn find_entry_by_header<'a>(&'a mut self, request: &Request,
|
||||
header_name: &HeaderName) -> Option<&'a mut CorsCacheEntry> {
|
||||
fn find_entry_by_header<'a>(
|
||||
&'a mut self,
|
||||
request: &Request,
|
||||
header_name: &HeaderName,
|
||||
) -> Option<&'a mut CorsCacheEntry> {
|
||||
self.cleanup();
|
||||
self.0.iter_mut().find(|e| match_headers(e, request) && e.header_or_method.match_header(header_name))
|
||||
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,
|
||||
method: Method) -> Option<&'a mut CorsCacheEntry> {
|
||||
fn find_entry_by_method<'a>(
|
||||
&'a mut self,
|
||||
request: &Request,
|
||||
method: Method,
|
||||
) -> Option<&'a mut CorsCacheEntry> {
|
||||
// we can take the method from CorSRequest itself
|
||||
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)
|
||||
pub fn clear(&mut self, request: &Request) {
|
||||
let CorsCache(buf) = self.clone();
|
||||
let new_buf: Vec<CorsCacheEntry> =
|
||||
buf.into_iter().filter(|e| e.origin == request.origin &&
|
||||
request.current_url() == e.url).collect();
|
||||
let new_buf: Vec<CorsCacheEntry> = buf
|
||||
.into_iter()
|
||||
.filter(|e| e.origin == request.origin && request.current_url() == e.url)
|
||||
.collect();
|
||||
*self = CorsCache(new_buf);
|
||||
}
|
||||
|
||||
|
@ -106,9 +122,10 @@ impl CorsCache {
|
|||
pub fn cleanup(&mut self) {
|
||||
let CorsCache(buf) = self.clone();
|
||||
let now = time::now().to_timespec();
|
||||
let new_buf: Vec<CorsCacheEntry> = buf.into_iter()
|
||||
.filter(|e| now.sec < e.created.sec + e.max_age as i64)
|
||||
.collect();
|
||||
let new_buf: Vec<CorsCacheEntry> = buf
|
||||
.into_iter()
|
||||
.filter(|e| now.sec < e.created.sec + e.max_age as i64)
|
||||
.collect();
|
||||
*self = CorsCache(new_buf);
|
||||
}
|
||||
|
||||
|
@ -122,16 +139,27 @@ impl CorsCache {
|
|||
/// [matching header](https://fetch.spec.whatwg.org/#concept-cache-match-header) is found.
|
||||
///
|
||||
/// If not, it will insert an equivalent entry
|
||||
pub fn match_header_and_update(&mut self, 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) {
|
||||
pub fn match_header_and_update(
|
||||
&mut self,
|
||||
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,
|
||||
None => {
|
||||
self.insert(CorsCacheEntry::new(request.origin.clone(), request.current_url(), new_max_age,
|
||||
request.credentials_mode == CredentialsMode::Include,
|
||||
HeaderOrMethod::HeaderData(header_name.clone())));
|
||||
self.insert(CorsCacheEntry::new(
|
||||
request.origin.clone(),
|
||||
request.current_url(),
|
||||
new_max_age,
|
||||
request.credentials_mode == CredentialsMode::Include,
|
||||
HeaderOrMethod::HeaderData(header_name.clone()),
|
||||
));
|
||||
false
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -145,15 +173,27 @@ impl CorsCache {
|
|||
/// [a matching method](https://fetch.spec.whatwg.org/#concept-cache-match-method) is found.
|
||||
///
|
||||
/// 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 {
|
||||
match self.find_entry_by_method(&request, method.clone()).map(|e| e.max_age = new_max_age) {
|
||||
pub fn match_method_and_update(
|
||||
&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,
|
||||
None => {
|
||||
self.insert(CorsCacheEntry::new(request.origin.clone(), request.current_url(), new_max_age,
|
||||
request.credentials_mode == CredentialsMode::Include,
|
||||
HeaderOrMethod::MethodData(method)));
|
||||
self.insert(CorsCacheEntry::new(
|
||||
request.origin.clone(),
|
||||
request.current_url(),
|
||||
new_max_age,
|
||||
request.credentials_mode == CredentialsMode::Include,
|
||||
HeaderOrMethod::MethodData(method),
|
||||
));
|
||||
false
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -35,7 +35,8 @@ use std::thread;
|
|||
use subresource_integrity::is_response_integrity_valid;
|
||||
|
||||
lazy_static! {
|
||||
static ref X_CONTENT_TYPE_OPTIONS: HeaderName = HeaderName::from_static("x-content-type-options");
|
||||
static ref X_CONTENT_TYPE_OPTIONS: HeaderName =
|
||||
HeaderName::from_static("x-content-type-options");
|
||||
}
|
||||
|
||||
const FILE_CHUNK_SIZE: usize = 32768; //32 KB
|
||||
|
@ -87,16 +88,16 @@ impl CancellationListener {
|
|||
pub type DoneChannel = Option<(Sender<Data>, Receiver<Data>)>;
|
||||
|
||||
/// [Fetch](https://fetch.spec.whatwg.org#concept-fetch)
|
||||
pub fn fetch(request: &mut Request,
|
||||
target: Target,
|
||||
context: &FetchContext) {
|
||||
pub fn fetch(request: &mut Request, target: Target, context: &FetchContext) {
|
||||
fetch_with_cors_cache(request, &mut CorsCache::new(), target, context);
|
||||
}
|
||||
|
||||
pub fn fetch_with_cors_cache(request: &mut Request,
|
||||
cache: &mut CorsCache,
|
||||
target: Target,
|
||||
context: &FetchContext) {
|
||||
pub fn fetch_with_cors_cache(
|
||||
request: &mut Request,
|
||||
cache: &mut CorsCache,
|
||||
target: Target,
|
||||
context: &FetchContext,
|
||||
) {
|
||||
// Step 1.
|
||||
if request.window == Window::Client {
|
||||
// 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)
|
||||
pub fn main_fetch(request: &mut Request,
|
||||
cache: &mut CorsCache,
|
||||
cors_flag: bool,
|
||||
recursive_flag: bool,
|
||||
target: Target,
|
||||
done_chan: &mut DoneChannel,
|
||||
context: &FetchContext)
|
||||
-> Response {
|
||||
pub fn main_fetch(
|
||||
request: &mut Request,
|
||||
cache: &mut CorsCache,
|
||||
cors_flag: bool,
|
||||
recursive_flag: bool,
|
||||
target: Target,
|
||||
done_chan: &mut DoneChannel,
|
||||
context: &FetchContext,
|
||||
) -> Response {
|
||||
// Step 1.
|
||||
let mut response = None;
|
||||
|
||||
// Step 2.
|
||||
if request.local_urls_only {
|
||||
if !matches!(request.current_url().scheme(), "about" | "blob" | "data" | "filesystem") {
|
||||
response = Some(Response::network_error(NetworkError::Internal("Non-local scheme".into())));
|
||||
if !matches!(
|
||||
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.
|
||||
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 by content security policy.
|
||||
|
@ -167,7 +176,9 @@ pub fn main_fetch(request: &mut Request,
|
|||
// TODO: handle request's client's referrer policy.
|
||||
|
||||
// Step 7.
|
||||
request.referrer_policy = request.referrer_policy.or(Some(ReferrerPolicy::NoReferrerWhenDowngrade));
|
||||
request.referrer_policy = request
|
||||
.referrer_policy
|
||||
.or(Some(ReferrerPolicy::NoReferrerWhenDowngrade));
|
||||
|
||||
// Step 8.
|
||||
{
|
||||
|
@ -182,11 +193,13 @@ pub fn main_fetch(request: &mut Request,
|
|||
Referrer::ReferrerUrl(url) => {
|
||||
request.headers.remove(header::REFERER);
|
||||
let current_url = request.current_url().clone();
|
||||
determine_request_referrer(&mut request.headers,
|
||||
request.referrer_policy.unwrap(),
|
||||
url,
|
||||
current_url)
|
||||
}
|
||||
determine_request_referrer(
|
||||
&mut request.headers,
|
||||
request.referrer_policy.unwrap(),
|
||||
url,
|
||||
current_url,
|
||||
)
|
||||
},
|
||||
};
|
||||
if let Some(referrer_url) = referrer_url {
|
||||
request.referrer = Referrer::ReferrerUrl(referrer_url);
|
||||
|
@ -197,8 +210,12 @@ pub fn main_fetch(request: &mut Request,
|
|||
// TODO: handle FTP URLs.
|
||||
|
||||
// Step 10.
|
||||
context.state.hsts_list.read().unwrap().switch_known_hsts_host_domain_url_to_https(
|
||||
request.current_url_mut());
|
||||
context
|
||||
.state
|
||||
.hsts_list
|
||||
.read()
|
||||
.unwrap()
|
||||
.switch_known_hsts_host_domain_url_to_https(request.current_url_mut());
|
||||
|
||||
// Step 11.
|
||||
// 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
|
||||
// since we load them through a file: URL.
|
||||
current_url.scheme() == "about" ||
|
||||
request.mode == RequestMode::Navigate {
|
||||
request.mode == RequestMode::Navigate
|
||||
{
|
||||
// Substep 1.
|
||||
request.response_tainting = ResponseTainting::Basic;
|
||||
|
||||
// Substep 2.
|
||||
scheme_fetch(request, cache, target, done_chan, context)
|
||||
|
||||
} else if request.mode == RequestMode::SameOrigin {
|
||||
Response::network_error(NetworkError::Internal("Cross-origin response".into()))
|
||||
|
||||
} else if request.mode == RequestMode::NoCors {
|
||||
// Substep 1.
|
||||
request.response_tainting = ResponseTainting::Opaque;
|
||||
|
||||
// Substep 2.
|
||||
scheme_fetch(request, cache, target, done_chan, context)
|
||||
|
||||
} else if !matches!(current_url.scheme(), "http" | "https") {
|
||||
Response::network_error(NetworkError::Internal("Non-http scheme".into()))
|
||||
|
||||
} else if request.use_cors_preflight ||
|
||||
(request.unsafe_request &&
|
||||
(!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.
|
||||
request.response_tainting = ResponseTainting::CorsTainting;
|
||||
// Substep 2.
|
||||
let response = http_fetch(request, cache, true, true, false,
|
||||
target, done_chan, context);
|
||||
let response = http_fetch(
|
||||
request, cache, true, true, false, target, done_chan, context,
|
||||
);
|
||||
// Substep 3.
|
||||
if response.is_network_error() {
|
||||
// TODO clear cache entries using request
|
||||
}
|
||||
// Substep 4.
|
||||
response
|
||||
|
||||
} else {
|
||||
// Substep 1.
|
||||
request.response_tainting = ResponseTainting::CorsTainting;
|
||||
// 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.
|
||||
if request.response_tainting == ResponseTainting::CorsTainting {
|
||||
// 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());
|
||||
match header_names {
|
||||
// Subsubstep 2.
|
||||
Some(ref list) if request.credentials_mode != CredentialsMode::Include => {
|
||||
if list.len() == 1 && list[0] == "*" {
|
||||
response.cors_exposed_header_name_list =
|
||||
response.headers.iter().map(|(name, _)| name.as_str().to_owned()).collect();
|
||||
response.cors_exposed_header_name_list = response
|
||||
.headers
|
||||
.iter()
|
||||
.map(|(name, _)| name.as_str().to_owned())
|
||||
.collect();
|
||||
}
|
||||
},
|
||||
// Subsubstep 3.
|
||||
Some(list) => {
|
||||
response.cors_exposed_header_name_list = list.iter().map(|h| h.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 = {
|
||||
// Tests for steps 17 and 18, before step 15 for borrowing concerns.
|
||||
let response_is_network_error = response.is_network_error();
|
||||
let should_replace_with_nosniff_error =
|
||||
!response_is_network_error && should_be_blocked_due_to_nosniff(request.destination, &response.headers);
|
||||
let should_replace_with_mime_type_error =
|
||||
!response_is_network_error && should_be_blocked_due_to_mime_type(request.destination, &response.headers);
|
||||
let should_replace_with_nosniff_error = !response_is_network_error &&
|
||||
should_be_blocked_due_to_nosniff(request.destination, &response.headers);
|
||||
let should_replace_with_mime_type_error = !response_is_network_error &&
|
||||
should_be_blocked_due_to_mime_type(request.destination, &response.headers);
|
||||
|
||||
// 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() {
|
||||
error_response
|
||||
} else {
|
||||
|
@ -326,27 +353,30 @@ pub fn main_fetch(request: &mut Request,
|
|||
// TODO: handle blocking as mixed content.
|
||||
// TODO: handle blocking by content security policy.
|
||||
let blocked_error_response;
|
||||
let internal_response =
|
||||
if should_replace_with_nosniff_error {
|
||||
// Defer rebinding result
|
||||
blocked_error_response = Response::network_error(NetworkError::Internal("Blocked by nosniff".into()));
|
||||
&blocked_error_response
|
||||
} else if should_replace_with_mime_type_error {
|
||||
// Defer rebinding result
|
||||
blocked_error_response = Response::network_error(NetworkError::Internal("Blocked by mime type".into()));
|
||||
&blocked_error_response
|
||||
} else {
|
||||
internal_response
|
||||
};
|
||||
let internal_response = if should_replace_with_nosniff_error {
|
||||
// Defer rebinding result
|
||||
blocked_error_response =
|
||||
Response::network_error(NetworkError::Internal("Blocked by nosniff".into()));
|
||||
&blocked_error_response
|
||||
} else if should_replace_with_mime_type_error {
|
||||
// Defer rebinding result
|
||||
blocked_error_response =
|
||||
Response::network_error(NetworkError::Internal("Blocked by mime type".into()));
|
||||
&blocked_error_response
|
||||
} else {
|
||||
internal_response
|
||||
};
|
||||
|
||||
// Step 18.
|
||||
// We check `internal_response` since we did not mutate `response`
|
||||
// in the previous step.
|
||||
let not_network_error = !response_is_network_error && !internal_response.is_network_error();
|
||||
if not_network_error && (is_null_body_status(&internal_response.status) ||
|
||||
match request.method {
|
||||
Method::HEAD | Method::CONNECT => true,
|
||||
_ => false }) {
|
||||
if not_network_error &&
|
||||
(is_null_body_status(&internal_response.status) ||
|
||||
match request.method {
|
||||
Method::HEAD | Method::CONNECT => true,
|
||||
_ => false,
|
||||
}) {
|
||||
// when Fetch is used only asynchronously, we will need to make sure
|
||||
// that nothing tries to write to the body at this point
|
||||
let mut body = internal_response.body.lock().unwrap();
|
||||
|
@ -373,8 +403,11 @@ pub fn main_fetch(request: &mut Request,
|
|||
// Step 19.2.
|
||||
let ref integrity_metadata = &request.integrity_metadata;
|
||||
if response.termination_reason.is_none() &&
|
||||
!is_response_integrity_valid(integrity_metadata, &response) {
|
||||
Response::network_error(NetworkError::Internal("Subresource integrity validation failed".into()))
|
||||
!is_response_integrity_valid(integrity_metadata, &response)
|
||||
{
|
||||
Response::network_error(NetworkError::Internal(
|
||||
"Subresource integrity validation failed".into(),
|
||||
))
|
||||
} else {
|
||||
response
|
||||
}
|
||||
|
@ -410,7 +443,7 @@ pub fn main_fetch(request: &mut Request,
|
|||
|
||||
// Step 23.
|
||||
if !response_loaded {
|
||||
wait_for_response(&mut response, target, done_chan);
|
||||
wait_for_response(&mut response, target, done_chan);
|
||||
}
|
||||
|
||||
// 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) {
|
||||
if let Some(ref ch) = *done_chan {
|
||||
loop {
|
||||
match ch.1.recv()
|
||||
.expect("fetch worker should always send Done before terminating") {
|
||||
match ch
|
||||
.1
|
||||
.recv()
|
||||
.expect("fetch worker should always send Done before terminating")
|
||||
{
|
||||
Data::Payload(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 => {
|
||||
response.aborted.store(true, Ordering::Relaxed);
|
||||
break;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
} 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)
|
||||
fn scheme_fetch(request: &mut Request,
|
||||
cache: &mut CorsCache,
|
||||
target: Target,
|
||||
done_chan: &mut DoneChannel,
|
||||
context: &FetchContext)
|
||||
-> Response {
|
||||
fn scheme_fetch(
|
||||
request: &mut Request,
|
||||
cache: &mut CorsCache,
|
||||
target: Target,
|
||||
done_chan: &mut DoneChannel,
|
||||
context: &FetchContext,
|
||||
) -> Response {
|
||||
let url = request.current_url();
|
||||
|
||||
match url.scheme() {
|
||||
"about" if url.path() == "blank" => {
|
||||
let mut response = Response::new(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
|
||||
},
|
||||
|
||||
"http" | "https" => {
|
||||
http_fetch(request, cache, false, false, false, target, done_chan, context)
|
||||
},
|
||||
"http" | "https" => http_fetch(
|
||||
request, cache, false, false, false, target, done_chan, context,
|
||||
),
|
||||
|
||||
"data" => {
|
||||
match decode(&url) {
|
||||
Ok((mime, bytes)) => {
|
||||
let mut response = Response::new(url);
|
||||
*response.body.lock().unwrap() = ResponseBody::Done(bytes);
|
||||
response.headers.typed_insert(ContentType::from(mime));
|
||||
response
|
||||
},
|
||||
Err(_) => Response::network_error(NetworkError::Internal("Decoding data URL failed".into()))
|
||||
}
|
||||
"data" => match decode(&url) {
|
||||
Ok((mime, bytes)) => {
|
||||
let mut response = Response::new(url);
|
||||
*response.body.lock().unwrap() = ResponseBody::Done(bytes);
|
||||
response.headers.typed_insert(ContentType::from(mime));
|
||||
response
|
||||
},
|
||||
Err(_) => {
|
||||
Response::network_error(NetworkError::Internal("Decoding data URL failed".into()))
|
||||
},
|
||||
},
|
||||
|
||||
"file" => {
|
||||
|
@ -507,9 +546,17 @@ fn scheme_fetch(request: &mut Request,
|
|||
|
||||
let cancellation_listener = context.cancellation_listener.clone();
|
||||
|
||||
let (start, end) = if let Some(ref range) = request.headers.typed_get::<Range>() {
|
||||
match range.iter().collect::<Vec<(Bound<u64>, Bound<u64>)>>().first() {
|
||||
Some(&(Bound::Included(start), Bound::Unbounded)) => (start, None),
|
||||
let (start, end) = if let Some(ref range) =
|
||||
request.headers.typed_get::<Range>()
|
||||
{
|
||||
match range
|
||||
.iter()
|
||||
.collect::<Vec<(Bound<u64>, Bound<u64>)>>()
|
||||
.first()
|
||||
{
|
||||
Some(&(Bound::Included(start), Bound::Unbounded)) => {
|
||||
(start, None)
|
||||
},
|
||||
Some(&(Bound::Included(start), Bound::Included(end))) => {
|
||||
// `end` should be less or equal to `start`.
|
||||
(start, Some(u64::max(start, end)))
|
||||
|
@ -517,75 +564,97 @@ fn scheme_fetch(request: &mut Request,
|
|||
Some(&(Bound::Unbounded, Bound::Included(offset))) => {
|
||||
if let Ok(metadata) = file.metadata() {
|
||||
// `offset` cannot be bigger than the file size.
|
||||
(metadata.len() - u64::min(metadata.len(), offset), None)
|
||||
(
|
||||
metadata.len() -
|
||||
u64::min(metadata.len(), offset),
|
||||
None,
|
||||
)
|
||||
} else {
|
||||
(0, None)
|
||||
}
|
||||
},
|
||||
_ => (0, None)
|
||||
_ => (0, None),
|
||||
}
|
||||
} else {
|
||||
(0, None)
|
||||
};
|
||||
|
||||
thread::Builder::new().name("fetch file worker thread".to_string()).spawn(move || {
|
||||
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);
|
||||
}
|
||||
thread::Builder::new()
|
||||
.name("fetch file worker thread".to_string())
|
||||
.spawn(move || {
|
||||
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 {
|
||||
if cancellation_listener.lock().unwrap().cancelled() {
|
||||
*res_body.lock().unwrap() = ResponseBody::Done(vec![]);
|
||||
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));
|
||||
loop {
|
||||
if cancellation_listener.lock().unwrap().cancelled() {
|
||||
*res_body.lock().unwrap() =
|
||||
ResponseBody::Done(vec![]);
|
||||
let _ = done_sender.send(Data::Cancelled);
|
||||
return;
|
||||
}
|
||||
buffer_len
|
||||
};
|
||||
if length == 0 {
|
||||
let mut body = res_body.lock().unwrap();
|
||||
let completed_body = match *body {
|
||||
ResponseBody::Receiving(ref mut body) => {
|
||||
mem::replace(body, vec![])
|
||||
},
|
||||
_ => vec![],
|
||||
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
|
||||
};
|
||||
*body = ResponseBody::Done(completed_body);
|
||||
let _ = done_sender.send(Data::Done);
|
||||
break;
|
||||
if length == 0 {
|
||||
let mut body = res_body.lock().unwrap();
|
||||
let completed_body = match *body {
|
||||
ResponseBody::Receiving(ref mut body) => {
|
||||
mem::replace(body, vec![])
|
||||
},
|
||||
_ => vec![],
|
||||
};
|
||||
*body = ResponseBody::Done(completed_body);
|
||||
let _ = done_sender.send(Data::Done);
|
||||
break;
|
||||
}
|
||||
reader.consume(length);
|
||||
}
|
||||
reader.consume(length);
|
||||
}
|
||||
}).expect("Failed to create fetch file worker thread");
|
||||
})
|
||||
.expect("Failed to create fetch file worker thread");
|
||||
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 {
|
||||
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());
|
||||
// Step 2.
|
||||
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()) {
|
||||
|
@ -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())),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -627,13 +698,15 @@ pub fn is_cors_safelisted_request_header(name: &HeaderName, value: &HeaderValue)
|
|||
if name == header::CONTENT_TYPE {
|
||||
if let Some(m) = value.to_str().ok().and_then(|s| s.parse::<Mime>().ok()) {
|
||||
m.type_() == mime::TEXT && m.subtype() == mime::PLAIN ||
|
||||
m.type_() == mime::APPLICATION && m.subtype() == mime::WWW_FORM_URLENCODED ||
|
||||
m.type_() == mime::MULTIPART && m.subtype() == mime::FORM_DATA
|
||||
m.type_() == mime::APPLICATION && m.subtype() == mime::WWW_FORM_URLENCODED ||
|
||||
m.type_() == mime::MULTIPART && m.subtype() == mime::FORM_DATA
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} 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 {
|
||||
match *m {
|
||||
Method::GET | Method::HEAD | Method::POST => true,
|
||||
_ => false
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_null_body_status(status: &Option<(StatusCode, String)>) -> bool {
|
||||
match *status {
|
||||
Some((status, _)) => match status {
|
||||
StatusCode::SWITCHING_PROTOCOLS | StatusCode::NO_CONTENT |
|
||||
StatusCode::RESET_CONTENT | StatusCode::NOT_MODIFIED => true,
|
||||
_ => false
|
||||
StatusCode::SWITCHING_PROTOCOLS |
|
||||
StatusCode::NO_CONTENT |
|
||||
StatusCode::RESET_CONTENT |
|
||||
StatusCode::NOT_MODIFIED => true,
|
||||
_ => false,
|
||||
},
|
||||
_ => false
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// <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.
|
||||
// TODO(eijebong): Replace this once typed headers allow custom ones...
|
||||
if response_headers.get("x-content-type-options")
|
||||
.map_or(true, |val| val.to_str().unwrap_or("").to_lowercase() != "nosniff")
|
||||
{
|
||||
if response_headers
|
||||
.get("x-content-type-options")
|
||||
.map_or(true, |val| {
|
||||
val.to_str().unwrap_or("").to_lowercase() != "nosniff"
|
||||
}) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -692,30 +772,34 @@ pub fn should_be_blocked_due_to_nosniff(destination: Destination, response_heade
|
|||
"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())
|
||||
}
|
||||
|
||||
match content_type_header {
|
||||
// Step 6
|
||||
Some(ref ct) if destination.is_script_like()
|
||||
=> !is_javascript_mime_type(&ct.clone().into()),
|
||||
Some(ref ct) if destination.is_script_like() => {
|
||||
!is_javascript_mime_type(&ct.clone().into())
|
||||
},
|
||||
|
||||
// Step 7
|
||||
Some(ref ct) if destination == Destination::Style
|
||||
=> {
|
||||
let m: mime::Mime = ct.clone().into();
|
||||
m.type_() != mime::TEXT && m.subtype() != mime::CSS
|
||||
},
|
||||
Some(ref ct) if destination == Destination::Style => {
|
||||
let m: mime::Mime = ct.clone().into();
|
||||
m.type_() != mime::TEXT && m.subtype() != mime::CSS
|
||||
},
|
||||
|
||||
None if destination == Destination::Style || destination.is_script_like() => true,
|
||||
// Step 8
|
||||
_ => false
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// <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
|
||||
let mime_type: mime::Mime = match response_headers.typed_get::<ContentType>() {
|
||||
Some(header) => header.into(),
|
||||
|
@ -725,12 +809,10 @@ fn should_be_blocked_due_to_mime_type(destination: Destination, response_headers
|
|||
// Step 2-3
|
||||
destination.is_script_like() &&
|
||||
match mime_type.type_() {
|
||||
mime::AUDIO |
|
||||
mime::VIDEO |
|
||||
mime::IMAGE => true,
|
||||
mime::AUDIO | mime::VIDEO | mime::IMAGE => true,
|
||||
mime::TEXT if mime_type.subtype() == mime::CSV => true,
|
||||
// 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
|
||||
// the given scheme, and thus this means the request should not be blocked
|
||||
// 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.
|
||||
if scheme == "ftp" && (port == 20 || port == 21) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Step 5.
|
||||
if is_network_scheme(scheme) && is_bad_port(port) {
|
||||
return true;
|
||||
|
@ -770,12 +855,10 @@ fn is_network_scheme(scheme: &str) -> bool {
|
|||
/// <https://fetch.spec.whatwg.org/#bad-port>
|
||||
fn is_bad_port(port: u16) -> bool {
|
||||
static BAD_PORTS: [u16; 64] = [
|
||||
1, 7, 9, 11, 13, 15, 17, 19, 20, 21, 22, 23, 25, 37, 42,
|
||||
43, 53, 77, 79, 87, 95, 101, 102, 103, 104, 109, 110, 111,
|
||||
113, 115, 117, 119, 123, 135, 139, 143, 179, 389, 465, 512,
|
||||
513, 514, 515, 526, 530, 531, 532, 540, 556, 563, 587, 601,
|
||||
636, 993, 995, 2049, 3659, 4045, 6000, 6665, 6666, 6667,
|
||||
6668, 6669
|
||||
1, 7, 9, 11, 13, 15, 17, 19, 20, 21, 22, 23, 25, 37, 42, 43, 53, 77, 79, 87, 95, 101, 102,
|
||||
103, 104, 109, 110, 111, 113, 115, 117, 119, 123, 135, 139, 143, 179, 389, 465, 512, 513,
|
||||
514, 515, 526, 530, 531, 532, 540, 556, 563, 587, 601, 636, 993, 995, 2049, 3659, 4045,
|
||||
6000, 6665, 6666, 6667, 6668, 6669,
|
||||
];
|
||||
|
||||
BAD_PORTS.binary_search(&port).is_ok()
|
||||
|
|
|
@ -34,7 +34,7 @@ struct FileStoreEntry {
|
|||
/// UUIDs only become valid blob URIs when explicitly requested
|
||||
/// by the user with createObjectURL. Validity can be revoked as well.
|
||||
/// (The UUID is the one that maps to this entry in `FileManagerStore`)
|
||||
is_valid_url: AtomicBool
|
||||
is_valid_url: AtomicBool,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -71,29 +71,38 @@ impl FileManager {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn read_file(&self,
|
||||
sender: IpcSender<FileManagerResult<ReadFileProgress>>,
|
||||
id: Uuid,
|
||||
check_url_validity: bool,
|
||||
origin: FileOrigin) {
|
||||
pub fn read_file(
|
||||
&self,
|
||||
sender: IpcSender<FileManagerResult<ReadFileProgress>>,
|
||||
id: Uuid,
|
||||
check_url_validity: bool,
|
||||
origin: FileOrigin,
|
||||
) {
|
||||
let store = self.store.clone();
|
||||
thread::Builder::new().name("read file".to_owned()).spawn(move || {
|
||||
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");
|
||||
thread::Builder::new()
|
||||
.name("read file".to_owned())
|
||||
.spawn(move || {
|
||||
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");
|
||||
}
|
||||
|
||||
pub fn promote_memory(&self,
|
||||
blob_buf: BlobBuf,
|
||||
set_valid: bool,
|
||||
sender: IpcSender<Result<Uuid, BlobURLStoreError>>,
|
||||
origin: FileOrigin) {
|
||||
pub fn promote_memory(
|
||||
&self,
|
||||
blob_buf: BlobBuf,
|
||||
set_valid: bool,
|
||||
sender: IpcSender<Result<Uuid, BlobURLStoreError>>,
|
||||
origin: FileOrigin,
|
||||
) {
|
||||
let store = self.store.clone();
|
||||
thread::Builder::new().name("transfer memory".to_owned()).spawn(move || {
|
||||
store.promote_memory(blob_buf, set_valid, sender, origin);
|
||||
}).expect("Thread spawning failed");
|
||||
thread::Builder::new()
|
||||
.name("transfer memory".to_owned())
|
||||
.spawn(move || {
|
||||
store.promote_memory(blob_buf, set_valid, sender, origin);
|
||||
})
|
||||
.expect("Thread spawning failed");
|
||||
}
|
||||
|
||||
/// Message handler
|
||||
|
@ -102,35 +111,41 @@ impl FileManager {
|
|||
FileManagerThreadMsg::SelectFile(filter, sender, origin, opt_test_path) => {
|
||||
let store = self.store.clone();
|
||||
let embedder = self.embedder_proxy.clone();
|
||||
thread::Builder::new().name("select file".to_owned()).spawn(move || {
|
||||
store.select_file(filter, sender, origin, opt_test_path, embedder);
|
||||
}).expect("Thread spawning failed");
|
||||
}
|
||||
thread::Builder::new()
|
||||
.name("select file".to_owned())
|
||||
.spawn(move || {
|
||||
store.select_file(filter, sender, origin, opt_test_path, embedder);
|
||||
})
|
||||
.expect("Thread spawning failed");
|
||||
},
|
||||
FileManagerThreadMsg::SelectFiles(filter, sender, origin, opt_test_paths) => {
|
||||
let store = self.store.clone();
|
||||
let embedder = self.embedder_proxy.clone();
|
||||
thread::Builder::new().name("select files".to_owned()).spawn(move || {
|
||||
store.select_files(filter, sender, origin, opt_test_paths, embedder);
|
||||
}).expect("Thread spawning failed");
|
||||
}
|
||||
thread::Builder::new()
|
||||
.name("select files".to_owned())
|
||||
.spawn(move || {
|
||||
store.select_files(filter, sender, origin, opt_test_paths, embedder);
|
||||
})
|
||||
.expect("Thread spawning failed");
|
||||
},
|
||||
FileManagerThreadMsg::ReadFile(sender, id, check_url_validity, origin) => {
|
||||
self.read_file(sender, id, check_url_validity, origin);
|
||||
}
|
||||
},
|
||||
FileManagerThreadMsg::PromoteMemory(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);
|
||||
}
|
||||
},
|
||||
FileManagerThreadMsg::DecRef(id, origin, sender) => {
|
||||
let _ = sender.send(self.store.dec_ref(&id, &origin));
|
||||
}
|
||||
},
|
||||
FileManagerThreadMsg::RevokeBlobURL(id, origin, sender) => {
|
||||
let _ = sender.send(self.store.set_blob_url_validity(false, &id, &origin));
|
||||
}
|
||||
},
|
||||
FileManagerThreadMsg::ActivateBlobURL(id, sender, 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
|
||||
fn get_impl(&self, id: &Uuid, origin_in: &FileOrigin,
|
||||
check_url_validity: bool) -> Result<FileImpl, BlobURLStoreError> {
|
||||
fn get_impl(
|
||||
&self,
|
||||
id: &Uuid,
|
||||
origin_in: &FileOrigin,
|
||||
check_url_validity: bool,
|
||||
) -> Result<FileImpl, BlobURLStoreError> {
|
||||
match self.entries.read().unwrap().get(id) {
|
||||
Some(ref entry) => {
|
||||
if *origin_in != *entry.origin {
|
||||
|
@ -164,7 +183,7 @@ impl FileManagerStore {
|
|||
Ok(entry.file_impl.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
None => Err(BlobURLStoreError::InvalidFileID),
|
||||
}
|
||||
}
|
||||
|
@ -177,7 +196,7 @@ impl FileManagerStore {
|
|||
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) {
|
||||
Some(entry) => {
|
||||
if entry.origin == *origin_in {
|
||||
|
@ -186,41 +205,53 @@ impl FileManagerStore {
|
|||
} else {
|
||||
Err(BlobURLStoreError::InvalidOrigin)
|
||||
}
|
||||
}
|
||||
},
|
||||
None => Err(BlobURLStoreError::InvalidFileID),
|
||||
}
|
||||
}
|
||||
|
||||
fn add_sliced_url_entry(&self, parent_id: Uuid, rel_pos: RelativePos,
|
||||
sender: IpcSender<Result<Uuid, BlobURLStoreError>>,
|
||||
origin_in: FileOrigin) {
|
||||
fn add_sliced_url_entry(
|
||||
&self,
|
||||
parent_id: Uuid,
|
||||
rel_pos: RelativePos,
|
||||
sender: IpcSender<Result<Uuid, BlobURLStoreError>>,
|
||||
origin_in: FileOrigin,
|
||||
) {
|
||||
match self.inc_ref(&parent_id, &origin_in) {
|
||||
Ok(_) => {
|
||||
let new_id = Uuid::new_v4();
|
||||
self.insert(new_id, FileStoreEntry {
|
||||
origin: origin_in,
|
||||
file_impl: FileImpl::Sliced(parent_id, rel_pos),
|
||||
refs: AtomicUsize::new(1),
|
||||
// Valid here since AddSlicedURLEntry implies URL creation
|
||||
// from a BlobImpl::Sliced
|
||||
is_valid_url: AtomicBool::new(true),
|
||||
});
|
||||
self.insert(
|
||||
new_id,
|
||||
FileStoreEntry {
|
||||
origin: origin_in,
|
||||
file_impl: FileImpl::Sliced(parent_id, rel_pos),
|
||||
refs: AtomicUsize::new(1),
|
||||
// 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
|
||||
let _ = sender.send(Ok(new_id));
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
let _ = sender.send(Err(e));
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn query_files_from_embedder(&self,
|
||||
patterns: Vec<FilterPattern>,
|
||||
multiple_files: bool,
|
||||
embedder_proxy: EmbedderProxy) -> Option<Vec<String>> {
|
||||
fn query_files_from_embedder(
|
||||
&self,
|
||||
patterns: Vec<FilterPattern>,
|
||||
multiple_files: bool,
|
||||
embedder_proxy: EmbedderProxy,
|
||||
) -> Option<Vec<String>> {
|
||||
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);
|
||||
match ipc_receiver.recv() {
|
||||
|
@ -228,23 +259,26 @@ impl FileManagerStore {
|
|||
Err(e) => {
|
||||
warn!("Failed to receive files from embedder ({}).", e);
|
||||
None
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn select_file(&self,
|
||||
patterns: Vec<FilterPattern>,
|
||||
sender: IpcSender<FileManagerResult<SelectedFile>>,
|
||||
origin: FileOrigin,
|
||||
opt_test_path: Option<String>,
|
||||
embedder_proxy: EmbedderProxy) {
|
||||
fn select_file(
|
||||
&self,
|
||||
patterns: Vec<FilterPattern>,
|
||||
sender: IpcSender<FileManagerResult<SelectedFile>>,
|
||||
origin: FileOrigin,
|
||||
opt_test_path: Option<String>,
|
||||
embedder_proxy: EmbedderProxy,
|
||||
) {
|
||||
// Check if the select_files preference is enabled
|
||||
// to ensure process-level security against compromised script;
|
||||
// Then try applying opt_test_path directly for testing convenience
|
||||
let opt_s = if select_files_pref_enabled() {
|
||||
opt_test_path
|
||||
} 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 {
|
||||
|
@ -252,20 +286,22 @@ impl FileManagerStore {
|
|||
let selected_path = Path::new(&s);
|
||||
let result = self.create_entry(selected_path, &origin);
|
||||
let _ = sender.send(result);
|
||||
}
|
||||
},
|
||||
None => {
|
||||
let _ = sender.send(Err(FileManagerThreadError::UserCancelled));
|
||||
return;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn select_files(&self,
|
||||
patterns: Vec<FilterPattern>,
|
||||
sender: IpcSender<FileManagerResult<Vec<SelectedFile>>>,
|
||||
origin: FileOrigin,
|
||||
opt_test_paths: Option<Vec<String>>,
|
||||
embedder_proxy: EmbedderProxy) {
|
||||
fn select_files(
|
||||
&self,
|
||||
patterns: Vec<FilterPattern>,
|
||||
sender: IpcSender<FileManagerResult<Vec<SelectedFile>>>,
|
||||
origin: FileOrigin,
|
||||
opt_test_paths: Option<Vec<String>>,
|
||||
embedder_proxy: EmbedderProxy,
|
||||
) {
|
||||
// Check if the select_files preference is enabled
|
||||
// to ensure process-level security against compromised script;
|
||||
// Then try applying opt_test_paths directly for testing convenience
|
||||
|
@ -291,30 +327,42 @@ impl FileManagerStore {
|
|||
Err(e) => {
|
||||
let _ = sender.send(Err(e));
|
||||
return;
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
let _ = sender.send(Ok(replies));
|
||||
}
|
||||
},
|
||||
None => {
|
||||
let _ = sender.send(Err(FileManagerThreadError::UserCancelled));
|
||||
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;
|
||||
|
||||
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 modified = metadata.modified().map_err(|e| FileSystemError(e.to_string()))?;
|
||||
let elapsed = modified.elapsed().map_err(|e| FileSystemError(e.to_string()))?;
|
||||
let metadata = file
|
||||
.metadata()
|
||||
.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
|
||||
let modified_epoch = elapsed.as_secs() * 1000 + elapsed.subsec_nanos() as u64 / 1000000;
|
||||
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 {
|
||||
path: file_path.to_path_buf(),
|
||||
|
@ -324,18 +372,21 @@ impl FileManagerStore {
|
|||
|
||||
let id = Uuid::new_v4();
|
||||
|
||||
self.insert(id, FileStoreEntry {
|
||||
origin: origin.to_string(),
|
||||
file_impl: file_impl,
|
||||
refs: AtomicUsize::new(1),
|
||||
// Invalid here since create_entry is called by file selection
|
||||
is_valid_url: AtomicBool::new(false),
|
||||
});
|
||||
self.insert(
|
||||
id,
|
||||
FileStoreEntry {
|
||||
origin: origin.to_string(),
|
||||
file_impl: file_impl,
|
||||
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 type_string = match guess_mime_type_opt(filename_path) {
|
||||
Some(x) => format!("{}", x),
|
||||
None => "".to_string(),
|
||||
None => "".to_string(),
|
||||
};
|
||||
|
||||
Ok(SelectedFile {
|
||||
|
@ -347,9 +398,14 @@ impl FileManagerStore {
|
|||
})
|
||||
}
|
||||
|
||||
fn get_blob_buf(&self, sender: &IpcSender<FileManagerResult<ReadFileProgress>>,
|
||||
id: &Uuid, origin_in: &FileOrigin, rel_pos: RelativePos,
|
||||
check_url_validity: bool) -> Result<(), BlobURLStoreError> {
|
||||
fn get_blob_buf(
|
||||
&self,
|
||||
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)?;
|
||||
match file_impl {
|
||||
FileImpl::Memory(buf) => {
|
||||
|
@ -365,7 +421,7 @@ impl FileManagerStore {
|
|||
let _ = sender.send(Ok(ReadFileProgress::EOF));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
},
|
||||
FileImpl::MetaDataOnly(metadata) => {
|
||||
/* XXX: Snapshot state check (optional) https://w3c.github.io/FileAPI/#snapshot-state.
|
||||
Concretely, here we create another file, and this file might not
|
||||
|
@ -373,45 +429,62 @@ impl FileManagerStore {
|
|||
create_entry is called.
|
||||
*/
|
||||
|
||||
let opt_filename = metadata.path.file_name()
|
||||
.and_then(|osstr| osstr.to_str())
|
||||
.map(|s| s.to_string());
|
||||
let opt_filename = metadata
|
||||
.path
|
||||
.file_name()
|
||||
.and_then(|osstr| osstr.to_str())
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let mime = guess_mime_type_opt(metadata.path.clone());
|
||||
let range = rel_pos.to_abs_range(metadata.size as usize);
|
||||
|
||||
let mut file = File::open(&metadata.path)
|
||||
.map_err(|e| BlobURLStoreError::External(e.to_string()))?;
|
||||
let seeked_start = file.seek(SeekFrom::Start(range.start as u64))
|
||||
.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))
|
||||
.map_err(|e| BlobURLStoreError::External(e.to_string()))?;
|
||||
|
||||
if seeked_start == (range.start as u64) {
|
||||
let type_string = match mime {
|
||||
Some(x) => format!("{}", x),
|
||||
None => "".to_string(),
|
||||
None => "".to_string(),
|
||||
};
|
||||
|
||||
chunked_read(sender, &mut file, range.len(), opt_filename,
|
||||
type_string);
|
||||
chunked_read(sender, &mut file, range.len(), opt_filename, type_string);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(BlobURLStoreError::InvalidEntry)
|
||||
}
|
||||
}
|
||||
},
|
||||
FileImpl::Sliced(parent_id, inner_rel_pos) => {
|
||||
// Next time we don't need to check validity since
|
||||
// we have already done that for requesting URL if necessary
|
||||
self.get_blob_buf(sender, &parent_id, origin_in,
|
||||
rel_pos.slice_inner(&inner_rel_pos), false)
|
||||
}
|
||||
self.get_blob_buf(
|
||||
sender,
|
||||
&parent_id,
|
||||
origin_in,
|
||||
rel_pos.slice_inner(&inner_rel_pos),
|
||||
false,
|
||||
)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Convenient wrapper over get_blob_buf
|
||||
fn try_read_file(&self, sender: &IpcSender<FileManagerResult<ReadFileProgress>>,
|
||||
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 try_read_file(
|
||||
&self,
|
||||
sender: &IpcSender<FileManagerResult<ReadFileProgress>>,
|
||||
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> {
|
||||
|
@ -436,7 +509,7 @@ impl FileManagerStore {
|
|||
} else {
|
||||
return Err(BlobURLStoreError::InvalidOrigin);
|
||||
}
|
||||
}
|
||||
},
|
||||
None => return Err(BlobURLStoreError::InvalidFileID),
|
||||
};
|
||||
|
||||
|
@ -454,28 +527,41 @@ impl FileManagerStore {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn promote_memory(&self, blob_buf: BlobBuf, set_valid: bool,
|
||||
sender: IpcSender<Result<Uuid, BlobURLStoreError>>, origin: FileOrigin) {
|
||||
match Url::parse(&origin) { // parse to check sanity
|
||||
fn promote_memory(
|
||||
&self,
|
||||
blob_buf: BlobBuf,
|
||||
set_valid: bool,
|
||||
sender: IpcSender<Result<Uuid, BlobURLStoreError>>,
|
||||
origin: FileOrigin,
|
||||
) {
|
||||
match Url::parse(&origin) {
|
||||
// parse to check sanity
|
||||
Ok(_) => {
|
||||
let id = Uuid::new_v4();
|
||||
self.insert(id, FileStoreEntry {
|
||||
origin: origin.clone(),
|
||||
file_impl: FileImpl::Memory(blob_buf),
|
||||
refs: AtomicUsize::new(1),
|
||||
is_valid_url: AtomicBool::new(set_valid),
|
||||
});
|
||||
self.insert(
|
||||
id,
|
||||
FileStoreEntry {
|
||||
origin: origin.clone(),
|
||||
file_impl: FileImpl::Memory(blob_buf),
|
||||
refs: AtomicUsize::new(1),
|
||||
is_valid_url: AtomicBool::new(set_valid),
|
||||
},
|
||||
);
|
||||
|
||||
let _ = sender.send(Ok(id));
|
||||
}
|
||||
},
|
||||
Err(_) => {
|
||||
let _ = sender.send(Err(BlobURLStoreError::InvalidOrigin));
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn set_blob_url_validity(&self, validity: bool, id: &Uuid,
|
||||
origin_in: &FileOrigin) -> Result<(), BlobURLStoreError> {
|
||||
fn set_blob_url_validity(
|
||||
&self,
|
||||
validity: bool,
|
||||
id: &Uuid,
|
||||
origin_in: &FileOrigin,
|
||||
) -> Result<(), BlobURLStoreError> {
|
||||
let (do_remove, opt_parent_id, res) = match self.entries.read().unwrap().get(id) {
|
||||
Some(entry) => {
|
||||
if *entry.origin == *origin_in {
|
||||
|
@ -485,7 +571,7 @@ impl FileManagerStore {
|
|||
// Check if it is the last possible reference
|
||||
// since refs only accounts for blob 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 {
|
||||
(zero_refs, Some(parent_id.clone()), Ok(()))
|
||||
|
@ -498,8 +584,8 @@ impl FileManagerStore {
|
|||
} else {
|
||||
(false, None, Err(BlobURLStoreError::InvalidOrigin))
|
||||
}
|
||||
}
|
||||
None => (false, None, Err(BlobURLStoreError::InvalidFileID))
|
||||
},
|
||||
None => (false, None, Err(BlobURLStoreError::InvalidFileID)),
|
||||
};
|
||||
|
||||
if do_remove {
|
||||
|
@ -515,15 +601,21 @@ impl FileManagerStore {
|
|||
}
|
||||
|
||||
fn select_files_pref_enabled() -> bool {
|
||||
PREFS.get("dom.testing.htmlinputelement.select_files.enabled")
|
||||
.as_boolean().unwrap_or(false)
|
||||
PREFS
|
||||
.get("dom.testing.htmlinputelement.select_files.enabled")
|
||||
.as_boolean()
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
const CHUNK_SIZE: usize = 8192;
|
||||
|
||||
fn chunked_read(sender: &IpcSender<FileManagerResult<ReadFileProgress>>,
|
||||
file: &mut File, size: usize, opt_filename: Option<String>,
|
||||
type_string: String) {
|
||||
fn chunked_read(
|
||||
sender: &IpcSender<FileManagerResult<ReadFileProgress>>,
|
||||
file: &mut File,
|
||||
size: usize,
|
||||
opt_filename: Option<String>,
|
||||
type_string: String,
|
||||
) {
|
||||
// First chunk
|
||||
let mut buf = vec![0; CHUNK_SIZE];
|
||||
match file.read(&mut buf) {
|
||||
|
@ -536,11 +628,11 @@ fn chunked_read(sender: &IpcSender<FileManagerResult<ReadFileProgress>>,
|
|||
bytes: buf,
|
||||
};
|
||||
let _ = sender.send(Ok(ReadFileProgress::Meta(blob_buf)));
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
let _ = sender.send(Err(FileManagerThreadError::FileSystemError(e.to_string())));
|
||||
return;
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// Send the remaining chunks
|
||||
|
@ -550,15 +642,15 @@ fn chunked_read(sender: &IpcSender<FileManagerResult<ReadFileProgress>>,
|
|||
Ok(0) => {
|
||||
let _ = sender.send(Ok(ReadFileProgress::EOF));
|
||||
return;
|
||||
}
|
||||
},
|
||||
Ok(n) => {
|
||||
buf.truncate(n);
|
||||
let _ = sender.send(Ok(ReadFileProgress::Partial(buf)));
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
let _ = sender.send(Err(FileManagerThreadError::FileSystemError(e.to_string())));
|
||||
return;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,21 +31,32 @@ pub fn replace_host_table(table: HashMap<String, IpAddr>) {
|
|||
}
|
||||
|
||||
pub fn parse_hostsfile(hostsfile_content: &str) -> HashMap<String, IpAddr> {
|
||||
hostsfile_content.lines().filter_map(|line| {
|
||||
let mut iter = line.split('#').next().unwrap().split_whitespace();
|
||||
Some((iter.next()?.parse().ok()?, iter))
|
||||
}).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)
|
||||
hostsfile_content
|
||||
.lines()
|
||||
.filter_map(|line| {
|
||||
let mut iter = line.split('#').next().unwrap().split_whitespace();
|
||||
Some((iter.next()?.parse().ok()?, iter))
|
||||
})
|
||||
}).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> {
|
||||
HOST_TABLE.lock().unwrap().as_ref()
|
||||
HOST_TABLE
|
||||
.lock()
|
||||
.unwrap()
|
||||
.as_ref()
|
||||
.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()
|
||||
})
|
||||
}
|
||||
|
|
|
@ -16,11 +16,15 @@ pub struct HstsEntry {
|
|||
pub host: String,
|
||||
pub include_subdomains: bool,
|
||||
pub max_age: Option<u64>,
|
||||
pub timestamp: Option<u64>
|
||||
pub timestamp: Option<u64>,
|
||||
}
|
||||
|
||||
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() {
|
||||
None
|
||||
} else {
|
||||
|
@ -28,7 +32,7 @@ impl HstsEntry {
|
|||
host: host,
|
||||
include_subdomains: (subdomains == IncludeSubdomains::Included),
|
||||
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) {
|
||||
(Some(max_age), Some(timestamp)) => {
|
||||
(time::get_time().sec as u64) - timestamp >= max_age
|
||||
}
|
||||
},
|
||||
|
||||
_ => false
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -59,7 +63,9 @@ pub struct HstsList {
|
|||
|
||||
impl 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.
|
||||
|
@ -107,9 +113,9 @@ impl HstsList {
|
|||
}
|
||||
|
||||
fn has_subdomain(&self, host: &str, base_domain: &str) -> bool {
|
||||
self.entries_map.get(base_domain).map_or(false, |entries| {
|
||||
entries.iter().any(|e| e.matches_subdomain(host))
|
||||
})
|
||||
self.entries_map.get(base_domain).map_or(false, |entries| {
|
||||
entries.iter().any(|e| e.matches_subdomain(host))
|
||||
})
|
||||
}
|
||||
|
||||
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_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 {
|
||||
entries.push(entry);
|
||||
} else if !have_subdomain {
|
||||
|
@ -136,7 +145,10 @@ impl HstsList {
|
|||
if url.scheme() != "http" {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,23 +30,22 @@ use std::time::SystemTime;
|
|||
use time;
|
||||
use time::{Duration, Timespec, Tm};
|
||||
|
||||
|
||||
/// The key used to differentiate requests in the cache.
|
||||
#[derive(Clone, Eq, Hash, MallocSizeOf, PartialEq )]
|
||||
#[derive(Clone, Eq, Hash, MallocSizeOf, PartialEq)]
|
||||
pub struct CacheKey {
|
||||
url: ServoUrl
|
||||
url: ServoUrl,
|
||||
}
|
||||
|
||||
impl CacheKey {
|
||||
fn new(request: Request) -> CacheKey {
|
||||
CacheKey {
|
||||
url: request.current_url().clone()
|
||||
url: request.current_url().clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_servo_url(servo_url: &ServoUrl) -> CacheKey {
|
||||
CacheKey {
|
||||
url: servo_url.clone()
|
||||
url: servo_url.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -63,7 +62,7 @@ struct CachedResource {
|
|||
body: Arc<Mutex<ResponseBody>>,
|
||||
aborted: Arc<AtomicBool>,
|
||||
awaiting_body: Arc<Mutex<Vec<Sender<Data>>>>,
|
||||
data: Measurable<MeasurableCachedResource>
|
||||
data: Measurable<MeasurableCachedResource>,
|
||||
}
|
||||
|
||||
#[derive(Clone, MallocSizeOf)]
|
||||
|
@ -82,9 +81,9 @@ impl MallocSizeOf for CachedResource {
|
|||
fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
|
||||
// TODO: self.request_headers.unconditional_size_of(ops) +
|
||||
self.body.unconditional_size_of(ops) +
|
||||
self.aborted.unconditional_size_of(ops) +
|
||||
self.awaiting_body.unconditional_size_of(ops) +
|
||||
self.data.size_of(ops)
|
||||
self.aborted.unconditional_size_of(ops) +
|
||||
self.awaiting_body.unconditional_size_of(ops) +
|
||||
self.data.size_of(ops)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -94,7 +93,7 @@ struct CachedMetadata {
|
|||
/// Headers
|
||||
pub headers: Arc<Mutex<HeaderMap>>,
|
||||
/// Fields that implement MallocSizeOf
|
||||
pub data: Measurable<MeasurableCachedMetadata>
|
||||
pub data: Measurable<MeasurableCachedMetadata>,
|
||||
}
|
||||
|
||||
#[derive(Clone, MallocSizeOf)]
|
||||
|
@ -106,7 +105,7 @@ struct MeasurableCachedMetadata {
|
|||
/// Character set.
|
||||
pub charset: Option<String>,
|
||||
/// HTTP Status
|
||||
pub status: Option<(u16, Vec<u8>)>
|
||||
pub status: Option<(u16, Vec<u8>)>,
|
||||
}
|
||||
|
||||
impl MallocSizeOf for CachedMetadata {
|
||||
|
@ -122,7 +121,7 @@ pub struct CachedResponse {
|
|||
/// The response constructed from the cached resource
|
||||
pub response: Response,
|
||||
/// The revalidation flag for the stored response
|
||||
pub needs_validation: bool
|
||||
pub needs_validation: bool,
|
||||
}
|
||||
|
||||
/// A memory cache.
|
||||
|
@ -132,7 +131,6 @@ pub struct HttpCache {
|
|||
entries: HashMap<CacheKey, Vec<CachedResource>>,
|
||||
}
|
||||
|
||||
|
||||
/// Determine if a given response is cacheable based on the initial metadata received.
|
||||
/// Based on <https://tools.ietf.org/html/rfc7234#section-3>
|
||||
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();
|
||||
if headers.contains_key(header::EXPIRES) ||
|
||||
headers.contains_key(header::LAST_MODIFIED) ||
|
||||
headers.contains_key(header::ETAG) {
|
||||
headers.contains_key(header::ETAG)
|
||||
{
|
||||
is_cacheable = true;
|
||||
}
|
||||
if let Some(ref directive) = headers.typed_get::<CacheControl>() {
|
||||
if directive.no_store() {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
if directive.public() || directive.s_max_age().is_some() ||
|
||||
directive.max_age().is_some() || directive.no_cache()
|
||||
if directive.public() ||
|
||||
directive.s_max_age().is_some() ||
|
||||
directive.max_age().is_some() ||
|
||||
directive.no_cache()
|
||||
{
|
||||
is_cacheable = true;
|
||||
}
|
||||
|
@ -245,12 +246,11 @@ fn get_response_expiry(response: &Response) -> Duration {
|
|||
match *code {
|
||||
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>
|
||||
return heuristic_freshness
|
||||
return heuristic_freshness;
|
||||
},
|
||||
_ => {
|
||||
// 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() {
|
||||
return heuristic_freshness;
|
||||
}
|
||||
|
@ -288,25 +288,30 @@ fn get_expiry_adjustment_from_request_headers(request: &Request, expires: Durati
|
|||
return expires - min_fresh;
|
||||
}
|
||||
if directive.no_cache() || directive.no_store() {
|
||||
return Duration::min_value()
|
||||
return Duration::min_value();
|
||||
}
|
||||
|
||||
expires
|
||||
}
|
||||
|
||||
/// 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_headers: &HeaderMap,
|
||||
done_chan: &mut DoneChannel)
|
||||
-> CachedResponse {
|
||||
done_chan: &mut DoneChannel,
|
||||
) -> CachedResponse {
|
||||
let mut response = Response::new(cached_resource.data.metadata.data.final_url.clone());
|
||||
response.headers = cached_headers.clone();
|
||||
response.body = cached_resource.body.clone();
|
||||
if let ResponseBody::Receiving(_) = *cached_resource.body.lock().unwrap() {
|
||||
let (done_sender, done_receiver) = channel();
|
||||
*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.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: if this cache is to be considered shared, take proxy-revalidate into account
|
||||
// <https://tools.ietf.org/html/rfc7234#section-5.2.2.7>
|
||||
let has_expired = (adjusted_expires < time_since_validated) ||
|
||||
(adjusted_expires == time_since_validated);
|
||||
CachedResponse { response: response, needs_validation: has_expired }
|
||||
let has_expired =
|
||||
(adjusted_expires < time_since_validated) || (adjusted_expires == time_since_validated);
|
||||
CachedResponse {
|
||||
response: response,
|
||||
needs_validation: has_expired,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new resource, based on the bytes requested, and an existing resource,
|
||||
/// with a status-code of 206.
|
||||
fn create_resource_with_bytes_from_resource(bytes: &[u8], resource: &CachedResource)
|
||||
-> CachedResource {
|
||||
fn create_resource_with_bytes_from_resource(
|
||||
bytes: &[u8],
|
||||
resource: &CachedResource,
|
||||
) -> CachedResource {
|
||||
CachedResource {
|
||||
request_headers: resource.request_headers.clone(),
|
||||
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(),
|
||||
expires: resource.data.expires.clone(),
|
||||
last_validated: resource.data.last_validated.clone(),
|
||||
})
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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>,
|
||||
range_spec: Vec<(Bound<u64>, Bound<u64>)>,
|
||||
done_chan: &mut DoneChannel)
|
||||
-> Option<CachedResponse> {
|
||||
let mut complete_cached_resources = candidates.iter().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 {
|
||||
Some((ref code, _)) => *code == 206,
|
||||
None => false
|
||||
}
|
||||
});
|
||||
match (range_spec.first().unwrap(), complete_cached_resources.next()) {
|
||||
done_chan: &mut DoneChannel,
|
||||
) -> Option<CachedResponse> {
|
||||
let mut complete_cached_resources =
|
||||
candidates
|
||||
.iter()
|
||||
.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 {
|
||||
Some((ref code, _)) => *code == 206,
|
||||
None => false,
|
||||
});
|
||||
match (
|
||||
range_spec.first().unwrap(),
|
||||
complete_cached_resources.next(),
|
||||
) {
|
||||
// TODO: take the full range spec into account.
|
||||
// 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,
|
||||
|
@ -384,9 +400,11 @@ fn handle_range_request(request: &Request,
|
|||
let e = end as usize + 1;
|
||||
let requested = body.get(b..e);
|
||||
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_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);
|
||||
}
|
||||
}
|
||||
|
@ -400,9 +418,9 @@ fn handle_range_request(request: &Request,
|
|||
if let Some(bytes_range) = range.bytes_range() {
|
||||
bytes_range
|
||||
} else {
|
||||
continue
|
||||
continue;
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => continue,
|
||||
};
|
||||
if res_beginning - 1 < beginning && res_end + 1 > end {
|
||||
|
@ -416,8 +434,10 @@ fn handle_range_request(request: &Request,
|
|||
_ => continue,
|
||||
};
|
||||
if let Some(bytes) = requested {
|
||||
let new_resource = create_resource_with_bytes_from_resource(&bytes, partial_resource);
|
||||
let cached_response = create_cached_response(request, &new_resource, &*headers, done_chan);
|
||||
let new_resource =
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -428,9 +448,11 @@ fn handle_range_request(request: &Request,
|
|||
let b = beginning as usize;
|
||||
let requested = body.get(b..);
|
||||
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_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);
|
||||
}
|
||||
}
|
||||
|
@ -457,8 +479,10 @@ fn handle_range_request(request: &Request,
|
|||
_ => continue,
|
||||
};
|
||||
if let Some(bytes) = requested {
|
||||
let new_resource = create_resource_with_bytes_from_resource(&bytes, partial_resource);
|
||||
let cached_response = create_cached_response(request, &new_resource, &*headers, done_chan);
|
||||
let new_resource =
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -469,9 +493,11 @@ fn handle_range_request(request: &Request,
|
|||
let from_byte = body.len() - offset as usize;
|
||||
let requested = body.get(from_byte..);
|
||||
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_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);
|
||||
}
|
||||
}
|
||||
|
@ -488,7 +514,7 @@ fn handle_range_request(request: &Request,
|
|||
} else {
|
||||
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 requested = match resource_body {
|
||||
&ResponseBody::Done(ref body) => {
|
||||
|
@ -498,38 +524,47 @@ fn handle_range_request(request: &Request,
|
|||
_ => continue,
|
||||
};
|
||||
if let Some(bytes) = requested {
|
||||
let new_resource = create_resource_with_bytes_from_resource(&bytes, partial_resource);
|
||||
let cached_response = create_cached_response(request, &new_resource, &*headers, done_chan);
|
||||
let new_resource =
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// All the cases with Bound::Excluded should be unreachable anyway
|
||||
_ => return None
|
||||
_ => return None,
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
|
||||
impl HttpCache {
|
||||
/// Create a new memory cache instance.
|
||||
pub fn new() -> HttpCache {
|
||||
HttpCache {
|
||||
entries: HashMap::new()
|
||||
entries: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Constructing Responses from Caches.
|
||||
/// <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>
|
||||
if request.method != Method::GET {
|
||||
// Only Get requests are cached, avoid a url based match for others.
|
||||
return None;
|
||||
}
|
||||
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![];
|
||||
for cached_resource in resources {
|
||||
let mut can_be_constructed = true;
|
||||
|
@ -545,7 +580,9 @@ impl HttpCache {
|
|||
match request.headers.get(vary_val) {
|
||||
Some(header_data) => {
|
||||
// If the header is present in the request.
|
||||
if let Some(original_header_data) = original_request_headers.get(vary_val) {
|
||||
if let Some(original_header_data) =
|
||||
original_request_headers.get(vary_val)
|
||||
{
|
||||
// Check that the value of the nominated header field,
|
||||
// in the original request, matches the value in the current request.
|
||||
if original_header_data != header_data {
|
||||
|
@ -558,7 +595,8 @@ impl HttpCache {
|
|||
// If a header field is absent from a request,
|
||||
// it can only match a stored response if those headers,
|
||||
// were also absent in the original request.
|
||||
can_be_constructed = original_request_headers.get(vary_val).is_none();
|
||||
can_be_constructed =
|
||||
original_request_headers.get(vary_val).is_none();
|
||||
},
|
||||
}
|
||||
if !can_be_constructed {
|
||||
|
@ -573,7 +611,12 @@ impl HttpCache {
|
|||
}
|
||||
// Support for range requests
|
||||
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 {
|
||||
// Not a Range request.
|
||||
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,
|
||||
// or using the Date header to return the most recent one.
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -602,7 +646,7 @@ impl HttpCache {
|
|||
let _ = done_sender.send(Data::Payload(completed_body.clone()));
|
||||
let _ = done_sender.send(Data::Done);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -610,7 +654,12 @@ impl HttpCache {
|
|||
|
||||
/// Freshening Stored Responses upon Validation.
|
||||
/// <https://tools.ietf.org/html/rfc7234#section-4.3.4>
|
||||
pub fn refresh(&mut self, request: &Request, response: Response, done_chan: &mut DoneChannel) -> Option<Response> {
|
||||
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));
|
||||
let entry_key = CacheKey::new(request.clone());
|
||||
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.
|
||||
// The response constructed here will replace the 304 one from the network.
|
||||
let in_progress_channel = match *cached_resource.body.lock().unwrap() {
|
||||
ResponseBody::Receiving(..) => {
|
||||
Some(channel())
|
||||
},
|
||||
ResponseBody::Empty | ResponseBody::Done(..) => None
|
||||
ResponseBody::Receiving(..) => Some(channel()),
|
||||
ResponseBody::Empty | ResponseBody::Done(..) => None,
|
||||
};
|
||||
match in_progress_channel {
|
||||
Some((done_sender, 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.
|
||||
// 1. update the headers of 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.status = cached_resource.data.status.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>
|
||||
pub fn invalidate(&mut self, request: &Request, response: &Response) {
|
||||
// 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) {
|
||||
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) {
|
||||
self.invalidate_for_url(&url);
|
||||
|
@ -683,18 +742,23 @@ impl HttpCache {
|
|||
/// Storing Responses in Caches.
|
||||
/// <https://tools.ietf.org/html/rfc7234#section-3>
|
||||
pub fn store(&mut self, request: &Request, response: &Response) {
|
||||
if PREFS.get("network.http-cache.disabled").as_boolean().unwrap_or(false) {
|
||||
return
|
||||
if PREFS
|
||||
.get("network.http-cache.disabled")
|
||||
.as_boolean()
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if request.method != Method::GET {
|
||||
// Only Get requests are cached.
|
||||
return
|
||||
return;
|
||||
}
|
||||
let entry_key = CacheKey::new(request.clone());
|
||||
let metadata = match response.metadata() {
|
||||
Ok(FetchMetadata::Filtered {
|
||||
filtered: _,
|
||||
unsafe_: metadata }) |
|
||||
filtered: _,
|
||||
unsafe_: metadata,
|
||||
}) |
|
||||
Ok(FetchMetadata::Unfiltered(metadata)) => metadata,
|
||||
_ => return,
|
||||
};
|
||||
|
@ -708,8 +772,8 @@ impl HttpCache {
|
|||
final_url: metadata.final_url,
|
||||
content_type: metadata.content_type.map(|v| v.0.to_string()),
|
||||
charset: metadata.charset,
|
||||
status: metadata.status
|
||||
})
|
||||
status: metadata.status,
|
||||
}),
|
||||
};
|
||||
let entry_resource = CachedResource {
|
||||
request_headers: Arc::new(Mutex::new(request.headers.clone())),
|
||||
|
@ -724,11 +788,10 @@ impl HttpCache {
|
|||
raw_status: response.raw_status.clone(),
|
||||
url_list: response.url_list.clone(),
|
||||
expires: expiry,
|
||||
last_validated: time::now()
|
||||
})
|
||||
last_validated: time::now(),
|
||||
}),
|
||||
};
|
||||
let entry = self.entries.entry(entry_key).or_insert(vec![]);
|
||||
entry.push(entry_resource);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -37,39 +37,39 @@ fn decode_bytes_sync(key: LoadKey, bytes: &[u8]) -> DecoderMsg {
|
|||
let image = load_from_memory(bytes);
|
||||
DecoderMsg {
|
||||
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();
|
||||
set_webrender_image_key(webrender_api, &mut image);
|
||||
Ok(Arc::new(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 is_opaque = match image.format {
|
||||
PixelFormat::BGRA8 => {
|
||||
bytes.extend_from_slice(&*image.bytes);
|
||||
pixels::premultiply_inplace(bytes.as_mut_slice())
|
||||
}
|
||||
},
|
||||
PixelFormat::RGB8 => {
|
||||
for bgr in image.bytes.chunks(3) {
|
||||
bytes.extend_from_slice(&[
|
||||
bgr[2],
|
||||
bgr[1],
|
||||
bgr[0],
|
||||
0xff
|
||||
]);
|
||||
bytes.extend_from_slice(&[bgr[2], bgr[1], bgr[0], 0xff]);
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
},
|
||||
PixelFormat::K8 | PixelFormat::KA8 => {
|
||||
panic!("Not support by webrender yet");
|
||||
}
|
||||
},
|
||||
};
|
||||
let descriptor = webrender_api::ImageDescriptor {
|
||||
size: webrender_api::DeviceUintSize::new(image.width, image.height),
|
||||
|
@ -121,20 +121,22 @@ impl AllPendingLoads {
|
|||
}
|
||||
|
||||
fn remove(&mut self, key: &LoadKey) -> Option<PendingLoad> {
|
||||
self.loads.remove(key).
|
||||
and_then(|pending_load| {
|
||||
self.url_to_load_key.remove(&pending_load.url).unwrap();
|
||||
Some(pending_load)
|
||||
})
|
||||
self.loads.remove(key).and_then(|pending_load| {
|
||||
self.url_to_load_key.remove(&pending_load.url).unwrap();
|
||||
Some(pending_load)
|
||||
})
|
||||
}
|
||||
|
||||
fn get_cached<'a>(&'a mut self, url: ServoUrl, can_request: CanRequestImages)
|
||||
-> CacheResult<'a> {
|
||||
fn get_cached<'a>(
|
||||
&'a mut self,
|
||||
url: ServoUrl,
|
||||
can_request: CanRequestImages,
|
||||
) -> CacheResult<'a> {
|
||||
match self.url_to_load_key.entry(url.clone()) {
|
||||
Occupied(url_entry) => {
|
||||
let load_key = url_entry.get();
|
||||
CacheResult::Hit(*load_key, self.loads.get_mut(load_key).unwrap())
|
||||
}
|
||||
},
|
||||
Vacant(url_entry) => {
|
||||
if can_request == CanRequestImages::No {
|
||||
return CacheResult::Miss(None);
|
||||
|
@ -149,9 +151,9 @@ impl AllPendingLoads {
|
|||
Vacant(load_entry) => {
|
||||
let mut_load = load_entry.insert(pending_load);
|
||||
CacheResult::Miss(Some((load_key, mut_load)))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -226,14 +228,12 @@ impl ImageBytes {
|
|||
type LoadKey = PendingImageId;
|
||||
|
||||
struct LoadKeyGenerator {
|
||||
counter: u64
|
||||
counter: u64,
|
||||
}
|
||||
|
||||
impl LoadKeyGenerator {
|
||||
fn new() -> LoadKeyGenerator {
|
||||
LoadKeyGenerator {
|
||||
counter: 0
|
||||
}
|
||||
LoadKeyGenerator { counter: 0 }
|
||||
}
|
||||
fn next(&mut self) -> PendingImageId {
|
||||
self.counter += 1;
|
||||
|
@ -244,7 +244,7 @@ impl LoadKeyGenerator {
|
|||
enum LoadResult {
|
||||
Loaded(Image),
|
||||
PlaceholderLoaded(Arc<Image>),
|
||||
None
|
||||
None,
|
||||
}
|
||||
|
||||
/// Represents an image that is either being loaded
|
||||
|
@ -271,10 +271,10 @@ struct PendingLoad {
|
|||
impl PendingLoad {
|
||||
fn new(url: ServoUrl) -> PendingLoad {
|
||||
PendingLoad {
|
||||
bytes: ImageBytes::InProgress(vec!()),
|
||||
bytes: ImageBytes::InProgress(vec![]),
|
||||
metadata: None,
|
||||
result: None,
|
||||
listeners: vec!(),
|
||||
listeners: vec![],
|
||||
url: url,
|
||||
final_url: None,
|
||||
}
|
||||
|
@ -314,20 +314,24 @@ impl ImageCacheStore {
|
|||
};
|
||||
|
||||
match load_result {
|
||||
LoadResult::Loaded(ref mut image) => set_webrender_image_key(&self.webrender_api, image),
|
||||
LoadResult::PlaceholderLoaded(..) | LoadResult::None => {}
|
||||
LoadResult::Loaded(ref mut image) => {
|
||||
set_webrender_image_key(&self.webrender_api, image)
|
||||
},
|
||||
LoadResult::PlaceholderLoaded(..) | LoadResult::None => {},
|
||||
}
|
||||
|
||||
let url = pending_load.final_url.clone();
|
||||
let image_response = match load_result {
|
||||
LoadResult::Loaded(image) => ImageResponse::Loaded(Arc::new(image), url.unwrap()),
|
||||
LoadResult::PlaceholderLoaded(image) =>
|
||||
ImageResponse::PlaceholderLoaded(image, self.placeholder_url.clone()),
|
||||
LoadResult::PlaceholderLoaded(image) => {
|
||||
ImageResponse::PlaceholderLoaded(image, self.placeholder_url.clone())
|
||||
},
|
||||
LoadResult::None => ImageResponse::None,
|
||||
};
|
||||
|
||||
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 {
|
||||
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
|
||||
/// or the complete load is not fully decoded or is unavailable.
|
||||
fn get_completed_image_if_available(&self,
|
||||
url: &ServoUrl,
|
||||
placeholder: UsePlaceholder)
|
||||
-> Option<Result<ImageOrMetadataAvailable, ImageState>> {
|
||||
fn get_completed_image_if_available(
|
||||
&self,
|
||||
url: &ServoUrl,
|
||||
placeholder: UsePlaceholder,
|
||||
) -> Option<Result<ImageOrMetadataAvailable, ImageState>> {
|
||||
self.completed_loads.get(url).map(|completed_load| {
|
||||
match (&completed_load.image_response, placeholder) {
|
||||
(&ImageResponse::Loaded(ref image, ref url), _) |
|
||||
(&ImageResponse::PlaceholderLoaded(ref image, ref url), UsePlaceholder::Yes) => {
|
||||
Ok(ImageOrMetadataAvailable::ImageAvailable(image.clone(), url.clone()))
|
||||
}
|
||||
(&ImageResponse::PlaceholderLoaded(ref image, ref url), UsePlaceholder::Yes) => Ok(
|
||||
ImageOrMetadataAvailable::ImageAvailable(image.clone(), url.clone()),
|
||||
),
|
||||
(&ImageResponse::PlaceholderLoaded(_, _), UsePlaceholder::No) |
|
||||
(&ImageResponse::None, _) |
|
||||
(&ImageResponse::MetadataLoaded(_), _) => {
|
||||
Err(ImageState::LoadError)
|
||||
}
|
||||
(&ImageResponse::MetadataLoaded(_), _) => Err(ImageState::LoadError),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -383,18 +386,19 @@ impl ImageCache for ImageCacheImpl {
|
|||
placeholder_image: get_placeholder_image(&webrender_api, &rippy_data).ok(),
|
||||
placeholder_url: ServoUrl::parse("chrome://resources/rippy.png").unwrap(),
|
||||
webrender_api: webrender_api,
|
||||
}))
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 else reserve a slot in the cache for the URL if the consumer can request images.
|
||||
fn find_image_or_metadata(&self,
|
||||
url: ServoUrl,
|
||||
use_placeholder: UsePlaceholder,
|
||||
can_request: CanRequestImages)
|
||||
-> Result<ImageOrMetadataAvailable, ImageState> {
|
||||
fn find_image_or_metadata(
|
||||
&self,
|
||||
url: ServoUrl,
|
||||
use_placeholder: UsePlaceholder,
|
||||
can_request: CanRequestImages,
|
||||
) -> Result<ImageOrMetadataAvailable, ImageState> {
|
||||
debug!("Find image or metadata for {}", url);
|
||||
let mut store = self.store.lock().unwrap();
|
||||
if let Some(result) = store.get_completed_image_if_available(&url, use_placeholder) {
|
||||
|
@ -409,24 +413,24 @@ impl ImageCache for ImageCacheImpl {
|
|||
(&Some(Ok(_)), _) => {
|
||||
debug!("Sync decoding {} ({:?})", url, key);
|
||||
decode_bytes_sync(key, &pl.bytes.as_slice())
|
||||
}
|
||||
},
|
||||
(&None, &Some(ref meta)) => {
|
||||
debug!("Metadata available for {} ({:?})", url, key);
|
||||
return Ok(ImageOrMetadataAvailable::MetadataAvailable(meta.clone()))
|
||||
}
|
||||
return Ok(ImageOrMetadataAvailable::MetadataAvailable(meta.clone()));
|
||||
},
|
||||
(&Some(Err(_)), _) | (&None, &None) => {
|
||||
debug!("{} ({:?}) is still pending", url, key);
|
||||
return Err(ImageState::Pending(key));
|
||||
}
|
||||
},
|
||||
},
|
||||
CacheResult::Miss(Some((key, _pl))) => {
|
||||
debug!("Should be requesting {} ({:?})", url, key);
|
||||
return Err(ImageState::NotRequested(key));
|
||||
}
|
||||
},
|
||||
CacheResult::Miss(None) => {
|
||||
debug!("Couldn't find an entry for {}", url);
|
||||
return Err(ImageState::LoadError);
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -468,17 +472,15 @@ impl ImageCache for ImageCacheImpl {
|
|||
let mut store = self.store.lock().unwrap();
|
||||
let pending_load = store.pending_loads.get_by_key_mut(&id).unwrap();
|
||||
let metadata = match response {
|
||||
Ok(meta) => {
|
||||
Some(match meta {
|
||||
FetchMetadata::Unfiltered(m) => m,
|
||||
FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
|
||||
})
|
||||
},
|
||||
Ok(meta) => Some(match meta {
|
||||
FetchMetadata::Unfiltered(m) => m,
|
||||
FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
|
||||
}),
|
||||
Err(_) => None,
|
||||
};
|
||||
let final_url = metadata.as_ref().map(|m| m.final_url.clone());
|
||||
pending_load.final_url = final_url;
|
||||
}
|
||||
},
|
||||
(FetchResponseMsg::ProcessResponseChunk(data), _) => {
|
||||
debug!("Got some data for {:?}", id);
|
||||
let mut store = self.store.lock().unwrap();
|
||||
|
@ -488,16 +490,17 @@ impl ImageCache for ImageCacheImpl {
|
|||
if let None = pending_load.metadata {
|
||||
if let Ok(metadata) = load_from_buf(&pending_load.bytes.as_slice()) {
|
||||
let dimensions = metadata.dimensions();
|
||||
let img_metadata = ImageMetadata { width: dimensions.width,
|
||||
height: dimensions.height };
|
||||
let img_metadata = ImageMetadata {
|
||||
width: dimensions.width,
|
||||
height: dimensions.height,
|
||||
};
|
||||
for listener in &pending_load.listeners {
|
||||
listener.respond(
|
||||
ImageResponse::MetadataLoaded(img_metadata.clone()));
|
||||
listener.respond(ImageResponse::MetadataLoaded(img_metadata.clone()));
|
||||
}
|
||||
pending_load.metadata = Some(img_metadata);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
(FetchResponseMsg::ProcessResponseEOF(result), key) => {
|
||||
debug!("Received EOF for {:?}", key);
|
||||
match result {
|
||||
|
@ -516,20 +519,20 @@ impl ImageCache for ImageCacheImpl {
|
|||
debug!("Image decoded");
|
||||
local_store.lock().unwrap().handle_decoder(msg);
|
||||
});
|
||||
}
|
||||
},
|
||||
Err(_) => {
|
||||
debug!("Processing error for {:?}", key);
|
||||
let mut store = self.store.lock().unwrap();
|
||||
match store.placeholder_image.clone() {
|
||||
Some(placeholder_image) => {
|
||||
store.complete_load(
|
||||
id, LoadResult::PlaceholderLoaded(placeholder_image))
|
||||
}
|
||||
Some(placeholder_image) => store.complete_load(
|
||||
id,
|
||||
LoadResult::PlaceholderLoaded(placeholder_image),
|
||||
),
|
||||
None => store.complete_load(id, LoadResult::None),
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -21,10 +21,14 @@ extern crate immeta;
|
|||
extern crate ipc_channel;
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
#[macro_use] extern crate log;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate malloc_size_of;
|
||||
#[macro_use] extern crate malloc_size_of_derive;
|
||||
#[macro_use] #[no_link] extern crate matches;
|
||||
#[macro_use]
|
||||
extern crate malloc_size_of_derive;
|
||||
#[macro_use]
|
||||
#[no_link]
|
||||
extern crate matches;
|
||||
extern crate mime;
|
||||
extern crate mime_guess;
|
||||
extern crate msg;
|
||||
|
@ -33,7 +37,8 @@ extern crate openssl;
|
|||
extern crate pixels;
|
||||
#[macro_use]
|
||||
extern crate profile_traits;
|
||||
#[macro_use] extern crate serde;
|
||||
#[macro_use]
|
||||
extern crate serde;
|
||||
extern crate serde_json;
|
||||
extern crate servo_allocator;
|
||||
extern crate servo_arc;
|
||||
|
|
|
@ -25,16 +25,17 @@ pub enum MediaType {
|
|||
|
||||
pub enum ApacheBugFlag {
|
||||
On,
|
||||
Off
|
||||
Off,
|
||||
}
|
||||
|
||||
impl ApacheBugFlag {
|
||||
/// <https://mimesniff.spec.whatwg.org/#supplied-mime-type-detection-algorithm>
|
||||
pub fn from_content_type(last_raw_content_type: &[u8]) -> ApacheBugFlag {
|
||||
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=UTF-8" {
|
||||
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=UTF-8"
|
||||
{
|
||||
ApacheBugFlag::On
|
||||
} else {
|
||||
ApacheBugFlag::Off
|
||||
|
@ -45,19 +46,22 @@ impl ApacheBugFlag {
|
|||
#[derive(PartialEq)]
|
||||
pub enum NoSniffFlag {
|
||||
On,
|
||||
Off
|
||||
Off,
|
||||
}
|
||||
|
||||
|
||||
impl MimeClassifier {
|
||||
//Performs MIME Type Sniffing Algorithm (sections 7 and 8)
|
||||
pub fn classify<'a>(&'a self,
|
||||
context: LoadContext,
|
||||
no_sniff_flag: NoSniffFlag,
|
||||
apache_bug_flag: ApacheBugFlag,
|
||||
supplied_type: &Option<Mime>,
|
||||
data: &'a [u8]) -> Mime {
|
||||
let supplied_type_or_octet_stream = supplied_type.clone().unwrap_or(mime::APPLICATION_OCTET_STREAM);
|
||||
pub fn classify<'a>(
|
||||
&'a self,
|
||||
context: LoadContext,
|
||||
no_sniff_flag: NoSniffFlag,
|
||||
apache_bug_flag: ApacheBugFlag,
|
||||
supplied_type: &Option<Mime>,
|
||||
data: &'a [u8],
|
||||
) -> Mime {
|
||||
let supplied_type_or_octet_stream = supplied_type
|
||||
.clone()
|
||||
.unwrap_or(mime::APPLICATION_OCTET_STREAM);
|
||||
match context {
|
||||
LoadContext::Browsing => match *supplied_type {
|
||||
None => self.sniff_unknown_type(no_sniff_flag, data),
|
||||
|
@ -69,30 +73,41 @@ impl MimeClassifier {
|
|||
NoSniffFlag::On => supplied_type.clone(),
|
||||
NoSniffFlag::Off => match apache_bug_flag {
|
||||
ApacheBugFlag::On => self.sniff_text_or_data(data),
|
||||
ApacheBugFlag::Off => match MimeClassifier::get_media_type(supplied_type) {
|
||||
Some(MediaType::Html) => self.feeds_classifier.classify(data),
|
||||
Some(MediaType::Image) => self.image_classifier.classify(data),
|
||||
Some(MediaType::AudioVideo) => self.audio_video_classifier.classify(data),
|
||||
Some(MediaType::Xml) | None => None,
|
||||
}.unwrap_or(supplied_type.clone())
|
||||
}
|
||||
ApacheBugFlag::Off => {
|
||||
match MimeClassifier::get_media_type(supplied_type) {
|
||||
Some(MediaType::Html) => {
|
||||
self.feeds_classifier.classify(data)
|
||||
},
|
||||
Some(MediaType::Image) => {
|
||||
self.image_classifier.classify(data)
|
||||
},
|
||||
Some(MediaType::AudioVideo) => {
|
||||
self.audio_video_classifier.classify(data)
|
||||
},
|
||||
Some(MediaType::Xml) | None => None,
|
||||
}
|
||||
.unwrap_or(supplied_type.clone())
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
LoadContext::Image => {
|
||||
// Section 8.2 Sniffing an image context
|
||||
match MimeClassifier::maybe_get_media_type(supplied_type) {
|
||||
Some(MediaType::Xml) => None,
|
||||
_ => self.image_classifier.classify(data),
|
||||
}.unwrap_or(supplied_type_or_octet_stream)
|
||||
}
|
||||
.unwrap_or(supplied_type_or_octet_stream)
|
||||
},
|
||||
LoadContext::AudioVideo => {
|
||||
// Section 8.3 Sniffing an image context
|
||||
match MimeClassifier::maybe_get_media_type(supplied_type) {
|
||||
Some(MediaType::Xml) => None,
|
||||
_ => self.audio_video_classifier.classify(data),
|
||||
}.unwrap_or(supplied_type_or_octet_stream)
|
||||
}
|
||||
.unwrap_or(supplied_type_or_octet_stream)
|
||||
},
|
||||
LoadContext::Plugin => {
|
||||
// 8.4 Sniffing in a plugin context
|
||||
|
@ -129,7 +144,8 @@ impl MimeClassifier {
|
|||
match MimeClassifier::maybe_get_media_type(supplied_type) {
|
||||
Some(MediaType::Xml) => None,
|
||||
_ => self.font_classifier.classify(data),
|
||||
}.unwrap_or(supplied_type_or_octet_stream)
|
||||
}
|
||||
.unwrap_or(supplied_type_or_octet_stream)
|
||||
},
|
||||
LoadContext::TextTrack => {
|
||||
// 8.8 Sniffing in a text track context
|
||||
|
@ -149,16 +165,16 @@ impl MimeClassifier {
|
|||
}
|
||||
|
||||
pub fn new() -> MimeClassifier {
|
||||
MimeClassifier {
|
||||
image_classifier: GroupedClassifier::image_classifer(),
|
||||
audio_video_classifier: GroupedClassifier::audio_video_classifier(),
|
||||
scriptable_classifier: GroupedClassifier::scriptable_classifier(),
|
||||
plaintext_classifier: GroupedClassifier::plaintext_classifier(),
|
||||
archive_classifier: GroupedClassifier::archive_classifier(),
|
||||
binary_or_plaintext: BinaryOrPlaintextClassifier,
|
||||
feeds_classifier: FeedsClassifier,
|
||||
font_classifier: GroupedClassifier::font_classifier()
|
||||
}
|
||||
MimeClassifier {
|
||||
image_classifier: GroupedClassifier::image_classifer(),
|
||||
audio_video_classifier: GroupedClassifier::audio_video_classifier(),
|
||||
scriptable_classifier: GroupedClassifier::scriptable_classifier(),
|
||||
plaintext_classifier: GroupedClassifier::plaintext_classifier(),
|
||||
archive_classifier: GroupedClassifier::archive_classifier(),
|
||||
binary_or_plaintext: BinaryOrPlaintextClassifier,
|
||||
feeds_classifier: FeedsClassifier,
|
||||
font_classifier: GroupedClassifier::font_classifier(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn validate(&self) -> Result<(), String> {
|
||||
|
@ -182,7 +198,8 @@ impl MimeClassifier {
|
|||
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.audio_video_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 {
|
||||
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 {
|
||||
mt.suffix() == Some(mime::XML) ||
|
||||
(mt.type_() == mime::APPLICATION && mt.subtype() == mime::XML) ||
|
||||
(mt.type_() == mime::TEXT && mt.subtype() == mime::XML)
|
||||
(mt.type_() == mime::APPLICATION && mt.subtype() == mime::XML) ||
|
||||
(mt.type_() == mime::TEXT && mt.subtype() == mime::XML)
|
||||
}
|
||||
|
||||
fn is_html(mt: &Mime) -> bool {
|
||||
|
@ -210,21 +229,21 @@ impl MimeClassifier {
|
|||
|
||||
fn is_audio_video(mt: &Mime) -> bool {
|
||||
mt.type_() == mime::AUDIO ||
|
||||
mt.type_() == mime::VIDEO ||
|
||||
mt.type_() == mime::APPLICATION && mt.subtype() == mime::OGG
|
||||
mt.type_() == mime::VIDEO ||
|
||||
mt.type_() == mime::APPLICATION && mt.subtype() == mime::OGG
|
||||
}
|
||||
|
||||
fn is_explicit_unknown(mt: &Mime) -> bool {
|
||||
mt.type_().as_str() == "unknown" && mt.subtype().as_str() == "unknown" ||
|
||||
mt.type_() == mime::APPLICATION && mt.subtype().as_str() == "unknown" ||
|
||||
mt.type_() == mime::STAR && mt.subtype() == mime::STAR
|
||||
mt.type_() == mime::APPLICATION && mt.subtype().as_str() == "unknown" ||
|
||||
mt.type_() == mime::STAR && mt.subtype() == mime::STAR
|
||||
}
|
||||
|
||||
fn get_media_type(mime: &Mime) -> Option<MediaType> {
|
||||
if MimeClassifier::is_xml(&mime) {
|
||||
Some(MediaType::Xml)
|
||||
} else if MimeClassifier::is_html(&mime) {
|
||||
Some(MediaType::Html)
|
||||
Some(MediaType::Html)
|
||||
} else if MimeClassifier::is_image(&mime) {
|
||||
Some(MediaType::Image)
|
||||
} else if MimeClassifier::is_audio_video(&mime) {
|
||||
|
@ -235,9 +254,9 @@ impl MimeClassifier {
|
|||
}
|
||||
|
||||
fn maybe_get_media_type(supplied_type: &Option<Mime>) -> Option<MediaType> {
|
||||
supplied_type.as_ref().and_then(|ref mime| {
|
||||
MimeClassifier::get_media_type(mime)
|
||||
})
|
||||
supplied_type
|
||||
.as_ref()
|
||||
.and_then(|ref mime| MimeClassifier::get_media_type(mime))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -252,7 +271,7 @@ trait Matches {
|
|||
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.
|
||||
// see if the next matches.len() bytes in data_iterator equal matches
|
||||
// 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 {
|
||||
if self.clone().nth(matches.len()).is_none() {
|
||||
// there are less than matches.len() elements in self
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
let result = self.clone().zip(matches).all(|(s, m)| *s == *m);
|
||||
if result {
|
||||
|
@ -294,64 +313,68 @@ impl ByteMatcher {
|
|||
} else if data == self.pattern {
|
||||
Some(self.pattern.len())
|
||||
} else {
|
||||
data[..data.len() - self.pattern.len() + 1].iter()
|
||||
data[..data.len() - self.pattern.len() + 1]
|
||||
.iter()
|
||||
.position(|x| !self.leading_ignore.contains(x))
|
||||
.and_then(|start|
|
||||
if data[start..].iter()
|
||||
.zip(self.pattern.iter()).zip(self.mask.iter())
|
||||
.all(|((&data, &pattern), &mask)| (data & mask) == pattern) {
|
||||
.and_then(|start| {
|
||||
if data[start..]
|
||||
.iter()
|
||||
.zip(self.pattern.iter())
|
||||
.zip(self.mask.iter())
|
||||
.all(|((&data, &pattern), &mask)| (data & mask) == pattern)
|
||||
{
|
||||
Some(start + self.pattern.len())
|
||||
} else {
|
||||
None
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MIMEChecker for ByteMatcher {
|
||||
fn classify(&self, data: &[u8]) -> Option<Mime> {
|
||||
self.matches(data).map(|_| {
|
||||
self.content_type.clone()
|
||||
})
|
||||
self.matches(data).map(|_| self.content_type.clone())
|
||||
}
|
||||
|
||||
fn validate(&self) -> Result<(), String> {
|
||||
if self.pattern.len() == 0 {
|
||||
return Err(format!(
|
||||
"Zero length pattern for {:?}",
|
||||
self.content_type
|
||||
))
|
||||
return Err(format!("Zero length pattern for {:?}", self.content_type));
|
||||
}
|
||||
if self.pattern.len() != self.mask.len() {
|
||||
return Err(format!(
|
||||
"Unequal pattern and mask length for {:?}",
|
||||
self.content_type
|
||||
))
|
||||
));
|
||||
}
|
||||
if self.pattern.iter().zip(self.mask.iter()).any(
|
||||
|(&pattern, &mask)| pattern & mask != pattern
|
||||
) {
|
||||
if self
|
||||
.pattern
|
||||
.iter()
|
||||
.zip(self.mask.iter())
|
||||
.any(|(&pattern, &mask)| pattern & mask != pattern)
|
||||
{
|
||||
return Err(format!(
|
||||
"Pattern not pre-masked for {:?}",
|
||||
self.content_type
|
||||
))
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct TagTerminatedByteMatcher {
|
||||
matcher: ByteMatcher
|
||||
matcher: ByteMatcher,
|
||||
}
|
||||
|
||||
impl MIMEChecker for TagTerminatedByteMatcher {
|
||||
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'>') {
|
||||
Some(self.matcher.content_type.clone())
|
||||
} else {
|
||||
None
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn validate(&self) -> Result<(), String> {
|
||||
|
@ -367,8 +390,10 @@ impl Mp4Matcher {
|
|||
return false;
|
||||
}
|
||||
|
||||
let box_size = ((data[0] as u32) << 24 | (data[1] as u32) << 16 |
|
||||
(data[2] as u32) << 8 | (data[3] as u32)) as usize;
|
||||
let box_size = ((data[0] as u32) << 24 |
|
||||
(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) {
|
||||
return false;
|
||||
}
|
||||
|
@ -380,9 +405,10 @@ impl Mp4Matcher {
|
|||
|
||||
let mp4 = [0x6D, 0x70, 0x34];
|
||||
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 {
|
||||
fn classify(&self, data: &[u8]) -> Option<Mime> {
|
||||
|
@ -403,14 +429,16 @@ struct BinaryOrPlaintextClassifier;
|
|||
impl BinaryOrPlaintextClassifier {
|
||||
fn classify_impl(&self, data: &[u8]) -> Mime {
|
||||
if data.starts_with(&[0xFFu8, 0xFEu8]) ||
|
||||
data.starts_with(&[0xFEu8, 0xFFu8]) ||
|
||||
data.starts_with(&[0xEFu8, 0xBBu8, 0xBFu8])
|
||||
data.starts_with(&[0xFEu8, 0xFFu8]) ||
|
||||
data.starts_with(&[0xEFu8, 0xBBu8, 0xBFu8])
|
||||
{
|
||||
mime::TEXT_PLAIN
|
||||
} else if data.iter().any(|&x| x <= 0x08u8 ||
|
||||
x == 0x0Bu8 ||
|
||||
(x >= 0x0Eu8 && x <= 0x1Au8) ||
|
||||
(x >= 0x1Cu8 && x <= 0x1Fu8)) {
|
||||
} else if data.iter().any(|&x| {
|
||||
x <= 0x08u8 ||
|
||||
x == 0x0Bu8 ||
|
||||
(x >= 0x0Eu8 && x <= 0x1Au8) ||
|
||||
(x >= 0x1Cu8 && x <= 0x1Fu8)
|
||||
}) {
|
||||
mime::APPLICATION_OCTET_STREAM
|
||||
} else {
|
||||
mime::TEXT_PLAIN
|
||||
|
@ -425,7 +453,6 @@ impl MIMEChecker for BinaryOrPlaintextClassifier {
|
|||
fn validate(&self) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
}
|
||||
struct GroupedClassifier {
|
||||
byte_matchers: Vec<Box<MIMEChecker + Send + Sync>>,
|
||||
|
@ -442,7 +469,7 @@ impl GroupedClassifier {
|
|||
Box::new(ByteMatcher::image_webp()),
|
||||
Box::new(ByteMatcher::image_png()),
|
||||
Box::new(ByteMatcher::image_jpeg()),
|
||||
]
|
||||
],
|
||||
}
|
||||
}
|
||||
fn audio_video_classifier() -> GroupedClassifier {
|
||||
|
@ -456,8 +483,8 @@ impl GroupedClassifier {
|
|||
Box::new(ByteMatcher::audio_midi()),
|
||||
Box::new(ByteMatcher::video_avi()),
|
||||
Box::new(ByteMatcher::audio_wave()),
|
||||
Box::new(Mp4Matcher)
|
||||
]
|
||||
Box::new(Mp4Matcher),
|
||||
],
|
||||
}
|
||||
}
|
||||
fn scriptable_classifier() -> GroupedClassifier {
|
||||
|
@ -481,8 +508,8 @@ impl GroupedClassifier {
|
|||
Box::new(ByteMatcher::text_html_p()),
|
||||
Box::new(ByteMatcher::text_html_comment()),
|
||||
Box::new(ByteMatcher::text_xml()),
|
||||
Box::new(ByteMatcher::application_pdf())
|
||||
]
|
||||
Box::new(ByteMatcher::application_pdf()),
|
||||
],
|
||||
}
|
||||
}
|
||||
fn plaintext_classifier() -> GroupedClassifier {
|
||||
|
@ -491,8 +518,8 @@ impl GroupedClassifier {
|
|||
Box::new(ByteMatcher::text_plain_utf_8_bom()),
|
||||
Box::new(ByteMatcher::text_plain_utf_16le_bom()),
|
||||
Box::new(ByteMatcher::text_plain_utf_16be_bom()),
|
||||
Box::new(ByteMatcher::application_postscript())
|
||||
]
|
||||
Box::new(ByteMatcher::application_postscript()),
|
||||
],
|
||||
}
|
||||
}
|
||||
fn archive_classifier() -> GroupedClassifier {
|
||||
|
@ -500,8 +527,8 @@ impl GroupedClassifier {
|
|||
byte_matchers: vec![
|
||||
Box::new(ByteMatcher::application_x_gzip()),
|
||||
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::true_type()),
|
||||
Box::new(ByteMatcher::application_vnd_ms_font_object()),
|
||||
]
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -536,7 +563,7 @@ impl MIMEChecker for GroupedClassifier {
|
|||
enum Match {
|
||||
Start,
|
||||
DidNotMatch,
|
||||
StartAndEnd
|
||||
StartAndEnd,
|
||||
}
|
||||
|
||||
impl Match {
|
||||
|
@ -549,7 +576,9 @@ impl 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) {
|
||||
Match::DidNotMatch
|
||||
} else if end.len() == 1 {
|
||||
|
@ -593,11 +622,12 @@ impl FeedsClassifier {
|
|||
|
||||
// Steps 5.2.1 to 5.2.4
|
||||
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::DidNotMatch => {},
|
||||
Match::Start => return None
|
||||
Match::Start => return None,
|
||||
}
|
||||
|
||||
// Step 5.2.5
|
||||
|
@ -611,15 +641,21 @@ impl FeedsClassifier {
|
|||
// Step 5.2.7
|
||||
if matcher.matches(b"rdf:RDF") {
|
||||
while matcher.next().is_some() {
|
||||
match eats_until(&mut matcher,
|
||||
b"http://purl.org/rss/1.0/",
|
||||
b"http://www.w3.org/1999/02/22-rdf-syntax-ns#")
|
||||
.chain(|| eats_until(&mut matcher,
|
||||
b"http://www.w3.org/1999/02/22-rdf-syntax-ns#",
|
||||
b"http://purl.org/rss/1.0/")) {
|
||||
match eats_until(
|
||||
&mut matcher,
|
||||
b"http://purl.org/rss/1.0/",
|
||||
b"http://www.w3.org/1999/02/22-rdf-syntax-ns#",
|
||||
)
|
||||
.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::DidNotMatch => {},
|
||||
Match::Start => return None
|
||||
Match::Start => return None,
|
||||
}
|
||||
}
|
||||
return None;
|
||||
|
@ -630,7 +666,7 @@ impl FeedsClassifier {
|
|||
|
||||
impl MIMEChecker for FeedsClassifier {
|
||||
fn classify(&self, data: &[u8]) -> Option<Mime> {
|
||||
self.classify_impl(data)
|
||||
self.classify_impl(data)
|
||||
}
|
||||
|
||||
fn validate(&self) -> Result<(), String> {
|
||||
|
@ -647,7 +683,7 @@ impl ByteMatcher {
|
|||
pattern: b"\x00\x00\x01\x00",
|
||||
mask: b"\xFF\xFF\xFF\xFF",
|
||||
content_type: "image/x-icon".parse().unwrap(),
|
||||
leading_ignore: &[]
|
||||
leading_ignore: &[],
|
||||
}
|
||||
}
|
||||
//A Windows Cursor signature.
|
||||
|
@ -656,7 +692,7 @@ impl ByteMatcher {
|
|||
pattern: b"\x00\x00\x02\x00",
|
||||
mask: b"\xFF\xFF\xFF\xFF",
|
||||
content_type: "image/x-icon".parse().unwrap(),
|
||||
leading_ignore: &[]
|
||||
leading_ignore: &[],
|
||||
}
|
||||
}
|
||||
//The string "BM", a BMP signature.
|
||||
|
@ -665,7 +701,7 @@ impl ByteMatcher {
|
|||
pattern: b"BM",
|
||||
mask: b"\xFF\xFF",
|
||||
content_type: mime::IMAGE_BMP,
|
||||
leading_ignore: &[]
|
||||
leading_ignore: &[],
|
||||
}
|
||||
}
|
||||
//The string "GIF89a", a GIF signature.
|
||||
|
@ -674,7 +710,7 @@ impl ByteMatcher {
|
|||
pattern: b"GIF89a",
|
||||
mask: b"\xFF\xFF\xFF\xFF\xFF\xFF",
|
||||
content_type: mime::IMAGE_GIF,
|
||||
leading_ignore: &[]
|
||||
leading_ignore: &[],
|
||||
}
|
||||
}
|
||||
//The string "GIF87a", a GIF signature.
|
||||
|
@ -683,7 +719,7 @@ impl ByteMatcher {
|
|||
pattern: b"GIF87a",
|
||||
mask: b"\xFF\xFF\xFF\xFF\xFF\xFF",
|
||||
content_type: mime::IMAGE_GIF,
|
||||
leading_ignore: &[]
|
||||
leading_ignore: &[],
|
||||
}
|
||||
}
|
||||
//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",
|
||||
mask: b"\xFF\xFF\xFF\xFF\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF",
|
||||
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
|
||||
|
@ -702,7 +738,7 @@ impl ByteMatcher {
|
|||
pattern: b"\x89PNG\r\n\x1A\n",
|
||||
mask: b"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF",
|
||||
content_type: mime::IMAGE_PNG,
|
||||
leading_ignore: &[]
|
||||
leading_ignore: &[],
|
||||
}
|
||||
}
|
||||
// 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",
|
||||
mask: b"\xFF\xFF\xFF",
|
||||
content_type: mime::IMAGE_JPEG,
|
||||
leading_ignore: &[]
|
||||
leading_ignore: &[],
|
||||
}
|
||||
}
|
||||
//The WebM signature. [TODO: Use more bytes?]
|
||||
|
@ -720,7 +756,7 @@ impl ByteMatcher {
|
|||
pattern: b"\x1A\x45\xDF\xA3",
|
||||
mask: b"\xFF\xFF\xFF\xFF",
|
||||
content_type: "video/webm".parse().unwrap(),
|
||||
leading_ignore: &[]
|
||||
leading_ignore: &[],
|
||||
}
|
||||
}
|
||||
//The string ".snd", the basic audio signature.
|
||||
|
@ -729,16 +765,16 @@ impl ByteMatcher {
|
|||
pattern: b".snd",
|
||||
mask: b"\xFF\xFF\xFF\xFF",
|
||||
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.
|
||||
fn audio_aiff() -> ByteMatcher {
|
||||
ByteMatcher {
|
||||
pattern: b"FORM\x00\x00\x00\x00AIFF",
|
||||
mask: b"\xFF\xFF\xFF\xFF\x00\x00\x00\x00\xFF\xFF\xFF\xFF",
|
||||
pattern: b"FORM\x00\x00\x00\x00AIFF",
|
||||
mask: b"\xFF\xFF\xFF\xFF\x00\x00\x00\x00\xFF\xFF\xFF\xFF",
|
||||
content_type: "audio/aiff".parse().unwrap(),
|
||||
leading_ignore: &[]
|
||||
leading_ignore: &[],
|
||||
}
|
||||
}
|
||||
//The string "ID3", the ID3v2-tagged MP3 signature.
|
||||
|
@ -747,7 +783,7 @@ impl ByteMatcher {
|
|||
pattern: b"ID3",
|
||||
mask: b"\xFF\xFF\xFF",
|
||||
content_type: "audio/mpeg".parse().unwrap(),
|
||||
leading_ignore: &[]
|
||||
leading_ignore: &[],
|
||||
}
|
||||
}
|
||||
//The string "OggS" followed by NUL, the Ogg container signature.
|
||||
|
@ -756,7 +792,7 @@ impl ByteMatcher {
|
|||
pattern: b"OggS\x00",
|
||||
mask: b"\xFF\xFF\xFF\xFF\xFF",
|
||||
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),
|
||||
|
@ -766,7 +802,7 @@ impl ByteMatcher {
|
|||
pattern: b"MThd\x00\x00\x00\x06",
|
||||
mask: b"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF",
|
||||
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.
|
||||
|
@ -775,7 +811,7 @@ impl ByteMatcher {
|
|||
pattern: b"RIFF\x00\x00\x00\x00AVI ",
|
||||
mask: b"\xFF\xFF\xFF\xFF\x00\x00\x00\x00\xFF\xFF\xFF\xFF",
|
||||
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.
|
||||
|
@ -784,7 +820,7 @@ impl ByteMatcher {
|
|||
pattern: b"RIFF\x00\x00\x00\x00WAVE",
|
||||
mask: b"\xFF\xFF\xFF\xFF\x00\x00\x00\x00\xFF\xFF\xFF\xFF",
|
||||
content_type: "audio/wave".parse().unwrap(),
|
||||
leading_ignore: &[]
|
||||
leading_ignore: &[],
|
||||
}
|
||||
}
|
||||
// doctype terminated with Tag terminating (TT) Byte
|
||||
|
@ -794,8 +830,8 @@ impl ByteMatcher {
|
|||
pattern: b"<!DOCTYPE HTML",
|
||||
mask: b"\xFF\xFF\xDF\xDF\xDF\xDF\xDF\xDF\xDF\xFF\xDF\xDF\xDF\xDF",
|
||||
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",
|
||||
mask: b"\xFF\xDF\xDF\xDF\xDF",
|
||||
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
|
||||
fn text_html_head() -> TagTerminatedByteMatcher {
|
||||
TagTerminatedByteMatcher {
|
||||
TagTerminatedByteMatcher {
|
||||
matcher: ByteMatcher {
|
||||
pattern: b"<HEAD",
|
||||
mask: b"\xFF\xDF\xDF\xDF\xDF",
|
||||
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",
|
||||
mask: b"\xFF\xDF\xDF\xDF\xDF\xDF\xDF",
|
||||
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",
|
||||
mask: b"\xFF\xDF\xDF\xDF\xDF\xDF\xDF",
|
||||
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",
|
||||
mask: b"\xFF\xDF\xFF",
|
||||
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",
|
||||
mask: b"\xFF\xDF\xDF\xDF",
|
||||
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",
|
||||
mask: b"\xFF\xDF\xDF\xDF\xDF",
|
||||
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 {
|
||||
TagTerminatedByteMatcher {
|
||||
matcher: ByteMatcher {
|
||||
pattern: b"<TABLE",
|
||||
mask: b"\xFF\xDF\xDF\xDF\xDF\xDF",
|
||||
content_type: mime::TEXT_HTML,
|
||||
leading_ignore: b"\t\n\x0C\r "
|
||||
}
|
||||
pattern: b"<TABLE",
|
||||
mask: b"\xFF\xDF\xDF\xDF\xDF\xDF",
|
||||
content_type: mime::TEXT_HTML,
|
||||
leading_ignore: b"\t\n\x0C\r ",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -902,8 +938,8 @@ impl ByteMatcher {
|
|||
pattern: b"<A",
|
||||
mask: b"\xFF\xDF",
|
||||
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",
|
||||
mask: b"\xFF\xDF\xDF\xDF\xDF\xDF",
|
||||
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",
|
||||
mask: b"\xFF\xDF\xDF\xDF\xDF\xDF",
|
||||
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",
|
||||
mask: b"\xFF\xDF",
|
||||
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",
|
||||
mask: b"\xFF\xDF\xDF\xDF\xDF",
|
||||
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",
|
||||
mask: b"\xFF\xDF\xDF",
|
||||
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",
|
||||
mask: b"\xFF\xDF",
|
||||
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"<!--",
|
||||
mask: b"\xFF\xFF\xFF\xFF",
|
||||
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",
|
||||
mask: b"\xFF\xFF\xFF\xFF\xFF",
|
||||
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.
|
||||
|
@ -1006,7 +1042,7 @@ impl ByteMatcher {
|
|||
pattern: b"%PDF-",
|
||||
mask: b"\xFF\xFF\xFF\xFF\xFF",
|
||||
content_type: mime::APPLICATION_PDF,
|
||||
leading_ignore: &[]
|
||||
leading_ignore: &[],
|
||||
}
|
||||
}
|
||||
//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\xFF\xFF",
|
||||
content_type: "application/vnd.ms-fontobject".parse().unwrap(),
|
||||
leading_ignore: &[]
|
||||
leading_ignore: &[],
|
||||
}
|
||||
}
|
||||
//4 bytes representing the version number 1.0, a TrueType signature.
|
||||
|
@ -1028,7 +1064,7 @@ impl ByteMatcher {
|
|||
pattern: b"\x00\x01\x00\x00",
|
||||
mask: b"\xFF\xFF\xFF\xFF",
|
||||
content_type: "application/font-sfnt".parse().unwrap(),
|
||||
leading_ignore: &[]
|
||||
leading_ignore: &[],
|
||||
}
|
||||
}
|
||||
//The string "OTTO", the OpenType signature.
|
||||
|
@ -1037,7 +1073,7 @@ impl ByteMatcher {
|
|||
pattern: b"OTTO",
|
||||
mask: b"\xFF\xFF\xFF\xFF",
|
||||
content_type: "application/font-sfnt".parse().unwrap(),
|
||||
leading_ignore: &[]
|
||||
leading_ignore: &[],
|
||||
}
|
||||
}
|
||||
// The string "ttcf", the TrueType Collection signature.
|
||||
|
@ -1046,7 +1082,7 @@ impl ByteMatcher {
|
|||
pattern: b"ttcf",
|
||||
mask: b"\xFF\xFF\xFF\xFF",
|
||||
content_type: "application/font-sfnt".parse().unwrap(),
|
||||
leading_ignore: &[]
|
||||
leading_ignore: &[],
|
||||
}
|
||||
}
|
||||
// The string "wOFF", the Web Open Font Format signature.
|
||||
|
@ -1055,7 +1091,7 @@ impl ByteMatcher {
|
|||
pattern: b"wOFF",
|
||||
mask: b"\xFF\xFF\xFF\xFF",
|
||||
content_type: "application/font-woff".parse().unwrap(),
|
||||
leading_ignore: &[]
|
||||
leading_ignore: &[],
|
||||
}
|
||||
}
|
||||
//The GZIP archive signature.
|
||||
|
@ -1064,7 +1100,7 @@ impl ByteMatcher {
|
|||
pattern: b"\x1F\x8B\x08",
|
||||
mask: b"\xFF\xFF\xFF",
|
||||
content_type: "application/x-gzip".parse().unwrap(),
|
||||
leading_ignore: &[]
|
||||
leading_ignore: &[],
|
||||
}
|
||||
}
|
||||
//The string "PK" followed by ETX EOT, the ZIP archive signature.
|
||||
|
@ -1073,7 +1109,7 @@ impl ByteMatcher {
|
|||
pattern: b"PK\x03\x04",
|
||||
mask: b"\xFF\xFF\xFF\xFF",
|
||||
content_type: "application/zip".parse().unwrap(),
|
||||
leading_ignore: &[]
|
||||
leading_ignore: &[],
|
||||
}
|
||||
}
|
||||
//The string "Rar " followed by SUB BEL NUL, the RAR archive signature.
|
||||
|
@ -1082,16 +1118,16 @@ impl ByteMatcher {
|
|||
pattern: b"Rar \x1A\x07\x00",
|
||||
mask: b"\xFF\xFF\xFF\xFF\xFF\xFF\xFF",
|
||||
content_type: "application/x-rar-compressed".parse().unwrap(),
|
||||
leading_ignore: &[]
|
||||
leading_ignore: &[],
|
||||
}
|
||||
}
|
||||
// The string "%!PS-Adobe-", the PostScript signature.
|
||||
fn application_postscript() -> ByteMatcher {
|
||||
ByteMatcher {
|
||||
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(),
|
||||
leading_ignore: &[]
|
||||
leading_ignore: &[],
|
||||
}
|
||||
}
|
||||
// UTF-16BE BOM
|
||||
|
@ -1100,7 +1136,7 @@ impl ByteMatcher {
|
|||
pattern: b"\xFE\xFF\x00\x00",
|
||||
mask: b"\xFF\xFF\x00\x00",
|
||||
content_type: mime::TEXT_PLAIN,
|
||||
leading_ignore: &[]
|
||||
leading_ignore: &[],
|
||||
}
|
||||
}
|
||||
//UTF-16LE BOM
|
||||
|
@ -1109,7 +1145,7 @@ impl ByteMatcher {
|
|||
pattern: b"\xFF\xFE\x00\x00",
|
||||
mask: b"\xFF\xFF\x00\x00",
|
||||
content_type: mime::TEXT_PLAIN,
|
||||
leading_ignore: &[]
|
||||
leading_ignore: &[],
|
||||
}
|
||||
}
|
||||
//UTF-8 BOM
|
||||
|
@ -1118,7 +1154,7 @@ impl ByteMatcher {
|
|||
pattern: b"\xEF\xBB\xBF\x00",
|
||||
mask: b"\xFF\xFF\xFF\x00",
|
||||
content_type: mime::TEXT_PLAIN,
|
||||
leading_ignore: &[]
|
||||
leading_ignore: &[],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,59 +48,65 @@ use storage_thread::StorageThreadFactory;
|
|||
use websocket_loader;
|
||||
|
||||
/// Returns a tuple of (public, private) senders to the new threads.
|
||||
pub fn new_resource_threads(user_agent: Cow<'static, str>,
|
||||
devtools_chan: Option<Sender<DevtoolsControlMsg>>,
|
||||
time_profiler_chan: ProfilerChan,
|
||||
mem_profiler_chan: MemProfilerChan,
|
||||
embedder_proxy: EmbedderProxy,
|
||||
config_dir: Option<PathBuf>)
|
||||
-> (ResourceThreads, ResourceThreads) {
|
||||
pub fn new_resource_threads(
|
||||
user_agent: Cow<'static, str>,
|
||||
devtools_chan: Option<Sender<DevtoolsControlMsg>>,
|
||||
time_profiler_chan: ProfilerChan,
|
||||
mem_profiler_chan: MemProfilerChan,
|
||||
embedder_proxy: EmbedderProxy,
|
||||
config_dir: Option<PathBuf>,
|
||||
) -> (ResourceThreads, ResourceThreads) {
|
||||
let (public_core, private_core) = new_core_resource_thread(
|
||||
user_agent,
|
||||
devtools_chan,
|
||||
time_profiler_chan,
|
||||
mem_profiler_chan,
|
||||
embedder_proxy,
|
||||
config_dir.clone());
|
||||
config_dir.clone(),
|
||||
);
|
||||
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
|
||||
pub fn new_core_resource_thread(user_agent: Cow<'static, str>,
|
||||
devtools_chan: Option<Sender<DevtoolsControlMsg>>,
|
||||
time_profiler_chan: ProfilerChan,
|
||||
mem_profiler_chan: MemProfilerChan,
|
||||
embedder_proxy: EmbedderProxy,
|
||||
config_dir: Option<PathBuf>)
|
||||
-> (CoreResourceThread, CoreResourceThread) {
|
||||
pub fn new_core_resource_thread(
|
||||
user_agent: Cow<'static, str>,
|
||||
devtools_chan: Option<Sender<DevtoolsControlMsg>>,
|
||||
time_profiler_chan: ProfilerChan,
|
||||
mem_profiler_chan: MemProfilerChan,
|
||||
embedder_proxy: EmbedderProxy,
|
||||
config_dir: Option<PathBuf>,
|
||||
) -> (CoreResourceThread, CoreResourceThread) {
|
||||
let (public_setup_chan, public_setup_port) = ipc::channel().unwrap();
|
||||
let (private_setup_chan, private_setup_port) = ipc::channel().unwrap();
|
||||
let (report_chan, report_port) = ipc::channel().unwrap();
|
||||
|
||||
thread::Builder::new().name("ResourceManager".to_owned()).spawn(move || {
|
||||
let resource_manager = CoreResourceManager::new(
|
||||
user_agent, devtools_chan, time_profiler_chan, embedder_proxy
|
||||
);
|
||||
thread::Builder::new()
|
||||
.name("ResourceManager".to_owned())
|
||||
.spawn(move || {
|
||||
let resource_manager = CoreResourceManager::new(
|
||||
user_agent,
|
||||
devtools_chan,
|
||||
time_profiler_chan,
|
||||
embedder_proxy,
|
||||
);
|
||||
|
||||
let mut channel_manager = ResourceChannelManager {
|
||||
resource_manager: resource_manager,
|
||||
config_dir: config_dir,
|
||||
};
|
||||
let mut channel_manager = ResourceChannelManager {
|
||||
resource_manager: resource_manager,
|
||||
config_dir: config_dir,
|
||||
};
|
||||
|
||||
mem_profiler_chan.run_with_memory_reporting(|| (
|
||||
channel_manager.start(
|
||||
public_setup_port,
|
||||
private_setup_port,
|
||||
report_port)
|
||||
),
|
||||
String::from("network-cache-reporter"),
|
||||
report_chan,
|
||||
|report_chan| report_chan);
|
||||
|
||||
}).expect("Thread spawning failed");
|
||||
mem_profiler_chan.run_with_memory_reporting(
|
||||
|| (channel_manager.start(public_setup_port, private_setup_port, report_port)),
|
||||
String::from("network-cache-reporter"),
|
||||
report_chan,
|
||||
|report_chan| report_chan,
|
||||
);
|
||||
})
|
||||
.expect("Thread spawning failed");
|
||||
(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 {
|
||||
Some(ref path) => {
|
||||
fs::read_to_string(path).expect("Couldn't not find certificate file")
|
||||
}
|
||||
None => {
|
||||
resources::read_string(Resource::SSLCertificates)
|
||||
},
|
||||
Some(ref path) => fs::read_to_string(path).expect("Couldn't not find certificate file"),
|
||||
None => resources::read_string(Resource::SSLCertificates),
|
||||
};
|
||||
|
||||
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 {
|
||||
#[allow(unsafe_code)]
|
||||
fn start(&mut self,
|
||||
public_receiver: IpcReceiver<CoreResourceMsg>,
|
||||
private_receiver: IpcReceiver<CoreResourceMsg>,
|
||||
memory_reporter: IpcReceiver<ReportsChan>) {
|
||||
fn start(
|
||||
&mut self,
|
||||
public_receiver: IpcReceiver<CoreResourceMsg>,
|
||||
private_receiver: IpcReceiver<CoreResourceMsg>,
|
||||
memory_reporter: IpcReceiver<ReportsChan>,
|
||||
) {
|
||||
let (public_http_state, private_http_state) =
|
||||
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.
|
||||
match receiver {
|
||||
ipc::IpcSelectionResult::ChannelClosed(..) => continue,
|
||||
_ => {}
|
||||
_ => {},
|
||||
}
|
||||
let (id, data) = receiver.unwrap();
|
||||
// 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,
|
||||
msg: ReportsChan,
|
||||
public_http_state: &Arc<HttpState>,
|
||||
private_http_state: &Arc<HttpState>) {
|
||||
fn process_report(
|
||||
&mut self,
|
||||
msg: ReportsChan,
|
||||
public_http_state: &Arc<HttpState>,
|
||||
private_http_state: &Arc<HttpState>,
|
||||
) {
|
||||
let mut ops = MallocSizeOfOps::new(servo_allocator::usable_size, None, None);
|
||||
let public_cache = public_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 {
|
||||
path: path!["memory-cache", "public"],
|
||||
kind: ReportKind::ExplicitJemallocHeapSize,
|
||||
size: public_cache.size_of(&mut ops)
|
||||
size: public_cache.size_of(&mut ops),
|
||||
};
|
||||
|
||||
let private_report = Report {
|
||||
path: path!["memory-cache", "private"],
|
||||
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.
|
||||
fn process_msg(&mut self,
|
||||
msg: CoreResourceMsg,
|
||||
http_state: &Arc<HttpState>) -> bool {
|
||||
fn process_msg(&mut self, msg: CoreResourceMsg, http_state: &Arc<HttpState>) -> bool {
|
||||
match msg {
|
||||
CoreResourceMsg::Fetch(req_init, channels) => {
|
||||
match channels {
|
||||
FetchChannels::ResponseMsg(sender, cancel_chan) =>
|
||||
self.resource_manager.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),
|
||||
}
|
||||
}
|
||||
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::Fetch(req_init, channels) => match channels {
|
||||
FetchChannels::ResponseMsg(sender, cancel_chan) => {
|
||||
self.resource_manager
|
||||
.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,
|
||||
),
|
||||
},
|
||||
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) => {
|
||||
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) => {
|
||||
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) => {
|
||||
self.resource_manager.swmanager_chan = Some(mediator_chan)
|
||||
}
|
||||
},
|
||||
CoreResourceMsg::GetCookiesDataForUrl(url, consumer, source) => {
|
||||
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();
|
||||
}
|
||||
},
|
||||
CoreResourceMsg::GetHistoryState(history_state_id, consumer) => {
|
||||
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) => {
|
||||
let mut history_states = http_state.history_states.write().unwrap();
|
||||
history_states.insert(history_state_id, history_state);
|
||||
}
|
||||
},
|
||||
CoreResourceMsg::RemoveHistoryStates(states_to_remove) => {
|
||||
let mut history_states = http_state.history_states.write().unwrap();
|
||||
for history_state in states_to_remove {
|
||||
history_states.remove(&history_state);
|
||||
}
|
||||
}
|
||||
},
|
||||
CoreResourceMsg::Synchronize(sender) => {
|
||||
let _ = sender.send(());
|
||||
}
|
||||
},
|
||||
CoreResourceMsg::ToFileManager(msg) => self.resource_manager.filemanager.handle(msg),
|
||||
CoreResourceMsg::Exit(sender) => {
|
||||
if let Some(ref config_dir) = self.config_dir {
|
||||
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"),
|
||||
}
|
||||
match http_state.cookie_jar.read() {
|
||||
|
@ -282,14 +309,15 @@ impl ResourceChannelManager {
|
|||
}
|
||||
let _ = sender.send(());
|
||||
return false;
|
||||
}
|
||||
},
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
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 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();
|
||||
match file.read_to_string(&mut string_buffer) {
|
||||
Err(why) => {
|
||||
panic!("couldn't read from {}: {}", display,
|
||||
Error::description(&why))
|
||||
},
|
||||
Err(why) => panic!(
|
||||
"couldn't read from {}: {}",
|
||||
display,
|
||||
Error::description(&why)
|
||||
),
|
||||
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)
|
||||
where T: Serialize
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
let json_encoded: String;
|
||||
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 mut file = match File::create(&path) {
|
||||
Err(why) => panic!("couldn't create {}: {}",
|
||||
display,
|
||||
Error::description(&why)),
|
||||
Err(why) => panic!("couldn't create {}: {}", display, Error::description(&why)),
|
||||
Ok(file) => file,
|
||||
};
|
||||
|
||||
match file.write_all(json_encoded.as_bytes()) {
|
||||
Err(why) => {
|
||||
panic!("couldn't write to {}: {}", display,
|
||||
Error::description(&why))
|
||||
},
|
||||
Err(why) => panic!(
|
||||
"couldn't write to {}: {}",
|
||||
display,
|
||||
Error::description(&why)
|
||||
),
|
||||
Ok(_) => println!("successfully wrote to {}", display),
|
||||
}
|
||||
}
|
||||
|
@ -354,7 +383,7 @@ impl AuthCache {
|
|||
pub fn new() -> AuthCache {
|
||||
AuthCache {
|
||||
version: 1,
|
||||
entries: HashMap::new()
|
||||
entries: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -373,10 +402,12 @@ pub struct CoreResourceManager {
|
|||
}
|
||||
|
||||
impl CoreResourceManager {
|
||||
pub fn new(user_agent: Cow<'static, str>,
|
||||
devtools_channel: Option<Sender<DevtoolsControlMsg>>,
|
||||
_profiler_chan: ProfilerChan,
|
||||
embedder_proxy: EmbedderProxy) -> CoreResourceManager {
|
||||
pub fn new(
|
||||
user_agent: Cow<'static, str>,
|
||||
devtools_channel: Option<Sender<DevtoolsControlMsg>>,
|
||||
_profiler_chan: ProfilerChan,
|
||||
embedder_proxy: EmbedderProxy,
|
||||
) -> CoreResourceManager {
|
||||
CoreResourceManager {
|
||||
user_agent: user_agent,
|
||||
devtools_chan: devtools_channel,
|
||||
|
@ -385,55 +416,67 @@ impl CoreResourceManager {
|
|||
}
|
||||
}
|
||||
|
||||
fn set_cookie_for_url(&mut self, request: &ServoUrl,
|
||||
cookie: cookie_rs::Cookie<'static>,
|
||||
source: CookieSource,
|
||||
http_state: &Arc<HttpState>) {
|
||||
fn set_cookie_for_url(
|
||||
&mut self,
|
||||
request: &ServoUrl,
|
||||
cookie: cookie_rs::Cookie<'static>,
|
||||
source: CookieSource,
|
||||
http_state: &Arc<HttpState>,
|
||||
) {
|
||||
if let Some(cookie) = cookie::Cookie::new_wrapped(cookie, request, source) {
|
||||
let mut cookie_jar = http_state.cookie_jar.write().unwrap();
|
||||
cookie_jar.push(cookie, request, source)
|
||||
}
|
||||
}
|
||||
|
||||
fn fetch(&self,
|
||||
req_init: RequestInit,
|
||||
res_init_: Option<ResponseInit>,
|
||||
mut sender: IpcSender<FetchResponseMsg>,
|
||||
http_state: &Arc<HttpState>,
|
||||
cancel_chan: Option<IpcReceiver<()>>) {
|
||||
fn fetch(
|
||||
&self,
|
||||
req_init: RequestInit,
|
||||
res_init_: Option<ResponseInit>,
|
||||
mut sender: IpcSender<FetchResponseMsg>,
|
||||
http_state: &Arc<HttpState>,
|
||||
cancel_chan: Option<IpcReceiver<()>>,
|
||||
) {
|
||||
let http_state = http_state.clone();
|
||||
let ua = self.user_agent.clone();
|
||||
let dc = self.devtools_chan.clone();
|
||||
let filemanager = self.filemanager.clone();
|
||||
|
||||
thread::Builder::new().name(format!("fetch thread for {}", req_init.url)).spawn(move || {
|
||||
let mut request = Request::from_init(req_init);
|
||||
// XXXManishearth: Check origin against pipeline id (also ensure that the mode is allowed)
|
||||
// todo load context / mimesniff in fetch
|
||||
// todo referrer policy?
|
||||
// todo service worker stuff
|
||||
let context = FetchContext {
|
||||
state: http_state,
|
||||
user_agent: ua,
|
||||
devtools_chan: dc,
|
||||
filemanager: filemanager,
|
||||
cancellation_listener: Arc::new(Mutex::new(CancellationListener::new(cancel_chan))),
|
||||
};
|
||||
thread::Builder::new()
|
||||
.name(format!("fetch thread for {}", req_init.url))
|
||||
.spawn(move || {
|
||||
let mut request = Request::from_init(req_init);
|
||||
// XXXManishearth: Check origin against pipeline id (also ensure that the mode is allowed)
|
||||
// todo load context / mimesniff in fetch
|
||||
// todo referrer policy?
|
||||
// todo service worker stuff
|
||||
let context = FetchContext {
|
||||
state: http_state,
|
||||
user_agent: ua,
|
||||
devtools_chan: dc,
|
||||
filemanager: filemanager,
|
||||
cancellation_listener: Arc::new(Mutex::new(CancellationListener::new(
|
||||
cancel_chan,
|
||||
))),
|
||||
};
|
||||
|
||||
match res_init_ {
|
||||
Some(res_init) => {
|
||||
let response = Response::from_init(res_init);
|
||||
http_redirect_fetch(&mut request,
|
||||
&mut CorsCache::new(),
|
||||
response,
|
||||
true,
|
||||
&mut sender,
|
||||
&mut None,
|
||||
&context);
|
||||
},
|
||||
None => fetch(&mut request, &mut sender, &context),
|
||||
};
|
||||
}).expect("Thread spawning failed");
|
||||
match res_init_ {
|
||||
Some(res_init) => {
|
||||
let response = Response::from_init(res_init);
|
||||
http_redirect_fetch(
|
||||
&mut request,
|
||||
&mut CorsCache::new(),
|
||||
response,
|
||||
true,
|
||||
&mut sender,
|
||||
&mut None,
|
||||
&context,
|
||||
);
|
||||
},
|
||||
None => fetch(&mut request, &mut sender, &context),
|
||||
};
|
||||
})
|
||||
.expect("Thread spawning failed");
|
||||
}
|
||||
|
||||
fn websocket_connect(
|
||||
|
@ -441,7 +484,7 @@ impl CoreResourceManager {
|
|||
request: RequestInit,
|
||||
event_sender: IpcSender<WebSocketNetworkEvent>,
|
||||
action_receiver: IpcReceiver<WebSocketDomAction>,
|
||||
http_state: &Arc<HttpState>
|
||||
http_state: &Arc<HttpState>,
|
||||
) {
|
||||
websocket_loader::init(request, event_sender, action_receiver, http_state.clone());
|
||||
}
|
||||
|
|
|
@ -22,9 +22,12 @@ impl StorageThreadFactory for IpcSender<StorageThreadMsg> {
|
|||
/// Create a storage thread
|
||||
fn new(config_dir: Option<PathBuf>) -> IpcSender<StorageThreadMsg> {
|
||||
let (chan, port) = ipc::channel().unwrap();
|
||||
thread::Builder::new().name("StorageManager".to_owned()).spawn(move || {
|
||||
StorageManager::new(port, config_dir).start();
|
||||
}).expect("Thread spawning failed");
|
||||
thread::Builder::new()
|
||||
.name("StorageManager".to_owned())
|
||||
.spawn(move || {
|
||||
StorageManager::new(port, config_dir).start();
|
||||
})
|
||||
.expect("Thread spawning failed");
|
||||
chan
|
||||
}
|
||||
}
|
||||
|
@ -37,9 +40,7 @@ struct StorageManager {
|
|||
}
|
||||
|
||||
impl StorageManager {
|
||||
fn new(port: IpcReceiver<StorageThreadMsg>,
|
||||
config_dir: Option<PathBuf>)
|
||||
-> StorageManager {
|
||||
fn new(port: IpcReceiver<StorageThreadMsg>, config_dir: Option<PathBuf>) -> StorageManager {
|
||||
let mut local_data = HashMap::new();
|
||||
if let Some(ref config_dir) = config_dir {
|
||||
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() {
|
||||
StorageThreadMsg::Length(sender, url, storage_type) => {
|
||||
self.length(sender, url, storage_type)
|
||||
}
|
||||
},
|
||||
StorageThreadMsg::Key(sender, url, storage_type, index) => {
|
||||
self.key(sender, url, storage_type, index)
|
||||
}
|
||||
},
|
||||
StorageThreadMsg::Keys(sender, url, storage_type) => {
|
||||
self.keys(sender, url, storage_type)
|
||||
}
|
||||
},
|
||||
StorageThreadMsg::SetItem(sender, url, storage_type, name, value) => {
|
||||
self.set_item(sender, url, storage_type, name, value);
|
||||
self.save_state()
|
||||
}
|
||||
},
|
||||
StorageThreadMsg::GetItem(sender, url, storage_type, name) => {
|
||||
self.request_item(sender, url, storage_type, name)
|
||||
}
|
||||
},
|
||||
StorageThreadMsg::RemoveItem(sender, url, storage_type, name) => {
|
||||
self.remove_item(sender, url, storage_type, name);
|
||||
self.save_state()
|
||||
}
|
||||
},
|
||||
StorageThreadMsg::Clear(sender, url, storage_type) => {
|
||||
self.clear(sender, url, storage_type);
|
||||
self.save_state()
|
||||
}
|
||||
},
|
||||
StorageThreadMsg::Exit(sender) => {
|
||||
// Nothing to do since we save localstorage set eagerly.
|
||||
let _ = sender.send(());
|
||||
break
|
||||
}
|
||||
break;
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -96,49 +97,56 @@ impl StorageManager {
|
|||
}
|
||||
}
|
||||
|
||||
fn select_data(&self, storage_type: StorageType)
|
||||
-> &HashMap<String, (usize, BTreeMap<String, String>)> {
|
||||
fn select_data(
|
||||
&self,
|
||||
storage_type: StorageType,
|
||||
) -> &HashMap<String, (usize, BTreeMap<String, String>)> {
|
||||
match storage_type {
|
||||
StorageType::Session => &self.session_data,
|
||||
StorageType::Local => &self.local_data
|
||||
StorageType::Local => &self.local_data,
|
||||
}
|
||||
}
|
||||
|
||||
fn select_data_mut(&mut self, storage_type: StorageType)
|
||||
-> &mut HashMap<String, (usize, BTreeMap<String, String>)> {
|
||||
fn select_data_mut(
|
||||
&mut self,
|
||||
storage_type: StorageType,
|
||||
) -> &mut HashMap<String, (usize, BTreeMap<String, String>)> {
|
||||
match storage_type {
|
||||
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) {
|
||||
let origin = self.origin_as_string(url);
|
||||
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,
|
||||
sender: IpcSender<Option<String>>,
|
||||
url: ServoUrl,
|
||||
storage_type: StorageType,
|
||||
index: u32) {
|
||||
fn key(
|
||||
&self,
|
||||
sender: IpcSender<Option<String>>,
|
||||
url: ServoUrl,
|
||||
storage_type: StorageType,
|
||||
index: u32,
|
||||
) {
|
||||
let origin = self.origin_as_string(url);
|
||||
let data = self.select_data(storage_type);
|
||||
let key = data.get(&origin)
|
||||
.and_then(|&(_, ref entry)| entry.keys().nth(index as usize))
|
||||
.cloned();
|
||||
let key = data
|
||||
.get(&origin)
|
||||
.and_then(|&(_, ref entry)| entry.keys().nth(index as usize))
|
||||
.cloned();
|
||||
sender.send(key).unwrap();
|
||||
}
|
||||
|
||||
fn keys(&self,
|
||||
sender: IpcSender<Vec<String>>,
|
||||
url: ServoUrl,
|
||||
storage_type: StorageType) {
|
||||
fn keys(&self, sender: IpcSender<Vec<String>>, url: ServoUrl, storage_type: StorageType) {
|
||||
let origin = self.origin_as_string(url);
|
||||
let data = self.select_data(storage_type);
|
||||
let keys = data.get(&origin)
|
||||
.map_or(vec![], |&(_, ref entry)| entry.keys().cloned().collect());
|
||||
let keys = data
|
||||
.get(&origin)
|
||||
.map_or(vec![], |&(_, ref entry)| entry.keys().cloned().collect());
|
||||
|
||||
sender.send(keys).unwrap();
|
||||
}
|
||||
|
@ -147,12 +155,14 @@ impl StorageManager {
|
|||
/// value with the same key name but with different value name
|
||||
/// otherwise sends Err(()) to indicate that the operation would result in
|
||||
/// exceeding the quota limit
|
||||
fn set_item(&mut self,
|
||||
sender: IpcSender<Result<(bool, Option<String>), ()>>,
|
||||
url: ServoUrl,
|
||||
storage_type: StorageType,
|
||||
name: String,
|
||||
value: String) {
|
||||
fn set_item(
|
||||
&mut self,
|
||||
sender: IpcSender<Result<(bool, Option<String>), ()>>,
|
||||
url: ServoUrl,
|
||||
storage_type: StorageType,
|
||||
name: String,
|
||||
value: String,
|
||||
) {
|
||||
let origin = self.origin_as_string(url);
|
||||
|
||||
let (this_storage_size, other_storage_size) = {
|
||||
|
@ -171,64 +181,82 @@ impl StorageManager {
|
|||
data.insert(origin.clone(), (0, BTreeMap::new()));
|
||||
}
|
||||
|
||||
let message = data.get_mut(&origin).map(|&mut (ref mut total, ref mut entry)| {
|
||||
let mut new_total_size = this_storage_size + value.as_bytes().len();
|
||||
if let Some(old_value) = entry.get(&name) {
|
||||
new_total_size -= old_value.as_bytes().len();
|
||||
} else {
|
||||
new_total_size += name.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))
|
||||
let message = data
|
||||
.get_mut(&origin)
|
||||
.map(|&mut (ref mut total, ref mut entry)| {
|
||||
let mut new_total_size = this_storage_size + value.as_bytes().len();
|
||||
if let Some(old_value) = entry.get(&name) {
|
||||
new_total_size -= old_value.as_bytes().len();
|
||||
} else {
|
||||
Ok((true, Some(old)))
|
||||
});
|
||||
*total = new_total_size;
|
||||
message
|
||||
}).unwrap();
|
||||
new_total_size += name.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 {
|
||||
Ok((true, Some(old)))
|
||||
}
|
||||
});
|
||||
*total = new_total_size;
|
||||
message
|
||||
})
|
||||
.unwrap();
|
||||
sender.send(message).unwrap();
|
||||
}
|
||||
|
||||
fn request_item(&self,
|
||||
sender: IpcSender<Option<String>>,
|
||||
url: ServoUrl,
|
||||
storage_type: StorageType,
|
||||
name: String) {
|
||||
fn request_item(
|
||||
&self,
|
||||
sender: IpcSender<Option<String>>,
|
||||
url: ServoUrl,
|
||||
storage_type: StorageType,
|
||||
name: String,
|
||||
) {
|
||||
let origin = self.origin_as_string(url);
|
||||
let data = self.select_data(storage_type);
|
||||
sender.send(data.get(&origin)
|
||||
sender
|
||||
.send(
|
||||
data.get(&origin)
|
||||
.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
|
||||
fn remove_item(&mut self,
|
||||
sender: IpcSender<Option<String>>,
|
||||
url: ServoUrl,
|
||||
storage_type: StorageType,
|
||||
name: String) {
|
||||
fn remove_item(
|
||||
&mut self,
|
||||
sender: IpcSender<Option<String>>,
|
||||
url: ServoUrl,
|
||||
storage_type: StorageType,
|
||||
name: String,
|
||||
) {
|
||||
let origin = self.origin_as_string(url);
|
||||
let data = self.select_data_mut(storage_type);
|
||||
let old_value = data.get_mut(&origin).and_then(|&mut (ref mut total, ref mut entry)| {
|
||||
entry.remove(&name).and_then(|old| {
|
||||
*total -= name.as_bytes().len() + old.as_bytes().len();
|
||||
Some(old)
|
||||
})
|
||||
});
|
||||
let old_value = data
|
||||
.get_mut(&origin)
|
||||
.and_then(|&mut (ref mut total, ref mut entry)| {
|
||||
entry.remove(&name).and_then(|old| {
|
||||
*total -= name.as_bytes().len() + old.as_bytes().len();
|
||||
Some(old)
|
||||
})
|
||||
});
|
||||
sender.send(old_value).unwrap();
|
||||
}
|
||||
|
||||
fn clear(&mut self, sender: IpcSender<bool>, url: ServoUrl, storage_type: StorageType) {
|
||||
let origin = self.origin_as_string(url);
|
||||
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)| {
|
||||
if !entry.is_empty() {
|
||||
entry.clear();
|
||||
|
@ -236,7 +264,10 @@ impl StorageManager {
|
|||
true
|
||||
} else {
|
||||
false
|
||||
}})).unwrap();
|
||||
}
|
||||
}),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn origin_as_string(&self, url: ServoUrl) -> String {
|
||||
|
|
|
@ -9,22 +9,13 @@ use std::iter::Filter;
|
|||
use std::str::Split;
|
||||
use std::sync::MutexGuard;
|
||||
|
||||
const SUPPORTED_ALGORITHM: &'static [&'static str] = &[
|
||||
"sha256",
|
||||
"sha384",
|
||||
"sha512",
|
||||
];
|
||||
const SUPPORTED_ALGORITHM: &'static [&'static str] = &["sha256", "sha384", "sha512"];
|
||||
pub type StaticCharVec = &'static [char];
|
||||
/// A "space character" according to:
|
||||
///
|
||||
/// <https://html.spec.whatwg.org/multipage/#space-character>
|
||||
pub static HTML_SPACE_CHARACTERS: StaticCharVec = &[
|
||||
'\u{0020}',
|
||||
'\u{0009}',
|
||||
'\u{000a}',
|
||||
'\u{000c}',
|
||||
'\u{000d}',
|
||||
];
|
||||
pub static HTML_SPACE_CHARACTERS: StaticCharVec =
|
||||
&['\u{0020}', '\u{0009}', '\u{000a}', '\u{000c}', '\u{000d}'];
|
||||
#[derive(Clone)]
|
||||
pub struct SriEntry {
|
||||
pub alg: String,
|
||||
|
@ -79,9 +70,18 @@ pub fn parsed_metadata(integrity_metadata: &str) -> Vec<SriEntry> {
|
|||
}
|
||||
|
||||
/// <https://w3c.github.io/webappsec-subresource-integrity/#getprioritizedhashfunction>
|
||||
pub fn get_prioritized_hash_function(hash_func_left: &str, 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();
|
||||
pub fn get_prioritized_hash_function(
|
||||
hash_func_left: &str,
|
||||
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 {
|
||||
return None;
|
||||
|
@ -91,7 +91,6 @@ pub fn get_prioritized_hash_function(hash_func_left: &str, hash_func_right: &str
|
|||
} else {
|
||||
Some(hash_func_right.to_owned())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <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();
|
||||
|
||||
for integrity_metadata in &integrity_metadata_list[1..] {
|
||||
let prioritized_hash = get_prioritized_hash_function(&integrity_metadata.alg,
|
||||
&*current_algorithm);
|
||||
let prioritized_hash =
|
||||
get_prioritized_hash_function(&integrity_metadata.alg, &*current_algorithm);
|
||||
if prioritized_hash.is_none() {
|
||||
result.push(integrity_metadata.clone());
|
||||
} 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>
|
||||
fn apply_algorithm_to_response(body: MutexGuard<ResponseBody>,
|
||||
message_digest: MessageDigest)
|
||||
-> String {
|
||||
fn apply_algorithm_to_response(
|
||||
body: MutexGuard<ResponseBody>,
|
||||
message_digest: MessageDigest,
|
||||
) -> String {
|
||||
if let ResponseBody::Done(ref vec) = *body {
|
||||
let response_digest = hash(message_digest, vec).unwrap(); //Now hash
|
||||
base64::encode(&response_digest)
|
||||
|
@ -171,8 +171,12 @@ pub fn is_response_integrity_valid(integrity_metadata: &str, response: &Response
|
|||
false
|
||||
}
|
||||
|
||||
pub fn split_html_space_chars<'a>(s: &'a str) ->
|
||||
Filter<Split<'a, StaticCharVec>, fn(&&str) -> bool> {
|
||||
fn not_empty(&split: &&str) -> bool { !split.is_empty() }
|
||||
s.split(HTML_SPACE_CHARACTERS).filter(not_empty as fn(&&str) -> bool)
|
||||
pub fn split_html_space_chars<'a>(
|
||||
s: &'a str,
|
||||
) -> Filter<Split<'a, StaticCharVec>, fn(&&str) -> bool> {
|
||||
fn not_empty(&split: &&str) -> bool {
|
||||
!split.is_empty()
|
||||
}
|
||||
s.split(HTML_SPACE_CHARACTERS)
|
||||
.filter(not_empty as fn(&&str) -> bool)
|
||||
}
|
||||
|
|
|
@ -118,11 +118,13 @@ fn test_cookie_secure_prefix() {
|
|||
assert!(Cookie::new_wrapped(cookie, url, CookieSource::HTTP).is_none());
|
||||
|
||||
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());
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
|
@ -157,7 +159,8 @@ fn test_cookie_host_prefix() {
|
|||
assert!(Cookie::new_wrapped(cookie, url, CookieSource::HTTP).is_none());
|
||||
|
||||
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());
|
||||
|
||||
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_eq!(CookieStorage::cookie_comparator(&a, &b), Ordering::Greater);
|
||||
assert_eq!(CookieStorage::cookie_comparator(&b, &a), Ordering::Less);
|
||||
assert_eq!(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_prime),
|
||||
Ordering::Less
|
||||
);
|
||||
assert_eq!(
|
||||
CookieStorage::cookie_comparator(&a_prime, &a),
|
||||
Ordering::Greater
|
||||
);
|
||||
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 cookie = cookie_rs::Cookie::parse(cookie_str.to_owned()).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();
|
||||
|
||||
add_cookie_to_storage(&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,
|
||||
"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, "foo4=value; Path=/foo");
|
||||
|
||||
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;
|
||||
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;
|
||||
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]
|
||||
|
@ -267,14 +294,21 @@ fn test_secure_cookies_eviction() {
|
|||
let source = CookieSource::HTTP;
|
||||
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;
|
||||
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;
|
||||
assert_eq!(storage.cookies_for_url(&url, source).unwrap(),
|
||||
"foo4=bar; foo3=value; foo3=bar; foo4=value; foo2=value");
|
||||
assert_eq!(
|
||||
storage.cookies_for_url(&url, source).unwrap(),
|
||||
"foo4=bar; foo3=value; foo3=bar; foo4=value; foo2=value"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -302,21 +336,28 @@ fn test_secure_cookies_eviction_non_http_source() {
|
|||
let source = CookieSource::HTTP;
|
||||
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;
|
||||
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;
|
||||
assert_eq!(storage.cookies_for_url(&url, source).unwrap(),
|
||||
"foo4=bar; foo3=value; foo3=bar; foo4=value; foo2=value");
|
||||
assert_eq!(
|
||||
storage.cookies_for_url(&url, source).unwrap(),
|
||||
"foo4=bar; foo3=value; foo3=bar; foo4=value; foo2=value"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
fn add_retrieve_cookies(set_location: &str,
|
||||
set_cookies: &[String],
|
||||
final_location: &str)
|
||||
-> String {
|
||||
fn add_retrieve_cookies(
|
||||
set_location: &str,
|
||||
set_cookies: &[String],
|
||||
final_location: &str,
|
||||
) -> String {
|
||||
let mut storage = CookieStorage::new(5);
|
||||
let url = ServoUrl::parse(set_location).unwrap();
|
||||
let source = CookieSource::HTTP;
|
||||
|
@ -329,56 +370,75 @@ fn add_retrieve_cookies(set_location: &str,
|
|||
|
||||
// Get cookies for the test location
|
||||
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]
|
||||
fn test_cookie_eviction_expired() {
|
||||
let mut vec = Vec::new();
|
||||
for i in 1..6 {
|
||||
let st = format!("extra{}=bar; Secure; expires=Sun, 18-Apr-2000 21:06:29 GMT",
|
||||
i);
|
||||
let st = format!(
|
||||
"extra{}=bar; Secure; expires=Sun, 18-Apr-2000 21:06:29 GMT",
|
||||
i
|
||||
);
|
||||
vec.push(st);
|
||||
}
|
||||
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",
|
||||
&vec, "https://home.example.org:8888/cookie-parser-result?0001");
|
||||
let r = add_retrieve_cookies(
|
||||
"https://home.example.org:8888/cookie-parser?0001",
|
||||
&vec,
|
||||
"https://home.example.org:8888/cookie-parser-result?0001",
|
||||
);
|
||||
assert_eq!(&r, "foo=bar");
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn test_cookie_eviction_all_secure_one_nonsecure() {
|
||||
let mut vec = Vec::new();
|
||||
for i in 1..5 {
|
||||
let st = format!("extra{}=bar; Secure; expires=Sun, 18-Apr-2026 21:06:29 GMT",
|
||||
i);
|
||||
let st = format!(
|
||||
"extra{}=bar; Secure; expires=Sun, 18-Apr-2026 21:06:29 GMT",
|
||||
i
|
||||
);
|
||||
vec.push(st);
|
||||
}
|
||||
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());
|
||||
let r = add_retrieve_cookies("https://home.example.org:8888/cookie-parser?0001",
|
||||
&vec, "https://home.example.org:8888/cookie-parser-result?0001");
|
||||
assert_eq!(&r, "extra1=bar; extra2=bar; extra3=bar; extra4=bar; foo2=bar");
|
||||
let r = add_retrieve_cookies(
|
||||
"https://home.example.org:8888/cookie-parser?0001",
|
||||
&vec,
|
||||
"https://home.example.org:8888/cookie-parser-result?0001",
|
||||
);
|
||||
assert_eq!(
|
||||
&r,
|
||||
"extra1=bar; extra2=bar; extra3=bar; extra4=bar; foo2=bar"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn test_cookie_eviction_all_secure_new_nonsecure() {
|
||||
let mut vec = Vec::new();
|
||||
for i in 1..6 {
|
||||
let st = format!("extra{}=bar; Secure; expires=Sun, 18-Apr-2026 21:06:29 GMT",
|
||||
i);
|
||||
let st = format!(
|
||||
"extra{}=bar; Secure; expires=Sun, 18-Apr-2026 21:06:29 GMT",
|
||||
i
|
||||
);
|
||||
vec.push(st);
|
||||
}
|
||||
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",
|
||||
&vec, "https://home.example.org:8888/cookie-parser-result?0001");
|
||||
assert_eq!(&r, "extra1=bar; extra2=bar; extra3=bar; extra4=bar; extra5=bar");
|
||||
let r = add_retrieve_cookies(
|
||||
"https://home.example.org:8888/cookie-parser?0001",
|
||||
&vec,
|
||||
"https://home.example.org:8888/cookie-parser-result?0001",
|
||||
);
|
||||
assert_eq!(
|
||||
&r,
|
||||
"extra1=bar; extra2=bar; extra3=bar; extra4=bar; extra5=bar"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn test_cookie_eviction_all_nonsecure_new_secure() {
|
||||
let mut vec = Vec::new();
|
||||
|
@ -387,12 +447,17 @@ fn test_cookie_eviction_all_nonsecure_new_secure() {
|
|||
vec.push(st);
|
||||
}
|
||||
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",
|
||||
&vec, "https://home.example.org:8888/cookie-parser-result?0001");
|
||||
assert_eq!(&r, "extra2=bar; extra3=bar; extra4=bar; extra5=bar; foo=bar");
|
||||
let r = add_retrieve_cookies(
|
||||
"https://home.example.org:8888/cookie-parser?0001",
|
||||
&vec,
|
||||
"https://home.example.org:8888/cookie-parser-result?0001",
|
||||
);
|
||||
assert_eq!(
|
||||
&r,
|
||||
"extra2=bar; extra3=bar; extra4=bar; extra5=bar; foo=bar"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn test_cookie_eviction_all_nonsecure_new_nonsecure() {
|
||||
let mut vec = Vec::new();
|
||||
|
@ -401,7 +466,13 @@ fn test_cookie_eviction_all_nonsecure_new_nonsecure() {
|
|||
vec.push(st);
|
||||
}
|
||||
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",
|
||||
&vec, "https://home.example.org:8888/cookie-parser-result?0001");
|
||||
assert_eq!(&r, "extra2=bar; extra3=bar; extra4=bar; extra5=bar; foo=bar");
|
||||
let r = add_retrieve_cookies(
|
||||
"https://home.example.org:8888/cookie-parser?0001",
|
||||
&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
|
@ -14,10 +14,12 @@ use servo_url::ServoUrl;
|
|||
use std::ops::Deref;
|
||||
|
||||
#[cfg(test)]
|
||||
fn assert_parse(url: &'static str,
|
||||
content_type: Option<ContentType>,
|
||||
charset: Option<&str>,
|
||||
data: Option<&[u8]>) {
|
||||
fn assert_parse(
|
||||
url: &'static str,
|
||||
content_type: Option<ContentType>,
|
||||
charset: Option<&str>,
|
||||
data: Option<&[u8]>,
|
||||
) {
|
||||
let url = ServoUrl::parse(url).unwrap();
|
||||
let origin = Origin::Origin(url.origin());
|
||||
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);
|
||||
|
||||
let metadata = match response.metadata() {
|
||||
Ok(FetchMetadata::Filtered { filtered: FilteredMetadata::Basic(m), .. }) => m,
|
||||
Ok(FetchMetadata::Filtered {
|
||||
filtered: FilteredMetadata::Basic(m),
|
||||
..
|
||||
}) => m,
|
||||
result => panic!(result),
|
||||
};
|
||||
assert_eq!(metadata.content_type.map(Serde::into_inner), content_type);
|
||||
|
@ -49,7 +54,12 @@ fn assert_parse(url: &'static str,
|
|||
},
|
||||
None => {
|
||||
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() {
|
||||
assert_parse(
|
||||
"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(b"hello world"));
|
||||
Some(b"hello world"),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -74,7 +87,8 @@ fn plain_ct() {
|
|||
"data:text/plain,hello",
|
||||
Some(ContentType::from(mime::TEXT_PLAIN)),
|
||||
None,
|
||||
Some(b"hello"));
|
||||
Some(b"hello"),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -83,16 +97,20 @@ fn plain_html() {
|
|||
"data:text/html,<p>Servo</p>",
|
||||
Some(ContentType::from(mime::TEXT_HTML)),
|
||||
None,
|
||||
Some(b"<p>Servo</p>"));
|
||||
Some(b"<p>Servo</p>"),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plain_charset() {
|
||||
assert_parse(
|
||||
"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(b"hello"));
|
||||
Some(b"hello"),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -101,16 +119,20 @@ fn plain_only_charset() {
|
|||
"data:;charset=utf-8,hello",
|
||||
Some(ContentType::from(mime::TEXT_PLAIN_UTF_8)),
|
||||
Some("utf-8"),
|
||||
Some(b"hello"));
|
||||
Some(b"hello"),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn base64() {
|
||||
assert_parse(
|
||||
"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(&[0x0B, 0xAD, 0xBE, 0xEF]));
|
||||
Some(&[0x0B, 0xAD, 0xBE, 0xEF]),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -119,14 +141,20 @@ fn base64_ct() {
|
|||
"data:application/octet-stream;base64,C62+7w==",
|
||||
Some(ContentType::from(mime::APPLICATION_OCTET_STREAM)),
|
||||
None,
|
||||
Some(&[0x0B, 0xAD, 0xBE, 0xEF]));
|
||||
Some(&[0x0B, 0xAD, 0xBE, 0xEF]),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn base64_charset() {
|
||||
assert_parse(
|
||||
"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(&[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,
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -70,7 +70,10 @@ fn test_fetch_on_bad_port_is_network_error() {
|
|||
let fetch_response = fetch(&mut request, None);
|
||||
assert!(fetch_response.is_network_error());
|
||||
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]
|
||||
|
@ -94,7 +97,7 @@ fn test_fetch_response_body_matches_const_message() {
|
|||
ResponseBody::Done(ref body) => {
|
||||
assert_eq!(&**body, MESSAGE);
|
||||
},
|
||||
_ => panic!()
|
||||
_ => panic!(),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -106,7 +109,10 @@ fn test_fetch_aboutblank() {
|
|||
request.referrer = Referrer::NoReferrer;
|
||||
let fetch_response = fetch(&mut request, None);
|
||||
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]
|
||||
|
@ -127,11 +133,12 @@ fn test_fetch_blob() {
|
|||
let origin = ServoUrl::parse("http://www.example.org/").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 url = ServoUrl::parse(&format!("blob:{}{}", origin.as_str(), id.simple())).unwrap();
|
||||
|
||||
|
||||
let mut request = Request::new(url, Some(Origin::Origin(origin.origin())), None);
|
||||
let fetch_response = fetch_with_context(&mut request, &context);
|
||||
|
||||
|
@ -139,19 +146,27 @@ fn test_fetch_blob() {
|
|||
|
||||
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);
|
||||
|
||||
let content_length: ContentLength = fetch_response.headers.typed_get().unwrap();
|
||||
assert_eq!(content_length.0, bytes.len() as u64);
|
||||
|
||||
assert_eq!(*fetch_response.body.lock().unwrap(),
|
||||
ResponseBody::Done(bytes.to_vec()));
|
||||
assert_eq!(
|
||||
*fetch_response.body.lock().unwrap(),
|
||||
ResponseBody::Done(bytes.to_vec())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
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 origin = Origin::Origin(url.origin());
|
||||
let mut request = Request::new(url, Some(origin), None);
|
||||
|
@ -159,7 +174,11 @@ fn test_fetch_file() {
|
|||
let fetch_response = fetch(&mut request, None);
|
||||
assert!(!fetch_response.is_network_error());
|
||||
assert_eq!(fetch_response.headers.len(), 1);
|
||||
let content_type: 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);
|
||||
|
||||
let resp_body = fetch_response.body.lock().unwrap();
|
||||
|
@ -171,7 +190,7 @@ fn test_fetch_file() {
|
|||
ResponseBody::Done(ref val) => {
|
||||
assert_eq!(val, &bytes);
|
||||
},
|
||||
_ => panic!()
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -200,15 +219,34 @@ fn test_cors_preflight_fetch() {
|
|||
static ACK: &'static [u8] = b"ACK";
|
||||
let state = Arc::new(AtomicUsize::new(0));
|
||||
let handler = move |request: HyperRequest<Body>, response: &mut HyperResponse<Body>| {
|
||||
if request.method() == Method::OPTIONS && state.clone().fetch_add(1, Ordering::SeqCst) == 0 {
|
||||
assert!(request.headers().contains_key(header::ACCESS_CONTROL_REQUEST_METHOD));
|
||||
assert!(!request.headers().contains_key(header::ACCESS_CONTROL_REQUEST_HEADERS));
|
||||
assert!(!request.headers().get(header::REFERER).unwrap().to_str().unwrap().contains("a.html"));
|
||||
response.headers_mut().typed_insert(AccessControlAllowOrigin::ANY);
|
||||
response.headers_mut().typed_insert(AccessControlAllowCredentials);
|
||||
response.headers_mut().typed_insert(AccessControlAllowMethods::from_iter(vec![Method::GET]));
|
||||
if request.method() == Method::OPTIONS && state.clone().fetch_add(1, Ordering::SeqCst) == 0
|
||||
{
|
||||
assert!(request
|
||||
.headers()
|
||||
.contains_key(header::ACCESS_CONTROL_REQUEST_METHOD));
|
||||
assert!(!request
|
||||
.headers()
|
||||
.contains_key(header::ACCESS_CONTROL_REQUEST_HEADERS));
|
||||
assert!(!request
|
||||
.headers()
|
||||
.get(header::REFERER)
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.contains("a.html"));
|
||||
response
|
||||
.headers_mut()
|
||||
.typed_insert(AccessControlAllowOrigin::ANY);
|
||||
response
|
||||
.headers_mut()
|
||||
.typed_insert(AccessControlAllowCredentials);
|
||||
response
|
||||
.headers_mut()
|
||||
.typed_insert(AccessControlAllowMethods::from_iter(vec![Method::GET]));
|
||||
} else {
|
||||
response.headers_mut().typed_insert(AccessControlAllowOrigin::ANY);
|
||||
response
|
||||
.headers_mut()
|
||||
.typed_insert(AccessControlAllowOrigin::ANY);
|
||||
*response.body_mut() = ACK.to_vec().into();
|
||||
}
|
||||
};
|
||||
|
@ -228,7 +266,7 @@ fn test_cors_preflight_fetch() {
|
|||
assert!(!fetch_response.is_network_error());
|
||||
match *fetch_response.body.lock().unwrap() {
|
||||
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 mut cache = CorsCache::new();
|
||||
let handler = move |request: HyperRequest<Body>, response: &mut HyperResponse<Body>| {
|
||||
if request.method() == Method::OPTIONS && state.clone().fetch_add(1, Ordering::SeqCst) == 0 {
|
||||
assert!(request.headers().contains_key(header::ACCESS_CONTROL_REQUEST_METHOD));
|
||||
assert!(!request.headers().contains_key(header::ACCESS_CONTROL_REQUEST_HEADERS));
|
||||
response.headers_mut().typed_insert(AccessControlAllowOrigin::ANY);
|
||||
response.headers_mut().typed_insert(AccessControlAllowCredentials);
|
||||
response.headers_mut().typed_insert(AccessControlAllowMethods::from_iter(vec![Method::GET]));
|
||||
response.headers_mut().typed_insert(AccessControlMaxAge::from(Duration::new(6000, 0)));
|
||||
if request.method() == Method::OPTIONS && state.clone().fetch_add(1, Ordering::SeqCst) == 0
|
||||
{
|
||||
assert!(request
|
||||
.headers()
|
||||
.contains_key(header::ACCESS_CONTROL_REQUEST_METHOD));
|
||||
assert!(!request
|
||||
.headers()
|
||||
.contains_key(header::ACCESS_CONTROL_REQUEST_HEADERS));
|
||||
response
|
||||
.headers_mut()
|
||||
.typed_insert(AccessControlAllowOrigin::ANY);
|
||||
response
|
||||
.headers_mut()
|
||||
.typed_insert(AccessControlAllowCredentials);
|
||||
response
|
||||
.headers_mut()
|
||||
.typed_insert(AccessControlAllowMethods::from_iter(vec![Method::GET]));
|
||||
response
|
||||
.headers_mut()
|
||||
.typed_insert(AccessControlMaxAge::from(Duration::new(6000, 0)));
|
||||
} else {
|
||||
response.headers_mut().typed_insert(AccessControlAllowOrigin::ANY);
|
||||
response
|
||||
.headers_mut()
|
||||
.typed_insert(AccessControlAllowOrigin::ANY);
|
||||
*response.body_mut() = ACK.to_vec().into();
|
||||
}
|
||||
};
|
||||
|
@ -276,11 +329,11 @@ fn test_cors_preflight_cache_fetch() {
|
|||
|
||||
match *fetch_response0.body.lock().unwrap() {
|
||||
ResponseBody::Done(ref body) => assert_eq!(&**body, ACK),
|
||||
_ => panic!()
|
||||
_ => panic!(),
|
||||
};
|
||||
match *fetch_response1.body.lock().unwrap() {
|
||||
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";
|
||||
let state = Arc::new(AtomicUsize::new(0));
|
||||
let handler = move |request: HyperRequest<Body>, response: &mut HyperResponse<Body>| {
|
||||
if request.method() == Method::OPTIONS && state.clone().fetch_add(1, Ordering::SeqCst) == 0 {
|
||||
assert!(request.headers().contains_key(header::ACCESS_CONTROL_REQUEST_METHOD));
|
||||
assert!(!request.headers().contains_key(header::ACCESS_CONTROL_REQUEST_HEADERS));
|
||||
response.headers_mut().typed_insert(AccessControlAllowOrigin::ANY);
|
||||
response.headers_mut().typed_insert(AccessControlAllowCredentials);
|
||||
response.headers_mut().typed_insert(AccessControlAllowMethods::from_iter(vec![Method::GET]));
|
||||
if request.method() == Method::OPTIONS && state.clone().fetch_add(1, Ordering::SeqCst) == 0
|
||||
{
|
||||
assert!(request
|
||||
.headers()
|
||||
.contains_key(header::ACCESS_CONTROL_REQUEST_METHOD));
|
||||
assert!(!request
|
||||
.headers()
|
||||
.contains_key(header::ACCESS_CONTROL_REQUEST_HEADERS));
|
||||
response
|
||||
.headers_mut()
|
||||
.typed_insert(AccessControlAllowOrigin::ANY);
|
||||
response
|
||||
.headers_mut()
|
||||
.typed_insert(AccessControlAllowCredentials);
|
||||
response
|
||||
.headers_mut()
|
||||
.typed_insert(AccessControlAllowMethods::from_iter(vec![Method::GET]));
|
||||
} else {
|
||||
response.headers_mut().typed_insert(AccessControlAllowOrigin::ANY);
|
||||
response
|
||||
.headers_mut()
|
||||
.typed_insert(AccessControlAllowOrigin::ANY);
|
||||
*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() {
|
||||
static MESSAGE: &'static [u8] = b"";
|
||||
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
|
||||
response.headers_mut().insert(
|
||||
HeaderName::from_static("set-cookie2"),
|
||||
HeaderValue::from_bytes(&vec![]).unwrap()
|
||||
HeaderValue::from_bytes(&vec![]).unwrap(),
|
||||
);
|
||||
|
||||
*response.body_mut() = MESSAGE.to_vec().into();
|
||||
|
@ -340,7 +408,9 @@ fn test_fetch_response_is_basic_filtered() {
|
|||
|
||||
let headers = fetch_response.headers;
|
||||
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]
|
||||
|
@ -349,28 +419,41 @@ fn test_fetch_response_is_cors_filtered() {
|
|||
let handler = move |_: HyperRequest<Body>, response: &mut HyperResponse<Body>| {
|
||||
// this is mandatory for the Cors Check to pass
|
||||
// TODO test using different url encodings with this value ie. punycode
|
||||
response.headers_mut().typed_insert(AccessControlAllowOrigin::ANY);
|
||||
response
|
||||
.headers_mut()
|
||||
.typed_insert(AccessControlAllowOrigin::ANY);
|
||||
|
||||
// these are the headers that should be kept after filtering
|
||||
response.headers_mut().typed_insert(CacheControl::new());
|
||||
response.headers_mut().insert(header::CONTENT_LANGUAGE, HeaderValue::from_bytes(&vec![]).unwrap());
|
||||
response.headers_mut().typed_insert(ContentType::from(mime::TEXT_HTML));
|
||||
response.headers_mut().typed_insert(Expires::from(SystemTime::now() + Duration::new(86400, 0)));
|
||||
response.headers_mut().typed_insert(LastModified::from(SystemTime::now()));
|
||||
response.headers_mut().insert(
|
||||
header::CONTENT_LANGUAGE,
|
||||
HeaderValue::from_bytes(&vec![]).unwrap(),
|
||||
);
|
||||
response
|
||||
.headers_mut()
|
||||
.typed_insert(ContentType::from(mime::TEXT_HTML));
|
||||
response
|
||||
.headers_mut()
|
||||
.typed_insert(Expires::from(SystemTime::now() + Duration::new(86400, 0)));
|
||||
response
|
||||
.headers_mut()
|
||||
.typed_insert(LastModified::from(SystemTime::now()));
|
||||
response.headers_mut().typed_insert(Pragma::no_cache());
|
||||
|
||||
// these headers should not be kept after filtering, even though they are given a pass
|
||||
response.headers_mut().insert(header::SET_COOKIE, HeaderValue::from_static(""));
|
||||
response
|
||||
.headers_mut()
|
||||
.insert(header::SET_COOKIE, HeaderValue::from_static(""));
|
||||
response.headers_mut().insert(
|
||||
HeaderName::from_static("set-cookie2"),
|
||||
HeaderValue::from_bytes(&vec![]).unwrap()
|
||||
HeaderValue::from_bytes(&vec![]).unwrap(),
|
||||
);
|
||||
response.headers_mut().typed_insert(
|
||||
AccessControlAllowHeaders::from_iter(vec![
|
||||
response
|
||||
.headers_mut()
|
||||
.typed_insert(AccessControlAllowHeaders::from_iter(vec![
|
||||
HeaderName::from_static("set-cookie"),
|
||||
HeaderName::from_static("set-cookie2")
|
||||
])
|
||||
);
|
||||
HeaderName::from_static("set-cookie2"),
|
||||
]));
|
||||
|
||||
*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::SET_COOKIE));
|
||||
assert!(headers.get(HeaderName::from_static("set-cookie2")).is_none());
|
||||
assert!(headers
|
||||
.get(HeaderName::from_static("set-cookie2"))
|
||||
.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -424,12 +509,12 @@ fn test_fetch_response_is_opaque_filtered() {
|
|||
assert!(fetch_response.status.is_none());
|
||||
assert_eq!(fetch_response.headers, HeaderMap::new());
|
||||
match *fetch_response.body.lock().unwrap() {
|
||||
ResponseBody::Empty => { },
|
||||
_ => panic!()
|
||||
ResponseBody::Empty => {},
|
||||
_ => panic!(),
|
||||
}
|
||||
match fetch_response.cache_state {
|
||||
CacheState::None => { },
|
||||
_ => panic!()
|
||||
CacheState::None => {},
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -437,13 +522,21 @@ fn test_fetch_response_is_opaque_filtered() {
|
|||
fn test_fetch_response_is_opaque_redirect_filtered() {
|
||||
static MESSAGE: &'static [u8] = b"";
|
||||
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 {
|
||||
*response.body_mut() = MESSAGE.to_vec().into();
|
||||
} else {
|
||||
*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_eq!(fetch_response.headers, HeaderMap::new());
|
||||
match *fetch_response.body.lock().unwrap() {
|
||||
ResponseBody::Empty => { },
|
||||
_ => panic!()
|
||||
ResponseBody::Empty => {},
|
||||
_ => panic!(),
|
||||
}
|
||||
match fetch_response.cache_state {
|
||||
CacheState::None => { },
|
||||
_ => panic!()
|
||||
CacheState::None => {},
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -516,12 +609,19 @@ fn test_fetch_with_hsts() {
|
|||
*response.body_mut() = MESSAGE.to_vec().into();
|
||||
};
|
||||
|
||||
let cert_path = Path::new("../../resources/self_signed_certificate_for_testing.crt").canonicalize().unwrap();
|
||||
let key_path = Path::new("../../resources/privatekey_for_testing.key").canonicalize().unwrap();
|
||||
let cert_path = Path::new("../../resources/self_signed_certificate_for_testing.crt")
|
||||
.canonicalize()
|
||||
.unwrap();
|
||||
let key_path = Path::new("../../resources/privatekey_for_testing.key")
|
||||
.canonicalize()
|
||||
.unwrap();
|
||||
let (server, url) = make_ssl_server(handler, cert_path.clone(), key_path.clone());
|
||||
|
||||
let mut ca_content = String::new();
|
||||
File::open(cert_path).unwrap().read_to_string(&mut ca_content).unwrap();
|
||||
File::open(cert_path)
|
||||
.unwrap()
|
||||
.read_to_string(&mut ca_content)
|
||||
.unwrap();
|
||||
let ssl_client = create_ssl_connector_builder(&ca_content);
|
||||
|
||||
let context = FetchContext {
|
||||
|
@ -534,8 +634,9 @@ fn test_fetch_with_hsts() {
|
|||
|
||||
{
|
||||
let mut list = context.state.hsts_list.write().unwrap();
|
||||
list.push(HstsEntry::new("localhost".to_owned(), IncludeSubdomains::NotIncluded, None)
|
||||
.unwrap());
|
||||
list.push(
|
||||
HstsEntry::new("localhost".to_owned(), IncludeSubdomains::NotIncluded, None).unwrap(),
|
||||
);
|
||||
}
|
||||
let origin = Origin::Origin(url.origin());
|
||||
let mut request = Request::new(url, Some(origin), None);
|
||||
|
@ -544,8 +645,10 @@ fn test_fetch_with_hsts() {
|
|||
request.local_urls_only = false;
|
||||
let response = fetch_with_context(&mut request, &context);
|
||||
server.close();
|
||||
assert_eq!(response.internal_response.unwrap().url().unwrap().scheme(),
|
||||
"https");
|
||||
assert_eq!(
|
||||
response.internal_response.unwrap().url().unwrap().scheme(),
|
||||
"https"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -562,7 +665,7 @@ fn test_fetch_with_sri_network_error() {
|
|||
// To calulate hash use :
|
||||
// echo -n "alert('Hello, Network Error');" | openssl dgst -sha384 -binary | openssl base64 -A
|
||||
request.integrity_metadata =
|
||||
"sha384-H8BRh8j48O9oYatfu5AZzq6A9RINhZO5H16dQZngK7T62em8MUt1FLm52t+eX6xO".to_owned();
|
||||
"sha384-H8BRh8j48O9oYatfu5AZzq6A9RINhZO5H16dQZngK7T62em8MUt1FLm52t+eX6xO".to_owned();
|
||||
// Set the flag.
|
||||
request.local_urls_only = false;
|
||||
|
||||
|
@ -586,7 +689,7 @@ fn test_fetch_with_sri_sucess() {
|
|||
// To calulate hash use :
|
||||
// echo -n "alert('Hello, Network Error');" | openssl dgst -sha384 -binary | openssl base64 -A
|
||||
request.integrity_metadata =
|
||||
"sha384-H8BRh8j48O9oYatfu5AZzq6A9RINhZO5H16dQZngK7T62em8MUt1FLm52t+eX6xO".to_owned();
|
||||
"sha384-H8BRh8j48O9oYatfu5AZzq6A9RINhZO5H16dQZngK7T62em8MUt1FLm52t+eX6xO".to_owned();
|
||||
// Set the flag.
|
||||
request.local_urls_only = false;
|
||||
|
||||
|
@ -600,9 +703,7 @@ fn test_fetch_with_sri_sucess() {
|
|||
#[test]
|
||||
fn test_fetch_blocked_nosniff() {
|
||||
#[inline]
|
||||
fn test_nosniff_request(destination: Destination,
|
||||
mime: Mime,
|
||||
should_error: bool) {
|
||||
fn test_nosniff_request(destination: Destination, mime: Mime, should_error: bool) {
|
||||
const MESSAGE: &'static [u8] = b"";
|
||||
const HEADER: &'static str = "x-content-type-options";
|
||||
const VALUE: &'static [u8] = b"nosniff";
|
||||
|
@ -612,7 +713,10 @@ fn test_fetch_blocked_nosniff() {
|
|||
response.headers_mut().typed_insert(mime_header);
|
||||
assert!(response.headers().contains_key(header::CONTENT_TYPE));
|
||||
// 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();
|
||||
};
|
||||
|
@ -631,7 +735,7 @@ fn test_fetch_blocked_nosniff() {
|
|||
let tests = vec![
|
||||
(Destination::Script, mime::TEXT_JAVASCRIPT, false),
|
||||
(Destination::Script, mime::TEXT_CSS, true),
|
||||
(Destination::Style, mime::TEXT_CSS, false),
|
||||
(Destination::Style, mime::TEXT_CSS, false),
|
||||
];
|
||||
|
||||
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 {
|
||||
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 {
|
||||
*response.body_mut() = message.to_vec().into();
|
||||
} else {
|
||||
*response.status_mut() = StatusCode::FOUND;
|
||||
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) => {
|
||||
assert_eq!(&**body, MESSAGE);
|
||||
},
|
||||
_ => panic!()
|
||||
_ => panic!(),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -694,31 +806,43 @@ fn test_fetch_redirect_count_failure() {
|
|||
|
||||
match *fetch_response.body.lock().unwrap() {
|
||||
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_tx = Arc::new(Mutex::new(tx));
|
||||
|
||||
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;
|
||||
|
||||
if redirects == 0 {
|
||||
*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 {
|
||||
// this makes sure that the request method does't change from the wrong status code
|
||||
if handler_method != Method::GET && request.method() == Method::GET {
|
||||
test_pass = false;
|
||||
}
|
||||
*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 {
|
||||
test_pass = false;
|
||||
}
|
||||
|
@ -727,7 +851,6 @@ fn test_fetch_redirect_updates_method_runner(tx: Sender<bool>, status_code: Stat
|
|||
if redirects > 0 {
|
||||
handler_tx.lock().unwrap().send(test_pass).unwrap();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
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() {
|
||||
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);
|
||||
// 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();
|
||||
|
||||
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);
|
||||
// for MovedPermanently and Found, Method should only be changed if it was Post
|
||||
assert_eq!(rx.recv().unwrap(), false);
|
||||
|
@ -785,9 +916,9 @@ fn response_is_done(response: &Response) -> bool {
|
|||
let response_complete = match response.response_type {
|
||||
ResponseType::Default | ResponseType::Basic | ResponseType::Cors => {
|
||||
(*response.body.lock().unwrap()).is_done()
|
||||
}
|
||||
},
|
||||
// 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 {
|
||||
|
@ -842,13 +973,21 @@ fn test_opaque_filtered_fetch_async_returns_complete_response() {
|
|||
fn test_opaque_redirect_filtered_fetch_async_returns_complete_response() {
|
||||
static MESSAGE: &'static [u8] = b"";
|
||||
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 {
|
||||
*response.body_mut() = MESSAGE.to_vec().into();
|
||||
} else {
|
||||
*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
|
||||
let mut headers = HeaderMap::new();
|
||||
|
||||
headers.insert(header::ACCEPT_ENCODING, HeaderValue::from_static("gzip, deflate, br"));
|
||||
headers.typed_insert(
|
||||
Host::from(format!("{}:{}", url.host_str().unwrap(), url.port().unwrap()).parse::<Authority>().unwrap()));
|
||||
headers.insert(
|
||||
header::ACCEPT_ENCODING,
|
||||
HeaderValue::from_static("gzip, deflate, br"),
|
||||
);
|
||||
headers.typed_insert(Host::from(
|
||||
format!("{}:{}", url.host_str().unwrap(), url.port().unwrap())
|
||||
.parse::<Authority>()
|
||||
.unwrap(),
|
||||
));
|
||||
|
||||
headers.insert(header::ACCEPT, HeaderValue::from_static("*/*"));
|
||||
|
||||
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());
|
||||
|
||||
|
@ -918,7 +1066,11 @@ fn test_fetch_with_devtools() {
|
|||
let content = "Yay!";
|
||||
let mut response_headers = HeaderMap::new();
|
||||
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 {
|
||||
headers: Some(response_headers),
|
||||
|
|
|
@ -16,14 +16,18 @@ use std::path::PathBuf;
|
|||
#[test]
|
||||
fn test_filemanager() {
|
||||
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
|
||||
let mut handler = File::open("tests/test.jpeg").expect("test.jpeg is stolen");
|
||||
let mut test_file_content = vec![];
|
||||
|
||||
handler.read_to_end(&mut test_file_content)
|
||||
.expect("Read components/net/tests/test.jpeg error");
|
||||
handler
|
||||
.read_to_end(&mut test_file_content)
|
||||
.expect("Read components/net/tests/test.jpeg error");
|
||||
|
||||
let patterns = vec![FilterPattern(".txt".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"
|
||||
let (tx, rx) = ipc::channel().unwrap();
|
||||
filemanager.handle(FileManagerThreadMsg::SelectFile(patterns.clone(), tx, origin.clone(),
|
||||
Some("tests/test.jpeg".to_string())));
|
||||
let selected = rx.recv().expect("Broken channel")
|
||||
.expect("The file manager failed to find test.jpeg");
|
||||
filemanager.handle(FileManagerThreadMsg::SelectFile(
|
||||
patterns.clone(),
|
||||
tx,
|
||||
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
|
||||
assert_eq!(selected.filename, PathBuf::from("test.jpeg"));
|
||||
|
@ -43,24 +53,35 @@ fn test_filemanager() {
|
|||
// Test by reading, expecting same content
|
||||
{
|
||||
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");
|
||||
|
||||
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;
|
||||
|
||||
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(_) => {
|
||||
panic!("Invalid FileManager reply");
|
||||
}
|
||||
},
|
||||
ReadFileProgress::Partial(mut bytes_in) => {
|
||||
bytes.append(&mut bytes_in);
|
||||
}
|
||||
},
|
||||
ReadFileProgress::EOF => {
|
||||
break;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -73,7 +94,11 @@ fn test_filemanager() {
|
|||
// Delete the id
|
||||
{
|
||||
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");
|
||||
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
|
||||
{
|
||||
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");
|
||||
|
||||
match msg {
|
||||
Err(FileManagerThreadError::BlobURLStoreError(BlobURLStoreError::InvalidFileID)) => {},
|
||||
Err(FileManagerThreadError::BlobURLStoreError(
|
||||
BlobURLStoreError::InvalidFileID,
|
||||
)) => {},
|
||||
other => {
|
||||
assert!(false, "Get unexpected response after deleting the id: {:?}", other);
|
||||
}
|
||||
assert!(
|
||||
false,
|
||||
"Get unexpected response after deleting the id: {:?}",
|
||||
other
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ fn test_hsts_entry_is_not_expired_when_it_has_no_timestamp() {
|
|||
host: "mozilla.org".to_owned(),
|
||||
include_subdomains: false,
|
||||
max_age: Some(20),
|
||||
timestamp: None
|
||||
timestamp: None,
|
||||
};
|
||||
|
||||
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(),
|
||||
include_subdomains: false,
|
||||
max_age: None,
|
||||
timestamp: Some(time::get_time().sec as u64)
|
||||
timestamp: Some(time::get_time().sec as u64),
|
||||
};
|
||||
|
||||
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(),
|
||||
include_subdomains: false,
|
||||
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());
|
||||
|
@ -46,7 +46,9 @@ fn test_hsts_entry_is_expired_when_it_has_reached_its_max_age() {
|
|||
#[test]
|
||||
fn test_hsts_entry_cant_be_created_with_ipv6_address_as_host() {
|
||||
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");
|
||||
|
@ -54,9 +56,7 @@ fn test_hsts_entry_cant_be_created_with_ipv6_address_as_host() {
|
|||
|
||||
#[test]
|
||||
fn test_hsts_entry_cant_be_created_with_ipv4_address_as_host() {
|
||||
let entry = HstsEntry::new(
|
||||
"4.4.4.4".to_owned(), IncludeSubdomains::NotIncluded, None
|
||||
);
|
||||
let entry = HstsEntry::new("4.4.4.4".to_owned(), IncludeSubdomains::NotIncluded, None);
|
||||
|
||||
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 mut list = HstsList {
|
||||
entries_map: entries_map
|
||||
entries_map: entries_map,
|
||||
};
|
||||
|
||||
list.push(HstsEntry::new("servo.mozilla.org".to_owned(),
|
||||
IncludeSubdomains::NotIncluded, None).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());
|
||||
list.push(
|
||||
HstsEntry::new(
|
||||
"servo.mozilla.org".to_owned(),
|
||||
IncludeSubdomains::NotIncluded,
|
||||
None,
|
||||
)
|
||||
.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.get("mozilla.org").unwrap().len(), 2);
|
||||
|
@ -83,14 +101,27 @@ fn test_base_domain_in_entries_map() {
|
|||
#[test]
|
||||
fn test_push_entry_with_0_max_age_evicts_entry_from_list() {
|
||||
let mut entries_map = HashMap::new();
|
||||
entries_map.insert("mozilla.org".to_owned(), vec!(HstsEntry::new("mozilla.org".to_owned(),
|
||||
IncludeSubdomains::NotIncluded, Some(500000u64)).unwrap()));
|
||||
entries_map.insert(
|
||||
"mozilla.org".to_owned(),
|
||||
vec![HstsEntry::new(
|
||||
"mozilla.org".to_owned(),
|
||||
IncludeSubdomains::NotIncluded,
|
||||
Some(500000u64),
|
||||
)
|
||||
.unwrap()],
|
||||
);
|
||||
let mut list = HstsList {
|
||||
entries_map: entries_map
|
||||
entries_map: entries_map,
|
||||
};
|
||||
|
||||
list.push(HstsEntry::new("mozilla.org".to_owned(),
|
||||
IncludeSubdomains::NotIncluded, Some(0)).unwrap());
|
||||
list.push(
|
||||
HstsEntry::new(
|
||||
"mozilla.org".to_owned(),
|
||||
IncludeSubdomains::NotIncluded,
|
||||
Some(0),
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
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]
|
||||
fn test_push_entry_to_hsts_list_should_not_add_subdomains_whose_superdomain_is_already_matched() {
|
||||
let mut entries_map = HashMap::new();
|
||||
entries_map.insert("mozilla.org".to_owned(), vec!(HstsEntry::new("mozilla.org".to_owned(),
|
||||
IncludeSubdomains::Included, None).unwrap()));
|
||||
entries_map.insert(
|
||||
"mozilla.org".to_owned(),
|
||||
vec![HstsEntry::new("mozilla.org".to_owned(), IncludeSubdomains::Included, None).unwrap()],
|
||||
);
|
||||
let mut list = HstsList {
|
||||
entries_map: entries_map
|
||||
entries_map: entries_map,
|
||||
};
|
||||
|
||||
list.push(HstsEntry::new("servo.mozilla.org".to_owned(),
|
||||
IncludeSubdomains::NotIncluded, None).unwrap());
|
||||
list.push(
|
||||
HstsEntry::new(
|
||||
"servo.mozilla.org".to_owned(),
|
||||
IncludeSubdomains::NotIncluded,
|
||||
None,
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
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]
|
||||
fn test_push_entry_to_hsts_list_should_update_existing_domain_entrys_include_subdomains() {
|
||||
let mut entries_map = HashMap::new();
|
||||
entries_map.insert("mozilla.org".to_owned(), vec!(HstsEntry::new("mozilla.org".to_owned(),
|
||||
IncludeSubdomains::Included, None).unwrap()));
|
||||
entries_map.insert(
|
||||
"mozilla.org".to_owned(),
|
||||
vec![HstsEntry::new("mozilla.org".to_owned(), IncludeSubdomains::Included, None).unwrap()],
|
||||
);
|
||||
let mut list = HstsList {
|
||||
entries_map: entries_map
|
||||
entries_map: entries_map,
|
||||
};
|
||||
|
||||
assert!(list.is_host_secure("servo.mozilla.org"));
|
||||
|
||||
list.push(HstsEntry::new("mozilla.org".to_owned(),
|
||||
IncludeSubdomains::NotIncluded, None).unwrap());
|
||||
list.push(
|
||||
HstsEntry::new(
|
||||
"mozilla.org".to_owned(),
|
||||
IncludeSubdomains::NotIncluded,
|
||||
None,
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
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]
|
||||
fn test_push_entry_to_hsts_list_should_not_create_duplicate_entry() {
|
||||
let mut entries_map = HashMap::new();
|
||||
entries_map.insert("mozilla.org".to_owned(), vec!(HstsEntry::new("mozilla.org".to_owned(),
|
||||
IncludeSubdomains::NotIncluded, None).unwrap()));
|
||||
entries_map.insert(
|
||||
"mozilla.org".to_owned(),
|
||||
vec![HstsEntry::new(
|
||||
"mozilla.org".to_owned(),
|
||||
IncludeSubdomains::NotIncluded,
|
||||
None,
|
||||
)
|
||||
.unwrap()],
|
||||
);
|
||||
let mut list = HstsList {
|
||||
entries_map: entries_map
|
||||
entries_map: entries_map,
|
||||
};
|
||||
|
||||
list.push(HstsEntry::new("mozilla.org".to_owned(),
|
||||
IncludeSubdomains::NotIncluded, None).unwrap());
|
||||
list.push(
|
||||
HstsEntry::new(
|
||||
"mozilla.org".to_owned(),
|
||||
IncludeSubdomains::NotIncluded,
|
||||
None,
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
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]
|
||||
fn test_push_multiple_entrie_to_hsts_list_should_add_them_all() {
|
||||
let mut list = HstsList {
|
||||
entries_map: HashMap::new()
|
||||
entries_map: HashMap::new(),
|
||||
};
|
||||
|
||||
assert!(!list.is_host_secure("mozilla.org"));
|
||||
assert!(!list.is_host_secure("bugzilla.org"));
|
||||
|
||||
list.push(HstsEntry::new("mozilla.org".to_owned(),
|
||||
IncludeSubdomains::Included, None).unwrap());
|
||||
list.push(HstsEntry::new("bugzilla.org".to_owned(),
|
||||
IncludeSubdomains::Included, None).unwrap());
|
||||
list.push(HstsEntry::new("mozilla.org".to_owned(), IncludeSubdomains::Included, None).unwrap());
|
||||
list.push(
|
||||
HstsEntry::new("bugzilla.org".to_owned(), IncludeSubdomains::Included, None).unwrap(),
|
||||
);
|
||||
|
||||
assert!(list.is_host_secure("mozilla.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]
|
||||
fn test_push_entry_to_hsts_list_should_add_an_entry() {
|
||||
let mut list = HstsList {
|
||||
entries_map: HashMap::new()
|
||||
entries_map: HashMap::new(),
|
||||
};
|
||||
|
||||
assert!(!list.is_host_secure("mozilla.org"));
|
||||
|
||||
list.push(HstsEntry::new("mozilla.org".to_owned(),
|
||||
IncludeSubdomains::Included, None).unwrap());
|
||||
list.push(HstsEntry::new("mozilla.org".to_owned(), IncludeSubdomains::Included, None).unwrap());
|
||||
|
||||
assert!(list.is_host_secure("mozilla.org"));
|
||||
}
|
||||
|
@ -177,34 +236,43 @@ fn test_push_entry_to_hsts_list_should_add_an_entry() {
|
|||
#[test]
|
||||
fn test_parse_hsts_preload_should_return_none_when_json_invalid() {
|
||||
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]
|
||||
fn test_parse_hsts_preload_should_return_none_when_json_contains_no_entries_map_key() {
|
||||
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]
|
||||
fn test_parse_hsts_preload_should_decode_host_and_includes_subdomains() {
|
||||
let mock_preload_content = "{\
|
||||
\"entries\": [\
|
||||
{\"host\": \"mozilla.org\",\
|
||||
\"include_subdomains\": false}\
|
||||
]\
|
||||
\"entries\": [\
|
||||
{\"host\": \"mozilla.org\",\
|
||||
\"include_subdomains\": false}\
|
||||
]\
|
||||
}";
|
||||
let hsts_list = HstsList::from_preload(mock_preload_content);
|
||||
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);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hsts_list_with_no_entries_map_does_not_is_host_secure() {
|
||||
let hsts_list = HstsList {
|
||||
entries_map: HashMap::new()
|
||||
entries_map: HashMap::new(),
|
||||
};
|
||||
|
||||
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]
|
||||
fn test_hsts_list_with_exact_domain_entry_is_is_host_secure() {
|
||||
let mut entries_map = HashMap::new();
|
||||
entries_map.insert("mozilla.org".to_owned(), vec![HstsEntry::new("mozilla.org".to_owned(),
|
||||
IncludeSubdomains::NotIncluded, None).unwrap()]);
|
||||
entries_map.insert(
|
||||
"mozilla.org".to_owned(),
|
||||
vec![HstsEntry::new(
|
||||
"mozilla.org".to_owned(),
|
||||
IncludeSubdomains::NotIncluded,
|
||||
None,
|
||||
)
|
||||
.unwrap()],
|
||||
);
|
||||
|
||||
let hsts_list = HstsList {
|
||||
entries_map: entries_map
|
||||
entries_map: entries_map,
|
||||
};
|
||||
|
||||
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]
|
||||
fn test_hsts_list_with_subdomain_when_include_subdomains_is_true_is_is_host_secure() {
|
||||
let mut entries_map = HashMap::new();
|
||||
entries_map.insert("mozilla.org".to_owned(), vec![HstsEntry::new("mozilla.org".to_owned(),
|
||||
IncludeSubdomains::Included, None).unwrap()]);
|
||||
entries_map.insert(
|
||||
"mozilla.org".to_owned(),
|
||||
vec![HstsEntry::new("mozilla.org".to_owned(), IncludeSubdomains::Included, None).unwrap()],
|
||||
);
|
||||
let hsts_list = HstsList {
|
||||
entries_map: entries_map
|
||||
entries_map: entries_map,
|
||||
};
|
||||
|
||||
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]
|
||||
fn test_hsts_list_with_subdomain_when_include_subdomains_is_false_is_not_is_host_secure() {
|
||||
let mut entries_map = HashMap::new();
|
||||
entries_map.insert("mozilla.org".to_owned(), vec![HstsEntry::new("mozilla.org".to_owned(),
|
||||
IncludeSubdomains::NotIncluded, None).unwrap()]);
|
||||
entries_map.insert(
|
||||
"mozilla.org".to_owned(),
|
||||
vec![HstsEntry::new(
|
||||
"mozilla.org".to_owned(),
|
||||
IncludeSubdomains::NotIncluded,
|
||||
None,
|
||||
)
|
||||
.unwrap()],
|
||||
);
|
||||
let hsts_list = HstsList {
|
||||
entries_map: entries_map
|
||||
entries_map: entries_map,
|
||||
};
|
||||
|
||||
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]
|
||||
fn test_hsts_list_with_subdomain_when_host_is_not_a_subdomain_is_not_is_host_secure() {
|
||||
let mut entries_map = HashMap::new();
|
||||
entries_map.insert("mozilla.org".to_owned(), vec![HstsEntry::new("mozilla.org".to_owned(),
|
||||
IncludeSubdomains::Included, None).unwrap()]);
|
||||
entries_map.insert(
|
||||
"mozilla.org".to_owned(),
|
||||
vec![HstsEntry::new("mozilla.org".to_owned(), IncludeSubdomains::Included, None).unwrap()],
|
||||
);
|
||||
let hsts_list = HstsList {
|
||||
entries_map: entries_map
|
||||
entries_map: entries_map,
|
||||
};
|
||||
|
||||
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]
|
||||
fn test_hsts_list_with_subdomain_when_host_is_exact_match_is_is_host_secure() {
|
||||
let mut entries_map = HashMap::new();
|
||||
entries_map.insert("mozilla.org".to_owned(), vec![HstsEntry::new("mozilla.org".to_owned(),
|
||||
IncludeSubdomains::Included, None).unwrap()]);
|
||||
entries_map.insert(
|
||||
"mozilla.org".to_owned(),
|
||||
vec![HstsEntry::new("mozilla.org".to_owned(), IncludeSubdomains::Included, None).unwrap()],
|
||||
);
|
||||
let hsts_list = HstsList {
|
||||
entries_map: entries_map
|
||||
entries_map: entries_map,
|
||||
};
|
||||
|
||||
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]
|
||||
fn test_hsts_list_with_expired_entry_is_not_is_host_secure() {
|
||||
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(),
|
||||
include_subdomains: false,
|
||||
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 {
|
||||
entries_map: entries_map
|
||||
entries_map: entries_map,
|
||||
};
|
||||
|
||||
assert!(!hsts_list.is_host_secure("mozilla.org"));
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -70,9 +70,7 @@ use tokio::runtime::Runtime;
|
|||
use tokio_openssl::SslAcceptorExt;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref HANDLE: Mutex<Runtime> = {
|
||||
Mutex::new(Runtime::new().unwrap())
|
||||
};
|
||||
pub static ref HANDLE: Mutex<Runtime> = { Mutex::new(Runtime::new().unwrap()) };
|
||||
}
|
||||
|
||||
const DEFAULT_USER_AGENT: &'static str = "Such Browser. Very Layout. Wow.";
|
||||
|
@ -83,18 +81,17 @@ struct FetchResponseCollector {
|
|||
|
||||
fn create_embedder_proxy() -> EmbedderProxy {
|
||||
let (sender, _) = channel();
|
||||
let event_loop_waker = | | {
|
||||
struct DummyEventLoopWaker {
|
||||
}
|
||||
let event_loop_waker = || {
|
||||
struct DummyEventLoopWaker {}
|
||||
impl DummyEventLoopWaker {
|
||||
fn new() -> DummyEventLoopWaker {
|
||||
DummyEventLoopWaker { }
|
||||
DummyEventLoopWaker {}
|
||||
}
|
||||
}
|
||||
impl EventLoopWaker for DummyEventLoopWaker {
|
||||
fn wake(&self) { }
|
||||
fn wake(&self) {}
|
||||
fn clone(&self) -> Box<EventLoopWaker + Send> {
|
||||
Box::new(DummyEventLoopWaker { })
|
||||
Box::new(DummyEventLoopWaker {})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -103,12 +100,16 @@ fn create_embedder_proxy() -> EmbedderProxy {
|
|||
|
||||
EmbedderProxy {
|
||||
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 {
|
||||
let ssl_connector = create_ssl_connector_builder(&resources::read_string(Resource::SSLCertificates));
|
||||
fn new_fetch_context(
|
||||
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());
|
||||
FetchContext {
|
||||
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 {
|
||||
let (sender, receiver) = channel();
|
||||
let mut target = FetchResponseCollector {
|
||||
sender: sender,
|
||||
};
|
||||
let mut target = FetchResponseCollector { sender: sender };
|
||||
|
||||
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 {
|
||||
let (sender, receiver) = channel();
|
||||
let mut target = FetchResponseCollector {
|
||||
sender: sender,
|
||||
};
|
||||
let mut target = FetchResponseCollector { sender: sender };
|
||||
|
||||
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)
|
||||
where
|
||||
where
|
||||
H: Fn(HyperRequest<Body>, &mut HyperResponse<Body>) + Send + Sync + 'static,
|
||||
{
|
||||
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 = ServoUrl::parse(&url_string).unwrap();
|
||||
let (tx, rx) = futures::sync::oneshot::channel::<()>();
|
||||
let server = HyperServer::from_tcp(listener).unwrap().serve(
|
||||
move || {
|
||||
let server = HyperServer::from_tcp(listener)
|
||||
.unwrap()
|
||||
.serve(move || {
|
||||
let handler = handler.clone();
|
||||
service_fn_ok(move |req: HyperRequest<Body>| {
|
||||
let mut response = HyperResponse::new(Vec::<u8>::new().into());
|
||||
handler(req, &mut response);
|
||||
response
|
||||
})
|
||||
}
|
||||
)
|
||||
.with_graceful_shutdown(rx)
|
||||
.map_err(|_|());
|
||||
})
|
||||
.with_graceful_shutdown(rx)
|
||||
.map_err(|_| ());
|
||||
|
||||
HANDLE.lock().unwrap().spawn(server);
|
||||
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)
|
||||
where
|
||||
where
|
||||
H: Fn(HyperRequest<Body>, &mut HyperResponse<Body>) + Send + Sync + 'static,
|
||||
{
|
||||
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 = ServoUrl::parse(&url_string).unwrap();
|
||||
|
||||
let server = listener.incoming()
|
||||
.map_err(|_| ())
|
||||
.for_each(move |sock| {
|
||||
let mut ssl_builder = SslAcceptor::mozilla_modern(SslMethod::tls()).unwrap();
|
||||
ssl_builder.set_certificate_file(&cert_path, SslFiletype::PEM).unwrap();
|
||||
ssl_builder.set_private_key_file(&key_path, SslFiletype::PEM).unwrap();
|
||||
let server = listener.incoming().map_err(|_| ()).for_each(move |sock| {
|
||||
let mut ssl_builder = SslAcceptor::mozilla_modern(SslMethod::tls()).unwrap();
|
||||
ssl_builder
|
||||
.set_certificate_file(&cert_path, SslFiletype::PEM)
|
||||
.unwrap();
|
||||
ssl_builder
|
||||
.set_private_key_file(&key_path, SslFiletype::PEM)
|
||||
.unwrap();
|
||||
|
||||
let handler = handler.clone();
|
||||
ssl_builder.build().accept_async(sock).map_err(|_| ()).and_then(move |ssl| {
|
||||
Http::new().serve_connection(ssl,
|
||||
let handler = handler.clone();
|
||||
ssl_builder
|
||||
.build()
|
||||
.accept_async(sock)
|
||||
.map_err(|_| ())
|
||||
.and_then(move |ssl| {
|
||||
Http::new()
|
||||
.serve_connection(
|
||||
ssl,
|
||||
service_fn_ok(move |req: HyperRequest<Body>| {
|
||||
let mut response = HyperResponse::new(Vec::<u8>::new().into());
|
||||
handler(req, &mut response);
|
||||
response
|
||||
})
|
||||
)
|
||||
.map_err(|_|())
|
||||
}),
|
||||
)
|
||||
.map_err(|_| ())
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
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);
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ fn test_sniff_mp4_matcher() {
|
|||
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 mut data: [u8; 260] = [0; 260];
|
||||
&data[.. 11].clone_from_slice(
|
||||
&[0x00, 0x00, 0x01, 0x04, 0x66, 0x74, 0x79, 0x70, 0x6D, 0x70, 0x34]
|
||||
);
|
||||
&data[..11].clone_from_slice(&[
|
||||
0x00, 0x00, 0x01, 0x04, 0x66, 0x74, 0x79, 0x70, 0x6D, 0x70, 0x34,
|
||||
]);
|
||||
|
||||
assert!(matcher.matches(&data));
|
||||
}
|
||||
|
@ -57,13 +57,18 @@ fn test_validate_classifier() {
|
|||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn test_sniff_with_flags(filename_orig: &path::Path,
|
||||
expected_mime: Mime,
|
||||
supplied_type: Option<Mime>,
|
||||
no_sniff_flag: NoSniffFlag,
|
||||
apache_bug_flag: ApacheBugFlag) {
|
||||
fn test_sniff_with_flags(
|
||||
filename_orig: &path::Path,
|
||||
expected_mime: Mime,
|
||||
supplied_type: Option<Mime>,
|
||||
no_sniff_flag: NoSniffFlag,
|
||||
apache_bug_flag: ApacheBugFlag,
|
||||
) {
|
||||
let current_working_directory = env::current_dir().unwrap();
|
||||
println!("The current directory is {}", current_working_directory.display());
|
||||
println!(
|
||||
"The current directory is {}",
|
||||
current_working_directory.display()
|
||||
);
|
||||
|
||||
let mut filename = PathBuf::from("tests/parsable_mime/");
|
||||
filename.push(filename_orig);
|
||||
|
@ -74,30 +79,35 @@ fn test_sniff_with_flags(filename_orig: &path::Path,
|
|||
|
||||
match read_result {
|
||||
Ok(data) => {
|
||||
let parsed_mime = classifier.classify(LoadContext::Browsing,
|
||||
no_sniff_flag,
|
||||
apache_bug_flag,
|
||||
&supplied_type,
|
||||
&data);
|
||||
let parsed_mime = classifier.classify(
|
||||
LoadContext::Browsing,
|
||||
no_sniff_flag,
|
||||
apache_bug_flag,
|
||||
&supplied_type,
|
||||
&data,
|
||||
);
|
||||
if (parsed_mime.type_() != expected_mime.type_()) ||
|
||||
(parsed_mime.subtype() != expected_mime.subtype()) {
|
||||
panic!("File {:?} parsed incorrectly should be {:?}, parsed as {:?}",
|
||||
filename, expected_mime, parsed_mime);
|
||||
(parsed_mime.subtype() != expected_mime.subtype())
|
||||
{
|
||||
panic!(
|
||||
"File {:?} parsed incorrectly should be {:?}, parsed as {:?}",
|
||||
filename, expected_mime, parsed_mime
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(e) => panic!("Couldn't read from file {:?} with error {}",
|
||||
filename, e),
|
||||
},
|
||||
Err(e) => panic!("Couldn't read from file {:?} with error {}", filename, e),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn test_sniff_full(filename_orig: &path::Path, expected_mime: Mime,
|
||||
supplied_type: Option<Mime>) {
|
||||
test_sniff_with_flags(filename_orig,
|
||||
expected_mime,
|
||||
supplied_type,
|
||||
NoSniffFlag::Off,
|
||||
ApacheBugFlag::Off)
|
||||
fn test_sniff_full(filename_orig: &path::Path, expected_mime: Mime, supplied_type: Option<Mime>) {
|
||||
test_sniff_with_flags(
|
||||
filename_orig,
|
||||
expected_mime,
|
||||
supplied_type,
|
||||
NoSniffFlag::Off,
|
||||
ApacheBugFlag::Off,
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -198,32 +208,50 @@ fn test_sniff_wave() {
|
|||
#[test]
|
||||
fn test_sniff_ogg() {
|
||||
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]
|
||||
#[should_panic]
|
||||
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]
|
||||
#[should_panic]
|
||||
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]
|
||||
#[should_panic]
|
||||
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]
|
||||
#[should_panic]
|
||||
fn test_sniff_true_type_collection() {
|
||||
test_sniff_full(&PathBuf::from("unknown/true_type_collection.ttc"), "(TrueType Collection)/".parse().unwrap(),
|
||||
None);
|
||||
test_sniff_full(
|
||||
&PathBuf::from("unknown/true_type_collection.ttc"),
|
||||
"(TrueType Collection)/".parse().unwrap(),
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -244,7 +272,11 @@ fn test_sniff_zip() {
|
|||
|
||||
#[test]
|
||||
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]
|
||||
|
@ -466,86 +498,130 @@ fn test_sniff_utf_8_bom() {
|
|||
#[test]
|
||||
fn test_sniff_rss_feed() {
|
||||
// RSS feeds
|
||||
test_sniff_full(&PathBuf::from("text/xml/feed.rss"), "application/rss+xml".parse().unwrap(), Some(mime::TEXT_HTML));
|
||||
test_sniff_full(&PathBuf::from("text/xml/rdf_rss.xml"), "application/rss+xml".parse().unwrap(),
|
||||
Some(mime::TEXT_HTML));
|
||||
test_sniff_full(
|
||||
&PathBuf::from("text/xml/feed.rss"),
|
||||
"application/rss+xml".parse().unwrap(),
|
||||
Some(mime::TEXT_HTML),
|
||||
);
|
||||
test_sniff_full(
|
||||
&PathBuf::from("text/xml/rdf_rss.xml"),
|
||||
"application/rss+xml".parse().unwrap(),
|
||||
Some(mime::TEXT_HTML),
|
||||
);
|
||||
// Not RSS feeds
|
||||
test_sniff_full(&PathBuf::from("text/xml/rdf_rss_ko_1.xml"), 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_sniff_full(
|
||||
&PathBuf::from("text/xml/rdf_rss_ko_1.xml"),
|
||||
mime::TEXT_HTML,
|
||||
Some(mime::TEXT_HTML),
|
||||
);
|
||||
test_sniff_full(
|
||||
&PathBuf::from("text/xml/rdf_rss_ko_2.xml"),
|
||||
mime::TEXT_HTML,
|
||||
Some(mime::TEXT_HTML),
|
||||
);
|
||||
test_sniff_full(
|
||||
&PathBuf::from("text/xml/rdf_rss_ko_3.xml"),
|
||||
mime::TEXT_HTML,
|
||||
Some(mime::TEXT_HTML),
|
||||
);
|
||||
test_sniff_full(
|
||||
&PathBuf::from("text/xml/rdf_rss_ko_4.xml"),
|
||||
mime::TEXT_HTML,
|
||||
Some(mime::TEXT_HTML),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sniff_atom_feed() {
|
||||
test_sniff_full(&PathBuf::from("text/xml/feed.atom"), "application/atom+xml".parse().unwrap(),
|
||||
Some(mime::TEXT_HTML));
|
||||
test_sniff_full(
|
||||
&PathBuf::from("text/xml/feed.atom"),
|
||||
"application/atom+xml".parse().unwrap(),
|
||||
Some(mime::TEXT_HTML),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sniff_binary_file() {
|
||||
test_sniff_full(&PathBuf::from("unknown/binary_file"), mime::APPLICATION_OCTET_STREAM, None);
|
||||
test_sniff_full(
|
||||
&PathBuf::from("unknown/binary_file"),
|
||||
mime::APPLICATION_OCTET_STREAM,
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sniff_atom_feed_with_no_sniff_flag_on() {
|
||||
test_sniff_with_flags(&PathBuf::from("text/xml/feed.atom"),
|
||||
mime::TEXT_HTML,
|
||||
Some(mime::TEXT_HTML),
|
||||
NoSniffFlag::On,
|
||||
ApacheBugFlag::Off);
|
||||
test_sniff_with_flags(
|
||||
&PathBuf::from("text/xml/feed.atom"),
|
||||
mime::TEXT_HTML,
|
||||
Some(mime::TEXT_HTML),
|
||||
NoSniffFlag::On,
|
||||
ApacheBugFlag::Off,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sniff_with_no_sniff_flag_on_and_apache_flag_on() {
|
||||
test_sniff_with_flags(&PathBuf::from("text/xml/feed.atom"),
|
||||
mime::TEXT_HTML,
|
||||
Some(mime::TEXT_HTML),
|
||||
NoSniffFlag::On,
|
||||
ApacheBugFlag::On);
|
||||
test_sniff_with_flags(
|
||||
&PathBuf::from("text/xml/feed.atom"),
|
||||
mime::TEXT_HTML,
|
||||
Some(mime::TEXT_HTML),
|
||||
NoSniffFlag::On,
|
||||
ApacheBugFlag::On,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sniff_utf_8_bom_with_apache_flag_on() {
|
||||
test_sniff_with_flags(&PathBuf::from("text/plain/utf8bom.txt"),
|
||||
mime::TEXT_PLAIN,
|
||||
Some("dummy/text".parse().unwrap()),
|
||||
NoSniffFlag::Off,
|
||||
ApacheBugFlag::On);
|
||||
test_sniff_with_flags(
|
||||
&PathBuf::from("text/plain/utf8bom.txt"),
|
||||
mime::TEXT_PLAIN,
|
||||
Some("dummy/text".parse().unwrap()),
|
||||
NoSniffFlag::Off,
|
||||
ApacheBugFlag::On,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sniff_utf_16be_bom_with_apache_flag_on() {
|
||||
test_sniff_with_flags(&PathBuf::from("text/plain/utf16bebom.txt"),
|
||||
mime::TEXT_PLAIN,
|
||||
Some("dummy/text".parse().unwrap()),
|
||||
NoSniffFlag::Off,
|
||||
ApacheBugFlag::On);
|
||||
test_sniff_with_flags(
|
||||
&PathBuf::from("text/plain/utf16bebom.txt"),
|
||||
mime::TEXT_PLAIN,
|
||||
Some("dummy/text".parse().unwrap()),
|
||||
NoSniffFlag::Off,
|
||||
ApacheBugFlag::On,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sniff_utf_16le_bom_with_apache_flag_on() {
|
||||
test_sniff_with_flags(&PathBuf::from("text/plain/utf16lebom.txt"),
|
||||
mime::TEXT_PLAIN,
|
||||
Some("dummy/text".parse().unwrap()),
|
||||
NoSniffFlag::Off,
|
||||
ApacheBugFlag::On);
|
||||
test_sniff_with_flags(
|
||||
&PathBuf::from("text/plain/utf16lebom.txt"),
|
||||
mime::TEXT_PLAIN,
|
||||
Some("dummy/text".parse().unwrap()),
|
||||
NoSniffFlag::Off,
|
||||
ApacheBugFlag::On,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sniff_octet_stream_apache_flag_on() {
|
||||
test_sniff_with_flags(&PathBuf::from("unknown/binary_file"),
|
||||
mime::APPLICATION_OCTET_STREAM,
|
||||
Some("dummy/binary".parse().unwrap()),
|
||||
NoSniffFlag::Off,
|
||||
ApacheBugFlag::On);
|
||||
test_sniff_with_flags(
|
||||
&PathBuf::from("unknown/binary_file"),
|
||||
mime::APPLICATION_OCTET_STREAM,
|
||||
Some("dummy/binary".parse().unwrap()),
|
||||
NoSniffFlag::Off,
|
||||
ApacheBugFlag::On,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sniff_mp4_video_apache_flag_on() {
|
||||
test_sniff_with_flags(&PathBuf::from("video/mp4/test.mp4"),
|
||||
mime::APPLICATION_OCTET_STREAM,
|
||||
Some("video/mp4".parse().unwrap()),
|
||||
NoSniffFlag::Off,
|
||||
ApacheBugFlag::On);
|
||||
test_sniff_with_flags(
|
||||
&PathBuf::from("video/mp4/test.mp4"),
|
||||
mime::APPLICATION_OCTET_STREAM,
|
||||
Some("video/mp4".parse().unwrap()),
|
||||
NoSniffFlag::Off,
|
||||
ApacheBugFlag::On,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -21,7 +21,13 @@ fn test_exit() {
|
|||
let (mtx, _mrx) = ipc::channel().unwrap();
|
||||
let (sender, receiver) = ipc::channel().unwrap();
|
||||
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();
|
||||
receiver.recv().unwrap();
|
||||
}
|
||||
|
@ -32,12 +38,16 @@ fn test_parse_hostsfile() {
|
|||
let hosts_table = parse_hostsfile(mock_hosts_file_content);
|
||||
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.2"), *hosts_table.get("servo.test.server").unwrap());
|
||||
assert_eq!(
|
||||
ip("127.0.0.2"),
|
||||
*hosts_table.get("servo.test.server").unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
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);
|
||||
assert_eq!(1, hosts_table.len());
|
||||
assert_eq!(ip("127.0.0.1"), *hosts_table.get("foo.bar.com").unwrap());
|
||||
|
@ -45,7 +55,8 @@ fn test_parse_malformed_hostsfile() {
|
|||
|
||||
#[test]
|
||||
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);
|
||||
assert_eq!(1, hosts_table.len());
|
||||
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]
|
||||
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);
|
||||
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.2"), *hosts_table.get("servo.test.server").unwrap());
|
||||
assert_eq!(
|
||||
ip("127.0.0.2"),
|
||||
*hosts_table.get("servo.test.server").unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -86,12 +101,14 @@ fn test_parse_hostsfile_with_tabs_instead_spaces() {
|
|||
let hosts_table = parse_hostsfile(mock_hosts_file_content);
|
||||
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.2"), *hosts_table.get("servo.test.server").unwrap());
|
||||
assert_eq!(
|
||||
ip("127.0.0.2"),
|
||||
*hosts_table.get("servo.test.server").unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_hostsfile_with_valid_ipv4_addresses()
|
||||
{
|
||||
fn test_parse_hostsfile_with_valid_ipv4_addresses() {
|
||||
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";
|
||||
let hosts_table = parse_hostsfile(mock_hosts_file_content);
|
||||
|
@ -99,8 +116,7 @@ fn test_parse_hostsfile_with_valid_ipv4_addresses()
|
|||
}
|
||||
|
||||
#[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 \
|
||||
\n192.168.5.500 servo.foo.com\n192.abc.100.2 test.servo.com";
|
||||
let hosts_table = parse_hostsfile(mock_hosts_file_content);
|
||||
|
@ -108,8 +124,7 @@ fn test_parse_hostsfile_with_invalid_ipv4_addresses()
|
|||
}
|
||||
|
||||
#[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\
|
||||
2001:db8:0:0:0:ff00:42:8329 moz.foo.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]
|
||||
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\
|
||||
2001:zdb8:0:0:0:gg00:42:t329 moz.foo.com\n\
|
||||
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]
|
||||
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\
|
||||
2001:db8:0:0:0:ff00:42:8329 moz.foo.com\n \
|
||||
127.0.0.2 servo.test.server ";
|
||||
let hosts_table = parse_hostsfile(mock_hosts_file_content);
|
||||
assert_eq!(3, hosts_table.len());
|
||||
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!(ip("127.0.0.2"), *hosts_table.get("servo.test.server").unwrap());
|
||||
assert_eq!(
|
||||
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()
|
||||
);
|
||||
}
|
||||
|
|
|
@ -72,7 +72,8 @@ fn test_response_integrity_valid() {
|
|||
let url: ServoUrl = ServoUrl::parse("http://servo.org").unwrap();
|
||||
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();
|
||||
|
||||
*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 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();
|
||||
|
||||
*response.body.lock().unwrap() = ResponseBody::Done(response_body);
|
||||
|
|
|
@ -51,20 +51,25 @@ impl<'a> Factory for Client<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
impl<'a> Handler for Client<'a> {
|
||||
fn build_request(&mut self, url: &Url) -> WebSocketResult<Request> {
|
||||
let mut req = Request::from_url(url)?;
|
||||
req.headers_mut().push(("Origin".to_string(), self.origin.as_bytes().to_owned()));
|
||||
req.headers_mut().push(("Host".to_string(), format!("{}", self.host).as_bytes().to_owned()));
|
||||
req.headers_mut()
|
||||
.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 {
|
||||
req.add_protocol(protocol);
|
||||
};
|
||||
}
|
||||
|
||||
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) {
|
||||
req.headers_mut().push(("Cookie".into(), cookie_list.as_bytes().to_owned()))
|
||||
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()))
|
||||
}
|
||||
|
||||
Ok(req)
|
||||
|
@ -83,41 +88,48 @@ impl<'a> Handler for Client<'a> {
|
|||
// TODO(eijebong): Replace thise once typed headers settled on a cookie impl
|
||||
for cookie in headers.get_all(header::SET_COOKIE) {
|
||||
if let Ok(s) = cookie.to_str() {
|
||||
if let Some(cookie) = Cookie::from_cookie_string(s.into(), self.resource_url, CookieSource::HTTP) {
|
||||
if let Some(cookie) =
|
||||
Cookie::from_cookie_string(s.into(), self.resource_url, CookieSource::HTTP)
|
||||
{
|
||||
jar.push(cookie, self.resource_url, CookieSource::HTTP);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let _ = self.event_sender.send(
|
||||
WebSocketNetworkEvent::ConnectionEstablished { protocol_in_use: self.protocol_in_use.clone() });
|
||||
let _ = self
|
||||
.event_sender
|
||||
.send(WebSocketNetworkEvent::ConnectionEstablished {
|
||||
protocol_in_use: self.protocol_in_use.clone(),
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn on_message(&mut self, message: Message) -> WebSocketResult<()> {
|
||||
fn on_message(&mut self, message: Message) -> WebSocketResult<()> {
|
||||
let message = match message {
|
||||
Message::Text(message) => MessageData::Text(message),
|
||||
Message::Binary(message) => MessageData::Binary(message),
|
||||
};
|
||||
let _ = self.event_sender.send(WebSocketNetworkEvent::MessageReceived(message));
|
||||
let _ = self
|
||||
.event_sender
|
||||
.send(WebSocketNetworkEvent::MessageReceived(message));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
fn on_error(&mut self, err: WebSocketError) {
|
||||
debug!("Error in WebSocket communication: {:?}", err);
|
||||
let _ = self.event_sender.send(WebSocketNetworkEvent::Fail);
|
||||
}
|
||||
|
||||
|
||||
fn on_response(&mut self, res: &WsResponse) -> WebSocketResult<()> {
|
||||
let protocol_in_use = res.protocol()?;
|
||||
|
||||
if let Some(protocol_name) = protocol_in_use {
|
||||
if !self.protocols.is_empty() && !self.protocols.iter().any(|p| protocol_name == (*p)) {
|
||||
let error = WebSocketError::new(WebSocketErrorKind::Protocol,
|
||||
"Protocol in Use not in client-supplied protocol list");
|
||||
let error = WebSocketError::new(
|
||||
WebSocketErrorKind::Protocol,
|
||||
"Protocol in Use not in client-supplied protocol list",
|
||||
);
|
||||
return Err(error);
|
||||
}
|
||||
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) {
|
||||
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(
|
||||
|
@ -136,106 +151,120 @@ impl<'a> Handler for Client<'a> {
|
|||
url: &Url,
|
||||
) -> WebSocketResult<SslStream<TcpStream>> {
|
||||
let certs = match opts::get().certificate_path {
|
||||
Some(ref path) => {
|
||||
fs::read_to_string(path).expect("Couldn't not find certificate file")
|
||||
}
|
||||
None => {
|
||||
resources::read_string(Resource::SSLCertificates)
|
||||
},
|
||||
Some(ref path) => fs::read_to_string(path).expect("Couldn't not find certificate file"),
|
||||
None => resources::read_string(Resource::SSLCertificates),
|
||||
};
|
||||
|
||||
let domain = self.resource_url.as_url().domain().ok_or(WebSocketError::new(
|
||||
WebSocketErrorKind::Protocol,
|
||||
format!("Unable to parse domain from {}. Needed for SSL.", url),
|
||||
))?;
|
||||
let domain = self
|
||||
.resource_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();
|
||||
connector.connect(domain, stream).map_err(WebSocketError::from)
|
||||
connector
|
||||
.connect(domain, stream)
|
||||
.map_err(WebSocketError::from)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pub fn init(
|
||||
req_init: RequestInit,
|
||||
resource_event_sender: IpcSender<WebSocketNetworkEvent>,
|
||||
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 || {
|
||||
let protocols = match req_init.mode {
|
||||
RequestMode::WebSocket { protocols } => protocols.clone(),
|
||||
_ => panic!("Received a RequestInit with a non-websocket mode in websocket_loader"),
|
||||
};
|
||||
thread::Builder::new()
|
||||
.name(format!("WebSocket connection to {}", req_init.url))
|
||||
.spawn(move || {
|
||||
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 mut req_url = req_init.url.clone();
|
||||
if scheme == "ws" {
|
||||
req_url.as_mut_url().set_scheme("http").unwrap();
|
||||
} else if scheme == "wss" {
|
||||
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(),
|
||||
};
|
||||
}
|
||||
},
|
||||
}
|
||||
let scheme = req_init.url.scheme();
|
||||
let mut req_url = req_init.url.clone();
|
||||
if scheme == "ws" {
|
||||
req_url.as_mut_url().set_scheme("http").unwrap();
|
||||
} else if scheme == "wss" {
|
||||
req_url.as_mut_url().set_scheme("https").unwrap();
|
||||
}
|
||||
});
|
||||
|
||||
if let Err(e) = ws.run() {
|
||||
debug!("Failed to run WebSocket: {:?}", e);
|
||||
let _ = resource_event_sender.send(WebSocketNetworkEvent::Fail);
|
||||
};
|
||||
}).expect("Thread spawning failed");
|
||||
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() {
|
||||
debug!("Failed to run WebSocket: {:?}", e);
|
||||
let _ = resource_event_sender.send(WebSocketNetworkEvent::Fail);
|
||||
};
|
||||
})
|
||||
.expect("Thread spawning failed");
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue