diff --git a/components/net/hsts.rs b/components/net/hsts.rs new file mode 100644 index 00000000000..8fcaa907b2c --- /dev/null +++ b/components/net/hsts.rs @@ -0,0 +1,145 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use regex::Regex; +use rustc_serialize::json::{decode}; +use time; +use url::Url; + +use std::str::{from_utf8}; + +use net_traits::LoadData; +use util::resource_files::read_resource_file; + +static IPV4_REGEX: Regex = regex!( + r"^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$" +); +static IPV6_REGEX: Regex = regex!(r"^([a-fA-F0-9]{0,4}[:]?){1,8}(/\d{1,3})?$"); + +#[derive(RustcDecodable, RustcEncodable, Clone)] +pub struct HSTSEntry { + pub host: String, + pub include_subdomains: bool, + pub max_age: Option, + pub timestamp: Option +} + +#[derive(PartialEq, Copy, Clone)] +pub enum Subdomains { + Included, + NotIncluded +} + +impl HSTSEntry { + pub fn new(host: String, subdomains: Subdomains, max_age: Option) -> Option { + if IPV4_REGEX.is_match(&host) || IPV6_REGEX.is_match(&host) { + None + } else { + Some(HSTSEntry { + host: host, + include_subdomains: (subdomains == Subdomains::Included), + max_age: max_age, + timestamp: Some(time::get_time().sec as u64) + }) + } + } + + pub fn is_expired(&self) -> bool { + match (self.max_age, self.timestamp) { + (Some(max_age), Some(timestamp)) => { + (time::get_time().sec as u64) - timestamp >= max_age + }, + + _ => false + } + } + + fn matches_domain(&self, host: &str) -> bool { + !self.is_expired() && self.host == host + } + + fn matches_subdomain(&self, host: &str) -> bool { + !self.is_expired() && host.ends_with(&format!(".{}", self.host)) + } +} + +#[derive(RustcDecodable, RustcEncodable)] +pub struct HSTSList { + pub entries: Vec +} + +impl HSTSList { + pub fn new_from_preload(preload_content: &str) -> Option { + decode(preload_content).ok() + } + + pub fn is_host_secure(&self, host: &str) -> bool { + // TODO - Should this be faster than O(n)? The HSTS list is only a few + // hundred or maybe thousand entries... + // + // Could optimise by searching for exact matches first (via a map or + // something), then checking for subdomains. + self.entries.iter().any(|e| { + if e.include_subdomains { + e.matches_subdomain(host) || e.matches_domain(host) + } else { + e.matches_domain(host) + } + }) + } + + fn has_domain(&self, host: &str) -> bool { + self.entries.iter().any(|e| { + e.matches_domain(&host) + }) + } + + fn has_subdomain(&self, host: &str) -> bool { + self.entries.iter().any(|e| { + e.matches_subdomain(host) + }) + } + + pub fn push(&mut self, entry: HSTSEntry) { + let have_domain = self.has_domain(&entry.host); + let have_subdomain = self.has_subdomain(&entry.host); + + if !have_domain && !have_subdomain { + self.entries.push(entry); + } else if !have_subdomain { + for e in &mut self.entries { + if e.matches_domain(&entry.host) { + e.include_subdomains = entry.include_subdomains; + e.max_age = entry.max_age; + } + } + } + } +} + +pub fn preload_hsts_domains() -> Option { + read_resource_file(&["hsts_preload.json"]).ok().and_then(|bytes| { + from_utf8(&bytes).ok().and_then(|hsts_preload_content| { + HSTSList::new_from_preload(hsts_preload_content) + }) + }) +} + +pub fn secure_load_data(load_data: &LoadData) -> LoadData { + if &*load_data.url.scheme == "http" { + let mut secure_load_data = load_data.clone(); + let mut secure_url = load_data.url.clone(); + secure_url.scheme = "https".to_string(); + // The Url struct parses the port for a known scheme only once. + // Updating the scheme doesn't update the port internally, resulting in + // HTTPS connections attempted on port 80. Serialising and re-parsing + // the Url is a hack to get around this. + secure_load_data.url = Url::parse(&secure_url.serialize()).unwrap(); + + secure_load_data + } else { + load_data.clone() + } +} + diff --git a/components/net/lib.rs b/components/net/lib.rs index 45af99e1a70..34f7cfff4ce 100644 --- a/components/net/lib.rs +++ b/components/net/lib.rs @@ -40,6 +40,7 @@ pub mod image_cache_task; pub mod net_error_list; pub mod pub_domains; pub mod resource_task; +pub mod hsts; pub mod storage_task; pub mod mime_classifier; diff --git a/components/net/resource_task.rs b/components/net/resource_task.rs index 1448b32496c..0554a263f62 100644 --- a/components/net/resource_task.rs +++ b/components/net/resource_task.rs @@ -17,15 +17,18 @@ use net_traits::{Metadata, ProgressMsg, ResourceTask, AsyncResponseTarget, Respo use net_traits::ProgressMsg::Done; use util::opts; use util::task::spawn_named; -use util::resource_files::read_resource_file; use url::Url; +use hsts::HSTSList; +use hsts::HSTSEntry; +use hsts::Subdomains; +use hsts::preload_hsts_domains; +use hsts::secure_load_data; + use devtools_traits::{DevtoolsControlMsg}; use hyper::header::{ContentType, Header, SetCookie, UserAgent}; use hyper::mime::{Mime, TopLevel, SubLevel}; -use rustc_serialize::json::{decode}; - use regex::Regex; use std::borrow::ToOwned; use std::boxed::FnBox; @@ -33,10 +36,8 @@ use std::collections::HashMap; use std::env; use std::fs::File; use std::io::{BufReader, Read}; -use std::str::{from_utf8}; use std::sync::Arc; use std::sync::mpsc::{channel, Receiver, Sender}; -use time; static mut HOST_TABLE: Option<*mut HashMap> = None; static IPV4_REGEX: Regex = regex!( @@ -162,14 +163,6 @@ pub fn start_sending_opt(start_chan: LoadConsumer, metadata: Metadata) -> Result } } -fn preload_hsts_domains() -> Option { - read_resource_file(&["hsts_preload.json"]).ok().and_then(|bytes| { - from_utf8(&bytes).ok().and_then(|hsts_preload_content| { - HSTSList::new_from_preload(hsts_preload_content) - }) - }) -} - /// Create a ResourceTask pub fn new_resource_task(user_agent: Option, devtools_chan: Option>) -> ResourceTask { @@ -192,124 +185,6 @@ pub fn new_resource_task(user_agent: Option, setup_chan } -#[derive(RustcDecodable, RustcEncodable, Clone)] -pub struct HSTSEntry { - pub host: String, - pub include_subdomains: bool, - pub max_age: Option, - pub timestamp: Option -} - -#[derive(PartialEq, Copy, Clone)] -pub enum Subdomains { - Included, - NotIncluded -} - -impl HSTSEntry { - pub fn new(host: String, subdomains: Subdomains, max_age: Option) -> Option { - if IPV4_REGEX.is_match(&host) || IPV6_REGEX.is_match(&host) { - None - } else { - Some(HSTSEntry { - host: host, - include_subdomains: (subdomains == Subdomains::Included), - max_age: max_age, - timestamp: Some(time::get_time().sec as u64) - }) - } - } - - pub fn is_expired(&self) -> bool { - match (self.max_age, self.timestamp) { - (Some(max_age), Some(timestamp)) => { - (time::get_time().sec as u64) - timestamp >= max_age - }, - - _ => false - } - } - - fn matches_domain(&self, host: &str) -> bool { - !self.is_expired() && self.host == host - } - - fn matches_subdomain(&self, host: &str) -> bool { - !self.is_expired() && host.ends_with(&format!(".{}", self.host)) - } -} - -#[derive(RustcDecodable, RustcEncodable)] -pub struct HSTSList { - pub entries: Vec -} - -impl HSTSList { - pub fn new_from_preload(preload_content: &str) -> Option { - decode(preload_content).ok() - } - - pub fn is_host_secure(&self, host: &str) -> bool { - // TODO - Should this be faster than O(n)? The HSTS list is only a few - // hundred or maybe thousand entries... - // - // Could optimise by searching for exact matches first (via a map or - // something), then checking for subdomains. - self.entries.iter().any(|e| { - if e.include_subdomains { - e.matches_subdomain(host) || e.matches_domain(host) - } else { - e.matches_domain(host) - } - }) - } - - fn has_domain(&self, host: &str) -> bool { - self.entries.iter().any(|e| { - e.matches_domain(&host) - }) - } - - fn has_subdomain(&self, host: &str) -> bool { - self.entries.iter().any(|e| { - e.matches_subdomain(host) - }) - } - - pub fn push(&mut self, entry: HSTSEntry) { - let have_domain = self.has_domain(&entry.host); - let have_subdomain = self.has_subdomain(&entry.host); - - if !have_domain && !have_subdomain { - self.entries.push(entry); - } else if !have_subdomain { - for e in &mut self.entries { - if e.matches_domain(&entry.host) { - e.include_subdomains = entry.include_subdomains; - e.max_age = entry.max_age; - } - } - } - } -} - -pub fn secure_load_data(load_data: &LoadData) -> LoadData { - if &*load_data.url.scheme == "http" { - let mut secure_load_data = load_data.clone(); - let mut secure_url = load_data.url.clone(); - secure_url.scheme = "https".to_string(); - // The Url struct parses the port for a known scheme only once. - // Updating the scheme doesn't update the port internally, resulting in - // HTTPS connections attempted on port 80. Serialising and re-parsing - // the Url is a hack to get around this. - secure_load_data.url = Url::parse(&secure_url.serialize()).unwrap(); - - secure_load_data - } else { - load_data.clone() - } -} - pub fn parse_hostsfile(hostsfile_content: &str) -> Box> { let mut host_table = HashMap::new(); let lines: Vec<&str> = hostsfile_content.split('\n').collect(); diff --git a/tests/unit/net/hsts.rs b/tests/unit/net/hsts.rs new file mode 100644 index 00000000000..7a93d7dd564 --- /dev/null +++ b/tests/unit/net/hsts.rs @@ -0,0 +1,273 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use net::hsts::HSTSList; +use net::hsts::HSTSEntry; +use net::hsts::Subdomains; +use net::hsts::secure_load_data; +use net::resource_task::ResourceManager; +use net_traits::LoadData; +use std::sync::mpsc::channel; +use url::Url; +use time; + +#[test] +fn test_add_hsts_entry_to_resource_manager_adds_an_hsts_entry() { + let list = HSTSList { + entries: Vec::new() + }; + + let (tx, _) = channel(); + let mut manager = ResourceManager::new(None, tx, Some(list), None); + + let entry = HSTSEntry::new( + "mozilla.org".to_string(), Subdomains::NotIncluded, None + ); + + assert!(!manager.is_host_sts("mozilla.org")); + + manager.add_hsts_entry(entry.unwrap()); + + assert!(manager.is_host_sts("mozilla.org")) +} + +#[test] +fn test_hsts_entry_is_not_expired_when_it_has_no_timestamp() { + let entry = HSTSEntry { + host: "mozilla.org".to_string(), + include_subdomains: false, + max_age: Some(20), + timestamp: None + }; + + assert!(!entry.is_expired()); +} + +#[test] +fn test_hsts_entry_is_not_expired_when_it_has_no_max_age() { + let entry = HSTSEntry { + host: "mozilla.org".to_string(), + include_subdomains: false, + max_age: None, + timestamp: Some(time::get_time().sec as u64) + }; + + assert!(!entry.is_expired()); +} + +#[test] +fn test_hsts_entry_is_expired_when_it_has_reached_its_max_age() { + let entry = HSTSEntry { + host: "mozilla.org".to_string(), + include_subdomains: false, + max_age: Some(10), + timestamp: Some(time::get_time().sec as u64 - 20u64) + }; + + assert!(entry.is_expired()); +} + +#[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_string(), Subdomains::NotIncluded, None + ); + + assert!(entry.is_none(), "able to create HSTSEntry with IPv6 host"); +} + +#[test] +fn test_hsts_entry_cant_be_created_with_ipv4_address_as_host() { + let entry = HSTSEntry::new( + "4.4.4.4".to_string(), Subdomains::NotIncluded, None + ); + + assert!(entry.is_none(), "able to create HSTSEntry with IPv4 host"); +} + +#[test] +fn test_push_entry_with_0_max_age_evicts_entry_from_list() { + let mut list = HSTSList { + entries: vec!(HSTSEntry::new("mozilla.org".to_string(), Subdomains::NotIncluded, Some(500000u64)).unwrap()) + }; + + list.push(HSTSEntry::new("mozilla.org".to_string(), Subdomains::NotIncluded, Some(0)).unwrap()); + + assert!(list.is_host_secure("mozilla.org") == false) +} + +#[test] +fn test_push_entry_to_hsts_list_should_not_add_subdomains_whose_superdomain_is_already_matched() { + let mut list = HSTSList { + entries: vec!(HSTSEntry::new("mozilla.org".to_string(), Subdomains::Included, None).unwrap()) + }; + + list.push(HSTSEntry::new("servo.mozilla.org".to_string(), Subdomains::NotIncluded, None).unwrap()); + + assert!(list.entries.len() == 1) +} + +#[test] +fn test_push_entry_to_hsts_list_should_update_existing_domain_entrys_include_subdomains() { + let mut list = HSTSList { + entries: vec!(HSTSEntry::new("mozilla.org".to_string(), Subdomains::Included, None).unwrap()) + }; + + assert!(list.is_host_secure("servo.mozilla.org")); + + list.push(HSTSEntry::new("mozilla.org".to_string(), Subdomains::NotIncluded, None).unwrap()); + + assert!(!list.is_host_secure("servo.mozilla.org")) +} + +#[test] +fn test_push_entry_to_hsts_list_should_not_create_duplicate_entry() { + let mut list = HSTSList { + entries: vec!(HSTSEntry::new("mozilla.org".to_string(), Subdomains::NotIncluded, None).unwrap()) + }; + + list.push(HSTSEntry::new("mozilla.org".to_string(), Subdomains::NotIncluded, None).unwrap()); + + assert!(list.entries.len() == 1) +} + +#[test] +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: Vec::new() + }; + + assert!(!list.is_host_secure("mozilla.org")); + assert!(!list.is_host_secure("bugzilla.org")); + + list.push(HSTSEntry::new("mozilla.org".to_string(), Subdomains::Included, None).unwrap()); + list.push(HSTSEntry::new("bugzilla.org".to_string(), Subdomains::Included, None).unwrap()); + + assert!(list.is_host_secure("mozilla.org")); + assert!(list.is_host_secure("bugzilla.org")); +} + +#[test] +fn test_parse_hsts_preload_should_return_none_when_json_invalid() { + let mock_preload_content = "derp"; + assert!(HSTSList::new_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_key() { + let mock_preload_content = "{\"nothing\": \"to see here\"}"; + assert!(HSTSList::new_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}\ + ]\ + }"; + let hsts_list = HSTSList::new_from_preload(mock_preload_content); + let entries = hsts_list.unwrap().entries; + + assert_eq!(entries[0].host, "mozilla.org"); + assert!(!entries[0].include_subdomains); +} + +#[test] +fn test_hsts_list_with_no_entries_does_not_is_host_secure() { + let hsts_list = HSTSList { + entries: Vec::new() + }; + + assert!(!hsts_list.is_host_secure("mozilla.org")); +} + +#[test] +fn test_hsts_list_with_exact_domain_entry_is_is_host_secure() { + let hsts_list = HSTSList { + entries: vec![HSTSEntry::new("mozilla.org".to_string(), Subdomains::NotIncluded, None).unwrap()] + }; + + assert!(hsts_list.is_host_secure("mozilla.org")); +} + +#[test] +fn test_hsts_list_with_subdomain_when_include_subdomains_is_true_is_is_host_secure() { + let hsts_list = HSTSList { + entries: vec![HSTSEntry::new("mozilla.org".to_string(), Subdomains::Included, None).unwrap()] + }; + + assert!(hsts_list.is_host_secure("servo.mozilla.org")); +} + +#[test] +fn test_hsts_list_with_subdomain_when_include_subdomains_is_false_is_not_is_host_secure() { + let hsts_list = HSTSList { + entries: vec![HSTSEntry::new("mozilla.org".to_string(), Subdomains::NotIncluded, None).unwrap()] + }; + + assert!(!hsts_list.is_host_secure("servo.mozilla.org")); +} + +#[test] +fn test_hsts_list_with_subdomain_when_host_is_not_a_subdomain_is_not_is_host_secure() { + let hsts_list = HSTSList { + entries: vec![HSTSEntry::new("mozilla.org".to_string(), Subdomains::Included, None).unwrap()] + }; + + assert!(!hsts_list.is_host_secure("servo-mozilla.org")); +} + +#[test] +fn test_hsts_list_with_subdomain_when_host_is_exact_match_is_is_host_secure() { + let hsts_list = HSTSList { + entries: vec![HSTSEntry::new("mozilla.org".to_string(), Subdomains::Included, None).unwrap()] + }; + + assert!(hsts_list.is_host_secure("mozilla.org")); +} + +#[test] +fn test_hsts_list_with_expired_entry_is_not_is_host_secure() { + let hsts_list = HSTSList { + entries: vec![HSTSEntry { + host: "mozilla.org".to_string(), + include_subdomains: false, + max_age: Some(20), + timestamp: Some(time::get_time().sec as u64 - 100u64) + }] + }; + + assert!(!hsts_list.is_host_secure("mozilla.org")); +} + +#[test] +fn test_secure_load_data_does_not_change_explicit_port() { + let load_data = LoadData::new(Url::parse("http://mozilla.org:8080/").unwrap(), None); + let secure = secure_load_data(&load_data); + + assert!(secure.url.port().unwrap() == 8080u16); +} + +#[test] +fn test_secure_load_data_does_not_affect_non_http_schemas() { + let load_data = LoadData::new(Url::parse("file://mozilla.org").unwrap(), None); + let secure = secure_load_data(&load_data); + + assert_eq!(&secure.url.scheme, "file"); +} + +#[test] +fn test_secure_load_data_forces_an_http_host_in_list_to_https() { + let load_data = LoadData::new(Url::parse("http://mozilla.org").unwrap(), None); + let secure = secure_load_data(&load_data); + + assert_eq!(&secure.url.scheme, "https"); +} + diff --git a/tests/unit/net/lib.rs b/tests/unit/net/lib.rs index 335cbd721a6..d9d0f05a79f 100644 --- a/tests/unit/net/lib.rs +++ b/tests/unit/net/lib.rs @@ -14,3 +14,4 @@ extern crate time; #[cfg(test)] mod data_loader; #[cfg(test)] mod mime_classifier; #[cfg(test)] mod resource_task; +#[cfg(test)] mod hsts; diff --git a/tests/unit/net/resource_task.rs b/tests/unit/net/resource_task.rs index e1c5eac78e3..989558c2f21 100644 --- a/tests/unit/net/resource_task.rs +++ b/tests/unit/net/resource_task.rs @@ -5,19 +5,12 @@ use net::resource_task::new_resource_task; use net::resource_task::parse_hostsfile; use net::resource_task::replace_hosts; -use net::resource_task::HSTSList; -use net::resource_task::HSTSEntry; -use net::resource_task::Subdomains; -use net::resource_task::secure_load_data; -use net::resource_task::ResourceManager; use net_traits::{ControlMsg, LoadData, LoadConsumer}; use net_traits::ProgressMsg; use std::borrow::ToOwned; use std::collections::HashMap; use std::sync::mpsc::channel; use url::Url; -use time; - #[test] fn test_exit() { @@ -25,265 +18,6 @@ fn test_exit() { resource_task.send(ControlMsg::Exit).unwrap(); } -#[test] -fn test_add_hsts_entry_to_resource_manager_adds_an_hsts_entry() { - let list = HSTSList { - entries: Vec::new() - }; - - let (tx, _) = channel(); - let mut manager = ResourceManager::new(None, tx, Some(list), None); - - let entry = HSTSEntry::new( - "mozilla.org".to_string(), Subdomains::NotIncluded, None - ); - - assert!(!manager.is_host_sts("mozilla.org")); - - manager.add_hsts_entry(entry.unwrap()); - - assert!(manager.is_host_sts("mozilla.org")) -} - -#[test] -fn test_hsts_entry_is_not_expired_when_it_has_no_timestamp() { - let entry = HSTSEntry { - host: "mozilla.org".to_string(), - include_subdomains: false, - max_age: Some(20), - timestamp: None - }; - - assert!(!entry.is_expired()); -} - -#[test] -fn test_hsts_entry_is_not_expired_when_it_has_no_max_age() { - let entry = HSTSEntry { - host: "mozilla.org".to_string(), - include_subdomains: false, - max_age: None, - timestamp: Some(time::get_time().sec as u64) - }; - - assert!(!entry.is_expired()); -} - -#[test] -fn test_hsts_entry_is_expired_when_it_has_reached_its_max_age() { - let entry = HSTSEntry { - host: "mozilla.org".to_string(), - include_subdomains: false, - max_age: Some(10), - timestamp: Some(time::get_time().sec as u64 - 20u64) - }; - - assert!(entry.is_expired()); -} - -#[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_string(), Subdomains::NotIncluded, None - ); - - assert!(entry.is_none(), "able to create HSTSEntry with IPv6 host"); -} - -#[test] -fn test_hsts_entry_cant_be_created_with_ipv4_address_as_host() { - let entry = HSTSEntry::new( - "4.4.4.4".to_string(), Subdomains::NotIncluded, None - ); - - assert!(entry.is_none(), "able to create HSTSEntry with IPv4 host"); -} - -#[test] -fn test_push_entry_with_0_max_age_evicts_entry_from_list() { - let mut list = HSTSList { - entries: vec!(HSTSEntry::new("mozilla.org".to_string(), Subdomains::NotIncluded, Some(500000u64)).unwrap()) - }; - - list.push(HSTSEntry::new("mozilla.org".to_string(), Subdomains::NotIncluded, Some(0)).unwrap()); - - assert!(list.is_host_secure("mozilla.org") == false) -} - -#[test] -fn test_push_entry_to_hsts_list_should_not_add_subdomains_whose_superdomain_is_already_matched() { - let mut list = HSTSList { - entries: vec!(HSTSEntry::new("mozilla.org".to_string(), Subdomains::Included, None).unwrap()) - }; - - list.push(HSTSEntry::new("servo.mozilla.org".to_string(), Subdomains::NotIncluded, None).unwrap()); - - assert!(list.entries.len() == 1) -} - -#[test] -fn test_push_entry_to_hsts_list_should_update_existing_domain_entrys_include_subdomains() { - let mut list = HSTSList { - entries: vec!(HSTSEntry::new("mozilla.org".to_string(), Subdomains::Included, None).unwrap()) - }; - - assert!(list.is_host_secure("servo.mozilla.org")); - - list.push(HSTSEntry::new("mozilla.org".to_string(), Subdomains::NotIncluded, None).unwrap()); - - assert!(!list.is_host_secure("servo.mozilla.org")) -} - -#[test] -fn test_push_entry_to_hsts_list_should_not_create_duplicate_entry() { - let mut list = HSTSList { - entries: vec!(HSTSEntry::new("mozilla.org".to_string(), Subdomains::NotIncluded, None).unwrap()) - }; - - list.push(HSTSEntry::new("mozilla.org".to_string(), Subdomains::NotIncluded, None).unwrap()); - - assert!(list.entries.len() == 1) -} - -#[test] -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: Vec::new() - }; - - assert!(!list.is_host_secure("mozilla.org")); - assert!(!list.is_host_secure("bugzilla.org")); - - list.push(HSTSEntry::new("mozilla.org".to_string(), Subdomains::Included, None).unwrap()); - list.push(HSTSEntry::new("bugzilla.org".to_string(), Subdomains::Included, None).unwrap()); - - assert!(list.is_host_secure("mozilla.org")); - assert!(list.is_host_secure("bugzilla.org")); -} - -#[test] -fn test_parse_hsts_preload_should_return_none_when_json_invalid() { - let mock_preload_content = "derp"; - assert!(HSTSList::new_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_key() { - let mock_preload_content = "{\"nothing\": \"to see here\"}"; - assert!(HSTSList::new_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}\ - ]\ - }"; - let hsts_list = HSTSList::new_from_preload(mock_preload_content); - let entries = hsts_list.unwrap().entries; - - assert_eq!(entries[0].host, "mozilla.org"); - assert!(!entries[0].include_subdomains); -} - -#[test] -fn test_hsts_list_with_no_entries_does_not_is_host_secure() { - let hsts_list = HSTSList { - entries: Vec::new() - }; - - assert!(!hsts_list.is_host_secure("mozilla.org")); -} - -#[test] -fn test_hsts_list_with_exact_domain_entry_is_is_host_secure() { - let hsts_list = HSTSList { - entries: vec![HSTSEntry::new("mozilla.org".to_string(), Subdomains::NotIncluded, None).unwrap()] - }; - - assert!(hsts_list.is_host_secure("mozilla.org")); -} - -#[test] -fn test_hsts_list_with_subdomain_when_include_subdomains_is_true_is_is_host_secure() { - let hsts_list = HSTSList { - entries: vec![HSTSEntry::new("mozilla.org".to_string(), Subdomains::Included, None).unwrap()] - }; - - assert!(hsts_list.is_host_secure("servo.mozilla.org")); -} - -#[test] -fn test_hsts_list_with_subdomain_when_include_subdomains_is_false_is_not_is_host_secure() { - let hsts_list = HSTSList { - entries: vec![HSTSEntry::new("mozilla.org".to_string(), Subdomains::NotIncluded, None).unwrap()] - }; - - assert!(!hsts_list.is_host_secure("servo.mozilla.org")); -} - -#[test] -fn test_hsts_list_with_subdomain_when_host_is_not_a_subdomain_is_not_is_host_secure() { - let hsts_list = HSTSList { - entries: vec![HSTSEntry::new("mozilla.org".to_string(), Subdomains::Included, None).unwrap()] - }; - - assert!(!hsts_list.is_host_secure("servo-mozilla.org")); -} - -#[test] -fn test_hsts_list_with_subdomain_when_host_is_exact_match_is_is_host_secure() { - let hsts_list = HSTSList { - entries: vec![HSTSEntry::new("mozilla.org".to_string(), Subdomains::Included, None).unwrap()] - }; - - assert!(hsts_list.is_host_secure("mozilla.org")); -} - -#[test] -fn test_hsts_list_with_expired_entry_is_not_is_host_secure() { - let hsts_list = HSTSList { - entries: vec![HSTSEntry { - host: "mozilla.org".to_string(), - include_subdomains: false, - max_age: Some(20), - timestamp: Some(time::get_time().sec as u64 - 100u64) - }] - }; - - assert!(!hsts_list.is_host_secure("mozilla.org")); -} - -#[test] -fn test_secure_load_data_does_not_change_explicit_port() { - let load_data = LoadData::new(Url::parse("http://mozilla.org:8080/").unwrap(), None); - let secure = secure_load_data(&load_data); - - assert!(secure.url.port().unwrap() == 8080u16); -} - -#[test] -fn test_secure_load_data_does_not_affect_non_http_schemas() { - let load_data = LoadData::new(Url::parse("file://mozilla.org").unwrap(), None); - let secure = secure_load_data(&load_data); - - assert_eq!(&secure.url.scheme, "file"); -} - -#[test] -fn test_secure_load_data_forces_an_http_host_in_list_to_https() { - let load_data = LoadData::new(Url::parse("http://mozilla.org").unwrap(), None); - let secure = secure_load_data(&load_data); - - assert_eq!(&secure.url.scheme, "https"); -} - #[test] fn test_bad_scheme() { let resource_task = new_resource_task(None, None);