2017-06-06 20:41:04 +00:00
|
|
|
use std::fmt;
|
2017-03-07 09:19:06 +00:00
|
|
|
|
2020-10-15 04:37:16 +00:00
|
|
|
use parking_lot::Mutex;
|
|
|
|
|
2019-06-13 01:33:40 +00:00
|
|
|
use crate::Header;
|
2018-11-09 07:37:37 +00:00
|
|
|
|
2020-10-15 04:37:16 +00:00
|
|
|
pub use cookie::{Cookie, SameSite, Iter};
|
2018-11-09 07:37:37 +00:00
|
|
|
#[doc(hidden)] pub use self::key::*;
|
2018-11-05 20:29:03 +00:00
|
|
|
|
2018-11-09 07:37:37 +00:00
|
|
|
/// Types and methods to manage a `Key` when private cookies are enabled.
|
2018-11-05 20:29:03 +00:00
|
|
|
#[cfg(feature = "private-cookies")]
|
2018-11-09 07:37:37 +00:00
|
|
|
mod key {
|
|
|
|
pub use cookie::Key;
|
|
|
|
}
|
2017-03-08 11:28:12 +00:00
|
|
|
|
2018-11-09 07:37:37 +00:00
|
|
|
/// Types and methods to manage a `Key` when private cookies are disabled.
|
2018-11-05 20:29:03 +00:00
|
|
|
#[cfg(not(feature = "private-cookies"))]
|
UTF-8 routes. Forms revamp. Temp files. Capped.
So. Many. Changes.
This is an insane commit: simultaneously one of the best (because of all
the wonderful improvements!) and one of the worst (because it is just
massive) in the project's history.
Routing:
* All UTF-8 characters are accepted everywhere in route paths. (#998)
* `path` is now `uri` in `route` attribute: `#[route(GET, path = "..")]`
becomes `#[route(GET, uri = "..")]`.
Forms Revamp
* All form related types now reside in a new `form` module.
* Multipart forms are supported. (resolves #106)
* Collections are supported in forms and queries. (resolves #205)
* Nested structures in forms and queries are supported. (resolves #313)
* Form fields can be ad-hoc validated with `#[field(validate = expr)]`.
* `FromFormValue` is now `FromFormField`, blanket implements `FromForm`.
* Form field values are always percent-decoded apriori.
Temporary Files
* A new `TempFile` data and form guard allows streaming data directly to a
file which can then be persisted.
* A new `temp_dir` config parameter specifies where to store `TempFile`.
* The limits `file` and `file/$ext`, where `$ext` is the file extension,
determines the data limit for a `TempFile`.
Capped
* A new `Capped` type is used to indicate when data has been truncated due to
incoming data limits. It allows checking whether data is complete or
truncated.
* `DataStream` methods return `Capped` types.
* `DataStream` API has been revamped to account for `Capped` types.
* Several `Capped<T>` types implement `FromData`, `FromForm`.
* HTTP 413 (Payload Too Large) errors are now returned when data limits are
exceeded. (resolves #972)
Hierarchical Limits
* Data limits are now hierarchical, delimited with `/`. A limit of `a/b/c`
falls back to `a/b` then `a`.
Core
* `&RawStr` no longer implements `FromParam`.
* `&str` implements `FromParam`, `FromData`, `FromForm`.
* `FromTransformedData` was removed.
* `FromData` gained a lifetime for use with request-local data.
* The default error HTML is more compact.
* `&Config` is a request guard.
* The `DataStream` interface was entirely revamped.
* `State` is only exported via `rocket::State`.
* A `request::local_cache!()` macro was added for storing values in
request-local cache without consideration for type uniqueness by using a
locally generated anonymous type.
* `Request::get_param()` is now `Request::param()`.
* `Request::get_segments()` is now `Request::segments()`, takes a range.
* `Request::get_query_value()` is now `Request::query_value()`, can parse any
`FromForm` including sequences.
* `std::io::Error` implements `Responder` like `Debug<std::io::Error>`.
* `(Status, R)` where `R: Responder` implements `Responder` by overriding the
`Status` of `R`.
* The name of a route is printed first during route matching.
* `FlashMessage` now only has one lifetime generic.
HTTP
* `RawStr` implements `serde::{Serialize, Deserialize}`.
* `RawStr` implements _many_ more methods, in particular, those related to the
`Pattern` API.
* `RawStr::from_str()` is now `RawStr::new()`.
* `RawStr::url_decode()` and `RawStr::url_decode_lossy()` only allocate as
necessary, return `Cow`.
* `Status` implements `Default` with `Status::Ok`.
* `Status` implements `PartialEq`, `Eq`, `Hash`, `PartialOrd`, `Ord`.
* Authority and origin part of `Absolute` can be modified with new
`Absolute::{with,set}_authority()`, `Absolute::{with,set}_origin()` methods.
* `Origin::segments()` was removed in favor of methods split into query and
path parts and into raw and decoded versions.
* The `Segments` iterator is smarter, returns decoded `&str` items.
* `Segments::into_path_buf()` is now `Segments::to_path_buf()`.
* A new `QuerySegments` is the analogous query segment iterator.
* Once set, `expires` on private cookies is not overwritten. (resolves #1506)
* `Origin::path()` and `Origin::query()` return `&RawStr`, not `&str`.
Codegen
* Preserve more spans in `uri!` macro.
* Preserve spans `FromForm` field types.
* All dynamic parameters in a query string must typecheck as `FromForm`.
* `FromFormValue` derive removed; `FromFormField` added.
* The `form` `FromForm` and `FromFormField` field attribute is now named
`field`. `#[form(field = ..)]` is now `#[field(name = ..)]`.
Contrib
* `Json` implements `FromForm`.
* `MsgPack` implements `FromForm`.
* The `json!` macro is exported as `rocket_contrib::json::json!`.
* Added clarifying docs to `StaticFiles`.
Examples
* `form_validation` and `form_kitchen_sink` removed in favor of `forms`.
* The `hello_world` example uses unicode in paths.
* The `json` example only allocates as necessary.
Internal
* Codegen uses new `exports` module with the following conventions:
- Locals starts with `__` and are lowercased.
- Rocket modules start with `_` and are lowercased.
- `std` types start with `_` and are titlecased.
- Rocket types are titlecased.
* A `header` module was added to `http`, contains header types.
* `SAFETY` is used as doc-string keyword for `unsafe` related comments.
* The `Uri` parser no longer recognizes Rocket route URIs.
2020-10-30 03:50:06 +00:00
|
|
|
#[allow(missing_docs)]
|
2018-11-09 07:37:37 +00:00
|
|
|
mod key {
|
|
|
|
#[derive(Copy, Clone)]
|
|
|
|
pub struct Key;
|
|
|
|
|
|
|
|
impl Key {
|
2020-09-03 05:41:31 +00:00
|
|
|
pub fn from(_: &[u8]) -> Self { Key }
|
|
|
|
pub fn derive_from(_: &[u8]) -> Self { Key }
|
2018-11-09 07:37:37 +00:00
|
|
|
pub fn generate() -> Self { Key }
|
2020-06-07 11:02:18 +00:00
|
|
|
pub fn try_generate() -> Option<Self> { Some(Key) }
|
2021-01-13 23:23:39 +00:00
|
|
|
pub fn master(&self) -> &[u8] {
|
|
|
|
static ZERO: &'static [u8; 64] = &[0; 64];
|
|
|
|
&ZERO[..]
|
|
|
|
}
|
2020-09-03 05:41:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl PartialEq for Key {
|
|
|
|
fn eq(&self, _: &Self) -> bool {
|
|
|
|
true
|
|
|
|
}
|
2018-11-09 07:37:37 +00:00
|
|
|
}
|
|
|
|
}
|
2018-11-05 20:29:03 +00:00
|
|
|
|
2017-06-24 09:49:16 +00:00
|
|
|
/// Collection of one or more HTTP cookies.
|
|
|
|
///
|
2020-10-15 04:37:16 +00:00
|
|
|
/// `CookieJar` allows for retrieval of cookies from an incoming request. It
|
|
|
|
/// also tracks modifications (additions and removals) and marks them as
|
|
|
|
/// pending.
|
|
|
|
///
|
|
|
|
/// # Pending
|
|
|
|
///
|
|
|
|
/// Changes to a `CookieJar` are _not_ visible via the normal [`get()`] and
|
|
|
|
/// [`get_private()`] methods. This is typically the desired effect as a
|
|
|
|
/// `CookieJar` always reflects the cookies in an incoming request. In cases
|
|
|
|
/// where this is not desired, the [`get_pending()`] and
|
|
|
|
/// [`get_private_pending()`] methods are available, which always return the
|
|
|
|
/// latest changes.
|
|
|
|
///
|
|
|
|
/// ```rust
|
|
|
|
/// # #[macro_use] extern crate rocket;
|
|
|
|
/// use rocket::http::{CookieJar, Cookie};
|
|
|
|
///
|
|
|
|
/// #[get("/message")]
|
|
|
|
/// fn message(jar: &CookieJar<'_>) {
|
|
|
|
/// jar.add(Cookie::new("message", "hello!"));
|
|
|
|
/// jar.add(Cookie::new("other", "bye!"));
|
|
|
|
///
|
|
|
|
/// // `get()` does not reflect changes.
|
|
|
|
/// assert!(jar.get("other").is_none());
|
|
|
|
/// # assert_eq!(jar.get("message").map(|c| c.value()), Some("hi"));
|
|
|
|
///
|
|
|
|
/// // `get_pending()` does.
|
|
|
|
/// let other_pending = jar.get_pending("other");
|
|
|
|
/// let message_pending = jar.get_pending("message");
|
|
|
|
/// assert_eq!(other_pending.as_ref().map(|c| c.value()), Some("bye!"));
|
|
|
|
/// assert_eq!(message_pending.as_ref().map(|c| c.value()), Some("hello!"));
|
|
|
|
/// # jar.remove(Cookie::named("message"));
|
|
|
|
/// # assert_eq!(jar.get("message").map(|c| c.value()), Some("hi"));
|
|
|
|
/// # assert!(jar.get_pending("message").is_none());
|
|
|
|
/// }
|
|
|
|
/// # fn main() {
|
|
|
|
/// # use rocket::local::blocking::Client;
|
|
|
|
/// # let rocket = rocket::ignite().mount("/", routes![message]);
|
|
|
|
/// # let client = Client::tracked(rocket).unwrap();
|
|
|
|
/// # let response = client.get("/message")
|
|
|
|
/// # .cookie(Cookie::new("message", "hi"))
|
|
|
|
/// # .dispatch();
|
|
|
|
/// #
|
|
|
|
/// # assert!(response.status().class().is_success());
|
|
|
|
/// # }
|
|
|
|
/// ```
|
2017-06-24 09:49:16 +00:00
|
|
|
///
|
|
|
|
/// # Usage
|
|
|
|
///
|
2020-07-22 19:21:19 +00:00
|
|
|
/// A type of `&CookieJar` can be retrieved via its `FromRequest` implementation
|
|
|
|
/// as a request guard or via the [`Request::cookies()`] method. Individual
|
|
|
|
/// cookies can be retrieved via the [`get()`] and [`get_private()`] methods.
|
2020-10-15 04:37:16 +00:00
|
|
|
/// Pending changes can be observed via the [`get_pending()`] and
|
|
|
|
/// [`get_private_pending()`] methods. Cookies can be added or removed via the
|
|
|
|
/// [`add()`], [`add_private()`], [`remove()`], and [`remove_private()`]
|
|
|
|
/// methods.
|
2018-10-06 13:25:17 +00:00
|
|
|
///
|
2019-06-13 01:33:40 +00:00
|
|
|
/// [`Request::cookies()`]: rocket::Request::cookies()
|
2018-10-06 13:25:17 +00:00
|
|
|
/// [`get()`]: #method.get
|
|
|
|
/// [`get_private()`]: #method.get_private
|
2020-10-15 04:37:16 +00:00
|
|
|
/// [`get_pending()`]: #method.get_pending
|
|
|
|
/// [`get_private_pending()`]: #method.get_private_pending
|
2018-10-06 13:25:17 +00:00
|
|
|
/// [`add()`]: #method.add
|
|
|
|
/// [`add_private()`]: #method.add_private
|
|
|
|
/// [`remove()`]: #method.remove
|
|
|
|
/// [`remove_private()`]: #method.remove_private
|
2017-06-24 09:49:16 +00:00
|
|
|
///
|
|
|
|
/// ## Examples
|
|
|
|
///
|
2020-07-22 19:21:19 +00:00
|
|
|
/// The following example shows `&CookieJar` being used as a request guard in a
|
|
|
|
/// handler to retrieve the value of a "message" cookie.
|
2017-06-24 09:49:16 +00:00
|
|
|
///
|
|
|
|
/// ```rust
|
2018-09-20 04:14:30 +00:00
|
|
|
/// # #[macro_use] extern crate rocket;
|
2020-07-22 19:21:19 +00:00
|
|
|
/// use rocket::http::CookieJar;
|
2017-06-24 09:49:16 +00:00
|
|
|
///
|
|
|
|
/// #[get("/message")]
|
2020-10-15 04:37:16 +00:00
|
|
|
/// fn message<'a>(jar: &'a CookieJar<'_>) -> Option<&'a str> {
|
|
|
|
/// jar.get("message").map(|cookie| cookie.value())
|
2017-06-24 09:49:16 +00:00
|
|
|
/// }
|
|
|
|
/// # fn main() { }
|
|
|
|
/// ```
|
|
|
|
///
|
2020-07-22 19:21:19 +00:00
|
|
|
/// The following snippet shows `&CookieJar` being retrieved from a `Request` in
|
|
|
|
/// a custom request guard implementation for `User`. A [private cookie]
|
2017-06-24 09:49:16 +00:00
|
|
|
/// containing a user's ID is retrieved. If the cookie exists and the ID parses
|
|
|
|
/// as an integer, a `User` structure is validated. Otherwise, the guard
|
|
|
|
/// forwards.
|
|
|
|
///
|
2020-07-22 19:21:19 +00:00
|
|
|
/// [private cookie]: #method.add_private
|
2017-06-24 09:49:16 +00:00
|
|
|
///
|
|
|
|
/// ```rust
|
2018-09-20 04:14:30 +00:00
|
|
|
/// # #[macro_use] extern crate rocket;
|
2020-09-03 05:41:31 +00:00
|
|
|
/// # #[cfg(feature = "private-cookies")] {
|
2017-06-24 09:49:16 +00:00
|
|
|
/// use rocket::http::Status;
|
|
|
|
/// use rocket::outcome::IntoOutcome;
|
|
|
|
/// use rocket::request::{self, Request, FromRequest};
|
|
|
|
///
|
|
|
|
/// // In practice, we'd probably fetch the user from the database.
|
|
|
|
/// struct User(usize);
|
|
|
|
///
|
2020-01-31 09:34:15 +00:00
|
|
|
/// #[rocket::async_trait]
|
|
|
|
/// impl<'a, 'r> FromRequest<'a, 'r> for User {
|
2019-04-09 03:57:47 +00:00
|
|
|
/// type Error = std::convert::Infallible;
|
2017-06-24 09:49:16 +00:00
|
|
|
///
|
2020-01-31 09:34:15 +00:00
|
|
|
/// async fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
|
2017-06-24 09:49:16 +00:00
|
|
|
/// request.cookies()
|
|
|
|
/// .get_private("user_id")
|
2020-07-22 19:21:19 +00:00
|
|
|
/// .and_then(|c| c.value().parse().ok())
|
2017-06-24 09:49:16 +00:00
|
|
|
/// .map(|id| User(id))
|
|
|
|
/// .or_forward(())
|
|
|
|
/// }
|
|
|
|
/// }
|
2020-09-03 05:41:31 +00:00
|
|
|
/// # }
|
2017-06-24 09:49:16 +00:00
|
|
|
/// # fn main() { }
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// # Private Cookies
|
|
|
|
///
|
|
|
|
/// _Private_ cookies are just like regular cookies except that they are
|
|
|
|
/// encrypted using authenticated encryption, a form of encryption which
|
|
|
|
/// simultaneously provides confidentiality, integrity, and authenticity. This
|
|
|
|
/// means that private cookies cannot be inspected, tampered with, or
|
|
|
|
/// manufactured by clients. If you prefer, you can think of private cookies as
|
|
|
|
/// being signed and encrypted.
|
|
|
|
///
|
2020-07-22 19:21:19 +00:00
|
|
|
/// Private cookies can be retrieved, added, and removed from a `CookieJar`
|
2018-10-06 13:25:17 +00:00
|
|
|
/// collection via the [`get_private()`], [`add_private()`], and
|
|
|
|
/// [`remove_private()`] methods.
|
2017-06-24 09:49:16 +00:00
|
|
|
///
|
|
|
|
/// ## Encryption Key
|
|
|
|
///
|
|
|
|
/// To encrypt private cookies, Rocket uses the 256-bit key specified in the
|
|
|
|
/// `secret_key` configuration parameter. If one is not specified, Rocket will
|
|
|
|
/// automatically generate a fresh key. Note, however, that a private cookie can
|
|
|
|
/// only be decrypted with the same key with which it was encrypted. As such, it
|
|
|
|
/// is important to set a `secret_key` configuration parameter when using
|
|
|
|
/// private cookies so that cookies decrypt properly after an application
|
|
|
|
/// restart. Rocket will emit a warning if an application is run in production
|
|
|
|
/// mode without a configured `secret_key`.
|
|
|
|
///
|
|
|
|
/// Generating a string suitable for use as a `secret_key` configuration value
|
|
|
|
/// is usually done through tools like `openssl`. Using `openssl`, for instance,
|
|
|
|
/// a 256-bit base64 key can be generated with the command `openssl rand -base64
|
|
|
|
/// 32`.
|
2020-07-22 19:21:19 +00:00
|
|
|
pub struct CookieJar<'a> {
|
|
|
|
jar: cookie::CookieJar,
|
|
|
|
key: &'a Key,
|
2020-10-15 04:37:16 +00:00
|
|
|
ops: Mutex<Vec<Op>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> Clone for CookieJar<'a> {
|
|
|
|
fn clone(&self) -> Self {
|
|
|
|
CookieJar {
|
|
|
|
jar: self.jar.clone(),
|
|
|
|
key: self.key,
|
|
|
|
ops: Mutex::new(self.ops.lock().clone()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
enum Op {
|
|
|
|
Add(Cookie<'static>, bool),
|
|
|
|
Remove(Cookie<'static>, bool),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Op {
|
|
|
|
fn cookie(&self) -> &Cookie<'static> {
|
|
|
|
match self {
|
|
|
|
Op::Add(c, _) | Op::Remove(c, _) => c
|
|
|
|
}
|
|
|
|
}
|
2017-03-07 09:19:06 +00:00
|
|
|
}
|
|
|
|
|
2020-07-22 19:21:19 +00:00
|
|
|
impl<'a> CookieJar<'a> {
|
2020-10-15 04:37:16 +00:00
|
|
|
/// Returns a reference to the _original_ `Cookie` inside this container
|
|
|
|
/// with the name `name`. If no such cookie exists, returns `None`.
|
|
|
|
///
|
|
|
|
/// **Note:** This method _does not_ obverse changes made via additions and
|
|
|
|
/// removals to the cookie jar. To observe those changes, use
|
|
|
|
/// [`CookieJar::get_pending()`].
|
2017-06-24 09:49:16 +00:00
|
|
|
///
|
|
|
|
/// # Example
|
|
|
|
///
|
|
|
|
/// ```rust
|
2020-07-22 19:21:19 +00:00
|
|
|
/// # #[macro_use] extern crate rocket;
|
|
|
|
/// use rocket::http::{Cookie, CookieJar};
|
2017-06-24 09:49:16 +00:00
|
|
|
///
|
2020-07-22 19:21:19 +00:00
|
|
|
/// #[get("/")]
|
|
|
|
/// fn handler(jar: &CookieJar<'_>) {
|
|
|
|
/// let cookie = jar.get("name");
|
2017-06-24 09:49:16 +00:00
|
|
|
/// }
|
|
|
|
/// ```
|
2020-10-15 04:37:16 +00:00
|
|
|
pub fn get(&self, name: &str) -> Option<&Cookie<'static>> {
|
2020-07-22 19:21:19 +00:00
|
|
|
self.jar.get(name)
|
|
|
|
}
|
|
|
|
|
2020-10-15 04:37:16 +00:00
|
|
|
/// Retrives the _original_ `Cookie` inside this collection with the name
|
|
|
|
/// `name` and authenticates and decrypts the cookie's value. If the cookie
|
|
|
|
/// cannot be found, or the cookie fails to authenticate or decrypt, `None`
|
|
|
|
/// is returned.
|
|
|
|
///
|
|
|
|
/// **Note:** This method _does not_ obverse changes made via additions and
|
|
|
|
/// removals to the cookie jar. To observe those changes, use
|
|
|
|
/// [`CookieJar::get_private_pending()`].
|
2020-07-22 19:21:19 +00:00
|
|
|
///
|
|
|
|
/// # Example
|
|
|
|
///
|
|
|
|
/// ```rust
|
|
|
|
/// # #[macro_use] extern crate rocket;
|
|
|
|
/// use rocket::http::{Cookie, CookieJar};
|
|
|
|
///
|
|
|
|
/// #[get("/")]
|
|
|
|
/// fn handler(jar: &CookieJar<'_>) {
|
|
|
|
/// let cookie = jar.get_private("name");
|
|
|
|
/// }
|
|
|
|
/// ```
|
|
|
|
#[cfg(feature = "private-cookies")]
|
|
|
|
#[cfg_attr(nightly, doc(cfg(feature = "secrets")))]
|
|
|
|
pub fn get_private(&self, name: &str) -> Option<Cookie<'static>> {
|
|
|
|
self.jar.private(&*self.key).get(name)
|
2017-06-06 20:41:04 +00:00
|
|
|
}
|
|
|
|
|
2020-10-15 04:37:16 +00:00
|
|
|
/// Returns a reference to the _original or pending_ `Cookie` inside this
|
|
|
|
/// container with the name `name`. If no such cookie exists, returns
|
|
|
|
/// `None`.
|
|
|
|
///
|
|
|
|
/// # Example
|
|
|
|
///
|
|
|
|
/// ```rust
|
|
|
|
/// # #[macro_use] extern crate rocket;
|
|
|
|
/// use rocket::http::{Cookie, CookieJar};
|
|
|
|
///
|
|
|
|
/// #[get("/")]
|
|
|
|
/// fn handler(jar: &CookieJar<'_>) {
|
|
|
|
/// let pending_cookie = jar.get_pending("name");
|
|
|
|
/// }
|
|
|
|
/// ```
|
|
|
|
pub fn get_pending(&self, name: &str) -> Option<Cookie<'static>> {
|
|
|
|
let ops = self.ops.lock();
|
|
|
|
for op in ops.iter().rev().filter(|op| op.cookie().name() == name) {
|
|
|
|
match op {
|
|
|
|
Op::Add(c, _) => return Some(c.clone()),
|
|
|
|
Op::Remove(_, _) => return None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
drop(ops);
|
|
|
|
self.get(name).map(|c| c.clone())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Retrives the _original or pending_ `Cookie` inside this collection with
|
|
|
|
/// the name `name` and authenticates and decrypts the cookie's value. If
|
|
|
|
/// the cookie cannot be found, or the cookie fails to authenticate or
|
|
|
|
/// decrypt, `None` is returned.
|
|
|
|
///
|
|
|
|
/// # Example
|
|
|
|
///
|
|
|
|
/// ```rust
|
|
|
|
/// # #[macro_use] extern crate rocket;
|
|
|
|
/// use rocket::http::{Cookie, CookieJar};
|
|
|
|
///
|
|
|
|
/// #[get("/")]
|
|
|
|
/// fn handler(jar: &CookieJar<'_>) {
|
|
|
|
/// let pending_cookie = jar.get_private_pending("name");
|
|
|
|
/// }
|
|
|
|
/// ```
|
2020-10-15 07:46:30 +00:00
|
|
|
#[cfg(feature = "private-cookies")]
|
|
|
|
#[cfg_attr(nightly, doc(cfg(feature = "secrets")))]
|
2020-10-15 04:37:16 +00:00
|
|
|
pub fn get_private_pending(&self, name: &str) -> Option<Cookie<'static>> {
|
|
|
|
let cookie = self.get_pending(name)?;
|
|
|
|
self.jar.private(&*self.key).decrypt(cookie)
|
|
|
|
}
|
|
|
|
|
2018-11-05 20:29:03 +00:00
|
|
|
/// Adds `cookie` to this collection.
|
2017-06-24 09:49:16 +00:00
|
|
|
///
|
2020-07-22 19:21:19 +00:00
|
|
|
/// Unless a value is set for the given property, the following defaults are
|
|
|
|
/// set on `cookie` before being added to `self`:
|
|
|
|
///
|
|
|
|
/// * `path`: `"/"`
|
|
|
|
/// * `SameSite`: `Strict`
|
|
|
|
///
|
2017-06-24 09:49:16 +00:00
|
|
|
/// # Example
|
|
|
|
///
|
|
|
|
/// ```rust
|
2020-07-22 19:21:19 +00:00
|
|
|
/// # #[macro_use] extern crate rocket;
|
|
|
|
/// use rocket::http::{Cookie, SameSite, CookieJar};
|
2017-06-24 09:49:16 +00:00
|
|
|
///
|
2020-07-22 19:21:19 +00:00
|
|
|
/// #[get("/")]
|
|
|
|
/// fn handler(jar: &CookieJar<'_>) {
|
|
|
|
/// jar.add(Cookie::new("first", "value"));
|
2018-11-05 20:29:03 +00:00
|
|
|
///
|
2020-07-22 19:21:19 +00:00
|
|
|
/// let cookie = Cookie::build("other", "value_two")
|
2018-11-05 20:29:03 +00:00
|
|
|
/// .path("/")
|
|
|
|
/// .secure(true)
|
2020-07-22 19:21:19 +00:00
|
|
|
/// .same_site(SameSite::Lax);
|
2018-11-05 20:29:03 +00:00
|
|
|
///
|
2020-07-22 19:21:19 +00:00
|
|
|
/// jar.add(cookie.finish());
|
2017-06-24 09:49:16 +00:00
|
|
|
/// }
|
|
|
|
/// ```
|
2020-07-22 19:21:19 +00:00
|
|
|
pub fn add(&self, mut cookie: Cookie<'static>) {
|
|
|
|
Self::set_defaults(&mut cookie);
|
2020-10-15 04:37:16 +00:00
|
|
|
self.ops.lock().push(Op::Add(cookie, false));
|
2020-07-22 19:21:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Adds `cookie` to the collection. The cookie's value is encrypted with
|
|
|
|
/// authenticated encryption assuring confidentiality, integrity, and
|
|
|
|
/// authenticity. The cookie can later be retrieved using
|
|
|
|
/// [`get_private`](#method.get_private) and removed using
|
|
|
|
/// [`remove_private`](#method.remove_private).
|
|
|
|
///
|
|
|
|
/// Unless a value is set for the given property, the following defaults are
|
|
|
|
/// set on `cookie` before being added to `self`:
|
|
|
|
///
|
|
|
|
/// * `path`: `"/"`
|
|
|
|
/// * `SameSite`: `Strict`
|
|
|
|
/// * `HttpOnly`: `true`
|
|
|
|
/// * `Expires`: 1 week from now
|
|
|
|
///
|
|
|
|
/// These defaults ensure maximum usability and security. For additional
|
|
|
|
/// security, you may wish to set the `secure` flag.
|
|
|
|
///
|
|
|
|
/// # Example
|
|
|
|
///
|
|
|
|
/// ```rust
|
|
|
|
/// # #[macro_use] extern crate rocket;
|
|
|
|
/// use rocket::http::{Cookie, CookieJar};
|
|
|
|
///
|
|
|
|
/// #[get("/")]
|
|
|
|
/// fn handler(jar: &CookieJar<'_>) {
|
|
|
|
/// jar.add_private(Cookie::new("name", "value"));
|
|
|
|
/// }
|
|
|
|
/// ```
|
|
|
|
#[cfg(feature = "private-cookies")]
|
|
|
|
#[cfg_attr(nightly, doc(cfg(feature = "secrets")))]
|
|
|
|
pub fn add_private(&self, mut cookie: Cookie<'static>) {
|
|
|
|
Self::set_private_defaults(&mut cookie);
|
2020-10-15 04:37:16 +00:00
|
|
|
self.ops.lock().push(Op::Add(cookie, true));
|
2017-03-07 09:19:06 +00:00
|
|
|
}
|
|
|
|
|
2018-11-05 20:29:03 +00:00
|
|
|
/// 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
|
2020-07-22 19:21:19 +00:00
|
|
|
/// cookies that are not properly removed. For convenience, if a path is not
|
|
|
|
/// set on `cookie`, the `"/"` path will automatically be set.
|
2018-11-05 20:29:03 +00:00
|
|
|
///
|
|
|
|
/// 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.
|
2017-06-24 09:49:16 +00:00
|
|
|
///
|
|
|
|
/// # Example
|
|
|
|
///
|
|
|
|
/// ```rust
|
2020-07-22 19:21:19 +00:00
|
|
|
/// # #[macro_use] extern crate rocket;
|
|
|
|
/// use rocket::http::{Cookie, CookieJar};
|
2017-06-24 09:49:16 +00:00
|
|
|
///
|
2020-07-22 19:21:19 +00:00
|
|
|
/// #[get("/")]
|
|
|
|
/// fn handler(jar: &CookieJar<'_>) {
|
|
|
|
/// jar.remove(Cookie::named("name"));
|
2018-11-05 20:29:03 +00:00
|
|
|
/// }
|
|
|
|
/// ```
|
2020-07-22 19:21:19 +00:00
|
|
|
pub fn remove(&self, mut cookie: Cookie<'static>) {
|
|
|
|
if cookie.path().is_none() {
|
|
|
|
cookie.set_path("/");
|
2018-11-05 20:29:03 +00:00
|
|
|
}
|
|
|
|
|
2020-10-15 04:37:16 +00:00
|
|
|
self.ops.lock().push(Op::Remove(cookie, false));
|
2020-01-24 05:07:11 +00:00
|
|
|
}
|
|
|
|
|
2020-07-22 19:21:19 +00:00
|
|
|
/// 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.
|
2017-06-24 09:49:16 +00:00
|
|
|
///
|
2018-11-05 20:29:03 +00:00
|
|
|
/// # Example
|
2017-06-24 09:49:16 +00:00
|
|
|
///
|
2018-11-05 20:29:03 +00:00
|
|
|
/// ```rust
|
2020-07-22 19:21:19 +00:00
|
|
|
/// # #[macro_use] extern crate rocket;
|
|
|
|
/// use rocket::http::{Cookie, CookieJar};
|
2018-11-05 20:29:03 +00:00
|
|
|
///
|
2020-07-22 19:21:19 +00:00
|
|
|
/// #[get("/")]
|
|
|
|
/// fn handler(jar: &CookieJar<'_>) {
|
|
|
|
/// jar.remove_private(Cookie::named("name"));
|
2017-06-24 09:49:16 +00:00
|
|
|
/// }
|
|
|
|
/// ```
|
2020-07-22 19:21:19 +00:00
|
|
|
#[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("/");
|
2018-11-05 20:29:03 +00:00
|
|
|
}
|
|
|
|
|
2020-10-15 04:37:16 +00:00
|
|
|
self.ops.lock().push(Op::Remove(cookie, true));
|
2018-11-05 20:29:03 +00:00
|
|
|
}
|
|
|
|
|
2020-10-15 04:37:16 +00:00
|
|
|
/// Returns an iterator over all of the _original_ cookies present in this
|
|
|
|
/// collection.
|
|
|
|
///
|
|
|
|
/// **Note:** This method _does not_ obverse changes made via additions and
|
|
|
|
/// removals to the cookie jar.
|
2018-11-05 20:29:03 +00:00
|
|
|
///
|
|
|
|
/// # Example
|
|
|
|
///
|
|
|
|
/// ```rust
|
2020-07-22 19:21:19 +00:00
|
|
|
/// # #[macro_use] extern crate rocket;
|
|
|
|
/// use rocket::http::{Cookie, CookieJar};
|
2018-11-05 20:29:03 +00:00
|
|
|
///
|
2020-07-22 19:21:19 +00:00
|
|
|
/// #[get("/")]
|
|
|
|
/// fn handler(jar: &CookieJar<'_>) {
|
|
|
|
/// for c in jar.iter() {
|
|
|
|
/// println!("Name: {:?}, Value: {:?}", c.name(), c.value());
|
|
|
|
/// }
|
2018-11-05 20:29:03 +00:00
|
|
|
/// }
|
|
|
|
/// ```
|
2020-10-15 04:37:16 +00:00
|
|
|
pub fn iter(&self) -> impl Iterator<Item=&Cookie<'static>> {
|
2020-07-22 19:21:19 +00:00
|
|
|
self.jar.iter()
|
2017-03-07 09:19:06 +00:00
|
|
|
}
|
2020-07-22 19:21:19 +00:00
|
|
|
}
|
2017-03-07 09:19:06 +00:00
|
|
|
|
2020-10-15 04:37:16 +00:00
|
|
|
/// WARNING: These are unstable! Do not use outside of Rocket!
|
2020-07-22 19:21:19 +00:00
|
|
|
#[doc(hidden)]
|
|
|
|
impl<'a> CookieJar<'a> {
|
|
|
|
#[inline(always)]
|
2020-10-15 04:37:16 +00:00
|
|
|
pub fn new(key: &'a Key) -> Self {
|
|
|
|
CookieJar {
|
|
|
|
jar: cookie::CookieJar::new(),
|
|
|
|
key, ops: Mutex::new(Vec::new()),
|
|
|
|
}
|
2020-07-22 19:21:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[inline(always)]
|
|
|
|
pub fn from(jar: cookie::CookieJar, key: &'a Key) -> CookieJar<'a> {
|
2020-10-15 04:37:16 +00:00
|
|
|
CookieJar { jar, key, ops: Mutex::new(Vec::new()) }
|
2020-07-22 19:21:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Removes all delta cookies.
|
|
|
|
#[inline(always)]
|
|
|
|
pub fn reset_delta(&self) {
|
2020-10-15 04:37:16 +00:00
|
|
|
self.ops.lock().clear();
|
2020-07-22 19:21:19 +00:00
|
|
|
}
|
|
|
|
|
2020-10-15 04:37:16 +00:00
|
|
|
/// TODO: This could be faster by just returning the cookies directly via
|
|
|
|
/// an ordered hash-set of sorts.
|
2020-07-22 19:21:19 +00:00
|
|
|
#[inline(always)]
|
2020-10-15 04:37:16 +00:00
|
|
|
pub fn take_delta_jar(&self) -> cookie::CookieJar {
|
|
|
|
let ops = std::mem::replace(&mut *self.ops.lock(), Vec::new());
|
|
|
|
let mut jar = cookie::CookieJar::new();
|
|
|
|
|
|
|
|
for op in ops {
|
|
|
|
match op {
|
|
|
|
Op::Add(c, false) => jar.add(c),
|
|
|
|
#[cfg(feature = "private-cookies")]
|
|
|
|
Op::Add(c, true) => jar.private_mut(self.key).add(c),
|
|
|
|
Op::Remove(mut c, _) => {
|
|
|
|
if self.jar.get(c.name()).is_some() {
|
|
|
|
c.make_removal();
|
|
|
|
jar.add(c);
|
|
|
|
} else {
|
|
|
|
jar.remove(c);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#[allow(unreachable_patterns)]
|
|
|
|
_ => unreachable!()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
jar
|
2020-07-22 19:21:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Adds an original `cookie` to this collection.
|
|
|
|
#[inline(always)]
|
2020-10-15 04:37:16 +00:00
|
|
|
pub fn add_original(&mut self, cookie: Cookie<'static>) {
|
2020-07-22 19:21:19 +00:00
|
|
|
self.jar.add_original(cookie)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Adds an original, private `cookie` to the collection.
|
|
|
|
#[inline(always)]
|
|
|
|
#[cfg(feature = "private-cookies")]
|
|
|
|
#[cfg_attr(nightly, doc(cfg(feature = "secrets")))]
|
2020-10-15 04:37:16 +00:00
|
|
|
pub fn add_original_private(&mut self, cookie: Cookie<'static>) {
|
|
|
|
self.jar.private_mut(&*self.key).add_original(cookie);
|
2020-07-22 19:21:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// 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:
|
2017-10-06 04:58:04 +00:00
|
|
|
///
|
|
|
|
/// * `path`: `"/"`
|
|
|
|
/// * `SameSite`: `Strict`
|
2018-11-05 20:29:03 +00:00
|
|
|
///
|
2020-07-22 19:21:19 +00:00
|
|
|
fn set_defaults(cookie: &mut Cookie<'static>) {
|
|
|
|
if cookie.path().is_none() {
|
|
|
|
cookie.set_path("/");
|
2017-11-30 17:30:31 +00:00
|
|
|
}
|
2017-06-06 20:41:04 +00:00
|
|
|
|
2020-07-22 19:21:19 +00:00
|
|
|
if cookie.same_site().is_none() {
|
|
|
|
cookie.set_same_site(SameSite::Strict);
|
2017-11-30 17:30:31 +00:00
|
|
|
}
|
|
|
|
}
|
2017-10-06 04:58:04 +00:00
|
|
|
|
2020-07-22 19:21:19 +00:00
|
|
|
/// 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:
|
2017-11-30 17:30:31 +00:00
|
|
|
///
|
|
|
|
/// * `path`: `"/"`
|
|
|
|
/// * `SameSite`: `Strict`
|
|
|
|
/// * `HttpOnly`: `true`
|
|
|
|
/// * `Expires`: 1 week from now
|
|
|
|
///
|
2020-07-22 19:21:19 +00:00
|
|
|
#[cfg(feature = "private-cookies")]
|
|
|
|
#[cfg_attr(nightly, doc(cfg(feature = "secrets")))]
|
2017-11-30 17:30:31 +00:00
|
|
|
fn set_private_defaults(cookie: &mut Cookie<'static>) {
|
|
|
|
if cookie.path().is_none() {
|
|
|
|
cookie.set_path("/");
|
|
|
|
}
|
2017-10-06 04:58:04 +00:00
|
|
|
|
2020-07-22 19:21:19 +00:00
|
|
|
if cookie.same_site().is_none() {
|
|
|
|
cookie.set_same_site(SameSite::Strict);
|
|
|
|
}
|
|
|
|
|
2017-11-30 17:30:31 +00:00
|
|
|
if cookie.http_only().is_none() {
|
|
|
|
cookie.set_http_only(true);
|
|
|
|
}
|
2017-06-06 20:41:04 +00:00
|
|
|
|
2017-11-30 17:30:31 +00:00
|
|
|
if cookie.expires().is_none() {
|
2020-05-27 04:37:30 +00:00
|
|
|
cookie.set_expires(time::OffsetDateTime::now_utc() + time::Duration::weeks(1));
|
2017-11-30 17:30:31 +00:00
|
|
|
}
|
2017-03-08 11:28:12 +00:00
|
|
|
}
|
2017-03-07 09:19:06 +00:00
|
|
|
}
|
2017-06-06 20:41:04 +00:00
|
|
|
|
2020-07-22 19:21:19 +00:00
|
|
|
impl fmt::Debug for CookieJar<'_> {
|
2019-06-13 01:33:40 +00:00
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
2020-10-15 04:37:16 +00:00
|
|
|
let pending: Vec<_> = self.ops.lock()
|
|
|
|
.iter()
|
|
|
|
.map(|c| c.cookie())
|
|
|
|
.cloned()
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
f.debug_struct("CookieJar")
|
|
|
|
.field("original", &self.jar)
|
|
|
|
.field("pending", &pending)
|
|
|
|
.finish()
|
2017-06-06 20:41:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-13 01:33:40 +00:00
|
|
|
impl From<Cookie<'_>> for Header<'static> {
|
|
|
|
fn from(cookie: Cookie<'_>) -> Header<'static> {
|
2018-04-14 23:24:41 +00:00
|
|
|
Header::new("Set-Cookie", cookie.encoded().to_string())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-13 01:33:40 +00:00
|
|
|
impl From<&Cookie<'_>> for Header<'static> {
|
|
|
|
fn from(cookie: &Cookie<'_>) -> Header<'static> {
|
2017-06-06 20:41:04 +00:00
|
|
|
Header::new("Set-Cookie", cookie.encoded().to_string())
|
|
|
|
}
|
|
|
|
}
|