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