aws_smithy_xml/
escape.rs
1use std::borrow::Cow;
7use std::fmt::Write;
8
9const ESCAPES: &[char] = &[
10 '&', '\'', '\"', '<', '>', '\u{00D}', '\u{00A}', '\u{0085}', '\u{2028}',
11];
12
13pub(crate) fn escape(s: &str) -> Cow<'_, str> {
14 let mut remaining = s;
15 if !s.contains(ESCAPES) {
16 return Cow::Borrowed(s);
17 }
18 let mut out = String::new();
19 while let Some(idx) = remaining.find(ESCAPES) {
20 out.push_str(&remaining[..idx]);
21 remaining = &remaining[idx..];
22 let mut idxs = remaining.char_indices();
23 let (_, chr) = idxs.next().expect("must not be none");
24 match chr {
25 '>' => out.push_str(">"),
26 '<' => out.push_str("<"),
27 '\'' => out.push_str("'"),
28 '"' => out.push_str("""),
29 '&' => out.push_str("&"),
30 other => {
32 write!(&mut out, "&#x{:X};", other as u32).expect("write to string cannot fail")
33 }
34 };
35 match idxs.next() {
36 None => remaining = "",
37 Some((idx, _)) => remaining = &remaining[idx..],
38 }
39 }
40 out.push_str(remaining);
41 Cow::Owned(out)
42}
43
44#[cfg(test)]
45mod test {
46 #[test]
47 fn escape_basic() {
48 let inp = "<helo>&\"'";
49 assert_eq!(escape(inp), "<helo>&"'");
50 }
51
52 #[test]
53 fn escape_eol_encoding_sep() {
54 let test_cases = vec![
55 ("CiAK", "
 
"), ("YQ0KIGIKIGMN", "a
 b
 c
"), ("YQ3ChSBiwoU", "a
… b…"), ("YQ3igKggYsKFIGPigKg=", "a

 b… c
"), ];
60 for (base64_encoded, expected_xml_output) in test_cases {
61 let bytes = base64::decode(base64_encoded).expect("valid base64");
62 let input = String::from_utf8(bytes).expect("valid utf-8");
63 assert_eq!(escape(&input), expected_xml_output);
64 }
65 }
66
67 use crate::escape::escape;
68 use proptest::proptest;
69 proptest! {
70 #[test]
72 fn round_trip(s: String) {
73 let encoded = escape(&s);
74 let decoded = crate::unescape::unescape(&encoded).expect("encoded should be valid decoded");
75 assert_eq!(decoded, s);
76 }
77 }
78}