1use std::path::Path;
30
31const OCTET_STREAM: &str = "application/octet-stream";
32
33const TABLE: &[(&[&str], &str, &str)] = &[
34 (&["png"], "image/png", "image/png"),
35 (&["jpg", "jpeg"], "image/jpeg", "image/jpeg"),
36 (&["webp"], "image/webp", "image/webp"),
37 (&["gif"], "image/gif", "image/gif"),
38 (&["avif"], "image/avif", "image/avif"),
39 (&["bmp"], "image/bmp", "image/bmp"),
40 (&["tiff", "tif"], "image/tiff", "image/tiff"),
41 (&["ico"], "image/x-icon", "image/x-icon"),
42 (&["svg"], "image/svg+xml", "image/svg+xml"),
43 (&["mp4"], "video/mp4", "video/mp4"),
44 (&["webm"], "video/webm", "video/webm"),
45 (&["mov"], "video/quicktime", "video/quicktime"),
46 (&["avi"], "video/x-msvideo", "video/x-msvideo"),
47 (&["mkv"], "video/x-matroska", "video/x-matroska"),
48 (&["ogv"], "video/ogg", "video/ogg"),
49 (&["mp3"], "audio/mpeg", "audio/mpeg"),
50 (&["weba"], "audio/webm", "audio/webm"),
51 (&["wav"], "audio/wav", "audio/wav"),
52 (&["ogg"], "audio/ogg", "audio/ogg"),
53 (&["aac"], "audio/aac", "audio/aac"),
54 (&["flac"], "audio/flac", "audio/flac"),
55 (&["m4a"], "audio/mp4", "audio/mp4"),
56 (&["woff"], "font/woff", "font/woff"),
57 (&["woff2"], "font/woff2", "font/woff2"),
58 (&["ttf"], "font/ttf", "font/ttf"),
59 (&["otf"], "font/otf", "font/otf"),
60 (&["html", "htm"], "text/html", "text/html; charset=utf-8"),
61 (&["css"], "text/css", "text/css; charset=utf-8"),
62 (
63 &["js", "mjs"],
64 "text/javascript",
65 "text/javascript; charset=utf-8",
66 ),
67 (&["json", "map"], "application/json", "application/json"),
68 (&["txt"], "text/plain", "text/plain; charset=utf-8"),
69 (&["csv"], "text/csv", "text/csv; charset=utf-8"),
70 (&["md"], "text/markdown", "text/markdown; charset=utf-8"),
71 (&["ftl"], "text/plain", "text/plain; charset=utf-8"),
72 (&["xml"], "application/xml", "application/xml"),
73 (&["yaml", "yml"], "application/yaml", "application/yaml"),
74 (&["toml"], "application/toml", "application/toml"),
75 (&["sh"], "application/x-sh", "application/x-sh"),
76 (&["wasm"], "application/wasm", "application/wasm"),
77 (&["pdf"], "application/pdf", "application/pdf"),
78 (&["rtf"], "application/rtf", "application/rtf"),
79 (&["doc"], "application/msword", "application/msword"),
80 (
81 &["docx"],
82 "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
83 "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
84 ),
85 (
86 &["xls"],
87 "application/vnd.ms-excel",
88 "application/vnd.ms-excel",
89 ),
90 (
91 &["xlsx"],
92 "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
93 "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
94 ),
95 (
96 &["ppt"],
97 "application/vnd.ms-powerpoint",
98 "application/vnd.ms-powerpoint",
99 ),
100 (
101 &["pptx"],
102 "application/vnd.openxmlformats-officedocument.presentationml.presentation",
103 "application/vnd.openxmlformats-officedocument.presentationml.presentation",
104 ),
105 (&["gz", "tgz"], "application/gzip", "application/gzip"),
108 (&["tar"], "application/x-tar", "application/x-tar"),
109 (&["zip"], "application/zip", "application/zip"),
110 (&["bz2"], "application/x-bzip2", "application/x-bzip2"),
111 (&["xz"], "application/x-xz", "application/x-xz"),
112 (&["zst"], "application/zstd", "application/zstd"),
113 (&["exe", "msi"], OCTET_STREAM, OCTET_STREAM),
114 (
115 &["dmg"],
116 "application/x-apple-diskimage",
117 "application/x-apple-diskimage",
118 ),
119 (
120 &["deb"],
121 "application/vnd.debian.binary-package",
122 "application/vnd.debian.binary-package",
123 ),
124 (&["rpm"], "application/x-rpm", "application/x-rpm"),
125 (&["appimage"], OCTET_STREAM, OCTET_STREAM),
126 (
129 &["sha256", "sha512", "asc"],
130 "text/plain",
131 "text/plain; charset=utf-8",
132 ),
133 (&["sig"], OCTET_STREAM, OCTET_STREAM),
134];
135
136const ALIASES: &[(&str, &str)] = &[
137 ("image/vnd.microsoft.icon", "image/x-icon"),
138 ("text/xml", "application/xml"),
139 ("application/javascript", "text/javascript"),
140 ("text/yaml", "application/yaml"),
141 ("audio/mp3", "audio/mpeg"),
142 ("audio/wave", "audio/wav"),
143 ("audio/x-wav", "audio/wav"),
144 ("audio/x-m4a", "audio/mp4"),
145];
146
147fn lookup(ext: &str) -> Option<&'static (&'static [&'static str], &'static str, &'static str)> {
148 let lower = ext.to_ascii_lowercase();
149 TABLE
150 .iter()
151 .find(|(exts, _, _)| exts.iter().any(|e| *e == lower))
152}
153
154fn extension_of(path: &Path) -> Option<&str> {
155 path.extension().and_then(std::ffi::OsStr::to_str)
156}
157
158pub fn from_extension(ext: &str) -> Option<&'static str> {
159 lookup(ext).map(|(_, essence, _)| *essence)
160}
161
162pub fn from_path(path: &Path) -> &'static str {
163 extension_of(path)
164 .and_then(from_extension)
165 .unwrap_or(OCTET_STREAM)
166}
167
168pub fn http_content_type(path: &Path) -> &'static str {
169 http_content_type_opt(path).unwrap_or(OCTET_STREAM)
170}
171
172pub fn http_content_type_opt(path: &Path) -> Option<&'static str> {
173 extension_of(path)
174 .and_then(lookup)
175 .map(|(_, _, served)| *served)
176}
177
178pub fn extension_for(mime: &str) -> Option<&'static str> {
179 let essence = essence_of(mime);
180 let resolved = ALIASES
181 .iter()
182 .find(|(alias, _)| *alias == essence)
183 .map_or(essence.as_str(), |(_, canonical)| *canonical);
184
185 if resolved == OCTET_STREAM {
188 return Some("bin");
189 }
190
191 TABLE
192 .iter()
193 .find(|(_, e, _)| *e == resolved)
194 .and_then(|(exts, _, _)| exts.first().copied())
195}
196
197pub fn essence_of(mime: &str) -> String {
198 mime.split(';')
199 .next()
200 .unwrap_or(mime)
201 .trim()
202 .to_ascii_lowercase()
203}