itsybitsy/core/src/mime.rs

79 lines
2.6 KiB
Rust
Raw Normal View History

//! 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);
}
}