Skip to main content

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