Skip to main content

base/
encoding.rs

1// Strict Unicode interchange codecs. New native library functionality (no legacy counterpart).
2use alloc::{string::String, vec::Vec};
3
4/// Explicit text encodings; unmarked input is interpreted as UTF-8.
5#[derive(Clone, Copy, Debug, PartialEq, Eq)]
6#[non_exhaustive]
7pub enum Encoding {
8    Utf8,
9    Utf8Bom,
10    Utf16Le,
11    Utf16Be,
12}
13
14/// Invalid Unicode, unsupported UTF-32, or a caller-specified byte limit.
15#[derive(Clone, Copy, Debug, PartialEq, Eq)]
16#[non_exhaustive]
17pub enum Error {
18    Invalid,
19    Unsupported,
20    Limit,
21}
22
23/// Decode without replacement characters or guessed legacy code pages.
24pub fn decode(bytes: &[u8], limit: usize) -> Result<(String, Encoding), Error> {
25    if bytes.len() > limit {
26        return Err(Error::Limit);
27    }
28    if bytes.starts_with(&[0xff, 0xfe, 0, 0]) || bytes.starts_with(&[0, 0, 0xfe, 0xff]) {
29        return Err(Error::Unsupported);
30    }
31    let (b, enc) = if bytes.starts_with(&[0xef, 0xbb, 0xbf]) {
32        (&bytes[3..], Encoding::Utf8Bom)
33    } else if bytes.starts_with(&[0xff, 0xfe]) {
34        (&bytes[2..], Encoding::Utf16Le)
35    } else if bytes.starts_with(&[0xfe, 0xff]) {
36        (&bytes[2..], Encoding::Utf16Be)
37    } else {
38        (bytes, Encoding::Utf8)
39    };
40    let text = match enc {
41        Encoding::Utf8 | Encoding::Utf8Bom => {
42            String::from(core::str::from_utf8(b).map_err(|_| Error::Invalid)?)
43        }
44        _ => {
45            if b.len() % 2 != 0 {
46                return Err(Error::Invalid);
47            }
48            let units = b.chunks_exact(2).map(|p| {
49                if enc == Encoding::Utf16Le {
50                    u16::from_le_bytes([p[0], p[1]])
51                } else {
52                    u16::from_be_bytes([p[0], p[1]])
53                }
54            });
55            let mut s = String::new();
56            for c in core::char::decode_utf16(units) {
57                s.push(c.map_err(|_| Error::Invalid)?);
58            }
59            s
60        }
61    };
62    if text.len() > limit {
63        return Err(Error::Limit);
64    }
65    Ok((text, enc))
66}
67
68/// Encode Unicode; UTF-16 output always includes its byte-order mark.
69pub fn encode(text: &str, enc: Encoding) -> Vec<u8> {
70    let mut out = Vec::new();
71    match enc {
72        Encoding::Utf8 => out.extend_from_slice(text.as_bytes()),
73        Encoding::Utf8Bom => {
74            out.extend_from_slice(&[0xef, 0xbb, 0xbf]);
75            out.extend_from_slice(text.as_bytes());
76        }
77        Encoding::Utf16Le | Encoding::Utf16Be => {
78            out.extend_from_slice(if enc == Encoding::Utf16Le {
79                &[0xff, 0xfe]
80            } else {
81                &[0xfe, 0xff]
82            });
83            for u in text.encode_utf16() {
84                out.extend_from_slice(&if enc == Encoding::Utf16Le {
85                    u.to_le_bytes()
86                } else {
87                    u.to_be_bytes()
88                });
89            }
90        }
91    }
92    out
93}