From 214a9e6c1fd729fc2c49eb2a5d41b5651ff5bc61 Mon Sep 17 00:00:00 2001 From: Owen Jacobson Date: Tue, 22 Oct 2024 22:12:56 -0400 Subject: Set `charset` params on returned content types. This is a somewhat indirect change; it removes `mime_guess` in favour of some very, uh, "bespoke" mime detection logic that hardcodes mime types for the small repertoire of file extensions actually present in the UI. `mime_guess` doesn't provide a way to set params as it exports its own `Mime` struct, which doesn't provide `with_params()`. --- Cargo.lock | 34 +++++++++++++++++----------------- Cargo.toml | 3 ++- src/ui/assets.rs | 32 ++++++++++++++++++++++++++------ src/ui/mime.rs | 22 ++++++++++++++++++++++ src/ui/mod.rs | 1 + src/ui/routes/path.rs | 7 ++----- 6 files changed, 70 insertions(+), 29 deletions(-) create mode 100644 src/ui/mime.rs diff --git a/Cargo.lock b/Cargo.lock index 1cb656d..2b8eb5e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -813,7 +813,7 @@ dependencies = [ "headers", "hex-literal", "itertools", - "mime_guess", + "mime", "password-hash", "rand", "rand_core", @@ -827,6 +827,7 @@ dependencies = [ "tokio-stream", "unicode-casefold", "unicode-normalization", + "unix_path", "uuid", ] @@ -1093,16 +1094,6 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" -[[package]] -name = "mime_guess" -version = "2.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" -dependencies = [ - "mime", - "unicase", -] - [[package]] name = "minimal-lexical" version = "0.2.1" @@ -2097,12 +2088,6 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" -[[package]] -name = "unicase" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" - [[package]] name = "unicode-bidi" version = "0.3.17" @@ -2142,6 +2127,21 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" +[[package]] +name = "unix_path" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af8e291873ae77c4c8d9c9b34d0bee68a35b048fb39c263a5155e0e353783eaf" +dependencies = [ + "unix_str", +] + +[[package]] +name = "unix_str" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ace0b4755d0a2959962769239d56267f8a024fef2d9b32666b3dcd0946b0906" + [[package]] name = "url" version = "2.5.2" diff --git a/Cargo.toml b/Cargo.toml index 101498c..e9c9616 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ futures = "0.3.31" headers = "0.4.0" hex-literal = "0.4.1" itertools = "0.13.0" -mime_guess = "2.0.5" +mime = "0.3.17" password-hash = { version = "0.5.0", features = ["std"] } rand = "0.8.5" rand_core = { version = "0.6.4", features = ["getrandom"] } @@ -34,6 +34,7 @@ tokio = { version = "1.40.0", features = ["rt", "macros", "rt-multi-thread"] } tokio-stream = { version = "0.1.16", features = ["sync"] } unicode-casefold = "0.2.0" unicode-normalization = "0.1.24" +unix_path = "1.0.1" uuid = { version = "1.11.0", features = ["v4"] } [dev-dependencies] diff --git a/src/ui/assets.rs b/src/ui/assets.rs index 342ba59..6a7563a 100644 --- a/src/ui/assets.rs +++ b/src/ui/assets.rs @@ -1,29 +1,31 @@ +use ::mime::{FromStrError, Mime}; use axum::{ http::{header, StatusCode}, response::{IntoResponse, Response}, }; -use mime_guess::Mime; use rust_embed::EmbeddedFile; -use crate::{error::Internal, ui::error::NotFound}; +use super::{error::NotFound, mime}; +use crate::error::Internal; #[derive(rust_embed::Embed)] #[folder = "target/ui"] pub struct Assets; impl Assets { - pub fn load(path: impl AsRef) -> Result> { + pub fn load(path: impl AsRef) -> Result { let path = path.as_ref(); - let mime = mime_guess::from_path(path).first_or_octet_stream(); + let mime = mime::from_path(path)?; Self::get(path) .map(|file| Asset(mime, file)) - .ok_or(NotFound(format!("not found: {path}"))) + .ok_or(Error::NotFound(path.into())) } pub fn index() -> Result { // "not found" in this case really is an internal error, as it should - // never happen. `index.html` is a known-valid path. + // never happen. `index.html` is a known-valid path with a known-valid + // file extension. Ok(Self::load("index.html")?) } } @@ -41,3 +43,21 @@ impl IntoResponse for Asset { .into_response() } } + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("not found: {0}")] + NotFound(String), + #[error(transparent)] + Mime(#[from] FromStrError), +} + +impl IntoResponse for Error { + fn into_response(self) -> Response { + #[allow(clippy::match_wildcard_for_single_variants)] + match self { + Self::NotFound(_) => NotFound(self.to_string()).into_response(), + other => Internal::from(other).into_response(), + } + } +} diff --git a/src/ui/mime.rs b/src/ui/mime.rs new file mode 100644 index 0000000..9c724f0 --- /dev/null +++ b/src/ui/mime.rs @@ -0,0 +1,22 @@ +use mime::Mime; +use unix_path::Path; + +// Extremely manual; using `std::path` here would result in platform-dependent behaviour when it's not appropriate (the URLs passed here always use `/` and are parsed like URLs). Using `unix_path` might be an option, but it's not clearly +pub fn from_path

(path: P) -> Result +where + P: AsRef, +{ + let path = path.as_ref(); + let extension = path.extension().and_then(|ext| ext.to_str()); + let mime = match extension { + Some("css") => "text/css; charset=utf-8", + Some("js") => "text/javascript; charset=utf-8", + Some("json") => "application/json", + Some("html") => "text/html; charset=utf-8", + Some("png") => "image/png", + _ => "application/octet-stream", + }; + let mime = mime.parse()?; + + Ok(mime) +} diff --git a/src/ui/mod.rs b/src/ui/mod.rs index c145382..f8caa48 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,6 +1,7 @@ mod assets; mod error; mod middleware; +mod mime; mod routes; pub use self::routes::router; diff --git a/src/ui/routes/path.rs b/src/ui/routes/path.rs index 2e9a657..a387552 100644 --- a/src/ui/routes/path.rs +++ b/src/ui/routes/path.rs @@ -1,12 +1,9 @@ pub mod get { use axum::extract::Path; - use crate::ui::{ - assets::{Asset, Assets}, - error::NotFound, - }; + use crate::ui::assets::{Asset, Assets, Error}; - pub async fn handler(Path(path): Path) -> Result> { + pub async fn handler(Path(path): Path) -> Result { Assets::load(path) } } -- cgit v1.2.3