summaryrefslogtreecommitdiffstats
path: root/third_party/rust/cookie/src
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/rust/cookie/src
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/cookie/src')
-rw-r--r--third_party/rust/cookie/src/builder.rs246
-rw-r--r--third_party/rust/cookie/src/delta.rs65
-rw-r--r--third_party/rust/cookie/src/draft.rs108
-rw-r--r--third_party/rust/cookie/src/expiration.rs137
-rw-r--r--third_party/rust/cookie/src/jar.rs728
-rw-r--r--third_party/rust/cookie/src/lib.rs1422
-rw-r--r--third_party/rust/cookie/src/parse.rs550
-rw-r--r--third_party/rust/cookie/src/secure/key.rs301
-rw-r--r--third_party/rust/cookie/src/secure/macros.rs49
-rw-r--r--third_party/rust/cookie/src/secure/mod.rs14
-rw-r--r--third_party/rust/cookie/src/secure/private.rs264
-rw-r--r--third_party/rust/cookie/src/secure/signed.rs251
12 files changed, 4135 insertions, 0 deletions
diff --git a/third_party/rust/cookie/src/builder.rs b/third_party/rust/cookie/src/builder.rs
new file mode 100644
index 0000000000..78d1219a9a
--- /dev/null
+++ b/third_party/rust/cookie/src/builder.rs
@@ -0,0 +1,246 @@
+use std::borrow::Cow;
+
+use crate::{Cookie, SameSite, Expiration};
+
+/// Structure that follows the builder pattern for building `Cookie` structs.
+///
+/// To construct a cookie:
+///
+/// 1. Call [`Cookie::build`] to start building.
+/// 2. Use any of the builder methods to set fields in the cookie.
+/// 3. Call [`CookieBuilder::finish()`] to retrieve the built cookie.
+///
+/// # Example
+///
+/// ```rust
+/// # extern crate cookie;
+/// use cookie::Cookie;
+/// use cookie::time::Duration;
+///
+/// # fn main() {
+/// let cookie: Cookie = Cookie::build("name", "value")
+/// .domain("www.rust-lang.org")
+/// .path("/")
+/// .secure(true)
+/// .http_only(true)
+/// .max_age(Duration::days(1))
+/// .finish();
+/// # }
+/// ```
+#[derive(Debug, Clone)]
+pub struct CookieBuilder<'c> {
+ /// The cookie being built.
+ cookie: Cookie<'c>,
+}
+
+impl<'c> CookieBuilder<'c> {
+ /// Creates a new `CookieBuilder` instance from the given name and value.
+ ///
+ /// This method is typically called indirectly via [`Cookie::build()`].
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::Cookie;
+ ///
+ /// let c = Cookie::build("foo", "bar").finish();
+ /// assert_eq!(c.name_value(), ("foo", "bar"));
+ /// ```
+ pub fn new<N, V>(name: N, value: V) -> Self
+ where N: Into<Cow<'c, str>>,
+ V: Into<Cow<'c, str>>
+ {
+ CookieBuilder { cookie: Cookie::new(name, value) }
+ }
+
+ /// Sets the `expires` field in the cookie being built.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// # extern crate cookie;
+ /// use cookie::{Cookie, Expiration};
+ /// use cookie::time::OffsetDateTime;
+ ///
+ /// # fn main() {
+ /// let c = Cookie::build("foo", "bar")
+ /// .expires(OffsetDateTime::now_utc())
+ /// .finish();
+ ///
+ /// assert!(c.expires().is_some());
+ ///
+ /// let c = Cookie::build("foo", "bar")
+ /// .expires(None)
+ /// .finish();
+ ///
+ /// assert_eq!(c.expires(), Some(Expiration::Session));
+ /// # }
+ /// ```
+ #[inline]
+ pub fn expires<E: Into<Expiration>>(mut self, when: E) -> Self {
+ self.cookie.set_expires(when);
+ self
+ }
+
+ /// Sets the `max_age` field in the cookie being built.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// # extern crate cookie;
+ /// use cookie::Cookie;
+ /// use cookie::time::Duration;
+ ///
+ /// # fn main() {
+ /// let c = Cookie::build("foo", "bar")
+ /// .max_age(Duration::minutes(30))
+ /// .finish();
+ ///
+ /// assert_eq!(c.max_age(), Some(Duration::seconds(30 * 60)));
+ /// # }
+ /// ```
+ #[inline]
+ pub fn max_age(mut self, value: time::Duration) -> Self {
+ self.cookie.set_max_age(value);
+ self
+ }
+
+ /// Sets the `domain` field in the cookie being built.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::Cookie;
+ ///
+ /// let c = Cookie::build("foo", "bar")
+ /// .domain("www.rust-lang.org")
+ /// .finish();
+ ///
+ /// assert_eq!(c.domain(), Some("www.rust-lang.org"));
+ /// ```
+ pub fn domain<D: Into<Cow<'c, str>>>(mut self, value: D) -> Self {
+ self.cookie.set_domain(value);
+ self
+ }
+
+ /// Sets the `path` field in the cookie being built.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::Cookie;
+ ///
+ /// let c = Cookie::build("foo", "bar")
+ /// .path("/")
+ /// .finish();
+ ///
+ /// assert_eq!(c.path(), Some("/"));
+ /// ```
+ pub fn path<P: Into<Cow<'c, str>>>(mut self, path: P) -> Self {
+ self.cookie.set_path(path);
+ self
+ }
+
+ /// Sets the `secure` field in the cookie being built.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::Cookie;
+ ///
+ /// let c = Cookie::build("foo", "bar")
+ /// .secure(true)
+ /// .finish();
+ ///
+ /// assert_eq!(c.secure(), Some(true));
+ /// ```
+ #[inline]
+ pub fn secure(mut self, value: bool) -> Self {
+ self.cookie.set_secure(value);
+ self
+ }
+
+ /// Sets the `http_only` field in the cookie being built.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::Cookie;
+ ///
+ /// let c = Cookie::build("foo", "bar")
+ /// .http_only(true)
+ /// .finish();
+ ///
+ /// assert_eq!(c.http_only(), Some(true));
+ /// ```
+ #[inline]
+ pub fn http_only(mut self, value: bool) -> Self {
+ self.cookie.set_http_only(value);
+ self
+ }
+
+ /// Sets the `same_site` field in the cookie being built.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::{Cookie, SameSite};
+ ///
+ /// let c = Cookie::build("foo", "bar")
+ /// .same_site(SameSite::Strict)
+ /// .finish();
+ ///
+ /// assert_eq!(c.same_site(), Some(SameSite::Strict));
+ /// ```
+ #[inline]
+ pub fn same_site(mut self, value: SameSite) -> Self {
+ self.cookie.set_same_site(value);
+ self
+ }
+
+ /// Makes the cookie being built 'permanent' by extending its expiration and
+ /// max age 20 years into the future.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// # extern crate cookie;
+ /// use cookie::Cookie;
+ /// use cookie::time::Duration;
+ ///
+ /// # fn main() {
+ /// let c = Cookie::build("foo", "bar")
+ /// .permanent()
+ /// .finish();
+ ///
+ /// assert_eq!(c.max_age(), Some(Duration::days(365 * 20)));
+ /// # assert!(c.expires().is_some());
+ /// # }
+ /// ```
+ #[inline]
+ pub fn permanent(mut self) -> Self {
+ self.cookie.make_permanent();
+ self
+ }
+
+ /// Finishes building and returns the built `Cookie`.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::Cookie;
+ ///
+ /// let c = Cookie::build("foo", "bar")
+ /// .domain("crates.io")
+ /// .path("/")
+ /// .finish();
+ ///
+ /// assert_eq!(c.name_value(), ("foo", "bar"));
+ /// assert_eq!(c.domain(), Some("crates.io"));
+ /// assert_eq!(c.path(), Some("/"));
+ /// ```
+ #[inline]
+ pub fn finish(self) -> Cookie<'c> {
+ self.cookie
+ }
+}
diff --git a/third_party/rust/cookie/src/delta.rs b/third_party/rust/cookie/src/delta.rs
new file mode 100644
index 0000000000..3ef35fadd7
--- /dev/null
+++ b/third_party/rust/cookie/src/delta.rs
@@ -0,0 +1,65 @@
+use std::ops::{Deref, DerefMut};
+use std::hash::{Hash, Hasher};
+use std::borrow::Borrow;
+
+use crate::Cookie;
+
+/// A `DeltaCookie` is a helper structure used in a cookie jar. It wraps a
+/// `Cookie` so that it can be hashed and compared purely by name. It further
+/// records whether the wrapped cookie is a "removal" cookie, that is, a cookie
+/// that when sent to the client removes the named cookie on the client's
+/// machine.
+#[derive(Clone, Debug)]
+pub struct DeltaCookie {
+ pub cookie: Cookie<'static>,
+ pub removed: bool,
+}
+
+impl DeltaCookie {
+ /// Create a new `DeltaCookie` that is being added to a jar.
+ #[inline]
+ pub fn added(cookie: Cookie<'static>) -> DeltaCookie {
+ DeltaCookie { cookie, removed: false, }
+ }
+
+ /// Create a new `DeltaCookie` that is being removed from a jar. The
+ /// `cookie` should be a "removal" cookie.
+ #[inline]
+ pub fn removed(cookie: Cookie<'static>) -> DeltaCookie {
+ DeltaCookie { cookie, removed: true, }
+ }
+}
+
+impl Deref for DeltaCookie {
+ type Target = Cookie<'static>;
+
+ fn deref(&self) -> &Cookie<'static> {
+ &self.cookie
+ }
+}
+
+impl DerefMut for DeltaCookie {
+ fn deref_mut(&mut self) -> &mut Cookie<'static> {
+ &mut self.cookie
+ }
+}
+
+impl PartialEq for DeltaCookie {
+ fn eq(&self, other: &DeltaCookie) -> bool {
+ self.name() == other.name()
+ }
+}
+
+impl Eq for DeltaCookie {}
+
+impl Hash for DeltaCookie {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ self.name().hash(state);
+ }
+}
+
+impl Borrow<str> for DeltaCookie {
+ fn borrow(&self) -> &str {
+ self.name()
+ }
+}
diff --git a/third_party/rust/cookie/src/draft.rs b/third_party/rust/cookie/src/draft.rs
new file mode 100644
index 0000000000..e625e81c90
--- /dev/null
+++ b/third_party/rust/cookie/src/draft.rs
@@ -0,0 +1,108 @@
+//! This module contains types that represent cookie properties that are not yet
+//! standardized. That is, _draft_ features.
+
+use std::fmt;
+
+/// The `SameSite` cookie attribute.
+///
+/// A cookie with a `SameSite` attribute is imposed restrictions on when it is
+/// sent to the origin server in a cross-site request. If the `SameSite`
+/// attribute is "Strict", then the cookie is never sent in cross-site requests.
+/// If the `SameSite` attribute is "Lax", the cookie is only sent in cross-site
+/// requests with "safe" HTTP methods, i.e, `GET`, `HEAD`, `OPTIONS`, `TRACE`.
+/// If the `SameSite` attribute is "None", the cookie is sent in all cross-site
+/// requests if the "Secure" flag is also set, otherwise the cookie is ignored.
+/// This library automatically sets the "Secure" flag on cookies when
+/// `same_site` is set to `SameSite::None` as long as `secure` is not explicitly
+/// set to `false`.
+///
+/// If the `SameSite` attribute is not present (by not setting `SameSite`
+/// initally or passing `None` to [`Cookie::set_same_site()`]), then the cookie
+/// will be sent as normal.
+///
+/// **Note:** This cookie attribute is an [HTTP draft]! Its meaning and
+/// definition are subject to change.
+///
+/// [`Cookie::set_same_site()`]: crate::Cookie::set_same_site()
+/// [HTTP draft]: https://tools.ietf.org/html/draft-west-cookie-incrementalism-00
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum SameSite {
+ /// The "Strict" `SameSite` attribute.
+ Strict,
+ /// The "Lax" `SameSite` attribute.
+ Lax,
+ /// The "None" `SameSite` attribute.
+ None
+}
+
+impl SameSite {
+ /// Returns `true` if `self` is `SameSite::Strict` and `false` otherwise.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::SameSite;
+ ///
+ /// let strict = SameSite::Strict;
+ /// assert!(strict.is_strict());
+ /// assert!(!strict.is_lax());
+ /// assert!(!strict.is_none());
+ /// ```
+ #[inline]
+ pub fn is_strict(&self) -> bool {
+ match *self {
+ SameSite::Strict => true,
+ SameSite::Lax | SameSite::None => false,
+ }
+ }
+
+ /// Returns `true` if `self` is `SameSite::Lax` and `false` otherwise.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::SameSite;
+ ///
+ /// let lax = SameSite::Lax;
+ /// assert!(lax.is_lax());
+ /// assert!(!lax.is_strict());
+ /// assert!(!lax.is_none());
+ /// ```
+ #[inline]
+ pub fn is_lax(&self) -> bool {
+ match *self {
+ SameSite::Lax => true,
+ SameSite::Strict | SameSite::None => false,
+ }
+ }
+
+ /// Returns `true` if `self` is `SameSite::None` and `false` otherwise.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::SameSite;
+ ///
+ /// let none = SameSite::None;
+ /// assert!(none.is_none());
+ /// assert!(!none.is_lax());
+ /// assert!(!none.is_strict());
+ /// ```
+ #[inline]
+ pub fn is_none(&self) -> bool {
+ match *self {
+ SameSite::None => true,
+ SameSite::Lax | SameSite::Strict => false
+ }
+ }
+}
+
+impl fmt::Display for SameSite {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ SameSite::Strict => write!(f, "Strict"),
+ SameSite::Lax => write!(f, "Lax"),
+ SameSite::None => write!(f, "None"),
+ }
+ }
+}
diff --git a/third_party/rust/cookie/src/expiration.rs b/third_party/rust/cookie/src/expiration.rs
new file mode 100644
index 0000000000..2ff2885483
--- /dev/null
+++ b/third_party/rust/cookie/src/expiration.rs
@@ -0,0 +1,137 @@
+use time::OffsetDateTime;
+
+/// A cookie's expiration: either session or a date-time.
+///
+/// An `Expiration` is constructible via `Expiration::from()` with an
+/// `Option<OffsetDateTime>` or an `OffsetDateTime`:
+///
+/// * `None` -> `Expiration::Session`
+/// * `Some(OffsetDateTime)` -> `Expiration::DateTime`
+/// * `OffsetDateTime` -> `Expiration::DateTime`
+///
+/// ```rust
+/// use cookie::Expiration;
+/// use time::OffsetDateTime;
+///
+/// let expires = Expiration::from(None);
+/// assert_eq!(expires, Expiration::Session);
+///
+/// let now = OffsetDateTime::now_utc();
+/// let expires = Expiration::from(now);
+/// assert_eq!(expires, Expiration::DateTime(now));
+///
+/// let expires = Expiration::from(Some(now));
+/// assert_eq!(expires, Expiration::DateTime(now));
+/// ```
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum Expiration {
+ /// Expiration for a "permanent" cookie at a specific date-time.
+ DateTime(OffsetDateTime),
+ /// Expiration for a "session" cookie. Browsers define the notion of a
+ /// "session" and will automatically expire session cookies when they deem
+ /// the "session" to be over. This is typically, but need not be, when the
+ /// browser is closed.
+ Session,
+}
+
+impl Expiration {
+ /// Returns `true` if `self` is an `Expiration::DateTime`.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::Expiration;
+ /// use time::OffsetDateTime;
+ ///
+ /// let expires = Expiration::from(None);
+ /// assert!(!expires.is_datetime());
+ ///
+ /// let expires = Expiration::from(OffsetDateTime::now_utc());
+ /// assert!(expires.is_datetime());
+ /// ```
+ pub fn is_datetime(&self) -> bool {
+ match self {
+ Expiration::DateTime(_) => true,
+ Expiration::Session => false
+ }
+ }
+
+ /// Returns `true` if `self` is an `Expiration::Session`.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::Expiration;
+ /// use time::OffsetDateTime;
+ ///
+ /// let expires = Expiration::from(None);
+ /// assert!(expires.is_session());
+ ///
+ /// let expires = Expiration::from(OffsetDateTime::now_utc());
+ /// assert!(!expires.is_session());
+ /// ```
+ pub fn is_session(&self) -> bool {
+ match self {
+ Expiration::DateTime(_) => false,
+ Expiration::Session => true
+ }
+ }
+
+ /// Returns the inner `OffsetDateTime` if `self` is a `DateTime`.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::Expiration;
+ /// use time::OffsetDateTime;
+ ///
+ /// let expires = Expiration::from(None);
+ /// assert!(expires.datetime().is_none());
+ ///
+ /// let now = OffsetDateTime::now_utc();
+ /// let expires = Expiration::from(now);
+ /// assert_eq!(expires.datetime(), Some(now));
+ /// ```
+ pub fn datetime(self) -> Option<OffsetDateTime> {
+ match self {
+ Expiration::Session => None,
+ Expiration::DateTime(v) => Some(v)
+ }
+ }
+
+ /// Applied `f` to the inner `OffsetDateTime` if `self` is a `DateTime` and
+ /// returns the mapped `Expiration`.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::Expiration;
+ /// use time::{OffsetDateTime, Duration};
+ ///
+ /// let now = OffsetDateTime::now_utc();
+ /// let one_week = Duration::weeks(1);
+ ///
+ /// let expires = Expiration::from(now);
+ /// assert_eq!(expires.map(|t| t + one_week).datetime(), Some(now + one_week));
+ ///
+ /// let expires = Expiration::from(None);
+ /// assert_eq!(expires.map(|t| t + one_week).datetime(), None);
+ /// ```
+ pub fn map<F>(self, f: F) -> Self
+ where F: FnOnce(OffsetDateTime) -> OffsetDateTime
+ {
+ match self {
+ Expiration::Session => Expiration::Session,
+ Expiration::DateTime(v) => Expiration::DateTime(f(v)),
+ }
+ }
+}
+
+impl<T: Into<Option<OffsetDateTime>>> From<T> for Expiration {
+ fn from(option: T) -> Self {
+ match option.into() {
+ Some(value) => Expiration::DateTime(value),
+ None => Expiration::Session
+ }
+ }
+}
diff --git a/third_party/rust/cookie/src/jar.rs b/third_party/rust/cookie/src/jar.rs
new file mode 100644
index 0000000000..83f2a15fdc
--- /dev/null
+++ b/third_party/rust/cookie/src/jar.rs
@@ -0,0 +1,728 @@
+use std::collections::HashSet;
+
+#[cfg(feature = "signed")] use crate::secure::SignedJar;
+#[cfg(feature = "private")] use crate::secure::PrivateJar;
+#[cfg(any(feature = "signed", feature = "private"))] use crate::secure::Key;
+
+use crate::delta::DeltaCookie;
+use crate::Cookie;
+
+/// A collection of cookies that tracks its modifications.
+///
+/// A `CookieJar` provides storage for any number of cookies. Any changes made
+/// to the jar are tracked; the changes can be retrieved via the
+/// [delta](#method.delta) method which returns an iterator over the changes.
+///
+/// # Usage
+///
+/// A jar's life begins via [new](#method.new) and calls to
+/// [`add_original`](#method.add_original):
+///
+/// ```rust
+/// use cookie::{Cookie, CookieJar};
+///
+/// let mut jar = CookieJar::new();
+/// jar.add_original(Cookie::new("name", "value"));
+/// jar.add_original(Cookie::new("second", "another"));
+/// ```
+///
+/// Cookies can be added via [add](#method.add) and removed via
+/// [remove](#method.remove). Finally, cookies can be looked up via
+/// [get](#method.get):
+///
+/// ```rust
+/// # use cookie::{Cookie, CookieJar};
+/// let mut jar = CookieJar::new();
+/// jar.add(Cookie::new("a", "one"));
+/// jar.add(Cookie::new("b", "two"));
+///
+/// assert_eq!(jar.get("a").map(|c| c.value()), Some("one"));
+/// assert_eq!(jar.get("b").map(|c| c.value()), Some("two"));
+///
+/// jar.remove(Cookie::named("b"));
+/// assert!(jar.get("b").is_none());
+/// ```
+///
+/// # Deltas
+///
+/// A jar keeps track of any modifications made to it over time. The
+/// modifications are recorded as cookies. The modifications can be retrieved
+/// via [delta](#method.delta). Any new `Cookie` added to a jar via `add`
+/// results in the same `Cookie` appearing in the `delta`; cookies added via
+/// `add_original` do not count towards the delta. Any _original_ cookie that is
+/// removed from a jar results in a "removal" cookie appearing in the delta. A
+/// "removal" cookie is a cookie that a server sends so that the cookie is
+/// removed from the client's machine.
+///
+/// Deltas are typically used to create `Set-Cookie` headers corresponding to
+/// the changes made to a cookie jar over a period of time.
+///
+/// ```rust
+/// # use cookie::{Cookie, CookieJar};
+/// let mut jar = CookieJar::new();
+///
+/// // original cookies don't affect the delta
+/// jar.add_original(Cookie::new("original", "value"));
+/// assert_eq!(jar.delta().count(), 0);
+///
+/// // new cookies result in an equivalent `Cookie` in the delta
+/// jar.add(Cookie::new("a", "one"));
+/// jar.add(Cookie::new("b", "two"));
+/// assert_eq!(jar.delta().count(), 2);
+///
+/// // removing an original cookie adds a "removal" cookie to the delta
+/// jar.remove(Cookie::named("original"));
+/// assert_eq!(jar.delta().count(), 3);
+///
+/// // removing a new cookie that was added removes that `Cookie` from the delta
+/// jar.remove(Cookie::named("a"));
+/// assert_eq!(jar.delta().count(), 2);
+/// ```
+#[derive(Default, Debug, Clone)]
+pub struct CookieJar {
+ original_cookies: HashSet<DeltaCookie>,
+ delta_cookies: HashSet<DeltaCookie>,
+}
+
+impl CookieJar {
+ /// Creates an empty cookie jar.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::CookieJar;
+ ///
+ /// let jar = CookieJar::new();
+ /// assert_eq!(jar.iter().count(), 0);
+ /// ```
+ pub fn new() -> CookieJar {
+ CookieJar::default()
+ }
+
+ /// Returns a reference to the `Cookie` inside this jar with the name
+ /// `name`. If no such cookie exists, returns `None`.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::{CookieJar, Cookie};
+ ///
+ /// let mut jar = CookieJar::new();
+ /// assert!(jar.get("name").is_none());
+ ///
+ /// jar.add(Cookie::new("name", "value"));
+ /// assert_eq!(jar.get("name").map(|c| c.value()), Some("value"));
+ /// ```
+ pub fn get(&self, name: &str) -> Option<&Cookie<'static>> {
+ self.delta_cookies
+ .get(name)
+ .or_else(|| self.original_cookies.get(name))
+ .and_then(|c| if c.removed { None } else { Some(&c.cookie) })
+ }
+
+ /// Adds an "original" `cookie` to this jar. If an original cookie with the
+ /// same name already exists, it is replaced with `cookie`. Cookies added
+ /// with `add` take precedence and are not replaced by this method.
+ ///
+ /// Adding an original cookie does not affect the [delta](#method.delta)
+ /// computation. This method is intended to be used to seed the cookie jar
+ /// with cookies received from a client's HTTP message.
+ ///
+ /// For accurate `delta` computations, this method should not be called
+ /// after calling `remove`.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::{CookieJar, Cookie};
+ ///
+ /// let mut jar = CookieJar::new();
+ /// jar.add_original(Cookie::new("name", "value"));
+ /// jar.add_original(Cookie::new("second", "two"));
+ ///
+ /// assert_eq!(jar.get("name").map(|c| c.value()), Some("value"));
+ /// assert_eq!(jar.get("second").map(|c| c.value()), Some("two"));
+ /// assert_eq!(jar.iter().count(), 2);
+ /// assert_eq!(jar.delta().count(), 0);
+ /// ```
+ pub fn add_original(&mut self, cookie: Cookie<'static>) {
+ self.original_cookies.replace(DeltaCookie::added(cookie));
+ }
+
+ /// Adds `cookie` to this jar. If a cookie with the same name already
+ /// exists, it is replaced with `cookie`.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::{CookieJar, Cookie};
+ ///
+ /// let mut jar = CookieJar::new();
+ /// jar.add(Cookie::new("name", "value"));
+ /// jar.add(Cookie::new("second", "two"));
+ ///
+ /// assert_eq!(jar.get("name").map(|c| c.value()), Some("value"));
+ /// assert_eq!(jar.get("second").map(|c| c.value()), Some("two"));
+ /// assert_eq!(jar.iter().count(), 2);
+ /// assert_eq!(jar.delta().count(), 2);
+ /// ```
+ pub fn add(&mut self, cookie: Cookie<'static>) {
+ self.delta_cookies.replace(DeltaCookie::added(cookie));
+ }
+
+ /// Removes `cookie` from this jar. If an _original_ cookie with the same
+ /// name as `cookie` is present in the jar, a _removal_ cookie will be
+ /// present in the `delta` computation. To properly generate the removal
+ /// cookie, `cookie` must contain the same `path` and `domain` as the cookie
+ /// that was initially 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. See also [`Cookie::make_removal()`].
+ ///
+ /// # Example
+ ///
+ /// Removing an _original_ cookie results in a _removal_ cookie:
+ ///
+ /// ```rust
+ /// # extern crate cookie;
+ /// use cookie::{CookieJar, Cookie};
+ /// use cookie::time::Duration;
+ ///
+ /// # fn main() {
+ /// let mut jar = CookieJar::new();
+ ///
+ /// // Assume this cookie originally had a path of "/" and domain of "a.b".
+ /// jar.add_original(Cookie::new("name", "value"));
+ ///
+ /// // If the path and domain were set, they must be provided to `remove`.
+ /// jar.remove(Cookie::build("name", "").path("/").domain("a.b").finish());
+ ///
+ /// // The delta will contain the removal cookie.
+ /// let delta: Vec<_> = jar.delta().collect();
+ /// assert_eq!(delta.len(), 1);
+ /// assert_eq!(delta[0].name(), "name");
+ /// assert_eq!(delta[0].max_age(), Some(Duration::seconds(0)));
+ /// # }
+ /// ```
+ ///
+ /// Removing a new cookie does not result in a _removal_ cookie unless
+ /// there's an original cookie with the same name:
+ ///
+ /// ```rust
+ /// use cookie::{CookieJar, Cookie};
+ ///
+ /// let mut jar = CookieJar::new();
+ /// jar.add(Cookie::new("name", "value"));
+ /// assert_eq!(jar.delta().count(), 1);
+ ///
+ /// jar.remove(Cookie::named("name"));
+ /// assert_eq!(jar.delta().count(), 0);
+ ///
+ /// jar.add_original(Cookie::new("name", "value"));
+ /// jar.add(Cookie::new("name", "value"));
+ /// assert_eq!(jar.delta().count(), 1);
+ ///
+ /// jar.remove(Cookie::named("name"));
+ /// assert_eq!(jar.delta().count(), 1);
+ /// ```
+ pub fn remove(&mut self, mut cookie: Cookie<'static>) {
+ if self.original_cookies.contains(cookie.name()) {
+ cookie.make_removal();
+ self.delta_cookies.replace(DeltaCookie::removed(cookie));
+ } else {
+ self.delta_cookies.remove(cookie.name());
+ }
+ }
+
+ /// Removes `cookie` from this jar completely. This method differs from
+ /// `remove` in that no delta cookie is created under any condition. Neither
+ /// the `delta` nor `iter` methods will return a cookie that is removed
+ /// using this method.
+ ///
+ /// # Example
+ ///
+ /// Removing an _original_ cookie; no _removal_ cookie is generated:
+ ///
+ /// ```rust
+ /// # extern crate cookie;
+ /// use cookie::{CookieJar, Cookie};
+ /// use cookie::time::Duration;
+ ///
+ /// # fn main() {
+ /// let mut jar = CookieJar::new();
+ ///
+ /// // Add an original cookie and a new cookie.
+ /// jar.add_original(Cookie::new("name", "value"));
+ /// jar.add(Cookie::new("key", "value"));
+ /// assert_eq!(jar.delta().count(), 1);
+ /// assert_eq!(jar.iter().count(), 2);
+ ///
+ /// // Now force remove the original cookie.
+ /// jar.force_remove(&Cookie::named("name"));
+ /// assert_eq!(jar.delta().count(), 1);
+ /// assert_eq!(jar.iter().count(), 1);
+ ///
+ /// // Now force remove the new cookie.
+ /// jar.force_remove(&Cookie::named("key"));
+ /// assert_eq!(jar.delta().count(), 0);
+ /// assert_eq!(jar.iter().count(), 0);
+ /// # }
+ /// ```
+ pub fn force_remove<'a>(&mut self, cookie: &Cookie<'a>) {
+ self.original_cookies.remove(cookie.name());
+ self.delta_cookies.remove(cookie.name());
+ }
+
+ /// Removes all delta cookies, i.e. all cookies not added via
+ /// [`CookieJar::add_original()`], from this `CookieJar`. This undoes any
+ /// changes from [`CookieJar::add()`] and [`CookieJar::remove()`]
+ /// operations.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::{CookieJar, Cookie};
+ ///
+ /// let mut jar = CookieJar::new();
+ ///
+ /// // Only original cookies will remain after calling `reset_delta`.
+ /// jar.add_original(Cookie::new("name", "value"));
+ /// jar.add_original(Cookie::new("language", "Rust"));
+ ///
+ /// // These operations, represented by delta cookies, will be reset.
+ /// jar.add(Cookie::new("language", "C++"));
+ /// jar.remove(Cookie::named("name"));
+ ///
+ /// // All is normal.
+ /// assert_eq!(jar.get("name"), None);
+ /// assert_eq!(jar.get("language").map(Cookie::value), Some("C++"));
+ /// assert_eq!(jar.iter().count(), 1);
+ /// assert_eq!(jar.delta().count(), 2);
+ ///
+ /// // Resetting undoes delta operations.
+ /// jar.reset_delta();
+ /// assert_eq!(jar.get("name").map(Cookie::value), Some("value"));
+ /// assert_eq!(jar.get("language").map(Cookie::value), Some("Rust"));
+ /// assert_eq!(jar.iter().count(), 2);
+ /// assert_eq!(jar.delta().count(), 0);
+ /// ```
+ pub fn reset_delta(&mut self) {
+ self.delta_cookies = HashSet::new();
+ }
+
+ /// Returns an iterator over cookies that represent the changes to this jar
+ /// over time. These cookies can be rendered directly as `Set-Cookie` header
+ /// values to affect the changes made to this jar on the client.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::{CookieJar, Cookie};
+ ///
+ /// let mut jar = CookieJar::new();
+ /// jar.add_original(Cookie::new("name", "value"));
+ /// jar.add_original(Cookie::new("second", "two"));
+ ///
+ /// // Add new cookies.
+ /// jar.add(Cookie::new("new", "third"));
+ /// jar.add(Cookie::new("another", "fourth"));
+ /// jar.add(Cookie::new("yac", "fifth"));
+ ///
+ /// // Remove some cookies.
+ /// jar.remove(Cookie::named("name"));
+ /// jar.remove(Cookie::named("another"));
+ ///
+ /// // Delta contains two new cookies ("new", "yac") and a removal ("name").
+ /// assert_eq!(jar.delta().count(), 3);
+ /// ```
+ pub fn delta(&self) -> Delta {
+ Delta { iter: self.delta_cookies.iter() }
+ }
+
+ /// Returns an iterator over all of the cookies present in this jar.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::{CookieJar, Cookie};
+ ///
+ /// let mut jar = CookieJar::new();
+ ///
+ /// jar.add_original(Cookie::new("name", "value"));
+ /// jar.add_original(Cookie::new("second", "two"));
+ ///
+ /// jar.add(Cookie::new("new", "third"));
+ /// jar.add(Cookie::new("another", "fourth"));
+ /// jar.add(Cookie::new("yac", "fifth"));
+ ///
+ /// jar.remove(Cookie::named("name"));
+ /// jar.remove(Cookie::named("another"));
+ ///
+ /// // There are three cookies in the jar: "second", "new", and "yac".
+ /// # assert_eq!(jar.iter().count(), 3);
+ /// for cookie in jar.iter() {
+ /// match cookie.name() {
+ /// "second" => assert_eq!(cookie.value(), "two"),
+ /// "new" => assert_eq!(cookie.value(), "third"),
+ /// "yac" => assert_eq!(cookie.value(), "fifth"),
+ /// _ => unreachable!("there are only three cookies in the jar")
+ /// }
+ /// }
+ /// ```
+ pub fn iter(&self) -> Iter {
+ Iter {
+ delta_cookies: self.delta_cookies.iter()
+ .chain(self.original_cookies.difference(&self.delta_cookies)),
+ }
+ }
+
+ /// Returns a read-only `PrivateJar` with `self` as its parent jar using the
+ /// key `key` to verify/decrypt cookies retrieved from the child jar. Any
+ /// retrievals from the child jar will be made from the parent jar.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::{Cookie, CookieJar, Key};
+ ///
+ /// // Generate a secure key.
+ /// let key = Key::generate();
+ ///
+ /// // Add a private (signed + encrypted) cookie.
+ /// let mut jar = CookieJar::new();
+ /// jar.private_mut(&key).add(Cookie::new("private", "text"));
+ ///
+ /// // The cookie's contents are encrypted.
+ /// assert_ne!(jar.get("private").unwrap().value(), "text");
+ ///
+ /// // They can be decrypted and verified through the child jar.
+ /// assert_eq!(jar.private(&key).get("private").unwrap().value(), "text");
+ ///
+ /// // A tampered with cookie does not validate but still exists.
+ /// let mut cookie = jar.get("private").unwrap().clone();
+ /// jar.add(Cookie::new("private", cookie.value().to_string() + "!"));
+ /// assert!(jar.private(&key).get("private").is_none());
+ /// assert!(jar.get("private").is_some());
+ /// ```
+ #[cfg(feature = "private")]
+ #[cfg_attr(all(nightly, doc), doc(cfg(feature = "private")))]
+ pub fn private<'a>(&'a self, key: &Key) -> PrivateJar<&'a Self> {
+ PrivateJar::new(self, key)
+ }
+
+ /// Returns a read/write `PrivateJar` with `self` as its parent jar using
+ /// the key `key` to sign/encrypt and verify/decrypt cookies added/retrieved
+ /// from the child jar.
+ ///
+ /// Any modifications to the child jar will be reflected on the parent jar,
+ /// and any retrievals from the child jar will be made from the parent jar.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::{Cookie, CookieJar, Key};
+ ///
+ /// // Generate a secure key.
+ /// let key = Key::generate();
+ ///
+ /// // Add a private (signed + encrypted) cookie.
+ /// let mut jar = CookieJar::new();
+ /// jar.private_mut(&key).add(Cookie::new("private", "text"));
+ ///
+ /// // Remove a cookie using the child jar.
+ /// jar.private_mut(&key).remove(Cookie::named("private"));
+ /// ```
+ #[cfg(feature = "private")]
+ #[cfg_attr(all(nightly, doc), doc(cfg(feature = "private")))]
+ pub fn private_mut<'a>(&'a mut self, key: &Key) -> PrivateJar<&'a mut Self> {
+ PrivateJar::new(self, key)
+ }
+
+ /// Returns a read-only `SignedJar` with `self` as its parent jar using the
+ /// key `key` to verify cookies retrieved from the child jar. Any retrievals
+ /// from the child jar will be made from the parent jar.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::{Cookie, CookieJar, Key};
+ ///
+ /// // Generate a secure key.
+ /// let key = Key::generate();
+ ///
+ /// // Add a signed cookie.
+ /// let mut jar = CookieJar::new();
+ /// jar.signed_mut(&key).add(Cookie::new("signed", "text"));
+ ///
+ /// // The cookie's contents are signed but still in plaintext.
+ /// assert_ne!(jar.get("signed").unwrap().value(), "text");
+ /// assert!(jar.get("signed").unwrap().value().contains("text"));
+ ///
+ /// // They can be verified through the child jar.
+ /// assert_eq!(jar.signed(&key).get("signed").unwrap().value(), "text");
+ ///
+ /// // A tampered with cookie does not validate but still exists.
+ /// let mut cookie = jar.get("signed").unwrap().clone();
+ /// jar.add(Cookie::new("signed", cookie.value().to_string() + "!"));
+ /// assert!(jar.signed(&key).get("signed").is_none());
+ /// assert!(jar.get("signed").is_some());
+ /// ```
+ #[cfg(feature = "signed")]
+ #[cfg_attr(all(nightly, doc), doc(cfg(feature = "signed")))]
+ pub fn signed<'a>(&'a self, key: &Key) -> SignedJar<&'a Self> {
+ SignedJar::new(self, key)
+ }
+
+ /// Returns a read/write `SignedJar` with `self` as its parent jar using the
+ /// key `key` to sign/verify cookies added/retrieved from the child jar.
+ ///
+ /// Any modifications to the child jar will be reflected on the parent jar,
+ /// and any retrievals from the child jar will be made from the parent jar.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::{Cookie, CookieJar, Key};
+ ///
+ /// // Generate a secure key.
+ /// let key = Key::generate();
+ ///
+ /// // Add a signed cookie.
+ /// let mut jar = CookieJar::new();
+ /// jar.signed_mut(&key).add(Cookie::new("signed", "text"));
+ ///
+ /// // Remove a cookie.
+ /// jar.signed_mut(&key).remove(Cookie::named("signed"));
+ /// ```
+ #[cfg(feature = "signed")]
+ #[cfg_attr(all(nightly, doc), doc(cfg(feature = "signed")))]
+ pub fn signed_mut<'a>(&'a mut self, key: &Key) -> SignedJar<&'a mut Self> {
+ SignedJar::new(self, key)
+ }
+}
+
+use std::collections::hash_set::Iter as HashSetIter;
+
+/// Iterator over the changes to a cookie jar.
+pub struct Delta<'a> {
+ iter: HashSetIter<'a, DeltaCookie>,
+}
+
+impl<'a> Iterator for Delta<'a> {
+ type Item = &'a Cookie<'static>;
+
+ fn next(&mut self) -> Option<&'a Cookie<'static>> {
+ self.iter.next().map(|c| &c.cookie)
+ }
+}
+
+use std::collections::hash_set::Difference;
+use std::collections::hash_map::RandomState;
+use std::iter::Chain;
+
+/// Iterator over all of the cookies in a jar.
+pub struct Iter<'a> {
+ delta_cookies: Chain<HashSetIter<'a, DeltaCookie>, Difference<'a, DeltaCookie, RandomState>>,
+}
+
+impl<'a> Iterator for Iter<'a> {
+ type Item = &'a Cookie<'static>;
+
+ fn next(&mut self) -> Option<&'a Cookie<'static>> {
+ for cookie in self.delta_cookies.by_ref() {
+ if !cookie.removed {
+ return Some(&*cookie);
+ }
+ }
+
+ None
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::CookieJar;
+ use crate::Cookie;
+
+ #[test]
+ #[allow(deprecated)]
+ fn simple() {
+ let mut c = CookieJar::new();
+
+ c.add(Cookie::new("test", ""));
+ c.add(Cookie::new("test2", ""));
+ c.remove(Cookie::named("test"));
+
+ assert!(c.get("test").is_none());
+ assert!(c.get("test2").is_some());
+
+ c.add(Cookie::new("test3", ""));
+ c.remove(Cookie::named("test2"));
+ c.remove(Cookie::named("test3"));
+
+ assert!(c.get("test").is_none());
+ assert!(c.get("test2").is_none());
+ assert!(c.get("test3").is_none());
+ }
+
+ #[test]
+ fn jar_is_send() {
+ fn is_send<T: Send>(_: T) -> bool {
+ true
+ }
+
+ assert!(is_send(CookieJar::new()))
+ }
+
+ #[test]
+ #[cfg(all(feature = "signed", feature = "private"))]
+ fn iter() {
+ let key = crate::Key::generate();
+ let mut c = CookieJar::new();
+
+ c.add_original(Cookie::new("original", "original"));
+
+ c.add(Cookie::new("test", "test"));
+ c.add(Cookie::new("test2", "test2"));
+ c.add(Cookie::new("test3", "test3"));
+ assert_eq!(c.iter().count(), 4);
+
+ c.signed_mut(&key).add(Cookie::new("signed", "signed"));
+ c.private_mut(&key).add(Cookie::new("encrypted", "encrypted"));
+ assert_eq!(c.iter().count(), 6);
+
+ c.remove(Cookie::named("test"));
+ assert_eq!(c.iter().count(), 5);
+
+ c.remove(Cookie::named("signed"));
+ c.remove(Cookie::named("test2"));
+ assert_eq!(c.iter().count(), 3);
+
+ c.add(Cookie::new("test2", "test2"));
+ assert_eq!(c.iter().count(), 4);
+
+ c.remove(Cookie::named("test2"));
+ assert_eq!(c.iter().count(), 3);
+ }
+
+ #[test]
+ fn delta() {
+ use std::collections::HashMap;
+ use time::Duration;
+
+ let mut c = CookieJar::new();
+
+ c.add_original(Cookie::new("original", "original"));
+ c.add_original(Cookie::new("original1", "original1"));
+
+ c.add(Cookie::new("test", "test"));
+ c.add(Cookie::new("test2", "test2"));
+ c.add(Cookie::new("test3", "test3"));
+ c.add(Cookie::new("test4", "test4"));
+
+ c.remove(Cookie::named("test"));
+ c.remove(Cookie::named("original"));
+
+ assert_eq!(c.delta().count(), 4);
+
+ let names: HashMap<_, _> = c.delta()
+ .map(|c| (c.name(), c.max_age()))
+ .collect();
+
+ assert!(names.get("test2").unwrap().is_none());
+ assert!(names.get("test3").unwrap().is_none());
+ assert!(names.get("test4").unwrap().is_none());
+ assert_eq!(names.get("original").unwrap(), &Some(Duration::seconds(0)));
+ }
+
+ #[test]
+ fn replace_original() {
+ let mut jar = CookieJar::new();
+ jar.add_original(Cookie::new("original_a", "a"));
+ jar.add_original(Cookie::new("original_b", "b"));
+ assert_eq!(jar.get("original_a").unwrap().value(), "a");
+
+ jar.add(Cookie::new("original_a", "av2"));
+ assert_eq!(jar.get("original_a").unwrap().value(), "av2");
+ }
+
+ #[test]
+ fn empty_delta() {
+ let mut jar = CookieJar::new();
+ jar.add(Cookie::new("name", "val"));
+ assert_eq!(jar.delta().count(), 1);
+
+ jar.remove(Cookie::named("name"));
+ assert_eq!(jar.delta().count(), 0);
+
+ jar.add_original(Cookie::new("name", "val"));
+ assert_eq!(jar.delta().count(), 0);
+
+ jar.remove(Cookie::named("name"));
+ assert_eq!(jar.delta().count(), 1);
+
+ jar.add(Cookie::new("name", "val"));
+ assert_eq!(jar.delta().count(), 1);
+
+ jar.remove(Cookie::named("name"));
+ assert_eq!(jar.delta().count(), 1);
+ }
+
+ #[test]
+ fn add_remove_add() {
+ let mut jar = CookieJar::new();
+ jar.add_original(Cookie::new("name", "val"));
+ assert_eq!(jar.delta().count(), 0);
+
+ jar.remove(Cookie::named("name"));
+ assert_eq!(jar.delta().filter(|c| c.value().is_empty()).count(), 1);
+ assert_eq!(jar.delta().count(), 1);
+
+ // The cookie's been deleted. Another original doesn't change that.
+ jar.add_original(Cookie::new("name", "val"));
+ assert_eq!(jar.delta().filter(|c| c.value().is_empty()).count(), 1);
+ assert_eq!(jar.delta().count(), 1);
+
+ jar.remove(Cookie::named("name"));
+ assert_eq!(jar.delta().filter(|c| c.value().is_empty()).count(), 1);
+ assert_eq!(jar.delta().count(), 1);
+
+ jar.add(Cookie::new("name", "val"));
+ assert_eq!(jar.delta().filter(|c| !c.value().is_empty()).count(), 1);
+ assert_eq!(jar.delta().count(), 1);
+
+ jar.remove(Cookie::named("name"));
+ assert_eq!(jar.delta().filter(|c| c.value().is_empty()).count(), 1);
+ assert_eq!(jar.delta().count(), 1);
+ }
+
+ #[test]
+ fn replace_remove() {
+ let mut jar = CookieJar::new();
+ jar.add_original(Cookie::new("name", "val"));
+ assert_eq!(jar.delta().count(), 0);
+
+ jar.add(Cookie::new("name", "val"));
+ assert_eq!(jar.delta().count(), 1);
+ assert_eq!(jar.delta().filter(|c| !c.value().is_empty()).count(), 1);
+
+ jar.remove(Cookie::named("name"));
+ assert_eq!(jar.delta().filter(|c| c.value().is_empty()).count(), 1);
+ }
+
+ #[test]
+ fn remove_with_path() {
+ let mut jar = CookieJar::new();
+ jar.add_original(Cookie::build("name", "val").finish());
+ assert_eq!(jar.iter().count(), 1);
+ assert_eq!(jar.delta().count(), 0);
+ assert_eq!(jar.iter().filter(|c| c.path().is_none()).count(), 1);
+
+ jar.remove(Cookie::build("name", "").path("/").finish());
+ assert_eq!(jar.iter().count(), 0);
+ assert_eq!(jar.delta().count(), 1);
+ assert_eq!(jar.delta().filter(|c| c.value().is_empty()).count(), 1);
+ assert_eq!(jar.delta().filter(|c| c.path() == Some("/")).count(), 1);
+ }
+}
diff --git a/third_party/rust/cookie/src/lib.rs b/third_party/rust/cookie/src/lib.rs
new file mode 100644
index 0000000000..392728efbc
--- /dev/null
+++ b/third_party/rust/cookie/src/lib.rs
@@ -0,0 +1,1422 @@
+//! HTTP cookie parsing and cookie jar management.
+//!
+//! This crates provides the [`Cookie`] type, representing an HTTP cookie, and
+//! the [`CookieJar`] type, which manages a collection of cookies for session
+//! management, recording changes as they are made, and optional automatic
+//! cookie encryption and signing.
+//!
+//! # Usage
+//!
+//! Add the following to the `[dependencies]` section of your `Cargo.toml`:
+//!
+//! ```toml
+//! cookie = "0.16"
+//! ```
+//!
+//! # Features
+//!
+//! This crate exposes several features, all of which are disabled by default:
+//!
+//! * **`percent-encode`**
+//!
+//! Enables _percent encoding and decoding_ of names and values in cookies.
+//!
+//! When this feature is enabled, the [`Cookie::encoded()`] and
+//! [`Cookie::parse_encoded()`] methods are available. The `encoded` method
+//! returns a wrapper around a `Cookie` whose `Display` implementation
+//! percent-encodes the name and value of the cookie. The `parse_encoded`
+//! method percent-decodes the name and value of a `Cookie` during parsing.
+//!
+//! * **`signed`**
+//!
+//! Enables _signed_ cookies via [`CookieJar::signed()`].
+//!
+//! When this feature is enabled, the [`CookieJar::signed()`] method,
+//! [`SignedJar`] type, and [`Key`] type are available. The jar acts as "child
+//! jar"; operations on the jar automatically sign and verify cookies as they
+//! are added and retrieved from the parent jar.
+//!
+//! * **`private`**
+//!
+//! Enables _private_ (authenticated, encrypted) cookies via
+//! [`CookieJar::private()`].
+//!
+//! When this feature is enabled, the [`CookieJar::private()`] method,
+//! [`PrivateJar`] type, and [`Key`] type are available. The jar acts as "child
+//! jar"; operations on the jar automatically encrypt and decrypt/authenticate
+//! cookies as they are added and retrieved from the parent jar.
+//!
+//! * **`key-expansion`**
+//!
+//! Enables _key expansion_ or _key derivation_ via [`Key::derive_from()`].
+//!
+//! When this feature is enabled, and either `signed` or `private` are _also_
+//! enabled, the [`Key::derive_from()`] method is available. The method can be
+//! used to derive a `Key` structure appropriate for use with signed and
+//! private jars from cryptographically valid key material that is shorter in
+//! length than the full key.
+//!
+//! * **`secure`**
+//!
+//! A meta-feature that simultaneously enables `signed`, `private`, and
+//! `key-expansion`.
+//!
+//! You can enable features via `Cargo.toml`:
+//!
+//! ```toml
+//! [dependencies.cookie]
+//! features = ["secure", "percent-encode"]
+//! ```
+
+#![cfg_attr(all(nightly, doc), feature(doc_cfg))]
+
+#![doc(html_root_url = "https://docs.rs/cookie/0.16")]
+#![deny(missing_docs)]
+
+pub use time;
+
+mod builder;
+mod parse;
+mod jar;
+mod delta;
+mod draft;
+mod expiration;
+
+#[cfg(any(feature = "private", feature = "signed"))] #[macro_use] mod secure;
+#[cfg(any(feature = "private", feature = "signed"))] pub use secure::*;
+
+use std::borrow::Cow;
+use std::fmt;
+use std::str::FromStr;
+
+#[allow(unused_imports, deprecated)]
+use std::ascii::AsciiExt;
+
+use time::{Duration, OffsetDateTime, UtcOffset, macros::datetime};
+
+use crate::parse::parse_cookie;
+pub use crate::parse::ParseError;
+pub use crate::builder::CookieBuilder;
+pub use crate::jar::{CookieJar, Delta, Iter};
+pub use crate::draft::*;
+pub use crate::expiration::*;
+
+#[derive(Debug, Clone)]
+enum CookieStr<'c> {
+ /// An string derived from indexes (start, end).
+ Indexed(usize, usize),
+ /// A string derived from a concrete string.
+ Concrete(Cow<'c, str>),
+}
+
+impl<'c> CookieStr<'c> {
+ /// Retrieves the string `self` corresponds to. If `self` is derived from
+ /// indexes, the corresponding subslice of `string` is returned. Otherwise,
+ /// the concrete string is returned.
+ ///
+ /// # Panics
+ ///
+ /// Panics if `self` is an indexed string and `string` is None.
+ fn to_str<'s>(&'s self, string: Option<&'s Cow<str>>) -> &'s str {
+ match *self {
+ CookieStr::Indexed(i, j) => {
+ let s = string.expect("`Some` base string must exist when \
+ converting indexed str to str! (This is a module invariant.)");
+ &s[i..j]
+ },
+ CookieStr::Concrete(ref cstr) => &*cstr,
+ }
+ }
+
+ #[allow(clippy::ptr_arg)]
+ fn to_raw_str<'s, 'b: 's>(&'s self, string: &'s Cow<'b, str>) -> Option<&'b str> {
+ match *self {
+ CookieStr::Indexed(i, j) => {
+ match *string {
+ Cow::Borrowed(s) => Some(&s[i..j]),
+ Cow::Owned(_) => None,
+ }
+ },
+ CookieStr::Concrete(_) => None,
+ }
+ }
+
+ fn into_owned(self) -> CookieStr<'static> {
+ use crate::CookieStr::*;
+
+ match self {
+ Indexed(a, b) => Indexed(a, b),
+ Concrete(Cow::Owned(c)) => Concrete(Cow::Owned(c)),
+ Concrete(Cow::Borrowed(c)) => Concrete(Cow::Owned(c.into())),
+ }
+ }
+}
+
+/// Representation of an HTTP cookie.
+///
+/// # Constructing a `Cookie`
+///
+/// To construct a cookie with only a name/value, use [`Cookie::new()`]:
+///
+/// ```rust
+/// use cookie::Cookie;
+///
+/// let cookie = Cookie::new("name", "value");
+/// assert_eq!(&cookie.to_string(), "name=value");
+/// ```
+///
+/// To construct more elaborate cookies, use [`Cookie::build()`] and
+/// [`CookieBuilder`] methods:
+///
+/// ```rust
+/// use cookie::Cookie;
+///
+/// let cookie = Cookie::build("name", "value")
+/// .domain("www.rust-lang.org")
+/// .path("/")
+/// .secure(true)
+/// .http_only(true)
+/// .finish();
+/// ```
+#[derive(Debug, Clone)]
+pub struct Cookie<'c> {
+ /// Storage for the cookie string. Only used if this structure was derived
+ /// from a string that was subsequently parsed.
+ cookie_string: Option<Cow<'c, str>>,
+ /// The cookie's name.
+ name: CookieStr<'c>,
+ /// The cookie's value.
+ value: CookieStr<'c>,
+ /// The cookie's expiration, if any.
+ expires: Option<Expiration>,
+ /// The cookie's maximum age, if any.
+ max_age: Option<Duration>,
+ /// The cookie's domain, if any.
+ domain: Option<CookieStr<'c>>,
+ /// The cookie's path domain, if any.
+ path: Option<CookieStr<'c>>,
+ /// Whether this cookie was marked Secure.
+ secure: Option<bool>,
+ /// Whether this cookie was marked HttpOnly.
+ http_only: Option<bool>,
+ /// The draft `SameSite` attribute.
+ same_site: Option<SameSite>,
+}
+
+impl<'c> Cookie<'c> {
+ /// Creates a new `Cookie` with the given name and value.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::Cookie;
+ ///
+ /// let cookie = Cookie::new("name", "value");
+ /// assert_eq!(cookie.name_value(), ("name", "value"));
+ /// ```
+ pub fn new<N, V>(name: N, value: V) -> Self
+ where N: Into<Cow<'c, str>>,
+ V: Into<Cow<'c, str>>
+ {
+ Cookie {
+ cookie_string: None,
+ name: CookieStr::Concrete(name.into()),
+ value: CookieStr::Concrete(value.into()),
+ expires: None,
+ max_age: None,
+ domain: None,
+ path: None,
+ secure: None,
+ http_only: None,
+ same_site: None,
+ }
+ }
+
+ /// Creates a new `Cookie` with the given name and an empty value.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::Cookie;
+ ///
+ /// let cookie = Cookie::named("name");
+ /// assert_eq!(cookie.name(), "name");
+ /// assert!(cookie.value().is_empty());
+ /// ```
+ pub fn named<N>(name: N) -> Cookie<'c>
+ where N: Into<Cow<'c, str>>
+ {
+ Cookie::new(name, "")
+ }
+
+ /// Creates a new `CookieBuilder` instance from the given key and value
+ /// strings.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use cookie::Cookie;
+ ///
+ /// let c = Cookie::build("foo", "bar").finish();
+ /// assert_eq!(c.name_value(), ("foo", "bar"));
+ /// ```
+ pub fn build<N, V>(name: N, value: V) -> CookieBuilder<'c>
+ where N: Into<Cow<'c, str>>,
+ V: Into<Cow<'c, str>>
+ {
+ CookieBuilder::new(name, value)
+ }
+
+ /// Parses a `Cookie` from the given HTTP cookie header value string. Does
+ /// not perform any percent-decoding.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use cookie::Cookie;
+ ///
+ /// let c = Cookie::parse("foo=bar%20baz; HttpOnly").unwrap();
+ /// assert_eq!(c.name_value(), ("foo", "bar%20baz"));
+ /// assert_eq!(c.http_only(), Some(true));
+ /// ```
+ pub fn parse<S>(s: S) -> Result<Cookie<'c>, ParseError>
+ where S: Into<Cow<'c, str>>
+ {
+ parse_cookie(s, false)
+ }
+
+ /// Parses a `Cookie` from the given HTTP cookie header value string where
+ /// the name and value fields are percent-encoded. Percent-decodes the
+ /// name/value fields.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use cookie::Cookie;
+ ///
+ /// let c = Cookie::parse_encoded("foo=bar%20baz; HttpOnly").unwrap();
+ /// assert_eq!(c.name_value(), ("foo", "bar baz"));
+ /// assert_eq!(c.http_only(), Some(true));
+ /// ```
+ #[cfg(feature = "percent-encode")]
+ #[cfg_attr(all(nightly, doc), doc(cfg(feature = "percent-encode")))]
+ pub fn parse_encoded<S>(s: S) -> Result<Cookie<'c>, ParseError>
+ where S: Into<Cow<'c, str>>
+ {
+ parse_cookie(s, true)
+ }
+
+ /// Converts `self` into a `Cookie` with a static lifetime with as few
+ /// allocations as possible.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use cookie::Cookie;
+ ///
+ /// let c = Cookie::new("a", "b");
+ /// let owned_cookie = c.into_owned();
+ /// assert_eq!(owned_cookie.name_value(), ("a", "b"));
+ /// ```
+ pub fn into_owned(self) -> Cookie<'static> {
+ Cookie {
+ cookie_string: self.cookie_string.map(|s| s.into_owned().into()),
+ name: self.name.into_owned(),
+ value: self.value.into_owned(),
+ expires: self.expires,
+ max_age: self.max_age,
+ domain: self.domain.map(|s| s.into_owned()),
+ path: self.path.map(|s| s.into_owned()),
+ secure: self.secure,
+ http_only: self.http_only,
+ same_site: self.same_site,
+ }
+ }
+
+ /// Returns the name of `self`.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use cookie::Cookie;
+ ///
+ /// let c = Cookie::new("name", "value");
+ /// assert_eq!(c.name(), "name");
+ /// ```
+ #[inline]
+ pub fn name(&self) -> &str {
+ self.name.to_str(self.cookie_string.as_ref())
+ }
+
+ /// Returns the value of `self`.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use cookie::Cookie;
+ ///
+ /// let c = Cookie::new("name", "value");
+ /// assert_eq!(c.value(), "value");
+ /// ```
+ #[inline]
+ pub fn value(&self) -> &str {
+ self.value.to_str(self.cookie_string.as_ref())
+ }
+
+ /// Returns the name and value of `self` as a tuple of `(name, value)`.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use cookie::Cookie;
+ ///
+ /// let c = Cookie::new("name", "value");
+ /// assert_eq!(c.name_value(), ("name", "value"));
+ /// ```
+ #[inline]
+ pub fn name_value(&self) -> (&str, &str) {
+ (self.name(), self.value())
+ }
+
+ /// Returns whether this cookie was marked `HttpOnly` or not. Returns
+ /// `Some(true)` when the cookie was explicitly set (manually or parsed) as
+ /// `HttpOnly`, `Some(false)` when `http_only` was manually set to `false`,
+ /// and `None` otherwise.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use cookie::Cookie;
+ ///
+ /// let c = Cookie::parse("name=value; httponly").unwrap();
+ /// assert_eq!(c.http_only(), Some(true));
+ ///
+ /// let mut c = Cookie::new("name", "value");
+ /// assert_eq!(c.http_only(), None);
+ ///
+ /// let mut c = Cookie::new("name", "value");
+ /// assert_eq!(c.http_only(), None);
+ ///
+ /// // An explicitly set "false" value.
+ /// c.set_http_only(false);
+ /// assert_eq!(c.http_only(), Some(false));
+ ///
+ /// // An explicitly set "true" value.
+ /// c.set_http_only(true);
+ /// assert_eq!(c.http_only(), Some(true));
+ /// ```
+ #[inline]
+ pub fn http_only(&self) -> Option<bool> {
+ self.http_only
+ }
+
+ /// Returns whether this cookie was marked `Secure` or not. Returns
+ /// `Some(true)` when the cookie was explicitly set (manually or parsed) as
+ /// `Secure`, `Some(false)` when `secure` was manually set to `false`, and
+ /// `None` otherwise.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use cookie::Cookie;
+ ///
+ /// let c = Cookie::parse("name=value; Secure").unwrap();
+ /// assert_eq!(c.secure(), Some(true));
+ ///
+ /// let mut c = Cookie::parse("name=value").unwrap();
+ /// assert_eq!(c.secure(), None);
+ ///
+ /// let mut c = Cookie::new("name", "value");
+ /// assert_eq!(c.secure(), None);
+ ///
+ /// // An explicitly set "false" value.
+ /// c.set_secure(false);
+ /// assert_eq!(c.secure(), Some(false));
+ ///
+ /// // An explicitly set "true" value.
+ /// c.set_secure(true);
+ /// assert_eq!(c.secure(), Some(true));
+ /// ```
+ #[inline]
+ pub fn secure(&self) -> Option<bool> {
+ self.secure
+ }
+
+ /// Returns the `SameSite` attribute of this cookie if one was specified.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use cookie::{Cookie, SameSite};
+ ///
+ /// let c = Cookie::parse("name=value; SameSite=Lax").unwrap();
+ /// assert_eq!(c.same_site(), Some(SameSite::Lax));
+ /// ```
+ #[inline]
+ pub fn same_site(&self) -> Option<SameSite> {
+ self.same_site
+ }
+
+ /// Returns the specified max-age of the cookie if one was specified.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use cookie::Cookie;
+ ///
+ /// let c = Cookie::parse("name=value").unwrap();
+ /// assert_eq!(c.max_age(), None);
+ ///
+ /// let c = Cookie::parse("name=value; Max-Age=3600").unwrap();
+ /// assert_eq!(c.max_age().map(|age| age.whole_hours()), Some(1));
+ /// ```
+ #[inline]
+ pub fn max_age(&self) -> Option<Duration> {
+ self.max_age
+ }
+
+ /// Returns the `Path` of the cookie if one was specified.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use cookie::Cookie;
+ ///
+ /// let c = Cookie::parse("name=value").unwrap();
+ /// assert_eq!(c.path(), None);
+ ///
+ /// let c = Cookie::parse("name=value; Path=/").unwrap();
+ /// assert_eq!(c.path(), Some("/"));
+ ///
+ /// let c = Cookie::parse("name=value; path=/sub").unwrap();
+ /// assert_eq!(c.path(), Some("/sub"));
+ /// ```
+ #[inline]
+ pub fn path(&self) -> Option<&str> {
+ match self.path {
+ Some(ref c) => Some(c.to_str(self.cookie_string.as_ref())),
+ None => None,
+ }
+ }
+
+ /// Returns the `Domain` of the cookie if one was specified.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use cookie::Cookie;
+ ///
+ /// let c = Cookie::parse("name=value").unwrap();
+ /// assert_eq!(c.domain(), None);
+ ///
+ /// let c = Cookie::parse("name=value; Domain=crates.io").unwrap();
+ /// assert_eq!(c.domain(), Some("crates.io"));
+ /// ```
+ #[inline]
+ pub fn domain(&self) -> Option<&str> {
+ match self.domain {
+ Some(ref c) => Some(c.to_str(self.cookie_string.as_ref())),
+ None => None,
+ }
+ }
+
+ /// Returns the [`Expiration`] of the cookie if one was specified.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use cookie::{Cookie, Expiration};
+ ///
+ /// let c = Cookie::parse("name=value").unwrap();
+ /// assert_eq!(c.expires(), None);
+ ///
+ /// // Here, `cookie.expires_datetime()` returns `None`.
+ /// let c = Cookie::build("name", "value").expires(None).finish();
+ /// assert_eq!(c.expires(), Some(Expiration::Session));
+ ///
+ /// let expire_time = "Wed, 21 Oct 2017 07:28:00 GMT";
+ /// let cookie_str = format!("name=value; Expires={}", expire_time);
+ /// let c = Cookie::parse(cookie_str).unwrap();
+ /// assert_eq!(c.expires().and_then(|e| e.datetime()).map(|t| t.year()), Some(2017));
+ /// ```
+ #[inline]
+ pub fn expires(&self) -> Option<Expiration> {
+ self.expires
+ }
+
+ /// Returns the expiration date-time of the cookie if one was specified.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use cookie::Cookie;
+ ///
+ /// let c = Cookie::parse("name=value").unwrap();
+ /// assert_eq!(c.expires_datetime(), None);
+ ///
+ /// // Here, `cookie.expires()` returns `Some`.
+ /// let c = Cookie::build("name", "value").expires(None).finish();
+ /// assert_eq!(c.expires_datetime(), None);
+ ///
+ /// let expire_time = "Wed, 21 Oct 2017 07:28:00 GMT";
+ /// let cookie_str = format!("name=value; Expires={}", expire_time);
+ /// let c = Cookie::parse(cookie_str).unwrap();
+ /// assert_eq!(c.expires_datetime().map(|t| t.year()), Some(2017));
+ /// ```
+ #[inline]
+ pub fn expires_datetime(&self) -> Option<OffsetDateTime> {
+ self.expires.and_then(|e| e.datetime())
+ }
+
+ /// Sets the name of `self` to `name`.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use cookie::Cookie;
+ ///
+ /// let mut c = Cookie::new("name", "value");
+ /// assert_eq!(c.name(), "name");
+ ///
+ /// c.set_name("foo");
+ /// assert_eq!(c.name(), "foo");
+ /// ```
+ pub fn set_name<N: Into<Cow<'c, str>>>(&mut self, name: N) {
+ self.name = CookieStr::Concrete(name.into())
+ }
+
+ /// Sets the value of `self` to `value`.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use cookie::Cookie;
+ ///
+ /// let mut c = Cookie::new("name", "value");
+ /// assert_eq!(c.value(), "value");
+ ///
+ /// c.set_value("bar");
+ /// assert_eq!(c.value(), "bar");
+ /// ```
+ pub fn set_value<V: Into<Cow<'c, str>>>(&mut self, value: V) {
+ self.value = CookieStr::Concrete(value.into())
+ }
+
+ /// Sets the value of `http_only` in `self` to `value`. If `value` is
+ /// `None`, the field is unset.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use cookie::Cookie;
+ ///
+ /// let mut c = Cookie::new("name", "value");
+ /// assert_eq!(c.http_only(), None);
+ ///
+ /// c.set_http_only(true);
+ /// assert_eq!(c.http_only(), Some(true));
+ ///
+ /// c.set_http_only(false);
+ /// assert_eq!(c.http_only(), Some(false));
+ ///
+ /// c.set_http_only(None);
+ /// assert_eq!(c.http_only(), None);
+ /// ```
+ #[inline]
+ pub fn set_http_only<T: Into<Option<bool>>>(&mut self, value: T) {
+ self.http_only = value.into();
+ }
+
+ /// Sets the value of `secure` in `self` to `value`. If `value` is `None`,
+ /// the field is unset.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use cookie::Cookie;
+ ///
+ /// let mut c = Cookie::new("name", "value");
+ /// assert_eq!(c.secure(), None);
+ ///
+ /// c.set_secure(true);
+ /// assert_eq!(c.secure(), Some(true));
+ ///
+ /// c.set_secure(false);
+ /// assert_eq!(c.secure(), Some(false));
+ ///
+ /// c.set_secure(None);
+ /// assert_eq!(c.secure(), None);
+ /// ```
+ #[inline]
+ pub fn set_secure<T: Into<Option<bool>>>(&mut self, value: T) {
+ self.secure = value.into();
+ }
+
+ /// Sets the value of `same_site` in `self` to `value`. If `value` is
+ /// `None`, the field is unset. If `value` is `SameSite::None`, the "Secure"
+ /// flag will be set when the cookie is written out unless `secure` is
+ /// explicitly set to `false` via [`Cookie::set_secure()`] or the equivalent
+ /// builder method.
+ ///
+ /// [HTTP draft]: https://tools.ietf.org/html/draft-west-cookie-incrementalism-00
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use cookie::{Cookie, SameSite};
+ ///
+ /// let mut c = Cookie::new("name", "value");
+ /// assert_eq!(c.same_site(), None);
+ ///
+ /// c.set_same_site(SameSite::None);
+ /// assert_eq!(c.same_site(), Some(SameSite::None));
+ /// assert_eq!(c.to_string(), "name=value; SameSite=None; Secure");
+ ///
+ /// c.set_secure(false);
+ /// assert_eq!(c.to_string(), "name=value; SameSite=None");
+ ///
+ /// let mut c = Cookie::new("name", "value");
+ /// assert_eq!(c.same_site(), None);
+ ///
+ /// c.set_same_site(SameSite::Strict);
+ /// assert_eq!(c.same_site(), Some(SameSite::Strict));
+ /// assert_eq!(c.to_string(), "name=value; SameSite=Strict");
+ ///
+ /// c.set_same_site(None);
+ /// assert_eq!(c.same_site(), None);
+ /// assert_eq!(c.to_string(), "name=value");
+ /// ```
+ #[inline]
+ pub fn set_same_site<T: Into<Option<SameSite>>>(&mut self, value: T) {
+ self.same_site = value.into();
+ }
+
+ /// Sets the value of `max_age` in `self` to `value`. If `value` is `None`,
+ /// the field is unset.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// # extern crate cookie;
+ /// use cookie::Cookie;
+ /// use cookie::time::Duration;
+ ///
+ /// # fn main() {
+ /// let mut c = Cookie::new("name", "value");
+ /// assert_eq!(c.max_age(), None);
+ ///
+ /// c.set_max_age(Duration::hours(10));
+ /// assert_eq!(c.max_age(), Some(Duration::hours(10)));
+ ///
+ /// c.set_max_age(None);
+ /// assert!(c.max_age().is_none());
+ /// # }
+ /// ```
+ #[inline]
+ pub fn set_max_age<D: Into<Option<Duration>>>(&mut self, value: D) {
+ self.max_age = value.into();
+ }
+
+ /// Sets the `path` of `self` to `path`.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::Cookie;
+ ///
+ /// let mut c = Cookie::new("name", "value");
+ /// assert_eq!(c.path(), None);
+ ///
+ /// c.set_path("/");
+ /// assert_eq!(c.path(), Some("/"));
+ /// ```
+ pub fn set_path<P: Into<Cow<'c, str>>>(&mut self, path: P) {
+ self.path = Some(CookieStr::Concrete(path.into()));
+ }
+
+ /// Unsets the `path` of `self`.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use cookie::Cookie;
+ ///
+ /// let mut c = Cookie::new("name", "value");
+ /// assert_eq!(c.path(), None);
+ ///
+ /// c.set_path("/");
+ /// assert_eq!(c.path(), Some("/"));
+ ///
+ /// c.unset_path();
+ /// assert_eq!(c.path(), None);
+ /// ```
+ pub fn unset_path(&mut self) {
+ self.path = None;
+ }
+
+ /// Sets the `domain` of `self` to `domain`.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use cookie::Cookie;
+ ///
+ /// let mut c = Cookie::new("name", "value");
+ /// assert_eq!(c.domain(), None);
+ ///
+ /// c.set_domain("rust-lang.org");
+ /// assert_eq!(c.domain(), Some("rust-lang.org"));
+ /// ```
+ pub fn set_domain<D: Into<Cow<'c, str>>>(&mut self, domain: D) {
+ self.domain = Some(CookieStr::Concrete(domain.into()));
+ }
+
+ /// Unsets the `domain` of `self`.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use cookie::Cookie;
+ ///
+ /// let mut c = Cookie::new("name", "value");
+ /// assert_eq!(c.domain(), None);
+ ///
+ /// c.set_domain("rust-lang.org");
+ /// assert_eq!(c.domain(), Some("rust-lang.org"));
+ ///
+ /// c.unset_domain();
+ /// assert_eq!(c.domain(), None);
+ /// ```
+ pub fn unset_domain(&mut self) {
+ self.domain = None;
+ }
+
+ /// Sets the expires field of `self` to `time`. If `time` is `None`, an
+ /// expiration of [`Session`](Expiration::Session) is set.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// # extern crate cookie;
+ /// use cookie::{Cookie, Expiration};
+ /// use cookie::time::{Duration, OffsetDateTime};
+ ///
+ /// let mut c = Cookie::new("name", "value");
+ /// assert_eq!(c.expires(), None);
+ ///
+ /// let mut now = OffsetDateTime::now_utc();
+ /// now += Duration::weeks(52);
+ ///
+ /// c.set_expires(now);
+ /// assert!(c.expires().is_some());
+ ///
+ /// c.set_expires(None);
+ /// assert_eq!(c.expires(), Some(Expiration::Session));
+ /// ```
+ pub fn set_expires<T: Into<Expiration>>(&mut self, time: T) {
+ static MAX_DATETIME: OffsetDateTime = datetime!(9999-12-31 23:59:59.999_999 UTC);
+
+ // RFC 6265 requires dates not to exceed 9999 years.
+ self.expires = Some(time.into()
+ .map(|time| std::cmp::min(time, MAX_DATETIME)));
+ }
+
+ /// Unsets the `expires` of `self`.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use cookie::{Cookie, Expiration};
+ ///
+ /// let mut c = Cookie::new("name", "value");
+ /// assert_eq!(c.expires(), None);
+ ///
+ /// c.set_expires(None);
+ /// assert_eq!(c.expires(), Some(Expiration::Session));
+ ///
+ /// c.unset_expires();
+ /// assert_eq!(c.expires(), None);
+ /// ```
+ pub fn unset_expires(&mut self) {
+ self.expires = None;
+ }
+
+ /// Makes `self` a "permanent" cookie by extending its expiration and max
+ /// age 20 years into the future.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// # extern crate cookie;
+ /// use cookie::Cookie;
+ /// use cookie::time::Duration;
+ ///
+ /// # fn main() {
+ /// let mut c = Cookie::new("foo", "bar");
+ /// assert!(c.expires().is_none());
+ /// assert!(c.max_age().is_none());
+ ///
+ /// c.make_permanent();
+ /// assert!(c.expires().is_some());
+ /// assert_eq!(c.max_age(), Some(Duration::days(365 * 20)));
+ /// # }
+ /// ```
+ pub fn make_permanent(&mut self) {
+ let twenty_years = Duration::days(365 * 20);
+ self.set_max_age(twenty_years);
+ self.set_expires(OffsetDateTime::now_utc() + twenty_years);
+ }
+
+ /// Make `self` a "removal" cookie by clearing its value, setting a max-age
+ /// of `0`, and setting an expiration date far in the past.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// # extern crate cookie;
+ /// use cookie::Cookie;
+ /// use cookie::time::Duration;
+ ///
+ /// # fn main() {
+ /// let mut c = Cookie::new("foo", "bar");
+ /// c.make_permanent();
+ /// assert_eq!(c.max_age(), Some(Duration::days(365 * 20)));
+ /// assert_eq!(c.value(), "bar");
+ ///
+ /// c.make_removal();
+ /// assert_eq!(c.value(), "");
+ /// assert_eq!(c.max_age(), Some(Duration::ZERO));
+ /// # }
+ /// ```
+ pub fn make_removal(&mut self) {
+ self.set_value("");
+ self.set_max_age(Duration::seconds(0));
+ self.set_expires(OffsetDateTime::now_utc() - Duration::days(365));
+ }
+
+ fn fmt_parameters(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ if let Some(true) = self.http_only() {
+ write!(f, "; HttpOnly")?;
+ }
+
+ if let Some(same_site) = self.same_site() {
+ write!(f, "; SameSite={}", same_site)?;
+
+ if same_site.is_none() && self.secure().is_none() {
+ write!(f, "; Secure")?;
+ }
+ }
+
+ if let Some(true) = self.secure() {
+ write!(f, "; Secure")?;
+ }
+
+ if let Some(path) = self.path() {
+ write!(f, "; Path={}", path)?;
+ }
+
+ if let Some(domain) = self.domain() {
+ write!(f, "; Domain={}", domain)?;
+ }
+
+ if let Some(max_age) = self.max_age() {
+ write!(f, "; Max-Age={}", max_age.whole_seconds())?;
+ }
+
+ if let Some(time) = self.expires_datetime() {
+ let time = time.to_offset(UtcOffset::UTC);
+ write!(f, "; Expires={}", time.format(&crate::parse::FMT1).map_err(|_| fmt::Error)?)?;
+ }
+
+ Ok(())
+ }
+
+ /// Returns the name of `self` as a string slice of the raw string `self`
+ /// was originally parsed from. If `self` was not originally parsed from a
+ /// raw string, returns `None`.
+ ///
+ /// This method differs from [`Cookie::name()`] in that it returns a string
+ /// with the same lifetime as the originally parsed string. This lifetime
+ /// may outlive `self`. If a longer lifetime is not required, or you're
+ /// unsure if you need a longer lifetime, use [`Cookie::name()`].
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use cookie::Cookie;
+ ///
+ /// let cookie_string = format!("{}={}", "foo", "bar");
+ ///
+ /// // `c` will be dropped at the end of the scope, but `name` will live on
+ /// let name = {
+ /// let c = Cookie::parse(cookie_string.as_str()).unwrap();
+ /// c.name_raw()
+ /// };
+ ///
+ /// assert_eq!(name, Some("foo"));
+ /// ```
+ #[inline]
+ pub fn name_raw(&self) -> Option<&'c str> {
+ self.cookie_string.as_ref()
+ .and_then(|s| self.name.to_raw_str(s))
+ }
+
+ /// Returns the value of `self` as a string slice of the raw string `self`
+ /// was originally parsed from. If `self` was not originally parsed from a
+ /// raw string, returns `None`.
+ ///
+ /// This method differs from [`Cookie::value()`] in that it returns a
+ /// string with the same lifetime as the originally parsed string. This
+ /// lifetime may outlive `self`. If a longer lifetime is not required, or
+ /// you're unsure if you need a longer lifetime, use [`Cookie::value()`].
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use cookie::Cookie;
+ ///
+ /// let cookie_string = format!("{}={}", "foo", "bar");
+ ///
+ /// // `c` will be dropped at the end of the scope, but `value` will live on
+ /// let value = {
+ /// let c = Cookie::parse(cookie_string.as_str()).unwrap();
+ /// c.value_raw()
+ /// };
+ ///
+ /// assert_eq!(value, Some("bar"));
+ /// ```
+ #[inline]
+ pub fn value_raw(&self) -> Option<&'c str> {
+ self.cookie_string.as_ref()
+ .and_then(|s| self.value.to_raw_str(s))
+ }
+
+ /// Returns the `Path` of `self` as a string slice of the raw string `self`
+ /// was originally parsed from. If `self` was not originally parsed from a
+ /// raw string, or if `self` doesn't contain a `Path`, or if the `Path` has
+ /// changed since parsing, returns `None`.
+ ///
+ /// This method differs from [`Cookie::path()`] in that it returns a
+ /// string with the same lifetime as the originally parsed string. This
+ /// lifetime may outlive `self`. If a longer lifetime is not required, or
+ /// you're unsure if you need a longer lifetime, use [`Cookie::path()`].
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use cookie::Cookie;
+ ///
+ /// let cookie_string = format!("{}={}; Path=/", "foo", "bar");
+ ///
+ /// // `c` will be dropped at the end of the scope, but `path` will live on
+ /// let path = {
+ /// let c = Cookie::parse(cookie_string.as_str()).unwrap();
+ /// c.path_raw()
+ /// };
+ ///
+ /// assert_eq!(path, Some("/"));
+ /// ```
+ #[inline]
+ pub fn path_raw(&self) -> Option<&'c str> {
+ match (self.path.as_ref(), self.cookie_string.as_ref()) {
+ (Some(path), Some(string)) => path.to_raw_str(string),
+ _ => None,
+ }
+ }
+
+ /// Returns the `Domain` of `self` as a string slice of the raw string
+ /// `self` was originally parsed from. If `self` was not originally parsed
+ /// from a raw string, or if `self` doesn't contain a `Domain`, or if the
+ /// `Domain` has changed since parsing, returns `None`.
+ ///
+ /// This method differs from [`Cookie::domain()`] in that it returns a
+ /// string with the same lifetime as the originally parsed string. This
+ /// lifetime may outlive `self` struct. If a longer lifetime is not
+ /// required, or you're unsure if you need a longer lifetime, use
+ /// [`Cookie::domain()`].
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use cookie::Cookie;
+ ///
+ /// let cookie_string = format!("{}={}; Domain=crates.io", "foo", "bar");
+ ///
+ /// //`c` will be dropped at the end of the scope, but `domain` will live on
+ /// let domain = {
+ /// let c = Cookie::parse(cookie_string.as_str()).unwrap();
+ /// c.domain_raw()
+ /// };
+ ///
+ /// assert_eq!(domain, Some("crates.io"));
+ /// ```
+ #[inline]
+ pub fn domain_raw(&self) -> Option<&'c str> {
+ match (self.domain.as_ref(), self.cookie_string.as_ref()) {
+ (Some(domain), Some(string)) => domain.to_raw_str(string),
+ _ => None,
+ }
+ }
+
+ /// Wraps `self` in an encoded [`Display`]: a cost-free wrapper around
+ /// `Cookie` whose [`fmt::Display`] implementation percent-encodes the name
+ /// and value of the wrapped `Cookie`.
+ ///
+ /// The returned structure can be chained with [`Display::stripped()`] to
+ /// display only the name and value.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::Cookie;
+ ///
+ /// let mut c = Cookie::build("my name", "this; value?").secure(true).finish();
+ /// assert_eq!(&c.encoded().to_string(), "my%20name=this%3B%20value%3F; Secure");
+ /// assert_eq!(&c.encoded().stripped().to_string(), "my%20name=this%3B%20value%3F");
+ /// ```
+ #[cfg(feature = "percent-encode")]
+ #[cfg_attr(all(nightly, doc), doc(cfg(feature = "percent-encode")))]
+ #[inline(always)]
+ pub fn encoded<'a>(&'a self) -> Display<'a, 'c> {
+ Display::new_encoded(self)
+ }
+
+ /// Wraps `self` in a stripped `Display`]: a cost-free wrapper around
+ /// `Cookie` whose [`fmt::Display`] implementation prints only the `name`
+ /// and `value` of the wrapped `Cookie`.
+ ///
+ /// The returned structure can be chained with [`Display::encoded()`] to
+ /// encode the name and value.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::Cookie;
+ ///
+ /// let mut c = Cookie::build("key?", "value").secure(true).path("/").finish();
+ /// assert_eq!(&c.stripped().to_string(), "key?=value");
+ #[cfg_attr(feature = "percent-encode", doc = r##"
+// Note: `encoded()` is only available when `percent-encode` is enabled.
+assert_eq!(&c.stripped().encoded().to_string(), "key%3F=value");
+ #"##)]
+ /// ```
+ #[inline(always)]
+ pub fn stripped<'a>(&'a self) -> Display<'a, 'c> {
+ Display::new_stripped(self)
+ }
+}
+
+#[cfg(feature = "percent-encode")]
+mod encoding {
+ use percent_encoding::{AsciiSet, CONTROLS};
+
+ /// https://url.spec.whatwg.org/#fragment-percent-encode-set
+ const FRAGMENT: &AsciiSet = &CONTROLS
+ .add(b' ')
+ .add(b'"')
+ .add(b'<')
+ .add(b'>')
+ .add(b'`');
+
+ /// https://url.spec.whatwg.org/#path-percent-encode-set
+ const PATH: &AsciiSet = &FRAGMENT
+ .add(b'#')
+ .add(b'?')
+ .add(b'{')
+ .add(b'}');
+
+ /// https://url.spec.whatwg.org/#userinfo-percent-encode-set
+ const USERINFO: &AsciiSet = &PATH
+ .add(b'/')
+ .add(b':')
+ .add(b';')
+ .add(b'=')
+ .add(b'@')
+ .add(b'[')
+ .add(b'\\')
+ .add(b']')
+ .add(b'^')
+ .add(b'|')
+ .add(b'%');
+
+ /// https://www.rfc-editor.org/rfc/rfc6265#section-4.1.1 + '(', ')'
+ const COOKIE: &AsciiSet = &USERINFO
+ .add(b'(')
+ .add(b')')
+ .add(b',');
+
+ /// Percent-encode a cookie name or value with the proper encoding set.
+ pub fn encode(string: &str) -> impl std::fmt::Display + '_ {
+ percent_encoding::percent_encode(string.as_bytes(), COOKIE)
+ }
+}
+
+/// Wrapper around `Cookie` whose `Display` implementation either
+/// percent-encodes the cookie's name and value, skips displaying the cookie's
+/// parameters (only displaying it's name and value), or both.
+///
+/// A value of this type can be obtained via [`Cookie::encoded()`] and
+/// [`Cookie::stripped()`], or an arbitrary chaining of the two methods. This
+/// type should only be used for its `Display` implementation.
+///
+/// # Example
+///
+/// ```rust
+/// use cookie::Cookie;
+///
+/// let c = Cookie::build("my name", "this; value%?").secure(true).finish();
+/// assert_eq!(&c.stripped().to_string(), "my name=this; value%?");
+#[cfg_attr(feature = "percent-encode", doc = r##"
+// Note: `encoded()` is only available when `percent-encode` is enabled.
+assert_eq!(&c.encoded().to_string(), "my%20name=this%3B%20value%25%3F; Secure");
+assert_eq!(&c.stripped().encoded().to_string(), "my%20name=this%3B%20value%25%3F");
+assert_eq!(&c.encoded().stripped().to_string(), "my%20name=this%3B%20value%25%3F");
+"##)]
+/// ```
+pub struct Display<'a, 'c: 'a> {
+ cookie: &'a Cookie<'c>,
+ #[cfg(feature = "percent-encode")]
+ encode: bool,
+ strip: bool,
+}
+
+impl<'a, 'c: 'a> fmt::Display for Display<'a, 'c> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ #[cfg(feature = "percent-encode")] {
+ if self.encode {
+ let name = encoding::encode(self.cookie.name());
+ let value = encoding::encode(self.cookie.value());
+ write!(f, "{}={}", name, value)?;
+ } else {
+ write!(f, "{}={}", self.cookie.name(), self.cookie.value())?;
+ }
+ }
+
+ #[cfg(not(feature = "percent-encode"))] {
+ write!(f, "{}={}", self.cookie.name(), self.cookie.value())?;
+ }
+
+ match self.strip {
+ true => Ok(()),
+ false => self.cookie.fmt_parameters(f)
+ }
+ }
+}
+
+impl<'a, 'c> Display<'a, 'c> {
+ #[cfg(feature = "percent-encode")]
+ fn new_encoded(cookie: &'a Cookie<'c>) -> Self {
+ Display { cookie, strip: false, encode: true }
+ }
+
+ fn new_stripped(cookie: &'a Cookie<'c>) -> Self {
+ Display { cookie, strip: true, #[cfg(feature = "percent-encode")] encode: false }
+ }
+
+ /// Percent-encode the name and value pair.
+ #[inline]
+ #[cfg(feature = "percent-encode")]
+ #[cfg_attr(all(nightly, doc), doc(cfg(feature = "percent-encode")))]
+ pub fn encoded(mut self) -> Self {
+ self.encode = true;
+ self
+ }
+
+ /// Only display the name and value.
+ #[inline]
+ pub fn stripped(mut self) -> Self {
+ self.strip = true;
+ self
+ }
+}
+
+impl<'c> fmt::Display for Cookie<'c> {
+ /// Formats the cookie `self` as a `Set-Cookie` header value.
+ ///
+ /// Does _not_ percent-encode any values. To percent-encode, use
+ /// [`Cookie::encoded()`].
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::Cookie;
+ ///
+ /// let mut cookie = Cookie::build("foo", "bar")
+ /// .path("/")
+ /// .finish();
+ ///
+ /// assert_eq!(&cookie.to_string(), "foo=bar; Path=/");
+ /// ```
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{}={}", self.name(), self.value())?;
+ self.fmt_parameters(f)
+ }
+}
+
+impl FromStr for Cookie<'static> {
+ type Err = ParseError;
+
+ fn from_str(s: &str) -> Result<Cookie<'static>, ParseError> {
+ Cookie::parse(s).map(|c| c.into_owned())
+ }
+}
+
+impl<'a, 'b> PartialEq<Cookie<'b>> for Cookie<'a> {
+ fn eq(&self, other: &Cookie<'b>) -> bool {
+ let so_far_so_good = self.name() == other.name()
+ && self.value() == other.value()
+ && self.http_only() == other.http_only()
+ && self.secure() == other.secure()
+ && self.max_age() == other.max_age()
+ && self.expires() == other.expires();
+
+ if !so_far_so_good {
+ return false;
+ }
+
+ match (self.path(), other.path()) {
+ (Some(a), Some(b)) if a.eq_ignore_ascii_case(b) => {}
+ (None, None) => {}
+ _ => return false,
+ };
+
+ match (self.domain(), other.domain()) {
+ (Some(a), Some(b)) if a.eq_ignore_ascii_case(b) => {}
+ (None, None) => {}
+ _ => return false,
+ };
+
+ true
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::{Cookie, SameSite, parse::parse_date};
+ use time::{Duration, OffsetDateTime};
+
+ #[test]
+ fn format() {
+ let cookie = Cookie::new("foo", "bar");
+ assert_eq!(&cookie.to_string(), "foo=bar");
+
+ let cookie = Cookie::build("foo", "bar")
+ .http_only(true).finish();
+ assert_eq!(&cookie.to_string(), "foo=bar; HttpOnly");
+
+ let cookie = Cookie::build("foo", "bar")
+ .max_age(Duration::seconds(10)).finish();
+ assert_eq!(&cookie.to_string(), "foo=bar; Max-Age=10");
+
+ let cookie = Cookie::build("foo", "bar")
+ .secure(true).finish();
+ assert_eq!(&cookie.to_string(), "foo=bar; Secure");
+
+ let cookie = Cookie::build("foo", "bar")
+ .path("/").finish();
+ assert_eq!(&cookie.to_string(), "foo=bar; Path=/");
+
+ let cookie = Cookie::build("foo", "bar")
+ .domain("www.rust-lang.org").finish();
+ assert_eq!(&cookie.to_string(), "foo=bar; Domain=www.rust-lang.org");
+
+ let time_str = "Wed, 21 Oct 2015 07:28:00 GMT";
+ let expires = parse_date(time_str, &crate::parse::FMT1).unwrap();
+ let cookie = Cookie::build("foo", "bar")
+ .expires(expires).finish();
+ assert_eq!(&cookie.to_string(),
+ "foo=bar; Expires=Wed, 21 Oct 2015 07:28:00 GMT");
+
+ let cookie = Cookie::build("foo", "bar")
+ .same_site(SameSite::Strict).finish();
+ assert_eq!(&cookie.to_string(), "foo=bar; SameSite=Strict");
+
+ let cookie = Cookie::build("foo", "bar")
+ .same_site(SameSite::Lax).finish();
+ assert_eq!(&cookie.to_string(), "foo=bar; SameSite=Lax");
+
+ let mut cookie = Cookie::build("foo", "bar")
+ .same_site(SameSite::None).finish();
+ assert_eq!(&cookie.to_string(), "foo=bar; SameSite=None; Secure");
+
+ cookie.set_same_site(None);
+ assert_eq!(&cookie.to_string(), "foo=bar");
+
+ let mut cookie = Cookie::build("foo", "bar")
+ .same_site(SameSite::None)
+ .secure(false)
+ .finish();
+ assert_eq!(&cookie.to_string(), "foo=bar; SameSite=None");
+ cookie.set_secure(true);
+ assert_eq!(&cookie.to_string(), "foo=bar; SameSite=None; Secure");
+ }
+
+ #[test]
+ #[ignore]
+ fn format_date_wraps() {
+ let expires = OffsetDateTime::UNIX_EPOCH + Duration::MAX;
+ let cookie = Cookie::build("foo", "bar").expires(expires).finish();
+ assert_eq!(&cookie.to_string(), "foo=bar; Expires=Fri, 31 Dec 9999 23:59:59 GMT");
+
+ let expires = time::macros::datetime!(9999-01-01 0:00 UTC) + Duration::days(1000);
+ let cookie = Cookie::build("foo", "bar").expires(expires).finish();
+ assert_eq!(&cookie.to_string(), "foo=bar; Expires=Fri, 31 Dec 9999 23:59:59 GMT");
+ }
+
+ #[test]
+ fn cookie_string_long_lifetimes() {
+ let cookie_string = "bar=baz; Path=/subdir; HttpOnly; Domain=crates.io".to_owned();
+ let (name, value, path, domain) = {
+ // Create a cookie passing a slice
+ let c = Cookie::parse(cookie_string.as_str()).unwrap();
+ (c.name_raw(), c.value_raw(), c.path_raw(), c.domain_raw())
+ };
+
+ assert_eq!(name, Some("bar"));
+ assert_eq!(value, Some("baz"));
+ assert_eq!(path, Some("/subdir"));
+ assert_eq!(domain, Some("crates.io"));
+ }
+
+ #[test]
+ fn owned_cookie_string() {
+ let cookie_string = "bar=baz; Path=/subdir; HttpOnly; Domain=crates.io".to_owned();
+ let (name, value, path, domain) = {
+ // Create a cookie passing an owned string
+ let c = Cookie::parse(cookie_string).unwrap();
+ (c.name_raw(), c.value_raw(), c.path_raw(), c.domain_raw())
+ };
+
+ assert_eq!(name, None);
+ assert_eq!(value, None);
+ assert_eq!(path, None);
+ assert_eq!(domain, None);
+ }
+
+ #[test]
+ fn owned_cookie_struct() {
+ let cookie_string = "bar=baz; Path=/subdir; HttpOnly; Domain=crates.io";
+ let (name, value, path, domain) = {
+ // Create an owned cookie
+ let c = Cookie::parse(cookie_string).unwrap().into_owned();
+
+ (c.name_raw(), c.value_raw(), c.path_raw(), c.domain_raw())
+ };
+
+ assert_eq!(name, None);
+ assert_eq!(value, None);
+ assert_eq!(path, None);
+ assert_eq!(domain, None);
+ }
+
+ #[test]
+ #[cfg(feature = "percent-encode")]
+ fn format_encoded() {
+ let cookie = Cookie::build("foo !%?=", "bar;;, a").finish();
+ let cookie_str = cookie.encoded().to_string();
+ assert_eq!(&cookie_str, "foo%20!%25%3F%3D=bar%3B%3B%2C%20a");
+
+ let cookie = Cookie::parse_encoded(cookie_str).unwrap();
+ assert_eq!(cookie.name_value(), ("foo !%?=", "bar;;, a"));
+ }
+}
diff --git a/third_party/rust/cookie/src/parse.rs b/third_party/rust/cookie/src/parse.rs
new file mode 100644
index 0000000000..dcd15b7b32
--- /dev/null
+++ b/third_party/rust/cookie/src/parse.rs
@@ -0,0 +1,550 @@
+use std::borrow::Cow;
+use std::error::Error;
+use std::convert::{From, TryFrom};
+use std::str::Utf8Error;
+use std::fmt;
+
+#[allow(unused_imports, deprecated)]
+use std::ascii::AsciiExt;
+
+#[cfg(feature = "percent-encode")]
+use percent_encoding::percent_decode;
+use time::{PrimitiveDateTime, Duration, OffsetDateTime};
+use time::{parsing::Parsable, macros::format_description, format_description::FormatItem};
+
+use crate::{Cookie, SameSite, CookieStr};
+
+// The three formats spec'd in http://tools.ietf.org/html/rfc2616#section-3.3.1.
+// Additional ones as encountered in the real world.
+pub static FMT1: &[FormatItem<'_>] = format_description!("[weekday repr:short], [day] [month repr:short] [year padding:none] [hour]:[minute]:[second] GMT");
+pub static FMT2: &[FormatItem<'_>] = format_description!("[weekday], [day]-[month repr:short]-[year repr:last_two] [hour]:[minute]:[second] GMT");
+pub static FMT3: &[FormatItem<'_>] = format_description!("[weekday repr:short] [month repr:short] [day padding:space] [hour]:[minute]:[second] [year padding:none]");
+pub static FMT4: &[FormatItem<'_>] = format_description!("[weekday repr:short], [day]-[month repr:short]-[year padding:none] [hour]:[minute]:[second] GMT");
+
+/// Enum corresponding to a parsing error.
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+#[non_exhaustive]
+pub enum ParseError {
+ /// The cookie did not contain a name/value pair.
+ MissingPair,
+ /// The cookie's name was empty.
+ EmptyName,
+ /// Decoding the cookie's name or value resulted in invalid UTF-8.
+ Utf8Error(Utf8Error),
+}
+
+impl ParseError {
+ /// Returns a description of this error as a string
+ pub fn as_str(&self) -> &'static str {
+ match *self {
+ ParseError::MissingPair => "the cookie is missing a name/value pair",
+ ParseError::EmptyName => "the cookie's name is empty",
+ ParseError::Utf8Error(_) => {
+ "decoding the cookie's name or value resulted in invalid UTF-8"
+ }
+ }
+ }
+}
+
+impl fmt::Display for ParseError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{}", self.as_str())
+ }
+}
+
+impl From<Utf8Error> for ParseError {
+ fn from(error: Utf8Error) -> ParseError {
+ ParseError::Utf8Error(error)
+ }
+}
+
+impl Error for ParseError {
+ fn description(&self) -> &str {
+ self.as_str()
+ }
+}
+
+fn indexes_of(needle: &str, haystack: &str) -> Option<(usize, usize)> {
+ let haystack_start = haystack.as_ptr() as usize;
+ let needle_start = needle.as_ptr() as usize;
+
+ if needle_start < haystack_start {
+ return None;
+ }
+
+ if (needle_start + needle.len()) > (haystack_start + haystack.len()) {
+ return None;
+ }
+
+ let start = needle_start - haystack_start;
+ let end = start + needle.len();
+ Some((start, end))
+}
+
+#[cfg(feature = "percent-encode")]
+fn name_val_decoded(
+ name: &str,
+ val: &str
+) -> Result<Option<(CookieStr<'static>, CookieStr<'static>)>, ParseError> {
+ let decoded_name = percent_decode(name.as_bytes()).decode_utf8()?;
+ let decoded_value = percent_decode(val.as_bytes()).decode_utf8()?;
+
+ if let (&Cow::Borrowed(_), &Cow::Borrowed(_)) = (&decoded_name, &decoded_value) {
+ Ok(None)
+ } else {
+ let name = CookieStr::Concrete(Cow::Owned(decoded_name.into()));
+ let val = CookieStr::Concrete(Cow::Owned(decoded_value.into()));
+ Ok(Some((name, val)))
+ }
+}
+
+#[cfg(not(feature = "percent-encode"))]
+fn name_val_decoded(
+ _: &str,
+ _: &str
+) -> Result<Option<(CookieStr<'static>, CookieStr<'static>)>, ParseError> {
+ unreachable!("This function should never be called with 'percent-encode' disabled!")
+}
+
+fn trim_quotes(s: &str) -> &str {
+ if s.len() < 2 {
+ return s;
+ }
+
+ match (s.chars().next(), s.chars().last()) {
+ (Some('"'), Some('"')) => &s[1..(s.len() - 1)],
+ _ => s
+ }
+}
+
+// This function does the real parsing but _does not_ set the `cookie_string` in
+// the returned cookie object. This only exists so that the borrow to `s` is
+// returned at the end of the call, allowing the `cookie_string` field to be
+// set in the outer `parse` function.
+fn parse_inner<'c>(s: &str, decode: bool) -> Result<Cookie<'c>, ParseError> {
+ let mut attributes = s.split(';');
+
+ // Determine the name = val.
+ let key_value = attributes.next().expect("first str::split().next() returns Some");
+ let (name, value) = match key_value.find('=') {
+ Some(i) => {
+ let (key, value) = (key_value[..i].trim(), key_value[(i + 1)..].trim());
+ (key, trim_quotes(value).trim())
+ },
+ None => return Err(ParseError::MissingPair)
+ };
+
+ if name.is_empty() {
+ return Err(ParseError::EmptyName);
+ }
+
+ // If there is nothing to decode, or we're not decoding, use indexes.
+ let indexed_names = |s, name, value| {
+ let name_indexes = indexes_of(name, s).expect("name sub");
+ let value_indexes = indexes_of(value, s).expect("value sub");
+ let name = CookieStr::Indexed(name_indexes.0, name_indexes.1);
+ let value = CookieStr::Indexed(value_indexes.0, value_indexes.1);
+ (name, value)
+ };
+
+ // Create a cookie with all of the defaults. We'll fill things in while we
+ // iterate through the parameters below.
+ let (name, value) = if decode {
+ match name_val_decoded(name, value)? {
+ Some((name, value)) => (name, value),
+ None => indexed_names(s, name, value)
+ }
+ } else {
+ indexed_names(s, name, value)
+ };
+
+ let mut cookie: Cookie<'c> = Cookie {
+ name, value,
+ cookie_string: None,
+ expires: None,
+ max_age: None,
+ domain: None,
+ path: None,
+ secure: None,
+ http_only: None,
+ same_site: None
+ };
+
+ for attr in attributes {
+ let (key, value) = match attr.find('=') {
+ Some(i) => (attr[..i].trim(), Some(attr[(i + 1)..].trim())),
+ None => (attr.trim(), None),
+ };
+
+ match (&*key.to_ascii_lowercase(), value) {
+ ("secure", _) => cookie.secure = Some(true),
+ ("httponly", _) => cookie.http_only = Some(true),
+ ("max-age", Some(mut v)) => cookie.max_age = {
+ let is_negative = v.starts_with('-');
+ if is_negative {
+ v = &v[1..];
+ }
+
+ if !v.chars().all(|d| d.is_digit(10)) {
+ continue
+ }
+
+ // From RFC 6265 5.2.2: neg values indicate that the earliest
+ // expiration should be used, so set the max age to 0 seconds.
+ if is_negative {
+ Some(Duration::ZERO)
+ } else {
+ Some(v.parse::<i64>()
+ .map(Duration::seconds)
+ .unwrap_or_else(|_| Duration::seconds(i64::max_value())))
+ }
+ },
+ ("domain", Some(mut domain)) if !domain.is_empty() => {
+ if domain.starts_with('.') {
+ domain = &domain[1..];
+ }
+
+ let (i, j) = indexes_of(domain, s).expect("domain sub");
+ cookie.domain = Some(CookieStr::Indexed(i, j));
+ }
+ ("path", Some(v)) => {
+ let (i, j) = indexes_of(v, s).expect("path sub");
+ cookie.path = Some(CookieStr::Indexed(i, j));
+ }
+ ("samesite", Some(v)) => {
+ if v.eq_ignore_ascii_case("strict") {
+ cookie.same_site = Some(SameSite::Strict);
+ } else if v.eq_ignore_ascii_case("lax") {
+ cookie.same_site = Some(SameSite::Lax);
+ } else if v.eq_ignore_ascii_case("none") {
+ cookie.same_site = Some(SameSite::None);
+ } else {
+ // We do nothing here, for now. When/if the `SameSite`
+ // attribute becomes standard, the spec says that we should
+ // ignore this cookie, i.e, fail to parse it, when an
+ // invalid value is passed in. The draft is at
+ // http://httpwg.org/http-extensions/draft-ietf-httpbis-cookie-same-site.html.
+ }
+ }
+ ("expires", Some(v)) => {
+ let tm = parse_date(v, &FMT1)
+ .or_else(|_| parse_date(v, &FMT2))
+ .or_else(|_| parse_date(v, &FMT3))
+ .or_else(|_| parse_date(v, &FMT4));
+ // .or_else(|_| parse_date(v, &FMT5));
+
+ if let Ok(time) = tm {
+ cookie.expires = Some(time.into())
+ }
+ }
+ _ => {
+ // We're going to be permissive here. If we have no idea what
+ // this is, then it's something nonstandard. We're not going to
+ // store it (because it's not compliant), but we're also not
+ // going to emit an error.
+ }
+ }
+ }
+
+ Ok(cookie)
+}
+
+pub(crate) fn parse_cookie<'c, S>(cow: S, decode: bool) -> Result<Cookie<'c>, ParseError>
+ where S: Into<Cow<'c, str>>
+{
+ let s = cow.into();
+ let mut cookie = parse_inner(&s, decode)?;
+ cookie.cookie_string = Some(s);
+ Ok(cookie)
+}
+
+pub(crate) fn parse_date(s: &str, format: &impl Parsable) -> Result<OffsetDateTime, time::Error> {
+ // Parse. Handle "abbreviated" dates like Chromium. See cookie#162.
+ let mut date = format.parse(s.as_bytes())?;
+ if let Some(y) = date.year().or_else(|| date.year_last_two().map(|v| v as i32)) {
+ let offset = match y {
+ 0..=68 => 2000,
+ 69..=99 => 1900,
+ _ => 0,
+ };
+
+ date.set_year(y + offset);
+ }
+
+ Ok(PrimitiveDateTime::try_from(date)?.assume_utc())
+}
+
+#[cfg(test)]
+mod tests {
+ use super::parse_date;
+ use crate::{Cookie, SameSite};
+ use time::Duration;
+
+ macro_rules! assert_eq_parse {
+ ($string:expr, $expected:expr) => (
+ let cookie = match Cookie::parse($string) {
+ Ok(cookie) => cookie,
+ Err(e) => panic!("Failed to parse {:?}: {:?}", $string, e)
+ };
+
+ assert_eq!(cookie, $expected);
+ )
+ }
+
+ macro_rules! assert_ne_parse {
+ ($string:expr, $expected:expr) => (
+ let cookie = match Cookie::parse($string) {
+ Ok(cookie) => cookie,
+ Err(e) => panic!("Failed to parse {:?}: {:?}", $string, e)
+ };
+
+ assert_ne!(cookie, $expected);
+ )
+ }
+
+ #[test]
+ fn parse_same_site() {
+ let expected = Cookie::build("foo", "bar")
+ .same_site(SameSite::Lax)
+ .finish();
+
+ assert_eq_parse!("foo=bar; SameSite=Lax", expected);
+ assert_eq_parse!("foo=bar; SameSite=lax", expected);
+ assert_eq_parse!("foo=bar; SameSite=LAX", expected);
+ assert_eq_parse!("foo=bar; samesite=Lax", expected);
+ assert_eq_parse!("foo=bar; SAMESITE=Lax", expected);
+
+ let expected = Cookie::build("foo", "bar")
+ .same_site(SameSite::Strict)
+ .finish();
+
+ assert_eq_parse!("foo=bar; SameSite=Strict", expected);
+ assert_eq_parse!("foo=bar; SameSITE=Strict", expected);
+ assert_eq_parse!("foo=bar; SameSite=strict", expected);
+ assert_eq_parse!("foo=bar; SameSite=STrICT", expected);
+ assert_eq_parse!("foo=bar; SameSite=STRICT", expected);
+
+ let expected = Cookie::build("foo", "bar")
+ .same_site(SameSite::None)
+ .finish();
+
+ assert_eq_parse!("foo=bar; SameSite=None", expected);
+ assert_eq_parse!("foo=bar; SameSITE=none", expected);
+ assert_eq_parse!("foo=bar; SameSite=NOne", expected);
+ assert_eq_parse!("foo=bar; SameSite=nOne", expected);
+ }
+
+ #[test]
+ fn parse() {
+ assert!(Cookie::parse("bar").is_err());
+ assert!(Cookie::parse("=bar").is_err());
+ assert!(Cookie::parse(" =bar").is_err());
+ assert!(Cookie::parse("foo=").is_ok());
+
+ let expected = Cookie::build("foo", "bar=baz").finish();
+ assert_eq_parse!("foo=bar=baz", expected);
+
+ let expected = Cookie::build("foo", "\"bar\"").finish();
+ assert_eq_parse!("foo=\"\"bar\"\"", expected);
+
+ let expected = Cookie::build("foo", "\"bar").finish();
+ assert_eq_parse!("foo= \"bar", expected);
+ assert_eq_parse!("foo=\"bar ", expected);
+ assert_eq_parse!("foo=\"\"bar\"", expected);
+ assert_eq_parse!("foo=\"\"bar \"", expected);
+ assert_eq_parse!("foo=\"\"bar \" ", expected);
+
+ let expected = Cookie::build("foo", "bar\"").finish();
+ assert_eq_parse!("foo=bar\"", expected);
+ assert_eq_parse!("foo=\"bar\"\"", expected);
+ assert_eq_parse!("foo=\" bar\"\"", expected);
+ assert_eq_parse!("foo=\" bar\" \" ", expected);
+
+ let mut expected = Cookie::build("foo", "bar").finish();
+ assert_eq_parse!("foo=bar", expected);
+ assert_eq_parse!("foo = bar", expected);
+ assert_eq_parse!("foo=\"bar\"", expected);
+ assert_eq_parse!(" foo=bar ", expected);
+ assert_eq_parse!(" foo=\"bar \" ", expected);
+ assert_eq_parse!(" foo=bar ;Domain=", expected);
+ assert_eq_parse!(" foo=bar ;Domain= ", expected);
+ assert_eq_parse!(" foo=bar ;Ignored", expected);
+
+ let mut unexpected = Cookie::build("foo", "bar").http_only(false).finish();
+ assert_ne_parse!(" foo=bar ;HttpOnly", unexpected);
+ assert_ne_parse!(" foo=bar; httponly", unexpected);
+
+ expected.set_http_only(true);
+ assert_eq_parse!(" foo=bar ;HttpOnly", expected);
+ assert_eq_parse!(" foo=bar ;httponly", expected);
+ assert_eq_parse!(" foo=bar ;HTTPONLY=whatever", expected);
+ assert_eq_parse!(" foo=bar ; sekure; HTTPONLY", expected);
+
+ expected.set_secure(true);
+ assert_eq_parse!(" foo=bar ;HttpOnly; Secure", expected);
+ assert_eq_parse!(" foo=bar ;HttpOnly; Secure=aaaa", expected);
+
+ unexpected.set_http_only(true);
+ unexpected.set_secure(true);
+ assert_ne_parse!(" foo=bar ;HttpOnly; skeure", unexpected);
+ assert_ne_parse!(" foo=bar ;HttpOnly; =secure", unexpected);
+ assert_ne_parse!(" foo=bar ;HttpOnly;", unexpected);
+
+ unexpected.set_secure(false);
+ assert_ne_parse!(" foo=bar ;HttpOnly; secure", unexpected);
+ assert_ne_parse!(" foo=bar ;HttpOnly; secure", unexpected);
+ assert_ne_parse!(" foo=bar ;HttpOnly; secure", unexpected);
+
+ expected.set_max_age(Duration::ZERO);
+ assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=0", expected);
+ assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age = 0 ", expected);
+ assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=-1", expected);
+ assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age = -1 ", expected);
+
+ expected.set_max_age(Duration::minutes(1));
+ assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=60", expected);
+ assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age = 60 ", expected);
+
+ expected.set_max_age(Duration::seconds(4));
+ assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=4", expected);
+ assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age = 4 ", expected);
+
+ unexpected.set_secure(true);
+ unexpected.set_max_age(Duration::minutes(1));
+ assert_ne_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=122", unexpected);
+ assert_ne_parse!(" foo=bar ;HttpOnly; Secure; Max-Age = 38 ", unexpected);
+ assert_ne_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=51", unexpected);
+ assert_ne_parse!(" foo=bar ;HttpOnly; Secure; Max-Age = -1 ", unexpected);
+ assert_ne_parse!(" foo=bar ;HttpOnly; Secure; Max-Age = 0", unexpected);
+
+ expected.set_path("/");
+ assert_eq_parse!("foo=bar;HttpOnly; Secure; Max-Age=4; Path=/", expected);
+ assert_eq_parse!("foo=bar;HttpOnly; Secure; Max-Age=4;Path=/", expected);
+
+ expected.set_path("/foo");
+ assert_eq_parse!("foo=bar;HttpOnly; Secure; Max-Age=4; Path=/foo", expected);
+ assert_eq_parse!("foo=bar;HttpOnly; Secure; Max-Age=4;Path=/foo", expected);
+ assert_eq_parse!("foo=bar;HttpOnly; Secure; Max-Age=4;path=/foo", expected);
+ assert_eq_parse!("foo=bar;HttpOnly; Secure; Max-Age=4;path = /foo", expected);
+
+ unexpected.set_max_age(Duration::seconds(4));
+ unexpected.set_path("/bar");
+ assert_ne_parse!("foo=bar;HttpOnly; Secure; Max-Age=4; Path=/foo", unexpected);
+ assert_ne_parse!("foo=bar;HttpOnly; Secure; Max-Age=4;Path=/baz", unexpected);
+
+ expected.set_domain("www.foo.com");
+ assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \
+ Domain=www.foo.com", expected);
+
+ expected.set_domain("foo.com");
+ assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \
+ Domain=foo.com", expected);
+ assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \
+ Domain=FOO.COM", expected);
+
+ unexpected.set_path("/foo");
+ unexpected.set_domain("bar.com");
+ assert_ne_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \
+ Domain=foo.com", unexpected);
+ assert_ne_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \
+ Domain=FOO.COM", unexpected);
+
+ let time_str = "Wed, 21 Oct 2015 07:28:00 GMT";
+ let expires = parse_date(time_str, &super::FMT1).unwrap();
+ expected.set_expires(expires);
+ assert_eq_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \
+ Domain=foo.com; Expires=Wed, 21 Oct 2015 07:28:00 GMT", expected);
+
+ unexpected.set_domain("foo.com");
+ let bad_expires = parse_date(time_str, &super::FMT1).unwrap();
+ expected.set_expires(bad_expires);
+ assert_ne_parse!(" foo=bar ;HttpOnly; Secure; Max-Age=4; Path=/foo; \
+ Domain=foo.com; Expires=Wed, 21 Oct 2015 07:28:00 GMT", unexpected);
+ }
+
+ #[test]
+ fn parse_abbreviated_years() {
+ let cookie_str = "foo=bar; expires=Thu, 10-Sep-20 20:00:00 GMT";
+ let cookie = Cookie::parse(cookie_str).unwrap();
+ assert_eq!(cookie.expires_datetime().unwrap().year(), 2020);
+
+ let cookie_str = "foo=bar; expires=Thu, 10-Sep-68 20:00:00 GMT";
+ let cookie = Cookie::parse(cookie_str).unwrap();
+ assert_eq!(cookie.expires_datetime().unwrap().year(), 2068);
+
+ let cookie_str = "foo=bar; expires=Thu, 10-Sep-69 20:00:00 GMT";
+ let cookie = Cookie::parse(cookie_str).unwrap();
+ assert_eq!(cookie.expires_datetime().unwrap().year(), 1969);
+
+ let cookie_str = "foo=bar; expires=Thu, 10-Sep-99 20:00:00 GMT";
+ let cookie = Cookie::parse(cookie_str).unwrap();
+ assert_eq!(cookie.expires_datetime().unwrap().year(), 1999);
+
+ let cookie_str = "foo=bar; expires=Thu, 10-Sep-2069 20:00:00 GMT";
+ let cookie = Cookie::parse(cookie_str).unwrap();
+ assert_eq!(cookie.expires_datetime().unwrap().year(), 2069);
+ }
+
+ #[test]
+ fn parse_variant_date_fmts() {
+ let cookie_str = "foo=bar; expires=Sun, 06 Nov 1994 08:49:37 GMT";
+ Cookie::parse(cookie_str).unwrap().expires_datetime().unwrap();
+
+ let cookie_str = "foo=bar; expires=Sunday, 06-Nov-94 08:49:37 GMT";
+ Cookie::parse(cookie_str).unwrap().expires_datetime().unwrap();
+
+ let cookie_str = "foo=bar; expires=Sun Nov 6 08:49:37 1994";
+ Cookie::parse(cookie_str).unwrap().expires_datetime().unwrap();
+ }
+
+ #[test]
+ fn parse_very_large_max_ages() {
+ let mut expected = Cookie::build("foo", "bar")
+ .max_age(Duration::seconds(i64::max_value()))
+ .finish();
+
+ let string = format!("foo=bar; Max-Age={}", 1u128 << 100);
+ assert_eq_parse!(&string, expected);
+
+ expected.set_max_age(Duration::seconds(0));
+ assert_eq_parse!("foo=bar; Max-Age=-129", expected);
+
+ let string = format!("foo=bar; Max-Age=-{}", 1u128 << 100);
+ assert_eq_parse!(&string, expected);
+
+ let string = format!("foo=bar; Max-Age=-{}", i64::max_value());
+ assert_eq_parse!(&string, expected);
+
+ let string = format!("foo=bar; Max-Age={}", i64::max_value());
+ expected.set_max_age(Duration::seconds(i64::max_value()));
+ assert_eq_parse!(&string, expected);
+ }
+
+ #[test]
+ fn odd_characters() {
+ let expected = Cookie::new("foo", "b%2Fr");
+ assert_eq_parse!("foo=b%2Fr", expected);
+ }
+
+ #[test]
+ #[cfg(feature = "percent-encode")]
+ fn odd_characters_encoded() {
+ let expected = Cookie::new("foo", "b/r");
+ let cookie = match Cookie::parse_encoded("foo=b%2Fr") {
+ Ok(cookie) => cookie,
+ Err(e) => panic!("Failed to parse: {:?}", e)
+ };
+
+ assert_eq!(cookie, expected);
+ }
+
+ #[test]
+ fn do_not_panic_on_large_max_ages() {
+ let max_seconds = Duration::MAX.whole_seconds();
+ let expected = Cookie::build("foo", "bar")
+ .max_age(Duration::seconds(max_seconds))
+ .finish();
+ let too_many_seconds = (max_seconds as u64) + 1;
+ assert_eq_parse!(format!(" foo=bar; Max-Age={:?}", too_many_seconds), expected);
+ }
+}
diff --git a/third_party/rust/cookie/src/secure/key.rs b/third_party/rust/cookie/src/secure/key.rs
new file mode 100644
index 0000000000..9c2228eb3a
--- /dev/null
+++ b/third_party/rust/cookie/src/secure/key.rs
@@ -0,0 +1,301 @@
+use std::convert::TryFrom;
+
+const SIGNING_KEY_LEN: usize = 32;
+const ENCRYPTION_KEY_LEN: usize = 32;
+const COMBINED_KEY_LENGTH: usize = SIGNING_KEY_LEN + ENCRYPTION_KEY_LEN;
+
+// Statically ensure the numbers above are in-sync.
+#[cfg(feature = "signed")]
+const_assert!(crate::secure::signed::KEY_LEN == SIGNING_KEY_LEN);
+#[cfg(feature = "private")]
+const_assert!(crate::secure::private::KEY_LEN == ENCRYPTION_KEY_LEN);
+
+/// A cryptographic master key for use with `Signed` and/or `Private` jars.
+///
+/// This structure encapsulates secure, cryptographic keys for use with both
+/// [`PrivateJar`](crate::PrivateJar) and [`SignedJar`](crate::SignedJar). A
+/// single instance of a `Key` can be used for both a `PrivateJar` and a
+/// `SignedJar` simultaneously with no notable security implications.
+#[cfg_attr(all(nightly, doc), doc(cfg(any(feature = "private", feature = "signed"))))]
+#[derive(Clone)]
+pub struct Key([u8; COMBINED_KEY_LENGTH /* SIGNING | ENCRYPTION */]);
+
+impl PartialEq for Key {
+ fn eq(&self, other: &Self) -> bool {
+ use subtle::ConstantTimeEq;
+
+ self.0.ct_eq(&other.0).into()
+ }
+}
+
+impl Key {
+ // An empty key structure, to be filled.
+ const fn zero() -> Self {
+ Key([0; COMBINED_KEY_LENGTH])
+ }
+
+ /// Creates a new `Key` from a 512-bit cryptographically random string.
+ ///
+ /// The supplied key must be at least 512-bits (64 bytes). For security, the
+ /// master key _must_ be cryptographically random.
+ ///
+ /// # Panics
+ ///
+ /// Panics if `key` is less than 64 bytes in length.
+ ///
+ /// For a non-panicking version, use [`Key::try_from()`] or generate a key with
+ /// [`Key::generate()`] or [`Key::try_generate()`].
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::Key;
+ ///
+ /// # /*
+ /// let key = { /* a cryptographically random key >= 64 bytes */ };
+ /// # */
+ /// # let key: &Vec<u8> = &(0..64).collect();
+ ///
+ /// let key = Key::from(key);
+ /// ```
+ #[inline]
+ pub fn from(key: &[u8]) -> Key {
+ Key::try_from(key).unwrap()
+ }
+
+ /// Derives new signing/encryption keys from a master key.
+ ///
+ /// The master key must be at least 256-bits (32 bytes). For security, the
+ /// master key _must_ be cryptographically random. The keys are derived
+ /// deterministically from the master key.
+ ///
+ /// # Panics
+ ///
+ /// Panics if `key` is less than 32 bytes in length.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::Key;
+ ///
+ /// # /*
+ /// let master_key = { /* a cryptographically random key >= 32 bytes */ };
+ /// # */
+ /// # let master_key: &Vec<u8> = &(0..32).collect();
+ ///
+ /// let key = Key::derive_from(master_key);
+ /// ```
+ #[cfg(feature = "key-expansion")]
+ #[cfg_attr(all(nightly, doc), doc(cfg(feature = "key-expansion")))]
+ pub fn derive_from(master_key: &[u8]) -> Self {
+ if master_key.len() < 32 {
+ panic!("bad master key length: expected >= 32 bytes, found {}", master_key.len());
+ }
+
+ // Expand the master key into two HKDF generated keys.
+ const KEYS_INFO: &[u8] = b"COOKIE;SIGNED:HMAC-SHA256;PRIVATE:AEAD-AES-256-GCM";
+ let mut both_keys = [0; COMBINED_KEY_LENGTH];
+ let hk = hkdf::Hkdf::<sha2::Sha256>::from_prk(master_key).expect("key length prechecked");
+ hk.expand(KEYS_INFO, &mut both_keys).expect("expand into keys");
+ Key::from(&both_keys)
+ }
+
+ /// Generates signing/encryption keys from a secure, random source. Keys are
+ /// generated nondeterministically.
+ ///
+ /// # Panics
+ ///
+ /// Panics if randomness cannot be retrieved from the operating system. See
+ /// [`Key::try_generate()`] for a non-panicking version.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::Key;
+ ///
+ /// let key = Key::generate();
+ /// ```
+ pub fn generate() -> Key {
+ Self::try_generate().expect("failed to generate `Key` from randomness")
+ }
+
+ /// Attempts to generate signing/encryption keys from a secure, random
+ /// source. Keys are generated nondeterministically. If randomness cannot be
+ /// retrieved from the underlying operating system, returns `None`.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::Key;
+ ///
+ /// let key = Key::try_generate();
+ /// ```
+ pub fn try_generate() -> Option<Key> {
+ use crate::secure::rand::RngCore;
+
+ let mut rng = crate::secure::rand::thread_rng();
+ let mut key = Key::zero();
+ rng.try_fill_bytes(&mut key.0).ok()?;
+ Some(key)
+ }
+
+ /// Returns the raw bytes of a key suitable for signing cookies. Guaranteed
+ /// to be at least 32 bytes.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::Key;
+ ///
+ /// let key = Key::generate();
+ /// let signing_key = key.signing();
+ /// ```
+ pub fn signing(&self) -> &[u8] {
+ &self.0[..SIGNING_KEY_LEN]
+ }
+
+ /// Returns the raw bytes of a key suitable for encrypting cookies.
+ /// Guaranteed to be at least 32 bytes.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::Key;
+ ///
+ /// let key = Key::generate();
+ /// let encryption_key = key.encryption();
+ /// ```
+ pub fn encryption(&self) -> &[u8] {
+ &self.0[SIGNING_KEY_LEN..]
+ }
+
+ /// Returns the raw bytes of the master key. Guaranteed to be at least 64
+ /// bytes.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::Key;
+ ///
+ /// let key = Key::generate();
+ /// let master_key = key.master();
+ /// ```
+ pub fn master(&self) -> &[u8] {
+ &self.0
+ }
+}
+
+/// An error indicating an issue with generating or constructing a key.
+#[cfg_attr(all(nightly, doc), doc(cfg(any(feature = "private", feature = "signed"))))]
+#[derive(Debug)]
+#[non_exhaustive]
+pub enum KeyError {
+ /// Too few bytes (`.0`) were provided to generate a key.
+ ///
+ /// See [`Key::from()`] for minimum requirements.
+ TooShort(usize),
+}
+
+impl std::error::Error for KeyError { }
+
+impl std::fmt::Display for KeyError {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ KeyError::TooShort(n) => {
+ write!(f, "key material is too short: expected >= {} bytes, got {} bytes",
+ COMBINED_KEY_LENGTH, n)
+ }
+ }
+ }
+}
+
+impl TryFrom<&[u8]> for Key {
+ type Error = KeyError;
+
+ /// A fallible version of [`Key::from()`].
+ ///
+ /// Succeeds when [`Key::from()`] succeds and returns an error where
+ /// [`Key::from()`] panics, namely, if `key` is too short.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// # use std::convert::TryFrom;
+ /// use cookie::Key;
+ ///
+ /// # /*
+ /// let key = { /* a cryptographically random key >= 64 bytes */ };
+ /// # */
+ /// # let key: &Vec<u8> = &(0..64).collect();
+ /// # let key: &[u8] = &key[..];
+ /// assert!(Key::try_from(key).is_ok());
+ ///
+ /// // A key that's far too short to use.
+ /// let key = &[1, 2, 3, 4][..];
+ /// assert!(Key::try_from(key).is_err());
+ /// ```
+ fn try_from(key: &[u8]) -> Result<Self, Self::Error> {
+ if key.len() < COMBINED_KEY_LENGTH {
+ Err(KeyError::TooShort(key.len()))
+ } else {
+ let mut output = Key::zero();
+ output.0.copy_from_slice(&key[..COMBINED_KEY_LENGTH]);
+ Ok(output)
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::Key;
+
+ #[test]
+ fn from_works() {
+ let key = Key::from(&(0..64).collect::<Vec<_>>());
+
+ let signing: Vec<u8> = (0..32).collect();
+ assert_eq!(key.signing(), &*signing);
+
+ let encryption: Vec<u8> = (32..64).collect();
+ assert_eq!(key.encryption(), &*encryption);
+ }
+
+ #[test]
+ fn try_from_works() {
+ use core::convert::TryInto;
+ let data = (0..64).collect::<Vec<_>>();
+ let key_res: Result<Key, _> = data[0..63].try_into();
+ assert!(key_res.is_err());
+
+ let key_res: Result<Key, _> = data.as_slice().try_into();
+ assert!(key_res.is_ok());
+ }
+
+ #[test]
+ #[cfg(feature = "key-expansion")]
+ fn deterministic_derive() {
+ let master_key: Vec<u8> = (0..32).collect();
+
+ let key_a = Key::derive_from(&master_key);
+ let key_b = Key::derive_from(&master_key);
+
+ assert_eq!(key_a.signing(), key_b.signing());
+ assert_eq!(key_a.encryption(), key_b.encryption());
+ assert_ne!(key_a.encryption(), key_a.signing());
+
+ let master_key_2: Vec<u8> = (32..64).collect();
+ let key_2 = Key::derive_from(&master_key_2);
+
+ assert_ne!(key_2.signing(), key_a.signing());
+ assert_ne!(key_2.encryption(), key_a.encryption());
+ }
+
+ #[test]
+ fn non_deterministic_generate() {
+ let key_a = Key::generate();
+ let key_b = Key::generate();
+
+ assert_ne!(key_a.signing(), key_b.signing());
+ assert_ne!(key_a.encryption(), key_b.encryption());
+ }
+}
diff --git a/third_party/rust/cookie/src/secure/macros.rs b/third_party/rust/cookie/src/secure/macros.rs
new file mode 100644
index 0000000000..dfbddcf928
--- /dev/null
+++ b/third_party/rust/cookie/src/secure/macros.rs
@@ -0,0 +1,49 @@
+#[cfg(test)]
+macro_rules! assert_simple_behaviour {
+ ($clear:expr, $secure:expr) => ({
+ assert_eq!($clear.iter().count(), 0);
+
+ $secure.add(Cookie::new("name", "val"));
+ assert_eq!($clear.iter().count(), 1);
+ assert_eq!($secure.get("name").unwrap().value(), "val");
+ assert_ne!($clear.get("name").unwrap().value(), "val");
+
+ $secure.add(Cookie::new("another", "two"));
+ assert_eq!($clear.iter().count(), 2);
+
+ $clear.remove(Cookie::named("another"));
+ assert_eq!($clear.iter().count(), 1);
+
+ $secure.remove(Cookie::named("name"));
+ assert_eq!($clear.iter().count(), 0);
+ })
+}
+
+#[cfg(test)]
+macro_rules! assert_secure_behaviour {
+ ($clear:expr, $secure:expr) => ({
+ $secure.add(Cookie::new("secure", "secure"));
+ assert!($clear.get("secure").unwrap().value() != "secure");
+ assert!($secure.get("secure").unwrap().value() == "secure");
+
+ let mut cookie = $clear.get("secure").unwrap().clone();
+ let new_val = format!("{}l", cookie.value());
+ cookie.set_value(new_val);
+ $clear.add(cookie);
+ assert!($secure.get("secure").is_none());
+
+ let mut cookie = $clear.get("secure").unwrap().clone();
+ cookie.set_value("foobar");
+ $clear.add(cookie);
+ assert!($secure.get("secure").is_none());
+ })
+}
+
+// This is courtesty of `static_assertions`. That library is Copyright (c) 2017
+// Nikolai Vazquez. See https://github.com/nvzqz/static-assertions-rs for more.
+macro_rules! const_assert {
+ ($x:expr $(,)?) => {
+ #[allow(unknown_lints, clippy::eq_op)]
+ const _: [(); 0 - !{ const ASSERT: bool = $x; ASSERT } as usize] = [];
+ };
+}
diff --git a/third_party/rust/cookie/src/secure/mod.rs b/third_party/rust/cookie/src/secure/mod.rs
new file mode 100644
index 0000000000..ca066888e7
--- /dev/null
+++ b/third_party/rust/cookie/src/secure/mod.rs
@@ -0,0 +1,14 @@
+extern crate rand;
+extern crate base64;
+
+#[macro_use]
+mod macros;
+mod key;
+
+pub use self::key::*;
+
+#[cfg(feature = "private")] mod private;
+#[cfg(feature = "private")] pub use self::private::*;
+
+#[cfg(feature = "signed")] mod signed;
+#[cfg(feature = "signed")] pub use self::signed::*;
diff --git a/third_party/rust/cookie/src/secure/private.rs b/third_party/rust/cookie/src/secure/private.rs
new file mode 100644
index 0000000000..1264c3983c
--- /dev/null
+++ b/third_party/rust/cookie/src/secure/private.rs
@@ -0,0 +1,264 @@
+extern crate aes_gcm;
+
+use std::convert::TryInto;
+use std::borrow::{Borrow, BorrowMut};
+
+use crate::secure::{base64, rand, Key};
+use crate::{Cookie, CookieJar};
+
+use self::aes_gcm::aead::{generic_array::GenericArray, Aead, AeadInPlace, KeyInit, Payload};
+use self::aes_gcm::Aes256Gcm;
+use self::rand::RngCore;
+
+// Keep these in sync, and keep the key len synced with the `private` docs as
+// well as the `KEYS_INFO` const in secure::Key.
+pub(crate) const NONCE_LEN: usize = 12;
+pub(crate) const TAG_LEN: usize = 16;
+pub(crate) const KEY_LEN: usize = 32;
+
+/// A child cookie jar that provides authenticated encryption for its cookies.
+///
+/// A _private_ child jar signs and encrypts all the cookies added to it and
+/// verifies and decrypts cookies retrieved from it. Any cookies stored in a
+/// `PrivateJar` are simultaneously assured confidentiality, integrity, and
+/// authenticity. In other words, clients cannot discover nor tamper with the
+/// contents of a cookie, nor can they fabricate cookie data.
+#[cfg_attr(all(nightly, doc), doc(cfg(feature = "private")))]
+pub struct PrivateJar<J> {
+ parent: J,
+ key: [u8; KEY_LEN]
+}
+
+impl<J> PrivateJar<J> {
+ /// Creates a new child `PrivateJar` with parent `parent` and key `key`.
+ /// This method is typically called indirectly via the `signed` method of
+ /// `CookieJar`.
+ pub(crate) fn new(parent: J, key: &Key) -> PrivateJar<J> {
+ PrivateJar { parent, key: key.encryption().try_into().expect("enc key len") }
+ }
+
+ /// Encrypts the cookie's value with authenticated encryption providing
+ /// confidentiality, integrity, and authenticity.
+ fn encrypt_cookie(&self, cookie: &mut Cookie) {
+ // Create a vec to hold the [nonce | cookie value | tag].
+ let cookie_val = cookie.value().as_bytes();
+ let mut data = vec![0; NONCE_LEN + cookie_val.len() + TAG_LEN];
+
+ // Split data into three: nonce, input/output, tag. Copy input.
+ let (nonce, in_out) = data.split_at_mut(NONCE_LEN);
+ let (in_out, tag) = in_out.split_at_mut(cookie_val.len());
+ in_out.copy_from_slice(cookie_val);
+
+ // Fill nonce piece with random data.
+ let mut rng = self::rand::thread_rng();
+ rng.try_fill_bytes(nonce).expect("couldn't random fill nonce");
+ let nonce = GenericArray::clone_from_slice(nonce);
+
+ // Perform the actual sealing operation, using the cookie's name as
+ // associated data to prevent value swapping.
+ let aad = cookie.name().as_bytes();
+ let aead = Aes256Gcm::new(GenericArray::from_slice(&self.key));
+ let aad_tag = aead.encrypt_in_place_detached(&nonce, aad, in_out)
+ .expect("encryption failure!");
+
+ // Copy the tag into the tag piece.
+ tag.copy_from_slice(&aad_tag);
+
+ // Base64 encode [nonce | encrypted value | tag].
+ cookie.set_value(base64::encode(&data));
+ }
+
+ /// Given a sealed value `str` and a key name `name`, where the nonce is
+ /// prepended to the original value and then both are Base64 encoded,
+ /// verifies and decrypts the sealed value and returns it. If there's a
+ /// problem, returns an `Err` with a string describing the issue.
+ fn unseal(&self, name: &str, value: &str) -> Result<String, &'static str> {
+ let data = base64::decode(value).map_err(|_| "bad base64 value")?;
+ if data.len() <= NONCE_LEN {
+ return Err("length of decoded data is <= NONCE_LEN");
+ }
+
+ let (nonce, cipher) = data.split_at(NONCE_LEN);
+ let payload = Payload { msg: cipher, aad: name.as_bytes() };
+
+ let aead = Aes256Gcm::new(GenericArray::from_slice(&self.key));
+ aead.decrypt(GenericArray::from_slice(nonce), payload)
+ .map_err(|_| "invalid key/nonce/value: bad seal")
+ .and_then(|s| String::from_utf8(s).map_err(|_| "bad unsealed utf8"))
+ }
+
+ /// Authenticates and decrypts `cookie`, returning the plaintext version if
+ /// decryption succeeds or `None` otherwise. Authenticatation and decryption
+ /// _always_ succeeds if `cookie` was generated by a `PrivateJar` with the
+ /// same key as `self`.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::{CookieJar, Cookie, Key};
+ ///
+ /// let key = Key::generate();
+ /// let mut jar = CookieJar::new();
+ /// assert!(jar.private(&key).get("name").is_none());
+ ///
+ /// jar.private_mut(&key).add(Cookie::new("name", "value"));
+ /// assert_eq!(jar.private(&key).get("name").unwrap().value(), "value");
+ ///
+ /// let plain = jar.get("name").cloned().unwrap();
+ /// assert_ne!(plain.value(), "value");
+ /// let decrypted = jar.private(&key).decrypt(plain).unwrap();
+ /// assert_eq!(decrypted.value(), "value");
+ ///
+ /// let plain = Cookie::new("plaintext", "hello");
+ /// assert!(jar.private(&key).decrypt(plain).is_none());
+ /// ```
+ pub fn decrypt(&self, mut cookie: Cookie<'static>) -> Option<Cookie<'static>> {
+ if let Ok(value) = self.unseal(cookie.name(), cookie.value()) {
+ cookie.set_value(value);
+ return Some(cookie);
+ }
+
+ None
+ }
+}
+
+impl<J: Borrow<CookieJar>> PrivateJar<J> {
+ /// Returns a reference to the `Cookie` inside this jar with the name `name`
+ /// and authenticates and decrypts the cookie's value, returning a `Cookie`
+ /// with the decrypted value. If the cookie cannot be found, or the cookie
+ /// fails to authenticate or decrypt, `None` is returned.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::{CookieJar, Cookie, Key};
+ ///
+ /// let key = Key::generate();
+ /// let jar = CookieJar::new();
+ /// assert!(jar.private(&key).get("name").is_none());
+ ///
+ /// let mut jar = jar;
+ /// let mut private_jar = jar.private_mut(&key);
+ /// private_jar.add(Cookie::new("name", "value"));
+ /// assert_eq!(private_jar.get("name").unwrap().value(), "value");
+ /// ```
+ pub fn get(&self, name: &str) -> Option<Cookie<'static>> {
+ self.parent.borrow().get(name).and_then(|c| self.decrypt(c.clone()))
+ }
+}
+
+impl<J: BorrowMut<CookieJar>> PrivateJar<J> {
+ /// Adds `cookie` to the parent jar. The cookie's value is encrypted with
+ /// authenticated encryption assuring confidentiality, integrity, and
+ /// authenticity.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::{CookieJar, Cookie, Key};
+ ///
+ /// let key = Key::generate();
+ /// let mut jar = CookieJar::new();
+ /// jar.private_mut(&key).add(Cookie::new("name", "value"));
+ ///
+ /// assert_ne!(jar.get("name").unwrap().value(), "value");
+ /// assert_eq!(jar.private(&key).get("name").unwrap().value(), "value");
+ /// ```
+ pub fn add(&mut self, mut cookie: Cookie<'static>) {
+ self.encrypt_cookie(&mut cookie);
+ self.parent.borrow_mut().add(cookie);
+ }
+
+ /// Adds an "original" `cookie` to parent jar. The cookie's value is
+ /// encrypted with authenticated encryption assuring confidentiality,
+ /// integrity, and authenticity. Adding an original cookie does not affect
+ /// the [`CookieJar::delta()`] computation. This method is intended to be
+ /// used to seed the cookie jar with cookies received from a client's HTTP
+ /// message.
+ ///
+ /// For accurate `delta` computations, this method should not be called
+ /// after calling `remove`.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::{CookieJar, Cookie, Key};
+ ///
+ /// let key = Key::generate();
+ /// let mut jar = CookieJar::new();
+ /// jar.private_mut(&key).add_original(Cookie::new("name", "value"));
+ ///
+ /// assert_eq!(jar.iter().count(), 1);
+ /// assert_eq!(jar.delta().count(), 0);
+ /// ```
+ pub fn add_original(&mut self, mut cookie: Cookie<'static>) {
+ self.encrypt_cookie(&mut cookie);
+ self.parent.borrow_mut().add_original(cookie);
+ }
+
+ /// Removes `cookie` from the parent jar.
+ ///
+ /// For correct removal, the passed in `cookie` must contain the same `path`
+ /// and `domain` as the cookie that was initially set.
+ ///
+ /// This is identical to [`CookieJar::remove()`]. See the method's
+ /// documentation for more details.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::{CookieJar, Cookie, Key};
+ ///
+ /// let key = Key::generate();
+ /// let mut jar = CookieJar::new();
+ /// let mut private_jar = jar.private_mut(&key);
+ ///
+ /// private_jar.add(Cookie::new("name", "value"));
+ /// assert!(private_jar.get("name").is_some());
+ ///
+ /// private_jar.remove(Cookie::named("name"));
+ /// assert!(private_jar.get("name").is_none());
+ /// ```
+ pub fn remove(&mut self, cookie: Cookie<'static>) {
+ self.parent.borrow_mut().remove(cookie);
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use crate::{CookieJar, Cookie, Key};
+
+ #[test]
+ fn simple() {
+ let key = Key::generate();
+ let mut jar = CookieJar::new();
+ assert_simple_behaviour!(jar, jar.private_mut(&key));
+ }
+
+ #[test]
+ fn secure() {
+ let key = Key::generate();
+ let mut jar = CookieJar::new();
+ assert_secure_behaviour!(jar, jar.private_mut(&key));
+ }
+
+ #[test]
+ fn roundtrip() {
+ // Secret is SHA-256 hash of 'Super secret!' passed through HKDF-SHA256.
+ let key = Key::from(&[89, 202, 200, 125, 230, 90, 197, 245, 166, 249,
+ 34, 169, 135, 31, 20, 197, 94, 154, 254, 79, 60, 26, 8, 143, 254,
+ 24, 116, 138, 92, 225, 159, 60, 157, 41, 135, 129, 31, 226, 196, 16,
+ 198, 168, 134, 4, 42, 1, 196, 24, 57, 103, 241, 147, 201, 185, 233,
+ 10, 180, 170, 187, 89, 252, 137, 110, 107]);
+
+ let mut jar = CookieJar::new();
+ jar.add(Cookie::new("encrypted_with_ring014",
+ "lObeZJorGVyeSWUA8khTO/8UCzFVBY9g0MGU6/J3NN1R5x11dn2JIA=="));
+ jar.add(Cookie::new("encrypted_with_ring016",
+ "SU1ujceILyMBg3fReqRmA9HUtAIoSPZceOM/CUpObROHEujXIjonkA=="));
+
+ let private = jar.private(&key);
+ assert_eq!(private.get("encrypted_with_ring014").unwrap().value(), "Tamper-proof");
+ assert_eq!(private.get("encrypted_with_ring016").unwrap().value(), "Tamper-proof");
+ }
+}
diff --git a/third_party/rust/cookie/src/secure/signed.rs b/third_party/rust/cookie/src/secure/signed.rs
new file mode 100644
index 0000000000..b46fae4e80
--- /dev/null
+++ b/third_party/rust/cookie/src/secure/signed.rs
@@ -0,0 +1,251 @@
+use std::convert::TryInto;
+use std::borrow::{Borrow, BorrowMut};
+
+use sha2::Sha256;
+use hmac::{Hmac, Mac};
+
+use crate::secure::{base64, Key};
+use crate::{Cookie, CookieJar};
+
+// Keep these in sync, and keep the key len synced with the `signed` docs as
+// well as the `KEYS_INFO` const in secure::Key.
+pub(crate) const BASE64_DIGEST_LEN: usize = 44;
+pub(crate) const KEY_LEN: usize = 32;
+
+/// A child cookie jar that authenticates its cookies.
+///
+/// A _signed_ child jar signs all the cookies added to it and verifies cookies
+/// retrieved from it. Any cookies stored in a `SignedJar` are provided
+/// integrity and authenticity. In other words, clients cannot tamper with the
+/// contents of a cookie nor can they fabricate cookie values, but the data is
+/// visible in plaintext.
+#[cfg_attr(all(nightly, doc), doc(cfg(feature = "signed")))]
+pub struct SignedJar<J> {
+ parent: J,
+ key: [u8; KEY_LEN],
+}
+
+impl<J> SignedJar<J> {
+ /// Creates a new child `SignedJar` with parent `parent` and key `key`. This
+ /// method is typically called indirectly via the `signed{_mut}` methods of
+ /// `CookieJar`.
+ pub(crate) fn new(parent: J, key: &Key) -> SignedJar<J> {
+ SignedJar { parent, key: key.signing().try_into().expect("sign key len") }
+ }
+
+ /// Signs the cookie's value providing integrity and authenticity.
+ fn sign_cookie(&self, cookie: &mut Cookie) {
+ // Compute HMAC-SHA256 of the cookie's value.
+ let mut mac = Hmac::<Sha256>::new_from_slice(&self.key).expect("good key");
+ mac.update(cookie.value().as_bytes());
+
+ // Cookie's new value is [MAC | original-value].
+ let mut new_value = base64::encode(&mac.finalize().into_bytes());
+ new_value.push_str(cookie.value());
+ cookie.set_value(new_value);
+ }
+
+ /// Given a signed value `str` where the signature is prepended to `value`,
+ /// verifies the signed value and returns it. If there's a problem, returns
+ /// an `Err` with a string describing the issue.
+ fn _verify(&self, cookie_value: &str) -> Result<String, &'static str> {
+ if !cookie_value.is_char_boundary(BASE64_DIGEST_LEN) {
+ return Err("missing or invalid digest");
+ }
+
+ // Split [MAC | original-value] into its two parts.
+ let (digest_str, value) = cookie_value.split_at(BASE64_DIGEST_LEN);
+ let digest = base64::decode(digest_str).map_err(|_| "bad base64 digest")?;
+
+ // Perform the verification.
+ let mut mac = Hmac::<Sha256>::new_from_slice(&self.key).expect("good key");
+ mac.update(value.as_bytes());
+ mac.verify_slice(&digest)
+ .map(|_| value.to_string())
+ .map_err(|_| "value did not verify")
+ }
+
+ /// Verifies the authenticity and integrity of `cookie`, returning the
+ /// plaintext version if verification succeeds or `None` otherwise.
+ /// Verification _always_ succeeds if `cookie` was generated by a
+ /// `SignedJar` with the same key as `self`.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::{CookieJar, Cookie, Key};
+ ///
+ /// let key = Key::generate();
+ /// let mut jar = CookieJar::new();
+ /// assert!(jar.signed(&key).get("name").is_none());
+ ///
+ /// jar.signed_mut(&key).add(Cookie::new("name", "value"));
+ /// assert_eq!(jar.signed(&key).get("name").unwrap().value(), "value");
+ ///
+ /// let plain = jar.get("name").cloned().unwrap();
+ /// assert_ne!(plain.value(), "value");
+ /// let verified = jar.signed(&key).verify(plain).unwrap();
+ /// assert_eq!(verified.value(), "value");
+ ///
+ /// let plain = Cookie::new("plaintext", "hello");
+ /// assert!(jar.signed(&key).verify(plain).is_none());
+ /// ```
+ pub fn verify(&self, mut cookie: Cookie<'static>) -> Option<Cookie<'static>> {
+ if let Ok(value) = self._verify(cookie.value()) {
+ cookie.set_value(value);
+ return Some(cookie);
+ }
+
+ None
+ }
+}
+
+impl<J: Borrow<CookieJar>> SignedJar<J> {
+ /// Returns a reference to the `Cookie` inside this jar with the name `name`
+ /// and verifies the authenticity and integrity of the cookie's value,
+ /// returning a `Cookie` with the authenticated value. If the cookie cannot
+ /// be found, or the cookie fails to verify, `None` is returned.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::{CookieJar, Cookie, Key};
+ ///
+ /// let key = Key::generate();
+ /// let jar = CookieJar::new();
+ /// assert!(jar.signed(&key).get("name").is_none());
+ ///
+ /// let mut jar = jar;
+ /// let mut signed_jar = jar.signed_mut(&key);
+ /// signed_jar.add(Cookie::new("name", "value"));
+ /// assert_eq!(signed_jar.get("name").unwrap().value(), "value");
+ /// ```
+ pub fn get(&self, name: &str) -> Option<Cookie<'static>> {
+ self.parent.borrow().get(name).and_then(|c| self.verify(c.clone()))
+ }
+}
+
+impl<J: BorrowMut<CookieJar>> SignedJar<J> {
+ /// Adds `cookie` to the parent jar. The cookie's value is signed assuring
+ /// integrity and authenticity.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::{CookieJar, Cookie, Key};
+ ///
+ /// let key = Key::generate();
+ /// let mut jar = CookieJar::new();
+ /// jar.signed_mut(&key).add(Cookie::new("name", "value"));
+ ///
+ /// assert_ne!(jar.get("name").unwrap().value(), "value");
+ /// assert!(jar.get("name").unwrap().value().contains("value"));
+ /// assert_eq!(jar.signed(&key).get("name").unwrap().value(), "value");
+ /// ```
+ pub fn add(&mut self, mut cookie: Cookie<'static>) {
+ self.sign_cookie(&mut cookie);
+ self.parent.borrow_mut().add(cookie);
+ }
+
+ /// Adds an "original" `cookie` to this jar. The cookie's value is signed
+ /// assuring integrity and authenticity. Adding an original cookie does not
+ /// affect the [`CookieJar::delta()`] computation. This method is intended
+ /// to be used to seed the cookie jar with cookies received from a client's
+ /// HTTP message.
+ ///
+ /// For accurate `delta` computations, this method should not be called
+ /// after calling `remove`.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::{CookieJar, Cookie, Key};
+ ///
+ /// let key = Key::generate();
+ /// let mut jar = CookieJar::new();
+ /// jar.signed_mut(&key).add_original(Cookie::new("name", "value"));
+ ///
+ /// assert_eq!(jar.iter().count(), 1);
+ /// assert_eq!(jar.delta().count(), 0);
+ /// ```
+ pub fn add_original(&mut self, mut cookie: Cookie<'static>) {
+ self.sign_cookie(&mut cookie);
+ self.parent.borrow_mut().add_original(cookie);
+ }
+
+ /// Removes `cookie` from the parent jar.
+ ///
+ /// For correct removal, the passed in `cookie` must contain the same `path`
+ /// and `domain` as the cookie that was initially set.
+ ///
+ /// This is identical to [`CookieJar::remove()`]. See the method's
+ /// documentation for more details.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use cookie::{CookieJar, Cookie, Key};
+ ///
+ /// let key = Key::generate();
+ /// let mut jar = CookieJar::new();
+ /// let mut signed_jar = jar.signed_mut(&key);
+ ///
+ /// signed_jar.add(Cookie::new("name", "value"));
+ /// assert!(signed_jar.get("name").is_some());
+ ///
+ /// signed_jar.remove(Cookie::named("name"));
+ /// assert!(signed_jar.get("name").is_none());
+ /// ```
+ pub fn remove(&mut self, cookie: Cookie<'static>) {
+ self.parent.borrow_mut().remove(cookie);
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use crate::{CookieJar, Cookie, Key};
+
+ #[test]
+ fn simple() {
+ let key = Key::generate();
+ let mut jar = CookieJar::new();
+ assert_simple_behaviour!(jar, jar.signed_mut(&key));
+ }
+
+ #[test]
+ fn private() {
+ let key = Key::generate();
+ let mut jar = CookieJar::new();
+ assert_secure_behaviour!(jar, jar.signed_mut(&key));
+ }
+
+ #[test]
+ fn roundtrip() {
+ // Secret is SHA-256 hash of 'Super secret!' passed through HKDF-SHA256.
+ let key = Key::from(&[89, 202, 200, 125, 230, 90, 197, 245, 166, 249,
+ 34, 169, 135, 31, 20, 197, 94, 154, 254, 79, 60, 26, 8, 143, 254,
+ 24, 116, 138, 92, 225, 159, 60, 157, 41, 135, 129, 31, 226, 196, 16,
+ 198, 168, 134, 4, 42, 1, 196, 24, 57, 103, 241, 147, 201, 185, 233,
+ 10, 180, 170, 187, 89, 252, 137, 110, 107]);
+
+ let mut jar = CookieJar::new();
+ jar.add(Cookie::new("signed_with_ring014",
+ "3tdHXEQ2kf6fxC7dWzBGmpSLMtJenXLKrZ9cHkSsl1w=Tamper-proof"));
+ jar.add(Cookie::new("signed_with_ring016",
+ "3tdHXEQ2kf6fxC7dWzBGmpSLMtJenXLKrZ9cHkSsl1w=Tamper-proof"));
+
+ let signed = jar.signed(&key);
+ assert_eq!(signed.get("signed_with_ring014").unwrap().value(), "Tamper-proof");
+ assert_eq!(signed.get("signed_with_ring016").unwrap().value(), "Tamper-proof");
+ }
+
+ #[test]
+ fn issue_178() {
+ let data = "x=yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy£";
+ let c = Cookie::parse(data).expect("failed to parse cookie");
+ let key = Key::from(&[0u8; 64]);
+ let mut jar = CookieJar::new();
+ let signed = jar.signed_mut(&key);
+ assert!(signed.verify(c).is_none());
+ }
+}