Skip to content

Commit

Permalink
Constrain timezone parsing
Browse files Browse the repository at this point in the history
Constrain timezone parsing further. Only allow optional colon
char `:` between timezone hour offset and timezone minute offset.

Issue #660
  • Loading branch information
jtmoon79 authored and djc committed Apr 3, 2023
1 parent 9a1b1d1 commit 2cd60a2
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 140 deletions.
80 changes: 34 additions & 46 deletions src/datetime/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -809,11 +809,7 @@ fn test_parse_datetime_utc() {
"2001-02-03T04:05:06Z",
"2001-02-03T04:05:06+0000",
"2001-02-03T04:05:06-00:00",
"2001-02-03T04:05:06-00 00",
"2001-02-03T04:05:06-01:00",
"2001-02-03T04:05:06-01: 00",
"2001-02-03T04:05:06-01 :00",
"2001-02-03T04:05:06-01 : 00",
"2012-12-12T12:12:12Z",
"2015-02-18T23:16:09.153Z",
"2015-2-18T23:16:09.153Z",
Expand Down Expand Up @@ -885,6 +881,10 @@ fn test_parse_datetime_utc() {
"2012-12-12T12 : 12:12Z", // space space before and after hour-minute divider
"2012-12-12T12:12:12Z ", // trailing space
" 2012-12-12T12:12:12Z", // leading space
"2001-02-03T04:05:06-00 00", // invalid timezone spacing
"2001-02-03T04:05:06-01: 00", // invalid timezone spacing
"2001-02-03T04:05:06-01 :00", // invalid timezone spacing
"2001-02-03T04:05:06-01 : 00", // invalid timezone spacing
"2001-02-03T04:05:06-01 : 00", // invalid timezone spacing
"2001-02-03T04:05:06-01 : :00", // invalid timezone spacing
" +82701 - 05 - 6 T 15 : 9 : 60.898989898989 Z", // valid datetime, wrong format
Expand Down Expand Up @@ -1040,27 +1040,23 @@ fn test_datetime_parse_from_str() {
),
Ok(dt),
);
assert_eq!(
DateTime::<FixedOffset>::parse_from_str(
"Aug 09 2013 23:54:35 -09 00",
"%b %d %Y %H:%M:%S %z"
),
Ok(dt),
);
assert!(DateTime::<FixedOffset>::parse_from_str(
"Aug 09 2013 23:54:35 -09 00",
"%b %d %Y %H:%M:%S %z"
)
.is_err());
assert_eq!(
DateTime::<FixedOffset>::parse_from_str(
"Aug 09 2013 23:54:35 -09:00",
"%b %d %Y %H:%M:%S %z"
),
Ok(dt),
);
assert_eq!(
DateTime::<FixedOffset>::parse_from_str(
"Aug 09 2013 23:54:35 -09 : 00",
"%b %d %Y %H:%M:%S %z"
),
Ok(dt),
);
assert!(DateTime::<FixedOffset>::parse_from_str(
"Aug 09 2013 23:54:35 -09 : 00",
"%b %d %Y %H:%M:%S %z"
)
.is_err());
assert_eq!(
DateTime::<FixedOffset>::parse_from_str(
"Aug 09 2013 23:54:35 --0900",
Expand Down Expand Up @@ -1161,27 +1157,21 @@ fn test_datetime_parse_from_str() {
),
Ok(dt),
);
assert_eq!(
DateTime::<FixedOffset>::parse_from_str(
"Aug 09 2013 23:54:35 -09 00",
"%b %d %Y %H:%M:%S %:z"
),
Ok(dt),
);
assert_eq!(
DateTime::<FixedOffset>::parse_from_str(
"Aug 09 2013 23:54:35 -09 : 00",
"%b %d %Y %H:%M:%S %:z"
),
Ok(dt),
);
assert_eq!(
DateTime::<FixedOffset>::parse_from_str(
"Aug 09 2013 23:54:35 -09 : 00:",
"%b %d %Y %H:%M:%S %:z:"
),
Ok(dt),
);
assert!(DateTime::<FixedOffset>::parse_from_str(
"Aug 09 2013 23:54:35 -09 00",
"%b %d %Y %H:%M:%S %:z"
)
.is_err());
assert!(DateTime::<FixedOffset>::parse_from_str(
"Aug 09 2013 23:54:35 -09 : 00",
"%b %d %Y %H:%M:%S %:z"
)
.is_err());
assert!(DateTime::<FixedOffset>::parse_from_str(
"Aug 09 2013 23:54:35 -09 : 00:",
"%b %d %Y %H:%M:%S %:z:"
)
.is_err());
// wrong timezone data
assert!(DateTime::<FixedOffset>::parse_from_str(
"Aug 09 2013 23:54:35 -09",
Expand Down Expand Up @@ -1230,13 +1220,11 @@ fn test_datetime_parse_from_str() {
),
Ok(dt),
);
assert_eq!(
DateTime::<FixedOffset>::parse_from_str(
"Aug 09 2013 23:54:35 -09 : 00",
"%b %d %Y %H:%M:%S %::z"
),
Ok(dt),
);
assert!(DateTime::<FixedOffset>::parse_from_str(
"Aug 09 2013 23:54:35 -09 : 00",
"%b %d %Y %H:%M:%S %::z"
)
.is_err());
// mismatching colon expectations
assert!(DateTime::<FixedOffset>::parse_from_str(
"Aug 09 2013 23:54:35 -09:00:00",
Expand Down
42 changes: 21 additions & 21 deletions src/format/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -453,14 +453,14 @@ where
| &TimezoneOffset => {
s = scan::trim1(s);
let offset =
try_consume!(scan::timezone_offset(s, scan::maybe_colon_or_space));
try_consume!(scan::timezone_offset(s, scan::consume_colon_maybe));
parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?;
}

&TimezoneOffsetColonZ | &TimezoneOffsetZ => {
s = scan::trim1(s);
let offset =
try_consume!(scan::timezone_offset_zulu(s, scan::maybe_colon_or_space));
try_consume!(scan::timezone_offset_zulu(s, scan::consume_colon_maybe));
parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?;
}

Expand All @@ -470,7 +470,7 @@ where
s = scan::trim1(s);
let offset = try_consume!(scan::timezone_offset_permissive(
s,
scan::maybe_colon_or_space
scan::consume_colon_maybe
));
parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?;
}
Expand Down Expand Up @@ -929,7 +929,7 @@ fn test_parse() {
check!("+12:34:5", [fix!(TimezoneOffset)]; TOO_LONG);
check!("+12:34:56", [fix!(TimezoneOffset)]; TOO_LONG);
check!("+12:34:56:", [fix!(TimezoneOffset)]; TOO_LONG);
check!("+12 34", [fix!(TimezoneOffset)]; offset: 45_240);
check!("+12 34", [fix!(TimezoneOffset)]; INVALID);
check!("+12 34", [fix!(TimezoneOffset)]; INVALID);
check!("12:34", [fix!(TimezoneOffset)]; INVALID);
check!("12:34:56", [fix!(TimezoneOffset)]; INVALID);
Expand All @@ -956,15 +956,15 @@ fn test_parse() {
check!("+00:99", [fix!(TimezoneOffset)]; OUT_OF_RANGE);
check!("#12:34", [fix!(TimezoneOffset)]; INVALID);
check!("+12:34 ", [fix!(TimezoneOffset)]; TOO_LONG);
check!("+12 34 ", [fix!(TimezoneOffset)]; TOO_LONG);
check!("+12 34 ", [fix!(TimezoneOffset)]; INVALID);
check!(" +12:34", [fix!(TimezoneOffset)]; offset: 45_240);
check!(" -12:34", [fix!(TimezoneOffset)]; offset: -45_240);
check!(" +12:34", [fix!(TimezoneOffset)]; INVALID);
check!(" -12:34", [fix!(TimezoneOffset)]; INVALID);
check!("\t -12:34", [fix!(TimezoneOffset)]; INVALID);
check!("-12: 34", [fix!(TimezoneOffset)]; offset: -45_240);
check!("-12 :34", [fix!(TimezoneOffset)]; offset: -45_240);
check!("-12 : 34", [fix!(TimezoneOffset)]; offset: -45_240);
check!("-12: 34", [fix!(TimezoneOffset)]; INVALID);
check!("-12 :34", [fix!(TimezoneOffset)]; INVALID);
check!("-12 : 34", [fix!(TimezoneOffset)]; INVALID);
check!("-12 : 34", [fix!(TimezoneOffset)]; INVALID);
check!("-12 : 34", [fix!(TimezoneOffset)]; INVALID);
check!("-12: 34", [fix!(TimezoneOffset)]; INVALID);
Expand Down Expand Up @@ -1037,10 +1037,10 @@ fn test_parse() {
check!("+12:34:56:78", [fix!(TimezoneOffsetColon)]; TOO_LONG);
check!("+12:3456", [fix!(TimezoneOffsetColon)]; TOO_LONG);
check!("+1234:56", [fix!(TimezoneOffsetColon)]; TOO_LONG);
check!("+12 34", [fix!(TimezoneOffsetColon)]; offset: 45_240);
check!("+12: 34", [fix!(TimezoneOffsetColon)]; offset: 45_240);
check!("+12 :34", [fix!(TimezoneOffsetColon)]; offset: 45_240);
check!("+12 : 34", [fix!(TimezoneOffsetColon)]; offset: 45_240);
check!("+12 34", [fix!(TimezoneOffsetColon)]; INVALID);
check!("+12: 34", [fix!(TimezoneOffsetColon)]; INVALID);
check!("+12 :34", [fix!(TimezoneOffsetColon)]; INVALID);
check!("+12 : 34", [fix!(TimezoneOffsetColon)]; INVALID);
check!("+12 : 34", [fix!(TimezoneOffsetColon)]; INVALID);
check!("+12 : 34", [fix!(TimezoneOffsetColon)]; INVALID);
check!("+12 : 34", [fix!(TimezoneOffsetColon)]; INVALID);
Expand Down Expand Up @@ -1119,18 +1119,18 @@ fn test_parse() {
check!("+12::34", [fix!(TimezoneOffsetZ)]; INVALID);
check!("+12:3456", [fix!(TimezoneOffsetZ)]; TOO_LONG);
check!("+1234:56", [fix!(TimezoneOffsetZ)]; TOO_LONG);
check!("+12 34", [fix!(TimezoneOffsetZ)]; offset: 45_240);
check!("+12 34", [fix!(TimezoneOffsetZ)]; INVALID);
check!("+12 34", [fix!(TimezoneOffsetZ)]; INVALID);
check!("+12: 34", [fix!(TimezoneOffsetZ)]; offset: 45_240);
check!("+12 :34", [fix!(TimezoneOffsetZ)]; offset: 45_240);
check!("+12 : 34", [fix!(TimezoneOffsetZ)]; offset: 45_240);
check!("+12: 34", [fix!(TimezoneOffsetZ)]; INVALID);
check!("+12 :34", [fix!(TimezoneOffsetZ)]; INVALID);
check!("+12 : 34", [fix!(TimezoneOffsetZ)]; INVALID);
check!("+12 : 34", [fix!(TimezoneOffsetZ)]; INVALID);
check!("+12 : 34", [fix!(TimezoneOffsetZ)]; INVALID);
check!("+12 : 34", [fix!(TimezoneOffsetZ)]; INVALID);
check!("12:34 ", [fix!(TimezoneOffsetZ)]; INVALID);
check!(" 12:34", [fix!(TimezoneOffsetZ)]; INVALID);
check!("+12:34 ", [fix!(TimezoneOffsetZ)]; TOO_LONG);
check!("+12 34 ", [fix!(TimezoneOffsetZ)]; TOO_LONG);
check!("+12 34 ", [fix!(TimezoneOffsetZ)]; INVALID);
check!(" +12:34", [fix!(TimezoneOffsetZ)]; offset: 45_240);
check!("+12345", [fix!(TimezoneOffsetZ), num!(Day)]; offset: 45_240, day: 5);
check!("+12:345", [fix!(TimezoneOffsetZ), num!(Day)]; offset: 45_240, day: 5);
Expand Down Expand Up @@ -1202,11 +1202,11 @@ fn test_parse() {
check!("+12:34:56:", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG);
check!("+12:34:56:7", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG);
check!("+12:34:56:78", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG);
check!("+12 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240);
check!("+12 34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID);
check!("+12 34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID);
check!("+12 :34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240);
check!("+12: 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240);
check!("+12 : 34", [internal_fix!(TimezoneOffsetPermissive)]; offset: 45_240);
check!("+12 :34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID);
check!("+12: 34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID);
check!("+12 : 34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID);
check!("+12 :34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID);
check!("+12: 34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID);
check!("+12 : 34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID);
Expand Down
97 changes: 24 additions & 73 deletions src/format/scan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,45 +219,19 @@ pub(super) fn trim1(s: &str) -> &str {
}
}

/// Is `s.next()` whitespace?
/// Helper function to `maybe_colon_or_space`.
fn next_is_whitespace(s: &str) -> bool {
s.chars().next().map(|c| c.is_whitespace()).unwrap_or_default()
}

/// Allow a colon with possible one-character whitespace padding.
/// Consumes zero or one of these leading patterns:
/// `":"`, `" "`, `" :"`, `": "`, or `" : "`.
/// Consumes one colon char `:` if it is at the front of `s`.
/// Always returns `Ok(s)`.
pub(super) fn maybe_colon_or_space(mut s: &str) -> ParseResult<&str> {
pub(super) fn consume_colon_maybe(mut s: &str) -> ParseResult<&str> {
if s.is_empty() {
// nothing consumed
return Ok(s);
}

if s.starts_with(':') {
s = s_next(s);
if next_is_whitespace(s) {
s = s_next(s);
}
// consumed `":"` or `": "`
return Ok(s);
} else if !next_is_whitespace(s) {
return Ok(s);
}

s = s_next(s);
if s.starts_with(':') {
s = s_next(s);
} else {
// consumed `" "`
return Ok(s);
}
if next_is_whitespace(s) {
s = s_next(s);
// consumed `':'`
}

// consumed `" :"` or `" : "`
Ok(s)
}

Expand Down Expand Up @@ -503,48 +477,25 @@ fn test_trim1() {
}

#[test]
fn test_next_is_whitespace() {
assert!(!next_is_whitespace(""));
assert!(!next_is_whitespace("a"));
assert!(!next_is_whitespace("๐Ÿ˜ผ๐Ÿ˜ผ"));
assert!(next_is_whitespace(" "));
assert!(next_is_whitespace("\t\t"));
assert!(next_is_whitespace("\ta\t"));
assert!(next_is_whitespace("\t๐Ÿ˜ผ\t"));
}

#[test]
fn test_maybe_colon_or_space() {
assert_eq!(maybe_colon_or_space(""), Ok(""));
assert_eq!(maybe_colon_or_space(" "), Ok(""));
assert_eq!(maybe_colon_or_space("\n"), Ok(""));
assert_eq!(maybe_colon_or_space(" "), Ok(" "));
assert_eq!(maybe_colon_or_space(" "), Ok(" "));
assert_eq!(maybe_colon_or_space(" "), Ok(" "));
assert_eq!(maybe_colon_or_space("\t\t\t\t"), Ok("\t\t\t"));
assert_eq!(maybe_colon_or_space(":"), Ok(""));
assert_eq!(maybe_colon_or_space(" :"), Ok(""));
assert_eq!(maybe_colon_or_space(": "), Ok(""));
assert_eq!(maybe_colon_or_space(" : "), Ok(""));
assert_eq!(maybe_colon_or_space(" : "), Ok(" "));
assert_eq!(maybe_colon_or_space(" :"), Ok(" :"));
assert_eq!(maybe_colon_or_space(" : "), Ok(" : "));
assert_eq!(maybe_colon_or_space(" :: "), Ok(": "));
assert_eq!(maybe_colon_or_space(" : : "), Ok(": "));
assert_eq!(maybe_colon_or_space("๐Ÿ˜ธ"), Ok("๐Ÿ˜ธ"));
assert_eq!(maybe_colon_or_space("๐Ÿ˜ธ๐Ÿ˜ธ"), Ok("๐Ÿ˜ธ๐Ÿ˜ธ"));
assert_eq!(maybe_colon_or_space("๐Ÿ˜ธ:"), Ok("๐Ÿ˜ธ:"));
assert_eq!(maybe_colon_or_space("๐Ÿ˜ธ "), Ok("๐Ÿ˜ธ "));
assert_eq!(maybe_colon_or_space(" ๐Ÿ˜ธ"), Ok("๐Ÿ˜ธ"));
assert_eq!(maybe_colon_or_space(":๐Ÿ˜ธ"), Ok("๐Ÿ˜ธ"));
assert_eq!(maybe_colon_or_space(":๐Ÿ˜ธ "), Ok("๐Ÿ˜ธ "));
assert_eq!(maybe_colon_or_space(" :๐Ÿ˜ธ"), Ok("๐Ÿ˜ธ"));
assert_eq!(maybe_colon_or_space(" :๐Ÿ˜ธ "), Ok("๐Ÿ˜ธ "));
assert_eq!(maybe_colon_or_space(" :๐Ÿ˜ธ:"), Ok("๐Ÿ˜ธ:"));
assert_eq!(maybe_colon_or_space(": ๐Ÿ˜ธ"), Ok("๐Ÿ˜ธ"));
assert_eq!(maybe_colon_or_space(": ๐Ÿ˜ธ"), Ok(" ๐Ÿ˜ธ"));
assert_eq!(maybe_colon_or_space(": :๐Ÿ˜ธ"), Ok(":๐Ÿ˜ธ"));
assert_eq!(maybe_colon_or_space(" : ๐Ÿ˜ธ"), Ok("๐Ÿ˜ธ"));
assert_eq!(maybe_colon_or_space(" ::๐Ÿ˜ธ"), Ok(":๐Ÿ˜ธ"));
assert_eq!(maybe_colon_or_space(" :: ๐Ÿ˜ธ"), Ok(": ๐Ÿ˜ธ"));
fn test_consume_colon_maybe() {
assert_eq!(consume_colon_maybe(""), Ok(""));
assert_eq!(consume_colon_maybe(" "), Ok(" "));
assert_eq!(consume_colon_maybe("\n"), Ok("\n"));
assert_eq!(consume_colon_maybe(" "), Ok(" "));
assert_eq!(consume_colon_maybe(":"), Ok(""));
assert_eq!(consume_colon_maybe(" :"), Ok(" :"));
assert_eq!(consume_colon_maybe(": "), Ok(" "));
assert_eq!(consume_colon_maybe(" : "), Ok(" : "));
assert_eq!(consume_colon_maybe(": "), Ok(" "));
assert_eq!(consume_colon_maybe(" :"), Ok(" :"));
assert_eq!(consume_colon_maybe(":: "), Ok(": "));
assert_eq!(consume_colon_maybe("๐Ÿ˜ธ"), Ok("๐Ÿ˜ธ"));
assert_eq!(consume_colon_maybe("๐Ÿ˜ธ๐Ÿ˜ธ"), Ok("๐Ÿ˜ธ๐Ÿ˜ธ"));
assert_eq!(consume_colon_maybe("๐Ÿ˜ธ:"), Ok("๐Ÿ˜ธ:"));
assert_eq!(consume_colon_maybe("๐Ÿ˜ธ "), Ok("๐Ÿ˜ธ "));
assert_eq!(consume_colon_maybe(":๐Ÿ˜ธ"), Ok("๐Ÿ˜ธ"));
assert_eq!(consume_colon_maybe(":๐Ÿ˜ธ "), Ok("๐Ÿ˜ธ "));
assert_eq!(consume_colon_maybe(": ๐Ÿ˜ธ"), Ok(" ๐Ÿ˜ธ"));
assert_eq!(consume_colon_maybe(": ๐Ÿ˜ธ"), Ok(" ๐Ÿ˜ธ"));
assert_eq!(consume_colon_maybe(": :๐Ÿ˜ธ"), Ok(" :๐Ÿ˜ธ"));
}

0 comments on commit 2cd60a2

Please sign in to comment.