/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 | | } |