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}