mod decode;
mod encode;
mod tables;
pub use decode::*;
pub use encode::*;
const PAD_BYTE: u8 = b'=';
pub const STANDARD: Config = Config {
char_set: CharacterSet::Standard,
pad: true,
decode_allow_trailing_bits: false,
};
#[derive(Clone, Copy, Debug)]
pub struct Config {
char_set: CharacterSet,
pad: bool,
decode_allow_trailing_bits: bool,
}
impl Config {
pub const fn new(char_set: CharacterSet, pad: bool) -> Config {
Config {
char_set,
pad,
decode_allow_trailing_bits: false,
}
}
pub const fn pad(self, pad: bool) -> Config {
Config { pad, ..self }
}
pub const fn decode_allow_trailing_bits(self, allow: bool) -> Config {
Config {
decode_allow_trailing_bits: allow,
..self
}
}
}
#[derive(Clone, Copy, Debug)]
pub enum CharacterSet {
Standard,
UrlSafe,
Crypt,
Bcrypt,
ImapMutf7,
BinHex,
}
impl CharacterSet {
fn encode_table(self) -> &'static [u8; 64] {
match self {
CharacterSet::Standard => tables::STANDARD_ENCODE,
CharacterSet::UrlSafe => tables::URL_SAFE_ENCODE,
CharacterSet::Crypt => tables::CRYPT_ENCODE,
CharacterSet::Bcrypt => tables::BCRYPT_ENCODE,
CharacterSet::ImapMutf7 => tables::IMAP_MUTF7_ENCODE,
CharacterSet::BinHex => tables::BINHEX_ENCODE,
}
}
fn decode_table(self) -> &'static [u8; 256] {
match self {
CharacterSet::Standard => tables::STANDARD_DECODE,
CharacterSet::UrlSafe => tables::URL_SAFE_DECODE,
CharacterSet::Crypt => tables::CRYPT_DECODE,
CharacterSet::Bcrypt => tables::BCRYPT_DECODE,
CharacterSet::ImapMutf7 => tables::IMAP_MUTF7_DECODE,
CharacterSet::BinHex => tables::BINHEX_DECODE,
}
}
}
mod chunked_encoder {
use super::{add_padding, encode_to_slice, Config};
use core::cmp;
pub(crate) trait Sink {
type Error;
fn write_encoded_bytes(&mut self, encoded: &[u8]) -> Result<(), Self::Error>;
}
const BUF_SIZE: usize = 1024;
pub(crate) struct ChunkedEncoder {
config: Config,
max_input_chunk_len: usize,
}
impl ChunkedEncoder {
pub(crate) fn new(config: Config) -> ChunkedEncoder {
ChunkedEncoder {
config,
max_input_chunk_len: max_input_length(BUF_SIZE, config),
}
}
pub(crate) fn encode<S: Sink>(&self, bytes: &[u8], sink: &mut S) -> Result<(), S::Error> {
let mut encode_buf: [u8; BUF_SIZE] = [0; BUF_SIZE];
let encode_table = self.config.char_set.encode_table();
let mut input_index = 0;
while input_index < bytes.len() {
let input_chunk_len = cmp::min(self.max_input_chunk_len, bytes.len() - input_index);
let chunk = &bytes[input_index..(input_index + input_chunk_len)];
let mut b64_bytes_written = encode_to_slice(chunk, &mut encode_buf, encode_table);
input_index += input_chunk_len;
let more_input_left = input_index < bytes.len();
if self.config.pad && !more_input_left {
b64_bytes_written += add_padding(bytes.len(), &mut encode_buf[b64_bytes_written..]);
}
sink.write_encoded_bytes(&encode_buf[0..b64_bytes_written])?;
}
Ok(())
}
}
fn max_input_length(encoded_buf_len: usize, config: Config) -> usize {
let effective_buf_len = if config.pad {
encoded_buf_len
.checked_sub(2)
.expect("Don't use a tiny buffer")
} else {
encoded_buf_len
};
(effective_buf_len / 4) * 3
}
#[cfg(any(feature = "alloc", feature = "std", test))]
pub(crate) struct StringSink<'a> {
string: &'a mut String,
}
#[cfg(any(feature = "alloc", feature = "std", test))]
impl<'a> StringSink<'a> {
pub(crate) fn new(s: &mut String) -> StringSink<'_> {
StringSink { string: s }
}
}
#[cfg(any(feature = "alloc", feature = "std", test))]
impl<'a> Sink for StringSink<'a> {
type Error = ();
fn write_encoded_bytes(&mut self, s: &[u8]) -> Result<(), Self::Error> {
self.string.push_str(std::str::from_utf8(s).unwrap());
Ok(())
}
}
}