rmime/
lib.rs

1//! Mime Type crate
2//!
3//! This crate contains utils to work with MIME types.
4//!
5//! # Example
6//! ```
7//! use rmime::Mime;
8//!
9//! let mime = Mime::from_filename("my_video.mp4").unwrap();
10//! assert_eq!(mime.to_string(), "video/mp4");
11//! ```
12
13use core::str;
14use std::{borrow::Cow, fmt::Display, path::Path};
15
16/// Mime Type struct
17///
18/// This struct represents a Mime type.
19/// It contains a major and a minor type.
20#[derive(Debug)]
21pub struct Mime<'a>(Cow<'a, str>, Cow<'a, str>);
22
23type Result<T> = std::result::Result<T, &'static str>;
24
25impl<'a> Mime<'a> {
26    /// Create a MIME type from a given string.
27    ///
28    /// It can either be a reference or an owned String
29    ///
30    /// If the MIME type is built from a reference, it
31    /// can't outlive the reference.
32    ///
33    /// # Example
34    /// This piece of code fails to compile, since the mime
35    /// struct is used after the String it references is gone.
36    ///
37    /// ```compile_fail
38    /// use rmime::Mime;
39    ///
40    /// let mime: Mime;
41    /// {
42    ///     let string = "text/plain".to_string();
43    ///     mime = Mime::new(&string).unwrap();
44    /// }
45    /// mime.to_string();
46    /// ```
47    ///
48    /// You can fix this by using the [into_owned](Self::into_owned) method.
49    ///
50    /// ```
51    /// use rmime::Mime;
52    ///
53    /// let mime: Mime;
54    /// {
55    ///     let string = "text/plain".to_string();
56    ///     mime = Mime::new(&string).unwrap().into_owned();
57    /// }
58    /// mime.to_string();
59    /// ```
60    pub fn new(text: impl Into<Cow<'a, str>>) -> Result<Self> {
61        match text.into() {
62            Cow::Owned(own) => {
63                let mut text = own.split('/');
64                let major = text.next().ok_or("Malformatted mime type")?.to_owned();
65                let minor = text.next().ok_or("Malformatted mime type")?.to_owned();
66                Ok(Mime(major.into(), minor.into()))
67            }
68            Cow::Borrowed(borr) => {
69                let mut text = borr.split('/');
70                let major = text.next().ok_or("Malformatted mime type")?;
71                let minor = text.next().ok_or("Malformatted mime type")?;
72                Ok(Mime(major.into(), minor.into()))
73            }
74        }
75    }
76    /// Creates a MIME type from the given filename.
77    ///
78    /// # Example
79    /// ```
80    /// use rmime::Mime;
81    ///
82    /// let mime = Mime::from_filename("my_video.mp4").unwrap();
83    /// assert_eq!(mime.to_string(), "video/mp4");
84    ///
85    /// let mime = Mime::from_filename("index.html").unwrap();
86    /// assert_eq!(mime.to_string(), "text/html");
87    /// ```
88    pub fn from_filename(filename: &'a str) -> Result<Self> {
89        let ext = match Path::new(filename).extension() {
90            Some(ext) => ext.to_str().ok_or("Error convertion OsString to str")?,
91            None => "",
92        };
93        let major = match ext {
94            "abw" => "application/x-abiword",
95            "arc" => "application/x-freearc",
96            "avi" => "video/x-msvideo",
97            "azw" => "application/vnd.amazon.ebook",
98            "bin" => "application/octet-stream",
99            "bz" => "application/x-bzip",
100            "bz2" => "application/x-bzip2",
101            "cda" => "application/x-cdf",
102            "csh" => "application/x-csh",
103            "doc" => "application/msword",
104            "docx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
105            "eot" => "application/vnd.ms-fontobject",
106            "epub" => "application/epub+zip",
107            "gz" => "application/gzip",
108            "ico" => "image/vnd.microsoft.icon",
109            "ics" => "text/calendar",
110            "jar" => "application/java-archive",
111            "js" => "text/javascript",
112            "jsonld" => "application/ld+json",
113            "mid" | "midi" => "audio/x-midi",
114            "mjs" => "text/javascript",
115            "mp3" => "audio/mpeg",
116            "mpkg" => "application/vnd.apple.installer+xml",
117            "odp" => "application/vnd.oasis.opendocument.presentation",
118            "ods" => "application/vnd.oasis.opendocument.spreadsheet",
119            "odt" => "application/vnd.oasis.opendocument.text",
120            "oga" | "ogv" => "audio/ogg",
121            "ogx" => "application/ogg",
122            "php" => "application/x-httpd-php",
123            "ppt" => "application/vnd.ms-powerpoint",
124            "pptx" => "application/vnd.openxmlformats-officedocument.presentationml.presentation",
125            "rar" => "application/vnd.rar",
126            "sh" => "application/x-sh",
127            "sgv" => "image/svg+xml",
128            "tar" => "application/x-tar",
129            "tif" | "tiff" => "image/tiff",
130            "ts" => "video/mp2t",
131            "vsd" => "application/vnd.visio",
132            "weba" => "audio/webm",
133            "xhtml" => "application/xhtml+xml",
134            "xls" => "application/vnd.ms-excel",
135            "xlsx" => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
136            "xul" => "application/vnd.mozilla.xul+xml",
137            "3gp" => "video/3gpp",
138            "3g2" => "video/3gpp2",
139            "7z" => "application/x-7z-compressed",
140            "apng" | "avif" | "bmp" | "gif" | "jpeg" | "png" | "webp" => "image",
141            "jpg" => "image/jpeg",
142            "html" | "htm" | "css" | "csv" => "text",
143            "otf" | "ttf" | "woff" | "woff2" => "font",
144            "json" | "rtf" | "xml" | "zip" => "application",
145            "aac" | "opus" | "wav" | "flac" => "audio",
146            "mp4" | "mpeg" | "webm" | "mkv" => "video",
147            "" | "txt" => "text/plain",
148            _ => return Err("Unknown extension"),
149        };
150        Ok(if major.contains('/') {
151            Mime::new(major)?
152        } else {
153            Mime(major.into(), ext.into())
154        })
155    }
156    pub fn into_owned(self) -> Mime<'static> {
157        Mime(self.0.into_owned().into(), self.1.into_owned().into())
158    }
159    pub fn major(&self) -> &str {
160        &self.0
161    }
162    pub fn minor(&self) -> &str {
163        &self.1
164    }
165}
166
167impl Display for Mime<'_> {
168    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
169        write!(f, "{}/{}", self.major(), self.minor())
170    }
171}