Use thread-safe 'CookieJar's.

The user-facing changes effected by this commit are:

  * The 'http::Cookies<'_>' guard is now '&http::CookieJar<'_>'.
  * The "one-at-a-time" jar restriction is no longer imposed.
  * 'CookieJar' retrieval methods return 'http::CookieCrumb'.
  * The 'private-cookies' feature is now called 'secrets'.
  * Docs flag private cookie methods with feature cfg.
  * Local, async request dispatching is never serialized.
  * 'Client::cookies()' returns the tracked 'CookieJar'.
  * 'LocalResponse::cookies()' returns a 'CookieJar'.
  * 'Response::cookies()' returns an 'impl Iterator'.
  * A path of '/' is set by default on all cookies.
  * 'SameSite=strict' is set by default on all cookies.
  * 'LocalRequest::cookies()' accepts any 'Cookie' iterator.
  * The 'Debug' impl for 'Request' prints the cookie jar.

Resolves #1332.
This commit is contained in:
Sergio Benitez 2020-07-22 12:21:19 -07:00
parent 549c9241c4
commit 52320020bc
36 changed files with 564 additions and 558 deletions

View File

@ -1,10 +0,0 @@
// #[post("/<_name>?<_query>", format = "application/json", data = "<user>", rank = 2)]
// fn get(
// _name: &RawStr,
// _query: User,
// user: Form<User>,
// _cookies: Cookies
// ) -> String {
// format!("{}:{}", user.name, user.nickname)
// }

View File

