From 34bbf75ecc0b0610ab61fbab0a9faea35787313a Mon Sep 17 00:00:00 2001 From: kevinpoitra Date: Tue, 7 Jan 2020 16:47:41 -0600 Subject: [PATCH] Implement logic to convert a RFC 850 two-digit year into a full length year, and organize time parsing related functions --- actix-http/src/cookie/parse.rs | 10 +++--- actix-http/src/header/shared/httpdate.rs | 14 +++----- actix-http/src/lib.rs | 1 + actix-http/src/time_parser.rs | 42 ++++++++++++++++++++++++ 4 files changed, 53 insertions(+), 14 deletions(-) create mode 100644 actix-http/src/time_parser.rs diff --git a/actix-http/src/cookie/parse.rs b/actix-http/src/cookie/parse.rs index 00f50293e..58843ceea 100644 --- a/actix-http/src/cookie/parse.rs +++ b/actix-http/src/cookie/parse.rs @@ -10,6 +10,8 @@ use time::{Duration, PrimitiveDateTime, UtcOffset}; use super::{Cookie, CookieStr, SameSite}; +use crate::time_parser; + /// Enum corresponding to a parsing error. #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum ParseError { @@ -182,12 +184,10 @@ fn parse_inner<'c>(s: &str, decode: bool) -> Result, ParseError> { // Try parsing 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 = PrimitiveDateTime::parse(v, "%a, %d %b %Y %H:%M:%S") - .or_else(|_| PrimitiveDateTime::parse(v, "%A, %d-%b-%y %H:%M:%S")) - .or_else(|_| PrimitiveDateTime::parse(v, "%a, %d-%b-%Y %H:%M:%S")) - .or_else(|_| PrimitiveDateTime::parse(v, "%a %b %d %H:%M:%S %Y")); + let tm = time_parser::parse_http_date(v) + .or_else(|| PrimitiveDateTime::parse(v, "%a, %d-%b-%Y %H:%M:%S").ok()); - if let Ok(time) = tm { + if let Some(time) = tm { cookie.expires = Some(time.using_offset(UtcOffset::UTC)) } } diff --git a/actix-http/src/header/shared/httpdate.rs b/actix-http/src/header/shared/httpdate.rs index 77034b147..233f56aa3 100644 --- a/actix-http/src/header/shared/httpdate.rs +++ b/actix-http/src/header/shared/httpdate.rs @@ -9,6 +9,7 @@ use time::{PrimitiveDateTime, OffsetDateTime, UtcOffset}; use crate::error::ParseError; use crate::header::IntoHeaderValue; +use crate::time_parser; /// A timestamp with HTTP formatting and parsing #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] @@ -18,14 +19,9 @@ impl FromStr for HttpDate { type Err = ParseError; fn from_str(s: &str) -> Result { - match PrimitiveDateTime::parse(s, "%a, %d %b %Y %H:%M:%S") - .or_else(|_| PrimitiveDateTime::parse(s, "%A, %d-%b-%y %H:%M:%S")) - .or_else(|_| PrimitiveDateTime::parse(s, "%c")) - { - Ok(t) => Ok(HttpDate(t.using_offset(UtcOffset::UTC))), - Err(_) => { - Err(ParseError::Header) - }, + match time_parser::parse_http_date(s) { + Some(t) => Ok(HttpDate(t.using_offset(UtcOffset::UTC))), + None => Err(ParseError::Header) } } } @@ -37,7 +33,7 @@ impl Display for HttpDate { } impl From for HttpDate { - fn from(dt: time::OffsetDateTime) -> HttpDate { + fn from(dt: OffsetDateTime) -> HttpDate { HttpDate(dt) } } diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index 7a47012f8..a5ae4b447 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -27,6 +27,7 @@ mod payload; mod request; mod response; mod service; +mod time_parser; pub mod cookie; pub mod error; diff --git a/actix-http/src/time_parser.rs b/actix-http/src/time_parser.rs new file mode 100644 index 000000000..3616d2a17 --- /dev/null +++ b/actix-http/src/time_parser.rs @@ -0,0 +1,42 @@ +use time::{PrimitiveDateTime, Date}; + +/// Attempt to parse a `time` string as one of either RFC 1123, RFC 850, or asctime. +pub fn parse_http_date(time: &str) -> Option { + try_parse_rfc_1123(time) + .or_else(|| try_parse_rfc_850(time)) + .or_else(|| try_parse_asctime(time)) +} + +/// Attempt to parse a `time` string as a RFC 1123 formatted date time string. +fn try_parse_rfc_1123(time: &str) -> Option { + PrimitiveDateTime::parse(time, "%a, %d %b %Y %H:%M:%S").ok() +} + +/// Attempt to parse a `time` string as a RFC 850 formatted date time string. +fn try_parse_rfc_850(time: &str) -> Option { + match PrimitiveDateTime::parse(time, "%A, %d-%b-%y %H:%M:%S") { + Ok(dt) => { + // If the `time` string contains a two-digit year, then as per RFC 2616 ยง 19.3, + // we consider the year as part of this century if it's within the next 50 years, + // otherwise we consider as part of the previous century. + let now = PrimitiveDateTime::now(); + let century_start_year = (now.year() / 100) * 100; + let mut expanded_year = century_start_year + dt.year(); + + if expanded_year > now.year() + 50 { + expanded_year -= 100; + } + + match Date::try_from_ymd(expanded_year, dt.month(), dt.day()) { + Ok(date) => Some(PrimitiveDateTime::new(date, dt.time())), + Err(_) => None + } + } + Err(_) => None + } +} + +/// Attempt to parse a `time` string using ANSI C's `asctime` format. +fn try_parse_asctime(time: &str) -> Option { + PrimitiveDateTime::parse(time, "%c").ok() +}