finchers_session/
cookie.rs

1//! The session backend using Cookie as a session storage.
2//!
3//! # Example
4//!
5//! ```
6//! #[macro_use]
7//! extern crate finchers;
8//! extern crate finchers_session;
9//!
10//! use finchers::prelude::*;
11//! use finchers_session::Session;
12//! use finchers_session::cookie::CookieSession;
13//!
14//! # fn main() {
15//! let backend = finchers_session::cookie::plain();
16//!
17//! let endpoint = path!(@get /)
18//!     .and(backend)
19//!     .and_then(|session: Session<CookieSession>| {
20//!         session.with(|_session| {
21//!             // ...
22//! #           Ok("done")
23//!         })
24//!     });
25//! # drop(move || finchers::server::start(endpoint).serve("127.0.0.1:4000"));
26//! # }
27//! ```
28
29extern crate cookie;
30
31use finchers::endpoint::{ApplyContext, ApplyResult, Endpoint};
32use finchers::error::Error;
33use finchers::input::Input;
34
35#[cfg(feature = "secure")]
36use self::cookie::Key;
37use self::cookie::{Cookie, SameSite};
38use futures::future;
39use std::borrow::Cow;
40use std::fmt;
41use std::sync::Arc;
42use time::Duration;
43
44use session::{RawSession, Session};
45use util::BuilderExt;
46
47// TODOs:
48// * add support for setting whether to compress data
49
50/// Create a `CookieSessionBackend` without signing and encryption.
51///
52/// This function is equivalent to `CookieSessionBackend::plain()`.
53pub fn plain() -> CookieBackend {
54    CookieBackend::plain()
55}
56
57/// Create a `CookieSessionBackend` with signing
58/// (requires `feature = "secure"`).
59///
60/// This function is equivalent to `CookieSessionBackend::signed(Key::from_master(key.as_ref()))`.
61#[cfg(feature = "secure")]
62pub fn signed(master: impl AsRef<[u8]>) -> CookieBackend {
63    CookieBackend::signed(Key::from_master(master.as_ref()))
64}
65
66/// Create a `CookieSessionBackend` with encryption
67/// (requires `feature = "secure"`).
68///
69/// This function is equivalent to `CookieSessionBackend::private(Key::from_master(key.as_ref()))`.
70#[cfg(feature = "secure")]
71pub fn private(master: impl AsRef<[u8]>) -> CookieBackend {
72    CookieBackend::private(Key::from_master(master.as_ref()))
73}
74
75enum Security {
76    Plain,
77    #[cfg(feature = "secure")]
78    Signed(Key),
79    #[cfg(feature = "secure")]
80    Private(Key),
81}
82
83impl fmt::Debug for Security {
84    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85        match self {
86            Security::Plain => f.debug_tuple("Plain").finish(),
87            #[cfg(feature = "secure")]
88            Security::Signed(..) => f.debug_tuple("Signed").finish(),
89            #[cfg(feature = "secure")]
90            Security::Private(..) => f.debug_tuple("Private").finish(),
91        }
92    }
93}
94
95#[derive(Debug)]
96struct CookieConfig {
97    security: Security,
98    name: String,
99    path: Cow<'static, str>,
100    secure: bool,
101    http_only: bool,
102    domain: Option<Cow<'static, str>>,
103    same_site: Option<SameSite>,
104    max_age: Option<Duration>,
105}
106
107impl CookieConfig {
108    fn read_value(&self, input: &mut Input) -> Result<Option<String>, Error> {
109        let jar = input.cookies()?;
110        let cookie = match self.security {
111            Security::Plain => jar.get(&self.name).cloned(),
112            #[cfg(feature = "secure")]
113            Security::Signed(ref key) => jar.signed(key).get(&self.name),
114            #[cfg(feature = "secure")]
115            Security::Private(ref key) => jar.private(key).get(&self.name),
116        };
117
118        match cookie {
119            Some(cookie) => Ok(Some(cookie.value().to_string())),
120            None => Ok(None),
121        }
122    }
123
124    fn write_value(&self, input: &mut Input, value: String) -> Result<(), Error> {
125        let cookie = Cookie::build(self.name.clone(), value)
126            .path(self.path.clone())
127            .secure(self.secure)
128            .http_only(self.http_only)
129            .if_some(self.domain.clone(), |cookie, value| cookie.domain(value))
130            .if_some(self.same_site, |cookie, value| cookie.same_site(value))
131            .if_some(self.max_age, |cookie, value| cookie.max_age(value))
132            .finish();
133
134        let jar = input.cookies()?;
135        match self.security {
136            Security::Plain => jar.add(cookie),
137            #[cfg(feature = "secure")]
138            Security::Signed(ref key) => jar.signed(key).add(cookie),
139            #[cfg(feature = "secure")]
140            Security::Private(ref key) => jar.private(key).add(cookie),
141        }
142
143        Ok(())
144    }
145
146    fn remove_value(&self, input: &mut Input) -> Result<(), Error> {
147        let cookie = Cookie::named(self.name.clone());
148        let jar = input.cookies()?;
149        match self.security {
150            Security::Plain => jar.remove(cookie),
151            #[cfg(feature = "secure")]
152            Security::Signed(ref key) => jar.signed(key).remove(cookie),
153            #[cfg(feature = "secure")]
154            Security::Private(ref key) => jar.private(key).remove(cookie),
155        }
156        Ok(())
157    }
158}
159
160#[allow(missing_docs)]
161#[derive(Debug, Clone)]
162pub struct CookieBackend {
163    config: Arc<CookieConfig>,
164}
165
166impl CookieBackend {
167    fn new(security: Security) -> CookieBackend {
168        CookieBackend {
169            config: Arc::new(CookieConfig {
170                security,
171                name: "finchers-session".into(),
172                path: "/".into(),
173                domain: None,
174                same_site: None,
175                max_age: None,
176                secure: true,
177                http_only: true,
178            }),
179        }
180    }
181
182    /// Creates a `CookieSessionBackend` which stores the Cookie values as a raw form.
183    pub fn plain() -> CookieBackend {
184        CookieBackend::new(Security::Plain)
185    }
186
187    /// Creates a `CookieSessionBackend` which signs the Cookie values with the specified secret key.
188    ///
189    /// This method is only available if the feature flag `secure` is set.
190    #[cfg(feature = "secure")]
191    pub fn signed(key: Key) -> CookieBackend {
192        CookieBackend::new(Security::Signed(key))
193    }
194
195    /// Creates a `CookieSessionBackend` which encrypts the Cookie values with the specified secret key.
196    ///
197    /// This method is only available if the feature flag `secure` is set.
198    #[cfg(feature = "secure")]
199    pub fn private(key: Key) -> CookieBackend {
200        CookieBackend::new(Security::Private(key))
201    }
202
203    fn config_mut(&mut self) -> &mut CookieConfig {
204        Arc::get_mut(&mut self.config).expect("The instance has already shared.")
205    }
206
207    /// Sets the path of Cookie entry.
208    ///
209    /// The default value is `"/"`.
210    pub fn path(mut self, value: impl Into<Cow<'static, str>>) -> CookieBackend {
211        self.config_mut().path = value.into();
212        self
213    }
214
215    /// Sets the value of `secure` in Cookie entry.
216    ///
217    /// The default value is `true`.
218    pub fn secure(mut self, value: bool) -> CookieBackend {
219        self.config_mut().secure = value;
220        self
221    }
222
223    /// Sets the value of `http_only` in Cookie entry.
224    ///
225    /// The default value is `true`.
226    pub fn http_only(mut self, value: bool) -> CookieBackend {
227        self.config_mut().http_only = value;
228        self
229    }
230
231    /// Sets the value of `domain` in Cookie entry.
232    ///
233    /// The default value is `None`.
234    pub fn domain(mut self, value: impl Into<Cow<'static, str>>) -> CookieBackend {
235        self.config_mut().domain = Some(value.into());
236        self
237    }
238
239    /// Sets the value of `same_site` in Cookie entry.
240    ///
241    /// The default value is `None`.
242    pub fn same_site(mut self, value: SameSite) -> CookieBackend {
243        self.config_mut().same_site = Some(value);
244        self
245    }
246
247    /// Sets the value of `max_age` in Cookie entry.
248    ///
249    /// The default value is `None`.
250    pub fn max_age(mut self, value: Duration) -> CookieBackend {
251        self.config_mut().max_age = Some(value);
252        self
253    }
254}
255
256impl<'a> Endpoint<'a> for CookieBackend {
257    type Output = (Session<CookieSession>,);
258    type Future = future::FutureResult<Self::Output, Error>;
259
260    fn apply(&self, cx: &mut ApplyContext<'_>) -> ApplyResult<Self::Future> {
261        Ok(future::result(self.config.read_value(cx.input()).map(
262            |value| {
263                (Session::new(CookieSession {
264                    config: self.config.clone(),
265                    value,
266                }),)
267            },
268        )))
269    }
270}
271
272#[allow(missing_docs)]
273#[derive(Debug)]
274pub struct CookieSession {
275    config: Arc<CookieConfig>,
276    value: Option<String>,
277}
278
279impl CookieSession {
280    fn write_impl(self, input: &mut Input) -> Result<(), Error> {
281        if let Some(value) = self.value {
282            self.config.write_value(input, value)
283        } else {
284            self.config.remove_value(input)
285        }
286    }
287}
288
289impl RawSession for CookieSession {
290    type WriteFuture = future::FutureResult<(), Error>;
291
292    fn get(&self) -> Option<&str> {
293        self.value.as_ref().map(|s| s.as_str())
294    }
295
296    fn set(&mut self, value: String) {
297        self.value = Some(value);
298    }
299
300    fn remove(&mut self) {
301        self.value = None;
302    }
303
304    fn write(self, input: &mut Input) -> Self::WriteFuture {
305        future::result(self.write_impl(input))
306    }
307}