From 03348cb38f4bc11e27a6c31f646550a009810fe4 Mon Sep 17 00:00:00 2001 From: orhun Date: Mon, 9 Aug 2021 22:28:33 +0300 Subject: [PATCH] feat(paste): support overriding MIME types --- Cargo.lock | 13 ++++++++ Cargo.toml | 3 ++ config.toml | 9 ++++++ src/config.rs | 3 ++ src/lib.rs | 3 ++ src/mime.rs | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/server.rs | 5 ++++ 7 files changed, 119 insertions(+) create mode 100644 src/mime.rs diff --git a/Cargo.lock b/Cargo.lock index d0bc015..6aa07d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1520,9 +1520,12 @@ dependencies = [ "futures-util", "infer", "log", + "mime", "petname", "rand 0.8.4", + "regex", "serde", + "serde_regex", "url", ] @@ -1602,6 +1605,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_regex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8136f1a4ea815d7eac4101cfd0b16dc0cb5e1fe1b8609dfd728058656b7badf" +dependencies = [ + "regex", + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.0" diff --git a/Cargo.toml b/Cargo.toml index fa82b0b..6281d87 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,9 @@ petname = "1.1.0" rand = "0.8.4" dotenv = "0.15.0" url = "2.2.2" +mime = "0.3.16" +regex = "1.5.4" +serde_regex = "1.1.0" [dependencies.config] version = "0.11.0" diff --git a/config.toml b/config.toml index 4b3315f..bd7c2e4 100644 --- a/config.toml +++ b/config.toml @@ -8,3 +8,12 @@ upload_path="./upload" random_url = { enabled = true, type = "petname", words = 2, separator = "-" } #random_url = { enabled = true, type = "alphanumeric", length = 8 } default_extension = "txt" +mime_override = [ + { mime = "image/jpeg", regex = "^.*\\.jpg$" }, + { mime = "image/png", regex = "^.*\\.png$" }, + { mime = "image/svg+xml", regex = "^.*\\.svg$" }, + { mime = "video/webm", regex = "^.*\\.webm$" }, + { mime = "video/x-matroska", regex = "^.*\\.mkv$" }, + { mime = "application/octet-stream", regex = "^.*\\.bin$" }, + { mime = "text/plain", regex = "^.*\\.(log|txt|diff)$" }, +] diff --git a/src/config.rs b/src/config.rs index f9b95e7..5612a7a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,3 +1,4 @@ +use crate::mime::MimeMatcher; use crate::random::RandomURLConfig; use byte_unit::Byte; use config::{self, ConfigError}; @@ -32,6 +33,8 @@ pub struct PasteConfig { pub random_url: RandomURLConfig, /// Default file extension. pub default_extension: String, + /// Media type override options. + pub mime_override: Vec, } impl Config { diff --git a/src/lib.rs b/src/lib.rs index 8bd023b..089161d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,3 +18,6 @@ pub mod auth; /// Storage handler. pub mod paste; + +/// Media type handler. +pub mod mime; diff --git a/src/mime.rs b/src/mime.rs new file mode 100644 index 0000000..f712dc9 --- /dev/null +++ b/src/mime.rs @@ -0,0 +1,83 @@ +use actix_files::file_extension_to_mime; +use mime::{FromStrError, Mime}; +use regex::Regex; +use std::path::PathBuf; +use std::str::FromStr; + +/// Matcher for MIME types. +#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)] +pub struct MimeMatcher { + /// MIME type to set for the matched file name. + pub mime: String, + /// Regex for matching the file name. + #[serde(with = "serde_regex")] + pub regex: Option, +} + +/// Returns the appropriate media type using an array of +/// [`MIME matcher`]s and the file name. +/// +/// [`MIME matcher`]: MimeMatcher +pub fn get_mime_type( + mime_matchers: &[MimeMatcher], + file_name: String, +) -> Result { + let path = PathBuf::from(&file_name); + let mut mime_type = file_extension_to_mime( + path.extension() + .map(|v| v.to_str()) + .flatten() + .unwrap_or_default(), + ); + for matcher in mime_matchers { + if matcher + .regex + .as_ref() + .map(|r| r.is_match(&file_name)) + .unwrap_or(false) + { + mime_type = Mime::from_str(&matcher.mime)?; + break; + } + } + Ok(mime_type) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_match_mime_type() { + assert_eq!( + mime::TEXT_PLAIN, + get_mime_type( + &[MimeMatcher { + mime: String::from("text/plain"), + regex: Regex::new("^.*\\.test$").ok(), + }], + String::from("mime.test") + ) + .unwrap() + ); + assert_eq!( + mime::IMAGE_PNG, + get_mime_type( + &[MimeMatcher { + mime: String::from("image/png"), + regex: Regex::new("^.*\\.PNG$").ok(), + }], + String::from("image.PNG") + ) + .unwrap() + ); + assert_eq!( + mime::APPLICATION_PDF, + get_mime_type(&[], String::from("book.pdf")).unwrap() + ); + assert_eq!( + mime::APPLICATION_OCTET_STREAM, + get_mime_type(&[], String::from("x.unknown")).unwrap() + ); + } +} diff --git a/src/server.rs b/src/server.rs index 7813cce..c16c917 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,6 +1,7 @@ use crate::auth; use crate::config::Config; use crate::header::ContentDisposition; +use crate::mime; use crate::paste::{Paste, PasteType}; use actix_files::NamedFile; use actix_multipart::Multipart; @@ -38,6 +39,10 @@ async fn serve( match paste_type { PasteType::File => Ok(NamedFile::open(&path)? .disable_content_disposition() + .set_content_type( + mime::get_mime_type(&config.paste.mime_override, file.to_string()) + .map_err(error::ErrorInternalServerError)?, + ) .prefer_utf8(true) .into_response(&request)?), PasteType::Url => Ok(HttpResponse::Found()