Skip to content

Commit 1c84807

Browse files
authored
fix(wkt): panic in Duration deserialization (#6682)
When deserializing a Duration string with more than 9 fractional digits, `&pad[s.len()..]` would panic with an out-of-bounds byte index.
1 parent 9953b09 commit 1c84807

1 file changed

Lines changed: 39 additions & 5 deletions

File tree

src/wkt/src/duration.rs

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -304,12 +304,22 @@ impl TryFrom<&str> for Duration {
304304
.unwrap_or(0);
305305
let nanos = nanos
306306
.map(|s| {
307-
let pad = "000000000";
308-
format!("{s}{}", &pad[s.len()..])
307+
if s.is_empty() || !s.chars().all(|c| c.is_ascii_digit()) {
308+
return Err(DurationError::Deserialize(
309+
format!("nanos are not a number [{s}]").into(),
310+
));
311+
}
312+
let len = s.len();
313+
let (digits, power) = if len > 9 { (&s[..9], 0) } else { (s, 9 - len) };
314+
let mut val = digits
315+
.parse::<i32>()
316+
.map_err(|e| DurationError::Deserialize(e.into()))?;
317+
if power > 0 {
318+
val *= 10_i32.pow(power as u32)
319+
}
320+
Ok(val)
309321
})
310-
.map(|s| s.parse::<i32>())
311-
.transpose()
312-
.map_err(|e| DurationError::Deserialize(e.into()))?
322+
.transpose()?
313323
.unwrap_or(0);
314324

315325
Duration::new(sign * seconds, sign as i32 * nanos)
@@ -678,6 +688,8 @@ mod tests {
678688
#[test_case("1a.0s" ; "seconds are not a number [1a]")]
679689
#[test_case("1.aaas" ; "nanos are not a number [aaa]")]
680690
#[test_case("1.0as" ; "nanos are not a number [0a]")]
691+
#[test_case("1.1234567890as" ; "nanos with trailing chars [1234567890a]")]
692+
#[test_case("1.s" ; "empty nanos")]
681693
fn parse_detect_bad_input(input: &str) -> Result {
682694
let got = Duration::try_from(input);
683695
assert!(got.is_err(), "{got:?}");
@@ -689,6 +701,28 @@ mod tests {
689701
Ok(())
690702
}
691703

704+
#[test]
705+
fn fractional_seconds_exceed_9_digits() -> Result {
706+
let d = Duration::try_from("1.1234567890s")?;
707+
assert_eq!(d, Duration::new(1, 123_456_789)?);
708+
709+
let d = Duration::try_from("1.123456789012s")?;
710+
assert_eq!(d, Duration::new(1, 123_456_789)?);
711+
712+
let d: Duration = serde_json::from_str(r#""1.1234567890s""#)?;
713+
assert_eq!(d, Duration::new(1, 123_456_789)?);
714+
715+
let d: Duration = serde_json::from_str(r#""1.123456789012s""#)?;
716+
assert_eq!(d, Duration::new(1, 123_456_789)?);
717+
718+
let d = Duration::try_from("-1.1234567890s")?;
719+
assert_eq!(d, Duration::new(-1, -123_456_789)?);
720+
721+
let d: Duration = serde_json::from_str(r#""-1.123456789012s""#)?;
722+
assert_eq!(d, Duration::new(-1, -123_456_789)?);
723+
Ok(())
724+
}
725+
692726
#[test]
693727
fn deserialize_unexpected_input_type() -> Result {
694728
let got = serde_json::from_value::<Duration>(serde_json::json!({}));

0 commit comments

Comments
 (0)