base64_simd/
forgiving.rs

1use crate::ascii::*;
2use crate::STANDARD_FORGIVING;
3use crate::{Error, Out};
4
5use vsimd::tools::slice_mut;
6
7use core::ptr::copy_nonoverlapping;
8
9#[cfg(feature = "alloc")]
10use alloc::vec::Vec;
11
12/// Forgiving decodes a base64 string to bytes and writes inplace.
13///
14/// This function uses the standard charset.
15///
16/// See <https://infra.spec.whatwg.org/#forgiving-base64>
17///
18/// # Errors
19/// This function returns `Err` if the content of `data` is invalid.
20#[inline]
21pub fn forgiving_decode_inplace(data: &mut [u8]) -> Result<&mut [u8], Error> {
22    let data = remove_ascii_whitespace_inplace(data);
23    STANDARD_FORGIVING.decode_inplace(data)
24}
25
26/// Forgiving decodes a base64 string to bytes.
27///
28/// This function uses the standard charset.
29///
30/// See <https://infra.spec.whatwg.org/#forgiving-base64>
31///
32/// # Errors
33/// This function returns `Err` if the content of `src` is invalid.
34///
35/// # Panics
36/// This function asserts that `src.len() <= dst.len()`
37#[inline]
38pub fn forgiving_decode<'d>(src: &[u8], mut dst: Out<'d, [u8]>) -> Result<&'d mut [u8], Error> {
39    assert!(src.len() <= dst.len());
40
41    let pos = find_non_ascii_whitespace(src);
42    debug_assert!(pos <= src.len());
43
44    if pos == src.len() {
45        return STANDARD_FORGIVING.decode(src, dst);
46    }
47
48    unsafe {
49        let len = src.len();
50        let src = src.as_ptr();
51        let dst = dst.as_mut_ptr();
52
53        copy_nonoverlapping(src, dst, pos);
54
55        let rem = remove_ascii_whitespace_fallback(src.add(pos), len - pos, dst.add(pos));
56        debug_assert!(rem <= len - pos);
57
58        let data = slice_mut(dst, pos + rem);
59        STANDARD_FORGIVING.decode_inplace(data)
60    }
61}
62
63/// Forgiving decodes a base64 string to bytes and returns a new [`Vec<u8>`](Vec).
64///
65/// This function uses the standard charset.
66///
67/// See <https://infra.spec.whatwg.org/#forgiving-base64>
68///
69/// # Errors
70/// This function returns `Err` if the content of `data` is invalid.
71#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
72#[cfg(feature = "alloc")]
73#[inline]
74pub fn forgiving_decode_to_vec(data: &[u8]) -> Result<Vec<u8>, Error> {
75    let pos = find_non_ascii_whitespace(data);
76    debug_assert!(pos <= data.len());
77
78    if pos == data.len() {
79        return STANDARD_FORGIVING.decode_type::<Vec<u8>>(data);
80    }
81
82    let mut vec = Vec::with_capacity(data.len());
83
84    unsafe {
85        let len = data.len();
86        let src = data.as_ptr();
87        let dst = vec.as_mut_ptr();
88
89        copy_nonoverlapping(src, dst, pos);
90
91        let rem = remove_ascii_whitespace_fallback(src.add(pos), len - pos, dst.add(pos));
92        debug_assert!(rem <= len - pos);
93
94        let data = slice_mut(dst, pos + rem);
95        let ans_len = STANDARD_FORGIVING.decode_inplace(data)?.len();
96
97        vec.set_len(ans_len);
98    };
99
100    Ok(vec)
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106
107    use crate::AsOut;
108
109    #[cfg_attr(not(target_arch = "wasm32"), test)]
110    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
111    fn test_forgiving() {
112        use const_str::hex;
113
114        let mut inputs: Vec<&str> = Vec::new();
115        let mut outputs: Vec<&[u8]> = Vec::new();
116
117        {
118            let mut i = |i| inputs.push(i);
119            let mut o = |o| outputs.push(o);
120
121            i("ab");
122            o(&[0x69]);
123
124            i("abc");
125            o(&[0x69, 0xB7]);
126
127            i("abcd");
128            o(&[0x69, 0xB7, 0x1D]);
129
130            i("helloworld");
131            o(&hex!("85 E9 65 A3 0A 2B 95"));
132
133            i(" h e l l o w o r\nl\rd\t");
134            o(&hex!("85 E9 65 A3 0A 2B 95"));
135        }
136
137        for i in 0..inputs.len() {
138            let (src, expected) = (inputs[i], outputs[i]);
139
140            let mut buf = src.to_owned().into_bytes();
141
142            let ans = forgiving_decode_inplace(&mut buf).unwrap();
143            assert_eq!(ans, expected);
144
145            let ans = crate::forgiving_decode(src.as_bytes(), buf.as_out()).unwrap();
146            assert_eq!(ans, expected);
147
148            #[cfg(feature = "alloc")]
149            {
150                let ans = crate::forgiving_decode_to_vec(src.as_bytes()).unwrap();
151                assert_eq!(ans, expected);
152            }
153        }
154    }
155}