servo: Merge #10666 - Refactor, cleanup HSTS related code (from frewsxcv:hsts-cleanup); r=SimonSapin

Source-Repo: https://github.com/servo/servo
Source-Revision: 317629d86e323fca8603ce278e76e1be7758dd3a
This commit is contained in:
Corey Farwell 2016-04-17 23:21:13 +05:01
parent e70b7e0a13
commit 60a86d0dbb
5 changed files with 81 additions and 83 deletions

View File

@ -11,19 +11,19 @@ use url::Url;
use util::resource_files::read_resource_file;
#[derive(RustcDecodable, RustcEncodable, Clone)]
pub struct HSTSEntry {
pub struct HstsEntry {
pub host: String,
pub include_subdomains: bool,
pub max_age: Option<u64>,
pub timestamp: Option<u64>
}
impl HSTSEntry {
pub fn new(host: String, subdomains: IncludeSubdomains, max_age: Option<u64>) -> Option<HSTSEntry> {
impl 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 {
Some(HSTSEntry {
Some(HstsEntry {
host: host,
include_subdomains: (subdomains == IncludeSubdomains::Included),
max_age: max_age,
@ -52,19 +52,29 @@ impl HSTSEntry {
}
#[derive(RustcDecodable, RustcEncodable, Clone)]
pub struct HSTSList {
pub entries: Vec<HSTSEntry>
pub struct HstsList {
pub entries: Vec<HstsEntry>
}
impl HSTSList {
pub fn new() -> HSTSList {
HSTSList {
impl HstsList {
pub fn new() -> HstsList {
HstsList {
entries: vec![]
}
}
pub fn new_from_preload(preload_content: &str) -> Option<HSTSList> {
decode(preload_content).ok()
/// Create an `HstsList` from the bytes of a JSON preload file.
pub fn from_preload(preload_content: &[u8]) -> Option<HstsList> {
from_utf8(&preload_content)
.ok()
.and_then(|c| decode(c).ok())
}
pub fn from_servo_preload() -> HstsList {
let file_bytes = read_resource_file("hsts_preload.json")
.expect("Could not find Servo HSTS preload file");
HstsList::from_preload(&file_bytes)
.expect("Servo HSTS preload file is invalid")
}
pub fn is_host_secure(&self, host: &str) -> bool {
@ -94,7 +104,7 @@ impl HSTSList {
})
}
pub fn push(&mut self, entry: HSTSEntry) {
pub fn push(&mut self, entry: HstsEntry) {
let have_domain = self.has_domain(&entry.host);
let have_subdomain = self.has_subdomain(&entry.host);
@ -111,14 +121,6 @@ impl HSTSList {
}
}
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_url(url: &Url) -> Url {
if &*url.scheme == "http" {
let mut secure_url = url.clone();

View File

@ -10,7 +10,7 @@ use devtools_traits::{ChromeToDevtoolsControlMsg, DevtoolsControlMsg, HttpReques
use devtools_traits::{HttpResponse as DevtoolsHttpResponse, NetworkEvent};
use file_loader;
use flate2::read::{DeflateDecoder, GzDecoder};
use hsts::{HSTSEntry, HSTSList, secure_url};
use hsts::{HstsEntry, HstsList, secure_url};
use hyper::Error as HttpError;
use hyper::client::{Pool, Request, Response};
use hyper::header::{Accept, AcceptEncoding, ContentLength, ContentType, Host};
@ -125,7 +125,7 @@ fn inner_url(url: &Url) -> Url {
}
pub struct HttpState {
pub hsts_list: Arc<RwLock<HSTSList>>,
pub hsts_list: Arc<RwLock<HstsList>>,
pub cookie_jar: Arc<RwLock<CookieStorage>>,
pub auth_cache: Arc<RwLock<HashMap<Url, AuthCacheEntry>>>,
}
@ -133,7 +133,7 @@ pub struct HttpState {
impl HttpState {
pub fn new() -> HttpState {
HttpState {
hsts_list: Arc::new(RwLock::new(HSTSList::new())),
hsts_list: Arc::new(RwLock::new(HstsList::new())),
cookie_jar: Arc::new(RwLock::new(CookieStorage::new())),
auth_cache: Arc::new(RwLock::new(HashMap::new())),
}
@ -401,7 +401,7 @@ fn set_cookies_from_response(url: Url, response: &HttpResponse, cookie_jar: &Arc
}
}
fn update_sts_list_from_response(url: &Url, response: &HttpResponse, hsts_list: &Arc<RwLock<HSTSList>>) {
fn update_sts_list_from_response(url: &Url, response: &HttpResponse, hsts_list: &Arc<RwLock<HstsList>>) {
if url.scheme != "https" {
return;
}
@ -415,7 +415,7 @@ fn update_sts_list_from_response(url: &Url, response: &HttpResponse, hsts_list:
IncludeSubdomains::NotIncluded
};
if let Some(entry) = HSTSEntry::new(host.to_owned(), include_subdomains, Some(header.max_age)) {
if let Some(entry) = HstsEntry::new(host.to_owned(), include_subdomains, Some(header.max_age)) {
info!("adding host {} to the strict transport security list", host);
info!("- max-age {}", header.max_age);
if header.include_subdomains {
@ -518,7 +518,7 @@ fn send_response_to_devtools(devtools_chan: Option<Sender<DevtoolsControlMsg>>,
}
}
fn request_must_be_secured(url: &Url, hsts_list: &Arc<RwLock<HSTSList>>) -> bool {
fn request_must_be_secured(url: &Url, hsts_list: &Arc<RwLock<HstsList>>) -> bool {
match url.domain() {
Some(domain) => hsts_list.read().unwrap().is_host_secure(domain),
None => false
@ -597,7 +597,7 @@ fn auth_from_url(doc_url: &Url) -> Option<Authorization<Basic>> {
pub fn process_response_headers(response: &HttpResponse,
url: &Url,
cookie_jar: &Arc<RwLock<CookieStorage>>,
hsts_list: &Arc<RwLock<HSTSList>>,
hsts_list: &Arc<RwLock<HstsList>>,
load_data: &LoadData) {
info!("got HTTP response {}, headers:", response.status());
if log_enabled!(log::LogLevel::Info) {

View File

@ -10,7 +10,7 @@ use cookie_storage::CookieStorage;
use data_loader;
use devtools_traits::{DevtoolsControlMsg};
use file_loader;
use hsts::{HSTSList, preload_hsts_domains};
use hsts::HstsList;
use http_loader::{self, Connector, create_http_connector, HttpState};
use hyper::client::pool::Pool;
use hyper::header::{ContentType, Header, SetCookie};
@ -148,11 +148,7 @@ fn start_sending_opt(start_chan: LoadConsumer, metadata: Metadata) -> Result<Pro
/// Create a ResourceThread
pub fn new_resource_thread(user_agent: String,
devtools_chan: Option<Sender<DevtoolsControlMsg>>) -> ResourceThread {
let hsts_preload = match preload_hsts_domains() {
Some(list) => list,
None => HSTSList::new()
};
let hsts_preload = HstsList::from_servo_preload();
let (setup_chan, setup_port) = ipc::channel().unwrap();
let setup_chan_clone = setup_chan.clone();
spawn_named("ResourceManager".to_owned(), move || {
@ -280,7 +276,7 @@ pub struct ResourceManager {
auth_cache: Arc<RwLock<HashMap<Url, AuthCacheEntry>>>,
mime_classifier: Arc<MIMEClassifier>,
devtools_chan: Option<Sender<DevtoolsControlMsg>>,
hsts_list: Arc<RwLock<HSTSList>>,
hsts_list: Arc<RwLock<HstsList>>,
connector: Arc<Pool<Connector>>,
cancel_load_map: HashMap<ResourceId, Sender<()>>,
next_resource_id: ResourceId,
@ -288,7 +284,7 @@ pub struct ResourceManager {
impl ResourceManager {
pub fn new(user_agent: String,
hsts_list: HSTSList,
hsts_list: HstsList,
devtools_channel: Option<Sender<DevtoolsControlMsg>>) -> ResourceManager {
ResourceManager {
user_agent: user_agent,

View File

@ -2,15 +2,15 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use net::hsts::{HSTSList, HSTSEntry};
use net::hsts::{secure_url, preload_hsts_domains};
use net::hsts::secure_url;
use net::hsts::{HstsList, HstsEntry};
use net_traits::IncludeSubdomains;
use time;
use url::Url;
#[test]
fn test_hsts_entry_is_not_expired_when_it_has_no_timestamp() {
let entry = HSTSEntry {
let entry = HstsEntry {
host: "mozilla.org".to_owned(),
include_subdomains: false,
max_age: Some(20),
@ -22,7 +22,7 @@ fn test_hsts_entry_is_not_expired_when_it_has_no_timestamp() {
#[test]
fn test_hsts_entry_is_not_expired_when_it_has_no_max_age() {
let entry = HSTSEntry {
let entry = HstsEntry {
host: "mozilla.org".to_owned(),
include_subdomains: false,
max_age: None,
@ -34,7 +34,7 @@ fn test_hsts_entry_is_not_expired_when_it_has_no_max_age() {
#[test]
fn test_hsts_entry_is_expired_when_it_has_reached_its_max_age() {
let entry = HSTSEntry {
let entry = HstsEntry {
host: "mozilla.org".to_owned(),
include_subdomains: false,
max_age: Some(10),
@ -46,30 +46,30 @@ 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(
let entry = HstsEntry::new(
"2001:0db8:0000:0000:0000:ff00:0042:8329".to_owned(), IncludeSubdomains::NotIncluded, None
);
assert!(entry.is_none(), "able to create HSTSEntry with IPv6 host");
assert!(entry.is_none(), "able to create HstsEntry with IPv6 host");
}
#[test]
fn test_hsts_entry_cant_be_created_with_ipv4_address_as_host() {
let entry = HSTSEntry::new(
let entry = HstsEntry::new(
"4.4.4.4".to_owned(), IncludeSubdomains::NotIncluded, None
);
assert!(entry.is_none(), "able to create HSTSEntry with IPv4 host");
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_owned(),
let mut list = HstsList {
entries: vec!(HstsEntry::new("mozilla.org".to_owned(),
IncludeSubdomains::NotIncluded, Some(500000u64)).unwrap())
};
list.push(HSTSEntry::new("mozilla.org".to_owned(),
list.push(HstsEntry::new("mozilla.org".to_owned(),
IncludeSubdomains::NotIncluded, Some(0)).unwrap());
assert!(list.is_host_secure("mozilla.org") == false)
@ -77,12 +77,12 @@ 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 list = HSTSList {
entries: vec!(HSTSEntry::new("mozilla.org".to_owned(),
let mut list = HstsList {
entries: vec!(HstsEntry::new("mozilla.org".to_owned(),
IncludeSubdomains::Included, None).unwrap())
};
list.push(HSTSEntry::new("servo.mozilla.org".to_owned(),
list.push(HstsEntry::new("servo.mozilla.org".to_owned(),
IncludeSubdomains::NotIncluded, None).unwrap());
assert!(list.entries.len() == 1)
@ -90,14 +90,14 @@ 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 list = HSTSList {
entries: vec!(HSTSEntry::new("mozilla.org".to_owned(),
let mut list = HstsList {
entries: vec!(HstsEntry::new("mozilla.org".to_owned(),
IncludeSubdomains::Included, None).unwrap())
};
assert!(list.is_host_secure("servo.mozilla.org"));
list.push(HSTSEntry::new("mozilla.org".to_owned(),
list.push(HstsEntry::new("mozilla.org".to_owned(),
IncludeSubdomains::NotIncluded, None).unwrap());
assert!(!list.is_host_secure("servo.mozilla.org"))
@ -105,12 +105,12 @@ 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 list = HSTSList {
entries: vec!(HSTSEntry::new("mozilla.org".to_owned(),
let mut list = HstsList {
entries: vec!(HstsEntry::new("mozilla.org".to_owned(),
IncludeSubdomains::NotIncluded, None).unwrap())
};
list.push(HSTSEntry::new("mozilla.org".to_owned(),
list.push(HstsEntry::new("mozilla.org".to_owned(),
IncludeSubdomains::NotIncluded, None).unwrap());
assert!(list.entries.len() == 1)
@ -118,16 +118,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 {
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_owned(),
list.push(HstsEntry::new("mozilla.org".to_owned(),
IncludeSubdomains::Included, None).unwrap());
list.push(HSTSEntry::new("bugzilla.org".to_owned(),
list.push(HstsEntry::new("bugzilla.org".to_owned(),
IncludeSubdomains::Included, None).unwrap());
assert!(list.is_host_secure("mozilla.org"));
@ -136,13 +136,13 @@ 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 {
let mut list = HstsList {
entries: Vec::new()
};
assert!(!list.is_host_secure("mozilla.org"));
list.push(HSTSEntry::new("mozilla.org".to_owned(),
list.push(HstsEntry::new("mozilla.org".to_owned(),
IncludeSubdomains::Included, None).unwrap());
assert!(list.is_host_secure("mozilla.org"));
@ -150,25 +150,25 @@ 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::new_from_preload(mock_preload_content).is_none(), "invalid preload list should not have parsed")
let mock_preload_content = b"derp";
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_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")
let mock_preload_content = b"{\"nothing\": \"to see here\"}";
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 = "{\
let mock_preload_content = b"{\
\"entries\": [\
{\"host\": \"mozilla.org\",\
\"include_subdomains\": false}\
]\
}";
let hsts_list = HSTSList::new_from_preload(mock_preload_content);
let hsts_list = HstsList::from_preload(mock_preload_content);
let entries = hsts_list.unwrap().entries;
assert_eq!(entries[0].host, "mozilla.org");
@ -177,7 +177,7 @@ fn test_parse_hsts_preload_should_decode_host_and_includes_subdomains() {
#[test]
fn test_hsts_list_with_no_entries_does_not_is_host_secure() {
let hsts_list = HSTSList {
let hsts_list = HstsList {
entries: Vec::new()
};
@ -186,8 +186,8 @@ fn test_hsts_list_with_no_entries_does_not_is_host_secure() {
#[test]
fn test_hsts_list_with_exact_domain_entry_is_is_host_secure() {
let hsts_list = HSTSList {
entries: vec![HSTSEntry::new("mozilla.org".to_owned(),
let hsts_list = HstsList {
entries: vec![HstsEntry::new("mozilla.org".to_owned(),
IncludeSubdomains::NotIncluded, None).unwrap()]
};
@ -196,8 +196,8 @@ 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 hsts_list = HSTSList {
entries: vec![HSTSEntry::new("mozilla.org".to_owned(),
let hsts_list = HstsList {
entries: vec![HstsEntry::new("mozilla.org".to_owned(),
IncludeSubdomains::Included, None).unwrap()]
};
@ -206,8 +206,8 @@ 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 hsts_list = HSTSList {
entries: vec![HSTSEntry::new("mozilla.org".to_owned(),
let hsts_list = HstsList {
entries: vec![HstsEntry::new("mozilla.org".to_owned(),
IncludeSubdomains::NotIncluded, None).unwrap()]
};
@ -216,8 +216,8 @@ 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 hsts_list = HSTSList {
entries: vec![HSTSEntry::new("mozilla.org".to_owned(),
let hsts_list = HstsList {
entries: vec![HstsEntry::new("mozilla.org".to_owned(),
IncludeSubdomains::Included, None).unwrap()]
};
@ -226,8 +226,8 @@ 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 hsts_list = HSTSList {
entries: vec![HSTSEntry::new("mozilla.org".to_owned(),
let hsts_list = HstsList {
entries: vec![HstsEntry::new("mozilla.org".to_owned(),
IncludeSubdomains::Included, None).unwrap()]
};
@ -236,8 +236,8 @@ 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 hsts_list = HSTSList {
entries: vec![HSTSEntry {
let hsts_list = HstsList {
entries: vec![HstsEntry {
host: "mozilla.org".to_owned(),
include_subdomains: false,
max_age: Some(20),
@ -250,7 +250,7 @@ fn test_hsts_list_with_expired_entry_is_not_is_host_secure() {
#[test]
fn test_preload_hsts_domains_well_formed() {
let hsts_list = preload_hsts_domains().unwrap();
let hsts_list = HstsList::from_servo_preload();
assert!(!hsts_list.entries.is_empty());
}

View File

@ -19,7 +19,7 @@ use hyper::status::StatusCode;
use msg::constellation_msg::PipelineId;
use net::cookie::Cookie;
use net::cookie_storage::CookieStorage;
use net::hsts::HSTSEntry;
use net::hsts::HstsEntry;
use net::http_loader::{load, LoadError, HttpRequestFactory, HttpRequest, HttpResponse, UIProvider, HttpState};
use net::resource_thread::{AuthCacheEntry, CancellationListener};
use net_traits::{LoadData, CookieSource, LoadContext, IncludeSubdomains};
@ -767,7 +767,7 @@ fn test_load_sends_secure_cookie_if_http_changed_to_https_due_to_entry_in_hsts_s
let http_state = HttpState::new();
{
let mut hsts_list = http_state.hsts_list.write().unwrap();
let entry = HSTSEntry::new(
let entry = HstsEntry::new(
"mozilla.com".to_owned(), IncludeSubdomains::Included, Some(1000000)
).unwrap();
hsts_list.push(entry);