Skip to main content

oxixml_encoding/
bom.rs

1//! Byte-order-mark detection.
2
3use crate::label::Encoding;
4
5/// Detect a byte-order mark (BOM) at the start of `bytes`, one of the three signatures relevant
6/// to the encodings this crate supports (see
7/// [XML 1.0 Appendix F.1](https://www.w3.org/TR/xml/#sec-guessing)):
8///
9/// | Bytes            | Encoding      |
10/// |-------------------|--------------|
11/// | `EF BB BF`         | [`Encoding::Utf8`] |
12/// | `FE FF`             | [`Encoding::Utf16Be`] |
13/// | `FF FE`             | [`Encoding::Utf16Le`] |
14///
15/// Returns the detected encoding together with the number of leading bytes that make up the
16/// BOM, so a caller can skip exactly that many bytes before decoding the rest of the stream.
17/// Returns [`None`] if `bytes` does not start with any of the three signatures (this includes
18/// streams that are simply too short to contain one).
19///
20/// # Examples
21///
22/// ```
23/// use oxixml_encoding::{detect_bom, Encoding};
24///
25/// assert_eq!(detect_bom(&[0xEF, 0xBB, 0xBF, b'<']), Some((Encoding::Utf8, 3)));
26/// assert_eq!(detect_bom(&[0xFF, 0xFE, b'<', 0x00]), Some((Encoding::Utf16Le, 2)));
27/// assert_eq!(detect_bom(b"<?xml"), None);
28/// ```
29#[must_use]
30pub fn detect_bom(bytes: &[u8]) -> Option<(Encoding, usize)> {
31    if bytes.starts_with(&[0xEF, 0xBB, 0xBF]) {
32        Some((Encoding::Utf8, 3))
33    } else if bytes.starts_with(&[0xFE, 0xFF]) {
34        Some((Encoding::Utf16Be, 2))
35    } else if bytes.starts_with(&[0xFF, 0xFE]) {
36        Some((Encoding::Utf16Le, 2))
37    } else {
38        None
39    }
40}
41
42#[cfg(test)]
43mod tests {
44    use super::*;
45
46    #[test]
47    fn detects_utf8_bom() {
48        assert_eq!(detect_bom(&[0xEF, 0xBB, 0xBF]), Some((Encoding::Utf8, 3)));
49        assert_eq!(
50            detect_bom(&[0xEF, 0xBB, 0xBF, b'a', b'b']),
51            Some((Encoding::Utf8, 3))
52        );
53    }
54
55    #[test]
56    fn detects_utf16_be_bom() {
57        assert_eq!(detect_bom(&[0xFE, 0xFF]), Some((Encoding::Utf16Be, 2)));
58    }
59
60    #[test]
61    fn detects_utf16_le_bom() {
62        assert_eq!(detect_bom(&[0xFF, 0xFE]), Some((Encoding::Utf16Le, 2)));
63    }
64
65    #[test]
66    fn no_bom_returns_none() {
67        assert_eq!(detect_bom(b"<?xml version=\"1.0\"?>"), None);
68        assert_eq!(detect_bom(b""), None);
69        assert_eq!(detect_bom(&[0x00]), None);
70    }
71
72    #[test]
73    fn utf32_bom_is_not_recognized() {
74        // 00 00 FE FF is the UTF-32BE BOM; this crate has no UTF-32 support, so it must not be
75        // misreported as one of the three encodings it does support.
76        assert_eq!(detect_bom(&[0x00, 0x00, 0xFE, 0xFF]), None);
77    }
78}