Rocket

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

commit 53758c6dd7453c906f134b12112a1c4702ab0277
parent b16269a30ef9ae9d51878bfd8c6a84622360640d
Author: Linus Unneb├Ąck <linus@folkdatorn.se>
Date:   Mon,  5 Nov 2018 20:29:03 +0000

Introduce the 'private-cookies' feature.

Diffstat:
M.travis.yml | 1+
Mcore/http/Cargo.toml | 3++-
Mcore/http/src/cookies.rs | 173+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Mcore/http/src/lib.rs | 3++-
Mcore/lib/Cargo.toml | 1+
Mcore/lib/src/config/config.rs | 14+++++++++++++-
Mcore/lib/src/config/custom_values.rs | 6+++++-
Mcore/lib/src/local/request.rs | 3+++
Mcore/lib/src/request/request.rs | 3+++
Mcore/lib/src/rocket.rs | 7+++++--
Mcore/lib/tests/local_request_private_cookie-issue-368.rs | 2++
Mexamples/session/Cargo.toml | 2+-
Mscripts/test.sh | 20++++++++++++++++++++
Msite/guide/4-requests.md | 9+++++++++
14 files changed, 162 insertions(+), 85 deletions(-)

diff --git a/.travis.yml b/.travis.yml @@ -6,6 +6,7 @@ env: - TEST_FLAGS= - TEST_FLAGS=--release - TEST_FLAGS=--contrib + - TEST_FLAGS=--core rust: - nightly script: ./scripts/test.sh $TEST_FLAGS diff --git a/core/http/Cargo.toml b/core/http/Cargo.toml @@ -15,6 +15,7 @@ categories = ["web-programming"] [features] tls = ["rustls", "hyper-sync-rustls"] +private-cookies = ["cookie/secure"] [dependencies] smallvec = "0.6" @@ -24,7 +25,7 @@ time = "0.1" indexmap = "1.0" rustls = { version = "0.14", optional = true } state = "0.4" -cookie = { version = "0.11", features = ["percent-encode", "secure"] } +cookie = { version = "0.11", features = ["percent-encode"] } pear = "0.1" unicode-xid = "0.1" diff --git a/core/http/src/cookies.rs b/core/http/src/cookies.rs @@ -2,10 +2,16 @@ use std::fmt; use std::cell::RefMut; use cookie::Delta; -pub use cookie::{Cookie, Key, CookieJar, SameSite}; +pub use cookie::{Cookie, CookieJar, SameSite}; + +#[cfg(feature = "private-cookies")] +pub use cookie::Key; use Header; +#[cfg(not(feature = "private-cookies"))] +type Key = (); + /// Collection of one or more HTTP cookies. /// /// The `Cookies` type allows for retrieval of cookies from an incoming request @@ -166,29 +172,40 @@ impl<'a> Cookies<'a> { } } - /// Returns a reference to the `Cookie` inside this collection with the name - /// `name` and authenticates and decrypts the cookie's value, returning a - /// `Cookie` with the decrypted value. If the cookie cannot be found, or the - /// cookie fails to authenticate or decrypt, `None` is returned. + /// Adds `cookie` to this collection. /// /// # Example /// /// ```rust /// # extern crate rocket; - /// use rocket::http::Cookies; + /// use rocket::http::{Cookie, Cookies}; /// /// fn handler(mut cookies: Cookies) { - /// let cookie = cookies.get_private("name"); + /// cookies.add(Cookie::new("name", "value")); + /// + /// let cookie = Cookie::build("name", "value") + /// .path("/") + /// .secure(true) + /// .finish(); + /// + /// cookies.add(cookie); /// } /// ``` - pub fn get_private(&mut self, name: &str) -> Option<Cookie<'static>> { - match *self { - Cookies::Jarred(ref mut jar, key) => jar.private(key).get(name), - Cookies::Empty(_) => None + pub fn add(&mut self, cookie: Cookie<'static>) { + if let Cookies::Jarred(ref mut jar, _) = *self { + jar.add(cookie) } } - /// Adds `cookie` to this collection. + /// Removes `cookie` from this collection and generates a "removal" cookies + /// to send to the client on response. For correctness, `cookie` must + /// contain the same `path` and `domain` as the cookie that was initially + /// set. Failure to provide the initial `path` and `domain` will result in + /// cookies that are not properly removed. + /// + /// A "removal" cookie is a cookie that has the same name as the original + /// cookie but has an empty value, a max-age of 0, and an expiration date + /// far in the past. /// /// # Example /// @@ -197,19 +214,70 @@ impl<'a> Cookies<'a> { /// use rocket::http::{Cookie, Cookies}; /// /// fn handler(mut cookies: Cookies) { - /// cookies.add(Cookie::new("name", "value")); + /// cookies.remove(Cookie::named("name")); + /// } + /// ``` + pub fn remove(&mut self, cookie: Cookie<'static>) { + if let Cookies::Jarred(ref mut jar, _) = *self { + jar.remove(cookie) + } + } + + /// Returns an iterator over all of the cookies present in this collection. /// - /// let cookie = Cookie::build("name", "value") - /// .path("/") - /// .secure(true) - /// .finish(); + /// # Example /// - /// cookies.add(cookie); + /// ```rust + /// # extern crate rocket; + /// use rocket::http::Cookies; + /// + /// fn handler(cookies: Cookies) { + /// for c in cookies.iter() { + /// println!("Name: '{}', Value: '{}'", c.name(), c.value()); + /// } /// } /// ``` - pub fn add(&mut self, cookie: Cookie<'static>) { - if let Cookies::Jarred(ref mut jar, _) = *self { - jar.add(cookie) + pub fn iter(&self) -> impl Iterator<Item=&Cookie<'static>> { + match *self { + Cookies::Jarred(ref jar, _) => jar.iter(), + Cookies::Empty(ref jar) => jar.iter() + } + } + + /// WARNING: This is unstable! Do not use this method outside of Rocket! + #[doc(hidden)] + #[inline] + pub fn delta(&self) -> Delta { + match *self { + Cookies::Jarred(ref jar, _) => jar.delta(), + Cookies::Empty(ref jar) => jar.delta() + } + } +} + +#[cfg(feature = "private-cookies")] +impl<'a> Cookies<'a> { + /// Returns a reference to the `Cookie` inside this collection with the name + /// `name` and authenticates and decrypts the cookie's value, returning a + /// `Cookie` with the decrypted value. If the cookie cannot be found, or the + /// cookie fails to authenticate or decrypt, `None` is returned. + /// + /// This method is only available when the `private-cookies` feature is enabled. + /// + /// # Example + /// + /// ```rust + /// # extern crate rocket; + /// use rocket::http::Cookies; + /// + /// fn handler(mut cookies: Cookies) { + /// let cookie = cookies.get_private("name"); + /// } + /// ``` + pub fn get_private(&mut self, name: &str) -> Option<Cookie<'static>> { + match *self { + Cookies::Jarred(ref mut jar, key) => jar.private(key).get(name), + Cookies::Empty(_) => None } } @@ -230,6 +298,8 @@ impl<'a> Cookies<'a> { /// These defaults ensure maximum usability and security. For additional /// security, you may wish to set the `secure` flag. /// + /// This method is only available when the `private-cookies` feature is enabled. + /// /// # Example /// /// ```rust @@ -267,6 +337,8 @@ impl<'a> Cookies<'a> { /// * `HttpOnly`: `true` /// * `Expires`: 1 week from now /// + /// This method is only available when the `private-cookies` feature is enabled. + /// fn set_private_defaults(cookie: &mut Cookie<'static>) { if cookie.path().is_none() { cookie.set_path("/"); @@ -285,38 +357,14 @@ impl<'a> Cookies<'a> { } } - /// Removes `cookie` from this collection and generates a "removal" cookies - /// to send to the client on response. For correctness, `cookie` must - /// contain the same `path` and `domain` as the cookie that was initially - /// set. Failure to provide the initial `path` and `domain` will result in - /// cookies that are not properly removed. - /// - /// A "removal" cookie is a cookie that has the same name as the original - /// cookie but has an empty value, a max-age of 0, and an expiration date - /// far in the past. - /// - /// # Example - /// - /// ```rust - /// # extern crate rocket; - /// use rocket::http::{Cookie, Cookies}; - /// - /// fn handler(mut cookies: Cookies) { - /// cookies.remove(Cookie::named("name")); - /// } - /// ``` - pub fn remove(&mut self, cookie: Cookie<'static>) { - if let Cookies::Jarred(ref mut jar, _) = *self { - jar.remove(cookie) - } - } - /// Removes the private `cookie` from the collection. /// /// For correct removal, the passed in `cookie` must contain the same `path` /// and `domain` as the cookie that was initially set. If a path is not set /// on `cookie`, the `"/"` path will automatically be set. /// + /// This method is only available when the `private-cookies` feature is enabled. + /// /// # Example /// /// ```rust @@ -336,37 +384,6 @@ impl<'a> Cookies<'a> { jar.private(key).remove(cookie) } } - - /// Returns an iterator over all of the cookies present in this collection. - /// - /// # Example - /// - /// ```rust - /// # extern crate rocket; - /// use rocket::http::Cookies; - /// - /// fn handler(cookies: Cookies) { - /// for c in cookies.iter() { - /// println!("Name: '{}', Value: '{}'", c.name(), c.value()); - /// } - /// } - /// ``` - pub fn iter(&self) -> impl Iterator<Item=&Cookie<'static>> { - match *self { - Cookies::Jarred(ref jar, _) => jar.iter(), - Cookies::Empty(ref jar) => jar.iter() - } - } - - /// WARNING: This is unstable! Do not use this method outside of Rocket! - #[doc(hidden)] - #[inline] - pub fn delta(&self) -> Delta { - match *self { - Cookies::Jarred(ref jar, _) => jar.delta(), - Cookies::Empty(ref jar) => jar.delta() - } - } } impl<'a> fmt::Debug for Cookies<'a> { diff --git a/core/http/src/lib.rs b/core/http/src/lib.rs @@ -59,7 +59,8 @@ pub mod uncased; #[doc(hidden)] pub use smallvec::{SmallVec, Array}; // This one we need to expose for core. -#[doc(hidden)] pub use cookies::{Key, CookieJar}; +#[doc(hidden)] pub use cookies::CookieJar; +#[doc(hidden)] #[cfg(feature = "private-cookies")] pub use cookies::Key; pub use method::Method; pub use content_type::ContentType; diff --git a/core/lib/Cargo.toml b/core/lib/Cargo.toml @@ -16,6 +16,7 @@ categories = ["web-programming::http-server"] [features] tls = ["rocket_http/tls"] +private-cookies = ["rocket_http/private-cookies"] [dependencies] rocket_codegen = { version = "0.4.0-rc.1", path = "../codegen" } diff --git a/core/lib/src/config/config.rs b/core/lib/src/config/config.rs @@ -10,7 +10,8 @@ use {num_cpus, base64}; use config::Environment::*; use config::{Result, ConfigBuilder, Environment, ConfigError, LoggingLevel}; use config::{Table, Value, Array, Datetime}; -use http::Key; + +#[cfg(feature = "private-cookies")] use http::Key; /// Structure for Rocket application configuration. /// @@ -49,6 +50,7 @@ pub struct Config { /// How much information to log. pub log_level: LoggingLevel, /// The secret key. + #[cfg(feature = "private-cookies")] crate secret_key: SecretKey, /// TLS configuration. crate tls: Option<TlsConfig>, @@ -231,6 +233,7 @@ impl Config { let default_workers = (num_cpus::get() * 2) as u16; // Use a generated secret key by default. + #[cfg(feature = "private-cookies")] let key = SecretKey::Generated(Key::generate()); Ok(match env { @@ -242,6 +245,7 @@ impl Config { workers: default_workers, keep_alive: Some(5), log_level: LoggingLevel::Normal, + #[cfg(feature = "private-cookies")] secret_key: key, tls: None, limits: Limits::default(), @@ -257,6 +261,7 @@ impl Config { workers: default_workers, keep_alive: Some(5), log_level: LoggingLevel::Normal, + #[cfg(feature = "private-cookies")] secret_key: key, tls: None, limits: Limits::default(), @@ -272,6 +277,7 @@ impl Config { workers: default_workers, keep_alive: Some(5), log_level: LoggingLevel::Critical, + #[cfg(feature = "private-cookies")] secret_key: key, tls: None, limits: Limits::default(), @@ -473,6 +479,7 @@ impl Config { /// # Ok(()) /// # } /// ``` + #[cfg(feature = "private-cookies")] pub fn set_secret_key<K: Into<String>>(&mut self, key: K) -> Result<()> { let key = key.into(); let error = self.bad_type("secret_key", "string", @@ -490,6 +497,10 @@ impl Config { self.secret_key = SecretKey::Provided(Key::from_master(&bytes)); Ok(()) } + #[cfg(not(feature = "private-cookies"))] + pub fn set_secret_key<K: Into<String>>(&mut self, key: K) -> Result<()> { + Ok(()) + } /// Sets the logging level for `self` to `log_level`. /// @@ -663,6 +674,7 @@ impl Config { } /// Retrieves the secret key from `self`. + #[cfg(feature = "private-cookies")] #[inline] crate fn secret_key(&self) -> &Key { self.secret_key.inner() diff --git a/core/lib/src/config/custom_values.rs b/core/lib/src/config/custom_values.rs @@ -3,14 +3,17 @@ use std::fmt; #[cfg(feature = "tls")] use http::tls::{Certificate, PrivateKey}; use config::{Result, Config, Value, ConfigError, LoggingLevel}; -use http::Key; +#[cfg(feature = "private-cookies")] use http::Key; + +#[cfg(feature = "private-cookies")] #[derive(Clone)] pub enum SecretKey { Generated(Key), Provided(Key) } +#[cfg(feature = "private-cookies")] impl SecretKey { #[inline] crate fn inner(&self) -> &Key { @@ -28,6 +31,7 @@ impl SecretKey { } } +#[cfg(feature = "private-cookies")] impl fmt::Display for SecretKey { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { diff --git a/core/lib/src/local/request.rs b/core/lib/src/local/request.rs @@ -269,6 +269,8 @@ impl<'c> LocalRequest<'c> { /// /// [private cookie]: ::http::Cookies::add_private() /// + /// This method is only available when the `private-cookies` feature is enabled. + /// /// # Examples /// /// Add `user_id` as a private cookie: @@ -281,6 +283,7 @@ impl<'c> LocalRequest<'c> { /// # #[allow(unused_variables)] /// let req = client.get("/").private_cookie(Cookie::new("user_id", "sb")); /// ``` + #[cfg(feature = "private-cookies")] #[inline] pub fn private_cookie(self, cookie: Cookie<'static>) -> Self { self.request.cookies().add_original_private(cookie); diff --git a/core/lib/src/request/request.rs b/core/lib/src/request/request.rs @@ -290,7 +290,10 @@ impl<'r> Request<'r> { pub fn cookies(&self) -> Cookies { // FIXME: Can we do better? This is disappointing. match self.state.cookies.try_borrow_mut() { + #[cfg(feature = "private-cookies")] Ok(jar) => Cookies::new(jar, self.state.config.secret_key()), + #[cfg(not(feature = "private-cookies"))] + Ok(jar) => Cookies::new(jar, &()), Err(_) => { error_!("Multiple `Cookies` instances are active at once."); info_!("An instance of `Cookies` must be dropped before another \ diff --git a/core/lib/src/rocket.rs b/core/lib/src/rocket.rs @@ -396,6 +396,7 @@ impl Rocket { launch_info_!("port: {}", Paint::white(&config.port)); launch_info_!("log: {}", Paint::white(config.log_level)); launch_info_!("workers: {}", Paint::white(config.workers)); + #[cfg(feature = "private-cookies")] launch_info_!("secret key: {}", Paint::white(&config.secret_key)); launch_info_!("limits: {}", Paint::white(&config.limits)); @@ -414,8 +415,10 @@ impl Rocket { launch_info_!("tls: {}", Paint::white("disabled")); } - if config.secret_key.is_generated() && config.environment.is_prod() { - warn!("environment is 'production', but no `secret_key` is configured"); + #[cfg(feature = "private-cookies")] { + if config.secret_key.is_generated() && config.environment.is_prod() { + warn!("environment is 'production', but no `secret_key` is configured"); + } } for (name, value) in config.extras() { diff --git a/core/lib/tests/local_request_private_cookie-issue-368.rs b/core/lib/tests/local_request_private_cookie-issue-368.rs @@ -4,6 +4,7 @@ use rocket::http::Cookies; +#[cfg(feature = "private-cookies")] #[get("/")] fn return_private_cookie(mut cookies: Cookies) -> Option<String> { match cookies.get_private("cookie_name") { @@ -12,6 +13,7 @@ fn return_private_cookie(mut cookies: Cookies) -> Option<String> { } } +#[cfg(feature = "private-cookies")] mod tests { use super::*; use rocket::local::Client; diff --git a/examples/session/Cargo.toml b/examples/session/Cargo.toml @@ -5,7 +5,7 @@ workspace = "../../" publish = false [dependencies] -rocket = { path = "../../core/lib" } +rocket = { path = "../../core/lib", features = ["private-cookies"] } [dependencies.rocket_contrib] path = "../../contrib/lib" diff --git a/scripts/test.sh b/scripts/test.sh @@ -108,6 +108,26 @@ if [ "$1" = "--contrib" ]; then done popd > /dev/null 2>&1 +elif [ "$1" = "--core" ]; then + FEATURES=( + private-cookies + tls + ) + + pushd "${CORE_ROOT}" > /dev/null 2>&1 + + echo ":: Building and testing core [no features]..." + CARGO_INCREMENTAL=0 cargo test --no-default-features + + echo ":: Building and testing core [default]..." + CARGO_INCREMENTAL=0 cargo test + + for feature in "${FEATURES[@]}"; do + echo ":: Building and testing core [${feature}]..." + CARGO_INCREMENTAL=0 cargo test --no-default-features --features "${feature}" + done + + popd > /dev/null 2>&1 else echo ":: Bootstrapping examples..." bootstrap_examples diff --git a/site/guide/4-requests.md b/site/guide/4-requests.md @@ -503,6 +503,15 @@ fn logout(mut cookies: Cookies) -> Flash<Redirect> { [`Cookies::add()`]: @api/rocket/http/enum.Cookies.html#method.add +Private Cookies can be omitted at build time by excluding the feature +`private-cookies`. You can do this by setting the `default-features` +directive to `false` in your `Cargo.toml`: + +```toml +[dependencies.rocket] +default-features = false +``` + ### Secret Key To encrypt private cookies, Rocket uses the 256-bit key specified in the