From 6598097eae1e458fdf0968c6a19123d3aaaa6d75 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Fri, 19 May 2023 15:47:30 +0200 Subject: [PATCH 1/3] Replace `86_400` with `MAX_RFC3339_OFFSET` --- src/format/parse.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index 04628ac861..40bc7c8e85 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -216,7 +216,8 @@ pub(crate) fn parse_rfc3339<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseRes } let offset = try_consume!(scan::timezone_offset(s, |s| scan::char(s, b':'), true, false, true)); - if offset <= -86_400 || offset >= 86_400 { + const MAX_RFC3339_OFFSET: i32 = 86_400; + if !(-MAX_RFC3339_OFFSET..=MAX_RFC3339_OFFSET).contains(&offset) { return Err(OUT_OF_RANGE); } parsed.set_offset(i64::from(offset))?; From 76af9a0db536b9ed8569d9cd2b64a03af7f08591 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Wed, 7 Feb 2024 13:07:55 +0100 Subject: [PATCH 2/3] Make range check more accurate, and add comment why it is not useless --- src/format/parse.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index 40bc7c8e85..dea3d91fe0 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -216,7 +216,11 @@ pub(crate) fn parse_rfc3339<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseRes } let offset = try_consume!(scan::timezone_offset(s, |s| scan::char(s, b':'), true, false, true)); - const MAX_RFC3339_OFFSET: i32 = 86_400; + // This range check is similar to the one in `FixedOffset::east_opt`, so it would be redundant. + // But it is possible to read the offset directly from `Parsed`. We want to only successfully + // populate `Parsed` if the input is fully valid RFC 3339. + // Max for the hours field is `23`, and for the minutes field `59`. + const MAX_RFC3339_OFFSET: i32 = (23 * 60 + 59) * 60; if !(-MAX_RFC3339_OFFSET..=MAX_RFC3339_OFFSET).contains(&offset) { return Err(OUT_OF_RANGE); } From 18d8459f64a372bc7f7746365520075f664752b8 Mon Sep 17 00:00:00 2001 From: Paul Dicker Date: Sun, 4 Feb 2024 17:02:47 +0100 Subject: [PATCH 3/3] Remove incomplete parsing support for `-0000` offset --- src/format/parse.rs | 14 +++++--------- src/format/scan.rs | 44 +++++++++++++++++++++++--------------------- 2 files changed, 28 insertions(+), 30 deletions(-) diff --git a/src/format/parse.rs b/src/format/parse.rs index dea3d91fe0..05ebcf3119 100644 --- a/src/format/parse.rs +++ b/src/format/parse.rs @@ -142,10 +142,7 @@ fn parse_rfc2822<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult<(&'a st } s = scan::space(s)?; // mandatory - if let Some(offset) = try_consume!(scan::timezone_offset_2822(s)) { - // only set the offset when it is definitely known (i.e. not `-0000`) - parsed.set_offset(i64::from(offset))?; - } + parsed.set_offset(i64::from(try_consume!(scan::timezone_offset_2822(s))))?; // optional comments while let Ok((s_out, ())) = scan::comment_2822(s) { @@ -1644,7 +1641,6 @@ mod tests { ("Tue, 20 Jan 2015 17:35:90 -0800", Err(OUT_OF_RANGE)), // bad second ("Tue, 20 Jan 2015 17:35:20 -0890", Err(OUT_OF_RANGE)), // bad offset ("6 Jun 1944 04:00:00Z", Err(INVALID)), // bad offset (zulu not allowed) - ("Tue, 20 Jan 2015 17:35:20 HAS", Err(NOT_ENOUGH)), // bad named time zone // named timezones that have specific timezone offsets // see https://www.rfc-editor.org/rfc/rfc2822#section-4.3 ("Tue, 20 Jan 2015 17:35:20 GMT", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, 0))), @@ -1666,14 +1662,14 @@ mod tests { ("Tue, 20 Jan 2015 17:35:20 K", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, 0))), ("Tue, 20 Jan 2015 17:35:20 k", Ok(ymd_hmsn(2015, 1, 20, 17, 35, 20, 0, 0))), // named single-letter timezone "J" is specifically not valid - ("Tue, 20 Jan 2015 17:35:20 J", Err(NOT_ENOUGH)), + ("Tue, 20 Jan 2015 17:35:20 J", Err(INVALID)), ("Tue, 20 Jan 2015 17:35:20 -0890", Err(OUT_OF_RANGE)), // bad offset minutes ("Tue, 20 Jan 2015 17:35:20Z", Err(INVALID)), // bad offset: zulu not allowed - ("Tue, 20 Jan 2015 17:35:20 Zulu", Err(NOT_ENOUGH)), // bad offset: zulu not allowed - ("Tue, 20 Jan 2015 17:35:20 ZULU", Err(NOT_ENOUGH)), // bad offset: zulu not allowed + ("Tue, 20 Jan 2015 17:35:20 Zulu", Err(INVALID)), // bad offset: zulu not allowed + ("Tue, 20 Jan 2015 17:35:20 ZULU", Err(INVALID)), // bad offset: zulu not allowed ("Tue, 20 Jan 2015 17:35:20 −0800", Err(INVALID)), // bad offset: timezone offset using MINUS SIGN (U+2212), not specified for RFC 2822 ("Tue, 20 Jan 2015 17:35:20 0800", Err(INVALID)), // missing offset sign - ("Tue, 20 Jan 2015 17:35:20 HAS", Err(NOT_ENOUGH)), // bad named timezone + ("Tue, 20 Jan 2015 17:35:20 HAS", Err(INVALID)), // bad named timezone ("Tue, 20 Jan 2015😈17:35:20 -0800", Err(INVALID)), // bad character! ]; diff --git a/src/format/scan.rs b/src/format/scan.rs index 45b5bcbec0..1ab87b9dd5 100644 --- a/src/format/scan.rs +++ b/src/format/scan.rs @@ -287,37 +287,39 @@ where /// See [RFC 2822 Section 4.3]. /// /// [RFC 2822 Section 4.3]: https://tools.ietf.org/html/rfc2822#section-4.3 -pub(super) fn timezone_offset_2822(s: &str) -> ParseResult<(&str, Option)> { +pub(super) fn timezone_offset_2822(s: &str) -> ParseResult<(&str, i32)> { // tries to parse legacy time zone names let upto = s.as_bytes().iter().position(|&c| !c.is_ascii_alphabetic()).unwrap_or(s.len()); if upto > 0 { let name = &s.as_bytes()[..upto]; let s = &s[upto..]; - let offset_hours = |o| Ok((s, Some(o * 3600))); - if name.eq_ignore_ascii_case(b"gmt") || name.eq_ignore_ascii_case(b"ut") { - offset_hours(0) + let offset_hours = |o| Ok((s, o * 3600)); + // RFC 2822 requires support for some named North America timezones, a small subset of all + // named timezones. + if name.eq_ignore_ascii_case(b"gmt") + || name.eq_ignore_ascii_case(b"ut") + || name.eq_ignore_ascii_case(b"z") + { + return offset_hours(0); } else if name.eq_ignore_ascii_case(b"edt") { - offset_hours(-4) + return offset_hours(-4); } else if name.eq_ignore_ascii_case(b"est") || name.eq_ignore_ascii_case(b"cdt") { - offset_hours(-5) + return offset_hours(-5); } else if name.eq_ignore_ascii_case(b"cst") || name.eq_ignore_ascii_case(b"mdt") { - offset_hours(-6) + return offset_hours(-6); } else if name.eq_ignore_ascii_case(b"mst") || name.eq_ignore_ascii_case(b"pdt") { - offset_hours(-7) + return offset_hours(-7); } else if name.eq_ignore_ascii_case(b"pst") { - offset_hours(-8) + return offset_hours(-8); } else if name.len() == 1 { - match name[0] { + if let b'a'..=b'i' | b'k'..=b'y' | b'A'..=b'I' | b'K'..=b'Y' = name[0] { // recommended by RFC 2822: consume but treat it as -0000 - b'a'..=b'i' | b'k'..=b'z' | b'A'..=b'I' | b'K'..=b'Z' => offset_hours(0), - _ => Ok((s, None)), + return Ok((s, 0)); } - } else { - Ok((s, None)) } + Err(INVALID) } else { - let (s_, offset) = timezone_offset(s, |s| Ok(s), false, false, false)?; - Ok((s_, Some(offset))) + timezone_offset(s, |s| Ok(s), false, false, false) } } @@ -396,11 +398,11 @@ mod tests { #[test] fn test_timezone_offset_2822() { - assert_eq!(timezone_offset_2822("cSt").unwrap(), ("", Some(-21600))); - assert_eq!(timezone_offset_2822("pSt").unwrap(), ("", Some(-28800))); - assert_eq!(timezone_offset_2822("mSt").unwrap(), ("", Some(-25200))); - assert_eq!(timezone_offset_2822("-1551").unwrap(), ("", Some(-57060))); - assert_eq!(timezone_offset_2822("Gp").unwrap(), ("", None)); + assert_eq!(timezone_offset_2822("cSt").unwrap(), ("", -21600)); + assert_eq!(timezone_offset_2822("pSt").unwrap(), ("", -28800)); + assert_eq!(timezone_offset_2822("mSt").unwrap(), ("", -25200)); + assert_eq!(timezone_offset_2822("-1551").unwrap(), ("", -57060)); + assert_eq!(timezone_offset_2822("Gp"), Err(INVALID)); } #[test]