http_types_2/mime/
mod.rs

1//! IANA Media Types.
2//!
3//! [Read more](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types).
4
5mod constants;
6mod parse;
7
8pub use constants::*;
9
10use std::borrow::Cow;
11use std::fmt::{self, Debug, Display};
12use std::option;
13use std::str::FromStr;
14
15use crate::headers::{HeaderValue, ToHeaderValues};
16
17use infer::Infer;
18
19/// An IANA media type.
20///
21/// ```
22/// use http_types::mime::Mime;
23/// use std::str::FromStr;
24///
25/// let mime = Mime::from_str("text/html;charset=utf-8").unwrap();
26/// assert_eq!(mime.essence(), "text/html");
27/// assert_eq!(mime.param("charset").unwrap(), "utf-8");
28/// ```
29// NOTE: we cannot statically initialize Strings with values yet, so we keep dedicated static
30// fields for the static strings.
31#[derive(Clone, PartialEq, Eq, Debug)]
32pub struct Mime {
33    pub(crate) essence: Cow<'static, str>,
34    pub(crate) basetype: Cow<'static, str>,
35    pub(crate) subtype: Cow<'static, str>,
36    // NOTE(yosh): this is a hack because we can't populate vecs in const yet.
37    // This enables us to encode media types as utf-8 at compilation.
38    pub(crate) is_utf8: bool,
39    pub(crate) params: Vec<(ParamName, ParamValue)>,
40}
41
42impl Mime {
43    /// Sniff the mime type from a byte slice.
44    pub fn sniff(bytes: &[u8]) -> crate::Result<Self> {
45        let info = Infer::new();
46        let mime = match info.get(bytes) {
47            Some(info) => info.mime_type(),
48            None => crate::bail!("Could not sniff the mime type"),
49        };
50        Mime::from_str(mime)
51    }
52
53    /// Guess the mime type from a file extension
54    pub fn from_extension(extension: impl AsRef<str>) -> Option<Self> {
55        match extension.as_ref() {
56            "7z" => Some(SEVENZIP),
57            "atom" => Some(ATOM),
58            "avi" => Some(AVI),
59            "bin" | "exe" | "dll" | "iso" | "img" => Some(BYTE_STREAM),
60            "bmp" => Some(BMP),
61            "css" => Some(CSS),
62            "html" => Some(HTML),
63            "ico" => Some(ICO),
64            "js" | "mjs" | "jsonp" => Some(JAVASCRIPT),
65            "json" => Some(JSON),
66            "m4a" => Some(M4A),
67            "mid" | "midi" | "kar" => Some(MIDI),
68            "mp3" => Some(MP3),
69            "mp4" => Some(MP4),
70            "mpeg" | "mpg" => Some(MPEG),
71            "ogg" => Some(OGG),
72            "otf" => Some(OTF),
73            "rss" => Some(RSS),
74            "svg" | "svgz" => Some(SVG),
75            "ttf" => Some(TTF),
76            "txt" => Some(PLAIN),
77            "wasm" => Some(WASM),
78            "webm" => Some(WEBM),
79            "webp" => Some(WEBP),
80            "woff" => Some(WOFF),
81            "woff2" => Some(WOFF2),
82            "xml" => Some(XML),
83            "zip" => Some(ZIP),
84            _ => None,
85        }
86    }
87
88    /// Access the Mime's `type` value.
89    ///
90    /// According to the spec this method should be named `type`, but that's a reserved keyword in
91    /// Rust so hence prefix with `base` instead.
92    pub fn basetype(&self) -> &str {
93        &self.basetype
94    }
95
96    /// Access the Mime's `subtype` value.
97    pub fn subtype(&self) -> &str {
98        &self.subtype
99    }
100
101    /// Access the Mime's `essence` value.
102    pub fn essence(&self) -> &str {
103        &self.essence
104    }
105
106    /// Get a reference to a param.
107    pub fn param(&self, name: impl Into<ParamName>) -> Option<&ParamValue> {
108        let name: ParamName = name.into();
109        if name.as_str() == "charset" && self.is_utf8 {
110            return Some(&ParamValue(Cow::Borrowed("utf-8")));
111        }
112
113        self.params.iter().find(|(k, _)| k == &name).map(|(_, v)| v)
114    }
115
116    /// Remove a param from the set. Returns the `ParamValue` if it was contained within the set.
117    pub fn remove_param(&mut self, name: impl Into<ParamName>) -> Option<ParamValue> {
118        let name: ParamName = name.into();
119        if name.as_str() == "charset" && self.is_utf8 {
120            self.is_utf8 = false;
121            return Some(ParamValue(Cow::Borrowed("utf-8")));
122        }
123        self.params
124            .iter()
125            .position(|(k, _)| k == &name)
126            .map(|pos| self.params.remove(pos).1)
127    }
128
129    /// Check if this mime is a subtype of another mime.
130    ///
131    /// # Examples
132    ///
133    /// ```
134    /// // All mime types are subsets of */*
135    /// use http_types::mime::Mime;
136    /// use std::str::FromStr;
137    ///
138    /// assert!(Mime::from_str("text/css").unwrap().subset_eq(&Mime::from_str("*/*").unwrap()));
139    ///
140    /// // A mime type is subset of itself
141    /// assert!(Mime::from_str("text/css").unwrap().subset_eq(&Mime::from_str("text/css").unwrap()));
142    ///
143    /// // A mime type which is otherwise a subset with extra parameters is a subset of a mime type without those parameters
144    /// assert!(Mime::from_str("text/css;encoding=utf-8").unwrap().subset_eq(&Mime::from_str("text/css").unwrap()));
145    ///
146    /// // A mime type more general than another mime type is not a subset
147    /// assert!(!Mime::from_str("*/css;encoding=utf-8").unwrap().subset_eq(&Mime::from_str("text/css").unwrap()));
148    /// ```
149    pub fn subset_eq(&self, other: &Mime) -> bool {
150        if other.basetype() != "*" && self.basetype() != other.basetype() {
151            return false;
152        }
153        if other.subtype() != "*" && self.subtype() != other.subtype() {
154            return false;
155        }
156        for (name, value) in other.params.iter() {
157            if !self
158                .param(name.clone())
159                .map(|v| v == value)
160                .unwrap_or(false)
161            {
162                return false;
163            }
164        }
165        true
166    }
167}
168
169impl Display for Mime {
170    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
171        parse::format(self, f)
172    }
173}
174
175// impl Debug for Mime {
176//     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
177//         Debug::fmt(&self.essence, f)
178//     }
179// }
180
181impl FromStr for Mime {
182    type Err = crate::Error;
183
184    /// Create a new `Mime`.
185    ///
186    /// Follows the [WHATWG MIME parsing algorithm](https://mimesniff.spec.whatwg.org/#parsing-a-mime-type).
187    fn from_str(s: &str) -> Result<Self, Self::Err> {
188        parse::parse(s)
189    }
190}
191
192impl<'a> From<&'a str> for Mime {
193    fn from(value: &'a str) -> Self {
194        Self::from_str(value).unwrap()
195    }
196}
197
198impl ToHeaderValues for Mime {
199    type Iter = option::IntoIter<HeaderValue>;
200
201    fn to_header_values(&self) -> crate::Result<Self::Iter> {
202        let mime = self.clone();
203        let header: HeaderValue = mime.into();
204
205        // A HeaderValue will always convert into itself.
206        Ok(header.to_header_values().unwrap())
207    }
208}
209
210/// A parameter name.
211#[derive(Debug, Clone, PartialEq, Eq, Hash)]
212pub struct ParamName(Cow<'static, str>);
213
214impl ParamName {
215    /// Get the name as a `&str`
216    pub fn as_str(&self) -> &str {
217        &self.0
218    }
219}
220
221impl Display for ParamName {
222    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
223        Display::fmt(&self.0, f)
224    }
225}
226
227impl FromStr for ParamName {
228    type Err = crate::Error;
229
230    /// Create a new `HeaderName`.
231    ///
232    /// This checks it's valid ASCII, and lowercases it.
233    fn from_str(s: &str) -> Result<Self, Self::Err> {
234        crate::ensure!(s.is_ascii(), "String slice should be valid ASCII");
235        Ok(ParamName(Cow::Owned(s.to_ascii_lowercase())))
236    }
237}
238
239impl<'a> From<&'a str> for ParamName {
240    fn from(value: &'a str) -> Self {
241        Self::from_str(value).unwrap()
242    }
243}
244
245/// A parameter value.
246#[derive(Debug, Clone, PartialEq, Eq, Hash)]
247pub struct ParamValue(Cow<'static, str>);
248
249impl ParamValue {
250    /// Get the value as a `&str`
251    pub fn as_str(&self) -> &str {
252        &self.0
253    }
254}
255
256impl Display for ParamValue {
257    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
258        Display::fmt(&self.0, f)
259    }
260}
261
262impl<'a> PartialEq<&'a str> for ParamValue {
263    fn eq(&self, other: &&'a str) -> bool {
264        &self.0 == other
265    }
266}
267
268impl PartialEq<str> for ParamValue {
269    fn eq(&self, other: &str) -> bool {
270        self.0 == other
271    }
272}