hard_xml/
xml_escape.rs

1use jetscii::{bytes, BytesConst};
2use lazy_static::lazy_static;
3use std::borrow::Cow;
4
5pub fn xml_escape(raw: &str) -> Cow<'_, str> {
6    lazy_static! {
7        static ref ESCAPE_BYTES: BytesConst = bytes!(b'<', b'>', b'&', b'\'', b'"');
8    }
9
10    let bytes = raw.as_bytes();
11
12    if let Some(off) = ESCAPE_BYTES.find(bytes) {
13        let mut result = String::with_capacity(raw.len());
14
15        result.push_str(&raw[0..off]);
16
17        let mut pos = off + 1;
18
19        match bytes[pos - 1] {
20            b'<' => result.push_str("&lt;"),
21            b'>' => result.push_str("&gt;"),
22            b'&' => result.push_str("&amp;"),
23            b'\'' => result.push_str("&apos;"),
24            b'"' => result.push_str("&quot;"),
25            _ => unreachable!(),
26        }
27
28        while let Some(off) = ESCAPE_BYTES.find(&bytes[pos..]) {
29            result.push_str(&raw[pos..pos + off]);
30
31            pos += off + 1;
32
33            match bytes[pos - 1] {
34                b'<' => result.push_str("&lt;"),
35                b'>' => result.push_str("&gt;"),
36                b'&' => result.push_str("&amp;"),
37                b'\'' => result.push_str("&apos;"),
38                b'"' => result.push_str("&quot;"),
39                _ => unreachable!(),
40            }
41        }
42
43        result.push_str(&raw[pos..]);
44
45        Cow::Owned(result)
46    } else {
47        Cow::Borrowed(raw)
48    }
49}
50
51#[test]
52fn test_escape() {
53    assert_eq!(xml_escape("< < <"), "&lt; &lt; &lt;");
54    assert_eq!(
55        xml_escape("<script>alert('Hello XSS')</script>"),
56        "&lt;script&gt;alert(&apos;Hello XSS&apos;)&lt;/script&gt;"
57    );
58}