Skip to main content

rust_web_server/core/base64/
decode.rs

1//! The one base64 decoder: a lookup table per alphabet and a single pass
2//! over the input, so decoding is linear in its length.
3//!
4//! Padding is optional. A `=` ends a quantum of two or three characters
5//! (at most the padding that quantum needs); data after it starts a new
6//! segment, so concatenated padded base64 decodes as a whole. A lone
7//! trailing character (six bits, not a byte) is an error.
8
9use std::fmt;
10
11/// Which characters encode 62 and 63.
12#[derive(Clone, Copy, Debug, PartialEq, Eq)]
13pub enum Alphabet {
14    /// `+` and `/` (RFC 4648 §4).
15    Standard,
16    /// `-` and `_` (RFC 4648 §5).
17    UrlSafe,
18    /// Either pair, for input whose producer isn't known.
19    Either,
20}
21
22/// A base64 decoder configuration.
23///
24/// ```
25/// use rust_web_server::core::base64::Decoder;
26///
27/// assert_eq!(Decoder::STANDARD.decode("TWFu").unwrap(), b"Man");
28/// assert_eq!(Decoder::URL_SAFE.decode("-_8").unwrap(), [0xfb, 0xff]);
29/// assert_eq!(Decoder::STANDARD.skip_whitespace().decode("TW\r\nFu").unwrap(), b"Man");
30/// assert!(Decoder::STANDARD.decode("TWF").is_ok());   // padding is optional
31/// assert!(Decoder::STANDARD.decode("T").is_err());    // six bits aren't a byte
32/// ```
33#[derive(Clone, Copy, Debug, PartialEq, Eq)]
34pub struct Decoder {
35    alphabet: Alphabet,
36    whitespace: bool,
37    skip_invalid: bool,
38}
39
40/// Why input isn't base64.
41#[derive(Clone, Debug, PartialEq, Eq)]
42pub struct DecodeError {
43    /// Byte offset of the offending character, or the input length when it
44    /// ends with a lone character.
45    pub position: usize,
46    reason: &'static str,
47}
48
49impl fmt::Display for DecodeError {
50    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51        write!(f, "{} at byte {}", self.reason, self.position)
52    }
53}
54
55impl std::error::Error for DecodeError {}
56
57impl From<DecodeError> for String {
58    fn from(e: DecodeError) -> String {
59        e.to_string()
60    }
61}
62
63const INVALID: u8 = 0xff;
64
65const fn table(alphabet: Alphabet) -> [u8; 256] {
66    let mut t = [INVALID; 256];
67    let mut i = 0;
68    while i < 26 {
69        t[b'A' as usize + i] = i as u8;
70        t[b'a' as usize + i] = 26 + i as u8;
71        i += 1;
72    }
73    let mut d = 0;
74    while d < 10 {
75        t[b'0' as usize + d] = 52 + d as u8;
76        d += 1;
77    }
78    if !matches!(alphabet, Alphabet::UrlSafe) {
79        t[b'+' as usize] = 62;
80        t[b'/' as usize] = 63;
81    }
82    if !matches!(alphabet, Alphabet::Standard) {
83        t[b'-' as usize] = 62;
84        t[b'_' as usize] = 63;
85    }
86    t
87}
88
89static STANDARD: [u8; 256] = table(Alphabet::Standard);
90static URL_SAFE: [u8; 256] = table(Alphabet::UrlSafe);
91static EITHER: [u8; 256] = table(Alphabet::Either);
92
93impl Decoder {
94    /// `+` and `/`, no whitespace.
95    pub const STANDARD: Decoder = Decoder::new(Alphabet::Standard);
96    /// `-` and `_`, no whitespace.
97    pub const URL_SAFE: Decoder = Decoder::new(Alphabet::UrlSafe);
98    /// Both alphabets, no whitespace.
99    pub const EITHER: Decoder = Decoder::new(Alphabet::Either);
100
101    /// A decoder for `alphabet` that refuses whitespace.
102    pub const fn new(alphabet: Alphabet) -> Decoder {
103        Decoder { alphabet, whitespace: false, skip_invalid: false }
104    }
105
106    /// Ignore ASCII whitespace anywhere (PEM bodies, wrapped lines).
107    pub const fn skip_whitespace(self) -> Decoder {
108        Decoder { whitespace: true, ..self }
109    }
110
111    /// Ignore every character outside the alphabet, padding included —
112    /// only for callers that always did (`x509::base64_decode_std`).
113    pub(crate) const fn skip_invalid(self) -> Decoder {
114        Decoder { whitespace: true, skip_invalid: true, ..self }
115    }
116
117    /// Decode `input`.
118    pub fn decode(&self, input: impl AsRef<[u8]>) -> Result<Vec<u8>, DecodeError> {
119        let input = input.as_ref();
120        let table = match self.alphabet {
121            Alphabet::Standard => &STANDARD,
122            Alphabet::UrlSafe => &URL_SAFE,
123            Alphabet::Either => &EITHER,
124        };
125        let mut out = Vec::with_capacity(input.len() / 4 * 3 + 3);
126        // Sextets of the current quantum, and how many.
127        let mut quantum = 0u32;
128        let mut n = 0u32;
129        // Padding still allowed after a `=` (0 outside padding).
130        let mut pads_left = 0u32;
131        let mut padding = false;
132        for (i, &c) in input.iter().enumerate() {
133            let v = table[c as usize];
134            if v != INVALID {
135                padding = false;
136                quantum = quantum << 6 | v as u32;
137                n += 1;
138                if n == 4 {
139                    out.extend_from_slice(&[(quantum >> 16) as u8, (quantum >> 8) as u8, quantum as u8]);
140                    quantum = 0;
141                    n = 0;
142                }
143                continue;
144            }
145            if c == b'=' {
146                if self.skip_invalid {
147                    continue;
148                }
149                if padding {
150                    if pads_left == 0 {
151                        return Err(DecodeError { position: i, reason: "too much padding" });
152                    }
153                    pads_left -= 1;
154                    continue;
155                }
156                if n < 2 {
157                    return Err(DecodeError { position: i, reason: "padding where none belongs" });
158                }
159                flush(&mut out, quantum, n);
160                pads_left = 3 - n;
161                quantum = 0;
162                n = 0;
163                padding = true;
164                continue;
165            }
166            if (self.whitespace && c.is_ascii_whitespace()) || self.skip_invalid {
167                continue;
168            }
169            return Err(DecodeError { position: i, reason: "not a base64 character" });
170        }
171        if n == 1 {
172            return Err(DecodeError { position: input.len(), reason: "a lone trailing character" });
173        }
174        flush(&mut out, quantum, n);
175        Ok(out)
176    }
177}
178
179/// The bytes of an unfinished quantum of `n` (2 or 3) sextets.
180fn flush(out: &mut Vec<u8>, quantum: u32, n: u32) {
181    match n {
182        2 => out.push((quantum >> 4) as u8),
183        3 => out.extend_from_slice(&[(quantum >> 10) as u8, (quantum >> 2) as u8]),
184        _ => {}
185    }
186}