1use std::borrow::Cow;
2
3use pear::input::Extent;
4use pear::combinators::{prefixed_series, surrounded};
5use pear::macros::{parser, switch, parse};
6use pear::parsers::*;
7
8use crate::header::{MediaType, Source};
9use crate::parse::checkers::{is_whitespace, is_valid_token};
10
11type Input<'a> = pear::input::Pear<pear::input::Cursor<&'a str>>;
12type Result<'a, T> = pear::input::Result<T, Input<'a>>;
13
14#[parser]
15fn quoted_string<'a>(input: &mut Input<'a>) -> Result<'a, Extent<&'a str>> {
16 eat('"')?;
17
18 let mut is_escaped = false;
19 let inner = take_while(|&c| {
20 if is_escaped { is_escaped = false; return true; }
21 if c == '\\' { is_escaped = true; return true; }
22 c != '"'
23 })?;
24
25 eat('"')?;
26 inner
27}
28
29#[parser]
30fn media_param<'a>(input: &mut Input<'a>) -> Result<'a, (Extent<&'a str>, Extent<&'a str>)> {
31 let key = (take_some_while_until(is_valid_token, '=')?, eat('=')?).0;
32 let value = switch! {
33 peek('"') => quoted_string()?,
34 _ => take_some_while_until(is_valid_token, ';')?
35 };
36
37 (key, value)
38}
39
40#[parser]
41pub fn media_type<'a>(input: &mut Input<'a>) -> Result<'a, MediaType> {
42 let (top, sub, params) = {
43 let top = (take_some_while_until(is_valid_token, '/')?, eat('/')?).0;
44 let sub = take_some_while_until(is_valid_token, ';')?;
45 let params = prefixed_series(';', |i| {
46 let param = surrounded(i, media_param, is_whitespace)?;
47 Ok((param.0.into(), param.1.into()))
48 }, ';')?;
49
50 (top, sub, params)
51 };
52
53 MediaType {
54 params,
55 source: Source::Custom(Cow::Owned(input.start.to_string())),
56 top: top.into(),
57 sub: sub.into(),
58 }
59}
60
61pub fn parse_media_type(input: &str) -> Result<'_, MediaType> {
62 parse!(media_type: Input::new(input))
63}
64
65#[cfg(test)]
66mod test {
67 use crate::MediaType;
68 use super::parse_media_type;
69
70 macro_rules! assert_no_parse {
71 ($string:expr) => ({
72 let result: Result<_, _> = parse_media_type($string).into();
73 if result.is_ok() {
74 panic!("{:?} parsed unexpectedly.", $string)
75 }
76 });
77 }
78
79 macro_rules! assert_parse {
80 ($string:expr) => ({
81 match parse_media_type($string) {
82 Ok(media_type) => media_type,
83 Err(e) => panic!("{:?} failed to parse: {}", $string, e)
84 }
85 });
86 }
87
88 macro_rules! assert_parse_eq {
89 (@full $string:expr, $result:expr, $(($k:expr, $v:expr)),*) => ({
90 let result = assert_parse!($string);
91 assert_eq!(result, $result);
92
93 let expected_params: Vec<(&str, &str)> = vec![$(($k, $v)),*];
94 if expected_params.len() > 0 {
95 assert_eq!(result.params().count(), expected_params.len());
96 let all_params = result.params().zip(expected_params.iter());
97 for ((key, val), &(ekey, eval)) in all_params {
98 assert_eq!(key, ekey);
99 assert_eq!(val, eval);
100 }
101 }
102 });
103
104 (from: $string:expr, into: $result:expr)
105 => (assert_parse_eq!(@full $string, $result, ));
106 (from: $string:expr, into: $result:expr, params: $(($key:expr, $val:expr)),*)
107 => (assert_parse_eq!(@full $string, $result, $(($key, $val)),*));
108 }
109
110 #[test]
111 fn check_does_parse() {
112 assert_parse!("text/html");
113 assert_parse!("a/b");
114 assert_parse!("*/*");
115 }
116
117 #[test]
118 fn check_parse_eq() {
119 assert_parse_eq!(from: "text/html", into: MediaType::HTML);
120 assert_parse_eq!(from: "text/html; charset=utf-8", into: MediaType::HTML);
121 assert_parse_eq!(from: "text/html", into: MediaType::HTML);
122
123 assert_parse_eq!(from: "a/b", into: MediaType::new("a", "b"));
124 assert_parse_eq!(from: "*/*", into: MediaType::Any);
125 assert_parse_eq!(from: "application/pdf", into: MediaType::PDF);
126 assert_parse_eq!(from: "application/json", into: MediaType::JSON);
127 assert_parse_eq!(from: "image/svg+xml", into: MediaType::SVG);
128
129 assert_parse_eq!(from: "*/json", into: MediaType::new("*", "json"));
130 assert_parse_eq! {
131 from: "application/*; param=1",
132 into: MediaType::new("application", "*")
133 };
134 }
135
136 #[test]
137 fn check_param_eq() {
138 assert_parse_eq! {
139 from: "text/html; a=b; b=c; c=d",
140 into: MediaType::new("text", "html"),
141 params: ("a", "b"), ("b", "c"), ("c", "d")
142 };
143
144 assert_parse_eq! {
145 from: "text/html;a=b;b=c; c=d; d=e",
146 into: MediaType::new("text", "html"),
147 params: ("a", "b"), ("b", "c"), ("c", "d"), ("d", "e")
148 };
149
150 assert_parse_eq! {
151 from: "text/html; charset=utf-8",
152 into: MediaType::new("text", "html"),
153 params: ("charset", "utf-8")
154 };
155
156 assert_parse_eq! {
157 from: "application/*; param=1",
158 into: MediaType::new("application", "*"),
159 params: ("param", "1")
160 };
161
162 assert_parse_eq! {
163 from: "*/*;q=0.5;b=c;c=d",
164 into: MediaType::Any,
165 params: ("q", "0.5"), ("b", "c"), ("c", "d")
166 };
167
168 assert_parse_eq! {
169 from: "multipart/form-data; boundary=----WebKitFormBoundarypRshfItmvaC3aEuq",
170 into: MediaType::FormData,
171 params: ("boundary", "----WebKitFormBoundarypRshfItmvaC3aEuq")
172 };
173
174 assert_parse_eq! {
175 from: r#"*/*; a="hello, world!@#$%^&*();;hi""#,
176 into: MediaType::Any,
177 params: ("a", "hello, world!@#$%^&*();;hi")
178 };
179
180 assert_parse_eq! {
181 from: r#"application/json; a=";,;""#,
182 into: MediaType::JSON,
183 params: ("a", ";,;")
184 };
185
186 assert_parse_eq! {
187 from: r#"application/json; a=";,;"; b=c"#,
188 into: MediaType::JSON,
189 params: ("a", ";,;"), ("b", "c")
190 };
191
192 assert_parse_eq! {
193 from: r#"application/json; b=c; a=";.,.;""#,
194 into: MediaType::JSON,
195 params: ("b", "c"), ("a", ";.,.;")
196 };
197
198 assert_parse_eq! {
199 from: r#"*/*; a="a"; b="b"; a=a; b=b; c=c"#,
200 into: MediaType::Any,
201 params: ("a", "a"), ("b", "b"), ("a", "a"), ("b", "b"), ("c", "c")
202 };
203 }
204
205 #[test]
206 fn check_params_do_parse() {
207 assert_parse!("*/*; q=1; q=2");
208 assert_parse!("*/*; q=1;q=2;q=3;a=v;c=1;da=1;sdlkldsadasd=uhisdcb89");
209 assert_parse!("*/*; q=1; q=2");
210 assert_parse!("*/*; q=1; q=2; a=b;c=d; e=f; a=s;a=e");
211 assert_parse!("*/*; q=1; q=2 ; a=b");
212 assert_parse!("*/*; q=1; q=2; hello=\"world !\"");
213 }
214
215 #[test]
216 fn test_bad_parses() {
217 assert_no_parse!("*&_/*)()");
218 assert_no_parse!("/json");
219 assert_no_parse!("text/");
220 assert_no_parse!("text//");
221 assert_no_parse!("/");
222 assert_no_parse!("*/");
223 assert_no_parse!("/*");
224 assert_no_parse!("///");
225 assert_no_parse!("application//json");
226 assert_no_parse!("application///json");
227 assert_no_parse!("a/b;");
228 assert_no_parse!("*/*; a=b;;");
229 assert_no_parse!("*/*; a=b;a");
230 assert_no_parse!("*/*; a=b; ");
231 assert_no_parse!("*/*; a=b;");
232 assert_no_parse!("*/*; a = b");
233 assert_no_parse!("*/*; a= b");
234 assert_no_parse!("*/*; a =b");
235 assert_no_parse!(r#"*/*; a="b"#);
236 assert_no_parse!(r#"*/*; a="b; c=d"#);
237 assert_no_parse!(r#"*/*; a="b; c=d"#);
238 assert_no_parse!("*/*;a=@#$%^&*()");
239 assert_no_parse!("*/*;;");
240 assert_no_parse!("*/*;=;");
241 assert_no_parse!("*/*=;");
242 assert_no_parse!("*/*=;=");
243 }
244}