Skip to main content

base64/
decode.rs

1use crate::engine::{general_purpose::STANDARD, DecodeEstimate, Engine};
2#[cfg(any(feature = "alloc", test))]
3use alloc::vec::Vec;
4use core::fmt;
5#[cfg(any(feature = "std", test))]
6use std::error;
7
8/// Errors that can occur while decoding.
9#[derive(Clone, PartialEq, Eq)]
10pub enum DecodeError {
11    /// An invalid byte was found in the input. The offset and offending byte are provided.
12    ///
13    /// Padding characters (`=`) interspersed in the encoded form are invalid, as they may only
14    /// be present as the last 0-2 bytes of input.
15    ///
16    /// This error may also indicate that extraneous trailing input bytes are present, causing
17    /// otherwise valid padding to no longer be the last bytes of input.
18    InvalidByte(usize, u8),
19    /// The length of the input, as measured in valid base64 symbols, is invalid.
20    /// There must be 2-4 symbols in the last input quad.
21    InvalidLength(usize),
22    /// The last non-padding input symbol's encoded 6 bits have nonzero bits that will be discarded.
23    /// This is indicative of corrupted or truncated Base64.
24    /// Unlike [`DecodeError::InvalidByte`], which reports symbols that aren't in the alphabet,
25    /// this error is for symbols that are in the alphabet but represent nonsensical encodings.
26    ///
27    /// See [`crate::engine::GeneralPurposeConfig::with_decode_allow_trailing_bits`] to control
28    /// whether to detect this encoding error and produce this variant.
29    InvalidLastSymbol {
30        /// Offset in the input
31        offset: usize,
32        /// The offending symbol
33        symbol: u8,
34        /// The bits the symbol corresponds to.
35        ///
36        /// Since this error is being reported, this value has high bits erroneously set.
37        /// For a 2-symbol suffix, only the first 2 bits may be set (6 + 2 = 8 bits,
38        /// 1 byte), and for a 3 symbol, only the first 4 (6 + 6 + 4 = 16, 2 bytes).
39        symbol_value: u8,
40    },
41    /// The nature of the padding was not as configured: absent or incorrect when it must be
42    /// canonical, or present when it must be absent, etc.
43    InvalidPadding,
44}
45
46impl fmt::Display for DecodeError {
47    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
48        match *self {
49            Self::InvalidByte(index, byte) => {
50                write!(f, "Invalid symbol {}, offset {}.", byte, index)
51            }
52            Self::InvalidLength(len) => write!(f, "Invalid input length: {}", len),
53            Self::InvalidLastSymbol {
54                offset,
55                symbol,
56                symbol_value,
57            } => {
58                write!(
59                    f,
60                    "Invalid last symbol {:#4x} ('{}') at offset {}, decoded as {:#010b}.",
61                    symbol,
62                    // To have been decoded at all, it must have been ascii, but rather than have a
63                    // panicking code path, replacement char seems reasonable.
64                    // Can't use `char::from_u32` as that's 1.52+, so we make a 1-byte str.
65                    core::str::from_utf8(&[symbol])
66                        .ok()
67                        .and_then(|s| s.chars().next())
68                        // associated const is also 1.52+
69                        .unwrap_or(core::char::REPLACEMENT_CHARACTER),
70                    offset,
71                    symbol_value
72                )
73            }
74            Self::InvalidPadding => write!(f, "Invalid padding"),
75        }
76    }
77}
78
79impl fmt::Debug for DecodeError {
80    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81        // 1.48.0 can't handle {self}
82        write!(f, "{}", self)
83    }
84}
85
86#[cfg(any(feature = "std", test))]
87impl error::Error for DecodeError {}
88
89/// Errors that can occur while decoding into a slice.
90#[derive(Clone, Debug, PartialEq, Eq)]
91pub enum DecodeSliceError {
92    /// A [`DecodeError`] occurred
93    DecodeError(DecodeError),
94    /// The provided slice is too small.
95    OutputSliceTooSmall,
96}
97
98impl fmt::Display for DecodeSliceError {
99    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100        match self {
101            Self::DecodeError(e) => write!(f, "DecodeError: {}", e),
102            Self::OutputSliceTooSmall => write!(f, "Output slice too small"),
103        }
104    }
105}
106
107#[cfg(any(feature = "std", test))]
108impl error::Error for DecodeSliceError {
109    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
110        match self {
111            DecodeSliceError::DecodeError(e) => Some(e),
112            DecodeSliceError::OutputSliceTooSmall => None,
113        }
114    }
115}
116
117impl From<DecodeError> for DecodeSliceError {
118    fn from(e: DecodeError) -> Self {
119        DecodeSliceError::DecodeError(e)
120    }
121}
122
123/// Decode base64 using the [`STANDARD` engine](STANDARD).
124///
125/// See [`Engine::decode`].
126#[deprecated(since = "0.21.0", note = "Use Engine::decode")]
127#[cfg(any(feature = "alloc", test))]
128pub fn decode<T: AsRef<[u8]>>(input: T) -> Result<Vec<u8>, DecodeError> {
129    STANDARD.decode(input)
130}
131
132/// Decode from string reference as octets using the specified [Engine].
133///
134/// See [`Engine::decode`].
135///Returns a `Result` containing a `Vec<u8>`.
136#[deprecated(since = "0.21.0", note = "Use Engine::decode")]
137#[cfg(any(feature = "alloc", test))]
138pub fn decode_engine<E: Engine, T: AsRef<[u8]>>(
139    input: T,
140    engine: &E,
141) -> Result<Vec<u8>, DecodeError> {
142    engine.decode(input)
143}
144
145/// Decode from string reference as octets.
146///
147/// See [`Engine::decode_vec`].
148#[cfg(any(feature = "alloc", test))]
149#[deprecated(since = "0.21.0", note = "Use Engine::decode_vec")]
150pub fn decode_engine_vec<E: Engine, T: AsRef<[u8]>>(
151    input: T,
152    buffer: &mut Vec<u8>,
153    engine: &E,
154) -> Result<(), DecodeError> {
155    engine.decode_vec(input, buffer)
156}
157
158/// Decode the input into the provided output slice.
159///
160/// See [`Engine::decode_slice`].
161#[deprecated(since = "0.21.0", note = "Use Engine::decode_slice")]
162pub fn decode_engine_slice<E: Engine, T: AsRef<[u8]>>(
163    input: T,
164    output: &mut [u8],
165    engine: &E,
166) -> Result<usize, DecodeSliceError> {
167    engine.decode_slice(input, output)
168}
169
170/// Returns a conservative estimate of the decoded size of `encoded_len` base64 symbols (rounded up
171/// to the next group of 3 decoded bytes).
172///
173/// The resulting length will be a safe choice for the size of a decode buffer, but may have up to
174/// 2 trailing bytes that won't end up being needed.
175///
176/// # Examples
177///
178/// ```
179/// use base64::decoded_len_estimate;
180///
181/// assert_eq!(3, decoded_len_estimate(1));
182/// assert_eq!(3, decoded_len_estimate(2));
183/// assert_eq!(3, decoded_len_estimate(3));
184/// assert_eq!(3, decoded_len_estimate(4));
185/// // start of the next quad of encoded symbols
186/// assert_eq!(6, decoded_len_estimate(5));
187/// ```
188#[must_use]
189pub fn decoded_len_estimate(encoded_len: usize) -> usize {
190    STANDARD
191        .internal_decoded_len_estimate(encoded_len)
192        .decoded_len_estimate()
193}
194
195#[cfg(test)]
196mod tests {
197    use super::*;
198    use crate::{
199        alphabet,
200        engine::{general_purpose, GeneralPurpose},
201        tests::{assert_encode_sanity, random_engine},
202    };
203    use rand::distr::{Distribution, Uniform};
204    use rand::{rngs, RngExt};
205
206    #[test]
207    fn decode_into_nonempty_vec_doesnt_clobber_existing_prefix() {
208        let mut orig_data = Vec::new();
209        let mut encoded_data = String::new();
210        let mut decoded_with_prefix = Vec::new();
211        let mut decoded_without_prefix = Vec::new();
212        let mut prefix = Vec::new();
213
214        let prefix_len_range = Uniform::new(0, 1000).unwrap();
215        let input_len_range = Uniform::new(0, 1000).unwrap();
216
217        let mut rng = rand::make_rng::<rngs::SmallRng>();
218
219        for _ in 0..10_000 {
220            orig_data.clear();
221            encoded_data.clear();
222            decoded_with_prefix.clear();
223            decoded_without_prefix.clear();
224            prefix.clear();
225
226            let input_len = input_len_range.sample(&mut rng);
227
228            for _ in 0..input_len {
229                orig_data.push(rng.random());
230            }
231
232            let engine = random_engine(&mut rng);
233            engine.encode_string(&orig_data, &mut encoded_data);
234            assert_encode_sanity(&encoded_data, &engine, input_len);
235
236            let prefix_len = prefix_len_range.sample(&mut rng);
237
238            // fill the buf with a prefix
239            for _ in 0..prefix_len {
240                prefix.push(rng.random());
241            }
242
243            decoded_with_prefix.resize(prefix_len, 0);
244            decoded_with_prefix.copy_from_slice(&prefix);
245
246            // decode into the non-empty buf
247            engine
248                .decode_vec(&encoded_data, &mut decoded_with_prefix)
249                .unwrap();
250            // also decode into the empty buf
251            engine
252                .decode_vec(&encoded_data, &mut decoded_without_prefix)
253                .unwrap();
254
255            assert_eq!(
256                prefix_len + decoded_without_prefix.len(),
257                decoded_with_prefix.len()
258            );
259            assert_eq!(orig_data, decoded_without_prefix);
260
261            // append plain decode onto prefix
262            prefix.append(&mut decoded_without_prefix);
263
264            assert_eq!(prefix, decoded_with_prefix);
265        }
266    }
267
268    #[test]
269    fn decode_slice_doesnt_clobber_existing_prefix_or_suffix() {
270        do_decode_slice_doesnt_clobber_existing_prefix_or_suffix(|e, input, output| {
271            e.decode_slice(input, output).unwrap()
272        })
273    }
274
275    #[test]
276    fn decode_slice_unchecked_doesnt_clobber_existing_prefix_or_suffix() {
277        do_decode_slice_doesnt_clobber_existing_prefix_or_suffix(|e, input, output| {
278            e.decode_slice_unchecked(input, output).unwrap()
279        })
280    }
281
282    #[test]
283    fn decode_engine_estimation_works_for_various_lengths() {
284        let engine = GeneralPurpose::new(&alphabet::STANDARD, general_purpose::NO_PAD);
285        for num_prefix_quads in 0..100 {
286            for suffix in &["AA", "AAA", "AAAA"] {
287                let mut prefix = "AAAA".repeat(num_prefix_quads);
288                prefix.push_str(suffix);
289                // make sure no overflow (and thus a panic) occurs
290                let res = engine.decode(prefix);
291                assert!(res.is_ok());
292            }
293        }
294    }
295
296    #[test]
297    fn decode_slice_output_length_errors() {
298        for num_quads in 1..100 {
299            let input = "AAAA".repeat(num_quads);
300            let mut vec = vec![0; (num_quads - 1) * 3];
301            assert_eq!(
302                DecodeSliceError::OutputSliceTooSmall,
303                STANDARD.decode_slice(&input, &mut vec).unwrap_err()
304            );
305            vec.push(0);
306            assert_eq!(
307                DecodeSliceError::OutputSliceTooSmall,
308                STANDARD.decode_slice(&input, &mut vec).unwrap_err()
309            );
310            vec.push(0);
311            assert_eq!(
312                DecodeSliceError::OutputSliceTooSmall,
313                STANDARD.decode_slice(&input, &mut vec).unwrap_err()
314            );
315            vec.push(0);
316            // now it works
317            assert_eq!(
318                num_quads * 3,
319                STANDARD.decode_slice(&input, &mut vec).unwrap()
320            );
321        }
322    }
323
324    #[test]
325    fn invalid_last_symbol_debug() {
326        let err = DecodeError::InvalidLastSymbol {
327            offset: 100,
328            symbol: b'W',
329            symbol_value: 0x16,
330        };
331
332        assert_eq!(
333            "Invalid last symbol 0x57 ('W') at offset 100, decoded as 0b00010110.",
334            format!("{:?}", err)
335        );
336    }
337
338    fn do_decode_slice_doesnt_clobber_existing_prefix_or_suffix<
339        F: Fn(&GeneralPurpose, &[u8], &mut [u8]) -> usize,
340    >(
341        call_decode: F,
342    ) {
343        let mut orig_data = Vec::new();
344        let mut encoded_data = String::new();
345        let mut decode_buf = Vec::new();
346        let mut decode_buf_copy: Vec<u8> = Vec::new();
347
348        let input_len_range = Uniform::new(0, 1000).unwrap();
349
350        let mut rng = rand::make_rng::<rngs::SmallRng>();
351
352        for _ in 0..10_000 {
353            orig_data.clear();
354            encoded_data.clear();
355            decode_buf.clear();
356            decode_buf_copy.clear();
357
358            let input_len = input_len_range.sample(&mut rng);
359
360            for _ in 0..input_len {
361                orig_data.push(rng.random());
362            }
363
364            let engine = random_engine(&mut rng);
365            engine.encode_string(&orig_data, &mut encoded_data);
366            assert_encode_sanity(&encoded_data, &engine, input_len);
367
368            // fill the buffer with random garbage, long enough to have some room before and after
369            for _ in 0..5000 {
370                decode_buf.push(rng.random());
371            }
372
373            // keep a copy for later comparison
374            decode_buf_copy.extend(decode_buf.iter());
375
376            let offset = 1000;
377
378            // decode into the non-empty buf
379            let decode_bytes_written =
380                call_decode(&engine, encoded_data.as_bytes(), &mut decode_buf[offset..]);
381
382            assert_eq!(orig_data.len(), decode_bytes_written);
383            assert_eq!(
384                orig_data,
385                &decode_buf[offset..(offset + decode_bytes_written)]
386            );
387            assert_eq!(&decode_buf_copy[0..offset], &decode_buf[0..offset]);
388            assert_eq!(
389                &decode_buf_copy[offset + decode_bytes_written..],
390                &decode_buf[offset + decode_bytes_written..]
391            );
392        }
393    }
394}
395
396#[allow(deprecated)]
397#[cfg(test)]
398mod coverage_gaming {
399    use super::*;
400    use std::error::Error;
401
402    #[test]
403    fn decode_error() {
404        let _ = format!("{:?}", DecodeError::InvalidPadding.clone());
405        let _ = format!(
406            "{} {} {} {}",
407            DecodeError::InvalidByte(0, 0),
408            DecodeError::InvalidLength(0),
409            DecodeError::InvalidLastSymbol {
410                offset: 0,
411                symbol: 0,
412                symbol_value: 0,
413            },
414            DecodeError::InvalidPadding,
415        );
416    }
417
418    #[test]
419    fn decode_slice_error() {
420        let _ = format!("{:?}", DecodeSliceError::OutputSliceTooSmall.clone());
421        let _ = format!(
422            "{} {}",
423            DecodeSliceError::OutputSliceTooSmall,
424            DecodeSliceError::DecodeError(DecodeError::InvalidPadding)
425        );
426        let _ = DecodeSliceError::OutputSliceTooSmall.source();
427        let _ = DecodeSliceError::DecodeError(DecodeError::InvalidPadding).source();
428    }
429
430    #[test]
431    fn deprecated_fns() {
432        let _ = decode("");
433        let _ = decode_engine("", &crate::prelude::BASE64_STANDARD);
434        let _ = decode_engine_vec("", &mut Vec::new(), &crate::prelude::BASE64_STANDARD);
435        let _ = decode_engine_slice("", &mut [], &crate::prelude::BASE64_STANDARD);
436    }
437
438    #[test]
439    fn decoded_len_est() {
440        assert_eq!(3, decoded_len_estimate(4));
441    }
442}