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("<"),
21 b'>' => result.push_str(">"),
22 b'&' => result.push_str("&"),
23 b'\'' => result.push_str("'"),
24 b'"' => result.push_str("""),
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("<"),
35 b'>' => result.push_str(">"),
36 b'&' => result.push_str("&"),
37 b'\'' => result.push_str("'"),
38 b'"' => result.push_str("""),
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("< < <"), "< < <");
54 assert_eq!(
55 xml_escape("<script>alert('Hello XSS')</script>"),
56 "<script>alert('Hello XSS')</script>"
57 );
58}