mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
Moves HSTS code to it's own module
This commit is contained in:
parent
02bd5cdc1b
commit
826f56bdf3
6 changed files with 426 additions and 397 deletions
145
components/net/hsts.rs
Normal file
145
components/net/hsts.rs
Normal file
|
@ -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<u64>,
|
||||
pub timestamp: Option<u64>
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Copy, Clone)]
|
||||
pub enum Subdomains {
|
||||
Included,
|
||||
NotIncluded
|
||||
}
|
||||
|
||||
impl HSTSEntry {
|
||||
pub fn new(host: String, subdomains: Subdomains, max_age: Option<u64>) -> Option<HSTSEntry> {
|
||||
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<HSTSEntry>
|
||||
}
|
||||
|
||||
impl HSTSList {
|
||||
pub fn new_from_preload(preload_content: &str) -> Option<HSTSList> {
|
||||
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<HSTSList> {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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<String, String>> = 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<HSTSList> {
|
||||
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<String>,
|
||||
devtools_chan: Option<Sender<DevtoolsControlMsg>>) -> ResourceTask {
|
||||
|
@ -192,124 +185,6 @@ pub fn new_resource_task(user_agent: Option<String>,
|
|||
setup_chan
|
||||
}
|
||||
|
||||
#[derive(RustcDecodable, RustcEncodable, Clone)]
|
||||
pub struct HSTSEntry {
|
||||
pub host: String,
|
||||
pub include_subdomains: bool,
|
||||
pub max_age: Option<u64>,
|
||||
pub timestamp: Option<u64>
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Copy, Clone)]
|
||||
pub enum Subdomains {
|
||||
Included,
|
||||
NotIncluded
|
||||
}
|
||||
|
||||
impl HSTSEntry {
|
||||
pub fn new(host: String, subdomains: Subdomains, max_age: Option<u64>) -> Option<HSTSEntry> {
|
||||
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<HSTSEntry>
|
||||
}
|
||||
|
||||
impl HSTSList {
|
||||
pub fn new_from_preload(preload_content: &str) -> Option<HSTSList> {
|
||||
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<HashMap<String, String>> {
|
||||
let mut host_table = HashMap::new();
|
||||
let lines: Vec<&str> = hostsfile_content.split('\n').collect();
|
||||
|
|
273
tests/unit/net/hsts.rs
Normal file
273
tests/unit/net/hsts.rs
Normal file
|
@ -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");
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue