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 std::path::PathBuf;
|
||||||
|
|
||||||
use rocket::http::{RawStr, Cookies};
|
use rocket::http::{RawStr, CookieJar};
|
||||||
use rocket::http::uri::{FromUriParam, Query};
|
use rocket::http::uri::{FromUriParam, Query};
|
||||||
use rocket::request::Form;
|
use rocket::request::Form;
|
||||||
|
|
||||||
|
@ -51,13 +51,13 @@ fn simple4_flipped(name: String, id: i32) { }
|
||||||
fn unused_param(used: i32, _unused: i32) { }
|
fn unused_param(used: i32, _unused: i32) { }
|
||||||
|
|
||||||
#[post("/<id>")]
|
#[post("/<id>")]
|
||||||
fn guard_1(cookies: Cookies<'_>, id: i32) { }
|
fn guard_1(cookies: &CookieJar<'_>, id: i32) { }
|
||||||
|
|
||||||
#[post("/<id>/<name>")]
|
#[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")]
|
#[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>")]
|
#[post("/<id>", data = "<form>")]
|
||||||
fn no_uri_display_okay(id: i32, form: Form<Second>) { }
|
fn no_uri_display_okay(id: i32, form: Form<Second>) { }
|
||||||
|
@ -69,7 +69,7 @@ fn complex<'r>(
|
||||||
query: Form<User<'r>>,
|
query: Form<User<'r>>,
|
||||||
user: Form<User<'r>>,
|
user: Form<User<'r>>,
|
||||||
bar: &RawStr,
|
bar: &RawStr,
|
||||||
cookies: Cookies<'_>
|
cookies: &CookieJar<'_>
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
#[post("/a/<path..>")]
|
#[post("/a/<path..>")]
|
||||||
|
@ -79,7 +79,7 @@ fn segments(path: PathBuf) { }
|
||||||
fn param_and_segments(path: PathBuf, id: usize) { }
|
fn param_and_segments(path: PathBuf, id: usize) { }
|
||||||
|
|
||||||
#[post("/a/<id>/then/<path..>")]
|
#[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 {
|
macro_rules! assert_uri_eq {
|
||||||
($($uri:expr => $expected:expr,)+) => {
|
($($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:
|
= 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::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::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`
|
= 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
|
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:
|
= 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::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::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`
|
= 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
|
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:
|
= 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::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::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`
|
= 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
|
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:
|
= 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::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::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`
|
= 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
|
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:
|
= 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::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::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`
|
= 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
|
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:
|
= 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::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::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`
|
= 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
|
error[E0277]: the trait bound `usize: rocket::response::Responder<'_, '_>` is not satisfied
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
#[macro_use] extern crate rocket;
|
#[macro_use] extern crate rocket;
|
||||||
|
|
||||||
use rocket::http::{Cookies, RawStr};
|
use rocket::http::{CookieJar, RawStr};
|
||||||
|
|
||||||
#[post("/<id>")]
|
#[post("/<id>")]
|
||||||
fn has_one(id: i32) { }
|
fn has_one(id: i32) { }
|
||||||
|
|
||||||
#[post("/<id>")]
|
#[post("/<id>")]
|
||||||
fn has_one_guarded(cookies: Cookies, id: i32) { }
|
fn has_one_guarded(cookies: &CookieJar<'_>, id: i32) { }
|
||||||
|
|
||||||
#[post("/<id>?<name>")]
|
#[post("/<id>?<name>")]
|
||||||
fn has_two(cookies: Cookies, id: i32, name: String) { }
|
fn has_two(cookies: &CookieJar<'_>, id: i32, name: String) { }
|
||||||
|
|
||||||
#[post("/<id>/<name>")]
|
#[post("/<id>/<name>")]
|
||||||
fn optionals(id: Option<i32>, name: Result<String, &RawStr>) { }
|
fn optionals(id: Option<i32>, name: Result<String, &RawStr>) { }
|
||||||
|
|
|
@ -30,14 +30,21 @@ indexmap = "1.0"
|
||||||
state = "0.4"
|
state = "0.4"
|
||||||
tokio-rustls = { version = "0.14.0", optional = true }
|
tokio-rustls = { version = "0.14.0", optional = true }
|
||||||
tokio = { version = "0.2.9", features = ["sync", "tcp", "time"] }
|
tokio = { version = "0.2.9", features = ["sync", "tcp", "time"] }
|
||||||
cookie = { version = "0.14.0", features = ["percent-encode"] }
|
|
||||||
unicode-xid = "0.2"
|
unicode-xid = "0.2"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
ref-cast = "1.0"
|
ref-cast = "1.0"
|
||||||
|
|
||||||
|
[dependencies.cookie]
|
||||||
|
git = "https://github.com/SergioBenitez/cookie-rs.git"
|
||||||
|
rev = "3795f2e"
|
||||||
|
features = ["percent-encode"]
|
||||||
|
|
||||||
[dependencies.pear]
|
[dependencies.pear]
|
||||||
git = "https://github.com/SergioBenitez/Pear.git"
|
git = "https://github.com/SergioBenitez/Pear.git"
|
||||||
rev = "4b68055"
|
rev = "4b68055"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
rocket = { version = "0.5.0-dev", path = "../lib" }
|
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 std::fmt;
|
||||||
|
|
||||||
use crate::Header;
|
use crate::Header;
|
||||||
use cookie::Delta;
|
|
||||||
|
|
||||||
|
pub use cookie::{Cookie, CookieCrumb, SameSite, Iter};
|
||||||
#[doc(hidden)] pub use self::key::*;
|
#[doc(hidden)] pub use self::key::*;
|
||||||
pub use cookie::{Cookie, CookieJar, SameSite};
|
|
||||||
|
|
||||||
/// Types and methods to manage a `Key` when private cookies are enabled.
|
/// Types and methods to manage a `Key` when private cookies are enabled.
|
||||||
#[cfg(feature = "private-cookies")]
|
#[cfg(feature = "private-cookies")]
|
||||||
|
@ -27,18 +26,17 @@ mod key {
|
||||||
|
|
||||||
/// Collection of one or more HTTP cookies.
|
/// Collection of one or more HTTP cookies.
|
||||||
///
|
///
|
||||||
/// The `Cookies` type allows for retrieval of cookies from an incoming request
|
/// The `CookieJar` type allows for retrieval of cookies from an incoming
|
||||||
/// as well as modifications to cookies to be reflected by Rocket on outgoing
|
/// request as well as modifications to cookies to be reflected by Rocket on
|
||||||
/// responses. `Cookies` is a smart-pointer; it internally borrows and refers to
|
/// outgoing responses.
|
||||||
/// the collection of cookies active during a request's life-cycle.
|
|
||||||
///
|
///
|
||||||
/// # Usage
|
/// # Usage
|
||||||
///
|
///
|
||||||
/// A type of `Cookies` can be retrieved via its `FromRequest` implementation as
|
/// A type of `&CookieJar` can be retrieved via its `FromRequest` implementation
|
||||||
/// a request guard or via the [`Request::cookies()`] method. Individual cookies
|
/// as a request guard or via the [`Request::cookies()`] method. Individual
|
||||||
/// can be retrieved via the [`get()`] and [`get_private()`] methods. Cookies
|
/// cookies can be retrieved via the [`get()`] and [`get_private()`] methods.
|
||||||
/// can be added or removed via the [`add()`], [`add_private()`], [`remove()`],
|
/// Cookies can be added or removed via the [`add()`], [`add_private()`],
|
||||||
/// and [`remove_private()`] methods.
|
/// [`remove()`], and [`remove_private()`] methods.
|
||||||
///
|
///
|
||||||
/// [`Request::cookies()`]: rocket::Request::cookies()
|
/// [`Request::cookies()`]: rocket::Request::cookies()
|
||||||
/// [`get()`]: #method.get
|
/// [`get()`]: #method.get
|
||||||
|
@ -50,27 +48,27 @@ mod key {
|
||||||
///
|
///
|
||||||
/// ## Examples
|
/// ## Examples
|
||||||
///
|
///
|
||||||
/// The following short snippet shows `Cookies` being used as a request guard in
|
/// The following example shows `&CookieJar` being used as a request guard in a
|
||||||
/// a handler to retrieve the value of a "message" cookie.
|
/// handler to retrieve the value of a "message" cookie.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # #[macro_use] extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// use rocket::http::Cookies;
|
/// use rocket::http::CookieJar;
|
||||||
///
|
///
|
||||||
/// #[get("/message")]
|
/// #[get("/message")]
|
||||||
/// fn message(cookies: Cookies) -> Option<String> {
|
/// fn message(jar: &CookieJar<'_>) -> Option<String> {
|
||||||
/// cookies.get("message").map(|c| format!("Message: {}", c.value()))
|
/// jar.get("message").map(|c| format!("Message: {}", c.value()))
|
||||||
/// }
|
/// }
|
||||||
/// # fn main() { }
|
/// # fn main() { }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// The following snippet shows `Cookies` being retrieved from a `Request` in a
|
/// The following snippet shows `&CookieJar` being retrieved from a `Request` in
|
||||||
/// custom request guard implementation for `User`. A [private cookie]
|
/// 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
|
/// 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
|
/// as an integer, a `User` structure is validated. Otherwise, the guard
|
||||||
/// forwards.
|
/// forwards.
|
||||||
///
|
///
|
||||||
/// [private cookie]: Cookies::add_private()
|
/// [private cookie]: #method.add_private
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # #[macro_use] extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
|
@ -89,7 +87,7 @@ mod key {
|
||||||
/// async fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
|
/// async fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
|
||||||
/// request.cookies()
|
/// request.cookies()
|
||||||
/// .get_private("user_id")
|
/// .get_private("user_id")
|
||||||
/// .and_then(|cookie| cookie.value().parse().ok())
|
/// .and_then(|c| c.value().parse().ok())
|
||||||
/// .map(|id| User(id))
|
/// .map(|id| User(id))
|
||||||
/// .or_forward(())
|
/// .or_forward(())
|
||||||
/// }
|
/// }
|
||||||
|
@ -106,7 +104,7 @@ mod key {
|
||||||
/// manufactured by clients. If you prefer, you can think of private cookies as
|
/// manufactured by clients. If you prefer, you can think of private cookies as
|
||||||
/// being signed and encrypted.
|
/// 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
|
/// collection via the [`get_private()`], [`add_private()`], and
|
||||||
/// [`remove_private()`] methods.
|
/// [`remove_private()`] methods.
|
||||||
///
|
///
|
||||||
|
@ -125,194 +123,82 @@ mod key {
|
||||||
/// is usually done through tools like `openssl`. Using `openssl`, for instance,
|
/// 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
|
/// a 256-bit base64 key can be generated with the command `openssl rand -base64
|
||||||
/// 32`.
|
/// 32`.
|
||||||
pub enum Cookies<'a> {
|
#[derive(Clone)]
|
||||||
#[doc(hidden)]
|
pub struct CookieJar<'a> {
|
||||||
Jarred(CookieJar, &'a Key, Box<dyn FnOnce(CookieJar) + Send + 'a>),
|
jar: cookie::CookieJar,
|
||||||
#[doc(hidden)]
|
key: &'a Key,
|
||||||
Empty(CookieJar)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Cookies<'a> {
|
impl<'a> CookieJar<'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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a reference to the `Cookie` inside this container with the name
|
/// Returns a reference to the `Cookie` inside this container with the name
|
||||||
/// `name`. If no such cookie exists, returns `None`.
|
/// `name`. If no such cookie exists, returns `None`.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// use rocket::http::Cookies;
|
/// use rocket::http::{Cookie, CookieJar};
|
||||||
///
|
///
|
||||||
/// fn handler(cookies: Cookies) {
|
/// #[get("/")]
|
||||||
/// let cookie = cookies.get("name");
|
/// fn handler(jar: &CookieJar<'_>) {
|
||||||
|
/// let cookie = jar.get("name");
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn get(&self, name: &str) -> Option<&Cookie<'static>> {
|
pub fn get(&self, name: &str) -> Option<CookieCrumb> {
|
||||||
match *self {
|
self.jar.get(name)
|
||||||
Cookies::Jarred(ref jar, _, _) => jar.get(name),
|
|
||||||
Cookies::Empty(_) => None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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
|
/// Returns a reference to the `Cookie` inside this collection with the name
|
||||||
/// `name` and authenticates and decrypts the cookie's value, returning a
|
/// `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` with the decrypted value. If the cookie cannot be found, or the
|
||||||
/// cookie fails to authenticate or decrypt, `None` is returned.
|
/// cookie fails to authenticate or decrypt, `None` is returned.
|
||||||
///
|
///
|
||||||
/// This method is only available when the `private-cookies` feature is
|
/// # Example
|
||||||
/// enabled.
|
///
|
||||||
|
/// ```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
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// use rocket::http::Cookies;
|
/// use rocket::http::{Cookie, SameSite, CookieJar};
|
||||||
///
|
///
|
||||||
/// fn handler(mut cookies: Cookies) {
|
/// #[get("/")]
|
||||||
/// let cookie = cookies.get_private("name");
|
/// 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>> {
|
pub fn add(&self, mut cookie: Cookie<'static>) {
|
||||||
match *self {
|
Self::set_defaults(&mut cookie);
|
||||||
Cookies::Jarred(ref mut jar, key, _) => jar.private(key).get(name),
|
self.jar.add(cookie)
|
||||||
Cookies::Empty(_) => None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds `cookie` to the collection. The cookie's value is encrypted with
|
/// 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
|
/// [`get_private`](#method.get_private) and removed using
|
||||||
/// [`remove_private`](#method.remove_private).
|
/// [`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`:
|
/// set on `cookie` before being added to `self`:
|
||||||
///
|
///
|
||||||
/// * `path`: `"/"`
|
/// * `path`: `"/"`
|
||||||
|
@ -332,51 +218,176 @@ impl Cookies<'_> {
|
||||||
/// These defaults ensure maximum usability and security. For additional
|
/// These defaults ensure maximum usability and security. For additional
|
||||||
/// security, you may wish to set the `secure` flag.
|
/// security, you may wish to set the `secure` flag.
|
||||||
///
|
///
|
||||||
/// This method is only available when the `private-cookies` feature is
|
/// # Example
|
||||||
/// enabled.
|
///
|
||||||
|
/// ```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
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// use rocket::http::{Cookie, Cookies};
|
/// use rocket::http::{Cookie, CookieJar};
|
||||||
///
|
///
|
||||||
/// fn handler(mut cookies: Cookies) {
|
/// #[get("/")]
|
||||||
/// cookies.add_private(Cookie::new("name", "value"));
|
/// fn handler(jar: &CookieJar<'_>) {
|
||||||
|
/// jar.remove(Cookie::named("name"));
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn add_private(&mut self, mut cookie: Cookie<'static>) {
|
pub fn remove(&self, mut cookie: Cookie<'static>) {
|
||||||
if let Cookies::Jarred(ref mut jar, key, _) = *self {
|
if cookie.path().is_none() {
|
||||||
Cookies::set_private_defaults(&mut cookie);
|
cookie.set_path("/");
|
||||||
jar.private(key).add(cookie)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.
|
/// Adds an original, private `cookie` to the collection.
|
||||||
/// WARNING: This is unstable! Do not use this method outside of Rocket!
|
#[inline(always)]
|
||||||
#[doc(hidden)]
|
#[cfg(feature = "private-cookies")]
|
||||||
pub fn add_original_private(&mut self, mut cookie: Cookie<'static>) {
|
#[cfg_attr(nightly, doc(cfg(feature = "secrets")))]
|
||||||
if let Cookies::Jarred(ref mut jar, key, _) = *self {
|
pub fn add_original_private(&self, cookie: Cookie<'static>) {
|
||||||
Cookies::set_private_defaults(&mut cookie);
|
self.jar.private(&*self.key).add_original(cookie);
|
||||||
jar.private(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
|
/// For each property mentioned below, this method checks if there is a
|
||||||
/// if there is a provided value and if there is none, sets
|
/// provided value and if there is none, sets a default value. Default
|
||||||
/// a default value.
|
/// values are:
|
||||||
/// Default values are:
|
|
||||||
///
|
///
|
||||||
/// * `path`: `"/"`
|
/// * `path`: `"/"`
|
||||||
/// * `SameSite`: `Strict`
|
/// * `SameSite`: `Strict`
|
||||||
/// * `HttpOnly`: `true`
|
/// * `HttpOnly`: `true`
|
||||||
/// * `Expires`: 1 week from now
|
/// * `Expires`: 1 week from now
|
||||||
///
|
///
|
||||||
|
#[cfg(feature = "private-cookies")]
|
||||||
|
#[cfg_attr(nightly, doc(cfg(feature = "secrets")))]
|
||||||
fn set_private_defaults(cookie: &mut Cookie<'static>) {
|
fn set_private_defaults(cookie: &mut Cookie<'static>) {
|
||||||
if cookie.path().is_none() {
|
if cookie.path().is_none() {
|
||||||
cookie.set_path("/");
|
cookie.set_path("/");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cookie.same_site().is_none() {
|
||||||
|
cookie.set_same_site(SameSite::Strict);
|
||||||
|
}
|
||||||
|
|
||||||
if cookie.http_only().is_none() {
|
if cookie.http_only().is_none() {
|
||||||
cookie.set_http_only(true);
|
cookie.set_http_only(true);
|
||||||
}
|
}
|
||||||
|
@ -384,48 +395,12 @@ impl Cookies<'_> {
|
||||||
if cookie.expires().is_none() {
|
if cookie.expires().is_none() {
|
||||||
cookie.set_expires(time::OffsetDateTime::now_utc() + time::Duration::weeks(1));
|
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 {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match *self {
|
self.jar.fmt(f)
|
||||||
Cookies::Jarred(ref jar, _, _) => jar.fmt(f),
|
|
||||||
Cookies::Empty(ref jar) => jar.fmt(f)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -440,3 +415,15 @@ impl From<&Cookie<'_>> for Header<'static> {
|
||||||
Header::new("Set-Cookie", cookie.encoded().to_string())
|
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"]
|
#![recursion_limit="512"]
|
||||||
|
|
||||||
|
#![cfg_attr(nightly, feature(doc_cfg))]
|
||||||
|
|
||||||
#![warn(rust_2018_idioms)]
|
#![warn(rust_2018_idioms)]
|
||||||
|
|
||||||
//! Types that map to concepts in HTTP.
|
//! Types that map to concepts in HTTP.
|
||||||
|
@ -44,13 +46,17 @@ pub mod uncased;
|
||||||
pub mod private {
|
pub mod private {
|
||||||
// We need to export these for codegen, but otherwise it's unnecessary.
|
// We need to export these for codegen, but otherwise it's unnecessary.
|
||||||
// TODO: Expose a `const fn` from ContentType when possible. (see RFC#1817)
|
// 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::parse::Indexed;
|
||||||
pub use crate::media_type::{MediaParams, Source};
|
pub use crate::media_type::{MediaParams, Source};
|
||||||
pub use smallvec::{SmallVec, Array};
|
pub use smallvec::{SmallVec, Array};
|
||||||
|
|
||||||
// This one we need to expose for core.
|
// These we need to expose for core.
|
||||||
pub use crate::cookies::{Key, CookieJar};
|
pub mod cookie {
|
||||||
|
pub use cookie::*;
|
||||||
|
pub use crate::cookies::Key;
|
||||||
|
}
|
||||||
|
|
||||||
|
// These as well.
|
||||||
pub use crate::listener::{Incoming, Listener, Connection, bind_tcp};
|
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::status::{Status, StatusClass};
|
||||||
pub use crate::header::{Header, HeaderMap};
|
pub use crate::header::{Header, HeaderMap};
|
||||||
pub use crate::raw_str::RawStr;
|
pub use crate::raw_str::RawStr;
|
||||||
|
|
||||||
pub use crate::media_type::MediaType;
|
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
|
all-features = true
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["private-cookies"]
|
default = ["secrets"]
|
||||||
tls = ["rocket_http/tls"]
|
tls = ["rocket_http/tls"]
|
||||||
private-cookies = ["rocket_http/private-cookies"]
|
secrets = ["rocket_http/private-cookies"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rocket_codegen = { version = "0.5.0-dev", path = "../codegen" }
|
rocket_codegen = { version = "0.5.0-dev", path = "../codegen" }
|
||||||
|
@ -41,6 +41,11 @@ ref-cast = "1.0"
|
||||||
atomic = "0.4"
|
atomic = "0.4"
|
||||||
ubyte = "0.9.1"
|
ubyte = "0.9.1"
|
||||||
|
|
||||||
|
[dependencies.cookie]
|
||||||
|
git = "https://github.com/SergioBenitez/cookie-rs.git"
|
||||||
|
rev = "3795f2e"
|
||||||
|
features = ["percent-encode"]
|
||||||
|
|
||||||
[dependencies.pear]
|
[dependencies.pear]
|
||||||
git = "https://github.com/SergioBenitez/Pear.git"
|
git = "https://github.com/SergioBenitez/Pear.git"
|
||||||
rev = "4b68055"
|
rev = "4b68055"
|
||||||
|
|
|
@ -22,4 +22,8 @@ fn main() {
|
||||||
println!("cargo:warning={}", "Rocket was unable to check rustc compiler compatibility.");
|
println!("cargo:warning={}", "Rocket was unable to check rustc compiler compatibility.");
|
||||||
println!("cargo:warning={}", "Build may fail due to incompatible rustc version.");
|
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::convert::AsRef;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
|
use crate::http::private::cookie::Key;
|
||||||
use crate::config::Environment::*;
|
use crate::config::Environment::*;
|
||||||
use crate::config::{Result, ConfigBuilder, Environment, ConfigError, LoggingLevel};
|
use crate::config::{Result, ConfigBuilder, Environment, ConfigError, LoggingLevel};
|
||||||
use crate::config::{FullConfig, Table, Value, Array, Datetime};
|
use crate::config::{FullConfig, Table, Value, Array, Datetime};
|
||||||
use crate::data::Limits;
|
use crate::data::Limits;
|
||||||
use crate::http::private::Key;
|
|
||||||
|
|
||||||
use super::custom_values::*;
|
use super::custom_values::*;
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ use std::fmt;
|
||||||
|
|
||||||
#[cfg(feature = "tls")] use crate::http::tls::{Certificate, PrivateKey};
|
#[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::config::{Result, Config, Value, ConfigError, LoggingLevel};
|
||||||
use crate::data::Limits;
|
use crate::data::Limits;
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ impl SecretKey {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn is_generated(&self) -> bool {
|
pub(crate) fn is_generated(&self) -> bool {
|
||||||
match *self {
|
match *self {
|
||||||
#[cfg(feature = "private-cookies")]
|
#[cfg(feature = "secrets")]
|
||||||
SecretKey::Generated(_) => true,
|
SecretKey::Generated(_) => true,
|
||||||
_ => false
|
_ => false
|
||||||
}
|
}
|
||||||
|
@ -31,15 +31,17 @@ impl SecretKey {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for SecretKey {
|
impl fmt::Display for SecretKey {
|
||||||
|
#[cfg(feature = "secrets")]
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
#[cfg(feature = "private-cookies")]
|
|
||||||
match *self {
|
match *self {
|
||||||
SecretKey::Generated(_) => write!(f, "generated"),
|
SecretKey::Generated(_) => write!(f, "generated"),
|
||||||
SecretKey::Provided(_) => write!(f, "provided"),
|
SecretKey::Provided(_) => write!(f, "provided"),
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "private-cookies"))]
|
#[cfg(not(feature = "secrets"))]
|
||||||
write!(f, "private-cookies disabled")
|
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::uri;
|
||||||
use crate::http::ext::IntoOwned;
|
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)]
|
#[derive(Debug)]
|
||||||
pub enum RouteUriError {
|
pub enum RouteUriError {
|
||||||
/// The base (mount point) or route path contains invalid segments.
|
/// 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_root_url = "https://api.rocket.rs/v0.5")]
|
||||||
#![doc(html_favicon_url = "https://rocket.rs/images/favicon.ico")]
|
#![doc(html_favicon_url = "https://rocket.rs/images/favicon.ico")]
|
||||||
#![doc(html_logo_url = "https://rocket.rs/images/logo-boxed.png")]
|
#![doc(html_logo_url = "https://rocket.rs/images/logo-boxed.png")]
|
||||||
|
#![cfg_attr(nightly, feature(doc_cfg))]
|
||||||
|
|
||||||
#![warn(rust_2018_idioms)]
|
#![warn(rust_2018_idioms)]
|
||||||
|
|
||||||
|
@ -35,21 +36,13 @@
|
||||||
//!
|
//!
|
||||||
//! ## Usage
|
//! ## Usage
|
||||||
//!
|
//!
|
||||||
//! First, depend on `rocket` in `Cargo.toml`:
|
//! Depend on `rocket` in `Cargo.toml`:
|
||||||
//!
|
//!
|
||||||
//! ```toml
|
//! ```toml
|
||||||
//! [dependencies]
|
//! [dependencies]
|
||||||
//! rocket = "0.5.0-dev"
|
//! 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
|
//! 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:
|
//! 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
|
//! ## Configuration
|
||||||
//!
|
//!
|
||||||
//! Rocket and Rocket libraries are configured via the `Rocket.toml` file and/or
|
//! 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 std::borrow::Cow;
|
||||||
|
|
||||||
use crate::local::asynchronous::{LocalRequest, LocalResponse};
|
use crate::local::asynchronous::{LocalRequest, LocalResponse};
|
||||||
use crate::rocket::{Rocket, Cargo};
|
use crate::rocket::{Rocket, Cargo};
|
||||||
use crate::http::{Method, private::CookieJar};
|
use crate::http::{private::cookie, Method};
|
||||||
use crate::error::LaunchError;
|
use crate::error::LaunchError;
|
||||||
|
|
||||||
/// An `async` client to construct and dispatch local requests.
|
/// An `async` client to construct and dispatch local requests.
|
||||||
|
@ -14,19 +13,17 @@ use crate::error::LaunchError;
|
||||||
///
|
///
|
||||||
/// ## Multithreaded Synchronization Pitfalls
|
/// ## Multithreaded Synchronization Pitfalls
|
||||||
///
|
///
|
||||||
/// Unlike its [`blocking`](crate::local::blocking) variant, this `async` `Client`
|
/// Unlike its [`blocking`](crate::local::blocking) variant, this `async`
|
||||||
/// implements `Sync`. However, using it in a multithreaded environment while
|
/// `Client` implements `Sync`. However, using it in a multithreaded environment
|
||||||
/// tracking cookies can result in surprising, non-deterministic behavior. This
|
/// while tracking cookies can result in surprising, non-deterministic behavior.
|
||||||
/// is because while cookie modifications are serialized, the exact ordering
|
/// This is because while cookie modifications are serialized, the ordering
|
||||||
/// depends on when requests are dispatched. Specifically, when cookie tracking
|
/// depends on the ordering of request dispatch.
|
||||||
/// is enabled, all request dispatches are serialized, which in-turn serializes
|
|
||||||
/// modifications to the internally tracked cookies.
|
|
||||||
///
|
///
|
||||||
/// If possible, refrain from sharing a single instance of `Client` across
|
/// If possible, refrain from sharing a single instance of `Client` across
|
||||||
/// multiple threads. Instead, prefer to create a unique instance of `Client`
|
/// 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
|
/// per thread. If this is not possible, ensure that you are not depending on
|
||||||
/// on cookies, the ordering of their modifications, or both, or have arranged
|
/// the ordering of cookie modifications or have arranged for request dispatch
|
||||||
/// for dispatches to occur in a deterministic ordering.
|
/// to occur in a deterministic manner.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
///
|
///
|
||||||
|
@ -47,7 +44,8 @@ use crate::error::LaunchError;
|
||||||
/// ```
|
/// ```
|
||||||
pub struct Client {
|
pub struct Client {
|
||||||
cargo: Cargo,
|
cargo: Cargo,
|
||||||
pub(in super) cookies: Option<RwLock<CookieJar>>,
|
pub(in super) tracked: bool,
|
||||||
|
pub(in super) cookies: cookie::CookieJar,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Client {
|
impl Client {
|
||||||
|
@ -56,13 +54,9 @@ impl Client {
|
||||||
tracked: bool
|
tracked: bool
|
||||||
) -> Result<Client, LaunchError> {
|
) -> Result<Client, LaunchError> {
|
||||||
rocket.prelaunch_check().await?;
|
rocket.prelaunch_check().await?;
|
||||||
|
let cargo = rocket.into_cargo().await;
|
||||||
|
|
||||||
let cookies = match tracked {
|
Ok(Client { cargo, tracked, cookies: cookie::CookieJar::new() })
|
||||||
true => Some(RwLock::new(CookieJar::new())),
|
|
||||||
false => None
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Client { cargo: rocket.into_cargo().await, cookies })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// WARNING: This is unstable! Do not use this method outside of Rocket!
|
// WARNING: This is unstable! Do not use this method outside of Rocket!
|
||||||
|
@ -84,6 +78,11 @@ impl Client {
|
||||||
&self.cargo
|
&self.cargo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn _cookies(&self) -> &cookie::CookieJar {
|
||||||
|
&self.cookies
|
||||||
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn _req<'c, 'u: 'c, U>(&'c self, method: Method, uri: U) -> LocalRequest<'c>
|
fn _req<'c, 'u: 'c, U>(&'c self, method: Method, uri: U) -> LocalRequest<'c>
|
||||||
where U: Into<Cow<'u, str>>
|
where U: Into<Cow<'u, str>>
|
||||||
|
|
|
@ -30,8 +30,8 @@ use super::{Client, LocalResponse};
|
||||||
/// # });
|
/// # });
|
||||||
/// ```
|
/// ```
|
||||||
pub struct LocalRequest<'c> {
|
pub struct LocalRequest<'c> {
|
||||||
client: &'c Client,
|
pub(in super) client: &'c Client,
|
||||||
request: Request<'c>,
|
pub(in super) request: Request<'c>,
|
||||||
data: Vec<u8>,
|
data: Vec<u8>,
|
||||||
uri: Cow<'c, str>,
|
uri: Cow<'c, str>,
|
||||||
}
|
}
|
||||||
|
@ -47,11 +47,10 @@ impl<'c> LocalRequest<'c> {
|
||||||
let origin = Origin::parse(&uri).unwrap_or_else(|_| Origin::dummy());
|
let origin = Origin::parse(&uri).unwrap_or_else(|_| Origin::dummy());
|
||||||
let request = Request::new(client.rocket(), method, origin.into_owned());
|
let request = Request::new(client.rocket(), method, origin.into_owned());
|
||||||
|
|
||||||
// Set up any cookies we know about.
|
// Add any cookies we know about.
|
||||||
if let Some(ref jar) = client.cookies {
|
if client.tracked {
|
||||||
let cookies = jar.read().expect("LocalRequest::new() read lock");
|
for crumb in client.cookies.iter() {
|
||||||
for cookie in cookies.iter() {
|
request.cookies().add_original(crumb.into_cookie());
|
||||||
request.cookies().add_original(cookie.clone().into_owned());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,24 +85,24 @@ impl<'c> LocalRequest<'c> {
|
||||||
// Actually dispatch the request.
|
// Actually dispatch the request.
|
||||||
let mut data = Data::local(self.data);
|
let mut data = Data::local(self.data);
|
||||||
let token = rocket.preprocess_request(&mut self.request, &mut data).await;
|
let token = rocket.preprocess_request(&mut self.request, &mut data).await;
|
||||||
let response = LocalResponse::new(self.request, move |request| {
|
let response = LocalResponse::new(self.request, move |req| {
|
||||||
rocket.dispatch(token, request, data)
|
rocket.dispatch(token, req, data)
|
||||||
}).await;
|
}).await;
|
||||||
|
|
||||||
// If the client is tracking cookies, updates the internal cookie jar
|
// If the client is tracking cookies, updates the internal cookie jar
|
||||||
// with the changes reflected by `response`.
|
// with the changes reflected by `response`.
|
||||||
if let Some(ref jar) = self.client.cookies {
|
if self.client.tracked {
|
||||||
let mut jar = jar.write().expect("LocalRequest::_dispatch() write lock");
|
let jar = &self.client.cookies;
|
||||||
let current_time = time::OffsetDateTime::now_utc();
|
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 let Some(expires) = cookie.expires() {
|
||||||
if expires <= current_time {
|
if expires <= current_time {
|
||||||
jar.force_remove(cookie);
|
jar.force_remove(&cookie);
|
||||||
continue;
|
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 tokio::io::AsyncRead;
|
||||||
|
|
||||||
|
use crate::http::CookieJar;
|
||||||
use crate::{Request, Response};
|
use crate::{Request, Response};
|
||||||
|
|
||||||
/// An `async` response from a dispatched [`LocalRequest`](super::LocalRequest).
|
/// An `async` response from a dispatched [`LocalRequest`](super::LocalRequest).
|
||||||
|
@ -55,6 +56,7 @@ use crate::{Request, Response};
|
||||||
pub struct LocalResponse<'c> {
|
pub struct LocalResponse<'c> {
|
||||||
_request: Box<Request<'c>>,
|
_request: Box<Request<'c>>,
|
||||||
response: Response<'c>,
|
response: Response<'c>,
|
||||||
|
cookies: CookieJar<'c>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'c> LocalResponse<'c> {
|
impl<'c> LocalResponse<'c> {
|
||||||
|
@ -84,11 +86,15 @@ impl<'c> LocalResponse<'c> {
|
||||||
// away as `'_`, ensuring it is not used for any output value.
|
// away as `'_`, ensuring it is not used for any output value.
|
||||||
let boxed_req = Box::new(req);
|
let boxed_req = Box::new(req);
|
||||||
let request: &'c Request<'c> = unsafe { &*(&*boxed_req as *const _) };
|
let request: &'c Request<'c> = unsafe { &*(&*boxed_req as *const _) };
|
||||||
|
|
||||||
async move {
|
async move {
|
||||||
LocalResponse {
|
let response: Response<'c> = f(request).await;
|
||||||
_request: boxed_req,
|
let cookies = CookieJar::new(request.state.config.secret_key());
|
||||||
response: f(request).await
|
for cookie in response.cookies() {
|
||||||
|
cookies.add(cookie.into_owned());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LocalResponse { cookies, _request: boxed_req, response, }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -98,6 +104,10 @@ impl LocalResponse<'_> {
|
||||||
&self.response
|
&self.response
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn _cookies(&self) -> &CookieJar<'_> {
|
||||||
|
&self.cookies
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) async fn _into_string(mut self) -> Option<String> {
|
pub(crate) async fn _into_string(mut self) -> Option<String> {
|
||||||
self.response.body_string().await
|
self.response.body_string().await
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,9 @@ use std::borrow::Cow;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
|
||||||
use crate::error::LaunchError;
|
use crate::error::LaunchError;
|
||||||
use crate::http::Method;
|
|
||||||
use crate::local::{asynchronous, blocking::{LocalRequest, LocalResponse}};
|
use crate::local::{asynchronous, blocking::{LocalRequest, LocalResponse}};
|
||||||
use crate::rocket::{Rocket, Cargo};
|
use crate::rocket::{Rocket, Cargo};
|
||||||
|
use crate::http::Method;
|
||||||
|
|
||||||
/// A `blocking` client to construct and dispatch local requests.
|
/// A `blocking` client to construct and dispatch local requests.
|
||||||
///
|
///
|
||||||
|
@ -67,6 +67,11 @@ impl Client {
|
||||||
self.inner._cargo()
|
self.inner._cargo()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn _cookies(&self) -> &cookie::CookieJar {
|
||||||
|
self.inner._cookies()
|
||||||
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn _req<'c, 'u: 'c, U>(
|
pub(crate) fn _req<'c, 'u: 'c, U>(
|
||||||
&'c self,
|
&'c self,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::io;
|
use std::io;
|
||||||
use tokio::io::AsyncReadExt;
|
use tokio::io::AsyncReadExt;
|
||||||
|
|
||||||
use crate::{Response, local::asynchronous};
|
use crate::{Response, local::asynchronous, http::CookieJar};
|
||||||
|
|
||||||
use super::Client;
|
use super::Client;
|
||||||
|
|
||||||
|
@ -60,6 +60,10 @@ impl LocalResponse<'_> {
|
||||||
&self.inner._response()
|
&self.inner._response()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn _cookies(&self) -> &CookieJar<'_> {
|
||||||
|
self.inner._cookies()
|
||||||
|
}
|
||||||
|
|
||||||
fn _into_string(self) -> Option<String> {
|
fn _into_string(self) -> Option<String> {
|
||||||
self.client.block_on(self.inner._into_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
|
/// This is typically the desired mode of operation for a `Client` as it
|
||||||
/// removes the burden of manually tracking cookies. Under some
|
/// 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
|
/// [`untracked()`](Client::untracked()) method creates a `Client` that
|
||||||
/// _will not_ track cookies.
|
/// _will not_ track cookies.
|
||||||
///
|
///
|
||||||
|
@ -133,6 +133,29 @@ macro_rules! pub_client_impl {
|
||||||
self._cargo()
|
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, "GET", get, Method::Get);
|
||||||
req_method!($import, "PUT", put, Method::Put);
|
req_method!($import, "PUT", put, Method::Put);
|
||||||
req_method!($import, "POST", post, Method::Post);
|
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.
|
/// Add all of the cookies in `cookies` to this request.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
|
||||||
/// Add `user_id` cookie:
|
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
#[doc = $import]
|
#[doc = $import]
|
||||||
|
@ -133,7 +131,9 @@ macro_rules! pub_request_impl {
|
||||||
/// # });
|
/// # });
|
||||||
/// ```
|
/// ```
|
||||||
#[inline]
|
#[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 {
|
for cookie in cookies {
|
||||||
self._request().cookies().add_original(cookie.into_owned());
|
self._request().cookies().add_original(cookie.into_owned());
|
||||||
}
|
}
|
||||||
|
@ -143,10 +143,7 @@ macro_rules! pub_request_impl {
|
||||||
|
|
||||||
/// Add a [private cookie] to this request.
|
/// Add a [private cookie] to this request.
|
||||||
///
|
///
|
||||||
/// This method is only available when the `private-cookies` feature is
|
/// [private cookie]: crate::http::CookieJar::add_private()
|
||||||
/// enabled.
|
|
||||||
///
|
|
||||||
/// [private cookie]: crate::http::Cookies::add_private()
|
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
|
@ -161,8 +158,9 @@ macro_rules! pub_request_impl {
|
||||||
/// let req = request.private_cookie(Cookie::new("user_id", "sb"));
|
/// let req = request.private_cookie(Cookie::new("user_id", "sb"));
|
||||||
/// # });
|
/// # });
|
||||||
/// ```
|
/// ```
|
||||||
|
#[cfg(feature = "secrets")]
|
||||||
|
#[cfg_attr(nightly, doc(cfg(feature = "secrets")))]
|
||||||
#[inline]
|
#[inline]
|
||||||
#[cfg(feature = "private-cookies")]
|
|
||||||
pub fn private_cookie(self, cookie: crate::http::Cookie<'static>) -> Self {
|
pub fn private_cookie(self, cookie: crate::http::Cookie<'static>) -> Self {
|
||||||
self._request().cookies().add_original_private(cookie);
|
self._request().cookies().add_original_private(cookie);
|
||||||
self
|
self
|
||||||
|
|
|
@ -37,8 +37,22 @@ macro_rules! pub_response_impl {
|
||||||
getter_method!($doc_prelude, "HTTP headers",
|
getter_method!($doc_prelude, "HTTP headers",
|
||||||
headers -> &crate::http::HeaderMap<'_>);
|
headers -> &crate::http::HeaderMap<'_>);
|
||||||
|
|
||||||
getter_method!($doc_prelude, "HTTP cookies as set in the `Set-Cookie` header",
|
/// Return a cookie jar containing the HTTP cookies in the response.
|
||||||
cookies -> Vec<crate::http::Cookie<'_>>);
|
///
|
||||||
|
/// # 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,",
|
getter_method!($doc_prelude, "response body, if there is one,",
|
||||||
body -> Option<&crate::response::ResponseBody<'_>>);
|
body -> Option<&crate::response::ResponseBody<'_>>);
|
||||||
|
|
|
@ -8,7 +8,7 @@ use crate::request::Request;
|
||||||
use crate::outcome::{self, IntoOutcome};
|
use crate::outcome::{self, IntoOutcome};
|
||||||
use crate::outcome::Outcome::*;
|
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.
|
/// Type alias for the `Outcome` of a `FromRequest` conversion.
|
||||||
pub type Outcome<S, E> = outcome::Outcome<S, (Status, E), ()>;
|
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
|
/// For information on when an `&Route` is available, see
|
||||||
/// [`Request::route()`].
|
/// [`Request::route()`].
|
||||||
///
|
///
|
||||||
/// * **Cookies**
|
/// * **&CookieJar**
|
||||||
///
|
///
|
||||||
/// Returns a borrow to the [`Cookies`] in the incoming request. Note that
|
/// Returns a borrow to the [`CookieJar`] in the incoming request. Note that
|
||||||
/// `Cookies` implements internal mutability, so a handle to `Cookies`
|
/// `CookieJar` implements internal mutability, so a handle to a `CookieJar`
|
||||||
/// allows you to get _and_ set cookies in the request.
|
/// allows you to get _and_ set cookies in the request.
|
||||||
///
|
///
|
||||||
/// _This implementation always returns successfully._
|
/// _This implementation always returns successfully._
|
||||||
|
@ -242,7 +242,7 @@ impl<S, E> IntoOutcome<S, (Status, E), ()> for Result<S, E> {
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # #[macro_use] extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// # #[cfg(feature = "private-cookies")] mod inner {
|
/// # #[cfg(feature = "secrets")] mod wrapper {
|
||||||
/// # use rocket::outcome::IntoOutcome;
|
/// # use rocket::outcome::IntoOutcome;
|
||||||
/// # use rocket::request::{self, Outcome, FromRequest, Request};
|
/// # use rocket::request::{self, Outcome, FromRequest, Request};
|
||||||
/// # struct User { id: String, is_admin: bool }
|
/// # 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)]
|
/// #[get("/dashboard", rank = 2)]
|
||||||
/// fn user_dashboard(user: User) { }
|
/// fn user_dashboard(user: User) { }
|
||||||
/// # }
|
/// # } // end of cfg wrapper
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// When a non-admin user is logged in, the database will be queried twice: once
|
/// 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
|
/// ```rust
|
||||||
/// # #[macro_use] extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// # #[cfg(feature = "private-cookies")] mod inner {
|
/// # #[cfg(feature = "secrets")] mod wrapper {
|
||||||
/// # use rocket::outcome::IntoOutcome;
|
/// # use rocket::outcome::IntoOutcome;
|
||||||
/// # use rocket::request::{self, Outcome, FromRequest, Request};
|
/// # use rocket::request::{self, Outcome, FromRequest, Request};
|
||||||
/// # struct User { id: String, is_admin: bool }
|
/// # 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
|
/// 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.
|
/// 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
|
/// [request-local state]: https://rocket.rs/v0.5/guide/state/#request-local-state
|
||||||
|
|
||||||
#[crate::async_trait]
|
#[crate::async_trait]
|
||||||
pub trait FromRequest<'a, 'r>: Sized {
|
pub trait FromRequest<'a, 'r>: Sized {
|
||||||
/// The associated error to be returned if derivation fails.
|
/// 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]
|
#[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;
|
type Error = std::convert::Infallible;
|
||||||
|
|
||||||
async fn from_request(request: &'a Request<'r>) -> Outcome<Self, Self::Error> {
|
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::net::{IpAddr, SocketAddr};
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
@ -7,20 +7,18 @@ use std::str;
|
||||||
use yansi::Paint;
|
use yansi::Paint;
|
||||||
use state::{Container, Storage};
|
use state::{Container, Storage};
|
||||||
use futures::future::BoxFuture;
|
use futures::future::BoxFuture;
|
||||||
use atomic::Atomic;
|
use atomic::{Atomic, Ordering};
|
||||||
|
|
||||||
use crate::request::{FromParam, FromSegments, FromRequest, Outcome};
|
use crate::request::{FromParam, FromSegments, FromRequest, Outcome};
|
||||||
use crate::request::{FromFormValue, FormItems, FormItem};
|
use crate::request::{FromFormValue, FormItems, FormItem};
|
||||||
|
|
||||||
use crate::{Rocket, Config, Shutdown, Route};
|
use crate::{Rocket, Config, Shutdown, Route};
|
||||||
use crate::http::{hyper, uri::{Origin, Segments}};
|
use crate::http::{hyper, uri::{Origin, Segments}};
|
||||||
use crate::http::{Method, Header, HeaderMap, Cookies};
|
use crate::http::{Method, Header, HeaderMap};
|
||||||
use crate::http::{RawStr, ContentType, Accept, MediaType};
|
use crate::http::{RawStr, ContentType, Accept, MediaType, CookieJar, Cookie};
|
||||||
use crate::http::private::{Indexed, SmallVec, CookieJar};
|
use crate::http::private::{Indexed, SmallVec};
|
||||||
use crate::data::Limits;
|
use crate::data::Limits;
|
||||||
|
|
||||||
type Indices = (usize, usize);
|
|
||||||
|
|
||||||
/// The type of an incoming web request.
|
/// The type of an incoming web request.
|
||||||
///
|
///
|
||||||
/// This should be used sparingly in Rocket applications. In particular, it
|
/// 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 shutdown: &'r Shutdown,
|
||||||
pub path_segments: SmallVec<[Indices; 12]>,
|
pub path_segments: SmallVec<[Indices; 12]>,
|
||||||
pub query_items: Option<SmallVec<[IndexedFormItem; 6]>>,
|
pub query_items: Option<SmallVec<[IndexedFormItem; 6]>>,
|
||||||
pub route: RwLock<Option<&'r Route>>,
|
pub route: Atomic<Option<&'r Route>>,
|
||||||
pub cookies: Mutex<Option<CookieJar>>,
|
pub cookies: CookieJar<'r>,
|
||||||
pub accept: Storage<Option<Accept>>,
|
pub accept: Storage<Option<Accept>>,
|
||||||
pub content_type: Storage<Option<ContentType>>,
|
pub content_type: Storage<Option<ContentType>>,
|
||||||
pub cache: Arc<Container>,
|
pub cache: Arc<Container>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'r> Request<'r> {
|
impl Request<'_> {
|
||||||
pub(crate) fn clone(&self) -> Self {
|
pub(crate) fn clone(&self) -> Self {
|
||||||
Request {
|
Request {
|
||||||
method: Atomic::new(self.method()),
|
method: Atomic::new(self.method()),
|
||||||
|
@ -60,24 +58,16 @@ impl<'r> Request<'r> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'r> RequestState<'r> {
|
impl RequestState<'_> {
|
||||||
fn clone(&self) -> RequestState<'r> {
|
fn clone(&self) -> Self {
|
||||||
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()));
|
|
||||||
|
|
||||||
RequestState {
|
RequestState {
|
||||||
config: self.config,
|
config: self.config,
|
||||||
managed: self.managed,
|
managed: self.managed,
|
||||||
shutdown: self.shutdown,
|
shutdown: self.shutdown,
|
||||||
path_segments: self.path_segments.clone(),
|
path_segments: self.path_segments.clone(),
|
||||||
query_items: self.query_items.clone(),
|
query_items: self.query_items.clone(),
|
||||||
route: RwLock::new(route),
|
route: Atomic::new(self.route.load(Ordering::Acquire)),
|
||||||
cookies: Mutex::new(cookies),
|
cookies: self.cookies.clone(),
|
||||||
accept: self.accept.clone(),
|
accept: self.accept.clone(),
|
||||||
content_type: self.content_type.clone(),
|
content_type: self.content_type.clone(),
|
||||||
cache: self.cache.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> {
|
impl<'r> Request<'r> {
|
||||||
/// Create a new `Request` with the given `method` and `uri`.
|
/// Create a new `Request` with the given `method` and `uri`.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
@ -111,8 +94,8 @@ impl<'r> Request<'r> {
|
||||||
config: &rocket.config,
|
config: &rocket.config,
|
||||||
managed: &rocket.managed_state,
|
managed: &rocket.managed_state,
|
||||||
shutdown: &rocket.shutdown_handle,
|
shutdown: &rocket.shutdown_handle,
|
||||||
route: RwLock::new(None),
|
route: Atomic::new(None),
|
||||||
cookies: Mutex::new(Some(CookieJar::new())),
|
cookies: CookieJar::new(rocket.config.secret_key()),
|
||||||
accept: Storage::new(),
|
accept: Storage::new(),
|
||||||
content_type: Storage::new(),
|
content_type: Storage::new(),
|
||||||
cache: Arc::new(Container::new()),
|
cache: Arc::new(Container::new()),
|
||||||
|
@ -138,7 +121,7 @@ impl<'r> Request<'r> {
|
||||||
/// ```
|
/// ```
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn method(&self) -> Method {
|
pub fn method(&self) -> Method {
|
||||||
self.method.load(atomic::Ordering::Acquire)
|
self.method.load(Ordering::Acquire)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the method of `self`.
|
/// Set the method of `self`.
|
||||||
|
@ -308,8 +291,8 @@ impl<'r> Request<'r> {
|
||||||
|
|
||||||
/// Returns a wrapped borrow to the cookies in `self`.
|
/// Returns a wrapped borrow to the cookies in `self`.
|
||||||
///
|
///
|
||||||
/// [`Cookies`] implements internal mutability, so this method allows you to
|
/// [`CookieJar`] implements internal mutability, so this method allows you
|
||||||
/// get _and_ add/remove cookies in `self`.
|
/// to get _and_ add/remove cookies in `self`.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
|
@ -325,26 +308,8 @@ impl<'r> Request<'r> {
|
||||||
/// request.cookies().add(Cookie::new("ans", format!("life: {}", 38 + 4)));
|
/// request.cookies().add(Cookie::new("ans", format!("life: {}", 38 + 4)));
|
||||||
/// # });
|
/// # });
|
||||||
/// ```
|
/// ```
|
||||||
pub fn cookies(&self) -> Cookies<'_> {
|
pub fn cookies(&self) -> &CookieJar<'r> {
|
||||||
// FIXME: Can we do better? This is disappointing.
|
&self.state.cookies
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a [`HeaderMap`] of all of the headers in `self`.
|
/// 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> {
|
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.
|
/// 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.
|
// 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)]
|
#[doc(hidden)]
|
||||||
impl<'r> Request<'r> {
|
impl<'r> Request<'r> {
|
||||||
// Only used by doc-tests! Needs to be `pub` because doc-test are external.
|
// 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.
|
/// was `route`. Use during routing when attempting a given route.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn set_route(&self, route: &'r Route) {
|
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
|
/// Set the method of `self`, even when `self` is a shared reference. Used
|
||||||
/// during routing to override methods for re-routing.
|
/// during routing to override methods for re-routing.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn _set_method(&self, method: Method) {
|
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.
|
/// Convert from Hyper types into a Rocket Request.
|
||||||
|
@ -895,7 +860,6 @@ impl<'r> Request<'r> {
|
||||||
request.set_remote(h_addr);
|
request.set_remote(h_addr);
|
||||||
|
|
||||||
// Set the request cookies, if they exist.
|
// Set the request cookies, if they exist.
|
||||||
let mut cookie_jar = CookieJar::new();
|
|
||||||
for header in h_headers.get_all("Cookie") {
|
for header in h_headers.get_all("Cookie") {
|
||||||
let raw_str = match std::str::from_utf8(header.as_bytes()) {
|
let raw_str = match std::str::from_utf8(header.as_bytes()) {
|
||||||
Ok(string) => string,
|
Ok(string) => string,
|
||||||
|
@ -903,12 +867,11 @@ impl<'r> Request<'r> {
|
||||||
};
|
};
|
||||||
|
|
||||||
for cookie_str in raw_str.split(';').map(|s| s.trim()) {
|
for cookie_str in raw_str.split(';').map(|s| s.trim()) {
|
||||||
if let Some(cookie) = Cookies::parse_cookie(cookie_str) {
|
if let Ok(cookie) = Cookie::parse_encoded(cookie_str) {
|
||||||
cookie_jar.add_original(cookie);
|
request.state.cookies.add_original(cookie.into_owned());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
request.state.cookies = Mutex::new(Some(cookie_jar));
|
|
||||||
|
|
||||||
// Set the rest of the headers.
|
// Set the rest of the headers.
|
||||||
for (name, value) in h_headers.iter() {
|
for (name, value) in h_headers.iter() {
|
||||||
|
@ -929,6 +892,7 @@ impl fmt::Debug for Request<'_> {
|
||||||
.field("uri", &self.uri)
|
.field("uri", &self.uri)
|
||||||
.field("headers", &self.headers())
|
.field("headers", &self.headers())
|
||||||
.field("remote", &self.remote())
|
.field("remote", &self.remote())
|
||||||
|
.field("cookies", &self.cookies())
|
||||||
.finish()
|
.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 {
|
impl IndexedFormItem {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn from(s: &str, i: FormItem<'_>) -> Self {
|
fn from(s: &str, i: FormItem<'_>) -> Self {
|
||||||
|
|
|
@ -742,17 +742,13 @@ impl<'r> Response<'r> {
|
||||||
///
|
///
|
||||||
/// let mut response = Response::new();
|
/// let mut response = Response::new();
|
||||||
/// response.set_header(Cookie::new("hello", "world!"));
|
/// 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<'_>> {
|
pub fn cookies(&self) -> impl Iterator<Item = Cookie<'_>> {
|
||||||
let mut cookies = vec![];
|
self.headers()
|
||||||
for header in self.headers().get("Set-Cookie") {
|
.get("Set-Cookie")
|
||||||
if let Ok(cookie) = Cookie::parse_encoded(header) {
|
.filter_map(|header| Cookie::parse_encoded(header).ok())
|
||||||
cookies.push(cookie);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cookies
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a [`HeaderMap`] of all of the headers in `self`.
|
/// 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
|
// Set the cookies. Note that error responses will only include
|
||||||
// cookies set by the error handler. See `handle_error` for more.
|
// cookies set by the error handler. See `handle_error` for more.
|
||||||
for cookie in request.cookies().delta() {
|
for crumb in request.cookies().delta() {
|
||||||
response.adjoin_header(cookie);
|
response.adjoin_header(crumb)
|
||||||
}
|
}
|
||||||
|
|
||||||
response
|
response
|
||||||
|
@ -651,7 +651,7 @@ impl Rocket {
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.secret_key.is_generated() && config.environment.is_prod() {
|
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() {
|
for (name, value) in config.extras() {
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
#[macro_use] extern crate rocket;
|
#[macro_use] extern crate rocket;
|
||||||
|
|
||||||
use rocket::request::Request;
|
use rocket::request::Request;
|
||||||
use rocket::http::{Cookie, Cookies};
|
use rocket::http::{Cookie, CookieJar};
|
||||||
|
|
||||||
#[catch(404)]
|
#[catch(404)]
|
||||||
fn not_found(request: &Request) -> &'static str {
|
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"
|
"404 - Not Found"
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
fn index(mut cookies: Cookies) -> &'static str {
|
fn index(cookies: &CookieJar<'_>) -> &'static str {
|
||||||
cookies.add(Cookie::new("index", "hi"));
|
cookies.add(Cookie::new("index", "hi"));
|
||||||
"Hello, world!"
|
"Hello, world!"
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ mod tests {
|
||||||
.mount("/", routes![index])
|
.mount("/", routes![index])
|
||||||
.register(catchers![not_found])
|
.register(catchers![not_found])
|
||||||
.attach(AdHoc::on_request("Add Fairing Cookie", |req, _| Box::pin(async move {
|
.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();
|
let client = Client::new(rocket).unwrap();
|
||||||
|
@ -34,12 +34,14 @@ mod tests {
|
||||||
// Check that the index returns the `index` and `fairing` cookie.
|
// Check that the index returns the `index` and `fairing` cookie.
|
||||||
let response = client.get("/").dispatch();
|
let response = client.get("/").dispatch();
|
||||||
let cookies = response.cookies();
|
let cookies = response.cookies();
|
||||||
assert_eq!(cookies.len(), 2);
|
assert_eq!(cookies.iter().count(), 2);
|
||||||
assert!(cookies.iter().find(|c| c.name() == "index").is_some());
|
assert_eq!(cookies.get("index").unwrap().value(), "hi");
|
||||||
assert!(cookies.iter().find(|c| c.name() == "fairing").is_some());
|
assert_eq!(cookies.get("fairing").unwrap().value(), "woo");
|
||||||
|
|
||||||
// Check that the catcher returns only the `not_found` cookie.
|
// Check that the catcher returns only the `not_found` cookie.
|
||||||
let response = client.get("/not-existent").dispatch();
|
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 = "secrets")]
|
||||||
#[cfg(feature = "private-cookies")]
|
mod private_cookies {
|
||||||
extern crate rocket;
|
use rocket::http::CookieJar;
|
||||||
|
|
||||||
#[cfg(feature = "private-cookies")]
|
#[rocket::get("/")]
|
||||||
mod private_cookie_test {
|
fn return_private_cookie(cookies: &CookieJar<'_>) -> Option<String> {
|
||||||
use rocket::http::Cookies;
|
|
||||||
|
|
||||||
#[get("/")]
|
|
||||||
fn return_private_cookie(mut cookies: Cookies) -> Option<String> {
|
|
||||||
match cookies.get_private("cookie_name") {
|
match cookies.get_private("cookie_name") {
|
||||||
Some(cookie) => Some(cookie.value().into()),
|
Some(cookie) => Some(cookie.value().into()),
|
||||||
None => None,
|
None => None,
|
||||||
|
@ -16,9 +12,9 @@ mod private_cookie_test {
|
||||||
|
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use rocket::routes;
|
||||||
use rocket::local::blocking::Client;
|
use rocket::local::blocking::Client;
|
||||||
use rocket::http::Cookie;
|
use rocket::http::{Cookie, Status};
|
||||||
use rocket::http::Status;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn private_cookie_is_returned() {
|
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::request::Form;
|
||||||
use rocket::response::Redirect;
|
use rocket::response::Redirect;
|
||||||
use rocket::http::{Cookie, Cookies};
|
use rocket::http::{Cookie, CookieJar};
|
||||||
use rocket_contrib::templates::Template;
|
use rocket_contrib::templates::Template;
|
||||||
|
|
||||||
#[derive(FromForm)]
|
#[derive(FromForm)]
|
||||||
|
@ -16,13 +16,13 @@ struct Message {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/submit", data = "<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));
|
cookies.add(Cookie::new("message", message.into_inner().message));
|
||||||
Redirect::to("/")
|
Redirect::to("/")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
fn index(cookies: Cookies<'_>) -> Template {
|
fn index(cookies: &CookieJar<'_>) -> Template {
|
||||||
let cookie = cookies.get("message");
|
let cookie = cookies.get("message");
|
||||||
let mut context = HashMap::new();
|
let mut context = HashMap::new();
|
||||||
if let Some(ref cookie) = cookie {
|
if let Some(ref cookie) = cookie {
|
||||||
|
|
|
@ -14,11 +14,12 @@ fn test_submit() {
|
||||||
.dispatch();
|
.dispatch();
|
||||||
|
|
||||||
let cookie_headers: Vec<_> = response.headers().get("Set-Cookie").collect();
|
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);
|
let location_headers: Vec<_> = response.headers().get("Location").collect();
|
||||||
assert_eq!(cookie_headers, vec!["message=Hello%20from%20Rocket!".to_string()]);
|
|
||||||
assert_eq!(location_headers, vec!["/".to_string()]);
|
assert_eq!(location_headers, vec!["/".to_string()]);
|
||||||
|
assert_eq!(response.status(), Status::SeeOther);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_body(optional_cookie: Option<Cookie<'static>>, expected_body: String) {
|
fn test_body(optional_cookie: Option<Cookie<'static>>, expected_body: String) {
|
||||||
|
|
|
@ -6,7 +6,7 @@ edition = "2018"
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rocket = { path = "../../core/lib", features = ["private-cookies"] }
|
rocket = { path = "../../core/lib" }
|
||||||
|
|
||||||
[dependencies.rocket_contrib]
|
[dependencies.rocket_contrib]
|
||||||
path = "../../contrib/lib"
|
path = "../../contrib/lib"
|
||||||
|
|
|
@ -7,7 +7,7 @@ use std::collections::HashMap;
|
||||||
use rocket::outcome::IntoOutcome;
|
use rocket::outcome::IntoOutcome;
|
||||||
use rocket::request::{self, Form, FlashMessage, FromRequest, Request};
|
use rocket::request::{self, Form, FlashMessage, FromRequest, Request};
|
||||||
use rocket::response::{Redirect, Flash};
|
use rocket::response::{Redirect, Flash};
|
||||||
use rocket::http::{Cookie, Cookies};
|
use rocket::http::{Cookie, CookieJar};
|
||||||
use rocket_contrib::templates::Template;
|
use rocket_contrib::templates::Template;
|
||||||
|
|
||||||
#[derive(FromForm)]
|
#[derive(FromForm)]
|
||||||
|
@ -33,7 +33,7 @@ impl<'a, 'r> FromRequest<'a, 'r> for User {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/login", data = "<login>")]
|
#[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" {
|
if login.username == "Sergio" && login.password == "password" {
|
||||||
cookies.add_private(Cookie::new("user_id", 1.to_string()));
|
cookies.add_private(Cookie::new("user_id", 1.to_string()));
|
||||||
Ok(Redirect::to(uri!(index)))
|
Ok(Redirect::to(uri!(index)))
|
||||||
|
@ -43,7 +43,7 @@ fn login(mut cookies: Cookies<'_>, login: Form<Login>) -> Result<Redirect, Flash
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/logout")]
|
#[post("/logout")]
|
||||||
fn logout(mut cookies: Cookies<'_>) -> Flash<Redirect> {
|
fn logout(cookies: &CookieJar<'_>) -> Flash<Redirect> {
|
||||||
cookies.remove_private(Cookie::named("user_id"));
|
cookies.remove_private(Cookie::named("user_id"));
|
||||||
Flash::success(Redirect::to(uri!(login_page)), "Successfully logged out.")
|
Flash::success(Redirect::to(uri!(login_page)), "Successfully logged out.")
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,7 +106,7 @@ if [ "$1" = "--contrib" ]; then
|
||||||
popd > /dev/null 2>&1
|
popd > /dev/null 2>&1
|
||||||
elif [ "$1" = "--core" ]; then
|
elif [ "$1" = "--core" ]; then
|
||||||
FEATURES=(
|
FEATURES=(
|
||||||
private-cookies # this is already tested since it's the default feature
|
secrets
|
||||||
tls
|
tls
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -117,7 +117,6 @@ elif [ "$1" = "--core" ]; then
|
||||||
|
|
||||||
for feature in "${FEATURES[@]}"; do
|
for feature in "${FEATURES[@]}"; do
|
||||||
echo ":: Building and testing core [${feature}]..."
|
echo ":: Building and testing core [${feature}]..."
|
||||||
|
|
||||||
$CARGO test --no-default-features --features "${feature}"
|
$CARGO test --no-default-features --features "${feature}"
|
||||||
done
|
done
|
||||||
|
|
||||||
|
|
|
@ -438,7 +438,7 @@ more about request guards and implementing them, see the [`FromRequest`]
|
||||||
documentation.
|
documentation.
|
||||||
|
|
||||||
[`FromRequest`]: @api/rocket/request/trait.FromRequest.html
|
[`FromRequest`]: @api/rocket/request/trait.FromRequest.html
|
||||||
[`Cookies`]: @api/rocket/http/enum.Cookies.html
|
[`CookieJar`]: @api/rocket/http/struct.CookieJar.html
|
||||||
|
|
||||||
### Custom Guards
|
### Custom Guards
|
||||||
|
|
||||||
|
@ -568,34 +568,33 @@ it always succeeds. The user is redirected to a log in page.
|
||||||
|
|
||||||
## Cookies
|
## Cookies
|
||||||
|
|
||||||
[`Cookies`] is an important, built-in request guard: it allows you to get, set,
|
A reference to a [`CookieJar`] is an important, built-in request guard: it
|
||||||
and remove cookies. Because `Cookies` is a request guard, an argument of its
|
allows you to get, set, and remove cookies. Because `&CookieJar` is a request
|
||||||
type can simply be added to a handler:
|
guard, an argument of its type can simply be added to a handler:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
# #[macro_use] extern crate rocket;
|
# #[macro_use] extern crate rocket;
|
||||||
# fn main() {}
|
# fn main() {}
|
||||||
use rocket::http::Cookies;
|
use rocket::http::CookieJar;
|
||||||
|
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
fn index(cookies: Cookies) -> Option<String> {
|
fn index(cookies: &CookieJar<'_>) -> Option<String> {
|
||||||
cookies.get("message")
|
cookies.get("message").map(|crumb| format!("Message: {}", crumb.value()))
|
||||||
.map(|value| format!("Message: {}", value))
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
This results in the incoming request's cookies being accessible from the
|
This results in the incoming request's cookies being accessible from the
|
||||||
handler. The example above retrieves a cookie named `message`. Cookies can also
|
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
|
be set and removed using the `CookieJar` guard. The [cookies example] on GitHub
|
||||||
illustrates further use of the `Cookies` type to get and set cookies, while the
|
illustrates further use of the `CookieJar` type to get and set cookies, while
|
||||||
[`Cookies`] documentation contains complete usage information.
|
the [`CookieJar`] documentation contains complete usage information.
|
||||||
|
|
||||||
[cookies example]: @example/cookies
|
[cookies example]: @example/cookies
|
||||||
|
|
||||||
### Private Cookies
|
### Private Cookies
|
||||||
|
|
||||||
Cookies added via the [`Cookies::add()`] method are set _in the clear._ In other
|
Cookies added via the [`CookieJar::add()`] method are set _in the clear._ In
|
||||||
words, the value set is visible by the client. For sensitive data, Rocket
|
other words, the value set is visible to the client. For sensitive data, Rocket
|
||||||
provides _private_ cookies.
|
provides _private_ cookies.
|
||||||
|
|
||||||
Private cookies are just like regular cookies except that they are encrypted
|
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;
|
# #[macro_use] extern crate rocket;
|
||||||
# fn main() {}
|
# fn main() {}
|
||||||
|
|
||||||
use rocket::http::{Cookie, Cookies};
|
use rocket::http::{Cookie, CookieJar};
|
||||||
use rocket::response::{Flash, Redirect};
|
use rocket::response::{Flash, Redirect};
|
||||||
|
|
||||||
/// Retrieve the user's ID, if any.
|
/// Retrieve the user's ID, if any.
|
||||||
#[get("/user_id")]
|
#[get("/user_id")]
|
||||||
fn user_id(mut cookies: Cookies) -> Option<String> {
|
fn user_id(cookies: &CookieJar<'_>) -> Option<String> {
|
||||||
cookies.get_private("user_id")
|
cookies.get_private("user_id")
|
||||||
.map(|cookie| format!("User ID: {}", cookie.value()))
|
.map(|crumb| format!("User ID: {}", crumb.value()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove the `user_id` cookie.
|
/// Remove the `user_id` cookie.
|
||||||
#[post("/logout")]
|
#[post("/logout")]
|
||||||
fn logout(mut cookies: Cookies) -> Flash<Redirect> {
|
fn logout(cookies: &CookieJar<'_>) -> Flash<Redirect> {
|
||||||
cookies.remove_private(Cookie::named("user_id"));
|
cookies.remove_private(Cookie::named("user_id"));
|
||||||
Flash::success(Redirect::to("/"), "Successfully logged out.")
|
Flash::success(Redirect::to("/"), "Successfully logged out.")
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
[`Cookies::add()`]: @api/rocket/http/enum.Cookies.html#method.add
|
[`CookieJar::add()`]: @api/rocket/http/struct.CookieJar.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
|
|
||||||
|
|
||||||
### Secret Key
|
### 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)
|
For more information on configuration, see the [Configuration](../configuration)
|
||||||
section of the guide.
|
section of the guide.
|
||||||
|
|
||||||
[`get_private`]: @api/rocket/http/enum.Cookies.html#method.get_private
|
[`get_private`]: @api/rocket/http/struct.CookieJar.html#method.get_private
|
||||||
[`add_private`]: @api/rocket/http/enum.Cookies.html#method.add_private
|
[`add_private`]: @api/rocket/http/struct.CookieJar.html#method.add_private
|
||||||
[`remove_private`]: @api/rocket/http/enum.Cookies.html#method.remove_private
|
[`remove_private`]: @api/rocket/http/struct.CookieJar.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();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Format
|
## Format
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue