Coverage Report

Created: 2026-09-04 06:48

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/base64-0.23.1/src/encode.rs
Line
Count
Source
1
use crate::alphabet::Symbol;
2
#[cfg(any(feature = "alloc", test))]
3
use crate::engine::general_purpose::STANDARD;
4
use crate::engine::{Config, Engine};
5
#[cfg(any(feature = "alloc", test))]
6
use alloc::string::String;
7
use core::fmt;
8
#[cfg(any(feature = "std", test))]
9
use 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))]
17
0
pub fn encode<T: AsRef<[u8]>>(input: T) -> String {
18
0
    STANDARD.encode(input)
19
0
}
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))]
27
0
pub fn encode_engine<E: Engine, T: AsRef<[u8]>>(input: T, engine: &E) -> String {
28
0
    engine.encode(input)
29
0
}
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))]
37
0
pub fn encode_engine_string<E: Engine, T: AsRef<[u8]>>(
38
0
    input: T,
39
0
    output_buf: &mut String,
40
0
    engine: &E,
41
0
) {
42
0
    engine.encode_string(input, output_buf);
43
0
}
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")]
50
0
pub fn encode_engine_slice<E: Engine, T: AsRef<[u8]>>(
51
0
    input: T,
52
0
    output_buf: &mut [u8],
53
0
    engine: &E,
54
0
) -> Result<usize, EncodeSliceError> {
55
0
    engine.encode_slice(input, output_buf)
56
0
}
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.
68
0
pub(crate) fn encode_with_padding<E: Engine + ?Sized>(
69
0
    input: &[u8],
70
0
    output: &mut [u8],
71
0
    engine: &E,
72
0
    expected_encoded_size: usize,
73
0
) {
74
0
    debug_assert_eq!(expected_encoded_size, output.len());
75
76
0
    let b64_bytes_written = engine.internal_encode(input, output);
77
78
0
    let padding_bytes = if engine.config().encode_padding() {
79
0
        add_padding(
80
0
            b64_bytes_written,
81
0
            engine.padding(),
82
0
            &mut output[b64_bytes_written..],
83
        )
84
    } else {
85
0
        0
86
    };
87
88
0
    let encoded_bytes = b64_bytes_written
89
0
        .checked_add(padding_bytes)
90
0
        .expect("usize overflow when calculating b64 length");
91
92
0
    debug_assert_eq!(expected_encoded_size, encoded_bytes);
93
0
}
Unexecuted instantiation: base64::encode::encode_with_padding::<base64::engine::general_purpose::GeneralPurpose>
Unexecuted instantiation: base64::encode::encode_with_padding::<_>
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]
101
0
pub const fn encoded_len(bytes_len: usize, padding: bool) -> Option<usize> {
102
0
    let rem = bytes_len % 3;
103
104
0
    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
0
    let complete_chunk_output =
108
0
        if let Some(complete_chunk_output) = complete_input_chunks.checked_mul(4) {
109
0
            complete_chunk_output
110
        } else {
111
0
            return None;
112
        };
113
114
0
    if rem > 0 {
115
0
        if padding {
116
0
            complete_chunk_output.checked_add(4)
117
        } else {
118
0
            let encoded_rem = match rem {
119
0
                1 => 2,
120
                // only other possible remainder is 2
121
                // can't use a separate _ => unreachable!() in const fns in ancient rust versions
122
0
                _ => 3,
123
            };
124
0
            complete_chunk_output.checked_add(encoded_rem)
125
        }
126
    } else {
127
0
        Some(complete_chunk_output)
128
    }
129
0
}
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.
136
0
pub(crate) fn add_padding(unpadded_output_len: usize, padding: Symbol, output: &mut [u8]) -> usize {
137
0
    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
0
    for i in 0..pad_bytes {
142
0
        output[i] = padding.as_u8();
143
0
    }
144
145
0
    pad_bytes
146
0
}
147
148
/// Errors that can occur while encoding into a slice.
149
#[derive(Clone, Debug, PartialEq, Eq)]
150
pub enum EncodeSliceError {
151
    /// The provided slice is too small.
152
    OutputSliceTooSmall,
153
}
154
155
impl fmt::Display for EncodeSliceError {
156
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
157
0
        match self {
158
0
            Self::OutputSliceTooSmall => write!(f, "Output slice too small"),
159
        }
160
0
    }
161
}
162
163
#[cfg(any(feature = "std", test))]
164
impl error::Error for EncodeSliceError {}
165
166
#[cfg(test)]
167
mod 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
}