diff --git a/src/content.rs b/src/content.rs index 467f2f9a7e..d061a42851 100644 --- a/src/content.rs +++ b/src/content.rs @@ -1,5 +1,6 @@ #[derive(Debug, PartialEq)] pub(crate) enum Content<'a> { - Text(&'a str), + IFrame, Image, + Text(&'a str), } diff --git a/src/inscription.rs b/src/inscription.rs index 20b0a10bbf..a781890bae 100644 --- a/src/inscription.rs +++ b/src/inscription.rs @@ -89,7 +89,8 @@ impl Inscription { let content = self.content.as_ref()?; match self.content_type()? { - "text/plain;charset=utf-8" => Some(Content::Text(str::from_utf8(content).ok()?)), + content_type::HTML | content_type::SVG => Some(Content::IFrame), + content_type::TEXT => Some(Content::Text(str::from_utf8(content).ok()?)), content_type if content_type::is_image(content_type) => Some(Content::Image), _ => None, } @@ -115,7 +116,7 @@ impl Inscription { } pub(crate) fn is_graphical(&self) -> bool { - matches!(self.content(), Some(Content::Image)) + matches!(self.content(), Some(Content::Image) | Some(Content::IFrame)) } } @@ -712,6 +713,7 @@ mod tests { assert!(inscription("image/png", []).is_graphical()); assert!(!inscription("foo", []).is_graphical()); assert!(inscription("image/gif", []).is_graphical()); + assert!(inscription("image/svg+xml", []).is_graphical()); assert!(!Inscription::new(None, Some(Vec::new())).is_graphical()); } } diff --git a/src/inscription/content_type.rs b/src/inscription/content_type.rs index 4e1f1cf1ff..d6b368bc8c 100644 --- a/src/inscription/content_type.rs +++ b/src/inscription/content_type.rs @@ -1,12 +1,18 @@ use super::*; +pub const HTML: &str = "text/html;charset=utf-8"; +pub const SVG: &str = "image/svg+xml"; +pub const TEXT: &str = "text/plain;charset=utf-8"; + const TABLE: &[(&str, bool, &[&str])] = &[ ("image/apng", true, &["apng"]), ("image/gif", true, &["gif"]), ("image/jpeg", true, &["jpg", "jpeg"]), ("image/png", true, &["png"]), ("image/webp", true, &["webp"]), - ("text/plain;charset=utf-8", false, &["txt"]), + (HTML, false, &["html"]), + (SVG, true, &["svg"]), + (TEXT, false, &["txt"]), ]; lazy_static! { @@ -52,7 +58,7 @@ mod tests { assert_eq!(super::for_extension("jpeg").unwrap(), "image/jpeg"); assert_eq!( super::for_extension("foo").unwrap_err().to_string(), - "unsupported file extension `.foo`, supported extensions: apng gif jpg png webp txt" + "unsupported file extension `.foo`, supported extensions: apng gif jpg png webp html svg txt" ); } } diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index 2f02088013..92335f262e 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -676,7 +676,19 @@ impl Server { ServerError::NotFound(format!("inscription {inscription_id} has no content")) })?; - Ok(([(header::CONTENT_TYPE, content_type)], content).into_response()) + Ok( + ( + [ + (header::CONTENT_TYPE, content_type), + ( + header::CONTENT_SECURITY_POLICY, + "default-src 'none' 'unsafe-eval' 'unsafe-inline'".to_string(), + ), + ], + content, + ) + .into_response(), + ) } fn content_response(inscription: Inscription) -> Option<(String, Vec)> { diff --git a/src/subcommand/server/templates/content.rs b/src/subcommand/server/templates/content.rs index 34196647cc..760d4e16f2 100644 --- a/src/subcommand/server/templates/content.rs +++ b/src/subcommand/server/templates/content.rs @@ -14,7 +14,51 @@ impl<'a> Display for ContentHtml<'a> { write!(f, "") } Some(Content::Image) => write!(f, "", self.inscription_id), + Some(Content::IFrame) => { + write!(f, "", self.inscription_id) + } None => write!(f, "

UNKNOWN

"), } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn text() { + assert_eq!( + ContentHtml { + content: Some(Content::Text("foo")), + inscription_id: txid(1), + } + .to_string(), + "
foo
" + ); + } + + #[test] + fn image() { + assert_eq!( + ContentHtml { + content: Some(Content::Image), + inscription_id: txid(1), + } + .to_string(), + "" + ); + } + + #[test] + fn iframe() { + assert_eq!( + ContentHtml { + content: Some(Content::IFrame), + inscription_id: txid(1), + } + .to_string(), + "" + ); + } +} diff --git a/static/index.css b/static/index.css index 2fa55c936c..ec639f7463 100644 --- a/static/index.css +++ b/static/index.css @@ -178,6 +178,11 @@ a.mythic { image-rendering: pixelated; } +.inscriptions a iframe { + width: 100%; + height: 100%; +} + .content { display: flex; justify-content: center; @@ -188,6 +193,17 @@ a.mythic { image-rendering: pixelated; } +iframe { + border: none; + pointer-events: none; +} + +.content iframe { + aspect-ratio: 1 / 1; + border: none; + min-width: 50%; +} + a.content { color: inherit; text-decoration: none; diff --git a/tests/server.rs b/tests/server.rs index c907fce300..39698b6798 100644 --- a/tests/server.rs +++ b/tests/server.rs @@ -175,6 +175,10 @@ fn inscription_content() { response.headers().get("content-type").unwrap(), "text/plain;charset=utf-8" ); + assert_eq!( + response.headers().get("content-security-policy").unwrap(), + "default-src 'none' 'unsafe-eval' 'unsafe-inline'" + ); assert_eq!(response.bytes().unwrap(), "HELLOWORLD"); }