1use std::collections::HashMap;
4use std::hash::{Hash, Hasher};
5use std::iter::Peekable;
6use std::str::Chars;
7
8use itertools::Itertools;
9
10const SEPARATORS: [char; 10] = ['(', ')', '<', '>', '@', ',', ';', '=', '{', '}'];
11const VALUE_SEPARATORS: [char; 9] = ['(', ')', '<', '>', '@', ',', ';', '{', '}'];
12
13fn batch(values: &[String]) -> Vec<(String, String)> {
14 values.into_iter().batching(|it| {
15 match it.next() {
16 None => None,
17 Some(x) => match it.next() {
18 None => Some((x.to_string(), "".to_string())),
19 Some(y) => Some((x.to_string(), y.to_string())),
20 }
21 }
22 }).collect()
23}
24
25fn header_value(chars: &mut Peekable<Chars>, seperators: &[char]) -> String {
27 let mut value = String::new();
28 skip_whitespace(chars);
29 if chars.peek().is_some() && chars.peek().unwrap() == &'"' {
30 chars.next();
31 while chars.peek().is_some() && chars.peek().unwrap() != &'"' {
32 let ch = chars.next().unwrap();
33 match ch {
34 '\\' => {
35 if chars.peek().is_some() {
36 value.push(chars.next().unwrap());
37 } else {
38 value.push(ch);
39 }
40 },
41 _ => value.push(ch)
42 }
43 }
44 if chars.peek().is_some() {
45 chars.next();
46 }
47 } else {
48 while chars.peek().is_some() && !seperators.contains(chars.peek().unwrap()) {
49 value.push(chars.next().unwrap())
50 }
51 }
52 value.trim().to_string()
53}
54
55fn parse_header(s: &str) -> Vec<String> {
57 let mut chars = s.chars().peekable();
58 let header_value = header_value(&mut chars, &VALUE_SEPARATORS);
59 let mut values = vec![header_value];
60 if chars.peek().is_some() && chars.peek().unwrap() == &';' {
61 chars.next();
62 parse_header_parameters(&mut chars, &mut values);
63 }
64 values
65}
66
67fn parse_header_parameters(chars: &mut Peekable<Chars>, values: &mut Vec<String>) {
69 parse_header_parameter(chars, values);
70 if chars.peek().is_some() && chars.peek().unwrap() == &';' {
71 chars.next();
72 parse_header_parameters(chars, values);
73 }
74}
75
76fn parse_header_parameter(chars: &mut Peekable<Chars>, values: &mut Vec<String>) {
78 values.push(header_value(chars, &SEPARATORS));
79 if chars.peek().is_some() && chars.peek().unwrap() == &'=' {
80 chars.next();
81 parse_header_parameter_value(chars, values);
82 }
83}
84
85fn parse_header_parameter_value(chars: &mut Peekable<Chars>, values: &mut Vec<String>) {
87 skip_whitespace(chars);
88 if chars.peek().is_some() && chars.peek().unwrap() == &'"' {
89 chars.next();
90 let mut value = String::new();
91 while chars.peek().is_some() && chars.peek().unwrap() != &'"' {
92 let ch = chars.next().unwrap();
93 match ch {
94 '\\' => {
95 if chars.peek().is_some() {
96 value.push(chars.next().unwrap());
97 } else {
98 value.push(ch);
99 }
100 },
101 _ => value.push(ch)
102 }
103 }
104 if chars.peek().is_some() {
105 chars.next();
106 }
107 values.push(value.to_string());
108 } else {
109 values.push(header_value(chars, &[';']));
110 }
111}
112
113fn skip_whitespace(chars: &mut Peekable<Chars>) {
114 while chars.peek().is_some() && chars.peek().unwrap().is_whitespace() {
115 chars.next();
116 }
117}
118
119
120#[derive(Debug, Clone, Eq)]
122pub struct HeaderValue {
123 pub value: String,
125 pub params: HashMap<String, String>,
127 pub quote: bool
129}
130
131impl HeaderValue {
132 pub fn parse_string(s: &str) -> HeaderValue {
134 let values = parse_header(s);
135 let (first, second) = values.split_first().unwrap();
136 if second.is_empty() {
137 HeaderValue::basic(first.as_str())
138 } else {
139 HeaderValue {
140 value: first.clone(),
141 params: batch(second).iter()
142 .fold(HashMap::new(), |mut map, params| {
143 if !params.0.is_empty() {
144 map.insert(params.0.clone(), params.1.clone());
145 }
146 map
147 }),
148 quote: false
149 }
150 }
151 }
152
153 pub fn basic<S: Into<String>>(s: S) -> HeaderValue {
155 HeaderValue {
156 value: s.into(),
157 params: HashMap::new(),
158 quote: false
159 }
160 }
161
162 pub fn to_string(&self) -> String {
164 let sparams = self.params.iter()
165 .map(|(k, v)| format!("{}={}", k, v))
166 .join("; ");
167 if self.quote {
168 if sparams.is_empty() {
169 format!("\"{}\"", self.value)
170 } else {
171 format!("\"{}\"; {}", self.value, sparams)
172 }
173 } else {
174 if self.params.is_empty() {
175 self.value.clone()
176 } else {
177 format!("{}; {}", self.value, sparams)
178 }
179 }
180 }
181
182 pub fn weak_etag(&self) -> Option<String> {
185 if self.value.starts_with("W/") {
186 Some(parse_header(&self.value[2..])[0].clone())
187 } else {
188 None
189 }
190 }
191
192 pub fn quote(mut self) -> HeaderValue {
194 self.quote = true;
195 self
196 }
197}
198
199impl PartialEq<HeaderValue> for HeaderValue {
200 fn eq(&self, other: &HeaderValue) -> bool {
201 self.value == other.value && self.params == other.params
202 }
203}
204
205impl PartialEq<String> for HeaderValue {
206 fn eq(&self, other: &String) -> bool {
207 self.value == *other
208 }
209}
210
211impl PartialEq<str> for HeaderValue {
212 fn eq(&self, other: &str) -> bool {
213 self.value == *other
214 }
215}
216
217impl Hash for HeaderValue {
218 fn hash<H: Hasher>(&self, state: &mut H) {
219 self.value.hash(state);
220 for (k, v) in self.params.clone() {
221 k.hash(state);
222 v.hash(state);
223 }
224 }
225}
226
227#[macro_export]
229macro_rules! h {
230 ($e:expr) => (HeaderValue::parse_string($e.into()))
231}
232
233#[cfg(test)]
234mod tests {
235 use expectest::prelude::*;
236 use maplit::hashmap;
237
238 use super::*;
239
240 #[test]
241 fn parse_header_value_test() {
242 expect!(HeaderValue::parse_string("")).to(be_equal_to("".to_string()));
243 expect!(HeaderValue::parse_string("A B")).to(be_equal_to("A B".to_string()));
244 expect!(HeaderValue::parse_string("A; B")).to(be_equal_to(HeaderValue {
245 value: "A".to_string(),
246 params: hashmap!{ "B".to_string() => "".to_string() },
247 quote: false
248 }));
249 expect!(HeaderValue::parse_string("text/html;charset=utf-8")).to(be_equal_to(HeaderValue {
250 value: "text/html".to_string(),
251 params: hashmap!{ "charset".to_string() => "utf-8".to_string() },
252 quote: false
253 }));
254 expect!(HeaderValue::parse_string("text/html;charset=UTF-8")).to(be_equal_to(HeaderValue {
255 value: "text/html".to_string(),
256 params: hashmap!{ "charset".to_string() => "UTF-8".to_string() },
257 quote: false
258 }));
259 expect!(HeaderValue::parse_string("Text/HTML;Charset= \"utf-8\"")).to(be_equal_to(HeaderValue {
260 value: "Text/HTML".to_string(),
261 params: hashmap!{ "Charset".to_string() => "utf-8".to_string() },
262 quote: false
263 }));
264 expect!(HeaderValue::parse_string("text/html; charset = \" utf-8 \"")).to(be_equal_to(HeaderValue {
265 value: "text/html".to_string(),
266 params: hashmap!{ "charset".to_string() => " utf-8 ".to_string() },
267 quote: false
268 }));
269 expect!(HeaderValue::parse_string(";")).to(be_equal_to(HeaderValue {
270 value: "".to_string(),
271 params: hashmap!{},
272 quote: false
273 }));
274 expect!(HeaderValue::parse_string("A;b=c=d")).to(be_equal_to(HeaderValue {
275 value: "A".to_string(),
276 params: hashmap!{ "b".to_string() => "c=d".to_string() },
277 quote: false
278 }));
279 expect!(HeaderValue::parse_string("A;b=\"c;d\"")).to(be_equal_to(HeaderValue {
280 value: "A".to_string(),
281 params: hashmap!{ "b".to_string() => "c;d".to_string() },
282 quote: false
283 }));
284 expect!(HeaderValue::parse_string("A;b=\"c\\\"d\"")).to(be_equal_to(HeaderValue {
285 value: "A".to_string(),
286 params: hashmap!{ "b".to_string() => "c\"d".to_string() },
287 quote: false
288 }));
289 expect!(HeaderValue::parse_string("A;b=\"c,d\"")).to(be_equal_to(HeaderValue {
290 value: "A".to_string(),
291 params: hashmap!{ "b".to_string() => "c,d".to_string() },
292 quote: false
293 }));
294 expect!(HeaderValue::parse_string("en;q=0.0")).to(be_equal_to(HeaderValue {
295 value: "en".to_string(),
296 params: hashmap!{ "q".to_string() => "0.0".to_string() },
297 quote: false
298 }));
299 }
300
301 #[test]
302 fn parse_qouted_header_value_test() {
303 expect!(HeaderValue::parse_string("\"*\"")).to(be_equal_to(HeaderValue {
304 value: "*".to_string(),
305 params: hashmap!{},
306 quote: false
307 }));
308 expect!(HeaderValue::parse_string(" \"quoted; value\"")).to(be_equal_to(HeaderValue {
309 value: "quoted; value".to_string(),
310 params: hashmap!{},
311 quote: false
312 }));
313 }
314
315 #[test]
316 fn parse_etag_header_value_test() {
317 let etag = "\"1234567890\"";
318 let weak_etag = "W/\"1234567890\"";
319
320 let header = HeaderValue::parse_string(etag);
321 expect!(header.clone()).to(be_equal_to(HeaderValue {
322 value: "1234567890".to_string(),
323 params: hashmap!{},
324 quote: false
325 }));
326 expect!(header.weak_etag()).to(be_none());
327
328 let weak_etag_value = HeaderValue::parse_string(weak_etag);
329 expect!(weak_etag_value.clone()).to(be_equal_to(HeaderValue {
330 value: weak_etag.to_string(),
331 params: hashmap!{},
332 quote: false
333 }));
334 expect!(weak_etag_value.weak_etag()).to(be_some().value("1234567890"));
335 }
336}