Skip to main content

quick_xml/
encoding.rs

1//! A module for wrappers that encode / decode data.
2
3use std::str::Utf8Error;
4
5#[cfg(feature = "encoding")]
6use encoding_rs;
7#[cfg(feature = "encoding")]
8use std::borrow::Cow;
9#[cfg(feature = "encoding")]
10use std::io::{self, BufRead, Read};
11
12/// Unicode "byte order mark" (\u{FEFF}) encoded as UTF-8.
13/// See <https://unicode.org/faq/utf_bom.html#bom1>
14pub(crate) const UTF8_BOM: &[u8] = &[0xEF, 0xBB, 0xBF];
15/// Unicode "byte order mark" (\u{FEFF}) encoded as UTF-16 with little-endian byte order.
16/// See <https://unicode.org/faq/utf_bom.html#bom1>
17pub(crate) const UTF16_LE_BOM: &[u8] = &[0xFF, 0xFE];
18/// Unicode "byte order mark" (\u{FEFF}) encoded as UTF-16 with big-endian byte order.
19/// See <https://unicode.org/faq/utf_bom.html#bom1>
20pub(crate) const UTF16_BE_BOM: &[u8] = &[0xFE, 0xFF];
21
22/// An error when decoding or encoding
23///
24/// If feature [`encoding`] is disabled, the [`EncodingError`] is always [`EncodingError::Utf8`]
25///
26/// [`encoding`]: ../index.html#encoding
27#[derive(Clone, Debug, PartialEq, Eq)]
28#[non_exhaustive]
29pub enum EncodingError {
30    /// Input was not valid UTF-8
31    Utf8(Utf8Error),
32    /// Input did not adhere to the given encoding
33    #[cfg(feature = "encoding")]
34    Other(&'static encoding_rs::Encoding),
35}
36
37impl From<Utf8Error> for EncodingError {
38    #[inline]
39    fn from(e: Utf8Error) -> Self {
40        Self::Utf8(e)
41    }
42}
43
44impl std::error::Error for EncodingError {
45    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
46        match self {
47            Self::Utf8(e) => Some(e),
48            #[cfg(feature = "encoding")]
49            Self::Other(_) => None,
50        }
51    }
52}
53
54impl std::fmt::Display for EncodingError {
55    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56        match self {
57            Self::Utf8(e) => write!(f, "cannot decode input using UTF-8: {}", e),
58            #[cfg(feature = "encoding")]
59            Self::Other(encoding) => write!(f, "cannot decode input using {}", encoding.name()),
60        }
61    }
62}
63
64/// Decoder of byte slices into strings.
65///
66/// If feature [`encoding`] is enabled, this encoding taken from the `"encoding"`
67/// XML declaration or assumes UTF-8, if XML has no <?xml ?> declaration, encoding
68/// key is not defined or contains unknown encoding.
69///
70/// The library supports any UTF-8 compatible encodings that crate `encoding_rs`
71/// is supported. [*UTF-16 and ISO-2022-JP are not supported at the present*][utf16].
72///
73/// If feature [`encoding`] is disabled, the decoder is always UTF-8 decoder:
74/// any XML declarations are ignored.
75///
76/// [utf16]: https://github.com/tafia/quick-xml/issues/158
77/// [`encoding`]: ../index.html#encoding
78#[derive(Clone, Copy, Debug, Eq, PartialEq)]
79pub struct Decoder {
80    #[cfg(feature = "encoding")]
81    pub(crate) encoding: &'static encoding_rs::Encoding,
82}
83
84/// Decodes the provided bytes using the specified encoding.
85///
86/// Returns an error in case of malformed or non-representable sequences in the `bytes`.
87#[cfg(feature = "encoding")]
88pub fn decode<'b>(
89    bytes: &'b [u8],
90    encoding: &'static encoding_rs::Encoding,
91) -> Result<Cow<'b, str>, EncodingError> {
92    encoding
93        .decode_without_bom_handling_and_without_replacement(bytes)
94        .ok_or(EncodingError::Other(encoding))
95}
96
97/// Like [`decode`] but using a pre-allocated buffer.
98#[cfg(feature = "encoding")]
99pub fn decode_into(
100    bytes: &[u8],
101    encoding: &'static encoding_rs::Encoding,
102    buf: &mut String,
103) -> Result<(), EncodingError> {
104    if encoding == encoding_rs::UTF_8 {
105        buf.push_str(std::str::from_utf8(bytes)?);
106        return Ok(());
107    }
108
109    let mut decoder = encoding.new_decoder_without_bom_handling();
110    buf.reserve(
111        decoder
112            .max_utf8_buffer_length_without_replacement(bytes.len())
113            // SAFETY: None can be returned only if required size will overflow usize,
114            // but in that case String::reserve also panics
115            .unwrap(),
116    );
117    let (result, read) = decoder.decode_to_string_without_replacement(bytes, buf, true);
118    match result {
119        encoding_rs::DecoderResult::InputEmpty => {
120            debug_assert_eq!(read, bytes.len());
121            Ok(())
122        }
123        encoding_rs::DecoderResult::Malformed(_, _) => Err(EncodingError::Other(encoding)),
124        // SAFETY: We allocate enough space above
125        encoding_rs::DecoderResult::OutputFull => unreachable!(),
126    }
127}
128
129/// Automatic encoding detection of XML files based using the
130/// [recommended algorithm](https://www.w3.org/TR/xml11/#sec-guessing).
131///
132/// If encoding is detected, `Some` is returned with a [`DetectedEncoding`] that provides
133/// the BOM size in bytes (or zero if no BOM was present).
134///
135/// IF encoding was not recognized, `None` is returned.
136///
137/// Because the [`encoding_rs`] crate supports only subset of those encodings, only
138/// the supported subset are detected, which is UTF-8, UTF-16 BE and UTF-16 LE.
139///
140/// The algorithm suggests examine up to the first 4 bytes to determine encoding
141/// according to the following table:
142///
143/// | Bytes       |Detected encoding
144/// |-------------|------------------------------------------
145/// | **BOM**
146/// |`FE_FF_##_##`|UTF-16, big-endian
147/// |`FF FE ## ##`|UTF-16, little-endian
148/// |`EF BB BF`   |UTF-8
149/// | **No BOM**
150/// |`00 3C 00 3F`|UTF-16 BE or ISO-10646-UCS-2 BE or similar 16-bit BE (use declared encoding to find the exact one)
151/// |`3C 00 3F 00`|UTF-16 LE or ISO-10646-UCS-2 LE or similar 16-bit LE (use declared encoding to find the exact one)
152/// |`3C 3F 78 6D`|UTF-8, ISO 646, ASCII, some part of ISO 8859, Shift-JIS, EUC, or any other 7-bit, 8-bit, or mixed-width encoding which ensures that the characters of ASCII have their normal positions, width, and values; the actual encoding declaration must be read to detect which of these applies, but since all of these encodings use the same bit patterns for the relevant ASCII characters, the encoding declaration itself may be read reliably
153pub fn detect_encoding(bytes: &[u8]) -> Option<DetectedEncoding> {
154    // Prevent suggesting "<?xm". We want to have the same formatted lines for all arms.
155    #[allow(clippy::byte_char_slices)]
156    match bytes {
157        // with BOM
158        _ if bytes.starts_with(UTF16_BE_BOM) => Some(DetectedEncoding::Utf16BeBom),
159        _ if bytes.starts_with(UTF16_LE_BOM) => Some(DetectedEncoding::Utf16LeBom),
160        _ if bytes.starts_with(UTF8_BOM) => Some(DetectedEncoding::Utf8Bom),
161
162        // without BOM
163        _ if bytes.starts_with(&[0x00, b'<', 0x00, b'?']) => Some(DetectedEncoding::Utf16BeLike), // Some BE encoding, for example, UTF-16 or ISO-10646-UCS-2
164        _ if bytes.starts_with(&[b'<', 0x00, b'?', 0x00]) => Some(DetectedEncoding::Utf16LeLike), // Some LE encoding, for example, UTF-16 or ISO-10646-UCS-2
165        _ if bytes.starts_with(&[b'<', b'?', b'x', b'm']) => {
166            Some(DetectedEncoding::AsciiCompatible)
167        } // Some ASCII compatible
168
169        _ => None,
170    }
171}
172
173/// Possible scenarios for start-of-xml detection of encoding
174///
175/// See the documentation of [`detect_encoding`]
176pub enum DetectedEncoding {
177    /// Matches UTF-8 or some other ascii-compatible encoding
178    AsciiCompatible,
179    /// We saw a UTF-8 BOM
180    Utf8Bom,
181    /// Matches UTF-16-LE or some other UTF-16 compatible encoding (e.g. ISO-10646-UCS-2)
182    Utf16LeLike,
183    /// We saw a UTF-16 BOM in little-endian orientation
184    Utf16LeBom,
185    /// Matches UTF-16-BE or some other UTF-16 compatible encoding (e.g. ISO-10646-UCS-2)
186    Utf16BeLike,
187    /// We saw a UTF-16 BOM in big-endian orientation
188    Utf16BeBom,
189}
190
191impl DetectedEncoding {
192    /// Return an Encoding object appropriate for the detected encoding
193    #[cfg(feature = "encoding")]
194    pub const fn encoding(&self) -> &'static encoding_rs::Encoding {
195        match self {
196            DetectedEncoding::AsciiCompatible | DetectedEncoding::Utf8Bom => encoding_rs::UTF_8,
197            DetectedEncoding::Utf16LeLike | DetectedEncoding::Utf16LeBom => encoding_rs::UTF_16LE,
198            DetectedEncoding::Utf16BeLike | DetectedEncoding::Utf16BeBom => encoding_rs::UTF_16BE,
199        }
200    }
201
202    /// Length of the BOM, which may need to be stripped from the input
203    pub const fn bom_len(&self) -> usize {
204        match self {
205            DetectedEncoding::Utf8Bom => 3,
206            DetectedEncoding::Utf16LeBom | DetectedEncoding::Utf16BeBom => 2,
207            DetectedEncoding::AsciiCompatible
208            | DetectedEncoding::Utf16LeLike
209            | DetectedEncoding::Utf16BeLike => 0,
210        }
211    }
212}
213
214// Bytes read upfront so `set_encoding()` can be called before the main
215// decode loop. Kept small (just enough for an XML declaration) to limit
216// bytes decoded with a potentially wrong initial encoding.
217#[cfg(feature = "encoding")]
218const PREFIX_CAP: usize = 64;
219
220#[cfg(feature = "encoding")]
221struct Prefix {
222    buf: [u8; PREFIX_CAP],
223    len: usize,
224    detected: bool,
225}
226
227/// A reader wrapper that decodes a byte stream from any encoding into UTF-8.
228///
229/// This reader wraps a [`BufRead`] source and uses [`encoding_rs::Decoder`] to
230/// transcode the input into valid UTF-8. On first access, it detects the encoding
231/// from BOM or XML declaration byte patterns and configures the appropriate decoder.
232///
233/// For UTF-8 input, this acts as a validating passthrough. For UTF-16 or other
234/// encodings, the bytes are transcoded into UTF-8 in an internal buffer.
235///
236/// # Examples
237///
238/// ```
239/// use std::io::Read;
240/// use quick_xml::encoding::DecodingReader;
241///
242/// // UTF-8 input passes through:
243/// let data = b"Hello, World!";
244/// let mut reader = DecodingReader::new(&data[..]);
245/// let mut buf = Vec::new();
246/// reader.read_to_end(&mut buf).unwrap();
247/// assert_eq!(buf, data);
248/// ```
249///
250/// The example below shows how you can read documents using `DecodingReader`:
251/// ```
252/// use quick_xml::encoding::DecodingReader;
253/// use quick_xml::events::Event;
254/// use quick_xml::reader::Reader;
255///
256/// # fn to_utf16le_with_bom(string: &str) -> Vec<u8> {
257/// #     let mut bytes = Vec::new();
258/// #     bytes.extend_from_slice(&[0xFF, 0xFE]); // UTF-16 LE BOM
259/// #     for ch in string.encode_utf16() {
260/// #         bytes.extend_from_slice(&ch.to_le_bytes());
261/// #     }
262/// #     bytes
263/// # }
264/// let xml = to_utf16le_with_bom("<?xml encoding='UTF-16'?><element/>");
265/// let mut decoder = DecodingReader::new(xml.as_ref());
266/// let mut reader = Reader::from_reader(decoder);
267///
268/// let mut buf = Vec::new();
269/// loop {
270///     buf.clear();
271///     match reader.read_event_into(&mut buf).unwrap() {
272///         Event::Decl(e) => {
273///             // If XML declaration contains unknown encoding name, None is returned
274///             match e.encoder() {
275///                 Some(encoding) => reader.get_mut().set_encoding(encoding),
276///                 None => panic!("Unsupported encoding {:?}", e.encoding()),
277///             }
278///         }
279///         Event::Eof => break,
280///         _ => {}
281///     }
282/// }
283/// ```
284#[cfg(feature = "encoding")]
285pub struct DecodingReader<R> {
286    inner: R,
287    decoder: encoding_rs::Decoder,
288    /// `encoding_rs::Decoder` panics if called after finalization (`last=true`).
289    /// This flag prevents that by short-circuiting `fill_buf` after completion.
290    decoder_finished: bool,
291    /// Decoded UTF-8 output buffer
292    out_buf: Box<[u8]>,
293    /// Start of unconsumed data in out_buf
294    out_pos: usize,
295    /// End of valid data in out_buf
296    out_len: usize,
297    /// Bytes read upfront for encoding detection and XML declaration buffering.
298    /// `Some` until the prefix is fully drained; `None` afterward (main decode
299    /// path takes over and the allocation is freed).
300    prefix: Option<Box<Prefix>>,
301    /// Whether the inner reader has reached EOF
302    inner_eof: bool,
303}
304
305#[cfg(feature = "encoding")]
306impl<R: std::fmt::Debug> std::fmt::Debug for DecodingReader<R> {
307    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
308        f.debug_struct("DecodingReader")
309            .field("inner", &self.inner)
310            .field("encoding", &self.decoder.encoding())
311            .field("out_pos", &self.out_pos)
312            .field("out_len", &self.out_len)
313            .field("inner_eof", &self.inner_eof)
314            .field("prefix_active", &self.prefix.is_some())
315            .finish()
316    }
317}
318
319#[cfg(feature = "encoding")]
320impl<R> DecodingReader<R> {
321    /// Creates a new decoding reader.
322    ///
323    /// The encoding is auto-detected from BOM or XML declaration patterns on
324    /// first access. Defaults to UTF-8 if no pattern is recognized.
325    pub fn new(inner: R) -> Self {
326        Self {
327            inner,
328            decoder: encoding_rs::UTF_8.new_decoder_without_bom_handling(),
329            decoder_finished: false,
330            out_buf: vec![0u8; 8192].into_boxed_slice(),
331            out_pos: 0,
332            out_len: 0,
333            prefix: Some(Box::new(Prefix {
334                buf: [0; PREFIX_CAP],
335                len: 0,
336                detected: false,
337            })),
338            inner_eof: false,
339        }
340    }
341
342    /// Returns a reference to the underlying reader
343    pub const fn get_ref(&self) -> &R {
344        &self.inner
345    }
346
347    /// Returns a mutable reference to the underlying reader
348    pub const fn get_mut(&mut self) -> &mut R {
349        &mut self.inner
350    }
351
352    /// Consumes this reader and returns the underlying reader
353    pub fn into_inner(self) -> R {
354        self.inner
355    }
356
357    /// Returns the encoding currently used by the decoder.
358    ///
359    /// Before the first read, this is always UTF-8. After encoding detection
360    /// it reflects the detected (or overridden) encoding.
361    pub fn encoding(&self) -> &'static encoding_rs::Encoding {
362        self.decoder.encoding()
363    }
364
365    /// Replaces the decoder with one for the given encoding. The encoding
366    /// must be ASCII-compatible (the parser cannot read the declaration otherwise).
367    ///
368    /// # Panics
369    ///
370    /// Panics if the prefix buffer has already been drained. Must be called
371    /// before the prefix is exhausted — in practice, right after parsing
372    /// the XML declaration.
373    pub fn set_encoding(&mut self, encoding: &'static encoding_rs::Encoding) {
374        // No-op when the encoding matches - replacing the decoder would discard
375        // its internal state (e.g. a partial multi-byte sequence), corrupting output.
376        // This check is safe regardless of prefix state since nothing changes.
377        if self.decoder.encoding() == encoding {
378            return;
379        }
380        assert!(
381            self.prefix.is_some(),
382            "set_encoding() called after prefix buffer was drained; \
383             encoding can only be changed while the prefix is still active"
384        );
385        self.decoder = encoding.new_decoder_without_bom_handling();
386        self.decoder_finished = false;
387    }
388}
389
390#[cfg(feature = "encoding")]
391impl<R: BufRead> BufRead for DecodingReader<R> {
392    fn fill_buf(&mut self) -> io::Result<&[u8]> {
393        // Fast path: serve already-decoded data
394        if self.out_pos < self.out_len {
395            return Ok(&self.out_buf[self.out_pos..self.out_len]);
396        }
397
398        // Reset output buffer
399        self.out_pos = 0;
400        self.out_len = 0;
401
402        if let Some(prefix) = &mut self.prefix {
403            // On first access, fill the prefix buffer and detect encoding.
404            // The prefix is large enough to hold an entire XML declaration,
405            // ensuring set_encoding() can be called before the greedy main
406            // decode path consumes from inner.
407            if !prefix.detected {
408                prefix.detected = true;
409
410                while prefix.len < PREFIX_CAP {
411                    match self.inner.read(&mut prefix.buf[prefix.len..]) {
412                        Ok(0) => {
413                            self.inner_eof = true;
414                            break;
415                        }
416                        Ok(n) => prefix.len += n,
417                        Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
418                        Err(e) => return Err(e),
419                    }
420                }
421
422                let detection_bytes = &prefix.buf[..prefix.len];
423                if let Some(detected) = detect_encoding(detection_bytes) {
424                    let bom_len = detected.bom_len();
425                    if bom_len > 0 {
426                        prefix.buf.copy_within(bom_len..prefix.len, 0);
427                        prefix.len -= bom_len;
428                    }
429                    let encoding = detected.encoding();
430                    if encoding != encoding_rs::UTF_8 {
431                        self.decoder = encoding.new_decoder_without_bom_handling();
432                    }
433                }
434            }
435
436            if self.decoder_finished {
437                return Ok(&[]);
438            }
439
440            // Prefix fully decoded on a previous call - drop it and fall
441            // through to the main decode path.
442            if prefix.len == 0 {
443                self.prefix = None;
444            } else {
445                // Decode from prefix buffer
446                let src = &prefix.buf[..prefix.len];
447                let (result, read, written) = self.decoder.decode_to_utf8_without_replacement(
448                    src,
449                    &mut self.out_buf[..],
450                    false,
451                );
452                prefix.buf.copy_within(read..prefix.len, 0);
453                prefix.len -= read;
454                self.out_len = written;
455
456                match result {
457                    encoding_rs::DecoderResult::InputEmpty if written > 0 => {
458                        return Ok(&self.out_buf[..self.out_len]);
459                    }
460                    encoding_rs::DecoderResult::InputEmpty => {
461                        // prefix.len is now 0; keep prefix alive for
462                        // set_encoding() - it will be dropped on the next call.
463                    }
464                    encoding_rs::DecoderResult::OutputFull => {
465                        return Ok(&self.out_buf[..self.out_len]);
466                    }
467                    encoding_rs::DecoderResult::Malformed(_, _) => {
468                        return Err(io::Error::new(
469                            io::ErrorKind::InvalidData,
470                            EncodingError::Other(self.decoder.encoding()),
471                        ));
472                    }
473                }
474                // InputEmpty with written == 0: prefix drained, decoder may
475                // hold partial internal state (e.g. a lone byte of UTF-16).
476                // Drop prefix and fall through to the main decode path.
477                if prefix.len == 0 {
478                    self.prefix = None;
479                }
480            }
481        }
482
483        if self.decoder_finished {
484            return Ok(&[]);
485        }
486
487        // Loop until we produce output, hit EOF, or get an error.
488        // The decoder may consume input into internal state (e.g., partial
489        // UTF-16 code unit) without producing output - we must keep feeding
490        // it more input rather than returning an empty slice (which signals EOF).
491        loop {
492            // EOF flush path: tell decoder this is the last chunk
493            if self.inner_eof {
494                let (result, _, written) = self.decoder.decode_to_utf8_without_replacement(
495                    b"",
496                    &mut self.out_buf[..],
497                    true,
498                );
499                self.out_len = written;
500                match result {
501                    encoding_rs::DecoderResult::InputEmpty => {
502                        self.decoder_finished = true;
503                        return Ok(&self.out_buf[..self.out_len]);
504                    }
505                    encoding_rs::DecoderResult::OutputFull => {
506                        return Ok(&self.out_buf[..self.out_len]);
507                    }
508                    encoding_rs::DecoderResult::Malformed(_, _) => {
509                        return Err(io::Error::new(
510                            io::ErrorKind::InvalidData,
511                            EncodingError::Other(self.decoder.encoding()),
512                        ));
513                    }
514                }
515            }
516
517            // Main decode path: read from inner, decode into out_buf
518            let (result, read, written) = {
519                let src = self.inner.fill_buf()?;
520                if src.is_empty() {
521                    self.inner_eof = true;
522                    continue; // will hit EOF flush path on next iteration
523                }
524                self.decoder
525                    .decode_to_utf8_without_replacement(src, &mut self.out_buf[..], false)
526            };
527            self.inner.consume(read);
528            self.out_len = written;
529
530            match result {
531                encoding_rs::DecoderResult::InputEmpty if written > 0 => {
532                    return Ok(&self.out_buf[..self.out_len]);
533                }
534                encoding_rs::DecoderResult::InputEmpty => {
535                    // Decoder consumed all input but produced no output
536                    // (e.g., 1 byte of a 2-byte UTF-16 code unit stored
537                    // in decoder internal state). Loop to get more input.
538                }
539                encoding_rs::DecoderResult::OutputFull => {
540                    // Output buffer full; return what we have. Remaining
541                    // input will be decoded on the next fill_buf call.
542                    return Ok(&self.out_buf[..self.out_len]);
543                }
544                encoding_rs::DecoderResult::Malformed(_, _) => {
545                    return Err(io::Error::new(
546                        io::ErrorKind::InvalidData,
547                        EncodingError::Other(self.decoder.encoding()),
548                    ));
549                }
550            }
551        }
552    }
553
554    fn consume(&mut self, amt: usize) {
555        debug_assert!(
556            self.out_pos + amt <= self.out_len,
557            "consume({amt}) out of range: out_pos={}, out_len={}",
558            self.out_pos,
559            self.out_len,
560        );
561        self.out_pos += amt;
562    }
563}
564
565#[cfg(feature = "encoding")]
566impl<R: BufRead> Read for DecodingReader<R> {
567    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
568        if buf.is_empty() {
569            return Ok(0);
570        }
571        let available = self.fill_buf()?;
572        if available.is_empty() {
573            return Ok(0);
574        }
575        let len = available.len().min(buf.len());
576        buf[..len].copy_from_slice(&available[..len]);
577        self.consume(len);
578        Ok(len)
579    }
580}
581
582#[cfg(all(test, feature = "encoding"))]
583mod decoding_reader {
584    use super::*;
585    use std::io::{BufReader, Read};
586
587    /// Helper reader that returns data in fixed-size chunks
588    struct ChunkedReader<'a> {
589        data: &'a [u8],
590        pos: usize,
591        chunk_size: usize,
592    }
593
594    impl<'a> ChunkedReader<'a> {
595        fn new(data: &'a [u8], chunk_size: usize) -> Self {
596            Self {
597                data,
598                pos: 0,
599                chunk_size,
600            }
601        }
602    }
603
604    impl<'a> Read for ChunkedReader<'a> {
605        fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
606            if self.pos >= self.data.len() {
607                return Ok(0);
608            }
609            let len = self
610                .chunk_size
611                .min(buf.len())
612                .min(self.data.len() - self.pos);
613            buf[..len].copy_from_slice(&self.data[self.pos..self.pos + len]);
614            self.pos += len;
615            Ok(len)
616        }
617    }
618
619    /// Encode a string as UTF-16 LE bytes with BOM
620    fn utf16le_with_bom(s: &str) -> Vec<u8> {
621        let mut out = vec![0xFF, 0xFE]; // UTF-16 LE BOM
622        for code_unit in s.encode_utf16() {
623            out.extend_from_slice(&code_unit.to_le_bytes());
624        }
625        out
626    }
627
628    /// Encode a string as UTF-16 BE bytes with BOM
629    fn utf16be_with_bom(s: &str) -> Vec<u8> {
630        let mut out = vec![0xFE, 0xFF]; // UTF-16 BE BOM
631        for code_unit in s.encode_utf16() {
632            out.extend_from_slice(&code_unit.to_be_bytes());
633        }
634        out
635    }
636
637    /// Encode a string as UTF-16 LE bytes without BOM
638    fn utf16le_no_bom(s: &str) -> Vec<u8> {
639        let mut out = Vec::new();
640        for code_unit in s.encode_utf16() {
641            out.extend_from_slice(&code_unit.to_le_bytes());
642        }
643        out
644    }
645
646    /// Encode a string as UTF-16 BE bytes without BOM
647    fn utf16be_no_bom(s: &str) -> Vec<u8> {
648        let mut out = Vec::new();
649        for code_unit in s.encode_utf16() {
650            out.extend_from_slice(&code_unit.to_be_bytes());
651        }
652        out
653    }
654
655    /// Read all bytes from a reader into a String
656    fn read_all(reader: &mut DecodingReader<impl BufRead>) -> io::Result<String> {
657        let mut result = Vec::new();
658        reader.read_to_end(&mut result)?;
659        Ok(String::from_utf8(result).expect("DecodingReader should produce valid UTF-8"))
660    }
661
662    /// Simple edge cases and degenerate inputs
663    mod edge_cases {
664        use super::*;
665        use pretty_assertions::assert_eq;
666
667        /// Zero-length input should immediately return EOF (n == 0).
668        #[test]
669        fn empty_input() {
670            let data = b"";
671            let mut reader = DecodingReader::new(&data[..]);
672            let mut buf = [0u8; 10];
673            let n = reader.read(&mut buf).unwrap();
674            assert_eq!(n, 0);
675        }
676
677        /// A UTF-8 BOM with no payload should decode to an empty string.
678        #[test]
679        fn utf8_bom_only() {
680            let data = b"\xEF\xBB\xBF";
681            let mut reader = DecodingReader::new(&data[..]);
682            assert_eq!(read_all(&mut reader).unwrap(), "");
683        }
684
685        /// A UTF-16 LE BOM with no payload should decode to an empty string.
686        #[test]
687        fn utf16le_bom_only() {
688            let data = &[0xFF, 0xFE];
689            let mut reader = DecodingReader::new(&data[..]);
690            assert_eq!(read_all(&mut reader).unwrap(), "");
691        }
692
693        /// A UTF-16 BE BOM with no payload should decode to an empty string.
694        #[test]
695        fn utf16be_bom_only() {
696            let data = &[0xFE, 0xFF];
697            let mut reader = DecodingReader::new(&data[..]);
698            assert_eq!(read_all(&mut reader).unwrap(), "");
699        }
700
701        /// Invalid UTF-8 (no BOM, so treated as UTF-8) must produce an error.
702        #[test]
703        fn invalid_utf8_is_rejected() {
704            let data: &[u8] = &[0x48, 0x65, 0x6C, 0xFF, 0xFE];
705            let mut reader = DecodingReader::new(&data[..]);
706            let err = read_all(&mut reader).unwrap_err();
707            assert_eq!(err.kind(), io::ErrorKind::InvalidData);
708        }
709
710        /// An odd trailing byte in UTF-16 is malformed and must produce an error.
711        #[test]
712        fn truncated_utf16_at_eof() {
713            // UTF-16 LE BOM + one valid code unit + one incomplete byte
714            let data: &[u8] = &[0xFF, 0xFE, 0x48, 0x00, 0x65];
715            let mut reader = DecodingReader::new(&data[..]);
716            let err = read_all(&mut reader).unwrap_err();
717            assert_eq!(err.kind(), io::ErrorKind::InvalidData);
718        }
719
720        /// A 1-byte output buffer forces one byte per read() call; verifies
721        /// multi-byte UTF-8 sequences are still assembled correctly.
722        #[test]
723        fn read_with_one_byte_buffer() {
724            let data = "Hello, 世界!".as_bytes();
725            let mut reader = DecodingReader::new(&data[..]);
726            let mut result = Vec::new();
727            let mut buf = [0u8; 1];
728            loop {
729                let n = reader.read(&mut buf).unwrap();
730                if n == 0 {
731                    break;
732                }
733                result.extend_from_slice(&buf[..n]);
734            }
735            assert_eq!(String::from_utf8(result).unwrap(), "Hello, 世界!");
736        }
737    }
738
739    /// Tests that exercise the BufRead contract (fill_buf + consume) directly,
740    /// as opposed to the Read-based helpers used elsewhere.
741    mod bufread_interface {
742        use super::*;
743        use pretty_assertions::assert_eq;
744        use std::io::BufRead;
745
746        /// Basic fill_buf/consume cycle: partial consume leaves remaining
747        /// data available on the next fill_buf call.
748        #[test]
749        fn fill_buf_and_consume() {
750            let data = b"Hello, World!";
751            let mut reader = DecodingReader::new(&data[..]);
752
753            let buf = reader.fill_buf().unwrap();
754            assert!(!buf.is_empty());
755            assert_eq!(buf[0], b'H');
756
757            // Consume only part of the buffer
758            reader.consume(5);
759
760            let buf = reader.fill_buf().unwrap();
761            assert!(!buf.is_empty());
762            assert_eq!(buf[0], b',');
763        }
764
765        /// Drain the reader via fill_buf/consume, then confirm it stays at EOF.
766        #[test]
767        fn partial_consume_then_read_more() {
768            let data = b"Hello, World!";
769            let mut reader = DecodingReader::new(&data[..]);
770
771            // Collect all output via fill_buf/consume
772            let mut result = Vec::new();
773            loop {
774                let buf = reader.fill_buf().unwrap();
775                if buf.is_empty() {
776                    break;
777                }
778                result.extend_from_slice(buf);
779                let len = buf.len();
780                reader.consume(len);
781            }
782            assert_eq!(std::str::from_utf8(&result).unwrap(), "Hello, World!");
783
784            // Should remain at EOF
785            let buf = reader.fill_buf().unwrap();
786            assert!(buf.is_empty());
787        }
788
789        /// Calling fill_buf() repeatedly after EOF must keep returning empty
790        /// (and not panic - encoding_rs::Decoder panics if called after finalization).
791        #[test]
792        fn fill_buf_after_eof_is_idempotent() {
793            let data = b"Hello";
794            let mut reader = DecodingReader::new(&data[..]);
795
796            loop {
797                let buf = reader.fill_buf().unwrap();
798                if buf.is_empty() {
799                    break;
800                }
801                let len = buf.len();
802                reader.consume(len);
803            }
804
805            for _ in 0..3 {
806                let buf = reader.fill_buf().unwrap();
807                assert!(buf.is_empty());
808            }
809        }
810
811        /// consume() past the buffered length must trigger a debug_assert panic.
812        #[test]
813        #[should_panic(expected = "consume")]
814        fn consume_overflow_panics_in_debug() {
815            let data = b"Hi";
816            let mut reader = DecodingReader::new(&data[..]);
817            let _ = reader.fill_buf().unwrap();
818            reader.consume(100);
819        }
820    }
821
822    mod accessors {
823        use super::*;
824        use pretty_assertions::assert_eq;
825        use std::io::Cursor;
826
827        #[test]
828        fn get_ref() {
829            let data = b"Hello";
830            let cursor = Cursor::new(data.to_vec());
831            let reader = DecodingReader::new(cursor);
832            assert_eq!(reader.get_ref().get_ref(), data);
833        }
834
835        #[test]
836        fn get_mut() {
837            let data = b"Hello";
838            let cursor = Cursor::new(data.to_vec());
839            let mut reader = DecodingReader::new(cursor);
840            reader.get_mut().set_position(2);
841            assert_eq!(reader.get_ref().position(), 2);
842        }
843
844        #[test]
845        fn into_inner() {
846            let data = b"Hello";
847            let cursor = Cursor::new(data.to_vec());
848            let reader = DecodingReader::new(cursor);
849            let inner = reader.into_inner();
850            assert_eq!(inner.get_ref(), data);
851        }
852
853        /// Default encoding before any reads is UTF-8.
854        #[test]
855        fn encoding_default_is_utf8() {
856            let reader = DecodingReader::new(&b"Hello"[..]);
857            assert_eq!(reader.encoding(), encoding_rs::UTF_8);
858        }
859    }
860
861    // TODO: These tests emulate the updating of the internal decoder after reading the XML decl.
862    // Since `Reader` currently only speaks the `BufRead` trait, we can't test that directly.
863    // Eventually once `Reader` knows about the underlying `DecodingReader` we should test
864    // that directly.
865
866    /// Tests for encoding() and set_encoding(): detection, switching,
867    /// same-encoding no-op safety, and mid-stream override behavior.
868    mod encoding_switching {
869        use super::*;
870        use pretty_assertions::assert_eq;
871        use std::io::BufRead;
872
873        /// Encoding reflects BOM detection after first read.
874        #[test]
875        fn encoding_reflects_detection() {
876            let data = utf16le_with_bom("Hello");
877            let mut reader = DecodingReader::new(&data[..]);
878            let _ = read_all(&mut reader).unwrap();
879            assert_eq!(reader.encoding(), encoding_rs::UTF_16LE);
880        }
881
882        /// set_encoding switches the active decoder.
883        #[test]
884        fn set_encoding_changes_encoding() {
885            let mut reader = DecodingReader::new(&b"Hello"[..]);
886            assert_eq!(reader.encoding(), encoding_rs::UTF_8);
887            reader.set_encoding(encoding_rs::UTF_16LE);
888            assert_eq!(reader.encoding(), encoding_rs::UTF_16LE);
889        }
890
891        /// set_encoding after reading preserves already-buffered output.
892        #[test]
893        fn set_encoding_preserves_buffered_output() {
894            let data = b"Hello";
895            let mut reader = DecodingReader::new(&data[..]);
896
897            let buf = reader.fill_buf().unwrap();
898            assert_eq!(buf, b"Hello");
899
900            reader.set_encoding(encoding_rs::WINDOWS_1252);
901            assert_eq!(reader.encoding(), encoding_rs::WINDOWS_1252);
902
903            // Buffered data is unchanged
904            let buf = reader.fill_buf().unwrap();
905            assert_eq!(buf, b"Hello");
906        }
907
908        /// Calling set_encoding with the already-active encoding is a no-op:
909        /// the decoder's internal state is preserved and decoding continues
910        /// without corruption.
911        #[test]
912        fn set_encoding_same_as_detected_is_noop() {
913            let data = b"Hello, World!";
914            let mut reader = DecodingReader::new(&data[..]);
915
916            // Trigger detection and consume the first chunk
917            let first_chunk;
918            {
919                let buf = reader.fill_buf().unwrap();
920                assert!(buf.len() > 0);
921                first_chunk = std::str::from_utf8(buf).unwrap().to_string();
922                let n = buf.len();
923                reader.consume(n);
924            }
925            assert_eq!(reader.encoding(), encoding_rs::UTF_8);
926
927            // "Re-set" to the same encoding - must not reset decoder state
928            reader.set_encoding(encoding_rs::UTF_8);
929            assert_eq!(reader.encoding(), encoding_rs::UTF_8);
930
931            // Read the rest - combined output must equal the original string
932            let rest = read_all(&mut reader).unwrap();
933            assert_eq!(format!("{first_chunk}{rest}"), "Hello, World!");
934        }
935
936        /// set_encoding mid-stream: read some UTF-8 data, switch encoding,
937        /// then verify the encoding accessor reflects the change.
938        #[test]
939        fn set_encoding_mid_stream() {
940            let data = b"Hello, World!";
941            let mut reader = DecodingReader::new(&data[..]);
942
943            // Read a few bytes under UTF-8
944            let buf = reader.fill_buf().unwrap();
945            let n = std::cmp::min(buf.len(), 5);
946            reader.consume(n);
947
948            assert_eq!(reader.encoding(), encoding_rs::UTF_8);
949            reader.set_encoding(encoding_rs::WINDOWS_1252);
950            assert_eq!(reader.encoding(), encoding_rs::WINDOWS_1252);
951
952            // Remaining data still readable (ASCII is identical in both encodings)
953            let rest = read_all(&mut reader).unwrap();
954            assert_eq!(rest, ", World!");
955        }
956    }
957
958    /// Tests exercised across a matrix of (input text x encoding x read strategy).
959    /// Each test encodes a string, feeds it through DecodingReader, and asserts the
960    /// decoded output matches the original. This covers BOM detection, UTF-16
961    /// transcoding, surrogate pairs, and multi-byte UTF-8 characters in one sweep.
962    ///
963    /// Examples:
964    ///
965    /// - UTF-8 passthrough (ASCII and multibyte) with and without BOM
966    /// - UTF-16 LE/BE decoding with and without BOM
967    /// - BOM-less UTF-16 detection via `<?xml` byte pattern
968    /// - UTF-16 surrogate pairs (astral plane characters)
969    /// - Chunked input at misaligned boundaries (odd chunk sizes vs 2-byte code units)
970    /// - One-byte-at-a-time delivery for all encodings
971    /// - Inputs larger than the 8192-byte internal output buffer
972    /// - Empty and single-character inputs (prefix-only decode path)
973    mod matrix_decoding_tests {
974        use super::*;
975        use pretty_assertions::assert_eq;
976
977        struct TestCase {
978            label: &'static str,
979            text: &'static str,
980        }
981
982        /// Short inputs that exercise different Unicode categories.
983        const CASES: &[TestCase] = &[
984            TestCase {
985                label: "empty",
986                text: "",
987            },
988            TestCase {
989                label: "single_multibyte",
990                // Single 3-byte character - entire content fits in the prefix buffer
991                text: "€",
992            },
993            TestCase {
994                label: "ascii",
995                text: "Hello",
996            },
997            TestCase {
998                label: "multibyte",
999                // 3-byte CJK + 4-byte emoji
1000                text: "Hello, 世界! 😀",
1001            },
1002            TestCase {
1003                label: "surrogate_pairs",
1004                // U+1D11E and U+1F3B5 require surrogate pairs in UTF-16
1005                text: "Music: 𝄞🎵",
1006            },
1007            TestCase {
1008                label: "xml_declaration",
1009                // Enables BOM-less UTF-16 detection via the <?xml byte pattern
1010                text: "<?xml version=\"1.0\"?><root/>",
1011            },
1012        ];
1013
1014        /// Inputs larger than the 8192-byte internal output buffer.
1015        fn large_cases() -> Vec<(&'static str, String)> {
1016            vec![
1017                ("large_ascii", "abcdefghij".repeat(1000)),
1018                ("large_multibyte", "Hello, 世界! 😀 ".repeat(500)),
1019            ]
1020        }
1021
1022        enum Encoding {
1023            Utf8,
1024            Utf8Bom,
1025            Utf16Le,
1026            Utf16Be,
1027            Utf16LeNoBom,
1028            Utf16BeNoBom,
1029        }
1030
1031        impl Encoding {
1032            fn encode(&self, text: &str) -> Vec<u8> {
1033                match self {
1034                    Encoding::Utf8 => text.as_bytes().to_vec(),
1035                    Encoding::Utf8Bom => {
1036                        let mut out = vec![0xEF, 0xBB, 0xBF];
1037                        out.extend_from_slice(text.as_bytes());
1038                        out
1039                    }
1040                    Encoding::Utf16Le => utf16le_with_bom(text),
1041                    Encoding::Utf16Be => utf16be_with_bom(text),
1042                    Encoding::Utf16LeNoBom => utf16le_no_bom(text),
1043                    Encoding::Utf16BeNoBom => utf16be_no_bom(text),
1044                }
1045            }
1046
1047            fn label(&self) -> &'static str {
1048                match self {
1049                    Encoding::Utf8 => "utf8",
1050                    Encoding::Utf8Bom => "utf8_bom",
1051                    Encoding::Utf16Le => "utf16le",
1052                    Encoding::Utf16Be => "utf16be",
1053                    Encoding::Utf16LeNoBom => "utf16le_no_bom",
1054                    Encoding::Utf16BeNoBom => "utf16be_no_bom",
1055                }
1056            }
1057
1058            /// BOM-less UTF-16 detection requires a `<?xml` prefix, so those
1059            /// encodings are only included for inputs that start with one.
1060            fn all_for(text: &str) -> Vec<Encoding> {
1061                let mut encs = vec![
1062                    Encoding::Utf8,
1063                    Encoding::Utf8Bom,
1064                    Encoding::Utf16Le,
1065                    Encoding::Utf16Be,
1066                ];
1067                if text.starts_with("<?xml") {
1068                    encs.push(Encoding::Utf16LeNoBom);
1069                    encs.push(Encoding::Utf16BeNoBom);
1070                }
1071                encs
1072            }
1073        }
1074
1075        /// Encode -> decode with the entire input available at once.
1076        #[test]
1077        fn bulk_read() {
1078            for case in CASES {
1079                for enc in Encoding::all_for(case.text) {
1080                    let data = enc.encode(case.text);
1081                    let mut reader = DecodingReader::new(&data[..]);
1082                    assert_eq!(
1083                        read_all(&mut reader).unwrap(),
1084                        case.text,
1085                        "bulk_read failed: case={}, encoding={}",
1086                        case.label,
1087                        enc.label(),
1088                    );
1089                }
1090            }
1091            for (label, text) in large_cases() {
1092                for enc in Encoding::all_for(&text) {
1093                    let data = enc.encode(&text);
1094                    let mut reader = DecodingReader::new(&data[..]);
1095                    assert_eq!(
1096                        read_all(&mut reader).unwrap(),
1097                        text,
1098                        "bulk_read failed: case={}, encoding={}",
1099                        label,
1100                        enc.label(),
1101                    );
1102                }
1103            }
1104        }
1105
1106        /// Encode -> decode with the input delivered in fixed-size chunks via
1107        /// ChunkedReader, testing that the decoder handles arbitrary byte
1108        /// boundaries (mid-BOM, mid-code-unit, mid-surrogate-pair).
1109        #[test]
1110        fn chunked_read() {
1111            for case in CASES {
1112                for enc in Encoding::all_for(case.text) {
1113                    for chunk_size in [1, 2, 3, 4, 5] {
1114                        let data = enc.encode(case.text);
1115                        let mut reader = DecodingReader::new(BufReader::new(ChunkedReader::new(
1116                            &data, chunk_size,
1117                        )));
1118                        assert_eq!(
1119                            read_all(&mut reader).unwrap(),
1120                            case.text,
1121                            "chunked_read failed: case={}, encoding={}, chunk_size={}",
1122                            case.label,
1123                            enc.label(),
1124                            chunk_size,
1125                        );
1126                    }
1127                }
1128            }
1129        }
1130
1131        /// Same as chunked_read but with inputs exceeding the 8192-byte
1132        /// internal output buffer, exercising the multi-fill_buf decode loop.
1133        #[test]
1134        fn large_chunked_read() {
1135            for (label, text) in large_cases() {
1136                for enc in Encoding::all_for(&text) {
1137                    for chunk_size in [1, 2, 3, 4, 5] {
1138                        let data = enc.encode(&text);
1139                        let mut reader = DecodingReader::new(BufReader::new(ChunkedReader::new(
1140                            &data, chunk_size,
1141                        )));
1142                        assert_eq!(
1143                            read_all(&mut reader).unwrap(),
1144                            text,
1145                            "large_chunked_read failed: case={}, encoding={}, chunk_size={}",
1146                            label,
1147                            enc.label(),
1148                            chunk_size,
1149                        );
1150                    }
1151                }
1152            }
1153        }
1154    }
1155}