Coverage Report

Created: 2026-11-12 07:03

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/quick-xml-0.42.0/src/encoding.rs
Line
Count
Source
1
//! A module for wrappers that encode / decode data.
2
3
use std::str::Utf8Error;
4
5
#[cfg(feature = "encoding")]
6
use encoding_rs;
7
#[cfg(feature = "encoding")]
8
use std::borrow::Cow;
9
#[cfg(feature = "encoding")]
10
use 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>
14
pub(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>
17
pub(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>
20
pub(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]
29
pub 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
37
impl From<Utf8Error> for EncodingError {
38
    #[inline]
39
0
    fn from(e: Utf8Error) -> Self {
40
0
        Self::Utf8(e)
41
0
    }
42
}
43
44
impl std::error::Error for EncodingError {
45
0
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
46
0
        match self {
47
0
            Self::Utf8(e) => Some(e),
48
            #[cfg(feature = "encoding")]
49
            Self::Other(_) => None,
50
        }
51
0
    }
52
}
53
54
impl std::fmt::Display for EncodingError {
55
0
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56
0
        match self {
57
0
            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
0
    }
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)]
79
pub 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")]
88
pub 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")]
99
pub 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
153
0
pub 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
0
    match bytes {
157
        // with BOM
158
0
        _ if bytes.starts_with(UTF16_BE_BOM) => Some(DetectedEncoding::Utf16BeBom),
159
0
        _ if bytes.starts_with(UTF16_LE_BOM) => Some(DetectedEncoding::Utf16LeBom),
160
0
        _ if bytes.starts_with(UTF8_BOM) => Some(DetectedEncoding::Utf8Bom),
161
162
        // without BOM
163
0
        _ if bytes.starts_with(&[0x00, b'<', 0x00, b'?']) => Some(DetectedEncoding::Utf16BeLike), // Some BE encoding, for example, UTF-16 or ISO-10646-UCS-2
164
0
        _ if bytes.starts_with(&[b'<', 0x00, b'?', 0x00]) => Some(DetectedEncoding::Utf16LeLike), // Some LE encoding, for example, UTF-16 or ISO-10646-UCS-2
165
0
        _ if bytes.starts_with(&[b'<', b'?', b'x', b'm']) => {
166
0
            Some(DetectedEncoding::AsciiCompatible)
167
        } // Some ASCII compatible
168
169
0
        _ => None,
170
    }
171
0
}
172
173
/// Possible scenarios for start-of-xml detection of encoding
174
///
175
/// See the documentation of [`detect_encoding`]
176
pub 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
191
impl 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
0
    pub const fn bom_len(&self) -> usize {
204
0
        match self {
205
0
            DetectedEncoding::Utf8Bom => 3,
206
0
            DetectedEncoding::Utf16LeBom | DetectedEncoding::Utf16BeBom => 2,
207
            DetectedEncoding::AsciiCompatible
208
            | DetectedEncoding::Utf16LeLike
209
0
            | DetectedEncoding::Utf16BeLike => 0,
210
        }
211
0
    }
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")]
218
const PREFIX_CAP: usize = 64;
219
220
#[cfg(feature = "encoding")]
221
struct 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")]
285
pub 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")]
306
impl<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")]
320
impl<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")]
391
impl<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")]
566
impl<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"))]
583
mod 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
}