1use std::collections::HashMap;
11use std::sync::LazyLock;
12
13static MIME_MAPPINGS: LazyLock<HashMap<&'static str, &'static str>> = LazyLock::new(|| {
19 let entries: &[(&str, &str)] = &[
20 ("elf", "application/x-executable"),
22 ("pe32", "application/vnd.microsoft.portable-executable"),
23 ("pe32+", "application/vnd.microsoft.portable-executable"),
24 ("mach-o", "application/x-mach-binary"),
25 ("msdos", "application/x-dosexec"),
26 ("zip", "application/zip"),
28 ("gzip", "application/gzip"),
29 ("tar", "application/x-tar"),
30 ("rar", "application/vnd.rar"),
31 ("7-zip", "application/x-7z-compressed"),
32 ("bzip2", "application/x-bzip2"),
33 ("xz", "application/x-xz"),
34 ("jpeg", "image/jpeg"),
36 ("png", "image/png"),
37 ("gif", "image/gif"),
38 ("bmp", "image/bmp"),
39 ("webp", "image/webp"),
40 ("tiff", "image/tiff"),
41 ("ico", "image/x-icon"),
42 ("svg", "image/svg+xml"),
43 ("pdf", "application/pdf"),
45 ("postscript", "application/postscript"),
46 ("mp3", "audio/mpeg"),
48 ("mpeg adts", "audio/mpeg"),
49 ("mpeg audio", "audio/mpeg"),
50 ("mp4", "video/mp4"),
51 ("avi", "video/x-msvideo"),
52 ("wav", "audio/wav"),
53 ("ogg", "audio/ogg"),
54 ("flac", "audio/flac"),
55 ("webm", "video/webm"),
56 ("html", "text/html"),
58 ("xml", "application/xml"),
59 ("json", "application/json"),
60 ("javascript", "text/javascript"),
61 ("css", "text/css"),
62 ("ascii", "text/plain"),
64 ("utf-8", "text/plain"),
65 ("text", "text/plain"),
66 ("microsoft word", "application/msword"),
68 ("microsoft excel", "application/vnd.ms-excel"),
69 ("microsoft powerpoint", "application/vnd.ms-powerpoint"),
70 ];
71
72 let mut map = HashMap::with_capacity(entries.len());
73 for &(k, v) in entries {
74 map.insert(k, v);
75 }
76 map
77});
78
79#[derive(Debug, Clone, Copy, Default)]
98pub struct MimeMapper {
99 _private: (),
100}
101
102impl MimeMapper {
103 #[must_use]
118 pub fn new() -> Self {
119 Self { _private: () }
120 }
121
122 #[must_use]
151 pub fn get_mime_type(&self, description: &str) -> Option<&'static str> {
152 let lower = description.to_lowercase();
153
154 let mut best_match: Option<(&'static str, &'static str)> = None;
157
158 for (keyword, mime_type) in MIME_MAPPINGS.iter() {
159 if lower.contains(*keyword) {
160 match best_match {
161 Some((best_keyword, _)) if keyword.len() > best_keyword.len() => {
162 best_match = Some((*keyword, *mime_type));
163 }
164 None => {
165 best_match = Some((*keyword, *mime_type));
166 }
167 _ => {}
168 }
169 }
170 }
171
172 best_match.map(|(_, mime_type)| mime_type)
173 }
174
175 #[must_use]
177 pub fn len(&self) -> usize {
178 MIME_MAPPINGS.len()
179 }
180
181 #[must_use]
183 pub fn is_empty(&self) -> bool {
184 MIME_MAPPINGS.is_empty()
185 }
186}
187
188#[cfg(test)]
189mod tests {
190 use super::*;
191
192 #[test]
193 fn test_new_mapper_has_mappings() {
194 let mapper = MimeMapper::new();
195 assert!(!mapper.is_empty());
196 assert!(mapper.len() > 20); }
198
199 #[test]
200 fn test_elf_mime_type() {
201 let mapper = MimeMapper::new();
202 assert_eq!(
203 mapper.get_mime_type("ELF 64-bit LSB executable"),
204 Some("application/x-executable")
205 );
206 }
207
208 #[test]
209 fn test_pe32_mime_type() {
210 let mapper = MimeMapper::new();
211 assert_eq!(
212 mapper.get_mime_type("PE32 executable"),
213 Some("application/vnd.microsoft.portable-executable")
214 );
215 }
216
217 #[test]
218 fn test_pe32_plus_mime_type() {
219 let mapper = MimeMapper::new();
220 assert_eq!(
221 mapper.get_mime_type("PE32+ executable (DLL)"),
222 Some("application/vnd.microsoft.portable-executable")
223 );
224 }
225
226 #[test]
227 fn test_zip_mime_type() {
228 let mapper = MimeMapper::new();
229 assert_eq!(
230 mapper.get_mime_type("Zip archive data"),
231 Some("application/zip")
232 );
233 }
234
235 #[test]
236 fn test_jpeg_mime_type() {
237 let mapper = MimeMapper::new();
238 assert_eq!(
239 mapper.get_mime_type("JPEG image data, JFIF standard"),
240 Some("image/jpeg")
241 );
242 }
243
244 #[test]
245 fn test_png_mime_type() {
246 let mapper = MimeMapper::new();
247 assert_eq!(
248 mapper.get_mime_type("PNG image data, 800 x 600"),
249 Some("image/png")
250 );
251 }
252
253 #[test]
254 fn test_gif_mime_type() {
255 let mapper = MimeMapper::new();
256 assert_eq!(
257 mapper.get_mime_type("GIF image data, version 89a"),
258 Some("image/gif")
259 );
260 }
261
262 #[test]
263 fn test_pdf_mime_type() {
264 let mapper = MimeMapper::new();
265 assert_eq!(
266 mapper.get_mime_type("PDF document, version 1.4"),
267 Some("application/pdf")
268 );
269 }
270
271 #[test]
272 fn test_case_insensitive() {
273 let mapper = MimeMapper::new();
274 assert_eq!(
275 mapper.get_mime_type("elf executable"),
276 Some("application/x-executable")
277 );
278 assert_eq!(
279 mapper.get_mime_type("ELF EXECUTABLE"),
280 Some("application/x-executable")
281 );
282 }
283
284 #[test]
285 fn test_unknown_type_returns_none() {
286 let mapper = MimeMapper::new();
287 assert_eq!(mapper.get_mime_type("unknown binary format"), None);
288 assert_eq!(mapper.get_mime_type("data"), None);
289 }
290
291 #[test]
292 fn test_gzip_mime_type() {
293 let mapper = MimeMapper::new();
294 assert_eq!(
295 mapper.get_mime_type("gzip compressed data"),
296 Some("application/gzip")
297 );
298 }
299
300 #[test]
301 fn test_tar_mime_type() {
302 let mapper = MimeMapper::new();
303 assert_eq!(
304 mapper.get_mime_type("POSIX tar archive"),
305 Some("application/x-tar")
306 );
307 }
308
309 #[test]
310 fn test_html_mime_type() {
311 let mapper = MimeMapper::new();
312 assert_eq!(mapper.get_mime_type("HTML document"), Some("text/html"));
313 }
314
315 #[test]
316 fn test_json_mime_type() {
317 let mapper = MimeMapper::new();
318 assert_eq!(mapper.get_mime_type("JSON data"), Some("application/json"));
319 }
320
321 #[test]
322 fn test_mp3_mime_type() {
323 let mapper = MimeMapper::new();
324 assert_eq!(
325 mapper.get_mime_type("Audio file with ID3 version 2.4.0, contains: MPEG ADTS, layer III, v1, 128 kbps, 44.1 kHz, JntStereo"),
326 Some("audio/mpeg")
327 );
328 }
329
330 #[test]
331 fn test_default_trait() {
332 let mapper = MimeMapper::default();
333 assert!(!mapper.is_empty());
334 }
335}