cadmpeg_core/decode/
utf16.rs1use crate::CodecError;
5
6use super::{DecodeContext, View};
7
8#[derive(Clone, Copy)]
9enum Surrogates {
10 Reject,
11 Replace,
12}
13
14impl DecodeContext<'_> {
15 pub fn utf16le_text(
17 &self,
18 bytes: &[u8],
19 units: usize,
20 trim_nul: bool,
21 _operation: &'static str,
22 ) -> Result<String, CodecError> {
23 decode_text(bytes, units, trim_nul, Surrogates::Reject)
24 }
25
26 pub fn utf16le_lossy_text(
29 &self,
30 bytes: &[u8],
31 units: usize,
32 trim_nul: bool,
33 _operation: &'static str,
34 ) -> Result<String, CodecError> {
35 decode_text(bytes, units, trim_nul, Surrogates::Replace)
36 }
37}
38
39fn decode_text(
40 bytes: &[u8],
41 units: usize,
42 trim_nul: bool,
43 surrogates: Surrogates,
44) -> Result<String, CodecError> {
45 let byte_len = units
46 .checked_mul(2)
47 .ok_or_else(|| CodecError::malformed("UTF-16LE byte length overflow"))?;
48 let bytes = bytes
49 .get(..byte_len)
50 .ok_or_else(|| CodecError::malformed("truncated UTF-16LE text"))?;
51 characters(bytes, trim_nul, surrogates)
52 .collect::<Result<String, _>>()
53 .map_err(|_| CodecError::malformed("invalid UTF-16LE surrogate sequence"))
54}
55
56fn characters(
57 bytes: &[u8],
58 trim_nul: bool,
59 surrogates: Surrogates,
60) -> impl Iterator<Item = Result<char, std::char::DecodeUtf16Error>> + '_ {
61 let mut view = View::over_retained(bytes);
62 char::decode_utf16(
63 std::iter::from_fn(move || view.u16_le()).take_while(move |unit| !trim_nul || *unit != 0),
64 )
65 .map(move |character| match (surrogates, character) {
66 (Surrogates::Replace, Err(_)) => Ok(char::REPLACEMENT_CHARACTER),
67 (_, character) => character,
68 })
69}
70
71#[cfg(test)]
72mod tests {
73 use crate::decode::{DecodeArena, DecodeContext, DecodePolicy};
74 use crate::CodecError;
75
76 #[test]
77 fn utf16le_lossy_text_trims_first_nul() {
78 let arena = DecodeArena::new();
79 let (ctx, _) = DecodeContext::from_root_bytes(&[], &arena, &DecodePolicy::default())
80 .expect("empty root");
81 assert_eq!(
82 ctx.utf16le_lossy_text(&[0, 0xd8, 0, 0, 0, 0xdc], 3, true, "replacement test")
83 .expect("terminated replacement text"),
84 "�"
85 );
86 }
87
88 #[test]
89 fn utf16le_text_rejects_odd_byte_window() {
90 let arena = DecodeArena::new();
91 let (ctx, _) = DecodeContext::from_root_bytes(b"", &arena, &DecodePolicy::default())
92 .expect("empty root context");
93 assert!(matches!(
94 ctx.utf16le_text(&[0x41], 1, false, "UTF-16 test"),
95 Err(CodecError::Malformed(_))
96 ));
97 assert!(matches!(
98 ctx.utf16le_text(&[], usize::MAX, false, "UTF-16 test"),
99 Err(CodecError::Malformed(_))
100 ));
101 }
102
103 #[test]
104 fn utf16le_text_rejects_invalid_surrogates() {
105 let arena = DecodeArena::new();
106 let (ctx, _) = DecodeContext::from_root_bytes(b"", &arena, &DecodePolicy::default())
107 .expect("empty root context");
108 for bytes in [&[0, 0xd8][..], &[0, 0xdc][..], &[0, 0xd8, 0x41, 0][..]] {
109 assert!(matches!(
110 ctx.utf16le_text(bytes, bytes.len() / 2, false, "UTF-16 test"),
111 Err(CodecError::Malformed(_))
112 ));
113 }
114 }
115}