pptxboss_core/
encoding.rs1pub fn utf16_xml_to_utf8(bytes: &[u8]) -> Option<Vec<u8>> {
8 let (little_endian, start) = match bytes {
9 [0xff, 0xfe, ..] => (true, 2),
10 [0xfe, 0xff, ..] => (false, 2),
11 [b'<', 0, b'?', 0, ..] => (true, 0),
12 [0, b'<', 0, b'?', ..] => (false, 0),
13 _ => return None,
14 };
15 let (pairs, _) = bytes[start..].as_chunks::<2>();
16 let units = pairs.iter().map(|pair| match little_endian {
17 true => u16::from_le_bytes(*pair),
18 false => u16::from_be_bytes(*pair),
19 });
20 let mut out = String::with_capacity(bytes.len() / 2);
21 for ch in char::decode_utf16(units) {
22 out.push(ch.unwrap_or(char::REPLACEMENT_CHARACTER));
23 }
24 Some(out.into_bytes())
25}
26
27pub fn is_utf16_xml(bytes: &[u8]) -> bool {
29 matches!(
30 bytes,
31 [0xff, 0xfe, ..] | [0xfe, 0xff, ..] | [b'<', 0, b'?', 0, ..] | [0, b'<', 0, b'?', ..]
32 )
33}
34
35#[cfg(test)]
36mod tests {
37 use super::*;
38
39 fn utf16le(text: &str, bom: bool) -> Vec<u8> {
40 let mut out = Vec::new();
41 if bom {
42 out.extend([0xff, 0xfe]);
43 }
44 for unit in text.encode_utf16() {
45 out.extend(unit.to_le_bytes());
46 }
47 out
48 }
49
50 #[test]
51 fn transcodes_both_byte_orders_with_and_without_a_mark() {
52 let xml = "<?xml version=\"1.0\" encoding=\"UTF-16\"?><a>héllo 😀</a>";
53 assert_eq!(
54 utf16_xml_to_utf8(&utf16le(xml, true)).unwrap(),
55 xml.as_bytes()
56 );
57 assert_eq!(
58 utf16_xml_to_utf8(&utf16le(xml, false)).unwrap(),
59 xml.as_bytes()
60 );
61 let mut big_endian = vec![0xfe, 0xff];
62 for unit in xml.encode_utf16() {
63 big_endian.extend(unit.to_be_bytes());
64 }
65 assert_eq!(utf16_xml_to_utf8(&big_endian).unwrap(), xml.as_bytes());
66 }
67
68 #[test]
69 fn leaves_utf8_and_binaries_alone() {
70 assert!(utf16_xml_to_utf8(b"<?xml version=\"1.0\"?><a/>").is_none());
71 assert!(utf16_xml_to_utf8(&[0x89, b'P', b'N', b'G']).is_none());
72 assert!(utf16_xml_to_utf8(&[0xff, 0xd8, 0xff, 0xe0]).is_none());
73 assert!(!is_utf16_xml(b""));
74 }
75
76 #[test]
77 fn a_lone_surrogate_becomes_the_replacement_character() {
78 let mut bytes = utf16le("<a>", true);
79 bytes.extend(0xd800u16.to_le_bytes());
80 bytes.extend(utf16le("</a>", false));
81 assert_eq!(
82 utf16_xml_to_utf8(&bytes).unwrap(),
83 "<a>\u{fffd}</a>".as_bytes()
84 );
85 }
86}