1#[must_use]
10pub fn decode(raw: &[u8]) -> String {
11 let mut output = String::with_capacity(raw.len());
12 let mut position = 0;
13 let mut alphabet = b'A';
14 while position < raw.len() {
15 match raw[position] {
16 b'\'' if raw.get(position + 1) == Some(&b'\'') => {
17 output.push('\'');
18 position += 2;
19 }
20 b'\'' => {
21 output.push('\'');
22 position += 1;
23 }
24 b'\\' => {
25 if let Some(consumed) = decode_escape(&raw[position..], &mut output, &mut alphabet)
26 {
27 position += consumed;
28 } else {
29 output.push('\\');
30 position += 1;
31 }
32 }
33 _ => {
34 let start = position;
35 while !matches!(raw.get(position), None | Some(b'\'' | b'\\')) {
36 position += 1;
37 }
38 output.push_str(&String::from_utf8_lossy(&raw[start..position]));
39 }
40 }
41 }
42 output
43}
44
45fn decode_escape(input: &[u8], output: &mut String, alphabet: &mut u8) -> Option<usize> {
46 match input.get(1)? {
47 b'\\' => {
48 output.push('\\');
49 Some(2)
50 }
51 b'N' | b'n' | b'F' | b'f' if input.get(2) == Some(&b'\\') => Some(3),
52 b'S' | b's' if input.get(2) == Some(&b'\\') => {
53 let byte = input.get(3)?.wrapping_add(128);
54 output.push(decode_alphabet_byte(*alphabet, byte));
55 Some(4)
56 }
57 b'P' | b'p'
58 if matches!(input.get(2), Some(b'A'..=b'I' | b'a'..=b'i'))
59 && input.get(3) == Some(&b'\\') =>
60 {
61 *alphabet = input[2].to_ascii_uppercase();
62 Some(4)
63 }
64 b'X' | b'x' => match input.get(2)? {
65 b'\\' => {
66 let byte = u8::try_from(parse_hex(input.get(3..5)?)?).ok()?;
67 output.push(char::from(byte));
68 Some(5)
69 }
70 b'2' => {
71 decode_wide(input, output, 4).or_else(|| preserve_malformed_wide(input, output))
72 }
73 b'4' => {
74 decode_wide(input, output, 8).or_else(|| preserve_malformed_wide(input, output))
75 }
76 _ => None,
77 },
78 _ => None,
79 }
80}
81
82fn decode_alphabet_byte(alphabet: u8, byte: u8) -> char {
83 if alphabet == b'A' {
84 return char::from(byte);
85 }
86 let encoding = match alphabet {
87 b'B' => encoding_rs::ISO_8859_2,
88 b'C' => encoding_rs::ISO_8859_3,
89 b'D' => encoding_rs::ISO_8859_4,
90 b'E' => encoding_rs::ISO_8859_5,
91 b'F' => encoding_rs::ISO_8859_6,
92 b'G' => encoding_rs::ISO_8859_7,
93 b'H' => encoding_rs::ISO_8859_8,
94 b'I' => encoding_rs::WINDOWS_1254,
95 _ => encoding_rs::WINDOWS_1252,
96 };
97 let bytes = [byte];
98 let (decoded, _, _) = encoding.decode(&bytes);
99 decoded.chars().next().unwrap_or('\u{fffd}')
100}
101
102fn parse_hex(input: &[u8]) -> Option<u32> {
103 u32::from_str_radix(std::str::from_utf8(input).ok()?, 16).ok()
104}
105
106fn preserve_malformed_wide(input: &[u8], output: &mut String) -> Option<usize> {
107 let end = input
108 .get(4..)?
109 .windows(4)
110 .position(|window| window.eq_ignore_ascii_case(b"\\X0\\"))?;
111 let consumed = 4 + end + 4;
112 output.push_str(&String::from_utf8_lossy(&input[..consumed]));
113 Some(consumed)
114}
115
116fn decode_wide(input: &[u8], output: &mut String, digits: usize) -> Option<usize> {
117 if input.get(3) != Some(&b'\\') {
118 return None;
119 }
120 let mut position = 4;
121 let mut utf16 = Vec::new();
122 let mut utf32 = Vec::new();
123 loop {
124 if input
125 .get(position..position + 4)?
126 .eq_ignore_ascii_case(b"\\X0\\")
127 {
128 position += 4;
129 break;
130 }
131 let unit = parse_hex(input.get(position..position + digits)?)?;
132 if digits == 4 {
133 utf16.push(u16::try_from(unit).ok()?);
134 } else {
135 utf32.push(unit);
136 }
137 position += digits;
138 }
139 let mut decoded = String::new();
140 if digits == 4 {
141 if utf16.is_empty() {
142 return None;
143 }
144 for value in std::char::decode_utf16(utf16) {
145 decoded.push(value.ok()?);
146 }
147 } else {
148 if utf32.is_empty() {
149 return None;
150 }
151 for value in utf32 {
152 decoded.push(char::from_u32(value)?);
153 }
154 }
155 output.push_str(&decoded);
156 Some(position)
157}
158
159fn flush_utf16(pending: &mut Vec<u16>, output: &mut String) {
160 if pending.is_empty() {
161 return;
162 }
163 output.push_str("\\X2\\");
164 for unit in pending.drain(..) {
165 use std::fmt::Write as _;
166 let _ = write!(output, "{unit:04X}");
167 }
168 output.push_str("\\X0\\");
169}
170
171#[must_use]
177pub fn encode(text: &str) -> String {
178 let mut output = String::with_capacity(text.len());
179 let mut pending = Vec::new();
180
181 for character in text.chars() {
182 match character {
183 '\'' => {
184 flush_utf16(&mut pending, &mut output);
185 output.push_str("''");
186 }
187 '\\' => {
188 flush_utf16(&mut pending, &mut output);
189 output.push_str("\\\\");
190 }
191 value if value.is_ascii_control() => {
192 use std::fmt::Write as _;
193
194 flush_utf16(&mut pending, &mut output);
195 let _ = write!(output, "\\X\\{:02X}", value as u32);
196 }
197 value if value.is_ascii() => {
198 flush_utf16(&mut pending, &mut output);
199 output.push(value);
200 }
201 value if u32::from(value) <= 0xffff => {
202 let mut units = [0_u16; 2];
203 pending.extend_from_slice(value.encode_utf16(&mut units));
204 }
205 value => {
206 use std::fmt::Write as _;
207
208 flush_utf16(&mut pending, &mut output);
209 let _ = write!(output, "\\X4\\{:08X}\\X0\\", u32::from(value));
210 }
211 }
212 }
213 flush_utf16(&mut pending, &mut output);
214 output
215}