ruma_serde/
base64.rs

1//!Transparent base64 encoding / decoding as part of (de)serialization.
2
3use std::{fmt, marker::PhantomData};
4
5use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
6
7/// A wrapper around `B` (usually `Vec<u8>`) that (de)serializes from / to a base64 string.
8///
9/// The base64 character set (and miscellaneous other encoding / decoding options) can be customized
10/// through the generic parameter `C`.
11#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
12pub struct Base64<C = Standard, B = Vec<u8>> {
13    bytes: B,
14    // Invariant PhantomData, Send + Sync
15    _phantom_conf: PhantomData<fn(C) -> C>,
16}
17
18/// Config used for the [`Base64`] type.
19pub trait Base64Config {
20    /// The config as a constant.
21    ///
22    /// Opaque so our interface is not tied to the base64 crate version.
23    #[doc(hidden)]
24    const CONF: Conf;
25}
26
27#[doc(hidden)]
28pub struct Conf(base64::Config);
29
30/// Standard base64 character set without padding.
31///
32/// Allows trailing bits in decoding for maximum compatibility.
33#[non_exhaustive]
34// Easier than implementing these all for Base64 manually to avoid the `C: Trait` bounds.
35#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
36pub struct Standard;
37
38impl Base64Config for Standard {
39    // See https://github.com/matrix-org/matrix-doc/issues/3211
40    const CONF: Conf = Conf(base64::STANDARD_NO_PAD.decode_allow_trailing_bits(true));
41}
42
43/// Url-safe base64 character set without padding.
44///
45/// Allows trailing bits in decoding for maximum compatibility.
46#[non_exhaustive]
47// Easier than implementing these all for Base64 manually to avoid the `C: Trait` bounds.
48#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
49pub struct UrlSafe;
50
51impl Base64Config for UrlSafe {
52    const CONF: Conf = Conf(base64::URL_SAFE_NO_PAD.decode_allow_trailing_bits(true));
53}
54
55impl<C: Base64Config, B: AsRef<[u8]>> Base64<C, B> {
56    /// Create a `Base64` instance from raw bytes, to be base64-encoded in serialialization.
57    pub fn new(bytes: B) -> Self {
58        Self { bytes, _phantom_conf: PhantomData }
59    }
60
61    /// Get a reference to the raw bytes held by this `Base64` instance.
62    pub fn as_bytes(&self) -> &[u8] {
63        self.bytes.as_ref()
64    }
65
66    /// Encode the bytes contained in this `Base64` instance to unpadded base64.
67    pub fn encode(&self) -> String {
68        base64::encode_config(&self.bytes, C::CONF.0)
69    }
70}
71
72impl<C, B> Base64<C, B> {
73    /// Get the raw bytes held by this `Base64` instance.
74    pub fn into_inner(self) -> B {
75        self.bytes
76    }
77}
78
79impl<C: Base64Config> Base64<C> {
80    /// Create a `Base64` instance containing an empty `Vec<u8>`.
81    pub fn empty() -> Self {
82        Self::new(Vec::new())
83    }
84
85    /// Parse some base64-encoded data to create a `Base64` instance.
86    pub fn parse(encoded: impl AsRef<[u8]>) -> Result<Self, Base64DecodeError> {
87        base64::decode_config(encoded, C::CONF.0).map(Self::new).map_err(Base64DecodeError)
88    }
89}
90
91impl<C: Base64Config, B: AsRef<[u8]>> fmt::Debug for Base64<C, B> {
92    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93        self.encode().fmt(f)
94    }
95}
96
97impl<C: Base64Config, B: AsRef<[u8]>> fmt::Display for Base64<C, B> {
98    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99        self.encode().fmt(f)
100    }
101}
102
103impl<'de, C: Base64Config> Deserialize<'de> for Base64<C> {
104    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
105    where
106        D: Deserializer<'de>,
107    {
108        let encoded = crate::deserialize_cow_str(deserializer)?;
109        Self::parse(&*encoded).map_err(de::Error::custom)
110    }
111}
112
113impl<C: Base64Config, B: AsRef<[u8]>> Serialize for Base64<C, B> {
114    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
115    where
116        S: Serializer,
117    {
118        serializer.serialize_str(&self.encode())
119    }
120}
121
122/// An error that occurred while decoding a base64 string.
123#[derive(Clone)]
124pub struct Base64DecodeError(base64::DecodeError);
125
126impl fmt::Debug for Base64DecodeError {
127    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128        self.0.fmt(f)
129    }
130}
131
132impl fmt::Display for Base64DecodeError {
133    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
134        self.0.fmt(f)
135    }
136}
137
138impl std::error::Error for Base64DecodeError {}