Skip to main content

base64/
encode.rs

1use crate::alphabet::Symbol;
2#[cfg(any(feature = "alloc", test))]
3use crate::engine::general_purpose::STANDARD;
4use crate::engine::{Config, Engine};
5#[cfg(any(feature = "alloc", test))]
6use alloc::string::String;
7use core::fmt;
8#[cfg(any(feature = "std", test))]
9use std::error;
10
11/// Encode arbitrary octets as base64 using the [`STANDARD` engine](STANDARD).
12///
13/// See [`Engine::encode`].
14#[allow(unused)]
15#[deprecated(since = "0.21.0", note = "Use Engine::encode")]
16#[cfg(any(feature = "alloc", test))]
17pub fn encode<T: AsRef<[u8]>>(input: T) -> String {
18    STANDARD.encode(input)
19}
20
21///Encode arbitrary octets as base64 using the provided `Engine` into a new `String`.
22///
23/// See [`Engine::encode`].
24#[allow(unused)]
25#[deprecated(since = "0.21.0", note = "Use Engine::encode")]
26#[cfg(any(feature = "alloc", test))]
27pub fn encode_engine<E: Engine, T: AsRef<[u8]>>(input: T, engine: &E) -> String {
28    engine.encode(input)
29}
30
31///Encode arbitrary octets as base64 into a supplied `String`.
32///
33/// See [`Engine::encode_string`].
34#[allow(unused)]
35#[deprecated(since = "0.21.0", note = "Use Engine::encode_string")]
36#[cfg(any(feature = "alloc", test))]
37pub fn encode_engine_string<E: Engine, T: AsRef<[u8]>>(
38    input: T,
39    output_buf: &mut String,
40    engine: &E,
41) {
42    engine.encode_string(input, output_buf);
43}
44
45/// Encode arbitrary octets as base64 into a supplied slice.
46///
47/// See [`Engine::encode_slice`].
48#[allow(unused)]
49#[deprecated(since = "0.21.0", note = "Use Engine::encode_slice")]
50pub fn encode_engine_slice<E: Engine, T: AsRef<[u8]>>(
51    input: T,
52    output_buf: &mut [u8],
53    engine: &E,
54) -> Result<usize, EncodeSliceError> {
55    engine.encode_slice(input, output_buf)
56}
57
58/// B64-encode and pad (if configured).
59///
60/// This helper exists to avoid recalculating `encoded_size`, which is relatively expensive on short
61/// inputs.
62///
63/// `encoded_size` is the encoded size calculated for `input`.
64///
65/// `output` must be of size `encoded_size`.
66///
67/// All bytes in `output` will be written to since it is exactly the size of the output.
68pub(crate) fn encode_with_padding<E: Engine + ?Sized>(
69    input: &[u8],
70    output: &mut [u8],
71    engine: &E,
72    expected_encoded_size: usize,
73) {
74    debug_assert_eq!(expected_encoded_size, output.len());
75
76    let b64_bytes_written = engine.internal_encode(input, output);
77
78    let padding_bytes = if engine.config().encode_padding() {
79        add_padding(
80            b64_bytes_written,
81            engine.padding(),
82            &mut output[b64_bytes_written..],
83        )
84    } else {
85        0
86    };
87
88    let encoded_bytes = b64_bytes_written
89        .checked_add(padding_bytes)
90        .expect("usize overflow when calculating b64 length");
91
92    debug_assert_eq!(expected_encoded_size, encoded_bytes);
93}
94
95/// Calculate the base64 encoded length for a given input length, optionally including any
96/// appropriate padding bytes.
97///
98/// Returns `None` if the encoded length can't be represented in `usize`. This will happen for
99/// input lengths in approximately the top quarter of the range of `usize`.
100#[must_use]
101pub const fn encoded_len(bytes_len: usize, padding: bool) -> Option<usize> {
102    let rem = bytes_len % 3;
103
104    let complete_input_chunks = bytes_len / 3;
105    // `?` is disallowed in const, and `let Some(_) = _ else` requires 1.65.0, whereas this
106    // messier syntax works on 1.48
107    let complete_chunk_output =
108        if let Some(complete_chunk_output) = complete_input_chunks.checked_mul(4) {
109            complete_chunk_output
110        } else {
111            return None;
112        };
113
114    if rem > 0 {
115        if padding {
116            complete_chunk_output.checked_add(4)
117        } else {
118            let encoded_rem = match rem {
119                1 => 2,
120                // only other possible remainder is 2
121                // can't use a separate _ => unreachable!() in const fns in ancient rust versions
122                _ => 3,
123            };
124            complete_chunk_output.checked_add(encoded_rem)
125        }
126    } else {
127        Some(complete_chunk_output)
128    }
129}
130
131/// Write padding characters.
132/// `unpadded_output_len` is the size of the unpadded but base64 encoded data.
133/// `output` is the slice where padding should be written, of length at least 2.
134///
135/// Returns the number of padding bytes written.
136pub(crate) fn add_padding(unpadded_output_len: usize, padding: Symbol, output: &mut [u8]) -> usize {
137    let pad_bytes = (4 - (unpadded_output_len % 4)) % 4;
138    // for just a couple bytes, this has better performance than using
139    // .fill(), or iterating over mutable refs, which call memset()
140    #[allow(clippy::needless_range_loop)]
141    for i in 0..pad_bytes {
142        output[i] = padding.as_u8();
143    }
144
145    pad_bytes
146}
147
148/// Errors that can occur while encoding into a slice.
149#[derive(Clone, Debug, PartialEq, Eq)]
150pub enum EncodeSliceError {
151    /// The provided slice is too small.
152    OutputSliceTooSmall,
153}
154
155impl fmt::Display for EncodeSliceError {
156    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
157        match self {
158            Self::OutputSliceTooSmall => write!(f, "Output slice too small"),
159        }
160    }
161}
162
163#[cfg(any(feature = "std", test))]
164impl error::Error for EncodeSliceError {}
165
166#[cfg(test)]
167mod tests {
168    use super::*;
169
170    use crate::alphabet::PADDING_SYMBOL;
171    use crate::{
172        alphabet,
173        engine::general_purpose::{GeneralPurpose, NO_PAD, STANDARD},
174        tests::{assert_encode_sanity, random_config, random_engine},
175    };
176    use rand::distr::{Distribution, Uniform};
177    use rand::{rngs, RngExt};
178    use std::str;
179
180    const URL_SAFE_NO_PAD_ENGINE: GeneralPurpose = GeneralPurpose::new(&alphabet::URL_SAFE, NO_PAD);
181
182    #[test]
183    fn encoded_size_correct_standard() {
184        assert_encoded_length(0, 0, &STANDARD, true);
185
186        assert_encoded_length(1, 4, &STANDARD, true);
187        assert_encoded_length(2, 4, &STANDARD, true);
188        assert_encoded_length(3, 4, &STANDARD, true);
189
190        assert_encoded_length(4, 8, &STANDARD, true);
191        assert_encoded_length(5, 8, &STANDARD, true);
192        assert_encoded_length(6, 8, &STANDARD, true);
193
194        assert_encoded_length(7, 12, &STANDARD, true);
195        assert_encoded_length(8, 12, &STANDARD, true);
196        assert_encoded_length(9, 12, &STANDARD, true);
197
198        assert_encoded_length(54, 72, &STANDARD, true);
199
200        assert_encoded_length(55, 76, &STANDARD, true);
201        assert_encoded_length(56, 76, &STANDARD, true);
202        assert_encoded_length(57, 76, &STANDARD, true);
203
204        assert_encoded_length(58, 80, &STANDARD, true);
205    }
206
207    #[test]
208    fn encoded_size_correct_no_pad() {
209        assert_encoded_length(0, 0, &URL_SAFE_NO_PAD_ENGINE, false);
210
211        assert_encoded_length(1, 2, &URL_SAFE_NO_PAD_ENGINE, false);
212        assert_encoded_length(2, 3, &URL_SAFE_NO_PAD_ENGINE, false);
213        assert_encoded_length(3, 4, &URL_SAFE_NO_PAD_ENGINE, false);
214
215        assert_encoded_length(4, 6, &URL_SAFE_NO_PAD_ENGINE, false);
216        assert_encoded_length(5, 7, &URL_SAFE_NO_PAD_ENGINE, false);
217        assert_encoded_length(6, 8, &URL_SAFE_NO_PAD_ENGINE, false);
218
219        assert_encoded_length(7, 10, &URL_SAFE_NO_PAD_ENGINE, false);
220        assert_encoded_length(8, 11, &URL_SAFE_NO_PAD_ENGINE, false);
221        assert_encoded_length(9, 12, &URL_SAFE_NO_PAD_ENGINE, false);
222
223        assert_encoded_length(54, 72, &URL_SAFE_NO_PAD_ENGINE, false);
224
225        assert_encoded_length(55, 74, &URL_SAFE_NO_PAD_ENGINE, false);
226        assert_encoded_length(56, 75, &URL_SAFE_NO_PAD_ENGINE, false);
227        assert_encoded_length(57, 76, &URL_SAFE_NO_PAD_ENGINE, false);
228
229        assert_encoded_length(58, 78, &URL_SAFE_NO_PAD_ENGINE, false);
230    }
231
232    #[test]
233    fn encoded_size_overflow() {
234        assert_eq!(None, encoded_len(usize::MAX, true));
235    }
236
237    #[test]
238    fn encode_engine_string_into_nonempty_buffer_doesnt_clobber_prefix() {
239        let mut orig_data = Vec::new();
240        let mut prefix = String::new();
241        let mut encoded_data_no_prefix = String::new();
242        let mut encoded_data_with_prefix = String::new();
243        let mut decoded = Vec::new();
244
245        let prefix_len_range = Uniform::new(0, 1000).unwrap();
246        let input_len_range = Uniform::new(0, 1000).unwrap();
247
248        let mut rng = rand::make_rng::<rngs::SmallRng>();
249
250        for _ in 0..10_000 {
251            orig_data.clear();
252            prefix.clear();
253            encoded_data_no_prefix.clear();
254            encoded_data_with_prefix.clear();
255            decoded.clear();
256
257            let input_len = input_len_range.sample(&mut rng);
258
259            for _ in 0..input_len {
260                orig_data.push(rng.random());
261            }
262
263            let prefix_len = prefix_len_range.sample(&mut rng);
264            for _ in 0..prefix_len {
265                // getting convenient random single-byte printable chars that aren't base64 is
266                // annoying
267                prefix.push('#');
268            }
269            encoded_data_with_prefix.push_str(&prefix);
270
271            let engine = random_engine(&mut rng);
272            engine.encode_string(&orig_data, &mut encoded_data_no_prefix);
273            engine.encode_string(&orig_data, &mut encoded_data_with_prefix);
274
275            assert_eq!(
276                encoded_data_no_prefix.len() + prefix_len,
277                encoded_data_with_prefix.len()
278            );
279            assert_encode_sanity(&encoded_data_no_prefix, &engine, input_len);
280            assert_encode_sanity(&encoded_data_with_prefix[prefix_len..], &engine, input_len);
281
282            // append plain encode onto prefix
283            prefix.push_str(&encoded_data_no_prefix);
284
285            assert_eq!(prefix, encoded_data_with_prefix);
286
287            engine
288                .decode_vec(&encoded_data_no_prefix, &mut decoded)
289                .unwrap();
290            assert_eq!(orig_data, decoded);
291        }
292    }
293
294    #[test]
295    fn encode_engine_slice_into_nonempty_buffer_doesnt_clobber_suffix() {
296        let mut orig_data = Vec::new();
297        let mut encoded_data = Vec::new();
298        let mut encoded_data_original_state = Vec::new();
299        let mut decoded = Vec::new();
300
301        let input_len_range = Uniform::new(0, 1000).unwrap();
302
303        let mut rng = rand::make_rng::<rngs::SmallRng>();
304
305        for _ in 0..10_000 {
306            orig_data.clear();
307            encoded_data.clear();
308            encoded_data_original_state.clear();
309            decoded.clear();
310
311            let input_len = input_len_range.sample(&mut rng);
312
313            for _ in 0..input_len {
314                orig_data.push(rng.random());
315            }
316
317            // plenty of existing garbage in the encoded buffer
318            for _ in 0..10 * input_len {
319                encoded_data.push(rng.random());
320            }
321
322            encoded_data_original_state.extend_from_slice(&encoded_data);
323
324            let engine = random_engine(&mut rng);
325
326            let encoded_size = encoded_len(input_len, engine.config().encode_padding()).unwrap();
327
328            assert_eq!(
329                encoded_size,
330                engine.encode_slice(&orig_data, &mut encoded_data).unwrap()
331            );
332
333            assert_encode_sanity(
334                str::from_utf8(&encoded_data[0..encoded_size]).unwrap(),
335                &engine,
336                input_len,
337            );
338
339            assert_eq!(
340                &encoded_data[encoded_size..],
341                &encoded_data_original_state[encoded_size..]
342            );
343
344            engine
345                .decode_vec(&encoded_data[0..encoded_size], &mut decoded)
346                .unwrap();
347            assert_eq!(orig_data, decoded);
348        }
349    }
350
351    #[test]
352    fn encode_to_slice_random_valid_utf8() {
353        let mut input = Vec::new();
354        let mut output = Vec::new();
355
356        let input_len_range = Uniform::new(0, 1000).unwrap();
357
358        let mut rng = rand::make_rng::<rngs::SmallRng>();
359
360        for _ in 0..10_000 {
361            input.clear();
362            output.clear();
363
364            let input_len = input_len_range.sample(&mut rng);
365
366            for _ in 0..input_len {
367                input.push(rng.random());
368            }
369
370            let config = random_config(&mut rng);
371            let engine = random_engine(&mut rng);
372
373            // fill up the output buffer with garbage
374            let encoded_size = encoded_len(input_len, config.encode_padding()).unwrap();
375            for _ in 0..encoded_size {
376                output.push(rng.random());
377            }
378
379            let orig_output_buf = output.clone();
380
381            let bytes_written = engine.internal_encode(&input, &mut output);
382
383            // make sure the part beyond bytes_written is the same garbage it was before
384            assert_eq!(orig_output_buf[bytes_written..], output[bytes_written..]);
385
386            // make sure the encoded bytes are UTF-8
387            let _ = str::from_utf8(&output[0..bytes_written]).unwrap();
388        }
389    }
390
391    #[test]
392    fn encode_with_padding_random_valid_utf8() {
393        let mut input = Vec::new();
394        let mut output = Vec::new();
395
396        let input_len_range = Uniform::new(0, 1000).unwrap();
397
398        let mut rng = rand::make_rng::<rngs::SmallRng>();
399
400        for _ in 0..10_000 {
401            input.clear();
402            output.clear();
403
404            let input_len = input_len_range.sample(&mut rng);
405
406            for _ in 0..input_len {
407                input.push(rng.random());
408            }
409
410            let engine = random_engine(&mut rng);
411
412            // fill up the output buffer with garbage
413            let encoded_size = encoded_len(input_len, engine.config().encode_padding()).unwrap();
414            for _ in 0..encoded_size + 1000 {
415                output.push(rng.random());
416            }
417
418            let orig_output_buf = output.clone();
419
420            encode_with_padding(&input, &mut output[0..encoded_size], &engine, encoded_size);
421
422            // make sure the part beyond b64 is the same garbage it was before
423            assert_eq!(orig_output_buf[encoded_size..], output[encoded_size..]);
424
425            // make sure the encoded bytes are UTF-8
426            let _ = str::from_utf8(&output[0..encoded_size]).unwrap();
427        }
428    }
429
430    #[test]
431    fn add_padding_random_valid_utf8() {
432        let mut output = Vec::new();
433
434        let mut rng = rand::make_rng::<rngs::SmallRng>();
435
436        // cover our bases for length % 4
437        for unpadded_output_len in 0..20 {
438            output.clear();
439
440            // fill output with random
441            for _ in 0..100 {
442                output.push(rng.random());
443            }
444
445            let orig_output_buf = output.clone();
446
447            let bytes_written = add_padding(unpadded_output_len, PADDING_SYMBOL, &mut output);
448
449            // make sure the part beyond bytes_written is the same garbage it was before
450            assert_eq!(orig_output_buf[bytes_written..], output[bytes_written..]);
451
452            // make sure the encoded bytes are UTF-8
453            let _ = str::from_utf8(&output[0..bytes_written]).unwrap();
454        }
455    }
456
457    fn assert_encoded_length<E: Engine>(
458        input_len: usize,
459        enc_len: usize,
460        engine: &E,
461        padded: bool,
462    ) {
463        assert_eq!(enc_len, encoded_len(input_len, padded).unwrap());
464
465        let mut bytes: Vec<u8> = Vec::new();
466        let mut rng = rand::make_rng::<rngs::SmallRng>();
467
468        for _ in 0..input_len {
469            bytes.push(rng.random());
470        }
471
472        let encoded = engine.encode(&bytes);
473        assert_encode_sanity(&encoded, engine, input_len);
474
475        assert_eq!(enc_len, encoded.len());
476    }
477
478    #[test]
479    fn encode_imap() {
480        assert_eq!(
481            &GeneralPurpose::new(&alphabet::IMAP_MUTF7, NO_PAD).encode(b"\xFB\xFF"),
482            &GeneralPurpose::new(&alphabet::STANDARD, NO_PAD)
483                .encode(b"\xFB\xFF")
484                .replace('/', ",")
485        );
486    }
487}