mirror of https://github.com/rwf2/Rocket.git
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:
parent
549c9241c4
commit
52320020bc
|
@ -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)
|
||||
// }
|
||||
|
|
@ -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,)+) => {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>) { }
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
fn main() {
|
||||
if let Some(true) = version_check::is_feature_flaggable() {
|
||||
println!("cargo:rustc-cfg=nightly");
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
#[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<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)
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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::*;
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>>
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<'_>>);
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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`.
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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!");
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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.")
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue