Skip to content

Commit

Permalink
Constrain timezone separator colon strings
Browse files Browse the repository at this point in the history
Constrain timezone middle-colon separator string from
infinite intermixed whitespace and colons
to possible patterns `":"`, `" "`, `" :"`, `": "`, or `" : "`.
A reasonable trade-off of previous extreme flexibility for
a little flexbility and concise input.

Issue chronotope#660
  • Loading branch information
jtmoon79 authored and djc committed Mar 18, 2023
1 parent f4841ee commit 2fcdd9e
Show file tree
Hide file tree
Showing 3 changed files with 154 additions and 67 deletions.
46 changes: 23 additions & 23 deletions src/datetime/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -809,12 +809,11 @@ 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",
"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 @@ -886,6 +885,8 @@ 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-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
];
for &s in invalid.iter() {
Expand Down Expand Up @@ -1116,13 +1117,11 @@ fn test_datetime_parse_from_str() {
"%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!(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 @@ -1189,13 +1188,11 @@ fn test_datetime_parse_from_str() {
"%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!(DateTime::<FixedOffset>::parse_from_str(
"Aug 09 2013 23:54:35 -09::00",
"%b %d %Y %H:%M:%S %:z"
)
.is_err());
// timezone data hs too many colons
assert!(DateTime::<FixedOffset>::parse_from_str(
"Aug 09 2013 23:54:35 -09:00:",
Expand Down Expand Up @@ -1246,13 +1243,16 @@ fn test_datetime_parse_from_str() {
"%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!(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
85 changes: 44 additions & 41 deletions src/format/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -452,23 +452,26 @@ where
| &TimezoneOffsetTripleColon
| &TimezoneOffset => {
s = scan::trim1(s);
let offset = try_consume!(scan::timezone_offset(s, scan::colon_or_space));
let offset =
try_consume!(scan::timezone_offset(s, scan::maybe_colon_or_space));
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::colon_or_space));
try_consume!(scan::timezone_offset_zulu(s, scan::maybe_colon_or_space));
parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?;
}

&Internal(InternalFixed {
val: InternalInternal::TimezoneOffsetPermissive,
}) => {
s = scan::trim1(s);
let offset =
try_consume!(scan::timezone_offset_permissive(s, scan::colon_or_space));
let offset = try_consume!(scan::timezone_offset_permissive(
s,
scan::maybe_colon_or_space
));
parsed.set_offset(i64::from(offset)).map_err(|e| (s, e))?;
}

Expand Down Expand Up @@ -927,14 +930,14 @@ fn test_parse() {
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)]; offset: 45_240);
check!("+12 34", [fix!(TimezoneOffset)]; INVALID);
check!("12:34", [fix!(TimezoneOffset)]; INVALID);
check!("12:34:56", [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)]; 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:56", [fix!(TimezoneOffset)]; TOO_LONG);
check!("+12:3456", [fix!(TimezoneOffset)]; TOO_LONG);
check!("+1234:56", [fix!(TimezoneOffset)]; TOO_LONG);
Expand Down Expand Up @@ -962,11 +965,11 @@ fn test_parse() {
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)]; offset: -45_240);
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)]; 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);
check!(" 12:34", [fix!(TimezoneOffset)]; INVALID);
check!("", [fix!(TimezoneOffset)]; TOO_SHORT);
Expand Down Expand Up @@ -1038,14 +1041,14 @@ fn test_parse() {
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)]; 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)]; 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);
check!("+12::34", [fix!(TimezoneOffsetColon)]; INVALID);
check!("#1234", [fix!(TimezoneOffsetColon)]; INVALID);
check!("#12:34", [fix!(TimezoneOffsetColon)]; INVALID);
check!("+12:34 ", [fix!(TimezoneOffsetColon)]; TOO_LONG);
Expand Down Expand Up @@ -1113,17 +1116,17 @@ fn test_parse() {
check!("+12:34:56:", [fix!(TimezoneOffsetZ)]; TOO_LONG);
check!("+12:34:56:7", [fix!(TimezoneOffsetZ)]; TOO_LONG);
check!("+12:34:56:78", [fix!(TimezoneOffsetZ)]; TOO_LONG);
check!("+12::34", [fix!(TimezoneOffsetZ)]; offset: 45_240);
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)]; offset: 45_240);
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)]; 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)]; TOO_LONG);
Expand Down Expand Up @@ -1200,22 +1203,22 @@ fn test_parse() {
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)]; offset: 45_240);
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)]; 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)]; 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)]; 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)]; 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);
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);
check!("12:34 ", [internal_fix!(TimezoneOffsetPermissive)]; INVALID);
check!(" 12:34", [internal_fix!(TimezoneOffsetPermissive)]; INVALID);
check!("+12:34 ", [internal_fix!(TimezoneOffsetPermissive)]; TOO_LONG);
Expand Down
90 changes: 87 additions & 3 deletions src/format/scan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,9 +219,46 @@ pub(super) fn trim1(s: &str) -> &str {
}
}

/// Consumes any number (including zero) of colon or spaces.
pub(super) fn colon_or_space(s: &str) -> ParseResult<&str> {
Ok(s.trim_left_matches(|c: char| c == ':' || c.is_whitespace()))
/// 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 `" : "`.
/// Always returns `Ok(s)`.
pub(super) fn maybe_colon_or_space(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 `" :"` or `" : "`
Ok(s)
}

/// Tries to parse `[-+]\d\d` continued by `\d\d`. Return an offset in seconds if possible.
Expand Down Expand Up @@ -464,3 +501,50 @@ fn test_trim1() {
assert_eq!(trim1("๐Ÿ˜ผ"), "๐Ÿ˜ผ");
assert_eq!(trim1("๐Ÿ˜ผb"), "๐Ÿ˜ผb");
}

#[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(": ๐Ÿ˜ธ"));
}

0 comments on commit 2fcdd9e

Please sign in to comment.