Skip to content

Commit

Permalink
Add support for CHIPS 'partitioned' attribute.
Browse files Browse the repository at this point in the history
  • Loading branch information
finnbear authored and SergioBenitez committed Dec 18, 2023
1 parent 4e48da0 commit 273e397
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 5 deletions.
26 changes: 26 additions & 0 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,32 @@ impl<'c> CookieBuilder<'c> {
self
}

/// Sets the `partitioned` field in the cookie being built.
///
/// **Note:** _Partitioned_ cookies require the `Secure` attribute to be
/// set. As such, `Partitioned` cookies are always rendered with the
/// `Secure` attribute, irrespective of the `Secure` attribute's setting.
///
/// **Note:** This cookie attribute is an [HTTP draft]! Its meaning and
/// definition are not standardized and therefore subject to change.
///
/// [HTTP draft]: https://www.ietf.org/id/draft-cutler-httpbis-partitioned-cookies-01.html
///
/// # Example
///
/// ```rust
/// use cookie::Cookie;
///
/// let c = Cookie::build(("foo", "bar")).partitioned(true);
/// assert_eq!(c.inner().partitioned(), Some(true));
/// assert!(c.to_string().contains("Secure"));
/// ```
#[inline]
pub fn partitioned(mut self, value: bool) -> Self {
self.cookie.set_partitioned(value);
self
}

/// Makes the cookie being built 'permanent' by extending its expiration and
/// max age 20 years into the future.
///
Expand Down
102 changes: 98 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,8 @@ pub struct Cookie<'c> {
http_only: Option<bool>,
/// The draft `SameSite` attribute.
same_site: Option<SameSite>,
/// The draft `Partitioned` attribute.
partitioned: Option<bool>,
}

impl<'c> Cookie<'c> {
Expand Down Expand Up @@ -266,6 +268,7 @@ impl<'c> Cookie<'c> {
secure: None,
http_only: None,
same_site: None,
partitioned: None,
}
}

Expand Down Expand Up @@ -470,6 +473,7 @@ impl<'c> Cookie<'c> {
secure: self.secure,
http_only: self.http_only,
same_site: self.same_site,
partitioned: self.partitioned,
}
}

Expand Down Expand Up @@ -666,6 +670,43 @@ impl<'c> Cookie<'c> {
self.same_site
}

/// Returns whether this cookie was marked `Partitioned` or not. Returns
/// `Some(true)` when the cookie was explicitly set (manually or parsed) as
/// `Partitioned`, `Some(false)` when `partitioned` was manually set to `false`,
/// and `None` otherwise.
///
/// **Note:** This cookie attribute is an [HTTP draft]! Its meaning and
/// definition are not standardized and therefore subject to change.
///
/// [HTTP draft]: https://www.ietf.org/id/draft-cutler-httpbis-partitioned-cookies-01.html
///
/// # Example
///
/// ```
/// use cookie::Cookie;
///
/// let c = Cookie::parse("name=value; Partitioned").unwrap();
/// assert_eq!(c.partitioned(), Some(true));
///
/// let mut c = Cookie::parse("name=value").unwrap();
/// assert_eq!(c.partitioned(), None);
///
/// let mut c = Cookie::new("name", "value");
/// assert_eq!(c.partitioned(), None);
///
/// // An explicitly set "false" value.
/// c.set_partitioned(false);
/// assert_eq!(c.partitioned(), Some(false));
///
/// // An explicitly set "true" value.
/// c.set_partitioned(true);
/// assert_eq!(c.partitioned(), Some(true));
/// ```
#[inline]
pub fn partitioned(&self) -> Option<bool> {
self.partitioned
}

/// Returns the specified max-age of the cookie if one was specified.
///
/// # Example
Expand Down Expand Up @@ -914,6 +955,43 @@ impl<'c> Cookie<'c> {
self.same_site = value.into();
}

