headers_ext/common/
strict_transport_security.rs1use std::fmt;
2use std::time::Duration;
3
4use util::{self, IterExt, Seconds};
5
6#[derive(Clone, Debug, PartialEq)]
42pub struct StrictTransportSecurity {
43 include_subdomains: bool,
46
47 max_age: Seconds,
51}
52
53impl StrictTransportSecurity {
54 pub fn including_subdomains(max_age: Duration) -> StrictTransportSecurity {
60 StrictTransportSecurity {
61 max_age: max_age.into(),
62 include_subdomains: true
63 }
64 }
65
66 pub fn excluding_subdomains(max_age: Duration) -> StrictTransportSecurity {
68 StrictTransportSecurity {
69 max_age: max_age.into(),
70 include_subdomains: false
71 }
72 }
73}
74
75enum Directive {
76 MaxAge(u64),
77 IncludeSubdomains,
78 Unknown
79}
80
81fn from_str(s: &str) -> Result<StrictTransportSecurity, ::Error> {
82 s.split(';')
83 .map(str::trim)
84 .map(|sub| if sub.eq_ignore_ascii_case("includeSubdomains") {
85 Some(Directive::IncludeSubdomains)
86 } else {
87 let mut sub = sub.splitn(2, '=');
88 match (sub.next(), sub.next()) {
89 (Some(left), Some(right))
90 if left.trim().eq_ignore_ascii_case("max-age") => {
91 right
92 .trim()
93 .trim_matches('"')
94 .parse()
95 .ok()
96 .map(Directive::MaxAge)
97 },
98 _ => Some(Directive::Unknown)
99 }
100 })
101 .fold(Some((None, None)), |res, dir| match (res, dir) {
102 (Some((None, sub)), Some(Directive::MaxAge(age))) => Some((Some(age), sub)),
103 (Some((age, None)), Some(Directive::IncludeSubdomains)) => Some((age, Some(()))),
104 (Some((Some(_), _)), Some(Directive::MaxAge(_))) |
105 (Some((_, Some(_))), Some(Directive::IncludeSubdomains)) |
106 (_, None) => None,
107 (res, _) => res
108 })
109 .and_then(|res| match res {
110 (Some(age), sub) => Some(StrictTransportSecurity {
111 max_age: Duration::from_secs(age).into(),
112 include_subdomains: sub.is_some()
113 }),
114 _ => None
115 })
116 .ok_or_else(::Error::invalid)
117}
118
119impl ::Header for StrictTransportSecurity {
120 const NAME: &'static ::HeaderName = &::http::header::STRICT_TRANSPORT_SECURITY;
121
122 fn decode<'i, I: Iterator<Item = &'i ::HeaderValue>>(values: &mut I) -> Result<Self, ::Error> {
123 values
124 .just_one()
125 .and_then(|v| v.to_str().ok())
126 .map(from_str)
127 .unwrap_or_else(|| Err(::Error::invalid()))
128 }
129
130
131 fn encode<E: Extend<::HeaderValue>>(&self, values: &mut E) {
132 struct Adapter<'a>(&'a StrictTransportSecurity);
133
134 impl<'a> fmt::Display for Adapter<'a> {
135 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
136 if self.0.include_subdomains {
137 write!(f, "max-age={}; includeSubdomains", self.0.max_age)
138 } else {
139 write!(f, "max-age={}", self.0.max_age)
140 }
141 }
142 }
143
144 values.extend(::std::iter::once(util::fmt(Adapter(self))));
145 }
146}
147
148
149#[cfg(test)]
150mod tests {
151 use std::time::Duration;
152 use super::StrictTransportSecurity;
153 use super::super::test_decode;
154
155 #[test]
156 fn test_parse_max_age() {
157 let h = test_decode::<StrictTransportSecurity>(&["max-age=31536000"]).unwrap();
158 assert_eq!(h, StrictTransportSecurity {
159 include_subdomains: false,
160 max_age: Duration::from_secs(31536000).into(),
161 });
162 }
163
164 #[test]
165 fn test_parse_max_age_no_value() {
166 assert_eq!(
167 test_decode::<StrictTransportSecurity>(&["max-age"]),
168 None,
169 );
170 }
171
172 #[test]
173 fn test_parse_quoted_max_age() {
174 let h = test_decode::<StrictTransportSecurity>(&["max-age=\"31536000\""]).unwrap();
175 assert_eq!(h, StrictTransportSecurity {
176 include_subdomains: false,
177 max_age: Duration::from_secs(31536000).into(),
178 });
179 }
180
181 #[test]
182 fn test_parse_spaces_max_age() {
183 let h = test_decode::<StrictTransportSecurity>(&["max-age = 31536000"]).unwrap();
184 assert_eq!(h, StrictTransportSecurity {
185 include_subdomains: false,
186 max_age: Duration::from_secs(31536000).into(),
187 });
188 }
189
190 #[test]
191 fn test_parse_include_subdomains() {
192 let h = test_decode::<StrictTransportSecurity>(&["max-age=15768000 ; includeSubDomains"]).unwrap();
193 assert_eq!(h, StrictTransportSecurity {
194 include_subdomains: true,
195 max_age: Duration::from_secs(15768000).into(),
196 });
197 }
198
199 #[test]
200 fn test_parse_no_max_age() {
201 assert_eq!(
202 test_decode::<StrictTransportSecurity>(&["includeSubdomains"]),
203 None,
204 );
205 }
206
207 #[test]
208 fn test_parse_max_age_nan() {
209 assert_eq!(
210 test_decode::<StrictTransportSecurity>(&["max-age = izzy"]),
211 None,
212 );
213 }
214
215 #[test]
216 fn test_parse_duplicate_directives() {
217 assert_eq!(
218 test_decode::<StrictTransportSecurity>(&["max-age=1; max-age=2"]),
219 None,
220 );
221 }
222}
223
224