1#![allow(dead_code)]
18
19use alloc::{string::String, vec, vec::Vec};
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum MimeType {
28 TextPlain,
30 TextHtml,
31 TextCss,
32 TextJavascript,
33 TextXml,
34 TextMarkdown,
35 TextRust,
36 TextC,
37 TextCpp,
38 TextPython,
39 TextShell,
40
41 ImagePng,
43 ImageJpeg,
44 ImageGif,
45 ImageBmp,
46 ImageSvg,
47 ImagePpm,
48
49 AudioWav,
51 AudioMp3,
52 AudioOgg,
53
54 VideoMp4,
56 VideoAvi,
57
58 ApplicationPdf,
60 ApplicationZip,
61 ApplicationTar,
62 ApplicationGzip,
63 ApplicationElf,
64 ApplicationDesktop,
65
66 DirectoryType,
68 Unknown,
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77pub enum MimeCategory {
78 Text,
79 Image,
80 Audio,
81 Video,
82 Application,
83 Directory,
84}
85
86#[derive(Debug, Clone)]
92pub struct MimeAssociation {
93 pub mime_type: MimeType,
95 pub app_name: String,
97 pub app_exec: String,
99}
100
101pub struct MimeDatabase {
110 associations: Vec<MimeAssociation>,
112 custom_associations: Vec<MimeAssociation>,
114}
115
116impl MimeDatabase {
117 pub fn new() -> Self {
126 let text_editor_name = String::from("Text Editor");
127 let text_editor_exec = String::from("/bin/text-editor");
128 let image_viewer_name = String::from("Image Viewer");
129 let image_viewer_exec = String::from("/bin/image-viewer");
130
131 let associations = vec![
132 MimeAssociation {
134 mime_type: MimeType::TextPlain,
135 app_name: text_editor_name.clone(),
136 app_exec: text_editor_exec.clone(),
137 },
138 MimeAssociation {
139 mime_type: MimeType::TextHtml,
140 app_name: text_editor_name.clone(),
141 app_exec: text_editor_exec.clone(),
142 },
143 MimeAssociation {
144 mime_type: MimeType::TextCss,
145 app_name: text_editor_name.clone(),
146 app_exec: text_editor_exec.clone(),
147 },
148 MimeAssociation {
149 mime_type: MimeType::TextJavascript,
150 app_name: text_editor_name.clone(),
151 app_exec: text_editor_exec.clone(),
152 },
153 MimeAssociation {
154 mime_type: MimeType::TextXml,
155 app_name: text_editor_name.clone(),
156 app_exec: text_editor_exec.clone(),
157 },
158 MimeAssociation {
159 mime_type: MimeType::TextMarkdown,
160 app_name: text_editor_name.clone(),
161 app_exec: text_editor_exec.clone(),
162 },
163 MimeAssociation {
164 mime_type: MimeType::TextRust,
165 app_name: text_editor_name.clone(),
166 app_exec: text_editor_exec.clone(),
167 },
168 MimeAssociation {
169 mime_type: MimeType::TextC,
170 app_name: text_editor_name.clone(),
171 app_exec: text_editor_exec.clone(),
172 },
173 MimeAssociation {
174 mime_type: MimeType::TextCpp,
175 app_name: text_editor_name.clone(),
176 app_exec: text_editor_exec.clone(),
177 },
178 MimeAssociation {
179 mime_type: MimeType::TextPython,
180 app_name: text_editor_name.clone(),
181 app_exec: text_editor_exec.clone(),
182 },
183 MimeAssociation {
184 mime_type: MimeType::TextShell,
185 app_name: text_editor_name.clone(),
186 app_exec: text_editor_exec.clone(),
187 },
188 MimeAssociation {
190 mime_type: MimeType::ImagePng,
191 app_name: image_viewer_name.clone(),
192 app_exec: image_viewer_exec.clone(),
193 },
194 MimeAssociation {
195 mime_type: MimeType::ImageJpeg,
196 app_name: image_viewer_name.clone(),
197 app_exec: image_viewer_exec.clone(),
198 },
199 MimeAssociation {
200 mime_type: MimeType::ImageGif,
201 app_name: image_viewer_name.clone(),
202 app_exec: image_viewer_exec.clone(),
203 },
204 MimeAssociation {
205 mime_type: MimeType::ImageBmp,
206 app_name: image_viewer_name.clone(),
207 app_exec: image_viewer_exec.clone(),
208 },
209 MimeAssociation {
210 mime_type: MimeType::ImageSvg,
211 app_name: image_viewer_name.clone(),
212 app_exec: image_viewer_exec.clone(),
213 },
214 MimeAssociation {
215 mime_type: MimeType::ImagePpm,
216 app_name: image_viewer_name.clone(),
217 app_exec: image_viewer_exec.clone(),
218 },
219 MimeAssociation {
221 mime_type: MimeType::AudioWav,
222 app_name: text_editor_name.clone(),
223 app_exec: text_editor_exec.clone(),
224 },
225 MimeAssociation {
226 mime_type: MimeType::AudioMp3,
227 app_name: text_editor_name.clone(),
228 app_exec: text_editor_exec.clone(),
229 },
230 MimeAssociation {
231 mime_type: MimeType::AudioOgg,
232 app_name: text_editor_name.clone(),
233 app_exec: text_editor_exec.clone(),
234 },
235 MimeAssociation {
237 mime_type: MimeType::VideoMp4,
238 app_name: text_editor_name.clone(),
239 app_exec: text_editor_exec.clone(),
240 },
241 MimeAssociation {
242 mime_type: MimeType::VideoAvi,
243 app_name: text_editor_name.clone(),
244 app_exec: text_editor_exec.clone(),
245 },
246 MimeAssociation {
248 mime_type: MimeType::ApplicationPdf,
249 app_name: text_editor_name.clone(),
250 app_exec: text_editor_exec.clone(),
251 },
252 MimeAssociation {
253 mime_type: MimeType::ApplicationZip,
254 app_name: text_editor_name.clone(),
255 app_exec: text_editor_exec.clone(),
256 },
257 MimeAssociation {
258 mime_type: MimeType::ApplicationTar,
259 app_name: text_editor_name.clone(),
260 app_exec: text_editor_exec.clone(),
261 },
262 MimeAssociation {
263 mime_type: MimeType::ApplicationGzip,
264 app_name: text_editor_name.clone(),
265 app_exec: text_editor_exec.clone(),
266 },
267 MimeAssociation {
268 mime_type: MimeType::ApplicationElf,
269 app_name: String::from("Terminal"),
270 app_exec: String::from("/bin/terminal"),
271 },
272 MimeAssociation {
273 mime_type: MimeType::ApplicationDesktop,
274 app_name: text_editor_name.clone(),
275 app_exec: text_editor_exec.clone(),
276 },
277 MimeAssociation {
279 mime_type: MimeType::DirectoryType,
280 app_name: String::from("File Manager"),
281 app_exec: String::from("/bin/file-manager"),
282 },
283 MimeAssociation {
285 mime_type: MimeType::Unknown,
286 app_name: text_editor_name,
287 app_exec: text_editor_exec,
288 },
289 ];
290
291 Self {
292 associations,
293 custom_associations: Vec::new(),
294 }
295 }
296
297 pub fn detect_mime(filename: &str, header_bytes: Option<&[u8]>) -> MimeType {
308 if let Some(bytes) = header_bytes {
310 if let Some(mime) = Self::detect_from_magic(bytes) {
311 return mime;
312 }
313 }
314
315 if let Some(ext) = get_extension(filename) {
317 return detect_mime_from_extension(ext);
318 }
319
320 MimeType::Unknown
322 }
323
324 fn detect_from_magic(bytes: &[u8]) -> Option<MimeType> {
329 if bytes.len() < 2 {
330 return None;
331 }
332
333 if bytes.len() >= 4
335 && bytes[0] == 0x89
336 && bytes[1] == 0x50
337 && bytes[2] == 0x4E
338 && bytes[3] == 0x47
339 {
340 return Some(MimeType::ImagePng);
341 }
342
343 if bytes.len() >= 3 && bytes[0] == 0xFF && bytes[1] == 0xD8 && bytes[2] == 0xFF {
345 return Some(MimeType::ImageJpeg);
346 }
347
348 if bytes.len() >= 3 && bytes[0] == 0x47 && bytes[1] == 0x49 && bytes[2] == 0x46 {
350 return Some(MimeType::ImageGif);
351 }
352
353 if bytes[0] == 0x42 && bytes[1] == 0x4D {
355 return Some(MimeType::ImageBmp);
356 }
357
358 if bytes.len() >= 4
360 && bytes[0] == 0x25
361 && bytes[1] == 0x50
362 && bytes[2] == 0x44
363 && bytes[3] == 0x46
364 {
365 return Some(MimeType::ApplicationPdf);
366 }
367
368 if bytes.len() >= 4
370 && bytes[0] == 0x50
371 && bytes[1] == 0x4B
372 && bytes[2] == 0x03
373 && bytes[3] == 0x04
374 {
375 return Some(MimeType::ApplicationZip);
376 }
377
378 if bytes[0] == 0x1F && bytes[1] == 0x8B {
380 return Some(MimeType::ApplicationGzip);
381 }
382
383 if bytes.len() >= 4
385 && bytes[0] == 0x7F
386 && bytes[1] == 0x45
387 && bytes[2] == 0x4C
388 && bytes[3] == 0x46
389 {
390 return Some(MimeType::ApplicationElf);
391 }
392
393 if bytes.len() >= 12
395 && bytes[0] == b'R'
396 && bytes[1] == b'I'
397 && bytes[2] == b'F'
398 && bytes[3] == b'F'
399 && bytes[8] == b'W'
400 && bytes[9] == b'A'
401 && bytes[10] == b'V'
402 && bytes[11] == b'E'
403 {
404 return Some(MimeType::AudioWav);
405 }
406
407 if bytes.len() >= 4
409 && bytes[0] == b'O'
410 && bytes[1] == b'g'
411 && bytes[2] == b'g'
412 && bytes[3] == b'S'
413 {
414 return Some(MimeType::AudioOgg);
415 }
416
417 if bytes.len() >= 3
419 && ((bytes[0] == 0xFF && (bytes[1] & 0xE0) == 0xE0)
420 || (bytes[0] == b'I' && bytes[1] == b'D' && bytes[2] == b'3'))
421 {
422 return Some(MimeType::AudioMp3);
423 }
424
425 if bytes.len() >= 2 && bytes[0] == b'P' && (bytes[1] == b'6' || bytes[1] == b'3') {
427 return Some(MimeType::ImagePpm);
428 }
429
430 if bytes.len() >= 5
432 && ((bytes[0] == b'<'
433 && bytes[1] == b'?'
434 && bytes[2] == b'x'
435 && bytes[3] == b'm'
436 && bytes[4] == b'l')
437 || (bytes.len() >= 4
438 && bytes[0] == b'<'
439 && bytes[1] == b's'
440 && bytes[2] == b'v'
441 && bytes[3] == b'g'))
442 {
443 return Some(MimeType::TextXml);
449 }
450
451 if bytes.len() >= 263
453 && bytes[257] == b'u'
454 && bytes[258] == b's'
455 && bytes[259] == b't'
456 && bytes[260] == b'a'
457 && bytes[261] == b'r'
458 {
459 return Some(MimeType::ApplicationTar);
460 }
461
462 None
463 }
464
465 pub fn open_with(&self, mime: &MimeType) -> Option<&MimeAssociation> {
475 for assoc in &self.custom_associations {
477 if assoc.mime_type == *mime {
478 return Some(assoc);
479 }
480 }
481
482 for assoc in &self.associations {
484 if assoc.mime_type == *mime {
485 return Some(assoc);
486 }
487 }
488
489 if *mime != MimeType::Unknown {
491 for assoc in &self.associations {
492 if assoc.mime_type == MimeType::Unknown {
493 return Some(assoc);
494 }
495 }
496 }
497
498 None
499 }
500
501 pub fn register_association(
507 &mut self,
508 mime_type: MimeType,
509 app_name: String,
510 app_exec: String,
511 ) {
512 self.custom_associations
514 .retain(|a| a.mime_type != mime_type);
515
516 self.custom_associations.push(MimeAssociation {
517 mime_type,
518 app_name,
519 app_exec,
520 });
521 }
522
523 pub fn category(mime: &MimeType) -> MimeCategory {
529 match mime {
530 MimeType::TextPlain
531 | MimeType::TextHtml
532 | MimeType::TextCss
533 | MimeType::TextJavascript
534 | MimeType::TextXml
535 | MimeType::TextMarkdown
536 | MimeType::TextRust
537 | MimeType::TextC
538 | MimeType::TextCpp
539 | MimeType::TextPython
540 | MimeType::TextShell => MimeCategory::Text,
541
542 MimeType::ImagePng
543 | MimeType::ImageJpeg
544 | MimeType::ImageGif
545 | MimeType::ImageBmp
546 | MimeType::ImageSvg
547 | MimeType::ImagePpm => MimeCategory::Image,
548
549 MimeType::AudioWav | MimeType::AudioMp3 | MimeType::AudioOgg => MimeCategory::Audio,
550
551 MimeType::VideoMp4 | MimeType::VideoAvi => MimeCategory::Video,
552
553 MimeType::ApplicationPdf
554 | MimeType::ApplicationZip
555 | MimeType::ApplicationTar
556 | MimeType::ApplicationGzip
557 | MimeType::ApplicationElf
558 | MimeType::ApplicationDesktop => MimeCategory::Application,
559
560 MimeType::DirectoryType => MimeCategory::Directory,
561
562 MimeType::Unknown => MimeCategory::Application,
563 }
564 }
565
566 pub fn mime_to_str(mime: &MimeType) -> &'static str {
568 match mime {
569 MimeType::TextPlain => "text/plain",
570 MimeType::TextHtml => "text/html",
571 MimeType::TextCss => "text/css",
572 MimeType::TextJavascript => "text/javascript",
573 MimeType::TextXml => "text/xml",
574 MimeType::TextMarkdown => "text/markdown",
575 MimeType::TextRust => "text/x-rust",
576 MimeType::TextC => "text/x-csrc",
577 MimeType::TextCpp => "text/x-c++src",
578 MimeType::TextPython => "text/x-python",
579 MimeType::TextShell => "text/x-shellscript",
580
581 MimeType::ImagePng => "image/png",
582 MimeType::ImageJpeg => "image/jpeg",
583 MimeType::ImageGif => "image/gif",
584 MimeType::ImageBmp => "image/bmp",
585 MimeType::ImageSvg => "image/svg+xml",
586 MimeType::ImagePpm => "image/x-portable-pixmap",
587
588 MimeType::AudioWav => "audio/wav",
589 MimeType::AudioMp3 => "audio/mpeg",
590 MimeType::AudioOgg => "audio/ogg",
591
592 MimeType::VideoMp4 => "video/mp4",
593 MimeType::VideoAvi => "video/x-msvideo",
594
595 MimeType::ApplicationPdf => "application/pdf",
596 MimeType::ApplicationZip => "application/zip",
597 MimeType::ApplicationTar => "application/x-tar",
598 MimeType::ApplicationGzip => "application/gzip",
599 MimeType::ApplicationElf => "application/x-elf",
600 MimeType::ApplicationDesktop => "application/x-desktop",
601
602 MimeType::DirectoryType => "inode/directory",
603
604 MimeType::Unknown => "application/octet-stream",
605 }
606 }
607
608 pub fn icon_color(mime: &MimeType) -> u32 {
622 match mime {
623 MimeType::TextPlain => 0xFFCCCCCC,
625
626 MimeType::TextRust => 0xFFDE8A56, MimeType::TextC => 0xFFD19A55, MimeType::TextCpp => 0xFFCB6D9F, MimeType::TextPython => 0xFF55B4D1, MimeType::TextShell => 0xFF66CC66, MimeType::TextHtml => 0xFFE06633, MimeType::TextCss => 0xFFCC6699, MimeType::TextJavascript => 0xFF33CCDD, MimeType::TextXml => 0xFFAA8866, MimeType::TextMarkdown => 0xFFBBBBDD, MimeType::ImagePng
641 | MimeType::ImageJpeg
642 | MimeType::ImageGif
643 | MimeType::ImageBmp
644 | MimeType::ImageSvg
645 | MimeType::ImagePpm => 0xFF44DD44,
646
647 MimeType::AudioWav | MimeType::AudioMp3 | MimeType::AudioOgg => 0xFF44AAEE,
649
650 MimeType::VideoMp4 | MimeType::VideoAvi => 0xFFDD44DD,
652
653 MimeType::ApplicationPdf => 0xFF3333CC,
655
656 MimeType::ApplicationZip | MimeType::ApplicationTar | MimeType::ApplicationGzip => {
658 0xFF33DDDD
659 }
660
661 MimeType::ApplicationElf => 0xFF4444EE,
663
664 MimeType::ApplicationDesktop => 0xFFDDBB33,
666
667 MimeType::DirectoryType => 0xFFFFAA55,
669
670 MimeType::Unknown => 0xFF888888,
672 }
673 }
674}
675
676impl Default for MimeDatabase {
677 fn default() -> Self {
678 Self::new()
679 }
680}
681
682pub fn get_extension(filename: &str) -> Option<&str> {
697 let bytes = filename.as_bytes();
699 let mut dot_pos: Option<usize> = None;
700 let mut i = bytes.len();
701 while i > 0 {
702 i -= 1;
703 if bytes[i] == b'.' {
704 if i == 0 {
706 return None;
707 }
708 if i > 0 && bytes[i - 1] == b'/' {
710 return None;
711 }
712 dot_pos = Some(i);
713 break;
714 }
715 if bytes[i] == b'/' {
717 return None;
718 }
719 }
720
721 dot_pos.map(|pos| &filename[pos + 1..])
722}
723
724pub fn detect_mime_from_extension(ext: &str) -> MimeType {
729 let mut lower_buf = [0u8; 16];
733 let ext_bytes = ext.as_bytes();
734 if ext_bytes.len() > 15 {
735 return MimeType::Unknown;
736 }
737 for (i, &b) in ext_bytes.iter().enumerate() {
738 lower_buf[i] = if b.is_ascii_uppercase() { b + 32 } else { b };
739 }
740 let lower = core::str::from_utf8(&lower_buf[..ext_bytes.len()]).unwrap_or("");
741
742 match lower {
743 "txt" | "text" | "log" | "cfg" | "conf" | "ini" => MimeType::TextPlain,
745
746 "html" | "htm" | "xhtml" => MimeType::TextHtml,
748 "css" => MimeType::TextCss,
749 "js" | "mjs" | "cjs" => MimeType::TextJavascript,
750 "xml" | "xsl" | "xslt" => MimeType::TextXml,
751 "md" | "markdown" | "mkd" => MimeType::TextMarkdown,
752
753 "rs" => MimeType::TextRust,
755 "c" | "h" => MimeType::TextC,
756 "cpp" | "cxx" | "cc" | "hpp" | "hxx" | "hh" => MimeType::TextCpp,
757 "py" | "pyw" | "pyi" => MimeType::TextPython,
758 "sh" | "bash" | "zsh" | "fish" | "ksh" | "csh" => MimeType::TextShell,
759
760 "json" | "yaml" | "yml" | "toml" | "csv" | "tsv" => MimeType::TextPlain,
762 "diff" | "patch" => MimeType::TextPlain,
763 "makefile" => MimeType::TextPlain,
764
765 "png" => MimeType::ImagePng,
767 "jpg" | "jpeg" | "jpe" => MimeType::ImageJpeg,
768 "gif" => MimeType::ImageGif,
769 "bmp" | "dib" => MimeType::ImageBmp,
770 "svg" | "svgz" => MimeType::ImageSvg,
771 "ppm" | "pgm" | "pbm" | "pnm" => MimeType::ImagePpm,
772
773 "wav" | "wave" => MimeType::AudioWav,
775 "mp3" => MimeType::AudioMp3,
776 "ogg" | "oga" | "opus" => MimeType::AudioOgg,
777
778 "mp4" | "m4v" => MimeType::VideoMp4,
780 "avi" => MimeType::VideoAvi,
781
782 "pdf" => MimeType::ApplicationPdf,
784 "zip" | "jar" => MimeType::ApplicationZip,
785 "tar" => MimeType::ApplicationTar,
786 "gz" | "gzip" | "tgz" => MimeType::ApplicationGzip,
787 "elf" | "bin" | "out" => MimeType::ApplicationElf,
788 "desktop" => MimeType::ApplicationDesktop,
789
790 _ => MimeType::Unknown,
791 }
792}
793
794impl MimeType {
799 pub fn as_str(&self) -> &'static str {
802 MimeDatabase::mime_to_str(self)
803 }
804
805 pub fn category(&self) -> MimeCategory {
807 MimeDatabase::category(self)
808 }
809
810 pub fn icon_color(&self) -> u32 {
812 MimeDatabase::icon_color(self)
813 }
814
815 pub fn is_text(&self) -> bool {
817 matches!(self.category(), MimeCategory::Text)
818 }
819
820 pub fn is_image(&self) -> bool {
822 matches!(self.category(), MimeCategory::Image)
823 }
824
825 pub fn is_audio(&self) -> bool {
827 matches!(self.category(), MimeCategory::Audio)
828 }
829
830 pub fn is_video(&self) -> bool {
832 matches!(self.category(), MimeCategory::Video)
833 }
834
835 pub fn is_directory(&self) -> bool {
837 *self == MimeType::DirectoryType
838 }
839
840 pub fn is_executable(&self) -> bool {
842 *self == MimeType::ApplicationElf
843 }
844
845 pub fn is_archive(&self) -> bool {
847 matches!(
848 self,
849 MimeType::ApplicationZip | MimeType::ApplicationTar | MimeType::ApplicationGzip
850 )
851 }
852}
853
854#[cfg(test)]
859mod tests {
860 use super::*;
861
862 #[test]
865 fn test_get_extension_basic() {
866 assert_eq!(get_extension("readme.txt"), Some("txt"));
867 assert_eq!(get_extension("archive.tar.gz"), Some("gz"));
868 assert_eq!(get_extension("Makefile"), None);
869 assert_eq!(get_extension(".hidden"), None);
870 assert_eq!(get_extension("path/.hidden"), None);
871 assert_eq!(get_extension("no_ext"), None);
872 assert_eq!(get_extension("photo.JPEG"), Some("JPEG"));
873 }
874
875 #[test]
876 fn test_get_extension_empty() {
877 assert_eq!(get_extension(""), None);
878 assert_eq!(get_extension("."), None);
879 }
880
881 #[test]
884 fn test_detect_extension_text() {
885 assert_eq!(detect_mime_from_extension("txt"), MimeType::TextPlain);
886 assert_eq!(detect_mime_from_extension("TXT"), MimeType::TextPlain);
887 assert_eq!(detect_mime_from_extension("rs"), MimeType::TextRust);
888 assert_eq!(detect_mime_from_extension("c"), MimeType::TextC);
889 assert_eq!(detect_mime_from_extension("cpp"), MimeType::TextCpp);
890 assert_eq!(detect_mime_from_extension("py"), MimeType::TextPython);
891 assert_eq!(detect_mime_from_extension("sh"), MimeType::TextShell);
892 assert_eq!(detect_mime_from_extension("md"), MimeType::TextMarkdown);
893 }
894
895 #[test]
896 fn test_detect_extension_images() {
897 assert_eq!(detect_mime_from_extension("png"), MimeType::ImagePng);
898 assert_eq!(detect_mime_from_extension("jpg"), MimeType::ImageJpeg);
899 assert_eq!(detect_mime_from_extension("jpeg"), MimeType::ImageJpeg);
900 assert_eq!(detect_mime_from_extension("gif"), MimeType::ImageGif);
901 assert_eq!(detect_mime_from_extension("bmp"), MimeType::ImageBmp);
902 assert_eq!(detect_mime_from_extension("svg"), MimeType::ImageSvg);
903 assert_eq!(detect_mime_from_extension("ppm"), MimeType::ImagePpm);
904 }
905
906 #[test]
907 fn test_detect_extension_archives() {
908 assert_eq!(detect_mime_from_extension("zip"), MimeType::ApplicationZip);
909 assert_eq!(detect_mime_from_extension("tar"), MimeType::ApplicationTar);
910 assert_eq!(detect_mime_from_extension("gz"), MimeType::ApplicationGzip);
911 }
912
913 #[test]
914 fn test_detect_extension_unknown() {
915 assert_eq!(detect_mime_from_extension("xyz"), MimeType::Unknown);
916 assert_eq!(detect_mime_from_extension(""), MimeType::Unknown);
917 }
918
919 #[test]
922 fn test_magic_png() {
923 let bytes = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
924 assert_eq!(
925 MimeDatabase::detect_mime("unknown", Some(&bytes)),
926 MimeType::ImagePng
927 );
928 }
929
930 #[test]
931 fn test_magic_jpeg() {
932 let bytes = [0xFF, 0xD8, 0xFF, 0xE0];
933 assert_eq!(
934 MimeDatabase::detect_mime("unknown", Some(&bytes)),
935 MimeType::ImageJpeg
936 );
937 }
938
939 #[test]
940 fn test_magic_gif() {
941 let bytes = b"GIF89a";
942 assert_eq!(
943 MimeDatabase::detect_mime("unknown", Some(bytes)),
944 MimeType::ImageGif
945 );
946 }
947
948 #[test]
949 fn test_magic_elf() {
950 let bytes = [0x7F, 0x45, 0x4C, 0x46, 0x02, 0x01];
951 assert_eq!(
952 MimeDatabase::detect_mime("unknown", Some(&bytes)),
953 MimeType::ApplicationElf
954 );
955 }
956
957 #[test]
958 fn test_magic_pdf() {
959 let bytes = b"%PDF-1.7";
960 assert_eq!(
961 MimeDatabase::detect_mime("unknown", Some(bytes)),
962 MimeType::ApplicationPdf
963 );
964 }
965
966 #[test]
967 fn test_magic_gzip() {
968 let bytes = [0x1F, 0x8B, 0x08, 0x00];
969 assert_eq!(
970 MimeDatabase::detect_mime("unknown", Some(&bytes)),
971 MimeType::ApplicationGzip
972 );
973 }
974
975 #[test]
976 fn test_magic_zip() {
977 let bytes = [0x50, 0x4B, 0x03, 0x04];
978 assert_eq!(
979 MimeDatabase::detect_mime("unknown", Some(&bytes)),
980 MimeType::ApplicationZip
981 );
982 }
983
984 #[test]
985 fn test_magic_bmp() {
986 let bytes = [0x42, 0x4D, 0x00, 0x00];
987 assert_eq!(
988 MimeDatabase::detect_mime("unknown", Some(&bytes)),
989 MimeType::ImageBmp
990 );
991 }
992
993 #[test]
994 fn test_magic_wav() {
995 let mut bytes = [0u8; 16];
996 bytes[0..4].copy_from_slice(b"RIFF");
997 bytes[8..12].copy_from_slice(b"WAVE");
998 assert_eq!(
999 MimeDatabase::detect_mime("unknown", Some(&bytes)),
1000 MimeType::AudioWav
1001 );
1002 }
1003
1004 #[test]
1005 fn test_magic_ogg() {
1006 let bytes = b"OggS\x00\x02";
1007 assert_eq!(
1008 MimeDatabase::detect_mime("unknown", Some(bytes)),
1009 MimeType::AudioOgg
1010 );
1011 }
1012
1013 #[test]
1014 fn test_magic_ppm() {
1015 let bytes = b"P6\n640 480\n255\n";
1016 assert_eq!(
1017 MimeDatabase::detect_mime("unknown", Some(bytes)),
1018 MimeType::ImagePpm
1019 );
1020 }
1021
1022 #[test]
1023 fn test_magic_priority_over_extension() {
1024 let elf_bytes = [0x7F, 0x45, 0x4C, 0x46, 0x02, 0x01];
1026 assert_eq!(
1027 MimeDatabase::detect_mime("sneaky.txt", Some(&elf_bytes)),
1028 MimeType::ApplicationElf
1029 );
1030 }
1031
1032 #[test]
1033 fn test_extension_fallback_when_no_magic() {
1034 assert_eq!(
1035 MimeDatabase::detect_mime("script.py", None),
1036 MimeType::TextPython
1037 );
1038 }
1039
1040 #[test]
1043 fn test_open_with_defaults() {
1044 let db = MimeDatabase::new();
1045
1046 let assoc = db.open_with(&MimeType::TextPlain).unwrap();
1047 assert_eq!(assoc.app_name, "Text Editor");
1048
1049 let assoc = db.open_with(&MimeType::ImagePng).unwrap();
1050 assert_eq!(assoc.app_name, "Image Viewer");
1051
1052 let assoc = db.open_with(&MimeType::ApplicationElf).unwrap();
1053 assert_eq!(assoc.app_name, "Terminal");
1054
1055 let assoc = db.open_with(&MimeType::DirectoryType).unwrap();
1056 assert_eq!(assoc.app_name, "File Manager");
1057 }
1058
1059 #[test]
1060 fn test_custom_association_overrides_default() {
1061 let mut db = MimeDatabase::new();
1062
1063 db.register_association(
1065 MimeType::TextPlain,
1066 String::from("Custom Editor"),
1067 String::from("/bin/custom-editor"),
1068 );
1069
1070 let assoc = db.open_with(&MimeType::TextPlain).unwrap();
1071 assert_eq!(assoc.app_name, "Custom Editor");
1072 assert_eq!(assoc.app_exec, "/bin/custom-editor");
1073 }
1074
1075 #[test]
1076 fn test_custom_association_replaces_existing() {
1077 let mut db = MimeDatabase::new();
1078
1079 db.register_association(
1080 MimeType::ImagePng,
1081 String::from("Viewer A"),
1082 String::from("/bin/a"),
1083 );
1084 db.register_association(
1085 MimeType::ImagePng,
1086 String::from("Viewer B"),
1087 String::from("/bin/b"),
1088 );
1089
1090 let assoc = db.open_with(&MimeType::ImagePng).unwrap();
1091 assert_eq!(assoc.app_name, "Viewer B");
1092 }
1093
1094 #[test]
1097 fn test_category() {
1098 assert_eq!(
1099 MimeDatabase::category(&MimeType::TextRust),
1100 MimeCategory::Text
1101 );
1102 assert_eq!(
1103 MimeDatabase::category(&MimeType::ImagePng),
1104 MimeCategory::Image
1105 );
1106 assert_eq!(
1107 MimeDatabase::category(&MimeType::AudioWav),
1108 MimeCategory::Audio
1109 );
1110 assert_eq!(
1111 MimeDatabase::category(&MimeType::VideoMp4),
1112 MimeCategory::Video
1113 );
1114 assert_eq!(
1115 MimeDatabase::category(&MimeType::ApplicationElf),
1116 MimeCategory::Application,
1117 );
1118 assert_eq!(
1119 MimeDatabase::category(&MimeType::DirectoryType),
1120 MimeCategory::Directory,
1121 );
1122 }
1123
1124 #[test]
1125 fn test_mime_to_str() {
1126 assert_eq!(
1127 MimeDatabase::mime_to_str(&MimeType::TextPlain),
1128 "text/plain"
1129 );
1130 assert_eq!(MimeDatabase::mime_to_str(&MimeType::ImagePng), "image/png");
1131 assert_eq!(
1132 MimeDatabase::mime_to_str(&MimeType::ApplicationElf),
1133 "application/x-elf"
1134 );
1135 assert_eq!(
1136 MimeDatabase::mime_to_str(&MimeType::Unknown),
1137 "application/octet-stream"
1138 );
1139 }
1140
1141 #[test]
1144 fn test_mimetype_helpers() {
1145 assert!(MimeType::TextRust.is_text());
1146 assert!(!MimeType::TextRust.is_image());
1147 assert!(MimeType::ImagePng.is_image());
1148 assert!(MimeType::AudioMp3.is_audio());
1149 assert!(MimeType::VideoMp4.is_video());
1150 assert!(MimeType::DirectoryType.is_directory());
1151 assert!(MimeType::ApplicationElf.is_executable());
1152 assert!(MimeType::ApplicationZip.is_archive());
1153 assert!(MimeType::ApplicationTar.is_archive());
1154 assert!(MimeType::ApplicationGzip.is_archive());
1155 }
1156
1157 #[test]
1160 fn test_icon_color_nonzero() {
1161 let types = [
1163 MimeType::TextPlain,
1164 MimeType::TextRust,
1165 MimeType::ImagePng,
1166 MimeType::AudioWav,
1167 MimeType::VideoMp4,
1168 MimeType::ApplicationElf,
1169 MimeType::DirectoryType,
1170 MimeType::Unknown,
1171 ];
1172 for t in &types {
1173 assert_ne!(MimeDatabase::icon_color(t), 0);
1174 }
1175 }
1176}