1use alloc::string::String;
4use core::{error::Error, fmt};
5
6use super::{MediaType, Source};
7
8#[derive(Debug)]
13pub struct FromStrError {
14 inner: ParseError,
15}
16
17impl FromStrError {
18 pub(crate) fn new(inner: ParseError) -> Self {
19 Self {
20 inner,
21 }
22 }
23}
24
25impl fmt::Display for FromStrError {
26 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
27 write!(formatter, "media type parse error: {}", self.inner)
28 }
29}
30
31impl Error for FromStrError {}
32
33#[derive(Debug)]
35pub(crate) enum ParseError {
36 MissingSlash,
38 MissingEqual,
40 MissingQuote,
42 InvalidToken {
44 pos: usize,
46 byte: u8,
48 },
49}
50
51impl ParseError {
52 fn message(&self) -> &'static str {
53 match self {
54 ParseError::MissingSlash => "a slash (/) was missing between the type and subtype",
55 ParseError::MissingEqual => "an equals sign (=) was missing between a parameter and its value",
56 ParseError::MissingQuote => "a quote (\") was missing from a parameter value",
57 ParseError::InvalidToken {
58 ..
59 } => "an invalid token was encountered",
60 }
61 }
62}
63
64impl fmt::Display for ParseError {
65 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
66 match self {
67 ParseError::InvalidToken {
68 pos,
69 byte,
70 } => {
71 write!(formatter, "{}, {:X} at position {}", self.message(), byte, pos)
72 }
73 _ => formatter.write_str(self.message()),
74 }
75 }
76}
77
78impl Error for ParseError {}
79
80#[derive(Clone, Debug)]
100pub struct MediaTypeIter<'a> {
101 pos: usize,
102 source: &'a str,
103}
104
105impl<'a> MediaTypeIter<'a> {
106 pub fn new(source: &'a str) -> Self {
108 Self {
109 pos: 0,
110 source,
111 }
112 }
113}
114
115impl<'a> Iterator for MediaTypeIter<'a> {
116 type Item = Result<MediaType, &'a str>;
117
118 fn next(&mut self) -> Option<Self::Item> {
119 let start = self.pos;
120 let len = self.source.len();
121
122 if start >= len {
123 return None;
124 }
125
126 match parse(&self.source[start..len]) {
128 Ok(value) => {
129 self.pos = len;
130 Some(Ok(value))
131 }
132 Err(ParseError::InvalidToken {
133 pos, ..
134 }) => {
135 if pos == 0 {
137 self.pos += 1;
138 return self.next();
139 }
140
141 let slice = &self.source[start..start + pos];
143 match parse(slice) {
144 Ok(media_type) => {
145 self.pos = start + pos + 1;
146 Some(Ok(media_type))
147 }
148 Err(_) => {
149 if start + pos < len {
150 self.pos = start + pos;
152 Some(Err(slice))
153 } else {
154 None
155 }
156 }
157 }
158 }
159 Err(_) => None,
161 }
162 }
163}
164
165pub(crate) fn parse(s: &str) -> Result<MediaType, ParseError> {
170 if s == "*/*" {
171 return Ok(MediaType::from_static("*/*"));
172 }
173
174 let bytes = s.as_bytes();
175 let len = bytes.len();
176
177 let mut i = 0;
179 loop {
180 if i >= len {
181 return Err(ParseError::MissingSlash);
182 }
183 let byte = bytes[i];
184 if is_token(byte) {
185 i += 1;
186 } else if byte == b'/' && i > 0 {
187 break;
188 } else {
189 return Err(ParseError::InvalidToken {
190 pos: i,
191 byte,
192 });
193 }
194 }
195
196 let subtype_start = i + 1;
198 let semicolon;
199 loop {
200 if i + 1 >= len {
201 return Ok(MediaType {
203 source: Source::Dynamic(s.to_ascii_lowercase()),
204 });
205 }
206 i += 1;
207 let byte = bytes[i];
208 if byte == b';' && i > subtype_start {
209 semicolon = i;
210 break;
211 } else if is_token(byte) {
212 continue;
213 } else {
214 return Err(ParseError::InvalidToken {
215 pos: i,
216 byte,
217 });
218 }
219 }
220
221 let mut out = String::with_capacity(len + 2);
223 out.push_str(&s[..semicolon].to_ascii_lowercase());
224
225 let mut i = semicolon;
226 loop {
227 i += 1;
229 while i < len && (bytes[i] == b' ' || bytes[i] == b'\t') {
230 i += 1;
231 }
232 if i >= len {
233 break;
234 }
235
236 let name_start = i;
238 while i < len && is_token(bytes[i]) {
239 i += 1;
240 }
241 if i == name_start {
242 return Err(ParseError::InvalidToken {
243 pos: i,
244 byte: bytes.get(i).copied().unwrap_or(0),
245 });
246 }
247 if i >= len || bytes[i] != b'=' {
248 return Err(ParseError::MissingEqual);
249 }
250 let name_end = i;
251 i += 1;
252
253 let value;
255 if i < len && bytes[i] == b'"' {
256 i += 1;
257 let value_start = i;
258 while i < len && bytes[i] != b'"' {
259 let byte = bytes[i];
260 if byte < 0x20 || byte == 0x7f {
261 return Err(ParseError::InvalidToken {
262 pos: i,
263 byte,
264 });
265 }
266 i += 1;
267 }
268 if i >= len {
269 return Err(ParseError::MissingQuote);
270 }
271 value = &s[value_start..i];
272 i += 1;
273 } else {
274 let value_start = i;
275 while i < len && is_token(bytes[i]) {
276 i += 1;
277 }
278 value = &s[value_start..i];
279 }
280
281 let name = &s[name_start..name_end];
282 out.push_str("; ");
283 out.push_str(&name.to_ascii_lowercase());
284 out.push('=');
285 if name.eq_ignore_ascii_case(super::CHARSET) {
286 out.push_str(&value.to_ascii_lowercase());
287 } else {
288 out.push_str(value);
289 }
290
291 while i < len && (bytes[i] == b' ' || bytes[i] == b'\t') {
293 i += 1;
294 }
295 if i >= len {
296 break;
297 }
298 if bytes[i] != b';' {
299 return Err(ParseError::InvalidToken {
300 pos: i,
301 byte: bytes[i],
302 });
303 }
304 }
305
306 Ok(MediaType {
307 source: Source::Dynamic(out),
308 })
309}
310
311macro_rules! byte_map {
318 ($($flag:expr,)*) => {
319 [$($flag != 0,)*]
320 };
321}
322
323static TOKEN_MAP: [bool; 256] = byte_map![
324 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1,
325 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
326 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
327 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
328 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
329 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
330 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
331];
332
333#[inline]
334fn is_token(byte: u8) -> bool {
335 TOKEN_MAP[byte as usize]
336}
337
338#[cfg(test)]
339mod tests {
340 use alloc::string::ToString;
341
342 use super::*;
343
344 #[test]
345 fn test_lookup_tables() {
346 for (i, &valid) in TOKEN_MAP.iter().enumerate() {
347 let i = i as u8;
348 let should = matches!(
349 i,
350 b'a'..=b'z'
351 | b'A'..=b'Z'
352 | b'0'..=b'9'
353 | b'!'
354 | b'#'
355 | b'$'
356 | b'%'
357 | b'&'
358 | b'\''
359 | b'*'
360 | b'+'
361 | b'-'
362 | b'.'
363 | b'^'
364 | b'_'
365 | b'`'
366 | b'|'
367 | b'~'
368 );
369 assert_eq!(valid, should, "{:?} ({}) should be {}", i as char, i, should);
370 }
371 }
372
373 #[test]
374 fn test_parse_iterator() {
375 let mut iter = MediaTypeIter::new("application/json, application/json");
376 assert_eq!(iter.next().unwrap().unwrap(), "application/json");
377 assert_eq!(iter.next().unwrap().unwrap(), "application/json");
378 assert_eq!(iter.next(), None);
379
380 let mut iter = MediaTypeIter::new("application/json");
381 assert_eq!(iter.next().unwrap().unwrap(), "application/json");
382 assert_eq!(iter.next(), None);
383
384 let mut iter = MediaTypeIter::new("application/json; ");
385 assert_eq!(iter.next().unwrap().unwrap(), "application/json");
386 assert_eq!(iter.next(), None);
387 }
388
389 #[test]
390 fn test_parse_iterator_invalid() {
391 let mut iter = MediaTypeIter::new("application/json, invalid, application/json");
392 assert_eq!(iter.next().unwrap().unwrap(), "application/json");
393 assert_eq!(iter.next().unwrap().unwrap_err(), "invalid");
394 assert_eq!(iter.next().unwrap().unwrap(), "application/json");
395 assert_eq!(iter.next(), None);
396 }
397
398 #[test]
399 fn test_parse_iterator_leading_invalid() {
400 let mut iter = MediaTypeIter::new(";;application/json");
401 assert_eq!(iter.next().unwrap().unwrap(), "application/json");
402 assert_eq!(iter.next(), None);
403 }
404
405 #[test]
406 fn test_parse_canonical_form() {
407 assert_eq!(
408 "TEXT/PLAIN; CHARSET=UTF-8; FOO=BAR"
409 .parse::<MediaType>()
410 .unwrap()
411 .to_string(),
412 "text/plain; charset=utf-8; foo=BAR"
413 );
414 assert_eq!(
415 "text/plain;charset=\"utf-8\"".parse::<MediaType>().unwrap(),
416 super::super::TEXT_PLAIN_UTF_8
417 );
418 }
419
420 #[test]
421 fn test_parse_errors() {
422 assert!("f o o / bar".parse::<MediaType>().is_err());
423 assert!("text\n/plain".parse::<MediaType>().is_err());
424 assert!("text\r/plain".parse::<MediaType>().is_err());
425 assert!("text/\r\nplain".parse::<MediaType>().is_err());
426 assert!("text/plain;\r\ncharset=utf-8".parse::<MediaType>().is_err());
427 assert!("text/plain; charset=\r\nutf-8".parse::<MediaType>().is_err());
428 assert!("text/plain; charset=\"\r\nutf-8\"".parse::<MediaType>().is_err());
429 }
430
431 #[test]
432 fn test_from_static_and_from_str_are_equal() {
433 let parsed = "application/json".parse::<MediaType>().unwrap();
434 assert_eq!(parsed, super::super::APPLICATION_JSON);
435 }
436}