• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #[cfg(any(feature = "alloc", feature = "std", test))]
2 use alloc::string::String;
3 use core::fmt;
4 #[cfg(any(feature = "std", test))]
5 use std::error;
6 
7 #[cfg(any(feature = "alloc", feature = "std", test))]
8 use crate::engine::general_purpose::STANDARD;
9 use crate::engine::{Config, Engine};
10 use 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))]
encode<T: AsRef<[u8]>>(input: T) -> String18 pub 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))]
encode_engine<E: Engine, T: AsRef<[u8]>>(input: T, engine: &E) -> String28 pub 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))]
encode_engine_string<E: Engine, T: AsRef<[u8]>>( input: T, output_buf: &mut String, engine: &E, )38 pub 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")]
encode_engine_slice<E: Engine, T: AsRef<[u8]>>( input: T, output_buf: &mut [u8], engine: &E, ) -> Result<usize, EncodeSliceError>51 pub 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.
encode_with_padding<E: Engine + ?Sized>( input: &[u8], output: &mut [u8], engine: &E, expected_encoded_size: usize, )69 pub(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 = engine.internal_encode(input, output);
78 
79     let padding_bytes = if engine.config().encode_padding() {
80         add_padding(input.len(), &mut output[b64_bytes_written..])
81     } else {
82         0
83     };
84 
85     let encoded_bytes = b64_bytes_written
86         .checked_add(padding_bytes)
87         .expect("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`.
encoded_len(bytes_len: usize, padding: bool) -> Option<usize>97 pub fn encoded_len(bytes_len: usize, padding: bool) -> Option<usize> {
98     let rem = bytes_len % 3;
99 
100     let complete_input_chunks = bytes_len / 3;
101     let complete_chunk_output = complete_input_chunks.checked_mul(4);
102 
103     if rem > 0 {
104         if padding {
105             complete_chunk_output.and_then(|c| c.checked_add(4))
106         } else {
107             let encoded_rem = match rem {
108                 1 => 2,
109                 2 => 3,
110                 _ => unreachable!("Impossible remainder"),
111             };
112             complete_chunk_output.and_then(|c| c.checked_add(encoded_rem))
113         }
114     } else {
115         complete_chunk_output
116     }
117 }
118 
119 /// Write padding characters.
120 /// `input_len` is the size of the original, not encoded, input.
121 /// `output` is the slice where padding should be written, of length at least 2.
122 ///
123 /// Returns the number of padding bytes written.
add_padding(input_len: usize, output: &mut [u8]) -> usize124 pub(crate) fn add_padding(input_len: usize, output: &mut [u8]) -> usize {
125     // TODO base on encoded len to use cheaper mod by 4 (aka & 7)
126     let rem = input_len % 3;
127     let mut bytes_written = 0;
128     for _ in 0..((3 - rem) % 3) {
129         output[bytes_written] = PAD_BYTE;
130         bytes_written += 1;
131     }
132 
133     bytes_written
134 }
135 
136 /// Errors that can occur while encoding into a slice.
137 #[derive(Clone, Debug, PartialEq, Eq)]
138 pub enum EncodeSliceError {
139     /// The provided slice is too small.
140     OutputSliceTooSmall,
141 }
142 
143 impl fmt::Display for EncodeSliceError {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result144     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))]
152 impl error::Error for EncodeSliceError {
cause(&self) -> Option<&dyn error::Error>153     fn cause(&self) -> Option<&dyn error::Error> {
154         None
155     }
156 }
157 
158 #[cfg(test)]
159 mod 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]
encoded_size_correct_standard()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]
encoded_size_correct_no_pad()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]
encoded_size_overflow()226     fn encoded_size_overflow() {
227         assert_eq!(None, encoded_len(usize::MAX, true));
228     }
229 
230     #[test]
encode_engine_string_into_nonempty_buffer_doesnt_clobber_prefix()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]
encode_engine_slice_into_nonempty_buffer_doesnt_clobber_suffix()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]
encode_to_slice_random_valid_utf8()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]
encode_with_padding_random_valid_utf8()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]
add_padding_random_valid_utf8()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 % 3
438         for input_len in 0..10 {
439             output.clear();
440 
441             // fill output with random
442             for _ in 0..10 {
443                 output.push(rng.gen());
444             }
445 
446             let orig_output_buf = output.clone();
447 
448             let bytes_written = add_padding(input_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 
assert_encoded_length<E: Engine>( input_len: usize, enc_len: usize, engine: &E, padded: bool, )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]
encode_imap()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