diff --git a/src/lib.rs b/src/lib.rs index d84d9792..0b322576 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -79,9 +79,6 @@ #![doc(html_root_url = "https://docs.rs/cookie/0.16")] #![deny(missing_docs)] -#[cfg(feature = "percent-encode")] extern crate percent_encoding; -extern crate time; - mod builder; mod parse; mod jar; @@ -101,7 +98,7 @@ use std::ascii::AsciiExt; #[cfg(feature = "percent-encode")] use percent_encoding::{AsciiSet, percent_encode as encode}; -use time::{Duration, OffsetDateTime, UtcOffset}; +use time::{Duration, OffsetDateTime, UtcOffset, macros::datetime}; use crate::parse::parse_cookie; pub use crate::parse::ParseError; @@ -826,11 +823,7 @@ impl<'c> Cookie<'c> { /// assert_eq!(c.expires(), Some(Expiration::Session)); /// ``` pub fn set_expires>(&mut self, time: T) { - use time::macros::{date, time, offset}; - static MAX_DATETIME: OffsetDateTime = date!(9999-12-31) - .with_time(time!(23:59:59.999_999)) - .assume_utc() - .to_offset(offset!(UTC)); + 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() @@ -945,9 +938,7 @@ impl<'c> Cookie<'c> { if let Some(time) = self.expires_datetime() { let time = time.to_offset(UtcOffset::UTC); - write!(f, "; Expires={}", time.format(&time::macros::format_description!( - "[weekday repr:short], [day] [month repr:short] [year] [hour]:[minute]:[second] GMT" - )).unwrap())?; + write!(f, "; Expires={}", time.format(&crate::parse::FMT1).map_err(|_| fmt::Error)?)?; } Ok(()) @@ -1292,10 +1283,8 @@ impl<'a, 'b> PartialEq> for Cookie<'a> { #[cfg(test)] mod tests { - use crate::{Cookie, SameSite}; - use crate::parse::parse_gmt_date; - use crate::{time::Duration, OffsetDateTime}; - use crate::{time::macros::format_description}; + use crate::{Cookie, SameSite, parse::parse_date}; + use time::{Duration, OffsetDateTime}; #[test] fn format() { @@ -1323,10 +1312,7 @@ mod tests { 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_gmt_date( - time_str, - &format_description!("[weekday repr:short], [day] [month repr:short] [year] [hour]:[minute]:[second] GMT") - ).unwrap(); + let expires = parse_date(time_str, &crate::parse::FMT1).unwrap(); let cookie = Cookie::build("foo", "bar") .expires(expires).finish(); assert_eq!(&cookie.to_string(), @@ -1360,10 +1346,12 @@ mod tests { #[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 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] diff --git a/src/parse.rs b/src/parse.rs index a85f6864..dcd15b7b 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -1,21 +1,26 @@ use std::borrow::Cow; use std::error::Error; +use std::convert::{From, TryFrom}; use std::str::Utf8Error; use std::fmt; -use std::convert::{From, TryFrom}; #[allow(unused_imports, deprecated)] use std::ascii::AsciiExt; #[cfg(feature = "percent-encode")] use percent_encoding::percent_decode; -use time::{Duration, OffsetDateTime, PrimitiveDateTime, UtcOffset}; -use time::error::Parse; -use time::parsing::Parsed; -use time::macros::format_description; +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] @@ -222,22 +227,11 @@ fn parse_inner<'c>(s: &str, decode: bool) -> Result, ParseError> { } } ("expires", Some(v)) => { - // Try strptime with three date formats according to - // http://tools.ietf.org/html/rfc2616#section-3.3.1. Try - // additional ones as encountered in the real world. - let tm = parse_gmt_date( - v, - &format_description!("[weekday repr:short], [day] [month repr:short] [year] [hour]:[minute]:[second] GMT") - ).or_else(|_| parse_gmt_date( - v, - &format_description!("[weekday repr:short], [day]-[month repr:short]-[year repr:last_two] [hour]:[minute]:[second] GMT") - )).or_else(|_| parse_gmt_date( - v, - &format_description!("[weekday repr:short], [day]-[month repr:short]-[year] [hour]:[minute]:[second] GMT") - )).or_else(|_| parse_gmt_date( - v, - &format_description!("[weekday repr:short] [month repr:short] [day] [hour]:[minute]:[second] [year]") - )); + 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()) @@ -264,39 +258,27 @@ pub(crate) fn parse_cookie<'c, S>(cow: S, decode: bool) -> Result, Pa Ok(cookie) } -pub(crate) fn parse_gmt_date(s: &str, format: &T) -> Result -where - T: time::parsing::Parsable, -{ - format.parse(s.as_bytes()) - .map(|mut parsed| { - // Handle malformed "abbreviated" dates like Chromium. See cookie#162. - parsed.year_last_two() - .map(|y| { - let y = y as i32; - let offset = match y { - 0..=68 => 2000, - 69..=99 => 1900, - _ => 0, - }; - - parsed.set_year(y + offset) - }); - - parsed - }) - .and_then(|parsed| >::try_from(parsed) - .map_err(|err| Parse::TryFromParsed(err)) - ) - .map(|t| t.assume_utc().to_offset(UtcOffset::UTC)) +pub(crate) fn parse_date(s: &str, format: &impl Parsable) -> Result { + // 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 super::parse_gmt_date; - use ::time::Duration; - use ::time::macros::format_description; + use time::Duration; macro_rules! assert_eq_parse { ($string:expr, $expected:expr) => ( @@ -468,19 +450,13 @@ mod tests { Domain=FOO.COM", unexpected); let time_str = "Wed, 21 Oct 2015 07:28:00 GMT"; - let expires = parse_gmt_date( - time_str, - &format_description!("[weekday repr:short], [day] [month repr:short] [year] [hour]:[minute]:[second] GMT") - ).unwrap(); + 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_gmt_date( - time_str, - &format_description!("[weekday repr:short], [day] [month repr:short] [year] [hour]:[minute]:[minute] GMT") - ).unwrap(); + 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); @@ -503,6 +479,22 @@ mod tests { 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]