extern crate byteorder;
use std::{fmt, error, str};
use byteorder::{BigEndian, ByteOrder};
const STANDARD: [u8; 64] = [
0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50,
0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
0x59, 0x5A, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66,
0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E,
0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76,
0x77, 0x78, 0x79, 0x7A, 0x30, 0x31, 0x32, 0x33,
0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x2B, 0x2F
];
const URL_SAFE: [u8; 64] = [
0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50,
0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
0x59, 0x5A, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66,
0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E,
0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76,
0x77, 0x78, 0x79, 0x7A, 0x30, 0x31, 0x32, 0x33,
0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x2D, 0x5F
];
mod decode_tables;
pub enum Base64Mode {
Standard,
UrlSafe,
}
#[derive(Debug, PartialEq, Eq)]
pub enum Base64Error {
Utf8(str::Utf8Error),
InvalidByte(usize, u8),
InvalidLength,
}
impl fmt::Display for Base64Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Base64Error::Utf8(ref err) => err.fmt(f),
Base64Error::InvalidByte(index, byte) =>
write!(f, "Invalid byte {}, offset {}.", byte, index),
Base64Error::InvalidLength =>
write!(f, "Encoded text cannot have a 6-bit remainder.")
}
}
}
impl error::Error for Base64Error {
fn description(&self) -> &str {
match *self {
Base64Error::Utf8(ref err) => err.description(),
Base64Error::InvalidByte(_,_) => "invalid byte",
Base64Error::InvalidLength => "invalid length"
}
}
fn cause(&self) -> Option<&error::Error> {
match *self {
Base64Error::Utf8(ref err) => Some(err as &error::Error),
_ => None
}
}
}
impl From<str::Utf8Error> for Base64Error {
fn from(err: str::Utf8Error) -> Base64Error {
Base64Error::Utf8(err)
}
}
pub fn encode(input: &[u8]) -> String {
encode_mode(input, Base64Mode::Standard)
}
pub fn decode(input: &str) -> Result<Vec<u8>, Base64Error> {
decode_mode(input, Base64Mode::Standard)
}
pub fn decode_ws(input: &str) -> Result<Vec<u8>, Base64Error> {
let mut raw = Vec::<u8>::with_capacity(input.len());
raw.extend(input.bytes().filter(|b| !b" \n\t\r\x0c".contains(b)));
let sans_ws = String::from_utf8(raw).unwrap();
decode_mode(&sans_ws, Base64Mode::Standard)
}
pub fn encode_mode(bytes: &[u8], mode: Base64Mode) -> String {
let mut buf = match encoded_size(bytes.len()) {
Some(n) => String::with_capacity(n),
None => panic!("integer overflow when calculating buffer size")
};
encode_mode_buf(bytes, mode, &mut buf);
buf
}
fn encoded_size(bytes_len: usize) -> Option<usize> {
bytes_len
.checked_add(2)
.map(|x| x / 3)
.and_then(|x| x.checked_mul(4))
}
pub fn encode_mode_buf(bytes: &[u8], mode: Base64Mode, buf: &mut String) {
let (ref charset, _) = match mode {
Base64Mode::Standard => (STANDARD, false),
Base64Mode::UrlSafe => (URL_SAFE, false),
};
let resv_size = match encoded_size(bytes.len()) {
Some(n) => n,
None => panic!("integer overflow when calculating buffer size"),
};
buf.reserve(resv_size);
let rem = bytes.len() % 3;
let div = bytes.len() - rem;
let mut raw: &mut Vec<u8>;
unsafe {
raw = buf.as_mut_vec();
}
let mut i = 0;
while i < div {
raw.push(charset[(bytes[i] >> 2) as usize]);
raw.push(charset[((bytes[i] << 4 | bytes[i+1] >> 4) & 0x3f) as usize]);
raw.push(charset[((bytes[i+1] << 2 | bytes[i+2] >> 6) & 0x3f) as usize]);
raw.push(charset[(bytes[i+2] & 0x3f) as usize]);
i+=3;
}
if rem == 2 {
raw.push(charset[(bytes[div] >> 2) as usize]);
raw.push(charset[((bytes[div] << 4 | bytes[div+1] >> 4) & 0x3f) as usize]);
raw.push(charset[(bytes[div+1] << 2 & 0x3f) as usize]);
} else if rem == 1 {
raw.push(charset[(bytes[div] >> 2) as usize]);
raw.push(charset[(bytes[div] << 4 & 0x3f) as usize]);
}
for _ in 0..(3-rem)%3 {
raw.push(0x3d);
}
}
pub fn decode_mode(input: &str, mode: Base64Mode) -> Result<Vec<u8>, Base64Error> {
let mut buffer = Vec::<u8>::with_capacity(input.len() * 4 / 3);
decode_mode_buf(input, mode, &mut buffer).map(|_| buffer)
}
pub fn decode_mode_buf(input: &str, mode: Base64Mode, buffer: &mut Vec<u8>) -> Result<(), Base64Error> {
let (ref decode_table, _) = match mode {
Base64Mode::Standard => (decode_tables::STANDARD, false),
Base64Mode::UrlSafe => (decode_tables::URL_SAFE, false),
};
buffer.reserve(input.len() * 3 / 4);
let chunk_len = 8;
let decoded_chunk_len = 6;
let remainder_len = input.len() % chunk_len;
let trailing_bytes_to_skip = if remainder_len == 0 {
chunk_len
} else {
remainder_len
};
let length_of_full_chunks = input.len().saturating_sub(trailing_bytes_to_skip);
let starting_output_index = buffer.len();
let new_size = starting_output_index
+ length_of_full_chunks / chunk_len * decoded_chunk_len
+ (chunk_len - decoded_chunk_len);
buffer.resize(new_size, 0);
let mut output_index = starting_output_index;
let input_bytes = input.as_bytes();
{
let buffer_slice = buffer.as_mut_slice();
let mut input_index = 0;
let mut bad_byte_index: usize = 0;
let mut morsel: u8 = 0;
while input_index < length_of_full_chunks {
let mut accum: u64;
let input_chunk = BigEndian::read_u64(&input_bytes[input_index..(input_index + 8)]);
morsel = decode_table[(input_chunk >> 56) as usize];
if morsel == decode_tables::INVALID_VALUE {
bad_byte_index = input_index;
break;
};
accum = (morsel as u64) << 58;
morsel = decode_table[(input_chunk >> 48 & 0xFF) as usize];
if morsel == decode_tables::INVALID_VALUE {
bad_byte_index = input_index + 1;
break;
};
accum |= (morsel as u64) << 52;
morsel = decode_table[(input_chunk >> 40 & 0xFF) as usize];
if morsel == decode_tables::INVALID_VALUE {
bad_byte_index = input_index + 2;
break;
};
accum |= (morsel as u64) << 46;
morsel = decode_table[(input_chunk >> 32 & 0xFF) as usize];
if morsel == decode_tables::INVALID_VALUE {
bad_byte_index = input_index + 3;
break;
};
accum |= (morsel as u64) << 40;
morsel = decode_table[(input_chunk >> 24 & 0xFF) as usize];
if morsel == decode_tables::INVALID_VALUE {
bad_byte_index = input_index + 4;
break;
};
accum |= (morsel as u64) << 34;
morsel = decode_table[(input_chunk >> 16 & 0xFF) as usize];
if morsel == decode_tables::INVALID_VALUE {
bad_byte_index = input_index + 5;
break;
};
accum |= (morsel as u64) << 28;
morsel = decode_table[(input_chunk >> 8 & 0xFF) as usize];
if morsel == decode_tables::INVALID_VALUE {
bad_byte_index = input_index + 6;
break;
};
accum |= (morsel as u64) << 22;
morsel = decode_table[(input_chunk & 0xFF) as usize];
if morsel == decode_tables::INVALID_VALUE {
bad_byte_index = input_index + 7;
break;
};
accum |= (morsel as u64) << 16;
BigEndian::write_u64(&mut buffer_slice[(output_index)..(output_index + 8)],
accum);
output_index += 6;
input_index += chunk_len;
};
if morsel == decode_tables::INVALID_VALUE {
return Err(Base64Error::InvalidByte(bad_byte_index, input_bytes[bad_byte_index]));
}
}
let new_len = buffer.len() - (chunk_len - decoded_chunk_len);
buffer.truncate(new_len);
let mut leftover_bits: u64 = 0;
let mut morsels_in_leftover = 0;
let mut padding_bytes = 0;
let mut first_padding_index: usize = 0;
for (i, b) in input.as_bytes()[length_of_full_chunks..].iter().enumerate() {
if *b == 0x3D {
if i % 4 < 2 {
return Err(Base64Error::InvalidByte(length_of_full_chunks + i, *b));
};
if padding_bytes == 0 {
first_padding_index = i;
};
padding_bytes += 1;
continue;
};
if padding_bytes > 0 {
return Err(Base64Error::InvalidByte(
length_of_full_chunks + first_padding_index, 0x3D));
};
let shift = 64 - (morsels_in_leftover + 1) * 6;
let morsel = decode_table[*b as usize];
if morsel == decode_tables::INVALID_VALUE {
return Err(Base64Error::InvalidByte(length_of_full_chunks + i, *b));
};
leftover_bits |= (morsel as u64) << shift;
morsels_in_leftover += 1;
};
let leftover_bits_ready_to_append = match morsels_in_leftover {
0 => 0,
1 => return Err(Base64Error::InvalidLength),
2 => 8,
3 => 16,
4 => 24,
5 => return Err(Base64Error::InvalidLength),
6 => 32,
7 => 40,
8 => 48,
_ => panic!("Impossible: must only have 0 to 4 input bytes in last quad")
};
let mut leftover_bits_appended_to_buf = 0;
while leftover_bits_appended_to_buf < leftover_bits_ready_to_append {
let selected_bits = (leftover_bits >> (56 - leftover_bits_appended_to_buf)) as u8;
buffer.push(selected_bits);
leftover_bits_appended_to_buf += 8;
};
Ok(())
}
#[cfg(test)]
mod tests {
use super::encoded_size;
#[test]
fn encoded_size_correct() {
assert_eq!(0, encoded_size(0).unwrap());
assert_eq!(4, encoded_size(1).unwrap());
assert_eq!(4, encoded_size(2).unwrap());
assert_eq!(4, encoded_size(3).unwrap());
assert_eq!(8, encoded_size(4).unwrap());
assert_eq!(8, encoded_size(5).unwrap());
assert_eq!(8, encoded_size(6).unwrap());
assert_eq!(12, encoded_size(7).unwrap());
assert_eq!(12, encoded_size(8).unwrap());
assert_eq!(12, encoded_size(9).unwrap());
}
}