Implement logic to convert a RFC 850 two-digit year into a full length year, and organize time parsing related functions

This commit is contained in:
kevinpoitra 2020-01-07 16:47:41 -06:00
parent dca8e23b4d
commit 34bbf75ecc
4 changed files with 53 additions and 14 deletions

View File

@ -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<Cookie<'c>, 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))
}
}

View File

@ -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<HttpDate, ParseError> {
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<OffsetDateTime> for HttpDate {
fn from(dt: time::OffsetDateTime) -> HttpDate {
fn from(dt: OffsetDateTime) -> HttpDate {
HttpDate(dt)
}
}

View File

@ -27,6 +27,7 @@ mod payload;
mod request;
mod response;
mod service;
mod time_parser;
pub mod cookie;
pub mod error;

View File

@ -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<PrimitiveDateTime> {
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> {
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<PrimitiveDateTime> {
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> {
PrimitiveDateTime::parse(time, "%c").ok()
}