mirror of https://github.com/rwf2/Rocket.git
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.
This commit is contained in:
parent
05a458942d
commit
16cb7297ab
|
@ -30,5 +30,6 @@ members = [
|
||||||
"examples/pastebin",
|
"examples/pastebin",
|
||||||
"examples/state",
|
"examples/state",
|
||||||
"examples/uuid",
|
"examples/uuid",
|
||||||
|
"examples/session",
|
||||||
# "examples/raw_sqlite",
|
# "examples/raw_sqlite",
|
||||||
]
|
]
|
||||||
|
|
|
@ -42,9 +42,6 @@ pub fn test_config(environment: Environment) {
|
||||||
assert_eq!(config.extras().count(), 0);
|
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() {
|
pub fn test_hello() {
|
||||||
|
|
|
@ -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"] }
|
|
@ -0,0 +1,7 @@
|
||||||
|
[staging]
|
||||||
|
session_key = "itlYmFR2vYKrOmFhupMIn/hyB6lYCCTXz4yaQX89XVg="
|
||||||
|
address = "localhost"
|
||||||
|
port = 8000
|
||||||
|
|
||||||
|
[production]
|
||||||
|
session_key = "itlYmFR2vYKrOmFhupMIn/hyB6lYCCTXz4yaQX89XVg="
|
|
@ -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<User, ()> {
|
||||||
|
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 = "<login>")]
|
||||||
|
fn login(mut session: Session, login: Form<Login>) -> Flash<Redirect> {
|
||||||
|
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<Redirect> {
|
||||||
|
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<FlashMessage>) -> 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()
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width" />
|
||||||
|
<title>Rocket: Session Example</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Rocket Session Example</h1>
|
||||||
|
<p>Logged in with user ID {{ user_id }}.</p>
|
||||||
|
<form action="/logout" method="post" accept-charset="utf-8">
|
||||||
|
<input type="submit" name="logout" id="logout" value="logout" />
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,24 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width" />
|
||||||
|
<title>Rocket: Sessions</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Rocket Session: Please Login</h1>
|
||||||
|
{{#if flash}}
|
||||||
|
<p>{{ flash }}</p>
|
||||||
|
{{else}}
|
||||||
|
<p>Please login to continue.</p>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
<form action="/login" method="post" accept-charset="utf-8">
|
||||||
|
<label for="username">username</label>
|
||||||
|
<input type="text" name="username" id="username" value="" />
|
||||||
|
<label for="password">password</label>
|
||||||
|
<input type="password" name="password" id="password" value="" />
|
||||||
|
<p><input type="submit" value="login"></p>
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -14,21 +14,21 @@ license = "MIT/Apache-2.0"
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
categories = ["web-programming::http-server"]
|
categories = ["web-programming::http-server"]
|
||||||
|
|
||||||
[dependencies]
|
[features]
|
||||||
term-painter = "^0.2"
|
testing = []
|
||||||
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"
|
|
||||||
|
|
||||||
# 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"
|
base64 = "0.4"
|
||||||
|
|
||||||
# FIXME: session support should be optional
|
|
||||||
[dependencies.cookie]
|
[dependencies.cookie]
|
||||||
version = "^0.7"
|
version = "^0.7"
|
||||||
features = ["percent-encode", "secure"]
|
features = ["percent-encode", "secure"]
|
||||||
|
@ -40,6 +40,3 @@ rocket_codegen = { version = "0.2.2", path = "../codegen" }
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
ansi_term = "^0.9"
|
ansi_term = "^0.9"
|
||||||
version_check = "^0.1"
|
version_check = "^0.1"
|
||||||
|
|
||||||
[features]
|
|
||||||
testing = []
|
|
||||||
|
|
|
@ -156,8 +156,6 @@ impl ConfigBuilder {
|
||||||
/// let mut config = Config::build(Environment::Staging)
|
/// let mut config = Config::build(Environment::Staging)
|
||||||
/// .session_key(key)
|
/// .session_key(key)
|
||||||
/// .unwrap();
|
/// .unwrap();
|
||||||
///
|
|
||||||
/// assert!(config.take_session_key().is_some());
|
|
||||||
/// ```
|
/// ```
|
||||||
pub fn session_key<K: Into<String>>(mut self, key: K) -> Self {
|
pub fn session_key<K: Into<String>>(mut self, key: K) -> Self {
|
||||||
self.session_key = Some(key.into());
|
self.session_key = Some(key.into());
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::net::{IpAddr, lookup_host};
|
use std::net::{IpAddr, lookup_host};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::RwLock;
|
|
||||||
use std::convert::AsRef;
|
use std::convert::AsRef;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::env;
|
use std::env;
|
||||||
|
@ -11,6 +10,29 @@ use config::{self, Value, ConfigBuilder, Environment, ConfigError};
|
||||||
|
|
||||||
use {num_cpus, base64};
|
use {num_cpus, base64};
|
||||||
use logger::LoggingLevel;
|
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.
|
/// Structure for Rocket application configuration.
|
||||||
///
|
///
|
||||||
|
@ -44,7 +66,7 @@ pub struct Config {
|
||||||
/// The path to the configuration file this config belongs to.
|
/// The path to the configuration file this config belongs to.
|
||||||
pub config_path: PathBuf,
|
pub config_path: PathBuf,
|
||||||
/// The session key.
|
/// The session key.
|
||||||
session_key: RwLock<Option<Vec<u8>>>,
|
pub(crate) session_key: SessionKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! parse {
|
macro_rules! parse {
|
||||||
|
@ -102,22 +124,17 @@ impl Config {
|
||||||
Config::default(env, cwd.as_path().join("Rocket.custom.toml"))
|
Config::default(env, cwd.as_path().join("Rocket.custom.toml"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Aliases to `default_for` before the method is removed.
|
|
||||||
pub(crate) fn default<P>(env: Environment, path: P) -> config::Result<Config>
|
|
||||||
where P: AsRef<Path>
|
|
||||||
{
|
|
||||||
#[allow(deprecated)]
|
|
||||||
Config::default_for(env, path)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the default configuration for the environment `env` given that
|
/// Returns the default configuration for the environment `env` given that
|
||||||
/// the configuration was stored at `config_path`. If `config_path` is not
|
/// the configuration was stored at `config_path`. If `config_path` is not
|
||||||
/// an absolute path, an `Err` of `ConfigError::BadFilePath` is returned.
|
/// 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<P>(env: Environment, config_path: P) -> config::Result<Config>
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if randomness cannot be retrieved from the OS.
|
||||||
|
pub(crate) fn default<P>(env: Environment, path: P) -> config::Result<Config>
|
||||||
where P: AsRef<Path>
|
where P: AsRef<Path>
|
||||||
{
|
{
|
||||||
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() {
|
if config_path.parent().is_none() {
|
||||||
return Err(ConfigError::BadFilePath(config_path,
|
return Err(ConfigError::BadFilePath(config_path,
|
||||||
"Configuration files must be rooted in a directory."));
|
"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.
|
// 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;
|
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 {
|
Ok(match env {
|
||||||
Development => {
|
Development => {
|
||||||
Config {
|
Config {
|
||||||
|
@ -134,7 +154,7 @@ impl Config {
|
||||||
port: 8000,
|
port: 8000,
|
||||||
workers: default_workers,
|
workers: default_workers,
|
||||||
log_level: LoggingLevel::Normal,
|
log_level: LoggingLevel::Normal,
|
||||||
session_key: RwLock::new(None),
|
session_key: key,
|
||||||
extras: HashMap::new(),
|
extras: HashMap::new(),
|
||||||
config_path: config_path,
|
config_path: config_path,
|
||||||
}
|
}
|
||||||
|
@ -146,7 +166,7 @@ impl Config {
|
||||||
port: 80,
|
port: 80,
|
||||||
workers: default_workers,
|
workers: default_workers,
|
||||||
log_level: LoggingLevel::Normal,
|
log_level: LoggingLevel::Normal,
|
||||||
session_key: RwLock::new(None),
|
session_key: key,
|
||||||
extras: HashMap::new(),
|
extras: HashMap::new(),
|
||||||
config_path: config_path,
|
config_path: config_path,
|
||||||
}
|
}
|
||||||
|
@ -158,7 +178,7 @@ impl Config {
|
||||||
port: 80,
|
port: 80,
|
||||||
workers: default_workers,
|
workers: default_workers,
|
||||||
log_level: LoggingLevel::Critical,
|
log_level: LoggingLevel::Critical,
|
||||||
session_key: RwLock::new(None),
|
session_key: key,
|
||||||
extras: HashMap::new(),
|
extras: HashMap::new(),
|
||||||
config_path: config_path,
|
config_path: config_path,
|
||||||
}
|
}
|
||||||
|
@ -348,7 +368,7 @@ impl Config {
|
||||||
Err(_) => return Err(error)
|
Err(_) => return Err(error)
|
||||||
};
|
};
|
||||||
|
|
||||||
self.session_key = RwLock::new(Some(bytes));
|
self.session_key = SessionKey::Provided(Key::from_master(&bytes));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -425,33 +445,10 @@ impl Config {
|
||||||
self.extras.iter().map(|(k, v)| (k.as_str(), v))
|
self.extras.iter().map(|(k, v)| (k.as_str(), v))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Moves the session key string out of the `self` Config, if there is one.
|
/// Retrieves the session key from `self`.
|
||||||
/// 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);
|
|
||||||
/// ```
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn take_session_key(&self) -> Option<Vec<u8>> {
|
pub(crate) fn session_key(&self) -> &Key {
|
||||||
let mut key = self.session_key.write().expect("couldn't lock session key");
|
self.session_key.inner()
|
||||||
key.take()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attempts to retrieve the extra named `name` as a string.
|
/// Attempts to retrieve the extra named `name` as a string.
|
||||||
|
|
|
@ -63,27 +63,29 @@
|
||||||
//! port = 8000
|
//! port = 8000
|
||||||
//! workers = max(number_of_cpus, 2)
|
//! workers = max(number_of_cpus, 2)
|
||||||
//! log = "normal"
|
//! log = "normal"
|
||||||
|
//! session_key = [randomly generated at launch]
|
||||||
//!
|
//!
|
||||||
//! [staging]
|
//! [staging]
|
||||||
//! address = "0.0.0.0"
|
//! address = "0.0.0.0"
|
||||||
//! port = 80
|
//! port = 80
|
||||||
//! workers = max(number_of_cpus, 2)
|
//! workers = max(number_of_cpus, 2)
|
||||||
//! log = "normal"
|
//! log = "normal"
|
||||||
//! # don't use this key! generate your own and keep it private!
|
//! session_key = [randomly generated at launch]
|
||||||
//! session_key = "8Xui8SN4mI+7egV/9dlfYYLGQJeEx4+DwmSQLwDVXJg="
|
|
||||||
//!
|
//!
|
||||||
//! [production]
|
//! [production]
|
||||||
//! address = "0.0.0.0"
|
//! address = "0.0.0.0"
|
||||||
//! port = 80
|
//! port = 80
|
||||||
//! workers = max(number_of_cpus, 2)
|
//! workers = max(number_of_cpus, 2)
|
||||||
//! log = "critical"
|
//! log = "critical"
|
||||||
//! # don't use this key! generate your own and keep it private!
|
//! session_key = [randomly generated at launch]
|
||||||
//! session_key = "hPRYyVRiMyxpw5sBB1XeCMN1kFsDCqKvBi2QJxBVHQk="
|
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! The `workers` parameter is computed by Rocket automatically; the value above
|
//! The `workers` and `session_key` default parameters are computed by Rocket
|
||||||
//! is not valid TOML syntax. When manually specifying the number of workers,
|
//! automatically; the values above are not valid TOML syntax. When manually
|
||||||
//! the value should be an integer: `workers = 10`.
|
//! 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
|
//! The "global" pseudo-environment can be used to set and/or override
|
||||||
//! configuration parameters globally. A parameter defined in a `[global]` table
|
//! configuration parameters globally. A parameter defined in a `[global]` table
|
||||||
|
|
|
@ -4,23 +4,34 @@ use std::cell::RefMut;
|
||||||
|
|
||||||
pub use cookie::{Cookie, CookieJar, Iter, CookieBuilder, Delta};
|
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)]
|
#[derive(Debug)]
|
||||||
pub enum Cookies<'a> {
|
pub enum Cookies<'a> {
|
||||||
Jarred(RefMut<'a, CookieJar>),
|
Jarred(RefMut<'a, CookieJar>),
|
||||||
Empty(CookieJar)
|
Empty(CookieJar)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<RefMut<'a, CookieJar>> for Cookies<'a> {
|
impl<'a> Cookies<'a> {
|
||||||
fn from(jar: RefMut<'a, CookieJar>) -> Cookies<'a> {
|
pub(crate) fn new(jar: RefMut<'a, CookieJar>) -> Cookies<'a> {
|
||||||
Cookies::Jarred(jar)
|
Cookies::Jarred(jar)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Cookies<'a> {
|
|
||||||
pub(crate) fn empty() -> Cookies<'static> {
|
pub(crate) fn empty() -> Cookies<'static> {
|
||||||
Cookies::Empty(CookieJar::new())
|
Cookies::Empty(CookieJar::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn parse_cookie(cookie_str: &str) -> Option<Cookie<'static>> {
|
||||||
|
Cookie::parse_encoded(cookie_str).map(|c| c.into_owned()).ok()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get(&self, name: &str) -> Option<&Cookie<'static>> {
|
pub fn get(&self, name: &str) -> Option<&Cookie<'static>> {
|
||||||
match *self {
|
match *self {
|
||||||
Cookies::Jarred(ref jar) => jar.get(name),
|
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 {
|
pub fn iter(&self) -> Iter {
|
||||||
match *self {
|
match *self {
|
||||||
Cookies::Jarred(ref jar) => jar.iter(),
|
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 {
|
match *self {
|
||||||
Cookies::Jarred(ref jar) => jar.delta(),
|
Cookies::Jarred(ref jar) => jar.delta(),
|
||||||
Cookies::Empty(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())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ pub mod hyper;
|
||||||
pub mod uri;
|
pub mod uri;
|
||||||
|
|
||||||
mod cookies;
|
mod cookies;
|
||||||
|
mod session;
|
||||||
mod method;
|
mod method;
|
||||||
mod content_type;
|
mod content_type;
|
||||||
mod status;
|
mod status;
|
||||||
|
@ -23,4 +24,5 @@ pub use self::content_type::ContentType;
|
||||||
pub use self::status::{Status, StatusClass};
|
pub use self::status::{Status, StatusClass};
|
||||||
pub use self::header::{Header, HeaderMap};
|
pub use self::header::{Header, HeaderMap};
|
||||||
|
|
||||||
pub use self::cookies::{Cookie, Cookies, CookieJar, CookieBuilder};
|
pub use self::cookies::*;
|
||||||
|
pub use self::session::*;
|
||||||
|
|
|
@ -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<Cookies<'a>>,
|
||||||
|
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<Cookie<'static>> {
|
||||||
|
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<Cookie<'static>> {
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,7 +5,7 @@ use outcome::{self, IntoOutcome};
|
||||||
use request::Request;
|
use request::Request;
|
||||||
use outcome::Outcome::*;
|
use outcome::Outcome::*;
|
||||||
|
|
||||||
use http::{Status, ContentType, Method, Cookies};
|
use http::{Status, ContentType, Method, Cookies, Session};
|
||||||
use http::uri::URI;
|
use http::uri::URI;
|
||||||
|
|
||||||
/// Type alias for the `Outcome` of a `FromRequest` conversion.
|
/// Type alias for the `Outcome` of a `FromRequest` conversion.
|
||||||
|
@ -85,11 +85,11 @@ impl<S, E> IntoOutcome<S, (Status, E), ()> for Result<S, E> {
|
||||||
///
|
///
|
||||||
/// _This implementation always returns successfully._
|
/// _This implementation always returns successfully._
|
||||||
///
|
///
|
||||||
/// * **&Cookies**
|
/// * **Cookies**
|
||||||
///
|
///
|
||||||
/// Returns a borrow to the [Cookies](/rocket/http/type.Cookies.html) in the
|
/// Returns a borrow to the [Cookies](/rocket/http/type.Cookies.html) in the
|
||||||
/// incoming request. Note that `Cookies` implements internal mutability, so
|
/// 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.
|
/// request.
|
||||||
///
|
///
|
||||||
/// _This implementation always returns successfully._
|
/// _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<Self, Self::Error> {
|
||||||
|
Success(request.session())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a, 'r> FromRequest<'a, 'r> for ContentType {
|
impl<'a, 'r> FromRequest<'a, 'r> for ContentType {
|
||||||
type Error = ();
|
type Error = ();
|
||||||
|
|
||||||
|
|
|
@ -12,10 +12,7 @@ use super::{FromParam, FromSegments};
|
||||||
|
|
||||||
use router::Route;
|
use router::Route;
|
||||||
use http::uri::{URI, Segments};
|
use http::uri::{URI, Segments};
|
||||||
use http::{Method, ContentType, Header, HeaderMap, Cookie, Cookies};
|
use http::{Method, ContentType, Header, HeaderMap, Cookies, Session, CookieJar, Key};
|
||||||
|
|
||||||
use http::CookieJar;
|
|
||||||
|
|
||||||
use http::hyper;
|
use http::hyper;
|
||||||
|
|
||||||
/// The type of an incoming web request.
|
/// The type of an incoming web request.
|
||||||
|
@ -28,10 +25,12 @@ use http::hyper;
|
||||||
pub struct Request<'r> {
|
pub struct Request<'r> {
|
||||||
method: Method,
|
method: Method,
|
||||||
uri: URI<'r>,
|
uri: URI<'r>,
|
||||||
|
key: Option<&'r Key>,
|
||||||
headers: HeaderMap<'r>,
|
headers: HeaderMap<'r>,
|
||||||
remote: Option<SocketAddr>,
|
remote: Option<SocketAddr>,
|
||||||
params: RefCell<Vec<(usize, usize)>>,
|
params: RefCell<Vec<(usize, usize)>>,
|
||||||
cookies: RefCell<CookieJar>,
|
cookies: RefCell<CookieJar>,
|
||||||
|
session: RefCell<CookieJar>,
|
||||||
state: Option<&'r Container>,
|
state: Option<&'r Container>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,9 +53,11 @@ impl<'r> Request<'r> {
|
||||||
method: method,
|
method: method,
|
||||||
uri: uri.into(),
|
uri: uri.into(),
|
||||||
headers: HeaderMap::new(),
|
headers: HeaderMap::new(),
|
||||||
|
key: None,
|
||||||
remote: None,
|
remote: None,
|
||||||
params: RefCell::new(Vec::new()),
|
params: RefCell::new(Vec::new()),
|
||||||
cookies: RefCell::new(CookieJar::new()),
|
cookies: RefCell::new(CookieJar::new()),
|
||||||
|
session: RefCell::new(CookieJar::new()),
|
||||||
state: None
|
state: None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -256,7 +257,7 @@ impl<'r> Request<'r> {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn cookies(&self) -> Cookies {
|
pub fn cookies(&self) -> Cookies {
|
||||||
match self.cookies.try_borrow_mut() {
|
match self.cookies.try_borrow_mut() {
|
||||||
Ok(jar) => Cookies::from(jar),
|
Ok(jar) => Cookies::new(jar),
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
error_!("Multiple `Cookies` instances are active at once.");
|
error_!("Multiple `Cookies` instances are active at once.");
|
||||||
info_!("An instance of `Cookies` must be dropped before another \
|
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]
|
#[inline]
|
||||||
pub(crate) fn set_cookies(&mut self, jar: CookieJar) {
|
pub(crate) fn set_cookies(&mut self, jar: CookieJar) {
|
||||||
self.cookies = RefCell::new(jar);
|
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
|
/// Returns `Some` of the Content-Type header of `self`. If the header is
|
||||||
/// not present, returns `None`.
|
/// not present, returns `None`.
|
||||||
///
|
///
|
||||||
|
@ -407,6 +428,12 @@ impl<'r> Request<'r> {
|
||||||
self.state = Some(state);
|
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.
|
/// Convert from Hyper types into a Rocket Request.
|
||||||
pub(crate) fn from_hyp(h_method: hyper::Method,
|
pub(crate) fn from_hyp(h_method: hyper::Method,
|
||||||
h_headers: hyper::header::Headers,
|
h_headers: hyper::header::Headers,
|
||||||
|
@ -428,26 +455,27 @@ impl<'r> Request<'r> {
|
||||||
// Construct the request object.
|
// Construct the request object.
|
||||||
let mut request = Request::new(method, uri);
|
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") {
|
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 {
|
for header in cookie_headers {
|
||||||
let raw_str = match ::std::str::from_utf8(header) {
|
let raw_str = match ::std::str::from_utf8(header) {
|
||||||
Ok(string) => string,
|
Ok(string) => string,
|
||||||
Err(_) => continue
|
Err(_) => continue
|
||||||
};
|
};
|
||||||
|
|
||||||
for cookie_str in raw_str.split(";") {
|
for cookie_str in raw_str.split(";").map(|s| s.trim()) {
|
||||||
let cookie = match Cookie::parse_encoded(cookie_str.to_string()) {
|
if let Some(cookie) = Session::parse_cookie(cookie_str) {
|
||||||
Ok(cookie) => cookie,
|
session_jar.add_original(cookie);
|
||||||
Err(_) => continue
|
} else if let Some(cookie) = Cookies::parse_cookie(cookie_str) {
|
||||||
};
|
cookie_jar.add_original(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.
|
// Set the rest of the headers.
|
||||||
|
|
|
@ -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| {
|
let r = request.cookies().get(FLASH_COOKIE_NAME).ok_or(()).and_then(|cookie| {
|
||||||
trace_!("Flash: retrieving message: {:?}", 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.
|
// Parse the flash message.
|
||||||
let content = cookie.value();
|
let content = cookie.value();
|
||||||
let (len_str, rest) = match content.find(|c: char| !c.is_digit(10)) {
|
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))
|
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()
|
r.into_outcome()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ use catcher::{self, Catcher};
|
||||||
use outcome::Outcome;
|
use outcome::Outcome;
|
||||||
use error::Error;
|
use error::Error;
|
||||||
|
|
||||||
use http::{Method, Status, Header};
|
use http::{Method, Status, Header, Session};
|
||||||
use http::hyper::{self, header};
|
use http::hyper::{self, header};
|
||||||
use http::uri::URI;
|
use http::uri::URI;
|
||||||
|
|
||||||
|
@ -182,8 +182,9 @@ impl Rocket {
|
||||||
-> Response<'r> {
|
-> Response<'r> {
|
||||||
info!("{}:", request);
|
info!("{}:", request);
|
||||||
|
|
||||||
// Inform the request about the state.
|
// Inform the request about the state and session key.
|
||||||
request.set_state(&self.state);
|
request.set_state(&self.state);
|
||||||
|
request.set_key(&self.config.session_key());
|
||||||
|
|
||||||
// Do a bit of preprocessing before routing.
|
// Do a bit of preprocessing before routing.
|
||||||
self.preprocess_request(request, &data);
|
self.preprocess_request(request, &data);
|
||||||
|
@ -191,11 +192,16 @@ impl Rocket {
|
||||||
// Route the request to get a response.
|
// Route the request to get a response.
|
||||||
match self.route(request, data) {
|
match self.route(request, data) {
|
||||||
Outcome::Success(mut response) => {
|
Outcome::Success(mut response) => {
|
||||||
// A user's route responded!
|
// A user's route responded! Set the regular cookies.
|
||||||
for cookie in request.cookies().delta() {
|
for cookie in request.cookies().delta() {
|
||||||
response.adjoin_header(cookie);
|
response.adjoin_header(cookie);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// And now the session cookies.
|
||||||
|
for cookie in request.session().delta() {
|
||||||
|
response.adjoin_header(Session::header_for(cookie));
|
||||||
|
}
|
||||||
|
|
||||||
response
|
response
|
||||||
}
|
}
|
||||||
Outcome::Forward(data) => {
|
Outcome::Forward(data) => {
|
||||||
|
@ -342,13 +348,7 @@ impl Rocket {
|
||||||
info_!("port: {}", White.paint(&config.port));
|
info_!("port: {}", White.paint(&config.port));
|
||||||
info_!("log: {}", White.paint(config.log_level));
|
info_!("log: {}", White.paint(config.log_level));
|
||||||
info_!("workers: {}", White.paint(config.workers));
|
info_!("workers: {}", White.paint(config.workers));
|
||||||
|
info_!("session key: {}", White.paint(config.session_key.kind()));
|
||||||
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.");
|
|
||||||
}
|
|
||||||
|
|
||||||
for (name, value) in config.extras() {
|
for (name, value) in config.extras() {
|
||||||
info_!("{} {}: {}", Yellow.paint("[extra]"), name, White.paint(value));
|
info_!("{} {}: {}", Yellow.paint("[extra]"), name, White.paint(value));
|
||||||
|
|
Loading…
Reference in New Issue