tsukuyomi_session/backend/
cookie.rs

1use {
2    crate::{Backend, RawSession},
3    cookie::{Cookie, CookieBuilder},
4    serde_json,
5    std::{borrow::Cow, collections::HashMap, fmt, sync::Arc},
6    tsukuyomi::{
7        error::{Error, Result},
8        future::{Poll, TryFuture},
9        input::{Cookies, Input},
10    },
11};
12
13#[cfg(feature = "secure")]
14use cookie::Key;
15
16#[cfg(feature = "secure")]
17enum Security {
18    Plain,
19    Signed(Key),
20    Private(Key),
21}
22
23#[cfg(not(feature = "secure"))]
24enum Security {
25    Plain,
26}
27
28#[cfg_attr(tarpaulin, skip)]
29impl fmt::Debug for Security {
30    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31        match self {
32            Security::Plain => f.debug_tuple("Plain").finish(),
33            #[cfg(feature = "secure")]
34            Security::Signed(..) => f.debug_tuple("Signed").field(&"<secret key>").finish(),
35            #[cfg(feature = "secure")]
36            Security::Private(..) => f.debug_tuple("Private").field(&"<secret key>").finish(),
37        }
38    }
39}
40
41impl Security {
42    fn get(&self, name: &str, cookies: &mut Cookies<'_>) -> Result<Option<Cookie<'static>>> {
43        match self {
44            Security::Plain => Ok(cookies.jar()?.get(name).cloned()),
45            #[cfg(feature = "secure")]
46            Security::Signed(ref key) => Ok(cookies.signed_jar(key)?.get(name)),
47            #[cfg(feature = "secure")]
48            Security::Private(ref key) => Ok(cookies.private_jar(key)?.get(name)),
49        }
50    }
51
52    fn add(&self, cookie: Cookie<'static>, cookies: &mut Cookies<'_>) -> Result<()> {
53        match self {
54            Security::Plain => cookies.jar()?.add(cookie),
55            #[cfg(feature = "secure")]
56            Security::Signed(ref key) => cookies.signed_jar(key)?.add(cookie),
57            #[cfg(feature = "secure")]
58            Security::Private(ref key) => cookies.private_jar(key)?.add(cookie),
59        }
60        Ok(())
61    }
62}
63
64/// A `Backend` using a Cookie entry for storing the session data.
65#[derive(Debug, Clone)]
66pub struct CookieBackend {
67    inner: Arc<CookieBackendInner>,
68}
69
70impl CookieBackend {
71    fn new(security: Security) -> Self {
72        Self {
73            inner: Arc::new(CookieBackendInner {
74                security,
75                cookie_name: "tsukuyomi-session".into(),
76                builder: Box::new(|cookie| cookie),
77            }),
78        }
79    }
80
81    fn inner_mut(&mut self) -> &mut CookieBackendInner {
82        Arc::get_mut(&mut self.inner).expect("the instance has already shared")
83    }
84
85    /// Create a new `CookieBackend` that save uses the plain format.
86    pub fn plain() -> Self {
87        Self::new(Security::Plain)
88    }
89
90    /// Create a new `CookieBackend` that signs the cookie entry with the specified `Key`.
91    #[cfg(feature = "secure")]
92    pub fn signed(secret_key: Key) -> Self {
93        Self::new(Security::Signed(secret_key))
94    }
95
96    /// Create a new `CookieBackend` that encrypts the cookie entry with the specified `Key`.
97    #[cfg(feature = "secure")]
98    pub fn private(secret_key: Key) -> Self {
99        Self::new(Security::Private(secret_key))
100    }
101
102    /// Sets the name of Cookie entry to be used for storing the session data.
103    ///
104    /// The default value is `"tsukuyomi-session"`.
105    pub fn cookie_name(mut self, value: impl Into<Cow<'static, str>>) -> Self {
106        self.inner_mut().cookie_name = value.into();
107        self
108    }
109
110    /// Sets the functions for modifying the saved Cookie entry.
111    pub fn builder(
112        mut self,
113        builder: impl Fn(CookieBuilder) -> CookieBuilder + Send + Sync + 'static,
114    ) -> Self {
115        self.inner_mut().builder = Box::new(builder);
116        self
117    }
118}
119
120struct CookieBackendInner {
121    security: Security,
122    cookie_name: Cow<'static, str>,
123    builder: Box<dyn Fn(CookieBuilder) -> CookieBuilder + Send + Sync + 'static>,
124}
125
126#[cfg_attr(tarpaulin, skip)]
127impl fmt::Debug for CookieBackendInner {
128    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129        f.debug_struct("CookieBackendInner")
130            .field("security", &self.security)
131            .field("cookie_name", &self.cookie_name)
132            .finish()
133    }
134}
135
136impl CookieBackendInner {
137    fn deserialize(&self, s: &str) -> Result<HashMap<String, String>> {
138        serde_json::from_str(s).map_err(tsukuyomi::error::bad_request)
139    }
140
141    fn serialize(&self, map: &HashMap<String, String>) -> String {
142        serde_json::to_string(&map).expect("should be success")
143    }
144
145    fn read(&self, input: &mut Input<'_>) -> tsukuyomi::Result<Inner> {
146        match self.security.get(&*self.cookie_name, input.cookies)? {
147            Some(cookie) => {
148                let map = self.deserialize(cookie.value())?;
149                Ok(Inner::Some(map))
150            }
151            None => Ok(Inner::Empty),
152        }
153    }
154
155    fn write(&self, input: &mut Input<'_>, inner: Inner) -> tsukuyomi::Result<()> {
156        match inner {
157            Inner::Empty => {}
158            Inner::Some(map) => {
159                let value = self.serialize(&map);
160                let cookie =
161                    (self.builder)(Cookie::build(self.cookie_name.clone(), value)).finish();
162                self.security.add(cookie, input.cookies)?;
163            }
164            Inner::Clear => {
165                input
166                    .cookies
167                    .jar()?
168                    .remove(Cookie::named(self.cookie_name.clone()));
169            }
170        }
171
172        Ok(())
173    }
174}
175
176impl Backend for CookieBackend {
177    type Session = CookieSession;
178    type ReadError = Error;
179    type ReadSession = ReadSession;
180
181    fn read(&self) -> Self::ReadSession {
182        ReadSession(Some(self.clone()))
183    }
184}
185
186#[doc(hidden)]
187#[allow(missing_debug_implementations)]
188pub struct ReadSession(Option<CookieBackend>);
189
190impl TryFuture for ReadSession {
191    type Ok = CookieSession;
192    type Error = Error;
193
194    #[inline]
195    fn poll_ready(&mut self, input: &mut Input<'_>) -> Poll<Self::Ok, Self::Error> {
196        let backend = self.0.take().expect("the future has already been polled");
197        backend
198            .inner
199            .read(input)
200            .map(|inner| CookieSession { inner, backend }.into())
201    }
202}
203
204#[derive(Debug)]
205pub struct CookieSession {
206    inner: Inner,
207    backend: CookieBackend,
208}
209
210#[derive(Debug)]
211enum Inner {
212    Empty,
213    Some(HashMap<String, String>),
214    Clear,
215}
216
217impl RawSession for CookieSession {
218    type WriteSession = WriteSession;
219    type WriteError = Error;
220
221    fn get(&self, name: &str) -> Option<&str> {
222        match self.inner {
223            Inner::Some(ref map) => map.get(name).map(|s| &**s),
224            _ => None,
225        }
226    }
227
228    fn set(&mut self, name: &str, value: String) {
229        match self.inner {
230            Inner::Empty => {}
231            Inner::Some(ref mut map) => {
232                map.insert(name.to_owned(), value);
233                return;
234            }
235            Inner::Clear => return,
236        }
237
238        match std::mem::replace(&mut self.inner, Inner::Empty) {
239            Inner::Empty => {
240                self.inner = Inner::Some({
241                    let mut map = HashMap::new();
242                    map.insert(name.to_owned(), value);
243                    map
244                });
245            }
246            Inner::Some(..) | Inner::Clear => unreachable!(),
247        }
248    }
249
250    fn remove(&mut self, name: &str) {
251        if let Inner::Some(ref mut map) = self.inner {
252            map.remove(name);
253        }
254    }
255
256    fn clear(&mut self) {
257        self.inner = Inner::Clear;
258    }
259
260    fn write(self) -> Self::WriteSession {
261        WriteSession(Some(self))
262    }
263}
264
265#[doc(hidden)]
266#[allow(missing_debug_implementations)]
267pub struct WriteSession(Option<CookieSession>);
268
269impl TryFuture for WriteSession {
270    type Ok = ();
271    type Error = Error;
272
273    #[inline]
274    fn poll_ready(&mut self, input: &mut Input<'_>) -> Poll<Self::Ok, Self::Error> {
275        let session = self.0.take().expect("the future has already been polled");
276        session
277            .backend
278            .inner
279            .write(input, session.inner)
280            .map(Into::into)
281    }
282}