Skip to main content

oxixml_xml/
encoding.rs

1//! Wrappers that decode byte data into strings.
2//!
3//! The [`Decoder`] plays the same role as the decoder in quick-xml 0.41: without
4//! the `encoding` feature it is always a UTF-8 decoder; with the `encoding`
5//! feature it carries the document encoding inferred from the XML declaration and
6#![cfg_attr(
7    feature = "encoding",
8    doc = "is backed by the Pure-Rust [`oxixml-encoding`](oxixml_encoding) crate instead of \
9`encoding_rs`."
10)]
11#![cfg_attr(
12    not(feature = "encoding"),
13    doc = "is backed by the Pure-Rust `oxixml-encoding` crate instead of `encoding_rs`."
14)]
15
16use std::borrow::Cow;
17use std::str::Utf8Error;
18
19#[cfg(feature = "encoding")]
20use oxixml_encoding::Encoding;
21
22/// Unicode "byte order mark" (`U+FEFF`) encoded as UTF-8.
23pub(crate) const UTF8_BOM: &[u8] = &[0xEF, 0xBB, 0xBF];
24/// Unicode "byte order mark" (`U+FEFF`) encoded as UTF-16 little-endian.
25pub(crate) const UTF16_LE_BOM: &[u8] = &[0xFF, 0xFE];
26/// Unicode "byte order mark" (`U+FEFF`) encoded as UTF-16 big-endian.
27pub(crate) const UTF16_BE_BOM: &[u8] = &[0xFE, 0xFF];
28
29/// An error raised while decoding (or encoding) byte data.
30///
31/// If the `encoding` feature is disabled, the only possible value is
32/// [`EncodingError::Utf8`].
33#[derive(Clone, Debug, PartialEq, Eq)]
34#[non_exhaustive]
35pub enum EncodingError {
36    /// Input was not valid UTF-8.
37    Utf8(Utf8Error),
38    /// Input did not adhere to the given encoding.
39    #[cfg(feature = "encoding")]
40    #[cfg_attr(docsrs, doc(cfg(feature = "encoding")))]
41    Other(Encoding),
42}
43
44impl From<Utf8Error> for EncodingError {
45    #[inline]
46    fn from(e: Utf8Error) -> Self {
47        Self::Utf8(e)
48    }
49}
50
51impl std::error::Error for EncodingError {
52    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
53        match self {
54            Self::Utf8(e) => Some(e),
55            #[cfg(feature = "encoding")]
56            Self::Other(_) => None,
57        }
58    }
59}
60
61impl std::fmt::Display for EncodingError {
62    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63        match self {
64            Self::Utf8(e) => write!(f, "cannot decode input using UTF-8: {}", e),
65            #[cfg(feature = "encoding")]
66            Self::Other(encoding) => write!(f, "cannot decode input using {}", encoding.name()),
67        }
68    }
69}
70
71/// Decoder of byte slices into strings.
72///
73/// If the `encoding` feature is enabled, the encoding is taken from the
74/// `encoding="..."` pseudo-attribute of the XML declaration, or assumed to be
75/// UTF-8 if there is no declaration or the encoding is unknown.
76///
77/// If the `encoding` feature is disabled, the decoder is always a UTF-8 decoder
78/// and any XML declaration is ignored.
79#[derive(Clone, Copy, Debug, Eq, PartialEq)]
80pub struct Decoder {
81    #[cfg(feature = "encoding")]
82    pub(crate) encoding: Encoding,
83}
84
85impl Decoder {
86    /// A UTF-8 decoder. This is the decoder used for manually-constructed events.
87    pub(crate) const fn utf8() -> Self {
88        Decoder {
89            #[cfg(feature = "encoding")]
90            encoding: Encoding::Utf8,
91        }
92    }
93
94    /// A UTF-16 little-endian decoder, used only by the deserializer unit tests
95    /// to check that decoding a non-UTF-8 source produces owned data.
96    #[cfg(all(test, feature = "encoding", feature = "serialize"))]
97    pub(crate) const fn utf16() -> Self {
98        Decoder {
99            encoding: Encoding::Utf16Le,
100        }
101    }
102
103    /// Returns the encoding used by this decoder.
104    ///
105    /// This encoding is used by [`decode`](Self::decode).
106    #[cfg(feature = "encoding")]
107    #[cfg_attr(docsrs, doc(cfg(feature = "encoding")))]
108    pub const fn encoding(&self) -> Encoding {
109        self.encoding
110    }
111
112    /// Decodes the given bytes.
113    ///
114    /// Without the `encoding` feature this decodes a UTF-8 slice regardless of any
115    /// XML declaration (and ignoring any byte-order mark). With the `encoding`
116    /// feature it decodes using the encoding declared in the XML, or UTF-8
117    /// otherwise.
118    ///
119    /// Returns an error in case of malformed sequences in `bytes`.
120    pub fn decode<'b>(&self, bytes: &'b [u8]) -> Result<Cow<'b, str>, EncodingError> {
121        #[cfg(not(feature = "encoding"))]
122        {
123            Ok(Cow::Borrowed(std::str::from_utf8(bytes)?))
124        }
125
126        #[cfg(feature = "encoding")]
127        {
128            decode(bytes, self.encoding)
129        }
130    }
131
132    /// Like [`decode`](Self::decode) but writes into a pre-allocated buffer.
133    pub fn decode_into(&self, bytes: &[u8], buf: &mut String) -> Result<(), EncodingError> {
134        #[cfg(not(feature = "encoding"))]
135        {
136            buf.push_str(std::str::from_utf8(bytes)?);
137        }
138
139        #[cfg(feature = "encoding")]
140        {
141            decode_into(bytes, self.encoding, buf)?;
142        }
143
144        Ok(())
145    }
146
147    /// Decodes the `Cow` buffer, preserving the lifetime where possible.
148    pub(crate) fn decode_cow<'b>(
149        &self,
150        bytes: &Cow<'b, [u8]>,
151    ) -> Result<Cow<'b, str>, EncodingError> {
152        match bytes {
153            Cow::Borrowed(bytes) => self.decode(bytes),
154            // Convert to owned, because otherwise the `Cow` would be bound with the
155            // wrong lifetime.
156            Cow::Owned(bytes) => Ok(self.decode(bytes)?.into_owned().into()),
157        }
158    }
159
160    /// Decodes the `Cow` buffer and normalizes XML end-of-line sequences,
161    /// preserving the lifetime where possible.
162    pub(crate) fn content<'b>(
163        &self,
164        bytes: &Cow<'b, [u8]>,
165        normalize_eol: impl Fn(&str) -> Cow<str>,
166    ) -> Result<Cow<'b, str>, EncodingError> {
167        match bytes {
168            Cow::Borrowed(bytes) => {
169                let text = self.decode(bytes)?;
170                match normalize_eol(&text) {
171                    // If the text is still borrowed after normalization, it is unchanged.
172                    Cow::Borrowed(_) => Ok(text),
173                    Cow::Owned(s) => Ok(Cow::Owned(s)),
174                }
175            }
176            Cow::Owned(bytes) => {
177                let text = self.decode(bytes)?;
178                let text = normalize_eol(&text);
179                Ok(text.into_owned().into())
180            }
181        }
182    }
183}
184
185/// Decodes the provided bytes using the specified encoding.
186///
187/// Returns an error for malformed or non-representable sequences in `bytes`.
188#[cfg(feature = "encoding")]
189#[cfg_attr(docsrs, doc(cfg(feature = "encoding")))]
190pub fn decode(bytes: &[u8], encoding: Encoding) -> Result<Cow<'_, str>, EncodingError> {
191    if matches!(encoding, Encoding::Utf8) {
192        // Like quick-xml 0.41, which routes UTF-8 through the same fallible
193        // decoding path as every other encoding and therefore reports
194        // `EncodingError::Other(UTF-8)` — not `EncodingError::Utf8(..)` — for
195        // malformed input here. `decode_into` keeps the `Utf8` variant, again
196        // like quick-xml.
197        return std::str::from_utf8(bytes)
198            .map(Cow::Borrowed)
199            .map_err(|_| EncodingError::Other(Encoding::Utf8));
200    }
201    let mut out = String::new();
202    let mut decoder = encoding.new_decoder();
203    decoder
204        .decode_to_string(bytes, &mut out, true)
205        .map_err(|_| EncodingError::Other(encoding))?;
206    Ok(Cow::Owned(out))
207}
208
209/// Like [`decode`] but writes into a pre-allocated buffer.
210#[cfg(feature = "encoding")]
211#[cfg_attr(docsrs, doc(cfg(feature = "encoding")))]
212pub fn decode_into(
213    bytes: &[u8],
214    encoding: Encoding,
215    buf: &mut String,
216) -> Result<(), EncodingError> {
217    if matches!(encoding, Encoding::Utf8) {
218        buf.push_str(std::str::from_utf8(bytes)?);
219        return Ok(());
220    }
221    let mut decoder = encoding.new_decoder();
222    decoder
223        .decode_to_string(bytes, buf, true)
224        .map_err(|_| EncodingError::Other(encoding))?;
225    Ok(())
226}
227
228/// Automatic detection of the XML encoding using the
229/// [recommended algorithm](https://www.w3.org/TR/xml11/#sec-guessing).
230///
231/// Returns `Some` with a [`DetectedEncoding`] (which reports the BOM length, if
232/// any) when the encoding is recognized, otherwise `None`.
233///
234/// The algorithm examines up to the first four bytes to determine the encoding.
235pub fn detect_encoding(bytes: &[u8]) -> Option<DetectedEncoding> {
236    match bytes {
237        // with BOM
238        _ if bytes.starts_with(UTF16_BE_BOM) => Some(DetectedEncoding::Utf16BeBom),
239        _ if bytes.starts_with(UTF16_LE_BOM) => Some(DetectedEncoding::Utf16LeBom),
240        _ if bytes.starts_with(UTF8_BOM) => Some(DetectedEncoding::Utf8Bom),
241
242        // without BOM
243        _ if bytes.starts_with(&[0x00, b'<', 0x00, b'?']) => Some(DetectedEncoding::Utf16BeLike),
244        _ if bytes.starts_with(&[b'<', 0x00, b'?', 0x00]) => Some(DetectedEncoding::Utf16LeLike),
245        _ if bytes.starts_with(b"<?xm") => Some(DetectedEncoding::AsciiCompatible),
246
247        _ => None,
248    }
249}
250
251/// Possible outcomes of start-of-document encoding detection.
252///
253/// See [`detect_encoding`].
254#[derive(Clone, Copy, Debug, Eq, PartialEq)]
255pub enum DetectedEncoding {
256    /// Matches UTF-8 or some other ASCII-compatible encoding.
257    AsciiCompatible,
258    /// A UTF-8 BOM was seen.
259    Utf8Bom,
260    /// Matches UTF-16 LE (or a compatible 16-bit little-endian encoding).
261    Utf16LeLike,
262    /// A UTF-16 little-endian BOM was seen.
263    Utf16LeBom,
264    /// Matches UTF-16 BE (or a compatible 16-bit big-endian encoding).
265    Utf16BeLike,
266    /// A UTF-16 big-endian BOM was seen.
267    Utf16BeBom,
268}
269
270impl DetectedEncoding {
271    /// Returns the [`Encoding`] appropriate for the detected variant.
272    #[cfg(feature = "encoding")]
273    #[cfg_attr(docsrs, doc(cfg(feature = "encoding")))]
274    pub const fn encoding(&self) -> Encoding {
275        match self {
276            DetectedEncoding::AsciiCompatible | DetectedEncoding::Utf8Bom => Encoding::Utf8,
277            DetectedEncoding::Utf16LeLike | DetectedEncoding::Utf16LeBom => Encoding::Utf16Le,
278            DetectedEncoding::Utf16BeLike | DetectedEncoding::Utf16BeBom => Encoding::Utf16Be,
279        }
280    }
281
282    /// Length in bytes of the BOM that may need to be stripped from the input.
283    pub const fn bom_len(&self) -> usize {
284        match self {
285            DetectedEncoding::Utf8Bom => 3,
286            DetectedEncoding::Utf16LeBom | DetectedEncoding::Utf16BeBom => 2,
287            DetectedEncoding::AsciiCompatible
288            | DetectedEncoding::Utf16LeLike
289            | DetectedEncoding::Utf16BeLike => 0,
290        }
291    }
292}
293
294/// Bytes read upfront so that [`DecodingReader::set_encoding`] can be called
295/// before the main decode loop starts. Kept small — just enough for an XML
296/// declaration — to limit the number of bytes decoded with a possibly wrong
297/// initial encoding.
298#[cfg(feature = "encoding")]
299const PREFIX_CAP: usize = 64;
300
301/// A reader wrapper that decodes a byte stream from any supported encoding into
302/// UTF-8.
303///
304/// This mirrors `quick_xml::encoding::DecodingReader` of quick-xml 0.41, but is
305/// backed by the Pure-Rust [`oxixml-encoding`](oxixml_encoding) crate instead of
306/// `encoding_rs`.
307///
308/// The [`Reader`](crate::reader::Reader) scans for ASCII delimiters in the raw
309/// byte stream and therefore only understands ASCII-compatible encodings. Wrap
310/// the input in this reader to parse a document in an encoding that is not
311/// ASCII-compatible, such as UTF-16.
312///
313/// On the first access the encoding is detected from the byte-order mark or from
314/// the byte pattern of the XML declaration (see [`detect_encoding`]); it defaults
315/// to UTF-8. Once the declaration has been parsed, the encoding it names can be
316/// installed with [`set_encoding`](Self::set_encoding).
317///
318/// # Note on the declared encoding
319///
320/// What this reader hands to the parser is already UTF-8, but the `encoding`
321/// pseudo-attribute of the transcoded XML declaration still says what the
322/// *original* bytes were. With the `encoding` feature enabled the
323/// [`Reader`](crate::reader::Reader) picks that name up for its own
324/// [`Decoder`], exactly as quick-xml 0.41 does, and would then decode the
325/// event payloads a second time. Documents encoded in UTF-16 normally rely on
326/// the byte-order mark and carry no `encoding` pseudo-attribute, in which case
327/// nothing special is needed; if the declaration does name the original
328/// encoding, either build the reader without the `encoding` feature — the
329/// payloads are UTF-8 by then anyway — or take the payload bytes of the events
330/// as-is instead of running them through the reader's [`Decoder`] again.
331///
332/// # Examples
333///
334/// UTF-8 input passes through unchanged:
335///
336/// ```
337/// use std::io::Read;
338/// use oxixml_xml::encoding::DecodingReader;
339///
340/// let data = b"Hello, World!";
341/// let mut reader = DecodingReader::new(&data[..]);
342/// let mut buf = Vec::new();
343/// reader.read_to_end(&mut buf).expect("read");
344/// assert_eq!(buf, data);
345/// ```
346///
347/// A UTF-16 document becomes readable by the [`Reader`](crate::reader::Reader):
348///
349/// ```
350/// use oxixml_xml::encoding::DecodingReader;
351/// use oxixml_xml::events::Event;
352/// use oxixml_xml::reader::Reader;
353///
354/// let mut xml = vec![0xFF, 0xFE]; // UTF-16 LE BOM
355/// for unit in "<?xml version='1.0' encoding='UTF-16'?><element/>".encode_utf16() {
356///     xml.extend_from_slice(&unit.to_le_bytes());
357/// }
358/// let mut reader = Reader::from_reader(DecodingReader::new(xml.as_slice()));
359///
360/// let mut buf = Vec::new();
361/// assert!(matches!(reader.read_event_into(&mut buf).expect("decl"), Event::Decl(_)));
362/// buf.clear();
363/// assert!(matches!(reader.read_event_into(&mut buf).expect("element"), Event::Empty(_)));
364/// ```
365#[cfg(feature = "encoding")]
366#[cfg_attr(docsrs, doc(cfg(feature = "encoding")))]
367pub struct DecodingReader<R> {
368    /// The wrapped source of encoded bytes.
369    inner: R,
370    /// Incremental decoder for the current encoding.
371    decoder: oxixml_encoding::Decoder,
372    /// Whether the decoder has been told that the input is over. Feeding it more
373    /// input afterwards is not allowed.
374    finished: bool,
375    /// Decoded UTF-8 output that has not been handed out yet.
376    out: String,
377    /// Start of the unconsumed part of [`Self::out`], in bytes.
378    out_pos: usize,
379    /// Bytes read upfront for encoding detection. `Some` until they are drained.
380    prefix: Option<Box<Prefix>>,
381    /// Whether the inner reader has reached the end of its input.
382    inner_eof: bool,
383}
384
385/// The upfront-read prefix of a [`DecodingReader`].
386#[cfg(feature = "encoding")]
387struct Prefix {
388    /// Bytes read upfront, still encoded.
389    buf: [u8; PREFIX_CAP],
390    /// Number of valid bytes in [`Self::buf`].
391    len: usize,
392    /// Whether encoding detection has already run.
393    detected: bool,
394}
395
396#[cfg(feature = "encoding")]
397impl<R: std::fmt::Debug> std::fmt::Debug for DecodingReader<R> {
398    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
399        f.debug_struct("DecodingReader")
400            .field("inner", &self.inner)
401            .field("encoding", &self.decoder.encoding())
402            .field("inner_eof", &self.inner_eof)
403            .field("prefix_active", &self.prefix.is_some())
404            .finish()
405    }
406}
407
408#[cfg(feature = "encoding")]
409impl<R> DecodingReader<R> {
410    /// Creates a new decoding reader around `inner`.
411    ///
412    /// The encoding is auto-detected from the byte-order mark or from the XML
413    /// declaration pattern on the first access, defaulting to UTF-8.
414    pub fn new(inner: R) -> Self {
415        Self {
416            inner,
417            decoder: Encoding::Utf8.new_decoder(),
418            finished: false,
419            out: String::new(),
420            out_pos: 0,
421            prefix: Some(Box::new(Prefix {
422                buf: [0; PREFIX_CAP],
423                len: 0,
424                detected: false,
425            })),
426            inner_eof: false,
427        }
428    }
429
430    /// Returns a reference to the underlying reader.
431    pub const fn get_ref(&self) -> &R {
432        &self.inner
433    }
434
435    /// Returns a mutable reference to the underlying reader.
436    pub const fn get_mut(&mut self) -> &mut R {
437        &mut self.inner
438    }
439
440    /// Consumes this reader and returns the underlying reader.
441    pub fn into_inner(self) -> R {
442        self.inner
443    }
444
445    /// Returns the encoding the decoder currently uses.
446    ///
447    /// Before the first read this is always UTF-8; afterwards it reflects the
448    /// detected — or explicitly installed — encoding.
449    pub const fn encoding(&self) -> Encoding {
450        self.decoder.encoding()
451    }
452
453    /// Replaces the decoder with one for `encoding`, keeping the position in the
454    /// stream.
455    ///
456    /// Call this right after the XML declaration has been parsed, that is while
457    /// the upfront-read prefix is still being served. Once the prefix is drained
458    /// the encoding can no longer be changed and the call is ignored, because
459    /// the bytes that follow have already been fed to the current decoder.
460    ///
461    /// Returns whether the encoding was installed. Setting the encoding that is
462    /// already in use always succeeds and changes nothing (replacing the decoder
463    /// would discard the partial sequence it may hold).
464    pub fn set_encoding(&mut self, encoding: Encoding) -> bool {
465        if self.decoder.encoding() == encoding {
466            return true;
467        }
468        if self.prefix.is_none() {
469            return false;
470        }
471        self.decoder = encoding.new_decoder();
472        self.finished = false;
473        true
474    }
475
476    /// Decodes `bytes` into the output buffer, mapping a decoding failure to an
477    /// [`io::Error`](std::io::Error) that carries the [`EncodingError`].
478    fn decode_chunk(&mut self, bytes: &[u8], last: bool) -> std::io::Result<()> {
479        let encoding = self.decoder.encoding();
480        match self.decoder.decode_to_string(bytes, &mut self.out, last) {
481            Ok(_) => Ok(()),
482            Err(_) => Err(std::io::Error::new(
483                std::io::ErrorKind::InvalidData,
484                EncodingError::Other(encoding),
485            )),
486        }
487    }
488}
489
490#[cfg(feature = "encoding")]
491impl<R: std::io::BufRead> std::io::BufRead for DecodingReader<R> {
492    fn fill_buf(&mut self) -> std::io::Result<&[u8]> {
493        // Fast path: serve what has already been decoded.
494        if self.out_pos < self.out.len() {
495            return Ok(&self.out.as_bytes()[self.out_pos..]);
496        }
497        self.out.clear();
498        self.out_pos = 0;
499
500        if let Some(mut prefix) = self.prefix.take() {
501            if !prefix.detected {
502                prefix.detected = true;
503                while prefix.len < PREFIX_CAP {
504                    match std::io::Read::read(&mut self.inner, &mut prefix.buf[prefix.len..]) {
505                        Ok(0) => {
506                            self.inner_eof = true;
507                            break;
508                        }
509                        Ok(read) => prefix.len += read,
510                        Err(error) if error.kind() == std::io::ErrorKind::Interrupted => continue,
511                        Err(error) => {
512                            self.prefix = Some(prefix);
513                            return Err(error);
514                        }
515                    }
516                }
517                if let Some(detected) = detect_encoding(&prefix.buf[..prefix.len]) {
518                    let bom_len = detected.bom_len();
519                    if bom_len > 0 {
520                        prefix.buf.copy_within(bom_len..prefix.len, 0);
521                        prefix.len -= bom_len;
522                    }
523                    let encoding = detected.encoding();
524                    if encoding != Encoding::Utf8 {
525                        self.decoder = encoding.new_decoder();
526                    }
527                }
528            }
529
530            if prefix.len > 0 {
531                let len = prefix.len;
532                let mut bytes = [0; PREFIX_CAP];
533                bytes[..len].copy_from_slice(&prefix.buf[..len]);
534                prefix.len = 0;
535                // Keep the prefix alive (empty) so that `set_encoding` still
536                // works while the caller inspects the XML declaration that was
537                // just decoded from it.
538                self.prefix = Some(prefix);
539                self.decode_chunk(&bytes[..len], false)?;
540                if !self.out.is_empty() {
541                    return Ok(self.out.as_bytes());
542                }
543                // Nothing was produced (for example a lone byte of a UTF-16 code
544                // unit): drop the prefix and continue with the main path.
545                self.prefix = None;
546            }
547        }
548
549        if self.finished {
550            return Ok(&[]);
551        }
552
553        loop {
554            if self.inner_eof {
555                self.finished = true;
556                self.decode_chunk(&[], true)?;
557                return Ok(self.out.as_bytes());
558            }
559
560            let chunk = {
561                let available = self.inner.fill_buf()?;
562                if available.is_empty() {
563                    self.inner_eof = true;
564                    continue;
565                }
566                available.to_vec()
567            };
568            self.inner.consume(chunk.len());
569            self.decode_chunk(&chunk, false)?;
570            if !self.out.is_empty() {
571                return Ok(self.out.as_bytes());
572            }
573            // The decoder consumed everything into its internal state without
574            // producing output; an empty slice would be read as end of input, so
575            // more input has to be fed to it.
576        }
577    }
578
579    fn consume(&mut self, amount: usize) {
580        self.out_pos = (self.out_pos + amount).min(self.out.len());
581    }
582}
583
584#[cfg(feature = "encoding")]
585impl<R: std::io::BufRead> std::io::Read for DecodingReader<R> {
586    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
587        if buf.is_empty() {
588            return Ok(0);
589        }
590        let available = std::io::BufRead::fill_buf(self)?;
591        if available.is_empty() {
592            return Ok(0);
593        }
594        let len = available.len().min(buf.len());
595        buf[..len].copy_from_slice(&available[..len]);
596        std::io::BufRead::consume(self, len);
597        Ok(len)
598    }
599}
600
601#[cfg(test)]
602mod tests {
603    use super::*;
604
605    #[test]
606    fn utf8_decode_borrows() -> Result<(), EncodingError> {
607        let decoder = Decoder::utf8();
608        assert_eq!(decoder.decode(b"hello")?, Cow::Borrowed("hello"));
609        Ok(())
610    }
611
612    /// Malformed UTF-8 is rejected. Which variant is reported follows
613    /// quick-xml 0.41: without the `encoding` feature `decode` is a plain
614    /// `str::from_utf8`, with the feature it goes through the same fallible path
615    /// as every other encoding and reports `Other(UTF-8)`.
616    #[test]
617    fn utf8_decode_rejects_invalid() {
618        let decoder = Decoder::utf8();
619        #[cfg(not(feature = "encoding"))]
620        assert!(matches!(
621            decoder.decode(&[0xFF, 0xFE]),
622            Err(EncodingError::Utf8(_))
623        ));
624        #[cfg(feature = "encoding")]
625        {
626            assert_eq!(
627                decoder.decode(&[0xFF, 0xFE]),
628                Err(EncodingError::Other(Encoding::Utf8))
629            );
630            // `decode_into` keeps the `Utf8` variant, like quick-xml.
631            let mut buf = String::new();
632            assert!(matches!(
633                decoder.decode_into(&[0xFF, 0xFE], &mut buf),
634                Err(EncodingError::Utf8(_))
635            ));
636        }
637    }
638
639    #[test]
640    fn detect() {
641        assert_eq!(
642            detect_encoding(b"\xEF\xBB\xBF<?xml").map(|d| d.bom_len()),
643            Some(3)
644        );
645        assert_eq!(
646            detect_encoding(b"<?xml version").map(|d| d.bom_len()),
647            Some(0)
648        );
649        assert_eq!(detect_encoding(b"plain text"), None);
650    }
651
652    #[cfg(feature = "encoding")]
653    #[test]
654    fn non_utf8_decode() -> Result<(), EncodingError> {
655        // Windows-1252 0xE9 == 'é'
656        let decoder = Decoder {
657            encoding: Encoding::Windows1252,
658        };
659        assert_eq!(decoder.decode(&[b'r', b'o', b's', 0xE9])?, "rosé");
660        Ok(())
661    }
662
663    /// Regression: a byte-order mark is an authoritative signal of the actual
664    /// UTF-16 byte order, so a bare, endianness-free `encoding="UTF-16"`
665    /// declaration must not override it. Previously the reader's second
666    /// (declaration-driven) refinement stage trusted
667    /// `Encoding::for_label("UTF-16")`'s WHATWG default (`Utf16Le`) outright,
668    /// silently flipping a big-endian document to little-endian mojibake —
669    /// this is the same class of bug that was fixed in
670    /// `oxixml_encoding::SniffResult::best_effort_encoding`, but reachable
671    /// independently through this crate's own `Reader`/`EncodingRef` state
672    /// machine.
673    ///
674    /// The document body is deliberately plain ASCII (not genuinely
675    /// 2-bytes-per-character): only a byte-order mark makes it *sniff* as
676    /// UTF-16. `Reader` only understands ASCII-compatible byte streams (see
677    /// `DecodingReader`'s docs) unless wrapped by `DecodingReader`, so this is
678    /// the only way to reach the buggy refinement step through the crate's
679    /// public API without the unrelated, documented tokenizer limitation for
680    /// genuine 2-bytes-per-character content masking the result.
681    #[cfg(feature = "encoding")]
682    #[test]
683    fn bare_utf16_label_does_not_override_the_boms_byte_order() {
684        use crate::events::Event;
685        use crate::reader::Reader;
686
687        for big_endian in [true, false] {
688            let mut bytes = if big_endian {
689                vec![0xFE, 0xFF]
690            } else {
691                vec![0xFF, 0xFE]
692            };
693            bytes.extend_from_slice(b"<?xml version=\"1.0\" encoding=\"UTF-16\"?><r>hi</r>");
694
695            let mut reader = Reader::from_reader(bytes.as_slice());
696            let expected = if big_endian {
697                Encoding::Utf16Be
698            } else {
699                Encoding::Utf16Le
700            };
701
702            match reader.read_event() {
703                Ok(Event::Decl(_)) => {}
704                other => panic!("big_endian={big_endian}: expected Decl, got {other:?}"),
705            }
706            assert_eq!(
707                reader.decoder().encoding(),
708                expected,
709                "big_endian={big_endian}: the BOM's byte order must survive the declaration",
710            );
711
712            // And it stays that way for the rest of the document.
713            loop {
714                match reader.read_event() {
715                    Ok(Event::Eof) => break,
716                    Ok(_) => {}
717                    Err(e) => panic!("big_endian={big_endian}: unexpected error {e:?}"),
718                }
719            }
720            assert_eq!(
721                reader.decoder().encoding(),
722                expected,
723                "big_endian={big_endian}"
724            );
725        }
726    }
727}
728
729#[cfg(all(test, feature = "encoding"))]
730mod decoding_reader {
731    use super::*;
732    use std::io::{BufRead, Read};
733
734    /// A `BufRead` source that hands out at most `chunk` bytes at a time, so
735    /// that multi-byte sequences are split across `fill_buf` calls.
736    struct Chunked<'a> {
737        data: &'a [u8],
738        pos: usize,
739        chunk: usize,
740    }
741
742    impl std::io::Read for Chunked<'_> {
743        fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
744            let available = self.fill_buf()?;
745            let len = available.len().min(buf.len());
746            buf[..len].copy_from_slice(&available[..len]);
747            self.pos += len;
748            Ok(len)
749        }
750    }
751
752    impl std::io::BufRead for Chunked<'_> {
753        fn fill_buf(&mut self) -> std::io::Result<&[u8]> {
754            let end = (self.pos + self.chunk).min(self.data.len());
755            Ok(&self.data[self.pos..end])
756        }
757
758        fn consume(&mut self, amount: usize) {
759            self.pos = (self.pos + amount).min(self.data.len());
760        }
761    }
762
763    /// Encodes `text` as UTF-16, optionally with a byte-order mark.
764    fn utf16(text: &str, little_endian: bool, bom: bool) -> Vec<u8> {
765        let mut bytes = Vec::new();
766        if bom {
767            bytes.extend_from_slice(if little_endian {
768                UTF16_LE_BOM
769            } else {
770                UTF16_BE_BOM
771            });
772        }
773        for unit in text.encode_utf16() {
774            if little_endian {
775                bytes.extend_from_slice(&unit.to_le_bytes());
776            } else {
777                bytes.extend_from_slice(&unit.to_be_bytes());
778            }
779        }
780        bytes
781    }
782
783    fn decode_all(bytes: &[u8], chunk: usize) -> std::io::Result<String> {
784        let mut reader = DecodingReader::new(Chunked {
785            data: bytes,
786            pos: 0,
787            chunk,
788        });
789        let mut out = Vec::new();
790        reader.read_to_end(&mut out)?;
791        String::from_utf8(out)
792            .map_err(|error| std::io::Error::new(std::io::ErrorKind::InvalidData, error))
793    }
794
795    #[test]
796    fn utf8_passes_through() -> std::io::Result<()> {
797        assert_eq!(decode_all(b"<a>text</a>", 3)?, "<a>text</a>");
798        assert_eq!(decode_all(UTF8_BOM, 1)?, "");
799        Ok(())
800    }
801
802    #[test]
803    fn transcodes_utf16_at_every_chunk_size() -> std::io::Result<()> {
804        let text = "<?xml version='1.0' encoding='UTF-16'?><doc>текст</doc>";
805        for little_endian in [true, false] {
806            for bom in [true, false] {
807                for chunk in [1, 2, 3, 7, 64, 4096] {
808                    let bytes = utf16(text, little_endian, bom);
809                    assert_eq!(
810                        decode_all(&bytes, chunk)?,
811                        text,
812                        "little_endian={little_endian} bom={bom} chunk={chunk}",
813                    );
814                }
815            }
816        }
817        Ok(())
818    }
819
820    #[test]
821    fn reports_malformed_input() {
822        // A lone UTF-16 high surrogate at the end of the stream.
823        let mut bytes = utf16("<a/>", true, true);
824        bytes.extend_from_slice(&[0x00, 0xD8]);
825        let error = decode_all(&bytes, 4).expect_err("a truncated sequence must fail");
826        assert_eq!(error.kind(), std::io::ErrorKind::InvalidData);
827    }
828
829    #[test]
830    fn exposes_and_replaces_the_encoding() -> std::io::Result<()> {
831        // The declaration fits into the upfront-read prefix; the non-ASCII byte
832        // deliberately does not, so it is decoded with the installed encoding.
833        let mut bytes = b"<?xml version='1.0' encoding='ISO-8859-1'?><a>".to_vec();
834        bytes.extend_from_slice(&b"-".repeat(32));
835        bytes.extend_from_slice(&[0xE9]);
836        bytes.extend_from_slice(b"</a>");
837
838        let mut reader = DecodingReader::new(&bytes[..]);
839        assert_eq!(reader.encoding(), Encoding::Utf8);
840        assert_eq!(reader.get_ref().len(), bytes.len());
841
842        // Serve the prefix, which is what the XML declaration is parsed from...
843        assert!(!reader.fill_buf()?.is_empty());
844        // ... then install the declared encoding, still in time.
845        assert!(reader.set_encoding(Encoding::Latin1));
846        assert_eq!(reader.encoding(), Encoding::Latin1);
847
848        let mut out = String::new();
849        reader.read_to_string(&mut out)?;
850        assert!(out.ends_with("\u{e9}</a>"), "{out}");
851        assert!(reader.get_mut().is_empty());
852        assert!(reader.into_inner().is_empty());
853        Ok(())
854    }
855
856    #[test]
857    fn refuses_to_change_the_encoding_too_late() -> std::io::Result<()> {
858        let bytes =
859            b"<a>0123456789012345678901234567890123456789012345678901234567890123456789</a>";
860        let mut reader = DecodingReader::new(&bytes[..]);
861        let mut out = String::new();
862        reader.read_to_string(&mut out)?;
863        assert_eq!(out, std::str::from_utf8(bytes).expect("utf-8"));
864        // Setting the encoding that is already in use is a no-op that succeeds.
865        assert!(reader.set_encoding(Encoding::Utf8));
866        // Changing it once the prefix is gone is refused instead of panicking.
867        assert!(!reader.set_encoding(Encoding::Utf16Le));
868        Ok(())
869    }
870
871    #[test]
872    fn parses_a_utf16_document_with_the_reader() -> crate::errors::Result<()> {
873        use crate::events::Event;
874        use crate::reader::Reader;
875
876        // The document carries no `encoding` pseudo-attribute: the reader would
877        // otherwise apply it to the *transcoded* (UTF-8) stream, see the note on
878        // `DecodingReader`.
879        let bytes = utf16("<?xml version='1.0'?><doc>hi</doc>", true, true);
880        let mut reader = Reader::from_reader(DecodingReader::new(bytes.as_slice()));
881        let mut buf = Vec::new();
882        let mut texts = Vec::new();
883        loop {
884            buf.clear();
885            match reader.read_event_into(&mut buf)? {
886                Event::Eof => break,
887                Event::Text(text) => texts.push(text.decode()?.into_owned()),
888                _ => continue,
889            }
890        }
891        assert_eq!(texts, vec!["hi".to_owned()]);
892        Ok(())
893    }
894}