pub fn escape_html(input: &str) -> String {
let mut output = String::with_capacity(input.len());
for ch in input.chars() {
match ch {
'&' => output.push_str("&"),
'<' => output.push_str("<"),
'>' => output.push_str(">"),
'"' => output.push_str("""),
'\'' => output.push_str("'"),
'/' => output.push_str("/"),
_ => output.push(ch),
}
}
output
}
pub fn escape_attr(input: &str) -> String {
let mut output = String::with_capacity(input.len());
for ch in input.chars() {
match ch {
'&' => output.push_str("&"),
'<' => output.push_str("<"),
'>' => output.push_str(">"),
'"' => output.push_str("""),
'\'' => output.push_str("'"),
'`' => output.push_str("`"),
'/' => output.push_str("/"),
'=' => output.push_str("="),
_ => output.push(ch),
}
}
output
}
pub fn is_safe_class_name(class: &str) -> bool {
if class.is_empty() {
return false;
}
class
.chars()
.all(|c| c.is_alphanumeric() || c == '-' || c == '_' || c == ':' || c == '/' || c == '.')
}
pub fn is_safe_css_value(value: &str) -> bool {
let dangerous_patterns = [
"expression(",
"url(",
"javascript:",
"data:",
"vbscript:",
"@import",
"behavior:",
"-moz-binding:",
"</style",
];
let lower = value.to_lowercase();
!dangerous_patterns
.iter()
.any(|pattern| lower.contains(pattern))
}
pub fn sanitize_id(input: &str) -> String {
input
.chars()
.filter(|c| c.is_alphanumeric() || *c == '-' || *c == '_')
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn escape_html_basic() {
assert_eq!(escape_html("hello"), "hello");
assert_eq!(
escape_html("<script>alert('xss')</script>"),
"<script>alert('xss')</script>"
);
}
#[test]
fn escape_html_entities() {
assert_eq!(escape_html("a & b"), "a & b");
assert_eq!(escape_html("a < b"), "a < b");
assert_eq!(escape_html("a > b"), "a > b");
assert_eq!(escape_html("a \"b\""), "a "b"");
}
#[test]
fn escape_attr_basic() {
assert_eq!(escape_attr("hello"), "hello");
assert_eq!(
escape_attr("\" onmouseover=\"alert(1)"),
"" onmouseover="alert(1)"
);
}
#[test]
fn escape_attr_covers_slash_and_equals() {
assert!(escape_attr("a/b").contains("/"));
assert!(escape_attr("a=b").contains("="));
assert!(escape_attr("a`b").contains("`"));
}
#[test]
fn is_safe_class_name_valid() {
assert!(is_safe_class_name("btn"));
assert!(is_safe_class_name("btn-primary"));
assert!(is_safe_class_name("text-xl"));
assert!(is_safe_class_name("hover:bg-blue-500"));
assert!(is_safe_class_name("w-1/2"));
assert!(is_safe_class_name("mt-2.5"));
}
#[test]
fn is_safe_class_name_invalid() {
assert!(!is_safe_class_name(""));
assert!(!is_safe_class_name("btn; color: red"));
assert!(!is_safe_class_name("btn\" onclick=\""));
assert!(!is_safe_class_name("btn<script>"));
}
#[test]
fn is_safe_css_value_valid() {
assert!(is_safe_css_value("red"));
assert!(is_safe_css_value("16px"));
assert!(is_safe_css_value("#ff0000"));
assert!(is_safe_css_value("hsl(0 0% 100%)"));
assert!(is_safe_css_value("1px solid black"));
}
#[test]
fn is_safe_css_value_injection() {
assert!(!is_safe_css_value("expression(alert(1))"));
assert!(!is_safe_css_value("url(javascript:alert(1))"));
assert!(!is_safe_css_value("red; @import 'evil.css'"));
assert!(!is_safe_css_value("</style><script>"));
}
#[test]
fn sanitize_id_basic() {
assert_eq!(sanitize_id("my-button"), "my-button");
assert_eq!(sanitize_id("btn_123"), "btn_123");
assert_eq!(
sanitize_id("btn\" onclick=\"alert(1)"),
"btnonclickalert1"
);
assert_eq!(sanitize_id("btn<script>"), "btnscript");
}
}