/// Sets the value of `partitioned` in `self` to `value`. If `value` is
/// `None`, the field is unset.
///
/// **Note:** _Partitioned_ cookies require the `Secure` attribute to be
/// set. As such, `Partitioned` cookies are always rendered with the
/// `Secure` attribute, irrespective of the `Secure` attribute's setting.
///
/// **Note:** This cookie attribute is an [HTTP draft]! Its meaning and
/// definition are not standardized and therefore subject to change.
///
/// [HTTP draft]: https://www.ietf.org/id/draft-cutler-httpbis-partitioned-cookies-01.html
///
/// # Example
///
/// ```
/// use cookie::Cookie;
///
/// let mut c = Cookie::new("name", "value");
/// assert_eq!(c.partitioned(), None);
///
/// c.set_partitioned(true);
/// assert_eq!(c.partitioned(), Some(true));
/// assert!(c.to_string().contains("Secure"));
///
/// c.set_partitioned(false);
/// assert_eq!(c.partitioned(), Some(false));
/// assert!(!c.to_string().contains("Secure"));
///
/// c.set_partitioned(None);
/// assert_eq!(c.partitioned(), None);
/// assert!(!c.to_string().contains("Secure"));
/// ```
#[inline]
pub fn set_partitioned<T: Into<Option<bool>>>(&mut self, value: T) {
self.partitioned = value.into();
}

/// Sets the value of `max_age` in `self` to `value`. If `value` is `None`,
/// the field is unset.
///
Expand Down Expand Up @@ -1124,13 +1202,16 @@ impl<'c> Cookie<'c> {

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.partitioned() {
write!(f, "; Partitioned")?;
}

if let Some(true) = self.secure() {
if self.secure() == Some(true)
|| self.partitioned() == Some(true)
|| self.secure().is_none() && self.same_site() == Some(SameSite::None)
{
write!(f, "; Secure")?;
}

Expand Down Expand Up @@ -1532,6 +1613,7 @@ impl<'a, 'b> PartialEq<Cookie<'b>> for Cookie<'a> {
&& self.value() == other.value()
&& self.http_only() == other.http_only()
&& self.secure() == other.secure()
&& self.partitioned() == other.partitioned()
&& self.max_age() == other.max_age()
&& self.expires() == other.expires();

Expand Down Expand Up @@ -1646,7 +1728,19 @@ mod tests {
let mut cookie = Cookie::build(("foo", "bar")).same_site(SameSite::None).build();
assert_eq!(&cookie.to_string(), "foo=bar; SameSite=None; Secure");

cookie.set_partitioned(true);
assert_eq!(&cookie.to_string(), "foo=bar; SameSite=None; Partitioned; Secure");

cookie.set_same_site(None);
assert_eq!(&cookie.to_string(), "foo=bar; Partitioned; Secure");

cookie.set_secure(false);
assert_eq!(&cookie.to_string(), "foo=bar; Partitioned; Secure");

cookie.set_secure(None);
assert_eq!(&cookie.to_string(), "foo=bar; Partitioned; Secure");

cookie.set_partitioned(None);
assert_eq!(&cookie.to_string(), "foo=bar");

let mut c = Cookie::build(("foo", "bar")).same_site(SameSite::None).secure(false).build();
Expand Down
9 changes: 8 additions & 1 deletion src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,8 @@ fn parse_inner<'c>(s: &str, decode: bool) -> Result<Cookie<'c>, ParseError> {
path: None,
secure: None,
http_only: None,
same_site: None
same_site: None,
partitioned: None,
};

for attr in attributes {
Expand Down Expand Up @@ -187,6 +188,7 @@ fn parse_inner<'c>(s: &str, decode: bool) -> Result<Cookie<'c>, ParseError> {
// http://httpwg.org/http-extensions/draft-ietf-httpbis-cookie-same-site.html.
}
}
("partitioned", _) => cookie.partitioned = Some(true),
("expires", Some(v)) => {
let tm = parse_date(v, &FMT1)
.or_else(|_| parse_date(v, &FMT2))
Expand Down Expand Up @@ -312,6 +314,11 @@ mod tests {
assert_ne_parse!("foo=\" bar\"\"", expected);
assert_ne_parse!("foo=\" bar\" \" ", expected);

let expected = Cookie::build(("foo", "bar")).partitioned(true).build();
assert_eq_parse!("foo=bar; partitioned", expected);
assert_eq_parse!("foo=bar; Partitioned", expected);
assert_eq_parse!("foo=bar; PARTITIONED", expected);

let mut expected = Cookie::new("foo", "bar");
assert_eq_parse!("foo=bar", expected);
assert_eq_parse!("foo = bar", expected);
Expand Down

0 comments on commit 273e397

Please sign in to comment.