pub(super) fn escape_html(s: &str) -> String {
s.replace('&', "&")
.replace('<', "<")
.replace('>', ">")
.replace('"', """)
}
#[cfg(test)]
mod tests {
use super::escape_html;
#[test]
fn neutralizes_script_tags() {
let escaped = escape_html("<script>alert('x')</script>");
assert_eq!(
escaped, "<script>alert('x')</script>",
"angle brackets must be escaped so a snippet cannot open a tag"
);
assert!(!escaped.contains('<'));
assert!(!escaped.contains('>'));
}
#[test]
fn neutralizes_attribute_injection() {
let escaped = escape_html(r#"" onmouseover="steal()"#);
assert_eq!(escaped, "" onmouseover="steal()");
assert!(!escaped.contains('"'));
}
#[test]
fn escapes_ampersand_before_entities_to_avoid_double_encoding() {
assert_eq!(escape_html("a & b"), "a & b");
assert_eq!(escape_html("<"), "&lt;");
}
#[test]
fn leaves_plain_text_untouched() {
let snippet = "let total = sum(items); // ok";
assert_eq!(escape_html(snippet), snippet);
}
#[test]
fn round_trips_a_realistic_code_snippet() {
let snippet = r#"const html = `<div class="x">${user}</div>`;"#;
let escaped = escape_html(snippet);
assert_eq!(
escaped,
r#"const html = `<div class="x">${user}</div>`;"#
);
for dangerous in ['<', '>', '"'] {
assert!(
!escaped.contains(dangerous),
"{dangerous:?} leaked through escaping"
);
}
}
}