feat: resolve request paths within contained site roots
This commit is contained in:
parent
6a3a198975
commit
3031b8acdd
9 changed files with 1257 additions and 8 deletions
78
core/src/mime.rs
Normal file
78
core/src/mime.rs
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
//! 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);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue