| // Copyright (c) 2016 The Rouille developers | |
| // Licensed under the Apache License, Version 2.0 | |
| // <LICENSE-APACHE or | |
| // http://www.apache.org/licenses/LICENSE-2.0> or the MIT | |
| // license <LICENSE-MIT or http://opensource.org/licenses/MIT>, | |
| // at your option. All files in the project carrying such | |
| // notice may not be copied, modified, or distributed except | |
| // according to those terms. | |
| //! Sessions handling. | |
| //! | |
| //! The main feature of this module is the `session` function which handles a session. This | |
| //! function guarantees that a single unique identifier is assigned to each client. This identifier | |
| //! is accessible through the parameter passed to the inner closure. | |
| //! | |
| //! # Basic example | |
| //! | |
| //! Here is a basic example showing how to get a session ID. | |
| //! | |
| //! ``` | |
| //! use rouille::Request; | |
| //! use rouille::Response; | |
| //! use rouille::session; | |
| //! | |
| //! fn handle_request(request: &Request) -> Response { | |
| //! session::session(request, "SID", 3600, |session| { | |
| //! let id: &str = session.id(); | |
| //! | |
| //! // This id is unique to each client. | |
| //! | |
| //! Response::text(format!("Session ID: {}", id)) | |
| //! }) | |
| //! } | |
| //! ``` | |
| use std::borrow::Cow; | |
| use std::sync::atomic::AtomicBool; | |
| use std::sync::atomic::Ordering; | |
| use rand; | |
| use rand::Rng; | |
| use rand::distributions::Alphanumeric; | |
| use Request; | |
| use Response; | |
| use input; | |
| pub fn session<'r, F>(request: &'r Request, cookie_name: &str, timeout_s: u64, inner: F) -> Response | |
| where F: FnOnce(&Session<'r>) -> Response | |
| { | |
| let mut cookie = input::cookies(request).into_iter(); | |
| let cookie = cookie.find(|&(ref k, _)| k == &cookie_name); | |
| let cookie = cookie.map(|(_, v)| v); | |
| let session = if let Some(cookie) = cookie { | |
| Session { | |
| key_was_retreived: AtomicBool::new(false), | |
| key_was_given: true, | |
| key: cookie.into(), | |
| } | |
| } else { | |
| Session { | |
| key_was_retreived: AtomicBool::new(false), | |
| key_was_given: false, | |
| key: generate_session_id().into(), | |
| } | |
| }; | |
| let mut response = inner(&session); | |
| if session.key_was_retreived.load(Ordering::Relaxed) { // TODO: use `get_mut()` | |
| // FIXME: correct interactions with existing headers | |
| // TODO: allow setting domain | |
| let header_value = format!("{}={}; Max-Age={}; Path=/; HttpOnly", | |
| cookie_name, session.key, timeout_s); | |
| response.headers.push(("Set-Cookie".into(), header_value.into())); | |
| } | |
| response | |
| } | |
| /// Contains the ID of the session. | |
| pub struct Session<'r> { | |
| key_was_retreived: AtomicBool, | |
| key_was_given: bool, | |
| key: Cow<'r, str>, | |
| } | |
| impl<'r> Session<'r> { | |
| /// Returns true if the client gave us a session ID. | |
| /// | |
| /// If this returns false, then we are sure that no data is available. | |
| #[inline] | |
| pub fn client_has_sid(&self) -> bool { | |
| self.key_was_given | |
| } | |
| /// Returns the id of the session. | |
| #[inline] | |
| pub fn id(&self) -> &str { | |
| self.key_was_retreived.store(true, Ordering::Relaxed); | |
| &self.key | |
| } | |
| /*/// Generates a new id. This modifies the value returned by `id()`. | |
| // TODO: implement | |
| #[inline] | |
| pub fn regenerate_id(&self) { | |
| unimplemented!() | |
| }*/ | |
| } | |
| /// Generates a string suitable for a session ID. | |
| /// | |
| /// The output string doesn't contain any punctuation or character such as quotes or brackets | |
| /// that could need to be escaped. | |
| pub fn generate_session_id() -> String { | |
| // 5e+114 possibilities is reasonable. | |
| rand::OsRng::new().expect("Failed to initialize OsRng") // TODO: <- handle that? | |
| .sample_iter(&Alphanumeric) | |
| .filter(|&c| (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || | |
| (c >= '0' && c <= '9')) | |
| .take(64).collect::<String>() | |
| } |