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}