mr_mime/
lib.rs

1//! Parser and handler for MIME types.
2//!
3//! This crate provides a type, [`Mime`], which represents a MIME type as defined in
4//! [RFC 2045](https://tools.ietf.org/html/rfc2045) and [RFC 2046](https://tools.ietf.org/html/rfc2046).
5//! The aim of this library is to provide strongly typed MIME types that are an overall improvement over
6//! just repesenting MIME types as strings.
7//!
8//! ## Example
9//!
10//! ```rust
11//! use mr_mime::{Mime, constants};
12//!
13//! // Parse a MIME type from a string.
14//! let my_type = Mime::parse("text/html; charset=utf-8").unwrap();
15//!
16//! // Get the "essence" of a MIME type.
17//! let essence = my_type.essence();
18//!
19//! // Compare it to a wide variety of constants.
20//! assert_eq!(essence, constants::TEXT_HTML);
21//! ```
22//!
23//! ## Features
24//!
25//! This crate has the following features:
26//!
27//! - `std`, enabled by default, which enables the standard library. This is used to implement
28//!   [`std::error::Error`] for [`ParseError`].
29//! - `alloc`, enabled by default, which enables the `alloc` crate. This is used to implement
30//!   hashing for MIME types. By default, the hashing algorithm tries to use stack space, but for
31//!   strings longer than 128 bytes this can lead to a panic. The `alloc` feature ameliorates this
32//!   by using the heap instead.
33
34#![no_std]
35#![forbid(
36    unsafe_code,
37    future_incompatible,
38    missing_copy_implementations,
39    missing_debug_implementations,
40    missing_docs
41)]
42// copied() only stabilized later on
43#![allow(clippy::map_clone)]
44
45#[cfg(feature = "alloc")]
46extern crate alloc;
47
48#[cfg(feature = "std")]
49extern crate std;
50
51#[rustfmt::skip]
52mod segments;
53pub use segments::constants;
54use segments::{SubtypeIntern, SuffixIntern, TypeIntern};
55
56use core::cell::Cell;
57use core::cmp;
58use core::convert::{TryFrom, TryInto};
59use core::fmt::{self, Write};
60use core::hash::{Hash, Hasher};
61use core::iter::FusedIterator;
62use core::str::from_utf8;
63use core::write;
64
65use memchr::{memchr, memchr2};
66
67macro_rules! matches {
68    ($expr: expr, $($pat:pat)|+) => {{
69        match ($expr) {
70            $($pat)|+ => true,
71            _ => false,
72        }
73    }}
74}
75
76/// MIME type parsing error.
77#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
78#[non_exhaustive]
79pub enum ParseError {
80    /// There is no slash in the type.
81    NoSlash,
82
83    /// The MIME type is missing the type.
84    MissingType,
85
86    /// The MIME type is missing the subtype.
87    MissingSubtype,
88
89    /// A string contains non-HTTP codepoints.
90    NonHttpCodepoints,
91}
92
93impl fmt::Display for ParseError {
94    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95        match self {
96            ParseError::NoSlash => write!(f, "no slash in MIME type"),
97            ParseError::MissingType => write!(f, "missing MIME type"),
98            ParseError::MissingSubtype => write!(f, "missing MIME subtype"),
99            ParseError::NonHttpCodepoints => write!(f, "MIME type contains non-HTTP codepoints"),
100        }
101    }
102}
103
104#[cfg(feature = "std")]
105impl std::error::Error for ParseError {}
106
107/// A MIME type.
108///
109/// See the [crate-level documentation](../index.html) for more information.
110#[derive(Clone, Copy)]
111pub struct Mime<'a> {
112    /// The MIME type catagory.
113    ty: Type<'a>,
114
115    /// The MIME subtype.
116    subtype: Subtype<'a>,
117
118    /// The MIME subtype's suffix.
119    suffix: Option<Suffix<'a>>,
120
121    /// The MIME parameters.
122    parameters: Parameters<'a>,
123}
124
125impl<'a> fmt::Display for Mime<'a> {
126    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127        write!(f, "{}/{}", self.r#type(), self.subtype())?;
128
129        if let Some(suffix) = self.suffix() {
130            write!(f, "+{}", suffix)?;
131        }
132
133        for (key, value) in self.parameters() {
134            write!(f, ";{}={}", key, FormatQuotedString(value))?;
135        }
136
137        Ok(())
138    }
139}
140
141impl<'a> fmt::Debug for Mime<'a> {
142    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143        struct Parameter<'a>(&'a str, &'a [u8]);
144
145        impl<'a> fmt::Debug for Parameter<'a> {
146            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147                f.debug_struct("Parameter")
148                    .field("key", &self.0)
149                    .field("value", &FormatQuotedString(self.1))
150                    .finish()
151            }
152        }
153
154        struct Parameters<I>(Cell<Option<I>>);
155
156        impl<'a, 'b, I: Iterator<Item = (&'a str, &'b [u8])>> fmt::Debug for Parameters<I> {
157            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158                let iter = self.0.take().unwrap();
159                f.debug_list()
160                    .entries(iter.map(|(k, v)| Parameter(k, v)))
161                    .finish()
162            }
163        }
164
165        f.debug_struct("Mime")
166            .field("type", &self.r#type())
167            .field("subtype", &self.subtype())
168            .field("suffix", &self.suffix())
169            .field(
170                "parameters",
171                &Parameters(Cell::new(Some(self.parameters()))),
172            )
173            .finish()
174    }
175}
176
177impl<'a> Mime<'a> {
178    /// Create a new MIME type from its component parts.
179    ///
180    /// ## Example
181    ///
182    /// ```rust
183    /// use mr_mime::{Mime, constants};
184    ///
185    /// let my_type = Mime::new(constants::types::TEXT, constants::subtypes::PLAIN, None, &[]);
186    /// assert_eq!(my_type, constants::TEXT_PLAIN);
187    /// ```
188    pub fn new(
189        ty: Type<'a>,
190        subtype: Subtype<'a>,
191        suffix: Option<Suffix<'a>>,
192        parameters: &'a [(&'a str, &'a [u8])],
193    ) -> Self {
194        Self {
195            ty,
196            subtype,
197            suffix,
198            parameters: Parameters::Slice(parameters),
199        }
200    }
201
202    /// Create a new MIME type parsed from a string of bytes.
203    ///
204    /// ## Example
205    ///
206    /// ```rust
207    /// use mr_mime::{Mime, constants};
208    ///
209    /// let my_type = Mime::parse_bytes(b"text/plain").unwrap();
210    /// assert_eq!(my_type, constants::TEXT_PLAIN);
211    /// ```
212    pub fn parse_bytes(source: &'a [u8]) -> Result<Self, ParseError> {
213        let slash = memchr(b'/', source).ok_or(ParseError::NoSlash)?;
214        let plus = memchr(b'+', source);
215        let semicolon = memchr(b';', source);
216
217        // Ensure we don't have an empty item.
218        let subtype_end = plus.or(semicolon).unwrap_or(source.len());
219        if slash == 0 {
220            return Err(ParseError::MissingType);
221        } else if slash == subtype_end - 1 {
222            return Err(ParseError::MissingSubtype);
223        }
224
225        // Parse the type.
226        let ty = &source[..slash];
227        let ty = Type::from_bytes(trim_start(ty)).ok_or(ParseError::NonHttpCodepoints)?;
228
229        // Parse the subtype.
230        let subtype = &source[slash + 1..subtype_end];
231        let subtype =
232            Subtype::from_bytes(trim_end(subtype)).ok_or(ParseError::NonHttpCodepoints)?;
233
234        // Parse the suffix
235        let suffix = plus
236            .map(|plus| {
237                let suffix = &source[plus + 1..semicolon.unwrap_or(source.len())];
238                Suffix::from_bytes(trim_end(suffix)).ok_or(ParseError::NonHttpCodepoints)
239            })
240            .transpose()?;
241
242        // Delay parsing parameters until asked for.
243        let parameters = match semicolon {
244            None => Parameters::Slice(&[]),
245            Some(semicolon) => {
246                // Verify that the parameters are valid by parsing them.
247                let buffer = &source[semicolon + 1..];
248                for (key, value) in parameter_iter(buffer) {
249                    // Key should just be HTTP values.
250                    let key_valid = key.iter().all(|&b| is_http_codepoint(b));
251
252                    // Value can be HTTP values or quoted strings.
253                    let value_valid = if let Some(b'"') = value.first() {
254                        value.iter().all(|&b| is_http_quoted_codepoint(b))
255                    } else {
256                        value.iter().all(|&b| is_http_codepoint(b))
257                    };
258
259                    if !key_valid || !value_valid {
260                        return Err(ParseError::NonHttpCodepoints);
261                    }
262                }
263
264                Parameters::Buffer(buffer)
265            }
266        };
267
268        Ok(Self {
269            ty,
270            subtype,
271            suffix,
272            parameters,
273        })
274    }
275
276    /// Parse this MIME type from a string.
277    pub fn parse(source: &'a str) -> Result<Self, ParseError> {
278        Self::parse_bytes(source.as_bytes())
279    }
280
281    /// Get the type of this MIME type.
282    ///
283    /// ## Example
284    ///
285    /// ```rust
286    /// use mr_mime::constants;
287    ///
288    /// assert_eq!(constants::TEXT_PLAIN.r#type(), "text");
289    /// ```
290    pub fn r#type(&self) -> Type<'_> {
291        self.ty
292    }
293
294    /// Get the subtype of this MIME type.
295    ///
296    /// ## Example
297    ///
298    /// ```rust
299    /// use mr_mime::constants;
300    ///
301    /// assert_eq!(constants::TEXT_PLAIN.subtype(), "plain");
302    /// ```
303    pub fn subtype(&self) -> Subtype<'_> {
304        self.subtype
305    }
306
307    /// Get the suffix of this MIME type.
308    ///
309    /// ## Example
310    ///
311    /// ```rust
312    /// use mr_mime::constants;
313    ///
314    /// assert_eq!(constants::TEXT_PLAIN.suffix(), None);
315    /// assert_eq!(constants::IMAGE_SVG_XML.suffix().map(|s| s.into_str()), Some("xml"));
316    /// ```
317    pub fn suffix(&self) -> Option<Suffix<'_>> {
318        self.suffix
319    }
320
321    /// Iterate over the parameters of this MIME type.
322    ///
323    /// ## Example
324    ///
325    /// ```rust
326    /// use mr_mime::{Mime, constants};
327    ///
328    /// let mut ty = Mime::parse("text/plain; charset=utf-8").unwrap();
329    /// assert_eq!(ty.parameters().count(), 1);
330    /// assert_eq!(ty.parameters().next(), Some(("charset", b"utf-8".as_ref())));
331    /// ```
332    pub fn parameters(&self) -> impl Iterator<Item = (&str, &[u8])> {
333        match self.parameters {
334            Parameters::Slice(slice) => Either::Left(slice.iter().map(|&(k, v)| (k, v))),
335            Parameters::Buffer(buffer) => {
336                Either::Right(parameter_iter(buffer).map(|(key, value)| {
337                    // Key will always be valid because we parsed it.
338                    (from_utf8(key).unwrap(), value)
339                }))
340            }
341        }
342    }
343
344    /// Get the "essence" of this MIME type.
345    ///
346    /// The resulting MIME type only contains the type and the subtype, without the suffix or
347    /// the parameters.
348    ///
349    /// ## Example
350    ///
351    /// ```rust
352    /// use mr_mime::{Mime, constants};
353    ///
354    /// let my_type = Mime::parse("text/plain;charset=utf-8").unwrap();
355    /// assert_eq!(my_type.essence(), constants::TEXT_PLAIN);
356    /// ```
357    pub fn essence(&self) -> Mime<'a> {
358        Mime {
359            ty: self.ty,
360            subtype: self.subtype,
361            suffix: None,
362            parameters: Parameters::Slice(&[]),
363        }
364    }
365
366    /// Calculate the length of this MIME type.
367    ///
368    /// This returns the length for this given MIME type as if it had been formatted using its
369    /// Display trait. This length will thus include any suffix or parameters that it contains. See
370    /// essence() to get a slimmed down version of the MIME type.
371    pub fn len(&self) -> usize {
372        let suffix_length = match self.suffix() {
373            Some(s) => s.into_str().len() + 1,
374            _ => 0,
375        };
376
377        let param_length: usize = self
378            .parameters()
379            .map(|(k, v)| k.len() + FormatQuotedString(v).len() + 2)
380            .sum();
381
382        self.r#type().into_str().len()
383            + self.subtype().into_str().len()
384            + 1 // slash
385            + suffix_length
386            + param_length
387    }
388
389    /// Checks whether this MIME type is empty or not.
390    ///
391    /// This function always returns false as it is not possible to construct an empty MIME type.
392    /// It is only implemented to make clippy happy.
393    pub fn is_empty(&self) -> bool {
394        false
395    }
396}
397
398#[cfg(test)]
399mod mime_test {
400    use super::*;
401
402    #[test]
403    fn mime_len_handles_basic_type() {
404        assert_eq!(constants::TEXT_PLAIN.len(), "text/plain".len());
405    }
406
407    #[test]
408    fn mime_len_handles_suffix() {
409        assert_eq!(constants::IMAGE_SVG_XML.len(), "image/svg+xml".len());
410    }
411
412    #[test]
413    fn mime_len_handles_param() {
414        assert_eq!(
415            Mime::parse("text/html; charset=utf-8").unwrap().len(),
416            "text/html;charset=utf-8".len()
417        );
418    }
419
420    #[test]
421    fn mime_len_handles_multiple_params() {
422        assert_eq!(
423            Mime::parse("text/html; charset=utf-8; foo=bar")
424                .unwrap()
425                .len(),
426            "text/html;charset=utf-8;foo=bar".len()
427        );
428    }
429
430    #[test]
431    fn mime_len_handles_suffixes_and_params() {
432        assert_eq!(
433            Mime::parse("image/svg+xml; charset=utf-8; foo=bar")
434                .unwrap()
435                .len(),
436            "image/svg+xml;charset=utf-8;foo=bar".len()
437        );
438    }
439}
440
441impl Mime<'static> {
442    /// Guess the MIME type of a file by its extension.
443    ///
444    /// This library maintains a map of popular extensions to the MIME types that they may
445    /// represent. This function preforms a lookup into that list and returns an iterator
446    /// over the possible MIME types that the extension may represent.
447    ///
448    /// Remember that this function only inspects the extension, not the actual contents
449    /// of the file. Despite what a file's extension says, it may or may not be a valid
450    /// file of that type. For untrusted user input, you should always check the file's
451    /// contents to ensure that it is valid.
452    ///
453    /// ## Example
454    ///
455    /// ```rust
456    /// use mr_mime::{Mime, constants};
457    ///
458    /// assert_eq!(Mime::guess("html").next(), Some(constants::TEXT_HTML));
459    /// ```
460    pub fn guess(extension: &str) -> impl ExactSizeIterator<Item = Mime<'static>> + FusedIterator {
461        segments::guess_mime_type(extension)
462            .unwrap_or(&[])
463            .iter()
464            .map(|&c| c)
465    }
466}
467
468impl PartialEq<str> for Mime<'_> {
469    /// Compare a MIME type to a string.
470    ///
471    /// ## Example
472    ///
473    /// ```rust
474    /// use mr_mime::{Mime, constants};
475    ///
476    /// assert_eq!(constants::TEXT_PLAIN, "text/plain");
477    /// ```
478    fn eq(&self, mut other: &str) -> bool {
479        // See if the type names match.
480        let ty = self.r#type().into_str();
481        let ty_len = ty.len();
482
483        if !other.starts_with(ty) {
484            return false;
485        }
486
487        // Next char should be a slash.
488        if other.as_bytes()[ty_len] != b'/' {
489            return false;
490        }
491
492        // Next string should be the subtype.
493        other = &other[ty_len + 1..];
494        let subtype = self.subtype().into_str();
495        let subtype_len = subtype.len();
496
497        if !other.starts_with(subtype) {
498            return false;
499        }
500
501        // If we have a suffix, the next char is a plus.
502        if let Some(suffix) = self.suffix() {
503            let suffix = suffix.into_str();
504            if other.as_bytes()[subtype_len] != b'+' {
505                return false;
506            }
507
508            // Next string should be the suffix.
509            other = &other[subtype_len + 1..];
510            let suffix_len = suffix.len();
511
512            if !other.starts_with(suffix) {
513                return false;
514            }
515
516            other = &other[suffix_len..];
517        } else {
518            other = &other[subtype_len..];
519        }
520
521        // Now, compare for parameters.
522        for (key, value) in self.parameters() {
523            // The next char should be a semicolon.
524            if other.as_bytes()[0] != b';' {
525                return false;
526            }
527
528            // Next string should be the key.
529            other = &other[1..];
530            let key_len = key.len();
531
532            if !other.eq_ignore_ascii_case(key) {
533                return false;
534            }
535
536            // Next char should be an equals sign.
537            if other.as_bytes()[key_len] != b'=' {
538                return false;
539            }
540
541            // Next string should be the value.
542            other = &other[key_len + 1..];
543            let value_len = value.len();
544
545            if &other.as_bytes()[..value_len] != value {
546                return false;
547            }
548
549            // Advance the string up.
550            other = &other[value_len..];
551        }
552
553        true
554    }
555}
556
557impl<'a, 'b> PartialEq<&'a str> for Mime<'b> {
558    fn eq(&self, other: &&'a str) -> bool {
559        self.eq(*other)
560    }
561}
562
563impl<'a, 'b> PartialEq<Mime<'a>> for Mime<'b> {
564    fn eq(&self, other: &Mime<'a>) -> bool {
565        // All of these comparisons are case insensitive at worst and use interned values at best.
566        (self.r#type() == other.r#type())
567            .and_then(|| self.subtype() == other.subtype())
568            .and_then(|| self.suffix() == other.suffix())
569            .and_then(|| {
570                cmp_params_ignore_case(self.parameters(), other.parameters())
571                    == cmp::Ordering::Equal
572            })
573    }
574}
575
576impl<'a> Eq for Mime<'a> {}
577
578impl<'a, 'b> PartialOrd<Mime<'a>> for Mime<'b> {
579    fn partial_cmp(&self, other: &Mime<'a>) -> Option<cmp::Ordering> {
580        Some(self.cmp(other))
581    }
582}
583
584impl<'a> Ord for Mime<'a> {
585    fn cmp(&self, other: &Self) -> cmp::Ordering {
586        self.r#type()
587            .cmp(&other.r#type())
588            .and_then(|| self.subtype().cmp(&other.subtype()))
589            .and_then(|| self.suffix().cmp(&other.suffix()))
590            .and_then(|| cmp_params_ignore_case(self.parameters(), other.parameters()))
591    }
592}
593
594impl<'a> Hash for Mime<'a> {
595    fn hash<H: Hasher>(&self, state: &mut H) {
596        self.r#type().hash(state);
597        self.subtype().hash(state);
598        self.suffix().hash(state);
599        for (key, value) in self.parameters() {
600            hash_ignore_case(key, state);
601            value.hash(state);
602        }
603    }
604}
605
606/// Wrapper types for `Name<'a, T>`.
607macro_rules! name_wrappers {
608    (
609        $(
610            $(#[$outer:meta])*
611            $name: ident <'a> => Name<'a, $ty: ty>
612        ),* $(,)?
613    ) => {
614        $(
615            $(#[$outer])*
616            #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
617            pub struct $name <'a> ( Name<'a, $ty> );
618
619            impl<'a> $name<'a> {
620                /// Create a new name from a string.
621                pub fn new(s: &'a str) -> Option<Self> {
622                    Name::new(s).map($name)
623                }
624
625                /// Create a new name from a string of bytes.
626                pub fn from_bytes(s: &'a [u8]) -> Option<Self> {
627                    Name::from_bytes(s).map($name)
628                }
629
630                /// Get the string representation of this name.
631                pub fn into_str(self) -> &'a str {
632                    self.0.into_str()
633                }
634            }
635
636            impl fmt::Debug for $name <'_> {
637                fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
638                    f.debug_tuple(stringify!($name))
639                        .field(&self.0.into_str())
640                        .finish()
641                }
642            }
643
644            impl fmt::Display for $name <'_> {
645                fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
646                    f.write_str(self.0.into_str())
647                }
648            }
649
650            impl AsRef<str> for $name <'_> {
651                fn as_ref(&self) -> &str {
652                    self.0.into_str()
653                }
654            }
655
656            impl<'a> From<Name<'a, $ty>> for $name<'a> {
657                fn from(name: Name<'a, $ty>) -> Self {
658                    $name(name)
659                }
660            }
661
662            impl PartialEq<&str> for $name<'_> {
663                fn eq(&self, other: &&str) -> bool {
664                    self.0.into_str().eq_ignore_ascii_case(other)
665                }
666            }
667
668            impl<'a> TryFrom<&'a str> for $name<'a> {
669                type Error = ParseError;
670
671                fn try_from(s: &'a str) -> Result<Self, Self::Error> {
672                    Self::new(s).ok_or(ParseError::NonHttpCodepoints)
673                }
674            }
675
676            impl<'a> TryFrom<&'a [u8]> for $name<'a> {
677                type Error = ParseError;
678
679                fn try_from(s: &'a [u8]) -> Result<Self, Self::Error> {
680                    Self::from_bytes(s).ok_or(ParseError::NonHttpCodepoints)
681                }
682            }
683        )*
684    }
685}
686
687name_wrappers! {
688    /// The type name of a MIME type.
689    Type<'a> => Name<'a, TypeIntern>,
690    /// The subtype name of a MIME type.
691    Subtype<'a> => Name<'a, SubtypeIntern>,
692    /// The suffix name of a MIME type.
693    Suffix<'a> => Name<'a, SuffixIntern>
694}
695
696/// Inner representation for the MIME parameters.
697#[derive(Clone, Copy)]
698enum Parameters<'a> {
699    /// Parameters are given by a slice.
700    Slice(&'a [(&'a str, &'a [u8])]),
701
702    /// Parameters are given by a buffer we need to parse on demand.
703    Buffer(&'a [u8]),
704}
705
706/// Either an interned string or a dynamic string.
707#[derive(Debug, Clone, Copy)]
708enum Name<'a, Intern> {
709    /// An interned string.
710    Interned(Intern),
711
712    /// A dynamic string.
713    Dynamic(&'a str),
714}
715
716impl<'a, T: Into<&'static str>> Name<'a, T> {
717    fn into_str(self) -> &'a str {
718        match self {
719            Name::Interned(interned) => interned.into(),
720            Name::Dynamic(dynamic) => dynamic,
721        }
722    }
723}
724
725impl<'a, T> From<T> for Name<'a, T> {
726    fn from(item: T) -> Self {
727        Name::Interned(item)
728    }
729}
730
731impl<'a, T: AsRef<str>> AsRef<str> for Name<'a, T> {
732    fn as_ref(&self) -> &str {
733        match self {
734            Name::Interned(interned) => interned.as_ref(),
735            Name::Dynamic(dynamic) => dynamic,
736        }
737    }
738}
739
740impl<'a, T: TryFrom<&'a [u8]>> Name<'a, T> {
741    fn from_bytes(name: &'a [u8]) -> Option<Self> {
742        match name.try_into() {
743            Ok(interned) => Some(Name::Interned(interned)),
744            Err(_) => {
745                // Ensure all bytes are valid HTTP codepoints.
746                if !name.iter().all(|&c| is_http_codepoint(c)) {
747                    return None;
748                }
749
750                // unwrap() here is OK because all HTTP codepoints implies ASCII.
751                Some(Name::Dynamic(from_utf8(name).unwrap()))
752            }
753        }
754    }
755
756    fn new(name: &'a str) -> Option<Self> {
757        Self::from_bytes(name.as_bytes())
758    }
759}
760
761impl<'a, T: AsRef<str> + PartialEq> PartialEq for Name<'a, T> {
762    fn eq(&self, other: &Self) -> bool {
763        match (self, other) {
764            (Name::Interned(this), Name::Interned(other)) => this == other,
765            (Name::Dynamic(s), Name::Interned(i)) | (Name::Interned(i), Name::Dynamic(s)) => {
766                s.eq_ignore_ascii_case(i.as_ref())
767            }
768            (Name::Dynamic(this), Name::Dynamic(other)) => this.eq_ignore_ascii_case(other),
769        }
770    }
771}
772
773impl<'a, T: AsRef<str> + Eq> Eq for Name<'a, T> {}
774
775// Comparisons between the interned versions are sound because they're sorted in alphabetical order.
776impl<'a, T: AsRef<str> + PartialOrd> PartialOrd for Name<'a, T> {
777    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
778        match (self, other) {
779            (Name::Interned(this), Name::Interned(other)) => this.partial_cmp(other),
780            (Name::Dynamic(s), Name::Interned(i)) | (Name::Interned(i), Name::Dynamic(s)) => {
781                Some(cmp_str_ignore_case(s, i.as_ref()))
782            }
783            (Name::Dynamic(this), Name::Dynamic(other)) => Some(cmp_str_ignore_case(this, other)),
784        }
785    }
786}
787
788impl<'a, T: AsRef<str> + Ord> Ord for Name<'a, T> {
789    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
790        match (self, other) {
791            (Name::Interned(this), Name::Interned(other)) => this.cmp(other),
792            (Name::Dynamic(s), Name::Interned(i)) | (Name::Interned(i), Name::Dynamic(s)) => {
793                cmp_str_ignore_case(s, i.as_ref())
794            }
795            (Name::Dynamic(this), Name::Dynamic(other)) => cmp_str_ignore_case(this, other),
796        }
797    }
798}
799
800impl<'a, T: AsRef<str> + Hash> Hash for Name<'a, T> {
801    fn hash<H: Hasher>(&self, state: &mut H) {
802        hash_ignore_case(self.as_ref(), state)
803    }
804}
805
806/// Get an iterator over the parameters of a MIME type.
807///
808/// Takes the semicolon-separated list of parameters as a slice of bytes.
809fn parameter_iter(bytes: &[u8]) -> impl Iterator<Item = (&[u8], &[u8])> {
810    ParameterIter { bytes }
811}
812
813struct ParameterIter<'a> {
814    /// The bytes to parse.
815    bytes: &'a [u8],
816}
817
818impl<'a> Iterator for ParameterIter<'a> {
819    type Item = (&'a [u8], &'a [u8]);
820
821    fn next(&mut self) -> Option<Self::Item> {
822        loop {
823            if self.bytes.is_empty() {
824                return None;
825            }
826
827            // Read to the next semicolon or equals sign.
828            let posn = memchr2(b';', b'=', self.bytes).unwrap_or(self.bytes.len());
829
830            // Read the parameter name.
831            let name = trim_start(&self.bytes[..posn]);
832            let symbol = self.bytes.get(posn);
833            self.bytes = self.bytes.get(posn + 1..).unwrap_or(&[]);
834
835            if let Some(b';') | None = symbol {
836                // No equals sign, so this is a flag parameter.
837
838                if name.is_empty() {
839                    // Empty parameter name, so skip it.
840                    continue;
841                }
842
843                return Some((name, &[]));
844            }
845
846            // Is this a quoted string?
847            if let Some(b'"') = self.bytes.first() {
848                let mut start = 1;
849                loop {
850                    // Read to the next quote or the next slash.
851                    let posn =
852                        memchr2(b'"', b'\\', &self.bytes[start..]).unwrap_or(self.bytes.len());
853
854                    // Read the parameter value.
855                    match self.bytes.get(posn) {
856                        Some(b'"') | None => {
857                            // We've reached the end of the quoted string.
858                            let value = &self.bytes[1..posn];
859                            self.bytes = self.bytes.get(posn + 1..).unwrap_or(&[]);
860                            return Some((name, value));
861                        }
862                        Some(b'\\') => {
863                            // We've reached a backslash, so skip the next character.
864                            start = posn + 2;
865                        }
866                        _ => unreachable!(),
867                    }
868                }
869            } else {
870                // This isn't a quoted string, just read to the next semicolon.
871                let posn = memchr(b';', self.bytes).unwrap_or(self.bytes.len());
872                let value = &self.bytes[..posn];
873                self.bytes = self.bytes.get(posn + 1..).unwrap_or(&[]);
874
875                return Some((name, value));
876            }
877        }
878    }
879}
880
881/// Order two strings, ignoring case.
882fn cmp_str_ignore_case(a: &str, b: &str) -> cmp::Ordering {
883    let common_len = cmp::min(a.len(), b.len());
884
885    // Get the common part of each string.
886    let a_part = &a[..common_len];
887    let b_part = &b[..common_len];
888
889    // Compare the common part.
890    for (ac, bc) in a_part.chars().zip(b_part.chars()) {
891        let ac = ac.to_ascii_lowercase();
892        let bc = bc.to_ascii_lowercase();
893
894        match ac.cmp(&bc) {
895            cmp::Ordering::Equal => continue,
896            other => return other,
897        }
898    }
899
900    // If the common part is equal, compare the lengths.
901    a.len().cmp(&b.len())
902}
903
904/// Compare two sets of parameters, ignoring case.
905fn cmp_params_ignore_case<'a, 'b, 'c, 'd>(
906    left: impl Iterator<Item = (&'a str, &'b [u8])>,
907    right: impl Iterator<Item = (&'c str, &'d [u8])>,
908) -> cmp::Ordering {
909    let mut left = left.fuse();
910    let mut right = right.fuse();
911
912    for (left, right) in left.by_ref().zip(right.by_ref()) {
913        match cmp_str_ignore_case(left.0, right.0) {
914            cmp::Ordering::Equal => {}
915            other => return other,
916        }
917
918        match left.1.cmp(right.1) {
919            cmp::Ordering::Equal => {}
920            other => return other,
921        }
922    }
923
924    if left.next().is_some() {
925        cmp::Ordering::Greater
926    } else if right.next().is_some() {
927        cmp::Ordering::Less
928    } else {
929        cmp::Ordering::Equal
930    }
931}
932
933/// Hash a string in such a way that it ignores case.
934fn hash_ignore_case(a: &str, state: &mut impl Hasher) {
935    #[cfg(feature = "alloc")]
936    use alloc::string::String;
937
938    // For our purposes, strings should never be longer than this.
939    const MAX_LEN: usize = 128;
940
941    // Convert the string to lowercase on the stack or the heap.
942    let mut stack_space = [0u8; MAX_LEN];
943    #[cfg(feature = "alloc")]
944    let mut heap_space;
945
946    let copied_str = if a.len() > MAX_LEN {
947        #[cfg(not(feature = "alloc"))]
948        panic!("MIME type string cannot be hashed longer than 128 characters");
949
950        #[cfg(feature = "alloc")]
951        {
952            heap_space = String::from(a);
953            &mut heap_space
954        }
955    } else {
956        stack_space[..a.len()].copy_from_slice(a.as_bytes());
957        core::str::from_utf8_mut(&mut stack_space[..a.len()]).unwrap()
958    };
959
960    copied_str.make_ascii_lowercase();
961
962    // Hash the lowercase string.
963    copied_str.hash(state);
964}
965
966/// Is this byte a valid HTTP codepoint?
967fn is_http_codepoint(b: u8) -> bool {
968    matches!(
969        b,
970        b'!'
971        | b'#'
972        | b'$'
973        | b'%'
974        | b'&'
975        | b'\''
976        | b'*'
977        | b'+'
978        | b'-'
979        | b'.'
980        | b'^'
981        | b'_'
982        | b'`'
983        | b'|'
984        | b'~'
985        | b'a'..=b'z'
986        | b'A'..=b'Z'
987        | b'0'..=b'9'
988    )
989}
990
991/// Is this byte HTTP whitespace?
992fn is_http_whitespace(b: u8) -> bool {
993    matches!(b, b' ' | b'\t' | b'\r' | b'\n')
994}
995
996/// Is this byte valid in an HTTP quoted string?
997fn is_http_quoted_codepoint(b: u8) -> bool {
998    matches!(b, b'\t' | b' '..=b'~' | 0x80..=0xFF)
999}
1000
1001/// Trim the start of a byte stream of whitespace.
1002fn trim_start(mut s: &[u8]) -> &[u8] {
1003    while let Some((b, rest)) = s.split_first() {
1004        if !is_http_whitespace(*b) {
1005            break;
1006        }
1007
1008        s = rest;
1009    }
1010
1011    s
1012}
1013
1014/// Trim the end of a byte stream of whitespace.
1015fn trim_end(mut s: &[u8]) -> &[u8] {
1016    while let Some((b, rest)) = s.split_last() {
1017        if !is_http_whitespace(*b) {
1018            break;
1019        }
1020
1021        s = rest;
1022    }
1023
1024    s
1025}
1026
1027/// Monad for making comparisons slightly easier.
1028trait Comparison: Sized {
1029    /// Take these two comparisons together.
1030    fn and_then(self, other: impl FnOnce() -> Self) -> Self;
1031}
1032
1033impl Comparison for bool {
1034    fn and_then(self, other: impl FnOnce() -> Self) -> Self {
1035        match self {
1036            true => other(),
1037            false => false,
1038        }
1039    }
1040}
1041
1042impl Comparison for Option<cmp::Ordering> {
1043    fn and_then(self, other: impl FnOnce() -> Self) -> Self {
1044        match self {
1045            Some(cmp::Ordering::Greater) => other(),
1046            this => this,
1047        }
1048    }
1049}
1050
1051impl Comparison for cmp::Ordering {
1052    fn and_then(self, other: impl FnOnce() -> Self) -> Self {
1053        if let cmp::Ordering::Equal = self {
1054            other()
1055        } else {
1056            self
1057        }
1058    }
1059}
1060
1061/// Error for generated code to use for unmatched names.
1062#[derive(Debug, PartialEq, Eq, Clone, Copy)]
1063struct InvalidName;
1064
1065#[derive(Debug)]
1066enum Either<A, B> {
1067    Left(A),
1068    Right(B),
1069}
1070
1071impl<A, B> Iterator for Either<A, B>
1072where
1073    A: Iterator,
1074    B: Iterator<Item = A::Item>,
1075{
1076    type Item = A::Item;
1077
1078    fn next(&mut self) -> Option<Self::Item> {
1079        match self {
1080            Either::Left(a) => a.next(),
1081            Either::Right(b) => b.next(),
1082        }
1083    }
1084
1085    fn size_hint(&self) -> (usize, Option<usize>) {
1086        match self {
1087            Either::Left(a) => a.size_hint(),
1088            Either::Right(b) => b.size_hint(),
1089        }
1090    }
1091
1092    fn fold<Closure, F>(self, init: Closure, f: F) -> Closure
1093    where
1094        Self: Sized,
1095        F: FnMut(Closure, Self::Item) -> Closure,
1096    {
1097        match self {
1098            Either::Left(a) => a.fold(init, f),
1099            Either::Right(b) => b.fold(init, f),
1100        }
1101    }
1102
1103    fn nth(&mut self, n: usize) -> Option<Self::Item> {
1104        match self {
1105            Either::Left(a) => a.nth(n),
1106            Either::Right(b) => b.nth(n),
1107        }
1108    }
1109
1110    fn last(self) -> Option<Self::Item>
1111    where
1112        Self: Sized,
1113    {
1114        match self {
1115            Either::Left(a) => a.last(),
1116            Either::Right(b) => b.last(),
1117        }
1118    }
1119}
1120
1121impl<A, B> FusedIterator for Either<A, B>
1122where
1123    A: FusedIterator,
1124    B: FusedIterator<Item = A::Item>,
1125{
1126}
1127
1128impl<A, B> ExactSizeIterator for Either<A, B>
1129where
1130    A: ExactSizeIterator,
1131    B: ExactSizeIterator<Item = A::Item>,
1132{
1133}
1134
1135impl<A, B> DoubleEndedIterator for Either<A, B>
1136where
1137    A: DoubleEndedIterator,
1138    B: DoubleEndedIterator<Item = A::Item>,
1139{
1140    fn next_back(&mut self) -> Option<Self::Item> {
1141        match self {
1142            Either::Left(a) => a.next_back(),
1143            Either::Right(b) => b.next_back(),
1144        }
1145    }
1146
1147    fn rfold<Closure, F>(self, init: Closure, f: F) -> Closure
1148    where
1149        Self: Sized,
1150        F: FnMut(Closure, Self::Item) -> Closure,
1151    {
1152        match self {
1153            Either::Left(a) => a.rfold(init, f),
1154            Either::Right(b) => b.rfold(init, f),
1155        }
1156    }
1157
1158    fn nth_back(&mut self, n: usize) -> Option<Self::Item> {
1159        match self {
1160            Either::Left(a) => a.nth_back(n),
1161            Either::Right(b) => b.nth_back(n),
1162        }
1163    }
1164}
1165
1166/// Invariant: `0` is either:
1167///
1168/// - An ASCII string.
1169/// - An HTTP quoted string.
1170struct FormatQuotedString<'a>(&'a [u8]);
1171
1172impl<'a> FormatQuotedString<'a> {
1173    pub fn len(&self) -> usize {
1174        self.0
1175            .iter()
1176            .flat_map(|&c| core::ascii::escape_default(c))
1177            .count()
1178    }
1179}
1180
1181impl<'a> fmt::Display for FormatQuotedString<'a> {
1182    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1183        for ch in self.0.iter().flat_map(|&c| core::ascii::escape_default(c)) {
1184            f.write_char(ch as char)?;
1185        }
1186
1187        Ok(())
1188    }
1189}
1190
1191impl<'a> fmt::Debug for FormatQuotedString<'a> {
1192    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1193        fmt::Display::fmt(self, f)
1194    }
1195}
1196
1197#[cfg(test)]
1198mod fqs_test {
1199    use super::*;
1200
1201    #[test]
1202    fn fqs_len_handles_empty_array() {
1203        assert_eq!(FormatQuotedString(&[]).len(), 0);
1204    }
1205
1206    #[test]
1207    fn fqs_len_handles_quoted_string() {
1208        let input =
1209            b"this%20is%20http%20encoded%E2%80%A6%20or%20is%20it%3F%20%C5%B6%C4%99%C5%A1%20it%20is";
1210        assert_eq!(FormatQuotedString(input).len(), 84);
1211    }
1212
1213    #[test]
1214    fn fqs_len_handles_standard_ascii() {
1215        let input = b"this is not encoded or special at all";
1216        assert_eq!(FormatQuotedString(input).len(), 37);
1217    }
1218
1219    #[test]
1220    fn fqs_len_handles_utf8() {
1221        let input = b"\xC5\xB6'\"\\";
1222        assert_eq!(FormatQuotedString(input).len(), 14);
1223    }
1224}