paseto_core/
encodings.rs

1//! PASETO Message encodings.
2
3use alloc::borrow::ToOwned;
4use alloc::boxed::Box;
5use alloc::vec::Vec;
6use core::error::Error;
7use core::fmt;
8use core::marker::PhantomData;
9
10use crate::tokens::SealedToken;
11use crate::{PasetoError, version};
12
13/// An infallible byte writer
14pub trait WriteBytes {
15    /// Write bytes
16    fn write(&mut self, slice: &[u8]);
17}
18
19impl<W: WriteBytes> WriteBytes for &mut W {
20    fn write(&mut self, slice: &[u8]) {
21        W::write(self, slice);
22    }
23}
24
25impl WriteBytes for Vec<u8> {
26    fn write(&mut self, slice: &[u8]) {
27        self.extend_from_slice(slice)
28    }
29}
30
31/// A PASETO payload object.
32pub trait Payload: Sized {
33    /// Suffix for this encoding type.
34    ///
35    /// Currently the standard only supports JSON, which has no suffix.
36    const SUFFIX: &'static str;
37
38    /// Encode the message
39    fn encode(self, writer: impl WriteBytes) -> Result<(), Box<dyn Error + Send + Sync>>;
40
41    /// Decode the message
42    fn decode(payload: &[u8]) -> Result<Self, Box<dyn Error + Send + Sync>>;
43}
44
45/// Encoding scheme for PASETO footers.
46///
47/// Footers are allowed to be any encoding, but JSON is the standard.
48///
49/// Footers are also optional, so the `()` empty type is considered as a missing footer.
50pub trait Footer: Sized {
51    /// Encode the footer to bytes
52    fn encode(&self, writer: impl WriteBytes) -> Result<(), Box<dyn Error + Send + Sync>>;
53
54    /// Decode the footer from bytes
55    fn decode(footer: &[u8]) -> Result<Self, Box<dyn Error + Send + Sync>>;
56}
57
58impl Footer for Vec<u8> {
59    fn encode(&self, mut writer: impl WriteBytes) -> Result<(), Box<dyn Error + Send + Sync>> {
60        writer.write(self);
61        Ok(())
62    }
63
64    fn decode(footer: &[u8]) -> Result<Self, Box<dyn Error + Send + Sync>> {
65        Ok(footer.to_owned())
66    }
67}
68
69impl Footer for () {
70    fn encode(&self, _: impl WriteBytes) -> Result<(), Box<dyn Error + Send + Sync>> {
71        Ok(())
72    }
73
74    fn decode(footer: &[u8]) -> Result<Self, Box<dyn Error + Send + Sync>> {
75        match footer {
76            [] => Ok(()),
77            x => Err(format!("unexpected footer {x:?}").into()),
78        }
79    }
80}
81
82impl<V: version::Version, P: version::Purpose, M: Payload, F> fmt::Display
83    for SealedToken<V, P, M, F>
84{
85    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86        f.write_str(V::HEADER)?;
87        f.write_str(M::SUFFIX)?;
88        f.write_str(P::HEADER)?;
89        crate::base64::write_to_fmt(&self.payload, f)?;
90
91        if !self.encoded_footer.is_empty() {
92            f.write_str(".")?;
93            crate::base64::write_to_fmt(&self.encoded_footer, f)?;
94        }
95
96        Ok(())
97    }
98}
99
100impl<V: version::Version, P: version::Purpose, M: Payload, F: Footer> core::str::FromStr
101    for SealedToken<V, P, M, F>
102{
103    type Err = PasetoError;
104
105    fn from_str(s: &str) -> Result<Self, Self::Err> {
106        let s = s.strip_prefix(V::HEADER).ok_or(PasetoError::InvalidToken)?;
107        let s = s.strip_prefix(M::SUFFIX).ok_or(PasetoError::InvalidToken)?;
108        let s = s.strip_prefix(P::HEADER).ok_or(PasetoError::InvalidToken)?;
109
110        let (payload, footer) = match s.split_once('.') {
111            Some((payload, footer)) => (payload, Some(footer)),
112            None => (s, None),
113        };
114
115        let payload = crate::base64::decode_vec(payload)?.into_boxed_slice();
116        let encoded_footer = footer
117            .map(crate::base64::decode_vec)
118            .transpose()?
119            .unwrap_or_default()
120            .into_boxed_slice();
121        let footer = F::decode(&encoded_footer).map_err(PasetoError::PayloadError)?;
122
123        Ok(Self {
124            payload,
125            encoded_footer,
126            footer,
127            _message: PhantomData,
128            _version: PhantomData,
129            _purpose: PhantomData,
130        })
131    }
132}
133
134macro_rules! serde_str {
135    (
136        impl<$($ident:ident),*> $ty:ty
137        $(where
138            $($path:path: $bound:path,)*
139        )?
140        {
141            fn expecting() { $expecting:expr }
142        }
143    ) => {
144        #[cfg(feature = "serde")]
145        impl<$($ident),*> serde_core::Serialize for $ty
146        $(where
147            $($path: $bound,)*
148        )?
149        {
150            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
151            where
152                S: serde_core::Serializer,
153            {
154                serializer.collect_str(self)
155            }
156        }
157
158        #[cfg(feature = "serde")]
159        impl<'de, $($ident),*> serde_core::Deserialize<'de> for $ty
160        $(where
161            $($path: $bound,)*
162        )?
163        {
164            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
165            where
166                D: serde_core::Deserializer<'de>,
167            {
168                struct Visitor<$($ident),*>(core::marker::PhantomData<($($ident,)*)>);
169                impl<'de, $($ident),*> serde_core::de::Visitor<'de> for Visitor<$($ident),*>
170                $(where
171                    $($path: $bound,)*
172                )?
173                {
174                    type Value = $ty;
175
176                    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
177                        formatter.write_fmt($expecting)
178                    }
179
180                    fn visit_str<Err>(self, v: &str) -> Result<Self::Value, Err>
181                    where
182                        Err: serde_core::de::Error,
183                    {
184                        v.parse().map_err(Err::custom)
185                    }
186                }
187                deserializer.deserialize_str(Visitor(core::marker::PhantomData))
188            }
189        }
190    };
191}
192
193serde_str!(
194    impl<V, P, M, F> SealedToken<V, P, M, F>
195    where
196        V: version::Version,
197        P: version::Purpose,
198        M: Payload,
199        F: Footer,
200    {
201        fn expecting() {
202            format_args!("a \"{}{}\" paseto", V::HEADER, P::HEADER)
203        }
204    }
205);