Skip to main content

fakecloud_aws/
xml.rs

1/// Helper to wrap an XML body with the standard XML declaration.
2pub fn wrap_xml(inner: &str) -> String {
3    format!("<?xml version=\"1.0\" encoding=\"UTF-8\"?>{inner}")
4}
5
6/// Escape a string for safe embedding in XML content.
7///
8/// Handles the five standard XML entities plus control characters that are
9/// invalid in XML 1.0 (everything below U+0020 except `\t`, `\n`, `\r`).
10pub fn xml_escape(s: &str) -> String {
11    let mut out = String::with_capacity(s.len());
12    for c in s.chars() {
13        match c {
14            '&' => out.push_str("&amp;"),
15            '<' => out.push_str("&lt;"),
16            '>' => out.push_str("&gt;"),
17            '"' => out.push_str("&quot;"),
18            '\'' => out.push_str("&apos;"),
19            // XML 1.0 allows \t, \n, \r as valid characters; all other control chars
20            // need to be encoded as numeric character references.
21            c if (c as u32) < 0x20 && c != '\t' && c != '\n' && c != '\r' => {
22                out.push_str(&format!("&#x{:X};", c as u32));
23            }
24            c => out.push(c),
25        }
26    }
27    out
28}
29
30#[cfg(test)]
31mod tests {
32    use super::*;
33
34    #[test]
35    fn wrap_xml_prepends_declaration() {
36        let out = wrap_xml("<foo/>");
37        assert!(out.starts_with("<?xml"));
38        assert!(out.contains("<foo/>"));
39    }
40
41    #[test]
42    fn xml_escape_standard_entities() {
43        assert_eq!(
44            xml_escape("a&b<c>d\"e'f"),
45            "a&amp;b&lt;c&gt;d&quot;e&apos;f"
46        );
47    }
48
49    #[test]
50    fn xml_escape_preserves_whitespace() {
51        assert_eq!(xml_escape("a\tb\nc\rd"), "a\tb\nc\rd");
52    }
53
54    #[test]
55    fn xml_escape_control_chars() {
56        let input = "a\x01b";
57        let out = xml_escape(input);
58        assert_eq!(out, "a&#x1;b");
59    }
60
61    #[test]
62    fn xml_escape_plain_chars() {
63        assert_eq!(xml_escape("hello"), "hello");
64    }
65}