1pub fn escape_js_string(input: &str) -> String {
19 let mut escaped = String::with_capacity(input.len() + 16);
20 for ch in input.chars() {
21 match ch {
22 '\\' => escaped.push_str("\\\\"),
23 '\'' => escaped.push_str("\\'"),
24 '"' => escaped.push_str("\\\""),
25 '`' => escaped.push_str("\\`"),
26 '\n' => escaped.push_str("\\n"),
27 '\r' => escaped.push_str("\\r"),
28 '\0' => escaped.push_str("\\0"),
29 '<' => escaped.push_str("\\x3c"),
30 '>' => escaped.push_str("\\x3e"),
31 _ => escaped.push(ch),
32 }
33 }
34 escaped
35}
36
37#[cfg(test)]
38mod tests {
39 use super::*;
40
41 #[test]
42 fn test_escape_single_quotes() {
43 assert_eq!(escape_js_string("a'b"), "a\\'b");
44 }
45
46 #[test]
47 fn test_escape_double_quotes() {
48 assert_eq!(escape_js_string("a\"b"), "a\\\"b");
49 }
50
51 #[test]
52 fn test_escape_backslash() {
53 assert_eq!(escape_js_string("a\\b"), "a\\\\b");
54 }
55
56 #[test]
57 fn test_escape_backtick() {
58 assert_eq!(escape_js_string("a`b"), "a\\`b");
59 }
60
61 #[test]
62 fn test_escape_newlines() {
63 assert_eq!(escape_js_string("a\nb"), "a\\nb");
64 assert_eq!(escape_js_string("a\rb"), "a\\rb");
65 }
66
67 #[test]
68 fn test_escape_null_byte() {
69 assert_eq!(escape_js_string("a\0b"), "a\\0b");
70 }
71
72 #[test]
73 fn test_escape_script_tags() {
74 assert_eq!(escape_js_string("</script>"), "\\x3c/script\\x3e");
75 }
76
77 #[test]
78 fn test_escape_normal_selector() {
79 assert_eq!(escape_js_string("#my-button"), "#my-button");
80 assert_eq!(escape_js_string(".nav-link"), ".nav-link");
81 assert_eq!(escape_js_string("button[type=submit]"), "button[type=submit]");
82 }
83
84 #[test]
85 fn test_escape_injection_attempt() {
86 let malicious = "'); document.cookie='stolen'; ('";
87 let escaped = escape_js_string(malicious);
88 assert!(escaped.starts_with("\\'"));
90 assert!(escaped.contains("\\'"));
91 }
92
93 #[test]
94 fn test_escape_empty_string() {
95 assert_eq!(escape_js_string(""), "");
96 }
97}