use std::fmt;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Alphabet {
Standard,
UrlSafe,
Either,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Decoder {
alphabet: Alphabet,
whitespace: bool,
skip_invalid: bool,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DecodeError {
pub position: usize,
reason: &'static str,
}
impl fmt::Display for DecodeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} at byte {}", self.reason, self.position)
}
}
impl std::error::Error for DecodeError {}
impl From<DecodeError> for String {
fn from(e: DecodeError) -> String {
e.to_string()
}
}
const INVALID: u8 = 0xff;
const fn table(alphabet: Alphabet) -> [u8; 256] {
let mut t = [INVALID; 256];
let mut i = 0;
while i < 26 {
t[b'A' as usize + i] = i as u8;
t[b'a' as usize + i] = 26 + i as u8;
i += 1;
}
let mut d = 0;
while d < 10 {
t[b'0' as usize + d] = 52 + d as u8;
d += 1;
}
if !matches!(alphabet, Alphabet::UrlSafe) {
t[b'+' as usize] = 62;
t[b'/' as usize] = 63;
}
if !matches!(alphabet, Alphabet::Standard) {
t[b'-' as usize] = 62;
t[b'_' as usize] = 63;
}
t
}
static STANDARD: [u8; 256] = table(Alphabet::Standard);
static URL_SAFE: [u8; 256] = table(Alphabet::UrlSafe);
static EITHER: [u8; 256] = table(Alphabet::Either);
impl Decoder {
pub const STANDARD: Decoder = Decoder::new(Alphabet::Standard);
pub const URL_SAFE: Decoder = Decoder::new(Alphabet::UrlSafe);
pub const EITHER: Decoder = Decoder::new(Alphabet::Either);
pub const fn new(alphabet: Alphabet) -> Decoder {
Decoder { alphabet, whitespace: false, skip_invalid: false }
}
pub const fn skip_whitespace(self) -> Decoder {
Decoder { whitespace: true, ..self }
}
pub(crate) const fn skip_invalid(self) -> Decoder {
Decoder { whitespace: true, skip_invalid: true, ..self }
}
pub fn decode(&self, input: impl AsRef<[u8]>) -> Result<Vec<u8>, DecodeError> {
let input = input.as_ref();
let table = match self.alphabet {
Alphabet::Standard => &STANDARD,
Alphabet::UrlSafe => &URL_SAFE,
Alphabet::Either => &EITHER,
};
let mut out = Vec::with_capacity(input.len() / 4 * 3 + 3);
let mut quantum = 0u32;
let mut n = 0u32;
let mut pads_left = 0u32;
let mut padding = false;
for (i, &c) in input.iter().enumerate() {
let v = table[c as usize];
if v != INVALID {
padding = false;
quantum = quantum << 6 | v as u32;
n += 1;
if n == 4 {
out.extend_from_slice(&[(quantum >> 16) as u8, (quantum >> 8) as u8, quantum as u8]);
quantum = 0;
n = 0;
}
continue;
}
if c == b'=' {
if self.skip_invalid {
continue;
}
if padding {
if pads_left == 0 {
return Err(DecodeError { position: i, reason: "too much padding" });
}
pads_left -= 1;
continue;
}
if n < 2 {
return Err(DecodeError { position: i, reason: "padding where none belongs" });
}
flush(&mut out, quantum, n);
pads_left = 3 - n;
quantum = 0;
n = 0;
padding = true;
continue;
}
if (self.whitespace && c.is_ascii_whitespace()) || self.skip_invalid {
continue;
}
return Err(DecodeError { position: i, reason: "not a base64 character" });
}
if n == 1 {
return Err(DecodeError { position: input.len(), reason: "a lone trailing character" });
}
flush(&mut out, quantum, n);
Ok(out)
}
}
fn flush(out: &mut Vec<u8>, quantum: u32, n: u32) {
match n {
2 => out.push((quantum >> 4) as u8),
3 => out.extend_from_slice(&[(quantum >> 10) as u8, (quantum >> 2) as u8]),
_ => {}
}
}