pemmican/plugins/
session.rs

1
2use futures::Future;
3use plugins::{Plugin, PluginData};
4use hyper::header::Cookie as CookieHeader;
5use hyper::header::SetCookie;
6use cookie::Cookie;
7use textnonce::TextNonce;
8
9header! { (Dnt, "Dnt") => [String] }
10header! { (Tk, "Tk") => [String] }
11
12/// This plugin implements sessions. Sessions associate subsequent requests with
13/// earlier requests. Sessions are maintained automatically by always setting a
14/// cookie initially and finding it again on subsequent requests.
15///
16/// This plugin only manages the cookie and maintains PluginData.session_id.
17/// Associating data with that session_id is left up to the consumer of this library
18/// (hint: Store it in your shared state, the S type parameter on Pemmican, perhaps
19/// with a CHashMap)
20///
21/// Plug this in before main content handling plugins
22pub struct Session {
23    cookie_name: String,
24    secure: bool,
25    http_only: bool,
26    respect_dnt_ad_absurdum: bool,
27}
28
29impl Session
30{
31    /// Create the Session plugin.
32    ///
33    /// `cookie_name` is the name of the cookie (e.g. PHP uses PHP_SESS_ID)
34    ///
35    /// `secure` is whether or not to allow transmission of the cookie over HTTP (without SSL)
36    ///
37    /// `http_only` is whether or not to restrict the cookie to the HTTP protocol (or else
38    /// allow javascript to access it)
39    pub fn new(cookie_name: String, secure: bool, http_only: bool) -> Session {
40        Session {
41            cookie_name: cookie_name,
42            secure: secure,
43            http_only: http_only,
44            respect_dnt_ad_absurdum: false,
45        }
46    }
47
48    /// If you set this, then clients setting the "DNT: 1" HTTP header will be unable
49    /// to get sessions (using a cookie and checking it later is, strictly speaking,
50    /// tracking).
51    pub fn respect_dnt_ad_absurdum(&mut self) {
52        self.respect_dnt_ad_absurdum = true;
53    }
54}
55
56impl<S,E> Plugin<S,E> for Session
57    where S: 'static, E: 'static
58{
59    fn handle(&self, mut data: PluginData<S>)
60              -> Box<Future<Item = PluginData<S>, Error = E>>
61    {
62        if self.respect_dnt_ad_absurdum {
63            // Respect Dnt
64            let mut dnt = false;
65            if let Some(header) = data.request.headers().get::<Dnt>() {
66                match *header {
67                    Dnt(ref s) => {
68                        if &*s != "0" {
69                            dnt = true;
70                        }
71                    },
72                }
73            }
74            if dnt {
75                // The user has requested Do Not Track.  We strictly comply by removing
76                // any existing session and refusing to start one while this header is
77                // present
78                data.session_id = None;
79
80                // Set the Tk header, informing them that we are not tracking
81                data.response.headers_mut().set(Tk("N".to_owned()));
82
83                return Box::new(::futures::future::ok(data));
84            }
85        }
86
87        let mut maybe_key: Option<String> = None;
88        if let Some(cookie_header) = data.request.headers().get::<CookieHeader>() {
89            if let Some(cookie_value) = cookie_header.get(&*self.cookie_name) {
90                maybe_key = Some(cookie_value.to_owned());
91            }
92        }
93
94        if let Some(key) = maybe_key {
95            // Associate existing session
96            data.session_id = Some(key.to_owned());
97            return Box::new(::futures::future::ok(data));
98        }
99
100        // Create new session
101        let key = TextNonce::new().into_string();
102        data.session_id = Some(key.clone());
103
104        // Create the cookie
105        let mut cookie = Cookie::new(self.cookie_name.clone(), key);
106        // expiry defaults to 'on close'
107        // max_age defaults to None
108        cookie.set_path("/"); // force a root path
109        cookie.set_secure(self.secure);
110        cookie.set_http_only(self.http_only);
111
112        // Set the cookie
113        data.response.headers_mut().set(
114            SetCookie(vec![ cookie.to_string() ]));
115
116        // Pass data on through
117        Box::new(::futures::future::ok(data))
118    }
119}