@ -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("/<id>")]
fn guard_1(cookies: Cookies<'_>, id: i32) { }
fn guard_1(cookies: &CookieJar<'_>, id: i32) { }
#[post("/<id>/<name>")]
fn guard_2(name: String, cookies: Cookies<'_>, id: i32) { }
fn guard_2(name: String, cookies: &CookieJar<'_>, id: i32) { }
#[post("/a/<id>/hi/<name>/hey")]
fn guard_3(id: i32, name: String, cookies: Cookies<'_>) { }
fn guard_3(id: i32, name: String, cookies: &CookieJar<'_>) { }
#[post("/<id>", data = "<form>")]
fn no_uri_display_okay(id: i32, form: Form<Second>) { }
@ -69,7 +69,7 @@ fn complex<'r>(
query: Form<User<'r>>,
user: Form<User<'r>>,
bar: &RawStr,
cookies: Cookies<'_>
cookies: &CookieJar<'_>
) { }
#[post("/a/<path..>")]
@ -79,7 +79,7 @@ fn segments(path: PathBuf) { }
fn param_and_segments(path: PathBuf, id: usize) { }
#[post("/a/<id>/then/<path..>")]
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,)+) => {

View File

@ -14,7 +14,9 @@ error[E0277]: the trait bound `rocket::http::Header<'_>: std::convert::From<u8>`
|
= help: the following implementations were found:
<rocket::http::Header<'static> as std::convert::From<&rocket::http::Cookie<'_>>>
<rocket::http::Header<'static> as std::convert::From<&rocket::http::CookieCrumb>>
<rocket::http::Header<'static> as std::convert::From<rocket::http::Cookie<'_>>>
<rocket::http::Header<'static> as std::convert::From<rocket::http::CookieCrumb>>
= note: required because of the requirements on the impl of `std::convert::Into<rocket::http::Header<'_>>` 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<u8>`
|
= help: the following implementations were found:
<rocket::http::Header<'static> as std::convert::From<&rocket::http::Cookie<'_>>>
<rocket::http::Header<'static> as std::convert::From<&rocket::http::CookieCrumb>>
<rocket::http::Header<'static> as std::convert::From<rocket::http::Cookie<'_>>>
<rocket::http::Header<'static> as std::convert::From<rocket::http::CookieCrumb>>
= note: required because of the requirements on the impl of `std::convert::Into<rocket::http::Header<'_>>` for `u8`
error[E0277]: the trait bound `rocket::http::Header<'_>: std::convert::From<std::string::String>` is not satisfied
@ -44,7 +48,9 @@ error[E0277]: the trait bound `rocket::http::Header<'_>: std::convert::From<std:
|
= help: the following implementations were found:
<rocket::http::Header<'static> as std::convert::From<&rocket::http::Cookie<'_>>>
<rocket::http::Header<'static> as std::convert::From<&rocket::http::CookieCrumb>>
<rocket::http::Header<'static> as std::convert::From<rocket::http::Cookie<'_>>>
<rocket::http::Header<'static> as std::convert::From<rocket::http::CookieCrumb>>
= note: required because of the requirements on the impl of `std::convert::Into<rocket::http::Header<'_>>` for `std::string::String`
error[E0277]: the trait bound `usize: rocket::response::Responder<'_, '_>` is not satisfied

View File

@ -14,7 +14,9 @@ error[E0277]: the trait bound `rocket::http::Header<'_>: std::convert::From<u8>`
|
= help: the following implementations were found:
<rocket::http::Header<'static> as std::convert::From<&rocket::http::Cookie<'_>>>
<rocket::http::Header<'static> as std::convert::From<&rocket::http::CookieCrumb>>
<rocket::http::Header<'static> as std::convert::From<rocket::http::Cookie<'_>>>
<rocket::http::Header<'static> as std::convert::From<rocket::http::CookieCrumb>>
= note: required because of the requirements on the impl of `std::convert::Into<rocket::http::Header<'_>>` 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<u8>`
|
= help: the following implementations were found:
<rocket::http::Header<'static> as std::convert::From<&rocket::http::Cookie<'_>>>
<rocket::http::Header<'static> as std::convert::From<&rocket::http::CookieCrumb>>
<rocket::http::Header<'static> as std::convert::From<rocket::http::Cookie<'_>>>
<rocket::http::Header<'static> as std::convert::From<rocket::http::CookieCrumb>>
= note: required because of the requirements on the impl of `std::convert::Into<rocket::http::Header<'_>>` for `u8`
error[E0277]: the trait bound `rocket::http::Header<'_>: std::convert::From<std::string::String>` is not satisfied
@ -44,7 +48,9 @@ error[E0277]: the trait bound `rocket::http::Header<'_>: std::convert::From<std:
|
= help: the following implementations were found:
<rocket::http::Header<'static> as std::convert::From<&rocket::http::Cookie<'_>>>
<rocket::http::Header<'static> as std::convert::From<&rocket::http::CookieCrumb>>
<rocket::http::Header<'static> as std::convert::From<rocket::http::Cookie<'_>>>
<rocket::http::Header<'static> as std::convert::From<rocket::http::CookieCrumb>>
= note: required because of the requirements on the impl of `std::convert::Into<rocket::http::Header<'_>>` for `std::string::String`
error[E0277]: the trait bound `usize: rocket::response::Responder<'_, '_>` is not satisfied

View File

@ -1,15 +1,15 @@
#[macro_use] extern crate rocket;
use rocket::http::{Cookies, RawStr};
use rocket::http::{CookieJar, RawStr};
#[post("/<id>")]
fn has_one(id: i32) { }
#[post("/<id>")]
fn has_one_guarded(cookies: Cookies, id: i32) { }
fn has_one_guarded(cookies: &CookieJar<'_>, id: i32) { }
#[post("/<id>?<name>")]
fn has_two(cookies: Cookies, id: i32, name: String) { }
fn has_two(cookies: &CookieJar<'_>, id: i32, name: String) { }
#[post("/<id>/<name>")]
fn optionals(id: Option<i32>, name: Result<String, &RawStr>) { }

View File

@ -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"

5
core/http/build.rs Normal file
View File

@ -0,0 +1,5 @@
fn main() {
if let Some(true) = version_check::is_feature_flaggable() {
println!("cargo:rustc-cfg=nightly");
}
}

View File

@ -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<String> {
/// cookies.get("message").map(|c| format!("Message: {}", c.value()))
/// fn message(jar: &CookieJar<'_>) -> Option<String> {
/// 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<Self, Self::Error> {
/// 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<dyn FnOnce(CookieJar) + Send + 'a>),
#[doc(hidden)]
Empty(CookieJar)
}
impl<'a> Cookies<'a> {
/// WARNING: This is unstable! Do not use this method outside of Rocket!
#[inline]
#[doc(hidden)]
pub fn new<F: FnOnce(CookieJar) + Send + 'a>(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<'static>> {
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)
}
#[derive(Clone)]
pub struct CookieJar<'a> {
jar: cookie::CookieJar,
key: &'a Key,
}
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<CookieCrumb> {
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<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!
#[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<Cookie<'static>> {
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<Cookie<'static>> {
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<Item = CookieCrumb> + '_ {
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<CookieCrumb> 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())
}
}

View File

@ -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};

View File

@ -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"

View File

@ -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");
}
}

