81 lines
2.7 KiB
Rust
81 lines
2.7 KiB
Rust
//! 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";
|
|
|
|
/// Plain text, which is also what marks a file as inlinable ASCII art.
|
|
pub const PLAIN_TEXT: &str = "text/plain; charset=utf-8";
|
|
|
|
/// 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", PLAIN_TEXT),
|
|
("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", PLAIN_TEXT),
|
|
// 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);
|
|
}
|
|
}
|