aboutsummaryrefslogtreecommitdiffstats
path: root/components/net/hsts.rs
diff options
context:
space:
mode:
Diffstat (limited to 'components/net/hsts.rs')
-rw-r--r--components/net/hsts.rs137
1 files changed, 137 insertions, 0 deletions
diff --git a/components/net/hsts.rs b/components/net/hsts.rs
new file mode 100644
index 00000000000..db9dcb4b9d3
--- /dev/null
+++ b/components/net/hsts.rs
@@ -0,0 +1,137 @@
+/* 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 rustc_serialize::json::{decode};
+use time;
+use url::Url;
+use net_traits::IncludeSubdomains;
+use resource_task::{IPV4_REGEX, IPV6_REGEX};
+
+use std::str::{from_utf8};
+
+use util::resource_files::read_resource_file;
+
+#[derive(RustcDecodable, RustcEncodable, Clone)]
+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> {
+ if IPV4_REGEX.is_match(&host) || IPV6_REGEX.is_match(&host) {
+ None
+ } else {
+ Some(HSTSEntry {
+ host: host,
+ include_subdomains: (subdomains == IncludeSubdomains::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, Clone)]
+pub struct HSTSList {
+ pub entries: Vec<HSTSEntry>
+}
+
+impl HSTSList {
+ pub fn new() -> HSTSList {
+ HSTSList {
+ entries: vec![]
+ }
+ }
+
+ 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_url(url: &Url) -> Url {
+ if &*url.scheme == "http" {
+ let mut secure_url = url.clone();
+ secure_url.scheme = "https".to_string();
+ secure_url.relative_scheme_data_mut()
+ .map(|scheme_data| {
+ scheme_data.default_port = Some(443);
+ });
+ secure_url
+ } else {
+ url.clone()
+ }
+}
+