View File

@ -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::*;

View File

@ -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)
}
}

View File

@ -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.

View File

@ -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

View File

@ -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<RwLock<CookieJar>>,
pub(in super) tracked: bool,
pub(in super) cookies: cookie::CookieJar,
}
impl Client {
@ -56,13 +54,9 @@ impl Client {
tracked: bool
) -> Result<Client, LaunchError> {
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<Cow<'u, str>>

View File

@ -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<u8>,
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());
}
}

View File

@ -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<Request<'c>>,
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<String> {
self.response.body_string().await
}

View File

@ -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,

View File

@ -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<String> {
self.client.block_on(self.inner._into_string())
}

View File

@ -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);

View File

@ -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<crate::http::Cookie<'_>>) -> Self {
pub fn cookies<'a, C>(self, cookies: C) -> Self
where C: IntoIterator<Item = crate::http::Cookie<'a>>
{
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

View File

@ -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<crate::http::Cookie<'_>>);
/// 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<'_>>);

View File

@ -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<S, E> = outcome::Outcome<S, (Status, E), ()>;
@ -140,10 +140,10 @@ impl<S, E> IntoOutcome<S, (Status, E), ()> for Result<S, E> {
/// 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<S, E> IntoOutcome<S, (Status, E), ()> for Result<S, E> {
///
/// ```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<S, E> IntoOutcome<S, (Status, E), ()> for Result<S, E> {
///
/// #[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<S, E> IntoOutcome<S, (Status, E), ()> for Result<S, E> {
///
/// ```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<S, E> IntoOutcome<S, (Status, E), ()> for Result<S, E> {
/// }
/// }
/// }
/// # }
/// # } // 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<Self, Self::Error> {

View File

@ -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<SmallVec<[IndexedFormItem; 6]>>,
pub route: RwLock<Option<&'r Route>>,
pub cookies: Mutex<Option<CookieJar>>,
pub route: Atomic<Option<&'r Route>>,
pub cookies: CookieJar<'r>,
pub accept: Storage<Option<Accept>>,
pub content_type: Storage<Option<ContentType>>,
pub cache: Arc<Container>,
}
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 {

View File

@ -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<Cookie<'_>> {
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<Item = Cookie<'_>> {
self.headers()
.get("Set-Cookie")
.filter_map(|header| Cookie::parse_encoded(header).ok())
}
/// Returns a [`HeaderMap`] of all of the headers in `self`.

View File

@ -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() {

View File

@ -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");
}
}

View File

@ -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<String> {
#[rocket::get("/")]
fn return_private_cookie(cookies: &CookieJar<'_>) -> Option<String> {
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() {

View File

@ -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!");
}
}

View File

@ -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 = "<message>")]
fn submit(mut cookies: Cookies<'_>, message: Form<Message>) -> Redirect {
fn submit(cookies: &CookieJar<'_>, message: Form<Message>) -> 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 {

View File

@ -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<Cookie<'static>>, expected_body: String) {

View File

@ -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"

View File

@ -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 = "<login>")]
fn login(mut cookies: Cookies<'_>, login: Form<Login>) -> Result<Redirect, Flash<Redirect>> {
fn login(cookies: &CookieJar<'_>, login: Form<Login>) -> Result<Redirect, Flash<Redirect>> {
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<Login>) -> Result<Redirect, Flash
}
#[post("/logout")]
fn logout(mut cookies: Cookies<'_>) -> Flash<Redirect> {
fn logout(cookies: &CookieJar<'_>) -> Flash<Redirect> {
cookies.remove_private(Cookie::named("user_id"));
Flash::success(Redirect::to(uri!(login_page)), "Successfully logged out.")
}

View File

@ -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

View File

@ -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<String> {
cookies.get("message")
.map(|value| format!("Message: {}", value))
fn index(cookies: &CookieJar<'_>) -> Option<String> {
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<String> {
fn user_id(cookies: &CookieJar<'_>) -> Option<String> {
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<Redirect> {
fn logout(cookies: &CookieJar<'_>) -> Flash<Redirect> {
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