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}