Hacky fix to prevent XSS

This commit is contained in:
Bad Manners 2024-10-28 22:52:29 -03:00
parent 2c60c64181
commit eaac99f70f
3 changed files with 83 additions and 1 deletions
maud
maud_macros/src

View file

@ -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 {

View file

@ -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="&quot; onclick=alert(24); href=&quot;">XSS be gone!</button>"#
);
}
/// An example struct, for testing purposes only
struct Creature {
name: &'static str,

View file

@ -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),
}
}