hyperx/header/common/
strict_transport_security.rs1use std::fmt;
2use std::str::{self, FromStr};
3
4use unicase;
5
6use header::{Header, RawLike, parsing};
7
8#[derive(Clone, PartialEq, Debug)]
50pub struct StrictTransportSecurity {
51 pub include_subdomains: bool,
54
55 pub max_age: u64
59}
60
61impl StrictTransportSecurity {
62 pub fn including_subdomains(max_age: u64) -> StrictTransportSecurity {
64 StrictTransportSecurity {
65 max_age: max_age,
66 include_subdomains: true
67 }
68 }
69
70 pub fn excluding_subdomains(max_age: u64) -> StrictTransportSecurity {
72 StrictTransportSecurity {
73 max_age: max_age,
74 include_subdomains: false
75 }
76 }
77}
78
79enum Directive {
80 MaxAge(u64),
81 IncludeSubdomains,
82 Unknown
83}
84
85impl FromStr for StrictTransportSecurity {
86 type Err = ::Error;
87
88 fn from_str(s: &str) -> ::Result<StrictTransportSecurity> {
89 s.split(';')
90 .map(str::trim)
91 .map(|sub| if unicase::eq_ascii(sub, "includeSubdomains") {
92 Ok(Directive::IncludeSubdomains)
93 } else {
94 let mut sub = sub.splitn(2, '=');
95 match (sub.next(), sub.next()) {
96 (Some(left), Some(right))
97 if unicase::eq_ascii(left.trim(), "max-age") => {
98 right
99 .trim()
100 .trim_matches('"')
101 .parse()
102 .map(Directive::MaxAge)
103 },
104 _ => Ok(Directive::Unknown)
105 }
106 })
107 .fold(Ok((None, None)), |res, dir| match (res, dir) {
108 (Ok((None, sub)), Ok(Directive::MaxAge(age))) => Ok((Some(age), sub)),
109 (Ok((age, None)), Ok(Directive::IncludeSubdomains)) => Ok((age, Some(()))),
110 (Ok((Some(_), _)), Ok(Directive::MaxAge(_))) |
111 (Ok((_, Some(_))), Ok(Directive::IncludeSubdomains)) |
112 (_, Err(_)) => Err(::Error::Header),
113 (res, _) => res
114 })
115 .and_then(|res| match res {
116 (Some(age), sub) => Ok(StrictTransportSecurity {
117 max_age: age,
118 include_subdomains: sub.is_some()
119 }),
120 _ => Err(::Error::Header)
121 })
122 }
123}
124
125impl Header for StrictTransportSecurity {
126 fn header_name() -> &'static str {
127 static NAME: &'static str = "Strict-Transport-Security";
128 NAME
129 }
130
131 fn parse_header<'a, T>(raw: &'a T) -> ::Result<StrictTransportSecurity>
132 where T: RawLike<'a>
133 {
134 parsing::from_one_raw_str(raw)
135 }
136
137 fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result {
138 f.fmt_line(self)
139 }
140}
141
142impl fmt::Display for StrictTransportSecurity {
143 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
144 if self.include_subdomains {
145 write!(f, "max-age={}; includeSubdomains", self.max_age)
146 } else {
147 write!(f, "max-age={}", self.max_age)
148 }
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::StrictTransportSecurity;
155 use header::{Header, Raw};
156
157 #[test]
158 fn test_parse_max_age() {
159 let r: Raw = "max-age=31536000".into();
160 let h = Header::parse_header(&r);
161 assert_eq!(h.ok(), Some(StrictTransportSecurity { include_subdomains: false, max_age: 31536000u64 }));
162 }
163
164 #[test]
165 fn test_parse_max_age_no_value() {
166 let r: Raw = "max-age".into();
167 let h: ::Result<StrictTransportSecurity> = Header::parse_header(&r);
168 assert!(h.is_err());
169 }
170
171 #[test]
172 fn test_parse_quoted_max_age() {
173 let r: Raw = "max-age=\"31536000\"".into();
174 let h = Header::parse_header(&r);
175 assert_eq!(h.ok(), Some(StrictTransportSecurity { include_subdomains: false, max_age: 31536000u64 }));
176 }
177
178 #[test]
179 fn test_parse_spaces_max_age() {
180 let r: Raw = "max-age = 31536000".into();
181 let h = Header::parse_header(&r);
182 assert_eq!(h.ok(), Some(StrictTransportSecurity { include_subdomains: false, max_age: 31536000u64 }));
183 }
184
185 #[test]
186 fn test_parse_include_subdomains() {
187 let r: Raw = "max-age=15768000 ; includeSubDomains".into();
188 let h = Header::parse_header(&r);
189 assert_eq!(h.ok(), Some(StrictTransportSecurity { include_subdomains: true, max_age: 15768000u64 }));
190 }
191
192 #[test]
193 fn test_parse_no_max_age() {
194 let r: Raw = "includeSubDomains".into();
195 let h: ::Result<StrictTransportSecurity> = Header::parse_header(&r);
196 assert!(h.is_err());
197 }
198
199 #[test]
200 fn test_parse_max_age_nan() {
201 let r: Raw = "max-age = derp".into();
202 let h: ::Result<StrictTransportSecurity> = Header::parse_header(&r);
203 assert!(h.is_err());
204 }
205
206 #[test]
207 fn test_parse_duplicate_directives() {
208 let r: Raw = "max-age=100; max-age=5; max-age=0".into();
209 assert!(StrictTransportSecurity::parse_header(&r).is_err());
210 }
211}
212
213bench_header!(bench, StrictTransportSecurity, { vec![b"max-age=15768000 ; includeSubDomains".to_vec()] });
214
215standard_header!(StrictTransportSecurity, STRICT_TRANSPORT_SECURITY);