http_types/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;
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,
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            "html" => Some(HTML),
57            "js" | "mjs" | "jsonp" => Some(JAVASCRIPT),
58            "json" => Some(JSON),
59            "css" => Some(CSS),
60            "svg" => Some(SVG),
61            "xml" => Some(XML),
62            _ => None,
63        }
64    }
65
66    /// Access the Mime's `type` value.
67    ///
68    /// According to the spec this method should be named `type`, but that's a reserved keyword in
69    /// Rust so hence prefix with `base` instead.
70    pub fn basetype(&self) -> &str {
71        &self.basetype
72    }
73
74    /// Access the Mime's `subtype` value.
75    pub fn subtype(&self) -> &str {
76        &self.subtype
77    }
78
79    /// Access the Mime's `essence` value.
80    pub fn essence(&self) -> &str {
81        &self.essence
82    }
83
84    /// Get a reference to a param.
85    pub fn param(&self, name: impl Into<ParamName>) -> Option<&ParamValue> {
86        let name: ParamName = name.into();
87        if name.as_str() == "charset" && self.is_utf8 {
88            return Some(&ParamValue(Cow::Borrowed("utf-8")));
89        }
90
91        self.params.iter().find(|(k, _)| k == &name).map(|(_, v)| v)
92    }
93
94    /// Remove a param from the set. Returns the `ParamValue` if it was contained within the set.
95    pub fn remove_param(&mut self, name: impl Into<ParamName>) -> Option<ParamValue> {
96        let name: ParamName = name.into();
97        if name.as_str() == "charset" && self.is_utf8 {
98            self.is_utf8 = false;
99            return Some(ParamValue(Cow::Borrowed("utf-8")));
100        }
101        self.params
102            .iter()
103            .position(|(k, _)| k == &name)
104            .map(|pos| self.params.remove(pos).1)
105    }
106}
107
108impl Display for Mime {
109    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110        parse::format(self, f)
111    }
112}
113
114// impl Debug for Mime {
115//     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116//         Debug::fmt(&self.essence, f)
117//     }
118// }
119
120impl FromStr for Mime {
121    type Err = crate::Error;
122
123    /// Create a new `Mime`.
124    ///
125    /// Follows the [WHATWG MIME parsing algorithm](https://mimesniff.spec.whatwg.org/#parsing-a-mime-type).
126    fn from_str(s: &str) -> Result<Self, Self::Err> {
127        parse::parse(s)
128    }
129}
130
131impl<'a> From<&'a str> for Mime {
132    fn from(value: &'a str) -> Self {
133        Self::from_str(value).unwrap()
134    }
135}
136
137impl ToHeaderValues for Mime {
138    type Iter = option::IntoIter<HeaderValue>;
139
140    fn to_header_values(&self) -> crate::Result<Self::Iter> {
141        let mime = self.clone();
142        let header: HeaderValue = mime.into();
143
144        // A HeaderValue will always convert into itself.
145        Ok(header.to_header_values().unwrap())
146    }
147}
148
149/// A parameter name.
150#[derive(Debug, Clone, PartialEq, Eq, Hash)]
151pub struct ParamName(Cow<'static, str>);
152
153impl ParamName {
154    /// Get the name as a `&str`
155    pub fn as_str(&self) -> &str {
156        &self.0
157    }
158}
159
160impl Display for ParamName {
161    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
162        Display::fmt(&self.0, f)
163    }
164}
165
166impl FromStr for ParamName {
167    type Err = crate::Error;
168
169    /// Create a new `HeaderName`.
170    ///
171    /// This checks it's valid ASCII, and lowercases it.
172    fn from_str(s: &str) -> Result<Self, Self::Err> {
173        crate::ensure!(s.is_ascii(), "String slice should be valid ASCII");
174        Ok(ParamName(Cow::Owned(s.to_ascii_lowercase())))
175    }
176}
177
178impl<'a> From<&'a str> for ParamName {
179    fn from(value: &'a str) -> Self {
180        Self::from_str(value).unwrap()
181    }
182}
183
184/// A parameter value.
185#[derive(Debug, Clone, PartialEq, Eq, Hash)]
186pub struct ParamValue(Cow<'static, str>);
187
188impl ParamValue {
189    /// Get the value as a `&str`
190    pub fn as_str(&self) -> &str {
191        &self.0
192    }
193}
194
195impl Display for ParamValue {
196    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
197        Display::fmt(&self.0, f)
198    }
199}
200
201impl<'a> PartialEq<&'a str> for ParamValue {
202    fn eq(&self, other: &&'a str) -> bool {
203        &self.0 == other
204    }
205}
206
207impl PartialEq<str> for ParamValue {
208    fn eq(&self, other: &str) -> bool {
209        self.0 == other
210    }
211}