hyper_sync/header/common/
strict_transport_security.rs

1use std::fmt;
2use std::str::{self, FromStr};
3
4use unicase;
5
6use header::{Header, Raw, parsing};
7
8/// `StrictTransportSecurity` header, defined in [RFC6797](https://tools.ietf.org/html/rfc6797)
9///
10/// This specification defines a mechanism enabling web sites to declare
11/// themselves accessible only via secure connections and/or for users to be
12/// able to direct their user agent(s) to interact with given sites only over
13/// secure connections.  This overall policy is referred to as HTTP Strict
14/// Transport Security (HSTS).  The policy is declared by web sites via the
15/// Strict-Transport-Security HTTP response header field and/or by other means,
16/// such as user agent configuration, for example.
17///
18/// # ABNF
19///
20/// ```text
21///      [ directive ]  *( ";" [ directive ] )
22///
23///      directive                 = directive-name [ "=" directive-value ]
24///      directive-name            = token
25///      directive-value           = token | quoted-string
26///
27/// ```
28///
29/// # Example values
30///
31/// * `max-age=31536000`
32/// * `max-age=15768000 ; includeSubDomains`
33///
34/// # Example
35///
36/// ```
37/// # extern crate hyper_sync;
38/// # fn main() {
39/// use hyper_sync::header::{Headers, StrictTransportSecurity};
40///
41/// let mut headers = Headers::new();
42///
43/// headers.set(
44///    StrictTransportSecurity::including_subdomains(31536000u64)
45/// );
46/// # }
47/// ```
48#[derive(Clone, PartialEq, Debug)]
49pub struct StrictTransportSecurity {
50    /// Signals the UA that the HSTS Policy applies to this HSTS Host as well as
51    /// any subdomains of the host's domain name.
52    pub include_subdomains: bool,
53
54    /// Specifies the number of seconds, after the reception of the STS header
55    /// field, during which the UA regards the host (from whom the message was
56    /// received) as a Known HSTS Host.
57    pub max_age: u64
58}
59
60impl StrictTransportSecurity {
61    /// Create an STS header that includes subdomains
62    pub fn including_subdomains(max_age: u64) -> StrictTransportSecurity {
63        StrictTransportSecurity {
64            max_age: max_age,
65            include_subdomains: true
66        }
67    }
68
69    /// Create an STS header that excludes subdomains
70    pub fn excluding_subdomains(max_age: u64) -> StrictTransportSecurity {
71        StrictTransportSecurity {
72            max_age: max_age,
73            include_subdomains: false
74        }
75    }
76}
77
78enum Directive {
79    MaxAge(u64),
80    IncludeSubdomains,
81    Unknown
82}
83
84impl FromStr for StrictTransportSecurity {
85    type Err = ::Error;
86
87    fn from_str(s: &str) -> ::Result<StrictTransportSecurity> {
88        s.split(';')
89            .map(str::trim)
90            .map(|sub| if unicase::eq_ascii(sub, "includeSubdomains") {
91                Ok(Directive::IncludeSubdomains)
92            } else {
93                let mut sub = sub.splitn(2, '=');
94                match (sub.next(), sub.next()) {
95                    (Some(left), Some(right))
96                    if unicase::eq_ascii(left.trim(), "max-age") => {
97                        right
98                            .trim()
99                            .trim_matches('"')
100                            .parse()
101                            .map(Directive::MaxAge)
102                    },
103                    _ => Ok(Directive::Unknown)
104                }
105            })
106            .fold(Ok((None, None)), |res, dir| match (res, dir) {
107                (Ok((None, sub)), Ok(Directive::MaxAge(age))) => Ok((Some(age), sub)),
108                (Ok((age, None)), Ok(Directive::IncludeSubdomains)) => Ok((age, Some(()))),
109                (Ok((Some(_), _)), Ok(Directive::MaxAge(_))) |
110                (Ok((_, Some(_))), Ok(Directive::IncludeSubdomains)) |
111                (_, Err(_)) => Err(::Error::Header),
112                (res, _) => res
113            })
114            .and_then(|res| match res {
115                (Some(age), sub) => Ok(StrictTransportSecurity {
116                    max_age: age,
117                    include_subdomains: sub.is_some()
118                }),
119                _ => Err(::Error::Header)
120            })
121    }
122}
123
124impl Header for StrictTransportSecurity {
125    fn header_name() -> &'static str {
126        static NAME: &'static str = "Strict-Transport-Security";
127        NAME
128    }
129
130    fn parse_header(raw: &Raw) -> ::Result<StrictTransportSecurity> {
131        parsing::from_one_raw_str(raw)
132    }
133
134    fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result {
135        f.fmt_line(self)
136    }
137}
138
139impl fmt::Display for StrictTransportSecurity {
140    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
141        if self.include_subdomains {
142            write!(f, "max-age={}; includeSubdomains", self.max_age)
143        } else {
144            write!(f, "max-age={}", self.max_age)
145        }
146    }
147}
148
149#[cfg(test)]
150mod tests {
151    use super::StrictTransportSecurity;
152    use header::Header;
153
154    #[test]
155    fn test_parse_max_age() {
156        let h = Header::parse_header(&"max-age=31536000".into());
157        assert_eq!(h.ok(), Some(StrictTransportSecurity { include_subdomains: false, max_age: 31536000u64 }));
158    }
159
160    #[test]
161    fn test_parse_max_age_no_value() {
162        let h: ::Result<StrictTransportSecurity> = Header::parse_header(&"max-age".into());
163        assert!(h.is_err());
164    }
165
166    #[test]
167    fn test_parse_quoted_max_age() {
168        let h = Header::parse_header(&"max-age=\"31536000\"".into());
169        assert_eq!(h.ok(), Some(StrictTransportSecurity { include_subdomains: false, max_age: 31536000u64 }));
170    }
171
172    #[test]
173    fn test_parse_spaces_max_age() {
174        let h = Header::parse_header(&"max-age = 31536000".into());
175        assert_eq!(h.ok(), Some(StrictTransportSecurity { include_subdomains: false, max_age: 31536000u64 }));
176    }
177
178    #[test]
179    fn test_parse_include_subdomains() {
180        let h = Header::parse_header(&"max-age=15768000 ; includeSubDomains".into());
181        assert_eq!(h.ok(), Some(StrictTransportSecurity { include_subdomains: true, max_age: 15768000u64 }));
182    }
183
184    #[test]
185    fn test_parse_no_max_age() {
186        let h: ::Result<StrictTransportSecurity> = Header::parse_header(&"includeSubDomains".into());
187        assert!(h.is_err());
188    }
189
190    #[test]
191    fn test_parse_max_age_nan() {
192        let h: ::Result<StrictTransportSecurity> = Header::parse_header(&"max-age = derp".into());
193        assert!(h.is_err());
194    }
195
196    #[test]
197    fn test_parse_duplicate_directives() {
198        assert!(StrictTransportSecurity::parse_header(&"max-age=100; max-age=5; max-age=0".into()).is_err());
199    }
200}
201
202bench_header!(bench, StrictTransportSecurity, { vec![b"max-age=15768000 ; includeSubDomains".to_vec()] });