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