From 16cb7297abeef3af03666247e2fbbf6d9aae683e Mon Sep 17 00:00:00 2001 From: Sergio Benitez Date: Wed, 8 Mar 2017 03:28:12 -0800 Subject: [PATCH] Initial session support. This commit includes the following additions: * A `session` example was added. * `Config::take_session_key` was removed. * If a `session_key` is not supplied, one is automatically generated. * The `Session` type implements signed, encrypted sessions. * A `Session` can be retrieved via its request guard. --- Cargo.toml | 1 + examples/config/tests/common/mod.rs | 3 - examples/session/Cargo.toml | 16 +++++ examples/session/Rocket.toml | 7 ++ examples/session/src/main.rs | 87 +++++++++++++++++++++++ examples/session/templates/index.html.hbs | 15 ++++ examples/session/templates/login.html.hbs | 24 +++++++ lib/Cargo.toml | 27 ++++--- lib/src/config/builder.rs | 2 - lib/src/config/config.rs | 83 +++++++++++---------- lib/src/config/mod.rs | 16 +++-- lib/src/http/cookies.rs | 34 ++++++--- lib/src/http/mod.rs | 4 +- lib/src/http/session.rs | 62 ++++++++++++++++ lib/src/request/from_request.rs | 14 +++- lib/src/request/request.rs | 60 +++++++++++----- lib/src/response/flash.rs | 10 +-- lib/src/rocket.rs | 20 +++--- 18 files changed, 370 insertions(+), 115 deletions(-) create mode 100644 examples/session/Cargo.toml create mode 100644 examples/session/Rocket.toml create mode 100644 examples/session/src/main.rs create mode 100644 examples/session/templates/index.html.hbs create mode 100644 examples/session/templates/login.html.hbs create mode 100644 lib/src/http/session.rs diff --git a/Cargo.toml b/Cargo.toml index 50fef2e7..3934e3aa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,5 +30,6 @@ members = [ "examples/pastebin", "examples/state", "examples/uuid", + "examples/session", # "examples/raw_sqlite", ] diff --git a/examples/config/tests/common/mod.rs b/examples/config/tests/common/mod.rs index 7517593d..43832aab 100644 --- a/examples/config/tests/common/mod.rs +++ b/examples/config/tests/common/mod.rs @@ -42,9 +42,6 @@ pub fn test_config(environment: Environment) { assert_eq!(config.extras().count(), 0); } } - - // Rocket `take`s the key, so this should always be `None`. - assert_eq!(config.take_session_key(), None); } pub fn test_hello() { diff --git a/examples/session/Cargo.toml b/examples/session/Cargo.toml new file mode 100644 index 00000000..d1f37b9d --- /dev/null +++ b/examples/session/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "session" +version = "0.0.1" +workspace = "../../" + +[dependencies] +rocket = { path = "../../lib" } +rocket_codegen = { path = "../../codegen" } + +[dependencies.rocket_contrib] +path = "../../contrib" +default-features = false +features = ["handlebars_templates"] + +[dev-dependencies] +rocket = { path = "../../lib", features = ["testing"] } diff --git a/examples/session/Rocket.toml b/examples/session/Rocket.toml new file mode 100644 index 00000000..d9c2fbaa --- /dev/null +++ b/examples/session/Rocket.toml @@ -0,0 +1,7 @@ +[staging] +session_key = "itlYmFR2vYKrOmFhupMIn/hyB6lYCCTXz4yaQX89XVg=" +address = "localhost" +port = 8000 + +[production] +session_key = "itlYmFR2vYKrOmFhupMIn/hyB6lYCCTXz4yaQX89XVg=" diff --git a/examples/session/src/main.rs b/examples/session/src/main.rs new file mode 100644 index 00000000..a94f98e2 --- /dev/null +++ b/examples/session/src/main.rs @@ -0,0 +1,87 @@ +#![feature(plugin, custom_derive, custom_attribute)] +#![plugin(rocket_codegen)] + +extern crate rocket_contrib; +extern crate rocket; + +use std::collections::HashMap; + +use rocket::Outcome; +use rocket::request::{self, Form, FlashMessage, FromRequest, Request}; +use rocket::response::{Redirect, Flash}; +use rocket::http::{Cookie, Session}; +use rocket_contrib::Template; + +#[derive(FromForm)] +struct Login { + username: String, + password: String +} + +#[derive(Debug)] +struct User(usize); + +impl<'a, 'r> FromRequest<'a, 'r> for User { + type Error = (); + + fn from_request(request: &'a Request<'r>) -> request::Outcome { + let user = request.session() + .get("user_id") + .and_then(|cookie| cookie.value().parse().ok()) + .map(|id| User(id)); + + match user { + Some(user) => Outcome::Success(user), + None => Outcome::Forward(()) + } + } +} + +#[post("/login", data = "")] +fn login(mut session: Session, login: Form) -> Flash { + if login.get().username == "Sergio" && login.get().password == "password" { + session.add(Cookie::new("user_id", 1.to_string())); + Flash::success(Redirect::to("/"), "Successfully logged in.") + } else { + Flash::error(Redirect::to("/login"), "Invalid username/password.") + } +} + +#[post("/logout")] +fn logout(mut session: Session) -> Flash { + session.remove(Cookie::named("user_id")); + Flash::success(Redirect::to("/login"), "Successfully logged out.") +} + +#[get("/login")] +fn login_user(_user: User) -> Redirect { + Redirect::to("/") +} + +#[get("/login", rank = 2)] +fn login_page(flash: Option) -> Template { + let mut context = HashMap::new(); + if let Some(ref msg) = flash { + context.insert("flash", msg.msg()); + } + + Template::render("login", &context) +} + +#[get("/")] +fn user_index(user: User) -> Template { + let mut context = HashMap::new(); + context.insert("user_id", user.0); + Template::render("index", &context) +} + +#[get("/", rank = 2)] +fn index() -> Redirect { + Redirect::to("/login") +} + +fn main() { + rocket::ignite() + .mount("/", routes![index, user_index, login, logout, login_user, login_page]) + .launch() +} diff --git a/examples/session/templates/index.html.hbs b/examples/session/templates/index.html.hbs new file mode 100644 index 00000000..f1c84d0b --- /dev/null +++ b/examples/session/templates/index.html.hbs @@ -0,0 +1,15 @@ + + + + + + Rocket: Session Example + + +

Rocket Session Example

+

Logged in with user ID {{ user_id }}.

+
+ +
+ + diff --git a/examples/session/templates/login.html.hbs b/examples/session/templates/login.html.hbs new file mode 100644 index 00000000..504a2bcc --- /dev/null +++ b/examples/session/templates/login.html.hbs @@ -0,0 +1,24 @@ + + + + + + Rocket: Sessions + + +

Rocket Session: Please Login

+ {{#if flash}} +

{{ flash }}

+ {{else}} +

Please login to continue.

+ {{/if}} + +
+ + + + +

+
+ + diff --git a/lib/Cargo.toml b/lib/Cargo.toml index e95db71d..e2a85b2f 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -14,21 +14,21 @@ license = "MIT/Apache-2.0" build = "build.rs" categories = ["web-programming::http-server"] -[dependencies] -term-painter = "^0.2" -log = "^0.3" -url = "^1" -hyper = { version = "0.10.4", default-features = false } -toml = { version = "^0.2", default-features = false } -num_cpus = "1" -state = "^0.2" -time = "^0.1" -memchr = "1" +[features] +testing = [] -# FIXME: session support should be optional +[dependencies] +term-painter = "0.2" +log = "0.3" +url = "1" +hyper = { version = "0.10.4", default-features = false } +toml = { version = "0.2", default-features = false } +num_cpus = "1" +state = "0.2" +time = "0.1" +memchr = "1" base64 = "0.4" -# FIXME: session support should be optional [dependencies.cookie] version = "^0.7" features = ["percent-encode", "secure"] @@ -40,6 +40,3 @@ rocket_codegen = { version = "0.2.2", path = "../codegen" } [build-dependencies] ansi_term = "^0.9" version_check = "^0.1" - -[features] -testing = [] diff --git a/lib/src/config/builder.rs b/lib/src/config/builder.rs index 36746c4f..18647b37 100644 --- a/lib/src/config/builder.rs +++ b/lib/src/config/builder.rs @@ -156,8 +156,6 @@ impl ConfigBuilder { /// let mut config = Config::build(Environment::Staging) /// .session_key(key) /// .unwrap(); - /// - /// assert!(config.take_session_key().is_some()); /// ``` pub fn session_key>(mut self, key: K) -> Self { self.session_key = Some(key.into()); diff --git a/lib/src/config/config.rs b/lib/src/config/config.rs index 1f48a8ac..e12554b6 100644 --- a/lib/src/config/config.rs +++ b/lib/src/config/config.rs @@ -1,7 +1,6 @@ use std::collections::HashMap; use std::net::{IpAddr, lookup_host}; use std::path::{Path, PathBuf}; -use std::sync::RwLock; use std::convert::AsRef; use std::fmt; use std::env; @@ -11,6 +10,29 @@ use config::{self, Value, ConfigBuilder, Environment, ConfigError}; use {num_cpus, base64}; use logger::LoggingLevel; +use http::Key; + +pub enum SessionKey { + Generated(Key), + Provided(Key) +} + +impl SessionKey { + #[inline] + pub fn kind(&self) -> &'static str { + match *self { + SessionKey::Generated(_) => "generated", + SessionKey::Provided(_) => "provided", + } + } + + #[inline] + fn inner(&self) -> &Key { + match *self { + SessionKey::Generated(ref key) | SessionKey::Provided(ref key) => key + } + } +} /// Structure for Rocket application configuration. /// @@ -44,7 +66,7 @@ pub struct Config { /// The path to the configuration file this config belongs to. pub config_path: PathBuf, /// The session key. - session_key: RwLock>>, + pub(crate) session_key: SessionKey, } macro_rules! parse { @@ -102,22 +124,17 @@ impl Config { Config::default(env, cwd.as_path().join("Rocket.custom.toml")) } - // Aliases to `default_for` before the method is removed. - pub(crate) fn default

(env: Environment, path: P) -> config::Result - where P: AsRef - { - #[allow(deprecated)] - Config::default_for(env, path) - } - /// Returns the default configuration for the environment `env` given that /// the configuration was stored at `config_path`. If `config_path` is not /// an absolute path, an `Err` of `ConfigError::BadFilePath` is returned. - #[deprecated(since="0.2", note="use the `new` or `build` methods instead")] - pub fn default_for

(env: Environment, config_path: P) -> config::Result + /// + /// # Panics + /// + /// Panics if randomness cannot be retrieved from the OS. + pub(crate) fn default

(env: Environment, path: P) -> config::Result where P: AsRef { - let config_path = config_path.as_ref().to_path_buf(); + let config_path = path.as_ref().to_path_buf(); if config_path.parent().is_none() { return Err(ConfigError::BadFilePath(config_path, "Configuration files must be rooted in a directory.")); @@ -126,6 +143,9 @@ impl Config { // Note: This may truncate if num_cpus::get() > u16::max. That's okay. let default_workers = ::std::cmp::max(num_cpus::get(), 2) as u16; + // Use a generated session key by default. + let key = SessionKey::Generated(Key::generate()); + Ok(match env { Development => { Config { @@ -134,7 +154,7 @@ impl Config { port: 8000, workers: default_workers, log_level: LoggingLevel::Normal, - session_key: RwLock::new(None), + session_key: key, extras: HashMap::new(), config_path: config_path, } @@ -146,7 +166,7 @@ impl Config { port: 80, workers: default_workers, log_level: LoggingLevel::Normal, - session_key: RwLock::new(None), + session_key: key, extras: HashMap::new(), config_path: config_path, } @@ -158,7 +178,7 @@ impl Config { port: 80, workers: default_workers, log_level: LoggingLevel::Critical, - session_key: RwLock::new(None), + session_key: key, extras: HashMap::new(), config_path: config_path, } @@ -348,7 +368,7 @@ impl Config { Err(_) => return Err(error) }; - self.session_key = RwLock::new(Some(bytes)); + self.session_key = SessionKey::Provided(Key::from_master(&bytes)); Ok(()) } @@ -425,33 +445,10 @@ impl Config { self.extras.iter().map(|(k, v)| (k.as_str(), v)) } - /// Moves the session key string out of the `self` Config, if there is one. - /// Because the value is moved out, subsequent calls will result in a return - /// value of `None`. - /// - /// # Example - /// - /// ```rust - /// use rocket::config::{Config, Environment}; - /// - /// // Create a new config with a session key. - /// let key = "8Xui8SN4mI+7egV/9dlfYYLGQJeEx4+DwmSQLwDVXJg="; - /// let config = Config::build(Environment::Staging) - /// .session_key(key) - /// .unwrap(); - /// - /// // Get the key for the first time. - /// let session_key = config.take_session_key(); - /// assert!(session_key.is_some()); - /// - /// // Try to get the key again. - /// let session_key_again = config.take_session_key(); - /// assert_eq!(session_key_again, None); - /// ``` + /// Retrieves the session key from `self`. #[inline] - pub fn take_session_key(&self) -> Option> { - let mut key = self.session_key.write().expect("couldn't lock session key"); - key.take() + pub(crate) fn session_key(&self) -> &Key { + self.session_key.inner() } /// Attempts to retrieve the extra named `name` as a string. diff --git a/lib/src/config/mod.rs b/lib/src/config/mod.rs index 1fc3c04a..6849f33e 100644 --- a/lib/src/config/mod.rs +++ b/lib/src/config/mod.rs @@ -63,27 +63,29 @@ //! port = 8000 //! workers = max(number_of_cpus, 2) //! log = "normal" +//! session_key = [randomly generated at launch] //! //! [staging] //! address = "0.0.0.0" //! port = 80 //! workers = max(number_of_cpus, 2) //! log = "normal" -//! # don't use this key! generate your own and keep it private! -//! session_key = "8Xui8SN4mI+7egV/9dlfYYLGQJeEx4+DwmSQLwDVXJg=" +//! session_key = [randomly generated at launch] //! //! [production] //! address = "0.0.0.0" //! port = 80 //! workers = max(number_of_cpus, 2) //! log = "critical" -//! # don't use this key! generate your own and keep it private! -//! session_key = "hPRYyVRiMyxpw5sBB1XeCMN1kFsDCqKvBi2QJxBVHQk=" +//! session_key = [randomly generated at launch] //! ``` //! -//! The `workers` parameter is computed by Rocket automatically; the value above -//! is not valid TOML syntax. When manually specifying the number of workers, -//! the value should be an integer: `workers = 10`. +//! The `workers` and `session_key` default parameters are computed by Rocket +//! automatically; the values above are not valid TOML syntax. When manually +//! specifying the number of workers, the value should be an integer: `workers = +//! 10`. When manually specifying the session key, the value should a 256-bit +//! base64 encoded string. Such a string can be generated with the `openssl` +//! command line tool: `openssl rand -base64 32`. //! //! The "global" pseudo-environment can be used to set and/or override //! configuration parameters globally. A parameter defined in a `[global]` table diff --git a/lib/src/http/cookies.rs b/lib/src/http/cookies.rs index 1371f156..cc655629 100644 --- a/lib/src/http/cookies.rs +++ b/lib/src/http/cookies.rs @@ -4,23 +4,34 @@ use std::cell::RefMut; pub use cookie::{Cookie, CookieJar, Iter, CookieBuilder, Delta}; +use cookie::{PrivateJar, Key}; + +impl<'a, 'c> From<&'a Cookie<'c>> for Header<'static> { + fn from(cookie: &Cookie) -> Header<'static> { + Header::new("Set-Cookie", cookie.encoded().to_string()) + } +} + #[derive(Debug)] pub enum Cookies<'a> { Jarred(RefMut<'a, CookieJar>), Empty(CookieJar) } -impl<'a> From> for Cookies<'a> { - fn from(jar: RefMut<'a, CookieJar>) -> Cookies<'a> { +impl<'a> Cookies<'a> { + pub(crate) fn new(jar: RefMut<'a, CookieJar>) -> Cookies<'a> { Cookies::Jarred(jar) } -} -impl<'a> Cookies<'a> { pub(crate) fn empty() -> Cookies<'static> { Cookies::Empty(CookieJar::new()) } + #[inline(always)] + pub(crate) fn parse_cookie(cookie_str: &str) -> Option> { + Cookie::parse_encoded(cookie_str).map(|c| c.into_owned()).ok() + } + pub fn get(&self, name: &str) -> Option<&Cookie<'static>> { match *self { Cookies::Jarred(ref jar) => jar.get(name), @@ -40,6 +51,13 @@ impl<'a> Cookies<'a> { } } + pub(crate) fn private(&mut self, key: &Key) -> PrivateJar { + match *self { + Cookies::Jarred(ref mut jar) => jar.private(key), + Cookies::Empty(ref mut jar) => jar.private(key) + } + } + pub fn iter(&self) -> Iter { match *self { Cookies::Jarred(ref jar) => jar.iter(), @@ -47,16 +65,10 @@ impl<'a> Cookies<'a> { } } - pub fn delta(&self) -> Delta { + pub(crate) fn delta(&self) -> Delta { match *self { Cookies::Jarred(ref jar) => jar.delta(), Cookies::Empty(ref jar) => jar.delta() } } } - -impl<'a, 'c> From<&'a Cookie<'c>> for Header<'static> { - fn from(cookie: &Cookie) -> Header<'static> { - Header::new("Set-Cookie", cookie.encoded().to_string()) - } -} diff --git a/lib/src/http/mod.rs b/lib/src/http/mod.rs index 7e5f3471..c015e328 100644 --- a/lib/src/http/mod.rs +++ b/lib/src/http/mod.rs @@ -9,6 +9,7 @@ pub mod hyper; pub mod uri; mod cookies; +mod session; mod method; mod content_type; mod status; @@ -23,4 +24,5 @@ pub use self::content_type::ContentType; pub use self::status::{Status, StatusClass}; pub use self::header::{Header, HeaderMap}; -pub use self::cookies::{Cookie, Cookies, CookieJar, CookieBuilder}; +pub use self::cookies::*; +pub use self::session::*; diff --git a/lib/src/http/session.rs b/lib/src/http/session.rs new file mode 100644 index 00000000..b21dc2c4 --- /dev/null +++ b/lib/src/http/session.rs @@ -0,0 +1,62 @@ +use std::cell::{RefCell, RefMut}; + +use cookie::{Cookie, CookieJar, Delta}; +pub use cookie::Key; + +use http::{Header, Cookies}; + +const SESSION_PREFIX: &'static str = "__sess_"; + +pub struct Session<'a> { + cookies: RefCell>, + key: &'a Key +} + +impl<'a> Session<'a> { + #[inline(always)] + pub(crate) fn new(jar: RefMut<'a, CookieJar>, key: &'a Key) -> Session<'a> { + Session { cookies: RefCell::new(Cookies::new(jar)), key: key } + } + + #[inline(always)] + pub(crate) fn empty(key: &'a Key) -> Session<'a> { + Session { cookies: RefCell::new(Cookies::empty()), key: key } + } + + #[inline(always)] + pub(crate) fn header_for(cookie: &Cookie) -> Header<'static> { + Header::new("Set-Cookie", format!("{}{}", SESSION_PREFIX, cookie)) + } + + #[inline(always)] + pub(crate) fn parse_cookie(cookie_str: &str) -> Option> { + if !cookie_str.starts_with(SESSION_PREFIX) { + return None; + } + + let string = cookie_str[SESSION_PREFIX.len()..].to_string(); + Cookie::parse(string).ok() + } + + pub fn get(&self, name: &str) -> Option> { + self.cookies.borrow_mut().private(&self.key).get(name) + } + + pub fn add(&mut self, mut cookie: Cookie<'static>) { + cookie.set_http_only(true); + if cookie.path().is_none() { + cookie.set_path("/"); + } + + self.cookies.get_mut().private(&self.key).add(cookie) + } + + pub fn remove(&mut self, cookie: Cookie<'static>) { + self.cookies.get_mut().private(&self.key).remove(cookie) + } + + #[inline(always)] + pub(crate) fn delta(&mut self) -> Delta { + self.cookies.get_mut().delta() + } +} diff --git a/lib/src/request/from_request.rs b/lib/src/request/from_request.rs index 2d9bc309..9ce5d925 100644 --- a/lib/src/request/from_request.rs +++ b/lib/src/request/from_request.rs @@ -5,7 +5,7 @@ use outcome::{self, IntoOutcome}; use request::Request; use outcome::Outcome::*; -use http::{Status, ContentType, Method, Cookies}; +use http::{Status, ContentType, Method, Cookies, Session}; use http::uri::URI; /// Type alias for the `Outcome` of a `FromRequest` conversion. @@ -85,11 +85,11 @@ impl IntoOutcome for Result { /// /// _This implementation always returns successfully._ /// -/// * **&Cookies** +/// * **Cookies** /// /// Returns a borrow to the [Cookies](/rocket/http/type.Cookies.html) in the /// incoming request. Note that `Cookies` implements internal mutability, so -/// a handle to `&Cookies` allows you to get _and_ set cookies in the +/// a handle to `Cookies` allows you to get _and_ set cookies in the /// request. /// /// _This implementation always returns successfully._ @@ -212,6 +212,14 @@ impl<'a, 'r> FromRequest<'a, 'r> for Cookies<'a> { } } +impl<'a, 'r> FromRequest<'a, 'r> for Session<'a> { + type Error = (); + + fn from_request(request: &'a Request<'r>) -> Outcome { + Success(request.session()) + } +} + impl<'a, 'r> FromRequest<'a, 'r> for ContentType { type Error = (); diff --git a/lib/src/request/request.rs b/lib/src/request/request.rs index a4e90608..cb7f6049 100644 --- a/lib/src/request/request.rs +++ b/lib/src/request/request.rs @@ -12,10 +12,7 @@ use super::{FromParam, FromSegments}; use router::Route; use http::uri::{URI, Segments}; -use http::{Method, ContentType, Header, HeaderMap, Cookie, Cookies}; - -use http::CookieJar; - +use http::{Method, ContentType, Header, HeaderMap, Cookies, Session, CookieJar, Key}; use http::hyper; /// The type of an incoming web request. @@ -28,10 +25,12 @@ use http::hyper; pub struct Request<'r> { method: Method, uri: URI<'r>, + key: Option<&'r Key>, headers: HeaderMap<'r>, remote: Option, params: RefCell>, cookies: RefCell, + session: RefCell, state: Option<&'r Container>, } @@ -54,9 +53,11 @@ impl<'r> Request<'r> { method: method, uri: uri.into(), headers: HeaderMap::new(), + key: None, remote: None, params: RefCell::new(Vec::new()), cookies: RefCell::new(CookieJar::new()), + session: RefCell::new(CookieJar::new()), state: None } } @@ -256,7 +257,7 @@ impl<'r> Request<'r> { #[inline] pub fn cookies(&self) -> Cookies { match self.cookies.try_borrow_mut() { - Ok(jar) => Cookies::from(jar), + Ok(jar) => Cookies::new(jar), Err(_) => { error_!("Multiple `Cookies` instances are active at once."); info_!("An instance of `Cookies` must be dropped before another \ @@ -267,12 +268,32 @@ impl<'r> Request<'r> { } } - /// Replace all of the cookies in `self` with `cookies`. + #[inline] + pub fn session(&self) -> Session { + match self.session.try_borrow_mut() { + Ok(jar) => Session::new(jar, self.key.unwrap()), + Err(_) => { + error_!("Multiple `Session` instances are active at once."); + info_!("An instance of `Session` must be dropped before another \ + can be retrieved."); + warn_!("The retrieved `Session` instance will be empty."); + Session::empty(self.key.unwrap()) + } + } + } + + /// Replace all of the cookies in `self` with those in `jar`. #[inline] pub(crate) fn set_cookies(&mut self, jar: CookieJar) { self.cookies = RefCell::new(jar); } + /// Replace all of the session cookie in `self` with those in `jar`. + #[inline] + pub(crate) fn set_session(&mut self, jar: CookieJar) { + self.session = RefCell::new(jar); + } + /// Returns `Some` of the Content-Type header of `self`. If the header is /// not present, returns `None`. /// @@ -407,6 +428,12 @@ impl<'r> Request<'r> { self.state = Some(state); } + /// Set the session key. For internal use only! + #[inline] + pub(crate) fn set_key(&mut self, key: &'r Key) { + self.key = Some(key); + } + /// Convert from Hyper types into a Rocket Request. pub(crate) fn from_hyp(h_method: hyper::Method, h_headers: hyper::header::Headers, @@ -428,26 +455,27 @@ impl<'r> Request<'r> { // Construct the request object. let mut request = Request::new(method, uri); - // Set the request cookies, if they exist. TODO: Use session key. + // Set the request cookies, if they exist. if let Some(cookie_headers) = h_headers.get_raw("Cookie") { - let mut jar = CookieJar::new(); + let mut cookie_jar = CookieJar::new(); + let mut session_jar = CookieJar::new(); for header in cookie_headers { let raw_str = match ::std::str::from_utf8(header) { Ok(string) => string, Err(_) => continue }; - for cookie_str in raw_str.split(";") { - let cookie = match Cookie::parse_encoded(cookie_str.to_string()) { - Ok(cookie) => cookie, - Err(_) => continue - }; - - jar.add_original(cookie); + for cookie_str in raw_str.split(";").map(|s| s.trim()) { + if let Some(cookie) = Session::parse_cookie(cookie_str) { + session_jar.add_original(cookie); + } else if let Some(cookie) = Cookies::parse_cookie(cookie_str) { + cookie_jar.add_original(cookie); + } } } - request.set_cookies(jar); + request.set_cookies(cookie_jar); + request.set_session(session_jar); } // Set the rest of the headers. diff --git a/lib/src/response/flash.rs b/lib/src/response/flash.rs index c0c5478f..14234cd4 100644 --- a/lib/src/response/flash.rs +++ b/lib/src/response/flash.rs @@ -223,10 +223,6 @@ impl<'a, 'r> FromRequest<'a, 'r> for Flash<()> { let r = request.cookies().get(FLASH_COOKIE_NAME).ok_or(()).and_then(|cookie| { trace_!("Flash: retrieving message: {:?}", cookie); - // Delete the flash cookie from the jar. - let orig_cookie = Cookie::build(FLASH_COOKIE_NAME, "").path("/").finish(); - request.cookies().remove(orig_cookie); - // Parse the flash message. let content = cookie.value(); let (len_str, rest) = match content.find(|c: char| !c.is_digit(10)) { @@ -239,6 +235,12 @@ impl<'a, 'r> FromRequest<'a, 'r> for Flash<()> { Ok(Flash::named(name, msg)) }); + // If we found a flash cookie, delete it from the jar. + if r.is_ok() { + let cookie = Cookie::build(FLASH_COOKIE_NAME, "").path("/").finish(); + request.cookies().remove(cookie); + } + r.into_outcome() } } diff --git a/lib/src/rocket.rs b/lib/src/rocket.rs index c225f681..fcda5707 100644 --- a/lib/src/rocket.rs +++ b/lib/src/rocket.rs @@ -20,7 +20,7 @@ use catcher::{self, Catcher}; use outcome::Outcome; use error::Error; -use http::{Method, Status, Header}; +use http::{Method, Status, Header, Session}; use http::hyper::{self, header}; use http::uri::URI; @@ -182,8 +182,9 @@ impl Rocket { -> Response<'r> { info!("{}:", request); - // Inform the request about the state. + // Inform the request about the state and session key. request.set_state(&self.state); + request.set_key(&self.config.session_key()); // Do a bit of preprocessing before routing. self.preprocess_request(request, &data); @@ -191,11 +192,16 @@ impl Rocket { // Route the request to get a response. match self.route(request, data) { Outcome::Success(mut response) => { - // A user's route responded! + // A user's route responded! Set the regular cookies. for cookie in request.cookies().delta() { response.adjoin_header(cookie); } + // And now the session cookies. + for cookie in request.session().delta() { + response.adjoin_header(Session::header_for(cookie)); + } + response } Outcome::Forward(data) => { @@ -342,13 +348,7 @@ impl Rocket { info_!("port: {}", White.paint(&config.port)); info_!("log: {}", White.paint(config.log_level)); info_!("workers: {}", White.paint(config.workers)); - - let session_key = config.take_session_key(); - if session_key.is_some() { - info_!("session key: {}", White.paint("present")); - warn_!("Signing and encryption of cookies is currently disabled."); - warn_!("See https://github.com/SergioBenitez/Rocket/issues/20 for info."); - } + info_!("session key: {}", White.paint(config.session_key.kind())); for (name, value) in config.extras() { info_!("{} {}: {}", Yellow.paint("[extra]"), name, White.paint(value));