//! Media types for files served byte for byte. //! //! A fixed table rather than a system lookup: Python's `mimetypes` consults //! `/etc/mime.types` where it exists, so smolweb's `Content-Type` for the same //! file differs between hosts. Determinism is worth more here than coverage of //! the long tail, and an unknown extension has a correct answer anyway. use std::path::Path; /// Returned when the extension is absent or unrecognised. pub const DEFAULT: &str = "application/octet-stream"; /// The media type to serve `path` as, by extension, compared case-insensitively. pub fn media_type(path: &Path) -> &'static str { let Some(extension) = path.extension().and_then(|e| e.to_str()) else { return DEFAULT }; let extension = extension.to_ascii_lowercase(); TABLE .iter() .find(|(known, _)| *known == extension) .map(|(_, media_type)| *media_type) .unwrap_or(DEFAULT) } /// Extensions a folder of Markdown actually carries. Text types name their /// charset, since a client has no other way to know it. const TABLE: &[(&str, &str)] = &[ // Images ("png", "image/png"), ("jpg", "image/jpeg"), ("jpeg", "image/jpeg"), ("gif", "image/gif"), ("webp", "image/webp"), ("svg", "image/svg+xml"), ("ico", "image/vnd.microsoft.icon"), ("avif", "image/avif"), // Documents and data ("txt", "text/plain; charset=utf-8"), ("gmi", "text/gemini; charset=utf-8"), ("css", "text/css; charset=utf-8"), ("html", "text/html; charset=utf-8"), ("xml", "application/xml"), ("json", "application/json"), ("toml", "application/toml"), ("pdf", "application/pdf"), ("asc", "text/plain; charset=utf-8"), // Audio and video ("mp3", "audio/mpeg"), ("ogg", "audio/ogg"), ("opus", "audio/ogg"), ("flac", "audio/flac"), ("wav", "audio/wav"), ("mp4", "video/mp4"), ("webm", "video/webm"), // Archives and fonts ("gz", "application/gzip"), ("zip", "application/zip"), ("tar", "application/x-tar"), ("woff2", "font/woff2"), ]; #[cfg(test)] mod tests { use super::*; #[test] fn recognises_common_extensions() { assert_eq!(media_type(Path::new("img.png")), "image/png"); assert_eq!(media_type(Path::new("a/b/photo.JPEG")), "image/jpeg"); assert_eq!(media_type(Path::new("notes.txt")), "text/plain; charset=utf-8"); } #[test] fn falls_back_for_the_unknown_and_the_extensionless() { assert_eq!(media_type(Path::new("mystery.xyz")), DEFAULT); assert_eq!(media_type(Path::new("LICENSE")), DEFAULT); assert_eq!(media_type(Path::new("archive.tar.zst")), DEFAULT); } }