1use crate::{
27 class,
28 error::{ErrorKind, ParseError, Result, WriteError, err},
29 limits::Limits,
30 negotiate::QValue,
31 param::{self, Params},
32 scan::control_kind,
33 text::{Word, is_token, skip_ows, trim_ows},
34 write::{Sink, reserve, unique_names, word_len, write_word},
35};
36
37pub fn parse_media_type(value: &[u8]) -> Result<MediaType<'_>> {
52 parse(value, &Limits::DEFAULT)
53}
54
55fn bad_byte(value: &[u8], at: usize) -> ParseError {
57 match value.get(at) {
58 Some(&b) if crate::scan::is_control(b) => ParseError::new(control_kind(b), at),
59 _ => ParseError::new(ErrorKind::InvalidMediaType, at),
60 }
61}
62
63fn essence(value: &[u8], base: usize) -> Result<(&[u8], &[u8], usize)> {
66 let type_len = value.iter().take_while(|&&b| class::is_tchar(b)).count();
67 if type_len == 0 {
68 return Err(bad_byte(value, 0).shifted(base));
69 }
70 if value.get(type_len) != Some(&b'/') {
71 return Err(bad_byte(value, type_len).shifted(base));
72 }
73 let sub_start = type_len + 1;
74 let sub_len = value
75 .get(sub_start..)
76 .unwrap_or(&[])
77 .iter()
78 .take_while(|&&b| class::is_tchar(b))
79 .count();
80 if sub_len == 0 {
81 return Err(bad_byte(value, sub_start).shifted(base));
82 }
83 let type_ = value.get(..type_len).unwrap_or(&[]);
84 let subtype = value.get(sub_start..sub_start + sub_len).unwrap_or(&[]);
85 Ok((type_, subtype, sub_start + sub_len))
86}
87
88pub fn parse<'a>(value: &'a [u8], limits: &Limits) -> Result<MediaType<'a>> {
113 let lead = skip_ows(value);
114 let trimmed = trim_ows(value);
115 let (type_, subtype, used) = essence(trimmed, lead)?;
116 if type_ == b"*" {
117 return err(ErrorKind::Wildcard, lead);
118 }
119 if subtype == b"*" {
120 return err(ErrorKind::Wildcard, lead + type_.len() + 1);
121 }
122 let rest = trimmed.get(used..).unwrap_or(&[]);
123 let (params, n) = param::parse(rest, limits).map_err(|e| e.shifted(lead + used))?;
124 let end = used + n;
125 if end < trimmed.len() {
126 let at = end + skip_ows(trimmed.get(end..).unwrap_or(&[]));
127 return Err(bad_byte(trimmed, at).shifted(lead));
128 }
129 Ok(MediaType {
130 type_,
131 subtype,
132 params,
133 })
134}
135
136pub fn parse_range<'a>(value: &'a [u8], limits: &Limits) -> Result<MediaRange<'a>> {
161 let lead = skip_ows(value);
162 let trimmed = trim_ows(value);
163 let (type_, subtype, used) = essence(trimmed, lead)?;
164 if type_ == b"*" && subtype != b"*" {
165 return err(ErrorKind::Wildcard, lead + type_.len() + 1);
166 }
167 let rest = trimmed.get(used..).unwrap_or(&[]);
168 let (all, n) = param::parse(rest, limits).map_err(|e| e.shifted(lead + used))?;
169 let end = used + n;
170 if end < trimmed.len() {
171 let at = end + skip_ows(trimmed.get(end..).unwrap_or(&[]));
172 return Err(bad_byte(trimmed, at).shifted(lead));
173 }
174 let (params, q) = all.split_at_name(b"q");
175 let weight = match q {
176 None => QValue::ONE,
177 Some(p) => match p.value {
178 Word::Token(t) => QValue::parse(t).map_err(|e| {
179 let at = (t.as_ptr() as usize).saturating_sub(value.as_ptr() as usize);
180 ParseError::new(e.kind(), at)
181 })?,
182 Word::Quoted(_) => {
183 let at = (p.name.as_ptr() as usize).saturating_sub(value.as_ptr() as usize);
184 return err(ErrorKind::InvalidQValue, at);
185 }
186 },
187 };
188 Ok(MediaRange {
189 type_,
190 subtype,
191 params,
192 weight,
193 })
194}
195
196#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
210pub struct MediaRange<'a> {
211 type_: &'a [u8],
212 subtype: &'a [u8],
213 params: Params<'a>,
214 weight: QValue,
215}
216
217impl<'a> MediaRange<'a> {
218 #[must_use]
220 pub const fn type_(&self) -> &'a [u8] {
221 self.type_
222 }
223
224 #[must_use]
226 pub const fn subtype(&self) -> &'a [u8] {
227 self.subtype
228 }
229
230 #[must_use]
234 pub fn matches(&self, offer: &MediaType<'_>) -> bool {
235 let type_ok = self.type_ == b"*" || self.type_.eq_ignore_ascii_case(offer.type_());
236 let sub_ok = self.subtype == b"*" || self.subtype.eq_ignore_ascii_case(offer.subtype());
237 type_ok
238 && sub_ok
239 && self.params.iter().all(|p| {
240 let charset = p.name.eq_ignore_ascii_case(b"charset");
241 offer.params().iter().any(|o| {
242 o.name.eq_ignore_ascii_case(p.name) && p.value.same_value(&o.value, charset)
243 })
244 })
245 }
246
247 #[must_use]
250 pub const fn specificity(&self) -> u8 {
251 if matches!(self.type_, [b'*']) {
252 1
253 } else if matches!(self.subtype, [b'*']) {
254 2
255 } else if self.params.is_empty() {
256 3
257 } else {
258 4
259 }
260 }
261
262 #[must_use]
264 pub const fn weight(&self) -> QValue {
265 self.weight
266 }
267
268 #[must_use]
270 pub const fn params(&self) -> Params<'a> {
271 self.params
272 }
273}
274
275pub fn write(
299 type_: &str,
300 subtype: &str,
301 params: &[(&str, &str)],
302 out: &mut impl Sink,
303) -> core::result::Result<(), WriteError> {
304 let (t, s) = (type_.as_bytes(), subtype.as_bytes());
305 if !is_token(t) || !is_token(s) || t == b"*" || s == b"*" {
306 return Err(WriteError::NotToken);
307 }
308 unique_names(params)?;
309 let mut len = t.len().saturating_add(1).saturating_add(s.len());
310 for (name, value) in params {
311 if !is_token(name.as_bytes()) {
312 return Err(WriteError::NotToken);
313 }
314 let v = word_len(value.as_bytes())?;
315 len = len
316 .saturating_add(3)
317 .saturating_add(name.len())
318 .saturating_add(v);
319 }
320 reserve(out, len)?;
321 out.put(t)?;
322 out.put(b"/")?;
323 out.put(s)?;
324 for (name, value) in params {
325 out.put(b"; ")?;
326 out.put(name.as_bytes())?;
327 out.put(b"=")?;
328 write_word(value.as_bytes(), out)?;
329 }
330 Ok(())
331}
332
333#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
344pub struct MediaType<'a> {
345 type_: &'a [u8],
346 subtype: &'a [u8],
347 params: Params<'a>,
348}
349
350impl<'a> MediaType<'a> {
351 #[must_use]
353 pub const fn type_(&self) -> &'a [u8] {
354 self.type_
355 }
356
357 #[must_use]
359 pub const fn subtype(&self) -> &'a [u8] {
360 self.subtype
361 }
362
363 #[must_use]
366 pub fn suffix(&self) -> Option<&'a [u8]> {
367 let plus = self.subtype.iter().rposition(|&b| b == b'+')?;
368 self.subtype.get(plus + 1..).filter(|s| !s.is_empty())
369 }
370
371 #[must_use]
373 pub fn essence_is(&self, essence: &str) -> bool {
374 match essence.split_once('/') {
375 Some((t, s)) => self.is(t, s),
376 None => false,
377 }
378 }
379
380 #[must_use]
383 pub fn is(&self, type_: &str, subtype: &str) -> bool {
384 self.type_.eq_ignore_ascii_case(type_.as_bytes())
385 && self.subtype.eq_ignore_ascii_case(subtype.as_bytes())
386 }
387
388 #[must_use]
390 pub const fn params(&self) -> Params<'a> {
391 self.params
392 }
393
394 #[must_use]
396 pub fn param(&self, name: &str) -> Option<Word<'a>> {
397 self.params.get(name)
398 }
399
400 #[must_use]
402 pub fn charset(&self) -> Option<Word<'a>> {
403 self.params.get("charset")
404 }
405
406 pub fn boundary(&self) -> Result<Option<Boundary<'a>>> {
414 let Some(word) = self.params.get("boundary") else {
415 return Ok(None);
416 };
417 let bytes = match word {
418 Word::Token(t) => t,
419 Word::Quoted(q) if q.has_escapes() => {
421 let at = q.raw().iter().position(|&b| b == b'\\').unwrap_or(0);
422 return err(ErrorKind::InvalidBoundary, at);
423 }
424 Word::Quoted(q) => q.raw(),
425 };
426 if bytes.is_empty() {
427 return err(ErrorKind::InvalidBoundary, 0);
428 }
429 if let Some(at) = bytes
430 .iter()
431 .position(|&b| !(class::is_bchar_nospace(b) || b == b' '))
432 {
433 return err(ErrorKind::InvalidBoundary, at);
434 }
435 if bytes.len() > 70 {
436 return err(ErrorKind::InvalidBoundary, 70);
437 }
438 if bytes.last() == Some(&b' ') {
439 return err(ErrorKind::InvalidBoundary, bytes.len() - 1);
440 }
441 Ok(Some(Boundary(bytes)))
442 }
443
444 #[must_use]
446 pub fn is_json(&self) -> bool {
447 self.is("application", "json")
448 || self
449 .suffix()
450 .is_some_and(|s| s.eq_ignore_ascii_case(b"json"))
451 }
452
453 #[must_use]
455 pub fn is_form_urlencoded(&self) -> bool {
456 self.is("application", "x-www-form-urlencoded")
457 }
458
459 #[must_use]
461 pub fn is_multipart_form_data(&self) -> bool {
462 self.is("multipart", "form-data")
463 }
464
465 pub fn write_canonical(&self, out: &mut impl Sink) -> core::result::Result<(), WriteError> {
486 let mut len = self.type_.len() + 1 + self.subtype.len();
487 for p in self.params.iter() {
488 len = len.saturating_add(3 + p.name.len() + raw_word_len(&p.value));
489 }
490 reserve(out, len)?;
491 put_lower(self.type_, out)?;
492 out.put(b"/")?;
493 put_lower(self.subtype, out)?;
494 for p in self.params.iter() {
495 out.put(b"; ")?;
496 put_lower(p.name, out)?;
497 out.put(b"=")?;
498 match p.value {
499 Word::Token(t) => out.put(t)?,
500 Word::Quoted(q) => {
501 out.put(b"\"")?;
502 out.put(q.raw())?;
503 out.put(b"\"")?;
504 }
505 }
506 }
507 Ok(())
508 }
509}
510
511fn raw_word_len(w: &Word<'_>) -> usize {
513 match w {
514 Word::Token(t) => t.len(),
515 Word::Quoted(q) => q.raw().len() + 2,
516 }
517}
518
519fn put_lower(s: &[u8], out: &mut impl Sink) -> core::result::Result<(), WriteError> {
521 let mut buf = [0u8; 64];
522 for chunk in s.chunks(buf.len()) {
523 let dst = buf.get_mut(..chunk.len()).unwrap_or(&mut []);
524 for (d, &c) in dst.iter_mut().zip(chunk) {
525 *d = c.to_ascii_lowercase();
526 }
527 out.put(dst)?;
528 }
529 Ok(())
530}
531
532#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
542pub struct Boundary<'a>(&'a [u8]);
543
544impl<'a> Boundary<'a> {
545 #[must_use]
547 pub const fn as_bytes(&self) -> &'a [u8] {
548 self.0
549 }
550}
551
552#[cfg(test)]
553mod tests {
554 use std::{vec, vec::Vec};
555
556 use super::*;
557 use crate::write::SliceSink;
558
559 fn mt(s: &[u8]) -> Result<MediaType<'_>> {
560 parse_media_type(s)
561 }
562
563 #[test]
564 fn test_media_type_edge_cases() {
565 assert!(mt(b"text/html").unwrap().is("text", "html"));
566 assert!(mt(b"TEXT/HTML").unwrap().essence_is("text/html"));
567 assert!(mt(b"text/html;").unwrap().params().is_empty());
568 assert!(mt(b"text/html ;charset=utf-8").unwrap().charset().is_some());
569 assert!(mt(b" text/html \t").is_ok());
570 let e = mt(b"text/ html").unwrap_err();
571 assert_eq!((e.kind(), e.offset()), (ErrorKind::InvalidMediaType, 5));
572 let e = mt(b"text /html").unwrap_err();
573 assert_eq!((e.kind(), e.offset()), (ErrorKind::InvalidMediaType, 4));
574 assert_eq!(
575 mt(b"text/").unwrap_err().kind(),
576 ErrorKind::InvalidMediaType
577 );
578 assert_eq!(
579 mt(b"/html").unwrap_err().kind(),
580 ErrorKind::InvalidMediaType
581 );
582 assert_eq!(mt(b"").unwrap_err().kind(), ErrorKind::InvalidMediaType);
583 assert_eq!(mt(b"text").unwrap_err().kind(), ErrorKind::InvalidMediaType);
584 assert_eq!(mt(b"*/*").unwrap_err().kind(), ErrorKind::Wildcard);
585 let e = mt(b"text/*").unwrap_err();
586 assert_eq!((e.kind(), e.offset()), (ErrorKind::Wildcard, 5));
587 assert_eq!(
588 mt(b"text/html x").unwrap_err().kind(),
589 ErrorKind::InvalidMediaType
590 );
591 assert_eq!(mt(b"text/html\r").unwrap_err().kind(), ErrorKind::BareCr);
592 assert_eq!(
593 mt(b"text/html;a=b\nc").unwrap_err().kind(),
594 ErrorKind::LineFeed
595 );
596 let e = mt(b"text/html; charset=a; Charset=b").unwrap_err();
597 assert_eq!((e.kind(), e.offset()), (ErrorKind::DuplicateParameter, 22));
598 }
599
600 #[test]
601 fn test_media_type_suffix_and_predicates() {
602 let v = mt(b"application/vnd.api+json").unwrap();
603 assert_eq!(v.suffix(), Some(&b"json"[..]));
604 assert!(v.is_json());
605 assert!(mt(b"application/json").unwrap().is_json());
606 assert!(!mt(b"application/jsonx").unwrap().is_json());
607 assert_eq!(mt(b"application/a+").unwrap().suffix(), None);
608 assert!(
609 mt(b"application/x-www-form-urlencoded")
610 .unwrap()
611 .is_form_urlencoded()
612 );
613 assert!(!mt(b"text/html").unwrap().essence_is("texthtml"));
614 }
615
616 fn boundary(s: &[u8]) -> Result<Option<Vec<u8>>> {
617 Ok(mt(s)?.boundary()?.map(|b| b.as_bytes().to_vec()))
618 }
619
620 #[test]
621 fn test_boundary_rfc2046_rules() {
622 assert_eq!(boundary(b"multipart/form-data").unwrap(), None);
623 assert_eq!(
624 boundary(b"multipart/form-data; boundary=")
625 .unwrap_err()
626 .kind(),
627 ErrorKind::MissingValue
628 );
629 assert_eq!(
630 boundary(b"multipart/form-data; boundary=\"\"")
631 .unwrap_err()
632 .kind(),
633 ErrorKind::InvalidBoundary
634 );
635 let seventy = vec![b'a'; 70];
636 let mut v = b"multipart/mixed; boundary=".to_vec();
637 v.extend_from_slice(&seventy);
638 assert_eq!(boundary(&v).unwrap(), Some(seventy));
639 v.push(b'a');
640 assert_eq!(boundary(&v).unwrap_err().kind(), ErrorKind::InvalidBoundary);
641 assert_eq!(
642 boundary(b"multipart/mixed; boundary=\"ab \"")
643 .unwrap_err()
644 .kind(),
645 ErrorKind::InvalidBoundary
646 );
647 assert_eq!(
648 boundary(b"multipart/mixed; boundary=\"a b\"").unwrap(),
649 Some(b"a b".to_vec())
650 );
651 assert_eq!(
652 boundary(b"multipart/mixed; boundary=\"a\\\\b\"")
653 .unwrap_err()
654 .kind(),
655 ErrorKind::InvalidBoundary
656 );
657 assert_eq!(
658 boundary(b"multipart/mixed; boundary=a*b")
659 .unwrap_err()
660 .kind(),
661 ErrorKind::InvalidBoundary
662 );
663 assert_eq!(
664 mt(b"multipart/mixed; boundary=a; boundary=b")
665 .unwrap_err()
666 .kind(),
667 ErrorKind::DuplicateParameter
668 );
669 }
670
671 #[test]
672 fn test_media_write_refuses_bad_names_and_round_trips() {
673 let mut buf = [0u8; 128];
674 let mut out = SliceSink::new(&mut buf);
675 assert_eq!(write("text", "*", &[], &mut out), Err(WriteError::NotToken));
676 assert_eq!(
677 write("te xt", "a", &[], &mut out),
678 Err(WriteError::NotToken)
679 );
680 assert_eq!(
681 write("a", "b", &[("x y", "1")], &mut out),
682 Err(WriteError::NotToken)
683 );
684 assert_eq!(
685 write("a", "b", &[("x", "1\r\n")], &mut out),
686 Err(WriteError::Forbidden { at: 1 })
687 );
688 assert_eq!(
689 write("a", "b", &[("x", "1"), ("X", "2")], &mut out),
690 Err(WriteError::DuplicateName)
691 );
692 assert!(out.is_empty());
693 write("a", "b", &[("x", "1 2"), ("y", "")], &mut out).unwrap();
694 let back = mt(out.written()).unwrap();
695 assert!(back.param("x").unwrap().eq_bytes(b"1 2"));
696 assert!(back.param("y").unwrap().is_empty());
697 }
698
699 #[test]
700 fn test_write_canonical_lowercases_names_only() {
701 let v = mt(b"TEXT/Plain; FORMAT=Flowed;x=\"A\\\"B\"").unwrap();
702 let mut buf = [0u8; 64];
703 let mut out = SliceSink::new(&mut buf);
704 v.write_canonical(&mut out).unwrap();
705 assert_eq!(out.written(), b"text/plain; format=Flowed; x=\"A\\\"B\"");
706 let mut small = [0u8; 4];
707 let mut out = SliceSink::new(&mut small);
708 assert_eq!(v.write_canonical(&mut out), Err(WriteError::Full));
709 assert!(out.is_empty());
710 }
711}