serde_with/
base64.rs

1//! De/Serialization of base64 encoded bytes
2//!
3//! This modules is only available when using the `base64` feature of the crate.
4//!
5//! Please check the documentation on the [`Base64`] type for details.
6
7use crate::prelude::*;
8
9/// Serialize bytes with base64
10///
11/// The type serializes a sequence of bytes as a base64 string.
12/// It works on any type implementing `AsRef<[u8]>` for serialization and `TryFrom<Vec<u8>>` for deserialization.
13///
14/// The type allows customizing the character set and the padding behavior.
15/// The `ALPHABET` is a type implementing [`Alphabet`].
16/// `PADDING` specifies if serializing should emit padding.
17/// Deserialization always supports padded and unpadded formats.
18/// [`formats::Padded`] emits padding and [`formats::Unpadded`] leaves it off.
19///
20/// ```rust
21/// # #[cfg(feature = "macros")] {
22/// # use serde::{Deserialize, Serialize};
23/// # use serde_with::serde_as;
24/// use serde_with::base64::{Base64, Bcrypt, BinHex, Standard};
25/// use serde_with::formats::{Padded, Unpadded};
26///
27/// #[serde_as]
28/// # #[derive(Debug, PartialEq, Eq)]
29/// #[derive(Serialize, Deserialize)]
30/// struct B64 {
31///     // The default is the same as Standard character set with padding
32///     #[serde_as(as = "Base64")]
33///     default: Vec<u8>,
34///     // Only change the character set, implies padding
35///     #[serde_as(as = "Base64<BinHex>")]
36///     charset_binhex: Vec<u8>,
37///
38///     #[serde_as(as = "Base64<Standard, Padded>")]
39///     explicit_padding: Vec<u8>,
40///     #[serde_as(as = "Base64<Bcrypt, Unpadded>")]
41///     no_padding: Vec<u8>,
42/// }
43///
44/// let b64 = B64 {
45///     default: b"Hello World".to_vec(),
46///     charset_binhex: b"Hello World".to_vec(),
47///     explicit_padding: b"Hello World".to_vec(),
48///     no_padding: b"Hello World".to_vec(),
49/// };
50/// let json = serde_json::json!({
51///     "default": "SGVsbG8gV29ybGQ=",
52///     "charset_binhex": "5'9XE'mJ9fpbE'3=",
53///     "explicit_padding": "SGVsbG8gV29ybGQ=",
54///     "no_padding": "QETqZE6eT07wZEO",
55/// });
56///
57/// // Test serialization and deserialization
58/// assert_eq!(json, serde_json::to_value(&b64).unwrap());
59/// assert_eq!(b64, serde_json::from_value(json).unwrap());
60/// # }
61/// ```
62// The padding might be better as `const PADDING: bool = true`
63// https://blog.rust-lang.org/inside-rust/2021/09/06/Splitting-const-generics.html#featureconst_generics_default/
64pub struct Base64<ALPHABET: Alphabet = Standard, PADDING: formats::Format = formats::Padded>(
65    PhantomData<(ALPHABET, PADDING)>,
66);
67
68impl<T, ALPHABET> SerializeAs<T> for Base64<ALPHABET, formats::Padded>
69where
70    T: AsRef<[u8]>,
71    ALPHABET: Alphabet,
72{
73    fn serialize_as<S>(source: &T, serializer: S) -> Result<S::Ok, S::Error>
74    where
75        S: Serializer,
76    {
77        use ::base64::Engine as _;
78
79        ::base64::engine::GeneralPurpose::new(
80            &ALPHABET::charset(),
81            ::base64::engine::general_purpose::PAD,
82        )
83        .encode(source)
84        .serialize(serializer)
85    }
86}
87
88impl<T, ALPHABET> SerializeAs<T> for Base64<ALPHABET, formats::Unpadded>
89where
90    T: AsRef<[u8]>,
91    ALPHABET: Alphabet,
92{
93    fn serialize_as<S>(source: &T, serializer: S) -> Result<S::Ok, S::Error>
94    where
95        S: Serializer,
96    {
97        use ::base64::Engine as _;
98
99        ::base64::engine::GeneralPurpose::new(
100            &ALPHABET::charset(),
101            ::base64::engine::general_purpose::NO_PAD,
102        )
103        .encode(source)
104        .serialize(serializer)
105    }
106}
107
108// Our decoders uniformly do not care about padding.
109const PAD_INDIFFERENT: ::base64::engine::GeneralPurposeConfig =
110    ::base64::engine::GeneralPurposeConfig::new()
111        .with_decode_padding_mode(::base64::engine::DecodePaddingMode::Indifferent);
112
113impl<'de, T, ALPHABET, FORMAT> DeserializeAs<'de, T> for Base64<ALPHABET, FORMAT>
114where
115    T: TryFrom<Vec<u8>>,
116    ALPHABET: Alphabet,
117    FORMAT: formats::Format,
118{
119    fn deserialize_as<D>(deserializer: D) -> Result<T, D::Error>
120    where
121        D: Deserializer<'de>,
122    {
123        struct Helper<T, ALPHABET>(PhantomData<(T, ALPHABET)>);
124
125        impl<T, ALPHABET> Visitor<'_> for Helper<T, ALPHABET>
126        where
127            T: TryFrom<Vec<u8>>,
128            ALPHABET: Alphabet,
129        {
130            type Value = T;
131
132            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
133                formatter.write_str("a base64 encoded string")
134            }
135
136            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
137            where
138                E: DeError,
139            {
140                use ::base64::Engine as _;
141
142                let bytes =
143                    ::base64::engine::GeneralPurpose::new(&ALPHABET::charset(), PAD_INDIFFERENT)
144                        .decode(value)
145                        .map_err(DeError::custom)?;
146
147                let length = bytes.len();
148                bytes.try_into().map_err(|_e: T::Error| {
149                    DeError::custom(format_args!(
150                        "Can't convert a Byte Vector of length {length} to the output type."
151                    ))
152                })
153            }
154        }
155
156        deserializer.deserialize_str(Helper::<T, ALPHABET>(PhantomData))
157    }
158}
159
160mod sealed {
161    pub trait Sealed {}
162    impl Sealed for super::Standard {}
163    impl Sealed for super::UrlSafe {}
164    impl Sealed for super::Crypt {}
165    impl Sealed for super::Bcrypt {}
166    impl Sealed for super::ImapMutf7 {}
167    impl Sealed for super::BinHex {}
168}
169
170/// A base64 alphabet
171pub trait Alphabet: sealed::Sealed {
172    /// Return a specific alphabet.
173    fn charset() -> ::base64::alphabet::Alphabet;
174}
175/// The standard character set (uses `+` and `/`).
176///
177/// See [RFC 3548](https://tools.ietf.org/html/rfc3548#section-3).
178pub struct Standard;
179impl Alphabet for Standard {
180    fn charset() -> ::base64::alphabet::Alphabet {
181        ::base64::alphabet::STANDARD
182    }
183}
184
185/// The URL safe character set (uses `-` and `_`).
186///
187/// See [RFC 3548](https://tools.ietf.org/html/rfc3548#section-3).
188pub struct UrlSafe;
189impl Alphabet for UrlSafe {
190    fn charset() -> ::base64::alphabet::Alphabet {
191        ::base64::alphabet::URL_SAFE
192    }
193}
194
195/// The `crypt(3)` character set (uses `./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz`).
196///
197/// Not standardized, but folk wisdom on the net asserts that this alphabet is what crypt uses.
198pub struct Crypt;
199impl Alphabet for Crypt {
200    fn charset() -> ::base64::alphabet::Alphabet {
201        ::base64::alphabet::CRYPT
202    }
203}
204
205/// The bcrypt character set (uses `./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789`).
206pub struct Bcrypt;
207impl Alphabet for Bcrypt {
208    fn charset() -> ::base64::alphabet::Alphabet {
209        ::base64::alphabet::BCRYPT
210    }
211}
212
213/// The character set used in IMAP-modified UTF-7 (uses `+` and `,`).
214///
215/// See [RFC 3501](https://tools.ietf.org/html/rfc3501#section-5.1.3).
216pub struct ImapMutf7;
217impl Alphabet for ImapMutf7 {
218    fn charset() -> ::base64::alphabet::Alphabet {
219        ::base64::alphabet::IMAP_MUTF7
220    }
221}
222
223/// The character set used in `BinHex` 4.0 files.
224///
225/// See [BinHex 4.0 Definition](http://files.stairways.com/other/binhex-40-specs-info.txt).
226pub struct BinHex;
227impl Alphabet for BinHex {
228    fn charset() -> ::base64::alphabet::Alphabet {
229        ::base64::alphabet::BIN_HEX
230    }
231}