Skip to main content

adk_browser/
escape.rs

1//! String escaping utilities for safe JavaScript interpolation.
2
3/// Escape a string for safe interpolation into JavaScript code.
4///
5/// This prevents CSS selector injection attacks when user-supplied
6/// selectors are interpolated into JavaScript strings. It escapes
7/// characters that could break out of a JS string literal or inject
8/// malicious code.
9///
10/// # Example
11///
12/// ```
13/// use adk_browser::escape_js_string;
14///
15/// let safe = escape_js_string("div[data-id='test']");
16/// assert!(safe.contains("\\'"));  // single quotes are escaped
17/// ```
18pub 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        // The single quotes are escaped, so the string can't break out of a JS literal
89        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}