diff --git a/src/builder.rs b/src/builder.rs index 34505ac1..6c3a6263 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -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. /// diff --git a/src/lib.rs b/src/lib.rs index f9a1689b..f666d2c3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -234,6 +234,8 @@ pub struct Cookie<'c> { http_only: Option, /// The draft `SameSite` attribute. same_site: Option, + /// The draft `Partitioned` attribute. + partitioned: Option, } impl<'c> Cookie<'c> { @@ -266,6 +268,7 @@ impl<'c> Cookie<'c> { secure: None, http_only: None, same_site: None, + partitioned: None, } } @@ -470,6 +473,7 @@ impl<'c> Cookie<'c> { secure: self.secure, http_only: self.http_only, same_site: self.same_site, + partitioned: self.partitioned, } } @@ -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 { + self.partitioned + } + /// Returns the specified max-age of the cookie if one was specified. /// /// # Example @@ -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>>(&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. /// @@ -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")?; } @@ -1532,6 +1613,7 @@ impl<'a, 'b> PartialEq> 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(); @@ -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(); diff --git a/src/parse.rs b/src/parse.rs index 802d5ab8..b263d915 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -134,7 +134,8 @@ fn parse_inner<'c>(s: &str, decode: bool) -> Result, ParseError> { path: None, secure: None, http_only: None, - same_site: None + same_site: None, + partitioned: None, }; for attr in attributes { @@ -187,6 +188,7 @@ fn parse_inner<'c>(s: &str, decode: bool) -> Result, 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)) @@ -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);