#![allow(clippy::unwrap_used, clippy::expect_used)]
struct Unserializable;
impl serde::Serialize for Unserializable {
fn serialize<S: serde::Serializer>(&self, _serializer: S) -> Result<S::Ok, S::Error> {
Err(serde::ser::Error::custom("deliberately unserializable"))
}
}
#[crate::island]
fn wide_counter(start: u128) -> String {
format!("<b>{start}</b>")
}
#[crate::island]
fn opaque_widget(value: Unserializable) -> String {
let _ = value;
"<i>static</i>".to_string()
}
#[test]
fn island_renders_without_hydration_props_instead_of_panicking() {
assert_eq!(
wide_counter(5),
"<div data-island=\"wide_counter\" data-props=\"{"start":5}\"><b>5</b></div>"
);
let wide = u128::from(u64::MAX) + 1;
assert_eq!(
wide_counter(wide),
format!("<div data-island=\"wide_counter\" data-props=\"\"><b>{wide}</b></div>")
);
assert_eq!(
opaque_widget(Unserializable),
"<div data-island=\"opaque_widget\" data-props=\"\"><i>static</i></div>"
);
}
const SCRIPT_URLS: [&str; 8] = [
"javascript:alert(1)",
" JaVaScRiPt:alert(1)",
"java\tscript:alert(1)",
"java\nscript\r:alert(1)",
"\u{1}\u{20}javascript:alert(1)",
"vbscript:msgbox(1)",
"VBScript:msgbox(1)",
"data:text/html,<script>alert(1)</script>",
];
#[test]
fn dynamic_url_attributes_neutralize_script_schemes() {
for url in SCRIPT_URLS {
assert_eq!(
crate::html! { <a href={url}>"x"</a> },
"<a href=\"#\">x</a>",
"{url:?}"
);
assert_eq!(
crate::html! {
<form action={url}><button formaction={url}>"go"</button></form>
},
"<form action=\"#\"><button formaction=\"#\">go</button></form>"
);
assert_eq!(
crate::html! { <iframe src={url}></iframe> },
"<iframe src=\"#\"></iframe>"
);
assert_eq!(
crate::html! { <a HREF={url}>"x"</a> },
"<a HREF=\"#\">x</a>"
);
assert_eq!(
crate::html! { <a href={crate::html::RawHtml::new(url)}>"x"</a> },
"<a href=\"#\">x</a>"
);
}
}
#[test]
fn safe_urls_and_other_attributes_keep_plain_escaping() {
for (url, rendered) in [
(
"https://example.com/?a=1&b=\"2\"",
"https://example.com/?a=1&b="2"",
),
("/profile/7", "/profile/7"),
("#section", "#section"),
("mailto:team@example.com", "mailto:team@example.com"),
("./a:b", "./a:b"),
("javascript", "javascript"),
("javascript:alert(1)", "javascript&colon;alert(1)"),
] {
assert_eq!(
crate::html! { <a href={url}>"x"</a> },
format!("<a href=\"{rendered}\">x</a>"),
"{url:?}"
);
}
assert_eq!(
crate::html! { <a href={format!("/posts/{}", 7)}>"x"</a> },
"<a href=\"/posts/7\">x</a>"
);
assert_eq!(
crate::html! { <p title={"javascript:alert(1)"}>"x"</p> },
"<p title=\"javascript:alert(1)\">x</p>"
);
assert_eq!(
crate::html! { <a href="javascript:void(0)">"x"</a> },
"<a href=\"javascript:void(0)\">x</a>"
);
}
#[test]
fn data_urls_are_kept_only_on_media_elements() {
let image = "data:image/png;base64,iVBORw0KGgo=";
assert_eq!(
crate::html! { <img src={image} /> },
format!("<img src=\"{image}\" />")
);
assert_eq!(
crate::html! { <video poster={image}><source src={image} /></video> },
format!("<video poster=\"{image}\"><source src=\"{image}\" /></video>")
);
assert_eq!(
crate::html! { <img src={"javascript:alert(1)"} /> },
"<img src=\"#\" />"
);
assert_eq!(
crate::html! { <a href={image}>"x"</a> },
"<a href=\"#\">x</a>"
);
assert_eq!(
crate::html! { <embed src={image} /> },
"<embed src=\"#\" />"
);
}