macro_toolset/string/
base64.rs

1//! Base64 string utilities.
2
3use std::{marker::PhantomData, ops};
4
5use super::{NumStr, StringExtT, StringT};
6
7pub mod b64_padding {
8    //! Base64 padding
9    //!
10    //! The `base64` crate has ugly APIs and we here create some ZSTs
11    //! to represent the padding, convenient to use and performance improvement.
12
13    use super::{
14        Base64EncoderT, Base64Str, Decode, DecodeToAny, DecodeToHex, Encode, NumStr, PhantomData,
15        StringExtT, StringT,
16    };
17
18    macro_rules! enum_padding {
19        ($($name:ident) *) => {
20            $(
21                #[derive(Debug)]
22                #[allow(non_camel_case_types)]
23                #[doc = "Base64 Padding: "]
24                #[doc = stringify!($name) ]
25                pub struct $name;
26
27                impl<T: AsRef<[u8]>> Base64EncoderT for Base64Str<T, $name, Encode> {}
28
29                impl $name {
30                    #[inline]
31                    /// Create a new [`Base64Str`], and finally encode it to a Base64 string.
32                    pub fn encode<T: AsRef<[u8]>>(inner: T) -> Base64Str<T, $name, Encode> {
33                        Base64Str {
34                            inner,
35                            padding: PhantomData,
36                            command: PhantomData,
37                        }
38                    }
39
40                    #[inline]
41                    /// Create a new [`Base64Str`], and finally decode the inner Base64 string.
42                    ///
43                    /// Notice: will do nothing if the decoded string is not valid UTF-8 encoded.
44                    /// If that is acceptable, use [`decode_to_any`](Self::decode_to_any).
45                    pub fn decode<T: AsRef<[u8]>>(inner: T) -> Base64Str<T, $name, Decode> {
46                        Base64Str {
47                            inner,
48                            padding: PhantomData,
49                            command: PhantomData,
50                        }
51                    }
52
53                    #[allow(unsafe_code)]
54                    #[inline]
55                    /// Create a new [`Base64Str`], and finally decode the inner Base64 string.
56                    ///
57                    /// # Safety
58                    ///
59                    /// Calling this means the decoded string can be invalid UTF-8.
60                    pub unsafe fn decode_to_any<T: AsRef<[u8]>>(inner: T) -> Base64Str<T, $name, DecodeToAny> {
61                        Base64Str {
62                            inner,
63                            padding: PhantomData,
64                            command: PhantomData,
65                        }
66                    }
67
68                    #[inline]
69                    /// Create a new [`Base64Str`], and finally decode the inner Base64 string.
70                    ///
71                    /// Notice: will do nothing if the inner string is not a valid Base64 string.
72                    pub fn decode_to_hex<T: AsRef<[u8]>>(inner: T) -> Base64Str<T, $name, DecodeToHex> {
73                        Base64Str {
74                            inner,
75                            padding: PhantomData,
76                            command: PhantomData,
77                        }
78                    }
79                }
80
81                impl<T: AsRef<[u8]>> StringT for Base64Str<T, $name, Encode> {
82                    #[inline]
83                    fn encode_to_buf(self, string: &mut Vec<u8>) {
84                        let inner = self.inner.as_ref();
85
86                        let current_len = string.len();
87                        let base64_len = inner.len() * 4 / 3 + 4;
88                        let target_len = current_len + base64_len;
89
90                        string.reserve_exact(base64_len);
91                        #[allow(unsafe_code)]
92                        // Safety: have reserved and allocate enough space
93                        unsafe {
94                            string.set_len(target_len);
95                        }
96
97                        let bytes_written = base64::Engine::encode_slice(
98                            &base64::engine::general_purpose::$name,
99                            self.inner,
100                            &mut string[current_len..target_len],
101                        )
102                        .unwrap_or(0);
103
104                        string.truncate(current_len + bytes_written);
105                    }
106
107                    #[inline]
108                    fn encode_to_buf_with_separator(self, string: &mut Vec<u8>, separator: &str) {
109                        self.encode_to_buf(string);
110                        string.extend(separator.as_bytes());
111                    }
112
113                    #[inline]
114                    fn encode_to_bytes_buf(self, string: &mut bytes::BytesMut) {
115                        let inner = self.inner.as_ref();
116
117                        let current_len = string.len();
118                        let base64_len = inner.len() * 4 / 3 + 4;
119                        let target_len = current_len + base64_len;
120
121                        string.reserve(base64_len);
122                        #[allow(unsafe_code)]
123                        // Safety: have reserved and allocate enough space
124                        unsafe {
125                            string.set_len(target_len);
126                        }
127
128                        let bytes_written = base64::Engine::encode_slice(
129                            &base64::engine::general_purpose::$name,
130                            self.inner,
131                            &mut string[current_len..target_len],
132                        )
133                        .unwrap_or(0);
134
135                        string.truncate(current_len + bytes_written);
136                    }
137
138                    #[inline]
139                    fn encode_to_bytes_buf_with_separator(self, string: &mut bytes::BytesMut, separator: &str) {
140                        self.encode_to_bytes_buf(string);
141                        string.extend(separator.as_bytes());
142                    }
143                }
144
145                impl<T: AsRef<[u8]>> StringExtT for Base64Str<T, $name, Encode> {}
146
147                impl<T: AsRef<[u8]>> StringT for Base64Str<T, $name, Decode> {
148                    #[inline]
149                    fn encode_to_buf(self, string: &mut Vec<u8>) {
150                        use base64::Engine;
151
152                        let data = base64::engine::general_purpose::$name
153                            .decode(self.inner.as_ref())
154                            .unwrap_or_default();
155
156                        if std::str::from_utf8(&data).is_ok() {
157                            string.extend(data)
158                        }
159                    }
160
161                    #[inline]
162                    fn encode_to_buf_with_separator(self, string: &mut Vec<u8>, separator: &str) {
163                        self.encode_to_buf(string);
164                        string.extend(separator.as_bytes());
165                    }
166
167                    #[inline]
168                    fn encode_to_bytes_buf(self, string: &mut bytes::BytesMut) {
169                        use base64::Engine;
170
171                        let data = base64::engine::general_purpose::$name
172                            .decode(self.inner.as_ref())
173                            .unwrap_or_default();
174
175                        if std::str::from_utf8(&data).is_ok() {
176                            string.extend(data)
177                        }
178                    }
179
180                    #[inline]
181                    fn encode_to_bytes_buf_with_separator(self, string: &mut bytes::BytesMut, separator: &str) {
182                        self.encode_to_bytes_buf(string);
183                        string.extend(separator.as_bytes());
184                    }
185                }
186
187                impl<T: AsRef<[u8]>> StringExtT for Base64Str<T, $name, Decode> {}
188
189                impl<T: AsRef<[u8]>> StringT for Base64Str<T, $name, DecodeToAny> {
190                    #[inline]
191                    fn encode_to_buf(self, string: &mut Vec<u8>) {
192                        use base64::Engine;
193
194                        let _ = base64::engine::general_purpose::$name
195                            .decode_vec(self.inner.as_ref(), string);
196                    }
197
198                    #[inline]
199                    fn encode_to_buf_with_separator(self, string: &mut Vec<u8>, separator: &str) {
200                        self.encode_to_buf(string);
201                        string.extend(separator.as_bytes());
202                    }
203
204                    #[inline]
205                    fn encode_to_bytes_buf(self, string: &mut bytes::BytesMut) {
206                        use base64::Engine;
207
208                        if let Ok(data) = base64::engine::general_purpose::$name.decode(self.inner.as_ref()) {
209                            string.extend(data);
210                        }
211                    }
212
213                    #[inline]
214                    fn encode_to_bytes_buf_with_separator(self, string: &mut bytes::BytesMut, separator: &str) {
215                        self.encode_to_bytes_buf(string);
216                        string.extend(separator.as_bytes());
217                    }
218                }
219
220                impl<T: AsRef<[u8]>> StringExtT for Base64Str<T, $name, DecodeToAny> {}
221
222                impl<T: AsRef<[u8]>> StringT for Base64Str<T, $name, DecodeToHex> {
223                    #[inline]
224                    fn encode_to_buf(self, string: &mut Vec<u8>) {
225                        use base64::Engine;
226
227                        base64::engine::general_purpose::$name
228                            .decode(self.inner.as_ref())
229                            .unwrap_or_default()
230                            .into_iter()
231                            .map(NumStr::hex_byte_default)
232                            .encode_to_buf(string);
233                    }
234
235                    #[inline]
236                    fn encode_to_buf_with_separator(self, string: &mut Vec<u8>, separator: &str) {
237                        self.encode_to_buf(string);
238                        string.extend(separator.as_bytes());
239                    }
240
241                    #[inline]
242                    fn encode_to_bytes_buf(self, string: &mut bytes::BytesMut) {
243                        use base64::Engine;
244
245                        base64::engine::general_purpose::$name
246                            .decode(self.inner.as_ref())
247                            .unwrap_or_default()
248                            .into_iter()
249                            .map(NumStr::hex_byte_default)
250                            .encode_to_bytes_buf(string);
251                    }
252
253                    #[inline]
254                    fn encode_to_bytes_buf_with_separator(self, string: &mut bytes::BytesMut, separator: &str) {
255                        self.encode_to_bytes_buf(string);
256                        string.extend(separator.as_bytes());
257                    }
258                }
259
260                impl<T: AsRef<[u8]>> StringExtT for Base64Str<T, $name, DecodeToHex> {}
261            )*
262        };
263    }
264
265    enum_padding!(STANDARD STANDARD_NO_PAD URL_SAFE URL_SAFE_NO_PAD);
266}
267
268/// Marker trait
269pub trait Base64EncoderT: StringExtT {}
270
271#[derive(Debug)]
272/// Command: Encode, ZST marker struct
273pub struct Encode;
274
275#[derive(Debug)]
276/// Command: Decode, ZST marker struct
277///
278/// Notice: Will do nothing if the decoded string is not valid UTF-8 encoded.
279pub struct Decode;
280
281#[derive(Debug)]
282/// Command: Decode, ZST marker struct
283///
284/// This means the decoded string can be invalid UTF-8.
285pub struct DecodeToAny;
286
287#[derive(Debug)]
288/// Command: Decode, ZST marker struct
289///
290/// This means the decoded byte will be hex encoded, lowercase.
291pub struct DecodeToHex;
292
293#[derive(Debug)]
294#[repr(transparent)]
295/// Base64 string, to encode or decode.
296///
297/// This struct can only be created by [`b64_padding::STANDARD`], etc.
298///
299/// Notice: will do nothing if the inner is not base64 encoded when decoding.
300pub struct Base64Str<T: AsRef<[u8]>, P = b64_padding::STANDARD, C = Encode> {
301    inner: T,
302    padding: PhantomData<P>,
303    command: PhantomData<C>,
304}
305
306impl<T: AsRef<[u8]>, P, C> ops::Deref for Base64Str<T, P, C> {
307    type Target = T;
308
309    fn deref(&self) -> &Self::Target {
310        &self.inner
311    }
312}
313
314impl<T: AsRef<[u8]>, P, C> AsRef<[u8]> for Base64Str<T, P, C> {
315    fn as_ref(&self) -> &[u8] {
316        self.inner.as_ref()
317    }
318}
319
320#[allow(unsafe_code)]
321#[cfg(test)]
322mod test {
323    use super::*;
324
325    #[test]
326    fn test_base64() {
327        assert_eq!(
328            b64_padding::STANDARD::encode(b"hello world").to_string_ext(),
329            "aGVsbG8gd29ybGQ="
330        );
331        assert_eq!(
332            b64_padding::STANDARD::encode(b"hello world").to_string_ext(),
333            "aGVsbG8gd29ybGQ="
334        );
335        assert_eq!(
336            b64_padding::STANDARD::encode("hello world").to_string_ext(),
337            "aGVsbG8gd29ybGQ="
338        );
339        assert_eq!(
340            b64_padding::STANDARD::encode("hello world").to_string_ext(),
341            "aGVsbG8gd29ybGQ="
342        );
343        assert_eq!(
344            b64_padding::STANDARD::decode(b"aGVsbG8gd29ybGQ=").to_string_ext(),
345            "hello world"
346        );
347        assert_eq!(
348            unsafe { b64_padding::STANDARD::decode_to_any(b"aGVsbG8gd29ybGQ=") }.to_string_ext(),
349            "hello world"
350        );
351        assert_eq!(
352            b64_padding::STANDARD::decode_to_hex(
353                b64_padding::STANDARD::encode(vec![0x11, 0x45, 0x14, 0x19, 0x19, 0x81, 0x00])
354                    .to_string_ext()
355            )
356            .to_string_ext(),
357            "11451419198100"
358        );
359    }
360}