From de179ea7fa48e4be0160fcba95b934764b188b87 Mon Sep 17 00:00:00 2001 From: Slatian Date: Sun, 29 Oct 2023 16:51:43 +0100 Subject: [PATCH] Proper MimeType handling with mycelium --- Cargo.lock | 1 + Cargo.toml | 1 + src/mycelium/format.rs | 28 ++++++++++++++++------------ src/templating_engine.rs | 19 ++++++++++++------- 4 files changed, 30 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 02ec518..97a6ada 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -493,6 +493,7 @@ dependencies = [ "idna 0.3.0", "lazy_static", "maxminddb", + "mime", "parking_lot", "regex", "serde", diff --git a/Cargo.toml b/Cargo.toml index 365457c..6ce57bf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,3 +25,4 @@ tower-http = { version = "0.4", features = ["fs"] } trust-dns-proto = "0.22" trust-dns-resolver = { version = "0.22", features = ["dns-over-rustls","dns-over-https","dns-over-quic"] } maxminddb = "0.23" +mime = "0.3" diff --git a/src/mycelium/format.rs b/src/mycelium/format.rs index c1d6285..274a095 100644 --- a/src/mycelium/format.rs +++ b/src/mycelium/format.rs @@ -1,6 +1,7 @@ use serde::Deserialize; use serde::Serialize; +use mime::Mime; /// Defines how the response should be rendered. pub enum MycFormatFamily { @@ -14,7 +15,7 @@ pub enum MycFormatFamily { /// Implement on a type that is able to describe a response format. /// /// It is best implemented on an enum. -pub trait MycFormat: ToString+Clone+Default { +pub trait MycFormat: ToString+Clone+Default+Send { // Return the format family this fn get_family(&self) -> MycFormatFamily { @@ -48,7 +49,7 @@ pub trait MycFormat: ToString+Clone+Default { /// /// Implementing get_mime_type() properly is recommended /// for production use. - fn get_less_well_known_mimetype(&self) -> Option<&'static str> { + fn get_less_well_known_mimetype(&self) -> Option { None } @@ -61,8 +62,8 @@ pub trait MycFormat: ToString+Clone+Default { /// to get_less_well_known_mimetype() and the "application/octet-stream" type. /// /// The default implementation knows the following associations: - /// * `text`: `text/plain` - /// * `html`: `text/html` + /// * `text`: `text/plain; charset=utf-8` + /// * `html`: `text/html; charset=utf-8` /// * `json`: `application/json` /// * `xml`: `application/xml` /// * `rss`: `application/rss+xml` @@ -71,17 +72,20 @@ pub trait MycFormat: ToString+Clone+Default { /// *Implementation Note:* It may be possible that two different views /// have the same MimeType (maybe two json representations for different consumers). /// - fn get_mime_type(&self) -> &'static str { + fn get_mime_type(&self) -> Mime { match self.get_name().as_str() { - "text" => "text/plain", - "html" => "text/html", - "json" => "application/json", - "xml" => "application/xml", - "rss" => "application/rss+xml", - "atom" => "application/atom+xml", + "text" => mime::TEXT_PLAIN_UTF_8, + "html" => mime::TEXT_HTML_UTF_8, + "json" => mime::APPLICATION_JSON, + "xml" => "application/xml".parse() + .expect("Parse static application/xml"), + "rss" => "application/rss+xml".parse() + .expect("Parse static application/rss+xml"), + "atom" => "application/atom+xml".parse() + .expect("Parse static application/atom+xml"), _ => self.get_less_well_known_mimetype() - .unwrap_or_else(||"application/octet-stream"), + .unwrap_or(mime::APPLICATION_OCTET_STREAM), } } diff --git a/src/templating_engine.rs b/src/templating_engine.rs index b681444..afc28a7 100644 --- a/src/templating_engine.rs +++ b/src/templating_engine.rs @@ -4,10 +4,11 @@ */ use axum::{ + body::{Bytes,Full}, headers::HeaderValue, http::StatusCode, + http::header, http::header::SET_COOKIE, - response::Html, response::IntoResponse, response::Response, response::Json, @@ -22,7 +23,6 @@ use crate::mycelium::MycView; use crate::mycelium::MycFormat; use crate::mycelium::MycFormatFamily; use crate::settings::QuerySettings; -use crate::settings::ResponseFormat; /* The engine itself */ @@ -41,12 +41,13 @@ impl Engine { let mut response = match settings.format.get_family() { MycFormatFamily::Template => { let template_name = view.get_template_name(); + let mime_type = settings.format.get_mime_type(); let mut context = tera::Context::new(); context.insert("view", &template_name); //intented for shared macros context.insert("format", &settings.format.get_name()); - context.insert("mimetype", &settings.format.get_mime_type()); + context.insert("mimetype", &mime_type.to_string()); context.insert("language", &settings.lang); context.insert("dns_resolvers", &settings.available_dns_resolvers); context.insert("dns_resolver_id", &settings.dns_resolver_id); @@ -55,10 +56,14 @@ impl Engine { match self.tera.render(&(template_name.clone()+&settings.format.get_file_extension()), &context) { Ok(text) => - match settings.format { - ResponseFormat::Html => Html(text).into_response(), - _ => text.into_response(), - } + ( + [( + header::CONTENT_TYPE, + HeaderValue::from_str(mime_type.as_ref()) + .expect("MimeType should always be a valid header value.") + )], + Into::>::into(text), + ).into_response(), Err(e) => { println!("There was an error while rendering template {}: {e:?}", template_name); (