350 lines
12 KiB
Rust
350 lines
12 KiB
Rust
use crate::engine::{general_purpose::STANDARD, DecodeEstimate, Engine};
|
|
#[cfg(any(feature = "alloc", feature = "std", test))]
|
|
use alloc::vec::Vec;
|
|
use core::fmt;
|
|
#[cfg(any(feature = "std", test))]
|
|
use std::error;
|
|
|
|
/// Errors that can occur while decoding.
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
pub enum DecodeError {
|
|
/// An invalid byte was found in the input. The offset and offending byte are provided.
|
|
/// Padding characters (`=`) interspersed in the encoded form will be treated as invalid bytes.
|
|
InvalidByte(usize, u8),
|
|
/// The length of the input is invalid.
|
|
/// A typical cause of this is stray trailing whitespace or other separator bytes.
|
|
/// In the case where excess trailing bytes have produced an invalid length *and* the last byte
|
|
/// is also an invalid base64 symbol (as would be the case for whitespace, etc), `InvalidByte`
|
|
/// will be emitted instead of `InvalidLength` to make the issue easier to debug.
|
|
InvalidLength,
|
|
/// The last non-padding input symbol's encoded 6 bits have nonzero bits that will be discarded.
|
|
/// This is indicative of corrupted or truncated Base64.
|
|
/// Unlike `InvalidByte`, which reports symbols that aren't in the alphabet, this error is for
|
|
/// symbols that are in the alphabet but represent nonsensical encodings.
|
|
InvalidLastSymbol(usize, u8),
|
|
/// The nature of the padding was not as configured: absent or incorrect when it must be
|
|
/// canonical, or present when it must be absent, etc.
|
|
InvalidPadding,
|
|
}
|
|
|
|
impl fmt::Display for DecodeError {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
match *self {
|
|
Self::InvalidByte(index, byte) => write!(f, "Invalid byte {}, offset {}.", byte, index),
|
|
Self::InvalidLength => write!(f, "Encoded text cannot have a 6-bit remainder."),
|
|
Self::InvalidLastSymbol(index, byte) => {
|
|
write!(f, "Invalid last symbol {}, offset {}.", byte, index)
|
|
}
|
|
Self::InvalidPadding => write!(f, "Invalid padding"),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(any(feature = "std", test))]
|
|
impl error::Error for DecodeError {
|
|
fn cause(&self) -> Option<&dyn error::Error> {
|
|
None
|
|
}
|
|
}
|
|
|
|
/// Errors that can occur while decoding into a slice.
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
pub enum DecodeSliceError {
|
|
/// A [DecodeError] occurred
|
|
DecodeError(DecodeError),
|
|
/// The provided slice _may_ be too small.
|
|
///
|
|
/// The check is conservative (assumes the last triplet of output bytes will all be needed).
|
|
OutputSliceTooSmall,
|
|
}
|
|
|
|
impl fmt::Display for DecodeSliceError {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
match self {
|
|
Self::DecodeError(e) => write!(f, "DecodeError: {}", e),
|
|
Self::OutputSliceTooSmall => write!(f, "Output slice too small"),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(any(feature = "std", test))]
|
|
impl error::Error for DecodeSliceError {
|
|
fn cause(&self) -> Option<&dyn error::Error> {
|
|
match self {
|
|
DecodeSliceError::DecodeError(e) => Some(e),
|
|
DecodeSliceError::OutputSliceTooSmall => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<DecodeError> for DecodeSliceError {
|
|
fn from(e: DecodeError) -> Self {
|
|
DecodeSliceError::DecodeError(e)
|
|
}
|
|
}
|
|
|
|
/// Decode base64 using the [`STANDARD` engine](STANDARD).
|
|
///
|
|
/// See [Engine::decode].
|
|
#[deprecated(since = "0.21.0", note = "Use Engine::decode")]
|
|
#[cfg(any(feature = "alloc", feature = "std", test))]
|
|
pub fn decode<T: AsRef<[u8]>>(input: T) -> Result<Vec<u8>, DecodeError> {
|
|
STANDARD.decode(input)
|
|
}
|
|
|
|
/// Decode from string reference as octets using the specified [Engine].
|
|
///
|
|
/// See [Engine::decode].
|
|
///Returns a `Result` containing a `Vec<u8>`.
|
|
#[deprecated(since = "0.21.0", note = "Use Engine::decode")]
|
|
#[cfg(any(feature = "alloc", feature = "std", test))]
|
|
pub fn decode_engine<E: Engine, T: AsRef<[u8]>>(
|
|
input: T,
|
|
engine: &E,
|
|
) -> Result<Vec<u8>, DecodeError> {
|
|
engine.decode(input)
|
|
}
|
|
|
|
/// Decode from string reference as octets.
|
|
///
|
|
/// See [Engine::decode_vec].
|
|
#[cfg(any(feature = "alloc", feature = "std", test))]
|
|
#[deprecated(since = "0.21.0", note = "Use Engine::decode_vec")]
|
|
pub fn decode_engine_vec<E: Engine, T: AsRef<[u8]>>(
|
|
input: T,
|
|
buffer: &mut Vec<u8>,
|
|
engine: &E,
|
|
) -> Result<(), DecodeError> {
|
|
engine.decode_vec(input, buffer)
|
|
}
|
|
|
|
/// Decode the input into the provided output slice.
|
|
///
|
|
/// See [Engine::decode_slice].
|
|
#[deprecated(since = "0.21.0", note = "Use Engine::decode_slice")]
|
|
pub fn decode_engine_slice<E: Engine, T: AsRef<[u8]>>(
|
|
input: T,
|
|
output: &mut [u8],
|
|
engine: &E,
|
|
) -> Result<usize, DecodeSliceError> {
|
|
engine.decode_slice(input, output)
|
|
}
|
|
|
|
/// Returns a conservative estimate of the decoded size of `encoded_len` base64 symbols (rounded up
|
|
/// to the next group of 3 decoded bytes).
|
|
///
|
|
/// The resulting length will be a safe choice for the size of a decode buffer, but may have up to
|
|
/// 2 trailing bytes that won't end up being needed.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```
|
|
/// use base64::decoded_len_estimate;
|
|
///
|
|
/// assert_eq!(3, decoded_len_estimate(1));
|
|
/// assert_eq!(3, decoded_len_estimate(2));
|
|
/// assert_eq!(3, decoded_len_estimate(3));
|
|
/// assert_eq!(3, decoded_len_estimate(4));
|
|
/// // start of the next quad of encoded symbols
|
|
/// assert_eq!(6, decoded_len_estimate(5));
|
|
/// ```
|
|
///
|
|
/// # Panics
|
|
///
|
|
/// Panics if decoded length estimation overflows.
|
|
/// This would happen for sizes within a few bytes of the maximum value of `usize`.
|
|
pub fn decoded_len_estimate(encoded_len: usize) -> usize {
|
|
STANDARD
|
|
.internal_decoded_len_estimate(encoded_len)
|
|
.decoded_len_estimate()
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::{
|
|
alphabet,
|
|
engine::{general_purpose, Config, GeneralPurpose},
|
|
tests::{assert_encode_sanity, random_engine},
|
|
};
|
|
use rand::{
|
|
distributions::{Distribution, Uniform},
|
|
Rng, SeedableRng,
|
|
};
|
|
|
|
#[test]
|
|
fn decode_into_nonempty_vec_doesnt_clobber_existing_prefix() {
|
|
let mut orig_data = Vec::new();
|
|
let mut encoded_data = String::new();
|
|
let mut decoded_with_prefix = Vec::new();
|
|
let mut decoded_without_prefix = Vec::new();
|
|
let mut prefix = Vec::new();
|
|
|
|
let prefix_len_range = Uniform::new(0, 1000);
|
|
let input_len_range = Uniform::new(0, 1000);
|
|
|
|
let mut rng = rand::rngs::SmallRng::from_entropy();
|
|
|
|
for _ in 0..10_000 {
|
|
orig_data.clear();
|
|
encoded_data.clear();
|
|
decoded_with_prefix.clear();
|
|
decoded_without_prefix.clear();
|
|
prefix.clear();
|
|
|
|
let input_len = input_len_range.sample(&mut rng);
|
|
|
|
for _ in 0..input_len {
|
|
orig_data.push(rng.gen());
|
|
}
|
|
|
|
let engine = random_engine(&mut rng);
|
|
engine.encode_string(&orig_data, &mut encoded_data);
|
|
assert_encode_sanity(&encoded_data, engine.config().encode_padding(), input_len);
|
|
|
|
let prefix_len = prefix_len_range.sample(&mut rng);
|
|
|
|
// fill the buf with a prefix
|
|
for _ in 0..prefix_len {
|
|
prefix.push(rng.gen());
|
|
}
|
|
|
|
decoded_with_prefix.resize(prefix_len, 0);
|
|
decoded_with_prefix.copy_from_slice(&prefix);
|
|
|
|
// decode into the non-empty buf
|
|
engine
|
|
.decode_vec(&encoded_data, &mut decoded_with_prefix)
|
|
.unwrap();
|
|
// also decode into the empty buf
|
|
engine
|
|
.decode_vec(&encoded_data, &mut decoded_without_prefix)
|
|
.unwrap();
|
|
|
|
assert_eq!(
|
|
prefix_len + decoded_without_prefix.len(),
|
|
decoded_with_prefix.len()
|
|
);
|
|
assert_eq!(orig_data, decoded_without_prefix);
|
|
|
|
// append plain decode onto prefix
|
|
prefix.append(&mut decoded_without_prefix);
|
|
|
|
assert_eq!(prefix, decoded_with_prefix);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn decode_slice_doesnt_clobber_existing_prefix_or_suffix() {
|
|
do_decode_slice_doesnt_clobber_existing_prefix_or_suffix(|e, input, output| {
|
|
e.decode_slice(input, output).unwrap()
|
|
})
|
|
}
|
|
|
|
#[test]
|
|
fn decode_slice_unchecked_doesnt_clobber_existing_prefix_or_suffix() {
|
|
do_decode_slice_doesnt_clobber_existing_prefix_or_suffix(|e, input, output| {
|
|
e.decode_slice_unchecked(input, output).unwrap()
|
|
})
|
|
}
|
|
|
|
#[test]
|
|
fn decode_engine_estimation_works_for_various_lengths() {
|
|
let engine = GeneralPurpose::new(&alphabet::STANDARD, general_purpose::NO_PAD);
|
|
for num_prefix_quads in 0..100 {
|
|
for suffix in &["AA", "AAA", "AAAA"] {
|
|
let mut prefix = "AAAA".repeat(num_prefix_quads);
|
|
prefix.push_str(suffix);
|
|
// make sure no overflow (and thus a panic) occurs
|
|
let res = engine.decode(prefix);
|
|
assert!(res.is_ok());
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn decode_slice_output_length_errors() {
|
|
for num_quads in 1..100 {
|
|
let input = "AAAA".repeat(num_quads);
|
|
let mut vec = vec![0; (num_quads - 1) * 3];
|
|
assert_eq!(
|
|
DecodeSliceError::OutputSliceTooSmall,
|
|
STANDARD.decode_slice(&input, &mut vec).unwrap_err()
|
|
);
|
|
vec.push(0);
|
|
assert_eq!(
|
|
DecodeSliceError::OutputSliceTooSmall,
|
|
STANDARD.decode_slice(&input, &mut vec).unwrap_err()
|
|
);
|
|
vec.push(0);
|
|
assert_eq!(
|
|
DecodeSliceError::OutputSliceTooSmall,
|
|
STANDARD.decode_slice(&input, &mut vec).unwrap_err()
|
|
);
|
|
vec.push(0);
|
|
// now it works
|
|
assert_eq!(
|
|
num_quads * 3,
|
|
STANDARD.decode_slice(&input, &mut vec).unwrap()
|
|
);
|
|
}
|
|
}
|
|
|
|
fn do_decode_slice_doesnt_clobber_existing_prefix_or_suffix<
|
|
F: Fn(&GeneralPurpose, &[u8], &mut [u8]) -> usize,
|
|
>(
|
|
call_decode: F,
|
|
) {
|
|
let mut orig_data = Vec::new();
|
|
let mut encoded_data = String::new();
|
|
let mut decode_buf = Vec::new();
|
|
let mut decode_buf_copy: Vec<u8> = Vec::new();
|
|
|
|
let input_len_range = Uniform::new(0, 1000);
|
|
|
|
let mut rng = rand::rngs::SmallRng::from_entropy();
|
|
|
|
for _ in 0..10_000 {
|
|
orig_data.clear();
|
|
encoded_data.clear();
|
|
decode_buf.clear();
|
|
decode_buf_copy.clear();
|
|
|
|
let input_len = input_len_range.sample(&mut rng);
|
|
|
|
for _ in 0..input_len {
|
|
orig_data.push(rng.gen());
|
|
}
|
|
|
|
let engine = random_engine(&mut rng);
|
|
engine.encode_string(&orig_data, &mut encoded_data);
|
|
assert_encode_sanity(&encoded_data, engine.config().encode_padding(), input_len);
|
|
|
|
// fill the buffer with random garbage, long enough to have some room before and after
|
|
for _ in 0..5000 {
|
|
decode_buf.push(rng.gen());
|
|
}
|
|
|
|
// keep a copy for later comparison
|
|
decode_buf_copy.extend(decode_buf.iter());
|
|
|
|
let offset = 1000;
|
|
|
|
// decode into the non-empty buf
|
|
let decode_bytes_written =
|
|
call_decode(&engine, encoded_data.as_bytes(), &mut decode_buf[offset..]);
|
|
|
|
assert_eq!(orig_data.len(), decode_bytes_written);
|
|
assert_eq!(
|
|
orig_data,
|
|
&decode_buf[offset..(offset + decode_bytes_written)]
|
|
);
|
|
assert_eq!(&decode_buf_copy[0..offset], &decode_buf[0..offset]);
|
|
assert_eq!(
|
|
&decode_buf_copy[offset + decode_bytes_written..],
|
|
&decode_buf[offset + decode_bytes_written..]
|
|
);
|
|
}
|
|
}
|
|
}
|