use std::fmt;
use std::str::{self, FromStr};
use unicase::UniCase;
use header::{Header, HeaderFormat, parsing};
#[derive(Clone, PartialEq, Debug)]
pub struct StrictTransportSecurity {
pub include_subdomains: bool,
pub max_age: u64
}
impl StrictTransportSecurity {
pub fn including_subdomains(max_age: u64) -> StrictTransportSecurity {
StrictTransportSecurity {
max_age: max_age,
include_subdomains: true
}
}
pub fn excluding_subdomains(max_age: u64) -> StrictTransportSecurity {
StrictTransportSecurity {
max_age: max_age,
include_subdomains: false
}
}
}
enum Directive {
MaxAge(u64),
IncludeSubdomains,
Unknown
}
impl FromStr for StrictTransportSecurity {
type Err = ::Error;
fn from_str(s: &str) -> ::Result<StrictTransportSecurity> {
s.split(';')
.map(str::trim)
.map(|sub| if UniCase(sub) == UniCase("includeSubdomains") {
Ok(Directive::IncludeSubdomains)
} else {
let mut sub = sub.splitn(2, '=');
match (sub.next(), sub.next()) {
(Some(left), Some(right))
if UniCase(left.trim()) == UniCase("max-age") => {
right
.trim()
.trim_matches('"')
.parse()
.map(Directive::MaxAge)
},
_ => Ok(Directive::Unknown)
}
})
.fold(Ok((None, None)), |res, dir| match (res, dir) {
(Ok((None, sub)), Ok(Directive::MaxAge(age))) => Ok((Some(age), sub)),
(Ok((age, None)), Ok(Directive::IncludeSubdomains)) => Ok((age, Some(()))),
(Ok((Some(_), _)), Ok(Directive::MaxAge(_))) => Err(::Error::Header),
(Ok((_, Some(_))), Ok(Directive::IncludeSubdomains)) => Err(::Error::Header),
(_, Err(_)) => Err(::Error::Header),
(res, _) => res
})
.and_then(|res| match res {
(Some(age), sub) => Ok(StrictTransportSecurity {
max_age: age,
include_subdomains: sub.is_some()
}),
_ => Err(::Error::Header)
})
}
}
impl Header for StrictTransportSecurity {
fn header_name() -> &'static str {
"Strict-Transport-Security"
}
fn parse_header(raw: &[Vec<u8>]) -> ::Result<StrictTransportSecurity> {
parsing::from_one_raw_str(raw)
}
}
impl HeaderFormat for StrictTransportSecurity {
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.include_subdomains {
write!(f, "max-age={}; includeSubdomains", self.max_age)
} else {
write!(f, "max-age={}", self.max_age)
}
}
}
#[cfg(test)]
mod tests {
use super::StrictTransportSecurity;
use header::Header;
#[test]
fn test_parse_max_age() {
let h = Header::parse_header(&[b"max-age=31536000".to_vec()][..]);
assert_eq!(h.ok(), Some(StrictTransportSecurity { include_subdomains: false, max_age: 31536000u64 }));
}
#[test]
fn test_parse_max_age_no_value() {
let h: ::Result<StrictTransportSecurity> = Header::parse_header(&[b"max-age".to_vec()][..]);
assert!(h.is_err());
}
#[test]
fn test_parse_quoted_max_age() {
let h = Header::parse_header(&[b"max-age=\"31536000\"".to_vec()][..]);
assert_eq!(h.ok(), Some(StrictTransportSecurity { include_subdomains: false, max_age: 31536000u64 }));
}
#[test]
fn test_parse_spaces_max_age() {
let h = Header::parse_header(&[b"max-age = 31536000".to_vec()][..]);
assert_eq!(h.ok(), Some(StrictTransportSecurity { include_subdomains: false, max_age: 31536000u64 }));
}
#[test]
fn test_parse_include_subdomains() {
let h = Header::parse_header(&[b"max-age=15768000 ; includeSubDomains".to_vec()][..]);
assert_eq!(h.ok(), Some(StrictTransportSecurity { include_subdomains: true, max_age: 15768000u64 }));
}
#[test]
fn test_parse_no_max_age() {
let h: ::Result<StrictTransportSecurity> = Header::parse_header(&[b"includeSubDomains".to_vec()][..]);
assert!(h.is_err());
}
#[test]
fn test_parse_max_age_nan() {
let h: ::Result<StrictTransportSecurity> = Header::parse_header(&[b"max-age = derp".to_vec()][..]);
assert!(h.is_err());
}
#[test]
fn test_parse_duplicate_directives() {
assert!(StrictTransportSecurity::parse_header(&[b"max-age=100; max-age=5; max-age=0".to_vec()][..]).is_err());
}
}
bench_header!(bench, StrictTransportSecurity, { vec![b"max-age=15768000 ; includeSubDomains".to_vec()] });