diff --git a/maud/src/lib.rs b/maud/src/lib.rs index beaaea2..cd7d13c 100644 --- a/maud/src/lib.rs +++ b/maud/src/lib.rs @@ -418,6 +418,67 @@ pub mod macro_private { use alloc::string::String; use core::fmt::Display; + pub fn strip_to_attr_name(input: &str, output: &mut String) { + for c in input.chars() { + match c { + ' ' + | '"' + | '\'' + | '>' + | '/' + | '=' + | '\u{0000}'..='\u{001F}' + | '\u{FDD0}'..='\u{FDEF}' + | '\u{FFFE}' + | '\u{FFFF}' + | '\u{1FFFE}' + | '\u{1FFFF}' + | '\u{2FFFE}' + | '\u{2FFFF}' + | '\u{3FFFE}' + | '\u{3FFFF}' + | '\u{4FFFE}' + | '\u{4FFFF}' + | '\u{5FFFE}' + | '\u{5FFFF}' + | '\u{6FFFE}' + | '\u{6FFFF}' + | '\u{7FFFE}' + | '\u{7FFFF}' + | '\u{8FFFE}' + | '\u{8FFFF}' + | '\u{9FFFE}' + | '\u{9FFFF}' + | '\u{AFFFE}' + | '\u{AFFFF}' + | '\u{BFFFE}' + | '\u{BFFFF}' + | '\u{CFFFE}' + | '\u{CFFFF}' + | '\u{DFFFE}' + | '\u{DFFFF}' + | '\u{EFFFE}' + | '\u{EFFFF}' + | '\u{FFFFE}' + | '\u{FFFFF}' + | '\u{10FFFE}' + | '\u{10FFFF}' => (), + _ => output.push(c), + } + } + } + + #[doc(hidden)] + #[macro_export] + macro_rules! render_attr_name { + ($x:expr, $buffer:expr) => {{ + use $crate::macro_private::strip_to_attr_name; + strip_to_attr_name(($x), $buffer); + }}; + } + + pub use render_attr_name; + #[doc(hidden)] #[macro_export] macro_rules! render_to { diff --git a/maud/tests/splices.rs b/maud/tests/splices.rs index 7e2b6d0..3769d61 100644 --- a/maud/tests/splices.rs +++ b/maud/tests/splices.rs @@ -83,6 +83,20 @@ fn attribute_name() { ); } +#[test] +fn no_xss_from_spliced_attributes() { + let evil_tuple = ( + "x onclick=\"alert(42);\" x", + "\" onclick=alert(24); href=\"", + ); + let result = + html! { button (format!("data-{}", evil_tuple.0))=(evil_tuple.1) { "XSS be gone!" } }; + assert_eq!( + result.into_string(), + r#"<button data-xonclickalert(42);x="" onclick=alert(24); href="">XSS be gone!</button>"# + ); +} + /// An example struct, for testing purposes only struct Creature { name: &'static str, diff --git a/maud_macros/src/generate.rs b/maud_macros/src/generate.rs index f432a50..d00157c 100644 --- a/maud_macros/src/generate.rs +++ b/maud_macros/src/generate.rs @@ -106,6 +106,13 @@ impl Generator { build.push_tokens(quote!(maud::macro_private::render_to!(&(#expr), &mut #output_ident);)); } + fn splice_attr_name(&self, expr: TokenStream, build: &mut Builder) { + let output_ident = self.output_ident.clone(); + build.push_tokens( + quote!(maud::macro_private::render_attr_name!(&(#expr), &mut #output_ident);), + ); + } + fn element(&self, name: TokenStream, attrs: Vec<Attr>, body: ElementBody, build: &mut Builder) { build.push_str("<"); self.name(name.clone(), build); @@ -126,7 +133,7 @@ impl Generator { fn attr_name(&self, name: AttrName, build: &mut Builder) { match name { AttrName::Fixed { value } => self.name(value, build), - AttrName::Splice { expr, .. } => self.splice(expr, build), + AttrName::Splice { expr, .. } => self.splice_attr_name(expr, build), } }