feat(headers): add strict-transport-security header
Strict-Transport-Security allows servers to inform user-agents that they'd like them to always contact the secure host (https) instead of the insecure one (http). Closes #589
This commit is contained in:
committed by
Sean McArthur
parent
9a85ea553f
commit
7c2e5124e6
@@ -44,6 +44,7 @@ pub use self::range::{Range, ByteRangeSpec};
|
||||
pub use self::referer::Referer;
|
||||
pub use self::server::Server;
|
||||
pub use self::set_cookie::SetCookie;
|
||||
pub use self::strict_transport_security::StrictTransportSecurity;
|
||||
pub use self::transfer_encoding::TransferEncoding;
|
||||
pub use self::upgrade::{Upgrade, Protocol, ProtocolName};
|
||||
pub use self::user_agent::UserAgent;
|
||||
@@ -356,6 +357,7 @@ mod range;
|
||||
mod referer;
|
||||
mod server;
|
||||
mod set_cookie;
|
||||
mod strict_transport_security;
|
||||
mod transfer_encoding;
|
||||
mod upgrade;
|
||||
mod user_agent;
|
||||
|
||||
195
src/header/common/strict_transport_security.rs
Normal file
195
src/header/common/strict_transport_security.rs
Normal file
@@ -0,0 +1,195 @@
|
||||
use std::fmt;
|
||||
use std::str::{self, FromStr};
|
||||
|
||||
use unicase::UniCase;
|
||||
|
||||
use header::{Header, HeaderFormat, parsing};
|
||||
|
||||
/// `StrictTransportSecurity` header, defined in [RFC6797](https://tools.ietf.org/html/rfc6797)
|
||||
///
|
||||
/// This specification defines a mechanism enabling web sites to declare
|
||||
/// themselves accessible only via secure connections and/or for users to be
|
||||
/// able to direct their user agent(s) to interact with given sites only over
|
||||
/// secure connections. This overall policy is referred to as HTTP Strict
|
||||
/// Transport Security (HSTS). The policy is declared by web sites via the
|
||||
/// Strict-Transport-Security HTTP response header field and/or by other means,
|
||||
/// such as user agent configuration, for example.
|
||||
///
|
||||
/// # ABNF
|
||||
///
|
||||
/// ```plain
|
||||
/// [ directive ] *( ";" [ directive ] )
|
||||
///
|
||||
/// directive = directive-name [ "=" directive-value ]
|
||||
/// directive-name = token
|
||||
/// directive-value = token | quoted-string
|
||||
///
|
||||
/// ```
|
||||
///
|
||||
/// # Example values
|
||||
/// * `max-age=31536000`
|
||||
/// * `max-age=15768000 ; includeSubDomains`
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # extern crate hyper;
|
||||
/// # fn main() {
|
||||
/// use hyper::header::{Headers, StrictTransportSecurity};
|
||||
///
|
||||
/// let mut headers = Headers::new();
|
||||
///
|
||||
/// headers.set(
|
||||
/// StrictTransportSecurity::including_subdomains(31536000u64)
|
||||
/// );
|
||||
/// # }
|
||||
/// ```
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub struct StrictTransportSecurity {
|
||||
/// Signals the UA that the HSTS Policy applies to this HSTS Host as well as
|
||||
/// any subdomains of the host's domain name.
|
||||
pub include_subdomains: bool,
|
||||
|
||||
/// Specifies the number of seconds, after the reception of the STS header
|
||||
/// field, during which the UA regards the host (from whom the message was
|
||||
/// received) as a Known HSTS Host.
|
||||
pub max_age: u64
|
||||
}
|
||||
|
||||
impl StrictTransportSecurity {
|
||||
/// Create an STS header that includes subdomains
|
||||
pub fn including_subdomains(max_age: u64) -> StrictTransportSecurity {
|
||||
StrictTransportSecurity {
|
||||
max_age: max_age,
|
||||
include_subdomains: true
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an STS header that excludes subdomains
|
||||
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()] });
|
||||
Reference in New Issue
Block a user