b64_url/
lib.rs

1use lazy_static::lazy_static;
2
3pub const B64_URL_ENCODE: [u8; 64] = [
4    0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50,
5    0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66,
6    0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76,
7    0x77, 0x78, 0x79, 0x7a, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x2d, 0x5f,
8];
9
10pub const B64_URL_DECODE: [u8; 255] = [
11    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
12    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
13    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00,
14    0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
15    0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
16    0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x00, 0x00, 0x00, 0x00, 0x3f,
17    0x00, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
18    0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00,
19    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
20    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
21    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
22    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
23    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
24    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
25    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
26    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
27];
28
29pub const B64_URL_PAD: u8 = 0x3d;
30
31lazy_static! {
32    static ref DEFAULT_CONFIG: B64Config = B64Config::default();
33}
34
35#[derive(Default)]
36pub struct B64ConfigPadding {
37    pub omit: bool,
38}
39
40#[derive(Default)]
41pub struct B64Config {
42    pub padding: B64ConfigPadding,
43}
44
45#[inline(always)]
46pub fn _b64_url_encode_calculate_destination_capacity(length: usize) -> usize {
47    length / 3 * 4 + 4
48}
49
50#[inline(always)]
51pub unsafe fn _b64_url_encode_with_config(
52    mut source: *const u8,
53    mut source_length: usize,
54    mut destination: *mut u8,
55    config: &B64Config,
56) -> usize {
57    let mut bytes = 0;
58    while source_length >= 3 {
59        let value = ((*source as u32) << 16)
60            | ((*source.offset(1) as u32) << 8)
61            | (*source.offset(2) as u32);
62        *destination = B64_URL_ENCODE[((value >> 18) & 0b11_1111) as usize];
63        *destination.offset(1) = B64_URL_ENCODE[((value >> 12) & 0b11_1111) as usize];
64        *destination.offset(2) = B64_URL_ENCODE[((value >> 6) & 0b11_1111) as usize];
65        *destination.offset(3) = B64_URL_ENCODE[(value & 0b11_1111) as usize];
66        source = source.offset(3);
67        destination = destination.offset(4);
68        source_length -= 3;
69        bytes += 4;
70    }
71    match source_length {
72        2 => {
73            let value = ((*source as u32) << 16) | ((*source.offset(1) as u32) << 8);
74            *destination = B64_URL_ENCODE[((value >> 18) & 0b11_1111) as usize];
75            *destination.offset(1) = B64_URL_ENCODE[((value >> 12) & 0b11_1111) as usize];
76            *destination.offset(2) = B64_URL_ENCODE[((value >> 6) & 0b11_1111) as usize];
77            if !config.padding.omit {
78                *destination.offset(3) = B64_URL_PAD;
79                bytes += 4;
80            } else {
81                bytes += 3;
82            }
83        }
84        1 => {
85            let value = (*source as u32) << 16;
86            *destination = B64_URL_ENCODE[((value >> 18) & 0b11_1111) as usize];
87            *destination.offset(1) = B64_URL_ENCODE[((value >> 12) & 0b11_1111) as usize];
88            if !config.padding.omit {
89                *destination.offset(2) = B64_URL_PAD;
90                *destination.offset(3) = B64_URL_PAD;
91                bytes += 4;
92            } else {
93                bytes += 2;
94            }
95        }
96        _ => {}
97    }
98    bytes
99}
100
101#[inline(always)]
102pub fn b64_url_encode_with_config(bytes: &[u8], config: &B64Config) -> Vec<u8> {
103    let length = bytes.len();
104    let mut vec = Vec::<u8>::with_capacity(_b64_url_encode_calculate_destination_capacity(length));
105    unsafe {
106        let bytes = _b64_url_encode_with_config(bytes.as_ptr(), length, vec.as_mut_ptr(), config);
107        vec.set_len(bytes);
108    }
109    vec
110}
111
112#[cfg(test)]
113mod b64_url_encode_with_config_tests {
114    use super::*;
115
116    mod with_omit_pads {
117        use super::*;
118
119        #[test]
120        fn empty() {
121            let config = B64Config {
122                padding: B64ConfigPadding { omit: true },
123            };
124            let result_bytes = b64_url_encode_with_config(b"", &config);
125            let result = String::from_utf8(result_bytes).unwrap();
126            assert_eq!(result, "");
127        }
128
129        #[test]
130        fn f() {
131            let config = B64Config {
132                padding: B64ConfigPadding { omit: true },
133            };
134            let result_bytes = b64_url_encode_with_config(b"f", &config);
135            let result = String::from_utf8(result_bytes).unwrap();
136            assert_eq!(result, "Zg");
137        }
138
139        #[test]
140        fn fo() {
141            let config = B64Config {
142                padding: B64ConfigPadding { omit: true },
143            };
144            let result_bytes = b64_url_encode_with_config(b"fo", &config);
145            let result = String::from_utf8(result_bytes).unwrap();
146            assert_eq!(result, "Zm8");
147        }
148
149        #[test]
150        fn foo() {
151            let config = B64Config {
152                padding: B64ConfigPadding { omit: true },
153            };
154            let result_bytes = b64_url_encode_with_config(b"foo", &config);
155            let result = String::from_utf8(result_bytes).unwrap();
156            assert_eq!(result, "Zm9v");
157        }
158
159        #[test]
160        fn foob() {
161            let config = B64Config {
162                padding: B64ConfigPadding { omit: true },
163            };
164            let result_bytes = b64_url_encode_with_config(b"foob", &config);
165            let result = String::from_utf8(result_bytes).unwrap();
166            assert_eq!(result, "Zm9vYg");
167        }
168
169        #[test]
170        fn fooba() {
171            let config = B64Config {
172                padding: B64ConfigPadding { omit: true },
173            };
174            let result_bytes = b64_url_encode_with_config(b"fooba", &config);
175            let result = String::from_utf8(result_bytes).unwrap();
176            assert_eq!(result, "Zm9vYmE");
177        }
178
179        #[test]
180        fn foobar() {
181            let config = B64Config {
182                padding: B64ConfigPadding { omit: true },
183            };
184            let result_bytes = b64_url_encode_with_config(b"foobar", &config);
185            let result = String::from_utf8(result_bytes).unwrap();
186            assert_eq!(result, "Zm9vYmFy");
187        }
188    }
189}
190
191#[inline(always)]
192pub fn b64_url_encode(bytes: &[u8]) -> Vec<u8> {
193    b64_url_encode_with_config(bytes, &DEFAULT_CONFIG)
194}
195
196#[cfg(test)]
197mod b64_url_encode_tests {
198    use super::*;
199
200    #[test]
201    fn empty() {
202        let result_bytes = b64_url_encode(b"");
203        let result = String::from_utf8(result_bytes).unwrap();
204        assert_eq!(result, "");
205    }
206
207    #[test]
208    fn f() {
209        let result_bytes = b64_url_encode(b"f");
210        let result = String::from_utf8(result_bytes).unwrap();
211        assert_eq!(result, "Zg==");
212    }
213
214    #[test]
215    fn fo() {
216        let result_bytes = b64_url_encode(b"fo");
217        let result = String::from_utf8(result_bytes).unwrap();
218        assert_eq!(result, "Zm8=");
219    }
220
221    #[test]
222    fn foo() {
223        let result_bytes = b64_url_encode(b"foo");
224        let result = String::from_utf8(result_bytes).unwrap();
225        assert_eq!(result, "Zm9v");
226    }
227
228    #[test]
229    fn foob() {
230        let result_bytes = b64_url_encode(b"foob");
231        let result = String::from_utf8(result_bytes).unwrap();
232        assert_eq!(result, "Zm9vYg==");
233    }
234
235    #[test]
236    fn fooba() {
237        let result_bytes = b64_url_encode(b"fooba");
238        let result = String::from_utf8(result_bytes).unwrap();
239        assert_eq!(result, "Zm9vYmE=");
240    }
241
242    #[test]
243    fn foobar() {
244        let result_bytes = b64_url_encode(b"foobar");
245        let result = String::from_utf8(result_bytes).unwrap();
246        assert_eq!(result, "Zm9vYmFy");
247    }
248}
249
250/// # Safety
251///
252/// This function should not be called without checking the input value.
253#[inline(always)]
254pub unsafe fn b64_url_decode_without_validation(bytes: &[u8]) -> Vec<u8> {
255    b64_url_decode_with_config_without_validation(bytes, &DEFAULT_CONFIG)
256}
257
258#[cfg(test)]
259mod b64_url_decode_without_validation_tests {
260    use super::*;
261
262    #[test]
263    fn empty() {
264        let result_bytes = unsafe { b64_url_decode_without_validation(b"") };
265        let result = String::from_utf8(result_bytes).unwrap();
266        assert_eq!(result, "");
267    }
268
269    #[test]
270    fn f() {
271        let result_bytes = unsafe { b64_url_decode_without_validation(b"Zg==") };
272        let result = String::from_utf8(result_bytes).unwrap();
273        assert_eq!(result, "f");
274    }
275
276    #[test]
277    fn fo() {
278        let result_bytes = unsafe { b64_url_decode_without_validation(b"Zm8=") };
279        let result = String::from_utf8(result_bytes).unwrap();
280        assert_eq!(result, "fo");
281    }
282
283    #[test]
284    fn foo() {
285        let result_bytes = unsafe { b64_url_decode_without_validation(b"Zm9v") };
286        let result = String::from_utf8(result_bytes).unwrap();
287        assert_eq!(result, "foo");
288    }
289
290    #[test]
291    fn foob() {
292        let result_bytes = unsafe { b64_url_decode_without_validation(b"Zm9vYg==") };
293        let result = String::from_utf8(result_bytes).unwrap();
294        assert_eq!(result, "foob");
295    }
296
297    #[test]
298    fn fooba() {
299        let result_bytes = unsafe { b64_url_decode_without_validation(b"Zm9vYmE=") };
300        let result = String::from_utf8(result_bytes).unwrap();
301        assert_eq!(result, "fooba");
302    }
303
304    #[test]
305    fn foobar() {
306        let result_bytes = unsafe { b64_url_decode_without_validation(b"Zm9vYmFy") };
307        let result = String::from_utf8(result_bytes).unwrap();
308        assert_eq!(result, "foobar");
309    }
310}
311
312/// # Safety
313///
314/// This function should not be called without checking the input value.
315#[inline(always)]
316pub unsafe fn b64_url_decode_with_config_without_validation(
317    bytes: &[u8],
318    config: &B64Config,
319) -> Vec<u8> {
320    let length = bytes.len();
321    let mut vec = Vec::<u8>::with_capacity(length * 3 / 4);
322    let mut index = 0;
323    if length > 4 {
324        while index < length - 4 {
325            let value = ((B64_URL_DECODE[(bytes[index] as usize)] as u32) << 18)
326                | ((B64_URL_DECODE[(bytes[index + 1] as usize)] as u32) << 12)
327                | ((B64_URL_DECODE[(bytes[index + 2] as usize)] as u32) << 6)
328                | (B64_URL_DECODE[(bytes[index + 3] as usize)] as u32);
329            vec.push(((value >> 16) & 0b1111_1111) as u8);
330            vec.push(((value >> 8) & 0b1111_1111) as u8);
331            vec.push((value & 0b1111_1111) as u8);
332            index += 4;
333        }
334    }
335    if config.padding.omit {
336        if index + 2 <= length {
337            let mut value = ((B64_URL_DECODE[(bytes[index] as usize)] as u32) << 18)
338                | ((B64_URL_DECODE[(bytes[index + 1] as usize)] as u32) << 12);
339            if index + 3 <= length {
340                value |= (B64_URL_DECODE[(bytes[index + 2] as usize)] as u32) << 6;
341                if index + 4 <= length {
342                    value |= B64_URL_DECODE[(bytes[index + 3] as usize)] as u32;
343                    vec.push(((value >> 16) & 0b1111_1111) as u8);
344                    vec.push(((value >> 8) & 0b1111_1111) as u8);
345                    vec.push((value & 0b1111_1111) as u8);
346                } else {
347                    vec.push(((value >> 16) & 0b1111_1111) as u8);
348                    vec.push(((value >> 8) & 0b1111_1111) as u8);
349                }
350            } else {
351                vec.push(((value >> 16) & 0b1111_1111) as u8);
352            }
353        }
354        return vec;
355    }
356    if index + 4 == length {
357        let mut value = ((B64_URL_DECODE[(bytes[index] as usize)] as u32) << 18)
358            | ((B64_URL_DECODE[(bytes[index + 1] as usize)] as u32) << 12);
359        if bytes[index + 2] != B64_URL_PAD {
360            value |= (B64_URL_DECODE[(bytes[index + 2] as usize)] as u32) << 6;
361            if bytes[index + 3] != B64_URL_PAD {
362                value |= B64_URL_DECODE[(bytes[index + 3] as usize)] as u32;
363                vec.push(((value >> 16) & 0b1111_1111) as u8);
364                vec.push(((value >> 8) & 0b1111_1111) as u8);
365                vec.push((value & 0b1111_1111) as u8);
366            } else {
367                vec.push(((value >> 16) & 0b1111_1111) as u8);
368                vec.push(((value >> 8) & 0b1111_1111) as u8);
369            }
370        } else {
371            vec.push(((value >> 16) & 0b1111_1111) as u8);
372        }
373    }
374    vec
375}
376
377#[cfg(test)]
378mod b64_url_decode_with_config_without_validation_tests {
379    use super::*;
380
381    mod with_omit_pads {
382        use super::*;
383
384        #[test]
385        fn empty() {
386            let config = B64Config {
387                padding: B64ConfigPadding { omit: true },
388            };
389            let result_bytes =
390                unsafe { b64_url_decode_with_config_without_validation(b"", &config) };
391            let result = String::from_utf8(result_bytes).unwrap();
392            assert_eq!(result, "");
393        }
394
395        #[test]
396        fn f() {
397            let config = B64Config {
398                padding: B64ConfigPadding { omit: true },
399            };
400            let result_bytes =
401                unsafe { b64_url_decode_with_config_without_validation(b"Zg", &config) };
402            let result = String::from_utf8(result_bytes).unwrap();
403            assert_eq!(result, "f");
404        }
405
406        #[test]
407        fn fo() {
408            let config = B64Config {
409                padding: B64ConfigPadding { omit: true },
410            };
411            let result_bytes =
412                unsafe { b64_url_decode_with_config_without_validation(b"Zm8", &config) };
413            let result = String::from_utf8(result_bytes).unwrap();
414            assert_eq!(result, "fo");
415        }
416
417        #[test]
418        fn foo() {
419            let config = B64Config {
420                padding: B64ConfigPadding { omit: true },
421            };
422            let result_bytes =
423                unsafe { b64_url_decode_with_config_without_validation(b"Zm9v", &config) };
424            let result = String::from_utf8(result_bytes).unwrap();
425            assert_eq!(result, "foo");
426        }
427
428        #[test]
429        fn foob() {
430            let config = B64Config {
431                padding: B64ConfigPadding { omit: true },
432            };
433            let result_bytes =
434                unsafe { b64_url_decode_with_config_without_validation(b"Zm9vYg", &config) };
435            let result = String::from_utf8(result_bytes).unwrap();
436            assert_eq!(result, "foob");
437        }
438
439        #[test]
440        fn fooba() {
441            let config = B64Config {
442                padding: B64ConfigPadding { omit: true },
443            };
444            let result_bytes =
445                unsafe { b64_url_decode_with_config_without_validation(b"Zm9vYmE", &config) };
446            let result = String::from_utf8(result_bytes).unwrap();
447            assert_eq!(result, "fooba");
448        }
449
450        #[test]
451        fn foobar() {
452            let config = B64Config {
453                padding: B64ConfigPadding { omit: true },
454            };
455            let result_bytes =
456                unsafe { b64_url_decode_with_config_without_validation(b"Zm9vYmFy", &config) };
457            let result = String::from_utf8(result_bytes).unwrap();
458            assert_eq!(result, "foobar");
459        }
460    }
461}