1use crate::encoding_tables::*;
5use crate::error::{XmlError, XML_ERR_UNSUPPORTED_ENCODING};
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq)]
9#[repr(i32)]
10pub enum XmlCharEncoding {
11 Error = -1,
12 None = 0,
13 Utf8 = 1,
14 Utf16Le = 2,
15 Utf16Be = 3,
16 Ucs4Le = 4,
17 Ucs4Be = 5,
18 Ebcdic = 6,
19 Ucs4_2143 = 7,
20 Ucs4_3412 = 8,
21 Ucs2 = 9,
22 Iso8859_1 = 10,
23 Iso8859_2 = 11,
24 Iso8859_3 = 12,
25 Iso8859_4 = 13,
26 Iso8859_5 = 14,
27 Iso8859_6 = 15,
28 Iso8859_7 = 16,
29 Iso8859_8 = 17,
30 Iso8859_9 = 18,
31 Iso2022Jp = 19,
32 ShiftJis = 20,
33 EucJp = 21,
34 Ascii = 22,
35 Utf16 = 23,
36 Html = 24,
37 Iso8859_10 = 25,
38 Iso8859_11 = 26,
39 Iso8859_13 = 27,
40 Iso8859_14 = 28,
41 Iso8859_15 = 29,
42 Iso8859_16 = 30,
43 Windows1252 = 31,
44}
45
46const NAME_MAP: &[(&str, XmlCharEncoding)] = &[
47 ("ascii", XmlCharEncoding::Ascii),
48 ("csisolatin1", XmlCharEncoding::Iso8859_1),
49 ("iso-8859-1", XmlCharEncoding::Iso8859_1),
50 ("iso-8859-2", XmlCharEncoding::Iso8859_2),
51 ("iso-8859-3", XmlCharEncoding::Iso8859_3),
52 ("iso-8859-4", XmlCharEncoding::Iso8859_4),
53 ("iso-8859-5", XmlCharEncoding::Iso8859_5),
54 ("iso-8859-6", XmlCharEncoding::Iso8859_6),
55 ("iso-8859-7", XmlCharEncoding::Iso8859_7),
56 ("iso-8859-8", XmlCharEncoding::Iso8859_8),
57 ("iso-8859-9", XmlCharEncoding::Iso8859_9),
58 ("iso-8859-10", XmlCharEncoding::Iso8859_10),
59 ("iso-8859-11", XmlCharEncoding::Iso8859_11),
60 ("iso-8859-13", XmlCharEncoding::Iso8859_13),
61 ("iso-8859-14", XmlCharEncoding::Iso8859_14),
62 ("iso-8859-15", XmlCharEncoding::Iso8859_15),
63 ("iso-8859-16", XmlCharEncoding::Iso8859_16),
64 ("iso8859-1", XmlCharEncoding::Iso8859_1),
65 ("iso_8859-1", XmlCharEncoding::Iso8859_1),
66 ("latin1", XmlCharEncoding::Iso8859_1),
67 ("us-ascii", XmlCharEncoding::Ascii),
68 ("utf-16", XmlCharEncoding::Utf16),
69 ("utf-16be", XmlCharEncoding::Utf16Be),
70 ("utf-16le", XmlCharEncoding::Utf16Le),
71 ("utf-8", XmlCharEncoding::Utf8),
72 ("utf16", XmlCharEncoding::Utf16),
73 ("utf8", XmlCharEncoding::Utf8),
74 ("unicode", XmlCharEncoding::Utf16),
75 ("ucs-2", XmlCharEncoding::Ucs2),
76 ("ucs-4", XmlCharEncoding::Ucs4Le),
77 ("ucs2", XmlCharEncoding::Ucs2),
78 ("ucs4", XmlCharEncoding::Ucs4Le),
79 ("windows-1252", XmlCharEncoding::Windows1252),
80 ("x-cp1252", XmlCharEncoding::Windows1252),
81 ("ibm037", XmlCharEncoding::Ebcdic),
82 ("ebcdic", XmlCharEncoding::Ebcdic),
83 ("iso-2022-jp", XmlCharEncoding::Iso2022Jp),
84 ("shift_jis", XmlCharEncoding::ShiftJis),
85 ("shift-jis", XmlCharEncoding::ShiftJis),
86 ("sjis", XmlCharEncoding::ShiftJis),
87 ("euc-jp", XmlCharEncoding::EucJp),
88];
89
90#[doc(alias = "xmlParseCharEncoding")]
92pub fn xml_parse_char_encoding(name: &str) -> XmlCharEncoding {
93 let lower = name.trim().to_ascii_lowercase();
94 for (n, enc) in NAME_MAP {
95 if *n == lower {
96 return if *enc == XmlCharEncoding::Utf16 {
97 XmlCharEncoding::Utf16Le
98 } else {
99 *enc
100 };
101 }
102 }
103 XmlCharEncoding::Error
104}
105
106#[doc(alias = "xmlGetCharEncodingName")]
108pub fn xml_get_char_encoding_name(enc: XmlCharEncoding) -> Option<&'static str> {
109 Some(match enc {
110 XmlCharEncoding::Utf8 => "UTF-8",
111 XmlCharEncoding::Utf16Le | XmlCharEncoding::Utf16Be | XmlCharEncoding::Utf16 => "UTF-16",
112 XmlCharEncoding::Ucs4Le | XmlCharEncoding::Ucs4Be => "UCS-4",
113 XmlCharEncoding::Iso8859_1 => "ISO-8859-1",
114 XmlCharEncoding::Iso8859_2 => "ISO-8859-2",
115 XmlCharEncoding::Iso8859_3 => "ISO-8859-3",
116 XmlCharEncoding::Iso8859_4 => "ISO-8859-4",
117 XmlCharEncoding::Iso8859_5 => "ISO-8859-5",
118 XmlCharEncoding::Iso8859_6 => "ISO-8859-6",
119 XmlCharEncoding::Iso8859_7 => "ISO-8859-7",
120 XmlCharEncoding::Iso8859_8 => "ISO-8859-8",
121 XmlCharEncoding::Iso8859_9 => "ISO-8859-9",
122 XmlCharEncoding::Iso8859_10 => "ISO-8859-10",
123 XmlCharEncoding::Iso8859_11 => "ISO-8859-11",
124 XmlCharEncoding::Iso8859_13 => "ISO-8859-13",
125 XmlCharEncoding::Iso8859_14 => "ISO-8859-14",
126 XmlCharEncoding::Iso8859_15 => "ISO-8859-15",
127 XmlCharEncoding::Iso8859_16 => "ISO-8859-16",
128 XmlCharEncoding::Ascii => "US-ASCII",
129 XmlCharEncoding::Windows1252 => "windows-1252",
130 XmlCharEncoding::Ebcdic => "IBM037",
131 XmlCharEncoding::Ucs2 => "UCS-2",
132 XmlCharEncoding::Iso2022Jp => "ISO-2022-JP",
133 XmlCharEncoding::ShiftJis => "Shift_JIS",
134 XmlCharEncoding::EucJp => "EUC-JP",
135 XmlCharEncoding::Html => "HTML",
136 XmlCharEncoding::None | XmlCharEncoding::Error | XmlCharEncoding::Ucs4_2143 | XmlCharEncoding::Ucs4_3412 => {
137 return None
138 }
139 })
140}
141
142#[doc(alias = "xmlDetectCharEncoding")]
144pub fn xml_detect_char_encoding(input: &[u8]) -> XmlCharEncoding {
145 if input.len() >= 4 {
146 let b = &input[..4];
147 if b == [0x00, 0x00, 0x00, 0x3C] {
148 return XmlCharEncoding::Ucs4Be;
149 }
150 if b == [0x3C, 0x00, 0x00, 0x00] {
151 return XmlCharEncoding::Ucs4Le;
152 }
153 if b == [0x00, 0x00, 0x3C, 0x00] {
154 return XmlCharEncoding::Ucs4_2143;
155 }
156 if b == [0x00, 0x3C, 0x00, 0x00] {
157 return XmlCharEncoding::Ucs4_3412;
158 }
159 if b == [0x4C, 0x6F, 0xA7, 0x94] {
160 return XmlCharEncoding::Ebcdic;
161 }
162 if b == [0x3C, 0x3F, 0x78, 0x6D] {
163 return XmlCharEncoding::Utf8;
164 }
165 if b == [0x3C, 0x00, 0x3F, 0x00] {
166 return XmlCharEncoding::Utf16Le;
167 }
168 if b == [0x00, 0x3C, 0x00, 0x3F] {
169 return XmlCharEncoding::Utf16Be;
170 }
171 if b == [0x00, 0x00, 0xFE, 0xFF] {
172 return XmlCharEncoding::Ucs4Be;
173 }
174 if b == [0xFF, 0xFE, 0x00, 0x00] {
175 return XmlCharEncoding::Ucs4Le;
176 }
177 }
178 if input.len() >= 3 && input[..3] == [0xEF, 0xBB, 0xBF] {
179 return XmlCharEncoding::Utf8;
180 }
181 if input.len() >= 2 {
182 if input[0] == 0xFE && input[1] == 0xFF {
183 return XmlCharEncoding::Utf16Be;
184 }
185 if input[0] == 0xFF && input[1] == 0xFE {
186 return XmlCharEncoding::Utf16Le;
187 }
188 }
189 XmlCharEncoding::None
190}
191
192fn eightbit_to_utf8(input: &[u8], table: &[u16; 128]) -> Vec<u8> {
193 let mut out = Vec::with_capacity(input.len() * 2);
194 for &b in input {
195 let cp = if b < 0x80 {
196 b as u32
197 } else {
198 table[(b - 0x80) as usize] as u32
199 };
200 if let Some(c) = char::from_u32(cp) {
201 let mut buf = [0u8; 4];
202 out.extend_from_slice(c.encode_utf8(&mut buf).as_bytes());
203 }
204 }
205 out
206}
207
208fn latin1_to_utf8(input: &[u8]) -> Vec<u8> {
209 let mut out = Vec::with_capacity(input.len() * 2);
210 for &b in input {
211 let c = b as char;
212 let mut buf = [0u8; 4];
213 out.extend_from_slice(c.encode_utf8(&mut buf).as_bytes());
214 }
215 out
216}
217
218fn utf16_to_utf8(input: &[u8], be: bool) -> Result<Vec<u8>, XmlError> {
219 let mut i = 0;
220 if input.len() >= 2 {
221 let bom = if be {
222 input[0] == 0xFE && input[1] == 0xFF
223 } else {
224 input[0] == 0xFF && input[1] == 0xFE
225 };
226 if bom {
227 i = 2;
228 }
229 }
230 let mut units = Vec::new();
231 while i + 1 < input.len() {
232 let u = if be {
233 u16::from_be_bytes([input[i], input[i + 1]])
234 } else {
235 u16::from_le_bytes([input[i], input[i + 1]])
236 };
237 units.push(u);
238 i += 2;
239 }
240 let s = String::from_utf16(&units).map_err(|_| {
241 XmlError::new(XML_ERR_UNSUPPORTED_ENCODING, "Invalid UTF-16", 0, 0)
242 })?;
243 Ok(s.into_bytes())
244}
245
246fn ucs4_to_utf8(input: &[u8], order: [usize; 4]) -> Result<Vec<u8>, XmlError> {
247 let mut i = 0;
248 if input.len() >= 4 {
249 let w = u32::from_be_bytes([input[0], input[1], input[2], input[3]]);
250 if w == 0xFEFF || w == 0xFFFE0000 {
251 i = 4;
252 }
253 }
254 let mut out = String::new();
255 while i + 3 < input.len() {
256 let b = [input[i], input[i + 1], input[i + 2], input[i + 3]];
257 let cp = u32::from_be_bytes([b[order[0]], b[order[1]], b[order[2]], b[order[3]]]);
258 match char::from_u32(cp) {
259 Some(c) if c != '\u{feff}' || !out.is_empty() => out.push(c),
260 Some(_) => {}
261 None => {
262 return Err(XmlError::new(
263 XML_ERR_UNSUPPORTED_ENCODING,
264 "Invalid UCS-4",
265 0,
266 0,
267 ));
268 }
269 }
270 i += 4;
271 }
272 Ok(out.into_bytes())
273}
274
275static CP037: [u16; 256] = [
277 0x00, 0x01, 0x02, 0x03, 0x9c, 0x09, 0x86, 0x7f, 0x97, 0x8d, 0x8e, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
278 0x10, 0x11, 0x12, 0x13, 0x9d, 0x85, 0x08, 0x87, 0x18, 0x19, 0x92, 0x8f, 0x1c, 0x1d, 0x1e, 0x1f,
279 0x80, 0x81, 0x82, 0x83, 0x84, 0x0a, 0x17, 0x1b, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x05, 0x06, 0x07,
280 0x90, 0x91, 0x16, 0x93, 0x94, 0x95, 0x96, 0x04, 0x98, 0x99, 0x9a, 0x9b, 0x14, 0x15, 0x9e, 0x1a,
281 0x20, 0xa0, 0xe2, 0xe4, 0xe0, 0xe1, 0xe3, 0xe5, 0xe7, 0xf1, 0xa2, 0x2e, 0x3c, 0x28, 0x2b, 0x7c,
282 0x26, 0xe9, 0xea, 0xeb, 0xe8, 0xed, 0xee, 0xef, 0xec, 0xdf, 0x21, 0x24, 0x2a, 0x29, 0x3b, 0xac,
283 0x2d, 0x2f, 0xc2, 0xc4, 0xc0, 0xc1, 0xc3, 0xc5, 0xc7, 0xd1, 0xa6, 0x2c, 0x25, 0x5f, 0x3e, 0x3f,
284 0xf8, 0xc9, 0xca, 0xcb, 0xc8, 0xcd, 0xce, 0xcf, 0xcc, 0x60, 0x3a, 0x23, 0x40, 0x27, 0x3d, 0x22,
285 0xd8, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0xab, 0xbb, 0xf0, 0xfd, 0xfe, 0xb1,
286 0xb0, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0xaa, 0xba, 0xe6, 0xb8, 0xc6, 0xa4,
287 0xb5, 0x7e, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0xa1, 0xbf, 0xd0, 0xdd, 0xde, 0xae,
288 0x5e, 0xa3, 0xa5, 0xb7, 0xa9, 0xa7, 0xb6, 0xbc, 0xbd, 0xbe, 0x5b, 0x5d, 0xaf, 0xa8, 0xb4, 0xd7,
289 0x7b, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0xad, 0xf4, 0xf6, 0xf2, 0xf3, 0xf5,
290 0x7d, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0xb9, 0xfb, 0xfc, 0xf9, 0xfa, 0xff,
291 0x5c, 0xf7, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0xb2, 0xd4, 0xd6, 0xd2, 0xd3, 0xd5,
292 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0xb3, 0xdb, 0xdc, 0xd9, 0xda, 0x9f,
293];
294
295fn table_for(enc: XmlCharEncoding) -> Option<&'static [u16; 128]> {
296 Some(match enc {
297 XmlCharEncoding::Iso8859_2 => &XML_UNICODE_ISO8859_2,
298 XmlCharEncoding::Iso8859_3 => &XML_UNICODE_ISO8859_3,
299 XmlCharEncoding::Iso8859_4 => &XML_UNICODE_ISO8859_4,
300 XmlCharEncoding::Iso8859_5 => &XML_UNICODE_ISO8859_5,
301 XmlCharEncoding::Iso8859_6 => &XML_UNICODE_ISO8859_6,
302 XmlCharEncoding::Iso8859_7 => &XML_UNICODE_ISO8859_7,
303 XmlCharEncoding::Iso8859_8 => &XML_UNICODE_ISO8859_8,
304 XmlCharEncoding::Iso8859_9 => &XML_UNICODE_ISO8859_9,
305 XmlCharEncoding::Iso8859_10 => &XML_UNICODE_ISO8859_10,
306 XmlCharEncoding::Iso8859_11 => &XML_UNICODE_ISO8859_11,
307 XmlCharEncoding::Iso8859_13 => &XML_UNICODE_ISO8859_13,
308 XmlCharEncoding::Iso8859_14 => &XML_UNICODE_ISO8859_14,
309 XmlCharEncoding::Iso8859_15 => &XML_UNICODE_ISO8859_15,
310 XmlCharEncoding::Iso8859_16 => &XML_UNICODE_ISO8859_16,
311 XmlCharEncoding::Windows1252 => &XML_UNICODE_windows_1252,
312 _ => return None,
313 })
314}
315
316fn sniff_encoding_decl(bytes: &[u8]) -> Option<XmlCharEncoding> {
317 let n = bytes.len().min(1024);
320 let head = &bytes[..n];
321 if head.len() < 8 {
322 return None;
323 }
324 let idx = head
325 .windows(8)
326 .position(|w| w.eq_ignore_ascii_case(b"encoding"))?;
327 let mut i = idx + 8;
328 while i < n && matches!(head[i], b' ' | b'\t' | b'\r' | b'\n' | b'=') {
329 i += 1;
330 }
331 if i >= n || (head[i] != b'"' && head[i] != b'\'') {
332 return None;
333 }
334 let q = head[i];
335 i += 1;
336 let start = i;
337 while i < n && head[i] != q {
338 i += 1;
339 }
340 if i >= n {
341 return None;
342 }
343 let name = std::str::from_utf8(&head[start..i]).ok()?;
344 xml_parse_char_encoding(name).into_option()
345}
346
347impl XmlCharEncoding {
348 fn into_option(self) -> Option<XmlCharEncoding> {
349 match self {
350 XmlCharEncoding::Error | XmlCharEncoding::None => None,
351 e => Some(e),
352 }
353 }
354}
355
356pub fn xml_convert_to_utf8(
358 input: &[u8],
359 hint: Option<&str>,
360) -> Result<(Vec<u8>, Option<String>), XmlError> {
361 let (c, n) = xml_convert_to_utf8_cow(input, hint)?;
362 Ok((c.into_owned(), n.map(str::to_string)))
363}
364
365pub(crate) fn xml_convert_to_utf8_cow<'a>(
369 input: &'a [u8],
370 hint: Option<&str>,
371) -> Result<(std::borrow::Cow<'a, [u8]>, Option<&'static str>), XmlError> {
372 if input.len() >= 2 && input[0] == 0x1f && input[1] == 0x8b {
373 return Err(XmlError::new(
374 XML_ERR_UNSUPPORTED_ENCODING,
375 "gzip input requires the unzip feature / XML_PARSE_UNZIP",
376 0,
377 0,
378 ));
379 }
380 let mut enc = hint
381 .and_then(|h| xml_parse_char_encoding(h).into_option())
382 .unwrap_or_else(|| xml_detect_char_encoding(input));
383 if enc == XmlCharEncoding::None || enc == XmlCharEncoding::Utf8 {
384 if let Some(d) = sniff_encoding_decl(input) {
385 if d != XmlCharEncoding::Utf8 && d != XmlCharEncoding::Ascii {
386 enc = d;
387 }
388 }
389 }
390 if enc == XmlCharEncoding::None || enc == XmlCharEncoding::Utf8 || enc == XmlCharEncoding::Ascii
391 {
392 let body = if input.starts_with(&[0xEF, 0xBB, 0xBF]) { &input[3..] } else { input };
394 return Ok((std::borrow::Cow::Borrowed(body), xml_get_char_encoding_name(enc)));
395 }
396 let converted = match enc {
397 XmlCharEncoding::Iso8859_1 => latin1_to_utf8(input),
398 XmlCharEncoding::Utf16Le | XmlCharEncoding::Utf16 => utf16_to_utf8(input, false)?,
399 XmlCharEncoding::Utf16Be => utf16_to_utf8(input, true)?,
400 XmlCharEncoding::Ucs4Be => ucs4_to_utf8(input, [0, 1, 2, 3])?,
401 XmlCharEncoding::Ucs4Le => ucs4_to_utf8(input, [3, 2, 1, 0])?,
402 XmlCharEncoding::Ucs4_2143 => ucs4_to_utf8(input, [1, 0, 3, 2])?,
403 XmlCharEncoding::Ucs4_3412 => ucs4_to_utf8(input, [2, 3, 0, 1])?,
404 XmlCharEncoding::Ucs2 => utf16_to_utf8(input, true)?,
405 XmlCharEncoding::Ebcdic => {
406 let mut out = Vec::new();
407 for &b in input {
408 let c = char::from_u32(CP037[b as usize] as u32).unwrap_or('\u{fffd}');
409 let mut buf = [0u8; 4];
410 out.extend_from_slice(c.encode_utf8(&mut buf).as_bytes());
411 }
412 out
413 }
414 other => {
415 if let Some(t) = table_for(other) {
416 eightbit_to_utf8(input, t)
417 } else {
418 return Err(XmlError::new(
419 XML_ERR_UNSUPPORTED_ENCODING,
420 format!(
421 "Unsupported encoding {}",
422 xml_get_char_encoding_name(other).unwrap_or("?")
423 ),
424 0,
425 0,
426 ));
427 }
428 }
429 };
430 Ok((
431 std::borrow::Cow::Owned(converted),
432 xml_get_char_encoding_name(enc),
433 ))
434}
435
436#[cfg(test)]
437mod tests {
438 use super::*;
439
440 #[test]
441 fn latin1_converts() {
442 let (u, _) = xml_convert_to_utf8(&[0xE9], Some("ISO-8859-1")).unwrap();
443 assert_eq!(u, "é".as_bytes());
444 }
445
446 #[test]
447 fn utf16_le_bom() {
448 let mut b = vec![0xFF, 0xFE];
450 for c in "<a/>".encode_utf16() {
451 b.extend_from_slice(&c.to_le_bytes());
452 }
453 let (u, _) = xml_convert_to_utf8(&b, None).unwrap();
454 assert_eq!(std::str::from_utf8(&u).unwrap().trim_start_matches('\u{feff}'), "<a/>");
455 }
456
457 #[test]
458 fn iso8859_2_table() {
459 let (u, _) = xml_convert_to_utf8(&[0xA1], Some("ISO-8859-2")).unwrap();
461 assert_eq!(u, "Ą".as_bytes());
462 }
463
464 #[test]
465 fn parse_encoding_names() {
466 assert_eq!(xml_parse_char_encoding("utf-8"), XmlCharEncoding::Utf8);
467 assert_eq!(xml_parse_char_encoding("latin1"), XmlCharEncoding::Iso8859_1);
468 assert_eq!(xml_parse_char_encoding("windows-1252"), XmlCharEncoding::Windows1252);
469 }
470}
471