diff --git a/core/codegen/tests/route-params.rs b/core/codegen/tests/route-params.rs deleted file mode 100644 index 5b808339..00000000 --- a/core/codegen/tests/route-params.rs +++ /dev/null @@ -1,10 +0,0 @@ -// #[post("/<_name>?<_query>", format = "application/json", data = "", rank = 2)] -// fn get( -// _name: &RawStr, -// _query: User, -// user: Form, -// _cookies: Cookies -// ) -> String { -// format!("{}:{}", user.name, user.nickname) -// } - diff --git a/core/codegen/tests/typed-uris.rs b/core/codegen/tests/typed-uris.rs index 270acda9..ee766638 100644 --- a/core/codegen/tests/typed-uris.rs +++ b/core/codegen/tests/typed-uris.rs @@ -4,7 +4,7 @@ use std::path::PathBuf; -use rocket::http::{RawStr, Cookies}; +use rocket::http::{RawStr, CookieJar}; use rocket::http::uri::{FromUriParam, Query}; use rocket::request::Form; @@ -51,13 +51,13 @@ fn simple4_flipped(name: String, id: i32) { } fn unused_param(used: i32, _unused: i32) { } #[post("/")] -fn guard_1(cookies: Cookies<'_>, id: i32) { } +fn guard_1(cookies: &CookieJar<'_>, id: i32) { } #[post("//")] -fn guard_2(name: String, cookies: Cookies<'_>, id: i32) { } +fn guard_2(name: String, cookies: &CookieJar<'_>, id: i32) { } #[post("/a//hi//hey")] -fn guard_3(id: i32, name: String, cookies: Cookies<'_>) { } +fn guard_3(id: i32, name: String, cookies: &CookieJar<'_>) { } #[post("/", data = "
")] fn no_uri_display_okay(id: i32, form: Form) { } @@ -69,7 +69,7 @@ fn complex<'r>( query: Form>, user: Form>, bar: &RawStr, - cookies: Cookies<'_> + cookies: &CookieJar<'_> ) { } #[post("/a/")] @@ -79,7 +79,7 @@ fn segments(path: PathBuf) { } fn param_and_segments(path: PathBuf, id: usize) { } #[post("/a//then/")] -fn guarded_segments(cookies: Cookies<'_>, path: PathBuf, id: usize) { } +fn guarded_segments(cookies: &CookieJar<'_>, path: PathBuf, id: usize) { } macro_rules! assert_uri_eq { ($($uri:expr => $expected:expr,)+) => { diff --git a/core/codegen/tests/ui-fail-nightly/responder-types.stderr b/core/codegen/tests/ui-fail-nightly/responder-types.stderr index 0b172a22..8bc601ec 100644 --- a/core/codegen/tests/ui-fail-nightly/responder-types.stderr +++ b/core/codegen/tests/ui-fail-nightly/responder-types.stderr @@ -14,7 +14,9 @@ error[E0277]: the trait bound `rocket::http::Header<'_>: std::convert::From` | = help: the following implementations were found: as std::convert::From<&rocket::http::Cookie<'_>>> + as std::convert::From<&rocket::http::CookieCrumb>> as std::convert::From>> + as std::convert::From> = note: required because of the requirements on the impl of `std::convert::Into>` for `u8` error[E0277]: the trait bound `u8: rocket::response::Responder<'_, '_>` is not satisfied @@ -33,7 +35,9 @@ error[E0277]: the trait bound `rocket::http::Header<'_>: std::convert::From` | = help: the following implementations were found: as std::convert::From<&rocket::http::Cookie<'_>>> + as std::convert::From<&rocket::http::CookieCrumb>> as std::convert::From>> + as std::convert::From> = note: required because of the requirements on the impl of `std::convert::Into>` for `u8` error[E0277]: the trait bound `rocket::http::Header<'_>: std::convert::From` is not satisfied @@ -44,7 +48,9 @@ error[E0277]: the trait bound `rocket::http::Header<'_>: std::convert::From as std::convert::From<&rocket::http::Cookie<'_>>> + as std::convert::From<&rocket::http::CookieCrumb>> as std::convert::From>> + as std::convert::From> = note: required because of the requirements on the impl of `std::convert::Into>` for `std::string::String` error[E0277]: the trait bound `usize: rocket::response::Responder<'_, '_>` is not satisfied diff --git a/core/codegen/tests/ui-fail-stable/responder-types.stderr b/core/codegen/tests/ui-fail-stable/responder-types.stderr index f414b4cb..026ad119 100644 --- a/core/codegen/tests/ui-fail-stable/responder-types.stderr +++ b/core/codegen/tests/ui-fail-stable/responder-types.stderr @@ -14,7 +14,9 @@ error[E0277]: the trait bound `rocket::http::Header<'_>: std::convert::From` | = help: the following implementations were found: as std::convert::From<&rocket::http::Cookie<'_>>> + as std::convert::From<&rocket::http::CookieCrumb>> as std::convert::From>> + as std::convert::From> = note: required because of the requirements on the impl of `std::convert::Into>` for `u8` error[E0277]: the trait bound `u8: rocket::response::Responder<'_, '_>` is not satisfied @@ -33,7 +35,9 @@ error[E0277]: the trait bound `rocket::http::Header<'_>: std::convert::From` | = help: the following implementations were found: as std::convert::From<&rocket::http::Cookie<'_>>> + as std::convert::From<&rocket::http::CookieCrumb>> as std::convert::From>> + as std::convert::From> = note: required because of the requirements on the impl of `std::convert::Into>` for `u8` error[E0277]: the trait bound `rocket::http::Header<'_>: std::convert::From` is not satisfied @@ -44,7 +48,9 @@ error[E0277]: the trait bound `rocket::http::Header<'_>: std::convert::From as std::convert::From<&rocket::http::Cookie<'_>>> + as std::convert::From<&rocket::http::CookieCrumb>> as std::convert::From>> + as std::convert::From> = note: required because of the requirements on the impl of `std::convert::Into>` for `std::string::String` error[E0277]: the trait bound `usize: rocket::response::Responder<'_, '_>` is not satisfied diff --git a/core/codegen/tests/ui-fail/typed-uris-bad-params.rs b/core/codegen/tests/ui-fail/typed-uris-bad-params.rs index 58ff7b82..dc7cbe19 100644 --- a/core/codegen/tests/ui-fail/typed-uris-bad-params.rs +++ b/core/codegen/tests/ui-fail/typed-uris-bad-params.rs @@ -1,15 +1,15 @@ #[macro_use] extern crate rocket; -use rocket::http::{Cookies, RawStr}; +use rocket::http::{CookieJar, RawStr}; #[post("/")] fn has_one(id: i32) { } #[post("/")] -fn has_one_guarded(cookies: Cookies, id: i32) { } +fn has_one_guarded(cookies: &CookieJar<'_>, id: i32) { } #[post("/?")] -fn has_two(cookies: Cookies, id: i32, name: String) { } +fn has_two(cookies: &CookieJar<'_>, id: i32, name: String) { } #[post("//")] fn optionals(id: Option, name: Result) { } diff --git a/core/http/Cargo.toml b/core/http/Cargo.toml index d71e78a1..89745e18 100644 --- a/core/http/Cargo.toml +++ b/core/http/Cargo.toml @@ -30,14 +30,21 @@ indexmap = "1.0" state = "0.4" tokio-rustls = { version = "0.14.0", optional = true } tokio = { version = "0.2.9", features = ["sync", "tcp", "time"] } -cookie = { version = "0.14.0", features = ["percent-encode"] } unicode-xid = "0.2" log = "0.4" ref-cast = "1.0" +[dependencies.cookie] +git = "https://github.com/SergioBenitez/cookie-rs.git" +rev = "3795f2e" +features = ["percent-encode"] + [dependencies.pear] git = "https://github.com/SergioBenitez/Pear.git" rev = "4b68055" [dev-dependencies] rocket = { version = "0.5.0-dev", path = "../lib" } + +[build-dependencies] +version_check = "0.9" diff --git a/core/http/build.rs b/core/http/build.rs new file mode 100644 index 00000000..99369685 --- /dev/null +++ b/core/http/build.rs @@ -0,0 +1,5 @@ +fn main() { + if let Some(true) = version_check::is_feature_flaggable() { + println!("cargo:rustc-cfg=nightly"); + } +} diff --git a/core/http/src/cookies.rs b/core/http/src/cookies.rs index 35a8a5a4..708bbee1 100644 --- a/core/http/src/cookies.rs +++ b/core/http/src/cookies.rs @@ -1,10 +1,9 @@ use std::fmt; use crate::Header; -use cookie::Delta; +pub use cookie::{Cookie, CookieCrumb, SameSite, Iter}; #[doc(hidden)] pub use self::key::*; -pub use cookie::{Cookie, CookieJar, SameSite}; /// Types and methods to manage a `Key` when private cookies are enabled. #[cfg(feature = "private-cookies")] @@ -27,18 +26,17 @@ mod key { /// Collection of one or more HTTP cookies. /// -/// The `Cookies` type allows for retrieval of cookies from an incoming request -/// as well as modifications to cookies to be reflected by Rocket on outgoing -/// responses. `Cookies` is a smart-pointer; it internally borrows and refers to -/// the collection of cookies active during a request's life-cycle. +/// The `CookieJar` type allows for retrieval of cookies from an incoming +/// request as well as modifications to cookies to be reflected by Rocket on +/// outgoing responses. /// /// # Usage /// -/// A type of `Cookies` can be retrieved via its `FromRequest` implementation as -/// a request guard or via the [`Request::cookies()`] method. Individual cookies -/// can be retrieved via the [`get()`] and [`get_private()`] methods. Cookies -/// can be added or removed via the [`add()`], [`add_private()`], [`remove()`], -/// and [`remove_private()`] methods. +/// A type of `&CookieJar` can be retrieved via its `FromRequest` implementation +/// as a request guard or via the [`Request::cookies()`] method. Individual +/// cookies can be retrieved via the [`get()`] and [`get_private()`] methods. +/// Cookies can be added or removed via the [`add()`], [`add_private()`], +/// [`remove()`], and [`remove_private()`] methods. /// /// [`Request::cookies()`]: rocket::Request::cookies() /// [`get()`]: #method.get @@ -50,27 +48,27 @@ mod key { /// /// ## Examples /// -/// The following short snippet shows `Cookies` being used as a request guard in -/// a handler to retrieve the value of a "message" cookie. +/// The following example shows `&CookieJar` being used as a request guard in a +/// handler to retrieve the value of a "message" cookie. /// /// ```rust /// # #[macro_use] extern crate rocket; -/// use rocket::http::Cookies; +/// use rocket::http::CookieJar; /// /// #[get("/message")] -/// fn message(cookies: Cookies) -> Option { -/// cookies.get("message").map(|c| format!("Message: {}", c.value())) +/// fn message(jar: &CookieJar<'_>) -> Option { +/// jar.get("message").map(|c| format!("Message: {}", c.value())) /// } /// # fn main() { } /// ``` /// -/// The following snippet shows `Cookies` being retrieved from a `Request` in a -/// custom request guard implementation for `User`. A [private cookie] +/// The following snippet shows `&CookieJar` being retrieved from a `Request` in +/// a custom request guard implementation for `User`. A [private cookie] /// containing a user's ID is retrieved. If the cookie exists and the ID parses /// as an integer, a `User` structure is validated. Otherwise, the guard /// forwards. /// -/// [private cookie]: Cookies::add_private() +/// [private cookie]: #method.add_private /// /// ```rust /// # #[macro_use] extern crate rocket; @@ -89,7 +87,7 @@ mod key { /// async fn from_request(request: &'a Request<'r>) -> request::Outcome { /// request.cookies() /// .get_private("user_id") -/// .and_then(|cookie| cookie.value().parse().ok()) +/// .and_then(|c| c.value().parse().ok()) /// .map(|id| User(id)) /// .or_forward(()) /// } @@ -106,7 +104,7 @@ mod key { /// manufactured by clients. If you prefer, you can think of private cookies as /// being signed and encrypted. /// -/// Private cookies can be retrieved, added, and removed from a `Cookies` +/// Private cookies can be retrieved, added, and removed from a `CookieJar` /// collection via the [`get_private()`], [`add_private()`], and /// [`remove_private()`] methods. /// @@ -125,194 +123,82 @@ mod key { /// is usually done through tools like `openssl`. Using `openssl`, for instance, /// a 256-bit base64 key can be generated with the command `openssl rand -base64 /// 32`. -pub enum Cookies<'a> { - #[doc(hidden)] - Jarred(CookieJar, &'a Key, Box), - #[doc(hidden)] - Empty(CookieJar) +#[derive(Clone)] +pub struct CookieJar<'a> { + jar: cookie::CookieJar, + key: &'a Key, } -impl<'a> Cookies<'a> { - /// WARNING: This is unstable! Do not use this method outside of Rocket! - #[inline] - #[doc(hidden)] - pub fn new(jar: CookieJar, key: &'a Key, on_drop: F) -> Cookies<'a> { - Cookies::Jarred(jar, key, Box::new(on_drop)) - } - - /// WARNING: This is unstable! Do not use this method outside of Rocket! - #[doc(hidden)] - #[inline(always)] - pub fn empty() -> Cookies<'static> { - Cookies::Empty(CookieJar::new()) - } - - /// WARNING: This is unstable! Do not use this method outside of Rocket! - #[doc(hidden)] - #[inline(always)] - pub fn parse_cookie(cookie_str: &str) -> Option> { - Cookie::parse_encoded(cookie_str).map(|c| c.into_owned()).ok() - } - - /// Adds an original `cookie` to this collection. - /// WARNING: This is unstable! Do not use this method outside of Rocket! - #[inline] - #[doc(hidden)] - pub fn add_original(&mut self, cookie: Cookie<'static>) { - if let Cookies::Jarred(ref mut jar, _, _) = *self { - jar.add_original(cookie) - } - } - +impl<'a> CookieJar<'a> { /// Returns a reference to the `Cookie` inside this container with the name /// `name`. If no such cookie exists, returns `None`. /// /// # Example /// /// ```rust - /// # extern crate rocket; - /// use rocket::http::Cookies; + /// # #[macro_use] extern crate rocket; + /// use rocket::http::{Cookie, CookieJar}; /// - /// fn handler(cookies: Cookies) { - /// let cookie = cookies.get("name"); + /// #[get("/")] + /// fn handler(jar: &CookieJar<'_>) { + /// let cookie = jar.get("name"); /// } /// ``` - pub fn get(&self, name: &str) -> Option<&Cookie<'static>> { - match *self { - Cookies::Jarred(ref jar, _, _) => jar.get(name), - Cookies::Empty(_) => None - } + pub fn get(&self, name: &str) -> Option { + self.jar.get(name) } - /// Adds `cookie` to this collection. - /// - /// # Example - /// - /// ```rust - /// # extern crate rocket; - /// use rocket::http::{Cookie, Cookies}; - /// - /// fn handler(mut cookies: Cookies) { - /// cookies.add(Cookie::new("name", "value")); - /// - /// let cookie = Cookie::build("name", "value") - /// .path("/") - /// .secure(true) - /// .finish(); - /// - /// cookies.add(cookie); - /// } - /// ``` - pub fn add(&mut self, cookie: Cookie<'static>) { - if let Cookies::Jarred(ref mut jar, _, _) = *self { - jar.add(cookie) - } - } - - /// 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 all delta cookies. - /// WARNING: This is unstable! Do not use this method outside of Rocket! - #[inline] - #[doc(hidden)] - pub fn reset_delta(&mut self) { - match *self { - Cookies::Jarred(ref mut jar, ..) => jar.reset_delta(), - Cookies::Empty(ref mut jar) => jar.reset_delta() - } - } - - /// 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> { - 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! - #[inline] - #[doc(hidden)] - pub fn delta(&self) -> Delta<'_> { - match *self { - Cookies::Jarred(ref jar, _, _) => jar.delta(), - Cookies::Empty(ref jar) => jar.delta() - } - } -} - -impl<'a> Drop for Cookies<'a> { - fn drop(&mut self) { - if let Cookies::Jarred(ref mut jar, _, ref mut on_drop) = *self { - let jar = std::mem::replace(jar, CookieJar::new()); - let on_drop = std::mem::replace(on_drop, Box::new(|_| {})); - on_drop(jar); - } - } -} - -#[cfg(feature = "private-cookies")] -impl Cookies<'_> { /// 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 + /// # #[macro_use] extern crate rocket; + /// use rocket::http::{Cookie, CookieJar}; + /// + /// #[get("/")] + /// fn handler(jar: &CookieJar<'_>) { + /// let cookie = jar.get_private("name"); + /// } + /// ``` + #[cfg(feature = "private-cookies")] + #[cfg_attr(nightly, doc(cfg(feature = "secrets")))] + pub fn get_private(&self, name: &str) -> Option> { + self.jar.private(&*self.key).get(name) + } + + /// Adds `cookie` to this collection. + /// + /// Unless a value is set for the given property, the following defaults are + /// set on `cookie` before being added to `self`: + /// + /// * `path`: `"/"` + /// * `SameSite`: `Strict` /// /// # Example /// /// ```rust - /// # extern crate rocket; - /// use rocket::http::Cookies; + /// # #[macro_use] extern crate rocket; + /// use rocket::http::{Cookie, SameSite, CookieJar}; /// - /// fn handler(mut cookies: Cookies) { - /// let cookie = cookies.get_private("name"); + /// #[get("/")] + /// fn handler(jar: &CookieJar<'_>) { + /// jar.add(Cookie::new("first", "value")); + /// + /// let cookie = Cookie::build("other", "value_two") + /// .path("/") + /// .secure(true) + /// .same_site(SameSite::Lax); + /// + /// jar.add(cookie.finish()); /// } /// ``` - pub fn get_private(&mut self, name: &str) -> Option> { - match *self { - Cookies::Jarred(ref mut jar, key, _) => jar.private(key).get(name), - Cookies::Empty(_) => None - } + pub fn add(&self, mut cookie: Cookie<'static>) { + Self::set_defaults(&mut cookie); + self.jar.add(cookie) } /// Adds `cookie` to the collection. The cookie's value is encrypted with @@ -321,7 +207,7 @@ impl Cookies<'_> { /// [`get_private`](#method.get_private) and removed using /// [`remove_private`](#method.remove_private). /// - /// Unless a value is supplied for the given key, the following defaults are + /// Unless a value is set for the given property, the following defaults are /// set on `cookie` before being added to `self`: /// /// * `path`: `"/"` @@ -332,51 +218,176 @@ impl Cookies<'_> { /// 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 + /// # #[macro_use] extern crate rocket; + /// use rocket::http::{Cookie, CookieJar}; + /// + /// #[get("/")] + /// fn handler(jar: &CookieJar<'_>) { + /// jar.add_private(Cookie::new("name", "value")); + /// } + /// ``` + #[cfg(feature = "private-cookies")] + #[cfg_attr(nightly, doc(cfg(feature = "secrets")))] + pub fn add_private(&self, mut cookie: Cookie<'static>) { + Self::set_private_defaults(&mut cookie); + self.jar.private(&*self.key).add(cookie) + } + + /// 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. For convenience, if a path is not + /// set on `cookie`, the `"/"` path will automatically be set. + /// + /// 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}; + /// # #[macro_use] extern crate rocket; + /// use rocket::http::{Cookie, CookieJar}; /// - /// fn handler(mut cookies: Cookies) { - /// cookies.add_private(Cookie::new("name", "value")); + /// #[get("/")] + /// fn handler(jar: &CookieJar<'_>) { + /// jar.remove(Cookie::named("name")); /// } /// ``` - pub fn add_private(&mut self, mut cookie: Cookie<'static>) { - if let Cookies::Jarred(ref mut jar, key, _) = *self { - Cookies::set_private_defaults(&mut cookie); - jar.private(key).add(cookie) + pub fn remove(&self, mut cookie: Cookie<'static>) { + if cookie.path().is_none() { + cookie.set_path("/"); } + + 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. + /// + /// # Example + /// + /// ```rust + /// # #[macro_use] extern crate rocket; + /// use rocket::http::{Cookie, CookieJar}; + /// + /// #[get("/")] + /// fn handler(jar: &CookieJar<'_>) { + /// jar.remove_private(Cookie::named("name")); + /// } + /// ``` + #[cfg(feature = "private-cookies")] + #[cfg_attr(nightly, doc(cfg(feature = "secrets")))] + pub fn remove_private(&self, mut cookie: Cookie<'static>) { + if cookie.path().is_none() { + cookie.set_path("/"); + } + + self.jar.private(&*self.key).remove(cookie) + } + + /// Returns an iterator over all of the cookies present in this collection. + /// + /// # Example + /// + /// ```rust + /// # #[macro_use] extern crate rocket; + /// use rocket::http::{Cookie, CookieJar}; + /// + /// #[get("/")] + /// fn handler(jar: &CookieJar<'_>) { + /// for c in jar.iter() { + /// println!("Name: {:?}, Value: {:?}", c.name(), c.value()); + /// } + /// } + /// ``` + pub fn iter(&self) -> impl Iterator + '_ { + self.jar.iter() + } +} + +/// WARNING: These is unstable! Do not use outside of Rocket! +#[doc(hidden)] +impl<'a> CookieJar<'a> { + #[inline(always)] + pub fn new(key: &'a Key) -> CookieJar<'a> { + CookieJar { jar: cookie::CookieJar::new(), key } + } + + #[inline(always)] + pub fn from(jar: cookie::CookieJar, key: &'a Key) -> CookieJar<'a> { + CookieJar { jar, key } + } + + /// Removes all delta cookies. + #[inline(always)] + pub fn reset_delta(&self) { + self.jar.reset_delta() + } + + #[inline(always)] + pub fn delta(&self) -> cookie::Delta { + self.jar.delta() + } + + /// Adds an original `cookie` to this collection. + #[inline(always)] + pub fn add_original(&self, cookie: Cookie<'static>) { + self.jar.add_original(cookie) } /// Adds an original, private `cookie` to the collection. - /// WARNING: This is unstable! Do not use this method outside of Rocket! - #[doc(hidden)] - pub fn add_original_private(&mut self, mut cookie: Cookie<'static>) { - if let Cookies::Jarred(ref mut jar, key, _) = *self { - Cookies::set_private_defaults(&mut cookie); - jar.private(key).add_original(cookie) + #[inline(always)] + #[cfg(feature = "private-cookies")] + #[cfg_attr(nightly, doc(cfg(feature = "secrets")))] + pub fn add_original_private(&self, cookie: Cookie<'static>) { + self.jar.private(&*self.key).add_original(cookie); + } + + /// For each property mentioned below, this method checks if there is a + /// provided value and if there is none, sets a default value. Default + /// values are: + /// + /// * `path`: `"/"` + /// * `SameSite`: `Strict` + /// + fn set_defaults(cookie: &mut Cookie<'static>) { + if cookie.path().is_none() { + cookie.set_path("/"); + } + + if cookie.same_site().is_none() { + cookie.set_same_site(SameSite::Strict); } } - /// For each property mentioned below, this method checks - /// if there is a provided value and if there is none, sets - /// a default value. - /// Default values are: + /// For each property mentioned below, this method checks if there is a + /// provided value and if there is none, sets a default value. Default + /// values are: /// /// * `path`: `"/"` /// * `SameSite`: `Strict` /// * `HttpOnly`: `true` /// * `Expires`: 1 week from now /// + #[cfg(feature = "private-cookies")] + #[cfg_attr(nightly, doc(cfg(feature = "secrets")))] fn set_private_defaults(cookie: &mut Cookie<'static>) { if cookie.path().is_none() { cookie.set_path("/"); } + if cookie.same_site().is_none() { + cookie.set_same_site(SameSite::Strict); + } + if cookie.http_only().is_none() { cookie.set_http_only(true); } @@ -384,48 +395,12 @@ impl Cookies<'_> { if cookie.expires().is_none() { cookie.set_expires(time::OffsetDateTime::now_utc() + time::Duration::weeks(1)); } - - if cookie.same_site().is_none() { - cookie.set_same_site(SameSite::Strict); - } - } - - /// 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 - /// # extern crate rocket; - /// use rocket::http::{Cookie, Cookies}; - /// - /// fn handler(mut cookies: Cookies) { - /// cookies.remove_private(Cookie::named("name")); - /// } - /// ``` - pub fn remove_private(&mut self, mut cookie: Cookie<'static>) { - if let Cookies::Jarred(ref mut jar, key, _) = *self { - if cookie.path().is_none() { - cookie.set_path("/"); - } - - jar.private(key).remove(cookie) - } } } -impl fmt::Debug for Cookies<'_> { +impl fmt::Debug for CookieJar<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match *self { - Cookies::Jarred(ref jar, _, _) => jar.fmt(f), - Cookies::Empty(ref jar) => jar.fmt(f) - } + self.jar.fmt(f) } } @@ -440,3 +415,15 @@ impl From<&Cookie<'_>> for Header<'static> { Header::new("Set-Cookie", cookie.encoded().to_string()) } } + +impl From for Header<'static> { + fn from(cookie: CookieCrumb) -> Header<'static> { + Header::new("Set-Cookie", cookie.encoded().to_string()) + } +} + +impl From<&CookieCrumb> for Header<'static> { + fn from(cookie: &CookieCrumb) -> Header<'static> { + Header::new("Set-Cookie", cookie.encoded().to_string()) + } +} diff --git a/core/http/src/lib.rs b/core/http/src/lib.rs index 12c9308d..21f057ab 100644 --- a/core/http/src/lib.rs +++ b/core/http/src/lib.rs @@ -1,5 +1,7 @@ #![recursion_limit="512"] +#![cfg_attr(nightly, feature(doc_cfg))] + #![warn(rust_2018_idioms)] //! Types that map to concepts in HTTP. @@ -44,13 +46,17 @@ pub mod uncased; pub mod private { // We need to export these for codegen, but otherwise it's unnecessary. // TODO: Expose a `const fn` from ContentType when possible. (see RFC#1817) - // FIXME(rustc): These show up in the rexported module. pub use crate::parse::Indexed; pub use crate::media_type::{MediaParams, Source}; pub use smallvec::{SmallVec, Array}; - // This one we need to expose for core. - pub use crate::cookies::{Key, CookieJar}; + // These we need to expose for core. + pub mod cookie { + pub use cookie::*; + pub use crate::cookies::Key; + } + + // These as well. pub use crate::listener::{Incoming, Listener, Connection, bind_tcp}; } @@ -60,6 +66,5 @@ pub use crate::accept::{Accept, QMediaType}; pub use crate::status::{Status, StatusClass}; pub use crate::header::{Header, HeaderMap}; pub use crate::raw_str::RawStr; - pub use crate::media_type::MediaType; -pub use crate::cookies::{Cookie, SameSite, Cookies}; +pub use crate::cookies::{Cookie, CookieJar, CookieCrumb, SameSite}; diff --git a/core/lib/Cargo.toml b/core/lib/Cargo.toml index c467e642..168cd20c 100644 --- a/core/lib/Cargo.toml +++ b/core/lib/Cargo.toml @@ -19,9 +19,9 @@ edition = "2018" all-features = true [features] -default = ["private-cookies"] +default = ["secrets"] tls = ["rocket_http/tls"] -private-cookies = ["rocket_http/private-cookies"] +secrets = ["rocket_http/private-cookies"] [dependencies] rocket_codegen = { version = "0.5.0-dev", path = "../codegen" } @@ -41,6 +41,11 @@ ref-cast = "1.0" atomic = "0.4" ubyte = "0.9.1" +[dependencies.cookie] +git = "https://github.com/SergioBenitez/cookie-rs.git" +rev = "3795f2e" +features = ["percent-encode"] + [dependencies.pear] git = "https://github.com/SergioBenitez/Pear.git" rev = "4b68055" diff --git a/core/lib/build.rs b/core/lib/build.rs index e3f8605d..e3f4bdfe 100644 --- a/core/lib/build.rs +++ b/core/lib/build.rs @@ -22,4 +22,8 @@ fn main() { println!("cargo:warning={}", "Rocket was unable to check rustc compiler compatibility."); println!("cargo:warning={}", "Build may fail due to incompatible rustc version."); } + + if let Some(true) = version_check::is_feature_flaggable() { + println!("cargo:rustc-cfg=nightly"); + } } diff --git a/core/lib/src/config/config.rs b/core/lib/src/config/config.rs index 01e4350a..be622099 100644 --- a/core/lib/src/config/config.rs +++ b/core/lib/src/config/config.rs @@ -4,11 +4,11 @@ use std::path::{Path, PathBuf}; use std::convert::AsRef; use std::fmt; +use crate::http::private::cookie::Key; use crate::config::Environment::*; use crate::config::{Result, ConfigBuilder, Environment, ConfigError, LoggingLevel}; use crate::config::{FullConfig, Table, Value, Array, Datetime}; use crate::data::Limits; -use crate::http::private::Key; use super::custom_values::*; diff --git a/core/lib/src/config/custom_values.rs b/core/lib/src/config/custom_values.rs index 641f2cf6..a484bdcb 100644 --- a/core/lib/src/config/custom_values.rs +++ b/core/lib/src/config/custom_values.rs @@ -2,7 +2,7 @@ use std::fmt; #[cfg(feature = "tls")] use crate::http::tls::{Certificate, PrivateKey}; -use crate::http::private::Key; +use crate::http::private::cookie::Key; use crate::config::{Result, Config, Value, ConfigError, LoggingLevel}; use crate::data::Limits; @@ -23,7 +23,7 @@ impl SecretKey { #[inline] pub(crate) fn is_generated(&self) -> bool { match *self { - #[cfg(feature = "private-cookies")] + #[cfg(feature = "secrets")] SecretKey::Generated(_) => true, _ => false } @@ -31,15 +31,17 @@ impl SecretKey { } impl fmt::Display for SecretKey { + #[cfg(feature = "secrets")] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - #[cfg(feature = "private-cookies")] match *self { SecretKey::Generated(_) => write!(f, "generated"), SecretKey::Provided(_) => write!(f, "provided"), } + } - #[cfg(not(feature = "private-cookies"))] - write!(f, "private-cookies disabled") + #[cfg(not(feature = "secrets"))] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + "private-cookies disabled".fmt(f) } } diff --git a/core/lib/src/error.rs b/core/lib/src/error.rs index 7e1bb34e..7512ebe4 100644 --- a/core/lib/src/error.rs +++ b/core/lib/src/error.rs @@ -225,9 +225,9 @@ impl Drop for LaunchError { use crate::http::uri; use crate::http::ext::IntoOwned; -use crate::http::route::{Error as SegmentError}; +use crate::http::route::Error as SegmentError; -/// Error returned by [`set_uri()`](crate::Route::set_uri()) on invalid URIs. +/// Error returned by [`Route::map_base()`] on invalid URIs. #[derive(Debug)] pub enum RouteUriError { /// The base (mount point) or route path contains invalid segments. diff --git a/core/lib/src/lib.rs b/core/lib/src/lib.rs index e2d52f31..477bf166 100644 --- a/core/lib/src/lib.rs +++ b/core/lib/src/lib.rs @@ -3,6 +3,7 @@ #![doc(html_root_url = "https://api.rocket.rs/v0.5")] #![doc(html_favicon_url = "https://rocket.rs/images/favicon.ico")] #![doc(html_logo_url = "https://rocket.rs/images/logo-boxed.png")] +#![cfg_attr(nightly, feature(doc_cfg))] #![warn(rust_2018_idioms)] @@ -35,21 +36,13 @@ //! //! ## Usage //! -//! First, depend on `rocket` in `Cargo.toml`: +//! Depend on `rocket` in `Cargo.toml`: //! //! ```toml //! [dependencies] //! rocket = "0.5.0-dev" //! ``` //! -//! Then, add the following to the top of your `main.rs` file: -//! -//! ```rust -//! #[macro_use] extern crate rocket; -//! # #[get("/")] fn hello() { } -//! # fn main() { rocket::ignite().mount("/", routes![hello]); } -//! ``` -//! //! See the [guide](https://rocket.rs/v0.5/guide) for more information on how to //! write Rocket applications. Here's a simple example to get you started: //! @@ -67,6 +60,20 @@ //! } //! ``` //! +//! ## Features +//! +//! The `secrets` feature, which enables [private cookies], is enabled by +//! default. This necessitates pulling in additional dependencies. To avoid +//! these dependencies when your application does not use private cookies, +//! disable the `secrets` feature: +//! +//! ```toml +//! [dependencies] +//! rocket = { version = "0.5.0-dev", default-features = false } +//! ``` +//! +//! [private cookies]: crate::http::CookieJar#private-cookies +//! //! ## Configuration //! //! Rocket and Rocket libraries are configured via the `Rocket.toml` file and/or diff --git a/core/lib/src/local/asynchronous/client.rs b/core/lib/src/local/asynchronous/client.rs index 42873dc4..c18af310 100644 --- a/core/lib/src/local/asynchronous/client.rs +++ b/core/lib/src/local/asynchronous/client.rs @@ -1,9 +1,8 @@ -use std::sync::RwLock; use std::borrow::Cow; use crate::local::asynchronous::{LocalRequest, LocalResponse}; use crate::rocket::{Rocket, Cargo}; -use crate::http::{Method, private::CookieJar}; +use crate::http::{private::cookie, Method}; use crate::error::LaunchError; /// An `async` client to construct and dispatch local requests. @@ -14,19 +13,17 @@ use crate::error::LaunchError; /// /// ## Multithreaded Synchronization Pitfalls /// -/// Unlike its [`blocking`](crate::local::blocking) variant, this `async` `Client` -/// implements `Sync`. However, using it in a multithreaded environment while -/// tracking cookies can result in surprising, non-deterministic behavior. This -/// is because while cookie modifications are serialized, the exact ordering -/// depends on when requests are dispatched. Specifically, when cookie tracking -/// is enabled, all request dispatches are serialized, which in-turn serializes -/// modifications to the internally tracked cookies. +/// Unlike its [`blocking`](crate::local::blocking) variant, this `async` +/// `Client` implements `Sync`. However, using it in a multithreaded environment +/// while tracking cookies can result in surprising, non-deterministic behavior. +/// This is because while cookie modifications are serialized, the ordering +/// depends on the ordering of request dispatch. /// /// If possible, refrain from sharing a single instance of `Client` across /// multiple threads. Instead, prefer to create a unique instance of `Client` -/// per thread. If it's not possible, ensure that either you are not depending -/// on cookies, the ordering of their modifications, or both, or have arranged -/// for dispatches to occur in a deterministic ordering. +/// per thread. If this is not possible, ensure that you are not depending on +/// the ordering of cookie modifications or have arranged for request dispatch +/// to occur in a deterministic manner. /// /// ## Example /// @@ -47,7 +44,8 @@ use crate::error::LaunchError; /// ``` pub struct Client { cargo: Cargo, - pub(in super) cookies: Option>, + pub(in super) tracked: bool, + pub(in super) cookies: cookie::CookieJar, } impl Client { @@ -56,13 +54,9 @@ impl Client { tracked: bool ) -> Result { rocket.prelaunch_check().await?; + let cargo = rocket.into_cargo().await; - let cookies = match tracked { - true => Some(RwLock::new(CookieJar::new())), - false => None - }; - - Ok(Client { cargo: rocket.into_cargo().await, cookies }) + Ok(Client { cargo, tracked, cookies: cookie::CookieJar::new() }) } // WARNING: This is unstable! Do not use this method outside of Rocket! @@ -84,6 +78,11 @@ impl Client { &self.cargo } + #[inline(always)] + pub(crate) fn _cookies(&self) -> &cookie::CookieJar { + &self.cookies + } + #[inline(always)] fn _req<'c, 'u: 'c, U>(&'c self, method: Method, uri: U) -> LocalRequest<'c> where U: Into> diff --git a/core/lib/src/local/asynchronous/request.rs b/core/lib/src/local/asynchronous/request.rs index 0ef11df5..f81f5e2c 100644 --- a/core/lib/src/local/asynchronous/request.rs +++ b/core/lib/src/local/asynchronous/request.rs @@ -30,8 +30,8 @@ use super::{Client, LocalResponse}; /// # }); /// ``` pub struct LocalRequest<'c> { - client: &'c Client, - request: Request<'c>, + pub(in super) client: &'c Client, + pub(in super) request: Request<'c>, data: Vec, uri: Cow<'c, str>, } @@ -47,11 +47,10 @@ impl<'c> LocalRequest<'c> { let origin = Origin::parse(&uri).unwrap_or_else(|_| Origin::dummy()); let request = Request::new(client.rocket(), method, origin.into_owned()); - // Set up any cookies we know about. - if let Some(ref jar) = client.cookies { - let cookies = jar.read().expect("LocalRequest::new() read lock"); - for cookie in cookies.iter() { - request.cookies().add_original(cookie.clone().into_owned()); + // Add any cookies we know about. + if client.tracked { + for crumb in client.cookies.iter() { + request.cookies().add_original(crumb.into_cookie()); } } @@ -86,24 +85,24 @@ impl<'c> LocalRequest<'c> { // Actually dispatch the request. let mut data = Data::local(self.data); let token = rocket.preprocess_request(&mut self.request, &mut data).await; - let response = LocalResponse::new(self.request, move |request| { - rocket.dispatch(token, request, data) + let response = LocalResponse::new(self.request, move |req| { + rocket.dispatch(token, req, data) }).await; // If the client is tracking cookies, updates the internal cookie jar // with the changes reflected by `response`. - if let Some(ref jar) = self.client.cookies { - let mut jar = jar.write().expect("LocalRequest::_dispatch() write lock"); + if self.client.tracked { + let jar = &self.client.cookies; let current_time = time::OffsetDateTime::now_utc(); - for cookie in response.cookies() { + for cookie in response.cookies().iter() { if let Some(expires) = cookie.expires() { if expires <= current_time { - jar.force_remove(cookie); + jar.force_remove(&cookie); continue; } } - jar.add(cookie.into_owned()); + jar.add(cookie.into_cookie()); } } diff --git a/core/lib/src/local/asynchronous/response.rs b/core/lib/src/local/asynchronous/response.rs index ab66e8b0..a92a09de 100644 --- a/core/lib/src/local/asynchronous/response.rs +++ b/core/lib/src/local/asynchronous/response.rs @@ -4,6 +4,7 @@ use std::{pin::Pin, task::{Context, Poll}}; use tokio::io::AsyncRead; +use crate::http::CookieJar; use crate::{Request, Response}; /// An `async` response from a dispatched [`LocalRequest`](super::LocalRequest). @@ -55,6 +56,7 @@ use crate::{Request, Response}; pub struct LocalResponse<'c> { _request: Box>, response: Response<'c>, + cookies: CookieJar<'c>, } impl<'c> LocalResponse<'c> { @@ -84,11 +86,15 @@ impl<'c> LocalResponse<'c> { // away as `'_`, ensuring it is not used for any output value. let boxed_req = Box::new(req); let request: &'c Request<'c> = unsafe { &*(&*boxed_req as *const _) }; + async move { - LocalResponse { - _request: boxed_req, - response: f(request).await + let response: Response<'c> = f(request).await; + let cookies = CookieJar::new(request.state.config.secret_key()); + for cookie in response.cookies() { + cookies.add(cookie.into_owned()); } + + LocalResponse { cookies, _request: boxed_req, response, } } } } @@ -98,6 +104,10 @@ impl LocalResponse<'_> { &self.response } + pub(crate) fn _cookies(&self) -> &CookieJar<'_> { + &self.cookies + } + pub(crate) async fn _into_string(mut self) -> Option { self.response.body_string().await } diff --git a/core/lib/src/local/blocking/client.rs b/core/lib/src/local/blocking/client.rs index 6fe756bf..b68a1ce8 100644 --- a/core/lib/src/local/blocking/client.rs +++ b/core/lib/src/local/blocking/client.rs @@ -2,9 +2,9 @@ use std::borrow::Cow; use std::cell::RefCell; use crate::error::LaunchError; -use crate::http::Method; use crate::local::{asynchronous, blocking::{LocalRequest, LocalResponse}}; use crate::rocket::{Rocket, Cargo}; +use crate::http::Method; /// A `blocking` client to construct and dispatch local requests. /// @@ -67,6 +67,11 @@ impl Client { self.inner._cargo() } + #[inline(always)] + fn _cookies(&self) -> &cookie::CookieJar { + self.inner._cookies() + } + #[inline(always)] pub(crate) fn _req<'c, 'u: 'c, U>( &'c self, diff --git a/core/lib/src/local/blocking/response.rs b/core/lib/src/local/blocking/response.rs index 68dd9fe0..74048f5c 100644 --- a/core/lib/src/local/blocking/response.rs +++ b/core/lib/src/local/blocking/response.rs @@ -1,7 +1,7 @@ use std::io; use tokio::io::AsyncReadExt; -use crate::{Response, local::asynchronous}; +use crate::{Response, local::asynchronous, http::CookieJar}; use super::Client; @@ -60,6 +60,10 @@ impl LocalResponse<'_> { &self.inner._response() } + pub(crate) fn _cookies(&self) -> &CookieJar<'_> { + self.inner._cookies() + } + fn _into_string(self) -> Option { self.client.block_on(self.inner._into_string()) } diff --git a/core/lib/src/local/client.rs b/core/lib/src/local/client.rs index 798486ae..352c0f08 100644 --- a/core/lib/src/local/client.rs +++ b/core/lib/src/local/client.rs @@ -54,7 +54,7 @@ macro_rules! pub_client_impl { /// /// This is typically the desired mode of operation for a `Client` as it /// removes the burden of manually tracking cookies. Under some - /// circumstances, however, disabling this tracking may be desired. The + /// circumstances, however, disabling tracking may be desired. The /// [`untracked()`](Client::untracked()) method creates a `Client` that /// _will not_ track cookies. /// @@ -133,6 +133,29 @@ macro_rules! pub_client_impl { self._cargo() } + /// Returns a cookie jar containing all of the cookies this client is + /// currently tracking. + /// + /// If cookie tracking is disabled, the returned jar will always be empty. + /// Otherwise, it will contains all of the cookies collected from responses + /// to requests dispatched by this client that have not expired. + /// + /// # Example + /// + /// ```rust + #[doc = $import] + /// + /// # Client::_test(|client, _, _| { + /// let client: &Client = client; + /// let cookie = client.cookies(); + /// # }); + /// ``` + #[inline(always)] + pub fn cookies(&self) -> crate::http::CookieJar<'_> { + let key = self.rocket().config.secret_key(); + crate::http::CookieJar::from(self._cookies().clone(), key) + } + req_method!($import, "GET", get, Method::Get); req_method!($import, "PUT", put, Method::Put); req_method!($import, "POST", post, Method::Post); diff --git a/core/lib/src/local/request.rs b/core/lib/src/local/request.rs index 62dcf0e1..8fb7e0ba 100644 --- a/core/lib/src/local/request.rs +++ b/core/lib/src/local/request.rs @@ -118,9 +118,7 @@ macro_rules! pub_request_impl { /// Add all of the cookies in `cookies` to this request. /// - /// # Examples - /// - /// Add `user_id` cookie: + /// # Example /// /// ```rust #[doc = $import] @@ -133,7 +131,9 @@ macro_rules! pub_request_impl { /// # }); /// ``` #[inline] - pub fn cookies(self, cookies: Vec>) -> Self { + pub fn cookies<'a, C>(self, cookies: C) -> Self + where C: IntoIterator> + { for cookie in cookies { self._request().cookies().add_original(cookie.into_owned()); } @@ -143,10 +143,7 @@ macro_rules! pub_request_impl { /// Add a [private cookie] to this request. /// - /// This method is only available when the `private-cookies` feature is - /// enabled. - /// - /// [private cookie]: crate::http::Cookies::add_private() + /// [private cookie]: crate::http::CookieJar::add_private() /// /// # Examples /// @@ -161,8 +158,9 @@ macro_rules! pub_request_impl { /// let req = request.private_cookie(Cookie::new("user_id", "sb")); /// # }); /// ``` + #[cfg(feature = "secrets")] + #[cfg_attr(nightly, doc(cfg(feature = "secrets")))] #[inline] - #[cfg(feature = "private-cookies")] pub fn private_cookie(self, cookie: crate::http::Cookie<'static>) -> Self { self._request().cookies().add_original_private(cookie); self diff --git a/core/lib/src/local/response.rs b/core/lib/src/local/response.rs index c307ca00..43cae16b 100644 --- a/core/lib/src/local/response.rs +++ b/core/lib/src/local/response.rs @@ -37,8 +37,22 @@ macro_rules! pub_response_impl { getter_method!($doc_prelude, "HTTP headers", headers -> &crate::http::HeaderMap<'_>); - getter_method!($doc_prelude, "HTTP cookies as set in the `Set-Cookie` header", - cookies -> Vec>); + /// Return a cookie jar containing the HTTP cookies in the response. + /// + /// # Example + /// + /// ```rust + #[doc = $doc_prelude] + /// + /// # Client::_test(|_, _, response| { + /// let response: LocalResponse = response; + /// let string = response.cookies(); + /// # }); + /// ``` + #[inline(always)] + pub fn cookies(&self) -> &crate::http::CookieJar<'_> { + self._cookies() + } getter_method!($doc_prelude, "response body, if there is one,", body -> Option<&crate::response::ResponseBody<'_>>); diff --git a/core/lib/src/request/from_request.rs b/core/lib/src/request/from_request.rs index 1f724245..340fe8e2 100644 --- a/core/lib/src/request/from_request.rs +++ b/core/lib/src/request/from_request.rs @@ -8,7 +8,7 @@ use crate::request::Request; use crate::outcome::{self, IntoOutcome}; use crate::outcome::Outcome::*; -use crate::http::{Status, ContentType, Accept, Method, Cookies, uri::Origin}; +use crate::http::{Status, ContentType, Accept, Method, CookieJar, uri::Origin}; /// Type alias for the `Outcome` of a `FromRequest` conversion. pub type Outcome = outcome::Outcome; @@ -140,10 +140,10 @@ impl IntoOutcome for Result { /// For information on when an `&Route` is available, see /// [`Request::route()`]. /// -/// * **Cookies** +/// * **&CookieJar** /// -/// Returns a borrow to the [`Cookies`] in the incoming request. Note that -/// `Cookies` implements internal mutability, so a handle to `Cookies` +/// Returns a borrow to the [`CookieJar`] in the incoming request. Note that +/// `CookieJar` implements internal mutability, so a handle to a `CookieJar` /// allows you to get _and_ set cookies in the request. /// /// _This implementation always returns successfully._ @@ -242,7 +242,7 @@ impl IntoOutcome for Result { /// /// ```rust /// # #[macro_use] extern crate rocket; -/// # #[cfg(feature = "private-cookies")] mod inner { +/// # #[cfg(feature = "secrets")] mod wrapper { /// # use rocket::outcome::IntoOutcome; /// # use rocket::request::{self, Outcome, FromRequest, Request}; /// # struct User { id: String, is_admin: bool } @@ -296,7 +296,7 @@ impl IntoOutcome for Result { /// /// #[get("/dashboard", rank = 2)] /// fn user_dashboard(user: User) { } -/// # } +/// # } // end of cfg wrapper /// ``` /// /// When a non-admin user is logged in, the database will be queried twice: once @@ -306,7 +306,7 @@ impl IntoOutcome for Result { /// /// ```rust /// # #[macro_use] extern crate rocket; -/// # #[cfg(feature = "private-cookies")] mod inner { +/// # #[cfg(feature = "secrets")] mod wrapper { /// # use rocket::outcome::IntoOutcome; /// # use rocket::request::{self, Outcome, FromRequest, Request}; /// # struct User { id: String, is_admin: bool } @@ -358,14 +358,13 @@ impl IntoOutcome for Result { /// } /// } /// } -/// # } +/// # } // end of cfg wrapper /// ``` /// /// Notice that these request guards provide access to *borrowed* data (`&'a /// User` and `Admin<'a>`) as the data is now owned by the request's cache. /// /// [request-local state]: https://rocket.rs/v0.5/guide/state/#request-local-state - #[crate::async_trait] pub trait FromRequest<'a, 'r>: Sized { /// The associated error to be returned if derivation fails. @@ -411,7 +410,7 @@ impl<'a, 'r> FromRequest<'a, 'r> for &'r Route { } #[crate::async_trait] -impl<'a, 'r> FromRequest<'a, 'r> for Cookies<'a> { +impl<'a, 'r> FromRequest<'a, 'r> for &'a CookieJar<'r> { type Error = std::convert::Infallible; async fn from_request(request: &'a Request<'r>) -> Outcome { diff --git a/core/lib/src/request/request.rs b/core/lib/src/request/request.rs index f7600a37..33e3caa8 100644 --- a/core/lib/src/request/request.rs +++ b/core/lib/src/request/request.rs @@ -1,4 +1,4 @@ -use std::sync::{Arc, RwLock, Mutex}; +use std::sync::Arc; use std::net::{IpAddr, SocketAddr}; use std::future::Future; use std::fmt; @@ -7,20 +7,18 @@ use std::str; use yansi::Paint; use state::{Container, Storage}; use futures::future::BoxFuture; -use atomic::Atomic; +use atomic::{Atomic, Ordering}; use crate::request::{FromParam, FromSegments, FromRequest, Outcome}; use crate::request::{FromFormValue, FormItems, FormItem}; use crate::{Rocket, Config, Shutdown, Route}; use crate::http::{hyper, uri::{Origin, Segments}}; -use crate::http::{Method, Header, HeaderMap, Cookies}; -use crate::http::{RawStr, ContentType, Accept, MediaType}; -use crate::http::private::{Indexed, SmallVec, CookieJar}; +use crate::http::{Method, Header, HeaderMap}; +use crate::http::{RawStr, ContentType, Accept, MediaType, CookieJar, Cookie}; +use crate::http::private::{Indexed, SmallVec}; use crate::data::Limits; -type Indices = (usize, usize); - /// The type of an incoming web request. /// /// This should be used sparingly in Rocket applications. In particular, it @@ -41,14 +39,14 @@ pub(crate) struct RequestState<'r> { pub shutdown: &'r Shutdown, pub path_segments: SmallVec<[Indices; 12]>, pub query_items: Option>, - pub route: RwLock>, - pub cookies: Mutex>, + pub route: Atomic>, + pub cookies: CookieJar<'r>, pub accept: Storage>, pub content_type: Storage>, pub cache: Arc, } -impl<'r> Request<'r> { +impl Request<'_> { pub(crate) fn clone(&self) -> Self { Request { method: Atomic::new(self.method()), @@ -60,24 +58,16 @@ impl<'r> Request<'r> { } } -impl<'r> RequestState<'r> { - fn clone(&self) -> RequestState<'r> { - let route = self.route.try_read() - .map(|r| r.clone()) - .unwrap_or(None); - - let cookies = self.cookies.try_lock() - .map(|j| j.clone()) - .unwrap_or_else(|_| Some(CookieJar::new())); - +impl RequestState<'_> { + fn clone(&self) -> Self { RequestState { config: self.config, managed: self.managed, shutdown: self.shutdown, path_segments: self.path_segments.clone(), query_items: self.query_items.clone(), - route: RwLock::new(route), - cookies: Mutex::new(cookies), + route: Atomic::new(self.route.load(Ordering::Acquire)), + cookies: self.cookies.clone(), accept: self.accept.clone(), content_type: self.content_type.clone(), cache: self.cache.clone(), @@ -85,13 +75,6 @@ impl<'r> RequestState<'r> { } } -#[derive(Clone)] -pub(crate) struct IndexedFormItem { - raw: Indices, - key: Indices, - value: Indices -} - impl<'r> Request<'r> { /// Create a new `Request` with the given `method` and `uri`. #[inline(always)] @@ -111,8 +94,8 @@ impl<'r> Request<'r> { config: &rocket.config, managed: &rocket.managed_state, shutdown: &rocket.shutdown_handle, - route: RwLock::new(None), - cookies: Mutex::new(Some(CookieJar::new())), + route: Atomic::new(None), + cookies: CookieJar::new(rocket.config.secret_key()), accept: Storage::new(), content_type: Storage::new(), cache: Arc::new(Container::new()), @@ -138,7 +121,7 @@ impl<'r> Request<'r> { /// ``` #[inline(always)] pub fn method(&self) -> Method { - self.method.load(atomic::Ordering::Acquire) + self.method.load(Ordering::Acquire) } /// Set the method of `self`. @@ -308,8 +291,8 @@ impl<'r> Request<'r> { /// Returns a wrapped borrow to the cookies in `self`. /// - /// [`Cookies`] implements internal mutability, so this method allows you to - /// get _and_ add/remove cookies in `self`. + /// [`CookieJar`] implements internal mutability, so this method allows you + /// to get _and_ add/remove cookies in `self`. /// /// # Example /// @@ -325,26 +308,8 @@ impl<'r> Request<'r> { /// request.cookies().add(Cookie::new("ans", format!("life: {}", 38 + 4))); /// # }); /// ``` - pub fn cookies(&self) -> Cookies<'_> { - // FIXME: Can we do better? This is disappointing. - let mut guard = self.state.cookies.lock().expect("cookies lock"); - match guard.take() { - Some(jar) => { - let mutex = &self.state.cookies; - let on_drop = move |jar| { - *mutex.lock().expect("cookies lock") = Some(jar); - }; - - Cookies::new(jar, self.state.config.secret_key(), on_drop) - } - None => { - error_!("Multiple `Cookies` instances are active at once."); - info_!("An instance of `Cookies` must be dropped before another \ - can be retrieved."); - warn_!("The retrieved `Cookies` instance will be empty."); - Cookies::empty() - } - } + pub fn cookies(&self) -> &CookieJar<'r> { + &self.state.cookies } /// Returns a [`HeaderMap`] of all of the headers in `self`. @@ -542,7 +507,7 @@ impl<'r> Request<'r> { /// # }); /// ``` pub fn route(&self) -> Option<&'r Route> { - *self.state.route.read().unwrap() + self.state.route.load(Ordering::Acquire) } /// Invokes the request guard implementation for `T`, returning its outcome. @@ -779,7 +744,7 @@ impl<'r> Request<'r> { } // All of these methods only exist for internal, including codegen, purposes. -// They _are not_ part of the stable API. +// They _are not_ part of the stable API. Please, don't use these. #[doc(hidden)] impl<'r> Request<'r> { // Only used by doc-tests! Needs to be `pub` because doc-test are external. @@ -857,14 +822,14 @@ impl<'r> Request<'r> { /// was `route`. Use during routing when attempting a given route. #[inline(always)] pub(crate) fn set_route(&self, route: &'r Route) { - *self.state.route.write().unwrap() = Some(route); + self.state.route.store(Some(route), Ordering::Release) } /// Set the method of `self`, even when `self` is a shared reference. Used /// during routing to override methods for re-routing. #[inline(always)] pub(crate) fn _set_method(&self, method: Method) { - self.method.store(method, atomic::Ordering::Release) + self.method.store(method, Ordering::Release) } /// Convert from Hyper types into a Rocket Request. @@ -895,7 +860,6 @@ impl<'r> Request<'r> { request.set_remote(h_addr); // Set the request cookies, if they exist. - let mut cookie_jar = CookieJar::new(); for header in h_headers.get_all("Cookie") { let raw_str = match std::str::from_utf8(header.as_bytes()) { Ok(string) => string, @@ -903,12 +867,11 @@ impl<'r> Request<'r> { }; for cookie_str in raw_str.split(';').map(|s| s.trim()) { - if let Some(cookie) = Cookies::parse_cookie(cookie_str) { - cookie_jar.add_original(cookie); + if let Ok(cookie) = Cookie::parse_encoded(cookie_str) { + request.state.cookies.add_original(cookie.into_owned()); } } } - request.state.cookies = Mutex::new(Some(cookie_jar)); // Set the rest of the headers. for (name, value) in h_headers.iter() { @@ -929,6 +892,7 @@ impl fmt::Debug for Request<'_> { .field("uri", &self.uri) .field("headers", &self.headers()) .field("remote", &self.remote()) + .field("cookies", &self.cookies()) .finish() } } @@ -950,6 +914,15 @@ impl fmt::Display for Request<'_> { } } +type Indices = (usize, usize); + +#[derive(Clone)] +pub(crate) struct IndexedFormItem { + raw: Indices, + key: Indices, + value: Indices +} + impl IndexedFormItem { #[inline(always)] fn from(s: &str, i: FormItem<'_>) -> Self { diff --git a/core/lib/src/response/response.rs b/core/lib/src/response/response.rs index d29066f2..82729c76 100644 --- a/core/lib/src/response/response.rs +++ b/core/lib/src/response/response.rs @@ -742,17 +742,13 @@ impl<'r> Response<'r> { /// /// let mut response = Response::new(); /// response.set_header(Cookie::new("hello", "world!")); - /// assert_eq!(response.cookies(), vec![Cookie::new("hello", "world!")]); + /// let cookies: Vec<_> = response.cookies().collect(); + /// assert_eq!(cookies, vec![Cookie::new("hello", "world!")]); /// ``` - pub fn cookies(&self) -> Vec> { - let mut cookies = vec![]; - for header in self.headers().get("Set-Cookie") { - if let Ok(cookie) = Cookie::parse_encoded(header) { - cookies.push(cookie); - } - } - - cookies + pub fn cookies(&self) -> impl Iterator> { + self.headers() + .get("Set-Cookie") + .filter_map(|header| Cookie::parse_encoded(header).ok()) } /// Returns a [`HeaderMap`] of all of the headers in `self`. diff --git a/core/lib/src/rocket.rs b/core/lib/src/rocket.rs index 4b69bc88..d9f57ce0 100644 --- a/core/lib/src/rocket.rs +++ b/core/lib/src/rocket.rs @@ -359,8 +359,8 @@ impl Rocket { // Set the cookies. Note that error responses will only include // cookies set by the error handler. See `handle_error` for more. - for cookie in request.cookies().delta() { - response.adjoin_header(cookie); + for crumb in request.cookies().delta() { + response.adjoin_header(crumb) } response @@ -651,7 +651,7 @@ impl Rocket { } if config.secret_key.is_generated() && config.environment.is_prod() { - warn!("environment is 'production', but no `secret_key` is configured"); + warn!("environment is 'production' but no `secret_key` is configured"); } for (name, value) in config.extras() { diff --git a/core/lib/tests/catcher-cookies-1213.rs b/core/lib/tests/catcher-cookies-1213.rs index a733927d..3032e03a 100644 --- a/core/lib/tests/catcher-cookies-1213.rs +++ b/core/lib/tests/catcher-cookies-1213.rs @@ -1,16 +1,16 @@ #[macro_use] extern crate rocket; use rocket::request::Request; -use rocket::http::{Cookie, Cookies}; +use rocket::http::{Cookie, CookieJar}; #[catch(404)] fn not_found(request: &Request) -> &'static str { - request.cookies().add(Cookie::new("not_found", "hi")); + request.cookies().add(Cookie::new("not_found", "404")); "404 - Not Found" } #[get("/")] -fn index(mut cookies: Cookies) -> &'static str { +fn index(cookies: &CookieJar<'_>) -> &'static str { cookies.add(Cookie::new("index", "hi")); "Hello, world!" } @@ -26,7 +26,7 @@ mod tests { .mount("/", routes![index]) .register(catchers![not_found]) .attach(AdHoc::on_request("Add Fairing Cookie", |req, _| Box::pin(async move { - req.cookies().add(Cookie::new("fairing", "hi")); + req.cookies().add(Cookie::new("fairing", "woo")); }))); let client = Client::new(rocket).unwrap(); @@ -34,12 +34,14 @@ mod tests { // Check that the index returns the `index` and `fairing` cookie. let response = client.get("/").dispatch(); let cookies = response.cookies(); - assert_eq!(cookies.len(), 2); - assert!(cookies.iter().find(|c| c.name() == "index").is_some()); - assert!(cookies.iter().find(|c| c.name() == "fairing").is_some()); + assert_eq!(cookies.iter().count(), 2); + assert_eq!(cookies.get("index").unwrap().value(), "hi"); + assert_eq!(cookies.get("fairing").unwrap().value(), "woo"); // Check that the catcher returns only the `not_found` cookie. let response = client.get("/not-existent").dispatch(); - assert_eq!(response.cookies(), vec![Cookie::new("not_found", "hi")]); + let cookies = response.cookies(); + assert_eq!(cookies.iter().count(), 1); + assert_eq!(cookies.get("not_found").unwrap().value(), "404"); } } diff --git a/core/lib/tests/local_request_private_cookie-issue-368.rs b/core/lib/tests/local_request_private_cookie-issue-368.rs index ee7562ee..fc446d57 100644 --- a/core/lib/tests/local_request_private_cookie-issue-368.rs +++ b/core/lib/tests/local_request_private_cookie-issue-368.rs @@ -1,13 +1,9 @@ -#[macro_use] -#[cfg(feature = "private-cookies")] -extern crate rocket; +#[cfg(feature = "secrets")] +mod private_cookies { + use rocket::http::CookieJar; -#[cfg(feature = "private-cookies")] -mod private_cookie_test { - use rocket::http::Cookies; - - #[get("/")] - fn return_private_cookie(mut cookies: Cookies) -> Option { + #[rocket::get("/")] + fn return_private_cookie(cookies: &CookieJar<'_>) -> Option { match cookies.get_private("cookie_name") { Some(cookie) => Some(cookie.value().into()), None => None, @@ -16,9 +12,9 @@ mod private_cookie_test { mod tests { use super::*; + use rocket::routes; use rocket::local::blocking::Client; - use rocket::http::Cookie; - use rocket::http::Status; + use rocket::http::{Cookie, Status}; #[test] fn private_cookie_is_returned() { diff --git a/core/lib/tests/many-cookie-jars-at-once.rs b/core/lib/tests/many-cookie-jars-at-once.rs new file mode 100644 index 00000000..3c0a4c07 --- /dev/null +++ b/core/lib/tests/many-cookie-jars-at-once.rs @@ -0,0 +1,49 @@ +#[macro_use] extern crate rocket; + +use rocket::http::{Cookie, CookieJar}; + +#[post("/")] +fn multi_add(jar_a: &CookieJar<'_>, jar_b: &CookieJar<'_>) { + jar_a.add(Cookie::new("a", "v1")); + jar_b.add(Cookie::new("b", "v2")); +} + +#[get("/")] +fn multi_get(jar_a: &CookieJar<'_>, jar_b: &CookieJar<'_>, jar_c: &CookieJar<'_>) -> String { + let (a, a2, a3) = (jar_a.get("a"), jar_b.get("a"), jar_c.get("a")); + let (b, b2, b3) = (jar_a.get("b"), jar_b.get("b"), jar_c.get("b")); + assert_eq!(a, a2); assert_eq!(a2, a3); + assert_eq!(b, b2); assert_eq!(b2, b3); + format!("{}{}", a.unwrap().value(), b.unwrap().value()) +} + +#[cfg(test)] +mod many_cookie_jars_tests { + use super::*; + use rocket::local::blocking::Client; + + fn rocket() -> rocket::Rocket { + rocket::ignite().mount("/", routes![multi_add, multi_get]) + } + + #[test] + fn test_mutli_add() { + let client = Client::new(rocket()).unwrap(); + let response = client.post("/").dispatch(); + let cookies = response.cookies(); + assert_eq!(cookies.iter().count(), 2); + assert_eq!(cookies.get("a").unwrap().value(), "v1"); + assert_eq!(cookies.get("b").unwrap().value(), "v2"); + } + + #[test] + fn test_mutli_get() { + let client = Client::new(rocket()).unwrap(); + let response = client.get("/") + .cookie(Cookie::new("a", "a_val")) + .cookie(Cookie::new("b", "hi!")) + .dispatch(); + + assert_eq!(response.into_string().unwrap(), "a_valhi!"); + } +} diff --git a/examples/cookies/src/main.rs b/examples/cookies/src/main.rs index 9e2d1139..d2f14801 100644 --- a/examples/cookies/src/main.rs +++ b/examples/cookies/src/main.rs @@ -7,7 +7,7 @@ use std::collections::HashMap; use rocket::request::Form; use rocket::response::Redirect; -use rocket::http::{Cookie, Cookies}; +use rocket::http::{Cookie, CookieJar}; use rocket_contrib::templates::Template; #[derive(FromForm)] @@ -16,13 +16,13 @@ struct Message { } #[post("/submit", data = "")] -fn submit(mut cookies: Cookies<'_>, message: Form) -> Redirect { +fn submit(cookies: &CookieJar<'_>, message: Form) -> Redirect { cookies.add(Cookie::new("message", message.into_inner().message)); Redirect::to("/") } #[get("/")] -fn index(cookies: Cookies<'_>) -> Template { +fn index(cookies: &CookieJar<'_>) -> Template { let cookie = cookies.get("message"); let mut context = HashMap::new(); if let Some(ref cookie) = cookie { diff --git a/examples/cookies/src/tests.rs b/examples/cookies/src/tests.rs index 69a34a1c..209b0905 100644 --- a/examples/cookies/src/tests.rs +++ b/examples/cookies/src/tests.rs @@ -14,11 +14,12 @@ fn test_submit() { .dispatch(); let cookie_headers: Vec<_> = response.headers().get("Set-Cookie").collect(); - let location_headers: Vec<_> = response.headers().get("Location").collect(); + assert_eq!(cookie_headers.len(), 1); + assert!(cookie_headers[0].starts_with("message=Hello%20from%20Rocket!")); - assert_eq!(response.status(), Status::SeeOther); - assert_eq!(cookie_headers, vec!["message=Hello%20from%20Rocket!".to_string()]); + let location_headers: Vec<_> = response.headers().get("Location").collect(); assert_eq!(location_headers, vec!["/".to_string()]); + assert_eq!(response.status(), Status::SeeOther); } fn test_body(optional_cookie: Option>, expected_body: String) { diff --git a/examples/session/Cargo.toml b/examples/session/Cargo.toml index 3a367bde..b989387e 100644 --- a/examples/session/Cargo.toml +++ b/examples/session/Cargo.toml @@ -6,7 +6,7 @@ edition = "2018" publish = false [dependencies] -rocket = { path = "../../core/lib", features = ["private-cookies"] } +rocket = { path = "../../core/lib" } [dependencies.rocket_contrib] path = "../../contrib/lib" diff --git a/examples/session/src/main.rs b/examples/session/src/main.rs index 5769a99e..cc18988d 100644 --- a/examples/session/src/main.rs +++ b/examples/session/src/main.rs @@ -7,7 +7,7 @@ use std::collections::HashMap; use rocket::outcome::IntoOutcome; use rocket::request::{self, Form, FlashMessage, FromRequest, Request}; use rocket::response::{Redirect, Flash}; -use rocket::http::{Cookie, Cookies}; +use rocket::http::{Cookie, CookieJar}; use rocket_contrib::templates::Template; #[derive(FromForm)] @@ -33,7 +33,7 @@ impl<'a, 'r> FromRequest<'a, 'r> for User { } #[post("/login", data = "")] -fn login(mut cookies: Cookies<'_>, login: Form) -> Result> { +fn login(cookies: &CookieJar<'_>, login: Form) -> Result> { if login.username == "Sergio" && login.password == "password" { cookies.add_private(Cookie::new("user_id", 1.to_string())); Ok(Redirect::to(uri!(index))) @@ -43,7 +43,7 @@ fn login(mut cookies: Cookies<'_>, login: Form) -> Result) -> Flash { +fn logout(cookies: &CookieJar<'_>) -> Flash { cookies.remove_private(Cookie::named("user_id")); Flash::success(Redirect::to(uri!(login_page)), "Successfully logged out.") } diff --git a/scripts/test.sh b/scripts/test.sh index 032ba022..45bcb60f 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -106,7 +106,7 @@ if [ "$1" = "--contrib" ]; then popd > /dev/null 2>&1 elif [ "$1" = "--core" ]; then FEATURES=( - private-cookies # this is already tested since it's the default feature + secrets tls ) @@ -117,7 +117,6 @@ elif [ "$1" = "--core" ]; then for feature in "${FEATURES[@]}"; do echo ":: Building and testing core [${feature}]..." - $CARGO test --no-default-features --features "${feature}" done diff --git a/site/guide/4-requests.md b/site/guide/4-requests.md index f23629ac..68c3b5de 100644 --- a/site/guide/4-requests.md +++ b/site/guide/4-requests.md @@ -438,7 +438,7 @@ more about request guards and implementing them, see the [`FromRequest`] documentation. [`FromRequest`]: @api/rocket/request/trait.FromRequest.html -[`Cookies`]: @api/rocket/http/enum.Cookies.html +[`CookieJar`]: @api/rocket/http/struct.CookieJar.html ### Custom Guards @@ -568,34 +568,33 @@ it always succeeds. The user is redirected to a log in page. ## Cookies -[`Cookies`] is an important, built-in request guard: it allows you to get, set, -and remove cookies. Because `Cookies` is a request guard, an argument of its -type can simply be added to a handler: +A reference to a [`CookieJar`] is an important, built-in request guard: it +allows you to get, set, and remove cookies. Because `&CookieJar` is a request +guard, an argument of its type can simply be added to a handler: ```rust # #[macro_use] extern crate rocket; # fn main() {} -use rocket::http::Cookies; +use rocket::http::CookieJar; #[get("/")] -fn index(cookies: Cookies) -> Option { - cookies.get("message") - .map(|value| format!("Message: {}", value)) +fn index(cookies: &CookieJar<'_>) -> Option { + cookies.get("message").map(|crumb| format!("Message: {}", crumb.value())) } ``` This results in the incoming request's cookies being accessible from the handler. The example above retrieves a cookie named `message`. Cookies can also -be set and removed using the `Cookies` guard. The [cookies example] on GitHub -illustrates further use of the `Cookies` type to get and set cookies, while the -[`Cookies`] documentation contains complete usage information. +be set and removed using the `CookieJar` guard. The [cookies example] on GitHub +illustrates further use of the `CookieJar` type to get and set cookies, while +the [`CookieJar`] documentation contains complete usage information. [cookies example]: @example/cookies ### Private Cookies -Cookies added via the [`Cookies::add()`] method are set _in the clear._ In other -words, the value set is visible by the client. For sensitive data, Rocket +Cookies added via the [`CookieJar::add()`] method are set _in the clear._ In +other words, the value set is visible to the client. For sensitive data, Rocket provides _private_ cookies. Private cookies are just like regular cookies except that they are encrypted @@ -612,37 +611,25 @@ methods are suffixed with `_private`. These methods are: [`get_private`], # #[macro_use] extern crate rocket; # fn main() {} -use rocket::http::{Cookie, Cookies}; +use rocket::http::{Cookie, CookieJar}; use rocket::response::{Flash, Redirect}; /// Retrieve the user's ID, if any. #[get("/user_id")] -fn user_id(mut cookies: Cookies) -> Option { +fn user_id(cookies: &CookieJar<'_>) -> Option { cookies.get_private("user_id") - .map(|cookie| format!("User ID: {}", cookie.value())) + .map(|crumb| format!("User ID: {}", crumb.value())) } /// Remove the `user_id` cookie. #[post("/logout")] -fn logout(mut cookies: Cookies) -> Flash { +fn logout(cookies: &CookieJar<'_>) -> Flash { cookies.remove_private(Cookie::named("user_id")); Flash::success(Redirect::to("/"), "Successfully logged out.") } ``` -[`Cookies::add()`]: @api/rocket/http/enum.Cookies.html#method.add - -Support for private cookies, which depends on the [`ring`] library, can be -omitted at build time by disabling Rocket's default features, in-turn disabling -the default `private-cookies` feature. To do so, modify your `Cargo.toml` file -so that you depend on `rocket` as follows: - -```toml -[dependencies] -rocket = { version = "0.5.0-dev", default-features = false } -``` - -[`ring`]: https://github.com/briansmith/ring +[`CookieJar::add()`]: @api/rocket/http/struct.CookieJar.html#method.add ### Secret Key @@ -662,81 +649,9 @@ can be generated with the command `openssl rand -base64 32`. For more information on configuration, see the [Configuration](../configuration) section of the guide. -[`get_private`]: @api/rocket/http/enum.Cookies.html#method.get_private -[`add_private`]: @api/rocket/http/enum.Cookies.html#method.add_private -[`remove_private`]: @api/rocket/http/enum.Cookies.html#method.remove_private - -### One-At-A-Time - -For safety reasons, Rocket currently requires that at most one `Cookies` -instance be active at a time. It's uncommon to run into this restriction, but it -can be confusing to handle if it does crop up. - -If this does happen, Rocket will emit messages to the console that look as -follows: - -```text -=> Error: Multiple `Cookies` instances are active at once. -=> An instance of `Cookies` must be dropped before another can be retrieved. -=> Warning: The retrieved `Cookies` instance will be empty. -``` - -The messages will be emitted when a violating handler is called. The issue can -be resolved by ensuring that two instances of `Cookies` cannot be active at once -due to the offending handler. A common error is to have a handler that uses a -`Cookies` request guard as well as a `Custom` request guard that retrieves -`Cookies`, as so: - -```rust -# #[macro_use] extern crate rocket; -# fn main() {} -# use rocket::http::Cookies; -# type Custom = rocket::http::Method; - -#[get("/")] -fn bad(cookies: Cookies, custom: Custom) { /* .. */ } -``` - -Because the `cookies` guard will fire before the `custom` guard, the `custom` -guard will retrieve an instance of `Cookies` when one already exists for -`cookies`. This scenario can be fixed by simply swapping the order of the -guards: - -```rust -# #[macro_use] extern crate rocket; -# fn main() {} -# use rocket::http::Cookies; -# type Custom = rocket::http::Method; - -#[get("/")] -fn good(custom: Custom, cookies: Cookies) { /* .. */ } -``` - -When using request guards that modify cookies on-demand, such as -`FlashMessage`, a similar problem occurs. The fix in this case is to `drop` the -`Cookies` instance before accessing the `FlashMessage`. - -```rust -# #[macro_use] extern crate rocket; -# fn main() {} - -# use rocket::http::Cookies; -use rocket::request::FlashMessage; - -#[get("/")] -fn bad(cookies: Cookies, flash: FlashMessage) { - // Oh no! `flash` holds a reference to `Cookies` too! - let msg = flash.msg(); -} - -#[get("/")] -fn good(cookies: Cookies, flash: FlashMessage) { - std::mem::drop(cookies); - - // Now, `flash` holds an _exclusive_ reference to `Cookies`. Whew. - let msg = flash.msg(); -} -``` +[`get_private`]: @api/rocket/http/struct.CookieJar.html#method.get_private +[`add_private`]: @api/rocket/http/struct.CookieJar.html#method.add_private +[`remove_private`]: @api/rocket/http/struct.CookieJar.html#method.remove_private ## Format