mirror of
https://github.com/storytold/lightcraft.git
synced 2026-11-01 05:57:09 -06:00
Never crash: a UTC offset of i16::MIN formats and validates without overflow (#2579)
DateTime deserializes with public fields, so its offset can arrive without passing valid(). `i16::MIN.abs()` overflows: a panic in debug builds and a negative minute count in release, which to_iso() then printed. valid() and to_iso() use unsigned_abs(). Item 4 of #2579. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
2e5c1dd41d
commit
8b3ce31b66
@@ -31,7 +31,7 @@ impl DateTime {
|
||||
// chrono rejects impossible days (leap years and all); the time is checked
|
||||
// by hand so a leap second (`second == 60`) stays acceptable.
|
||||
let day_ok = self.year > 0 && chrono::NaiveDate::from_ymd_opt(i32::from(self.year), u32::from(self.month), u32::from(self.day)).is_some();
|
||||
let ok = day_ok && self.hour < 24 && self.minute < 60 && self.second < 61 && self.offset_minutes.is_none_or(|o| o.abs() <= 18 * 60);
|
||||
let ok = day_ok && self.hour < 24 && self.minute < 60 && self.second < 61 && self.offset_minutes.is_none_or(|o| o.unsigned_abs() <= 18 * 60);
|
||||
ok.then_some(self)
|
||||
}
|
||||
|
||||
@@ -135,7 +135,8 @@ impl DateTime {
|
||||
}
|
||||
if let Some(o) = self.offset_minutes {
|
||||
let sign = if o < 0 { '-' } else { '+' };
|
||||
s += &format!("{sign}{:02}:{:02}", o.abs() / 60, o.abs() % 60);
|
||||
let o = o.unsigned_abs();
|
||||
s += &format!("{sign}{:02}:{:02}", o / 60, o % 60);
|
||||
}
|
||||
s
|
||||
}
|
||||
@@ -189,6 +190,14 @@ mod tests {
|
||||
assert_eq!(DateTime::parse_offset("+99:00"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extreme_offsets_do_not_overflow() {
|
||||
// `DateTime` deserializes with public fields, so an offset can arrive without passing `valid()`
|
||||
let d = DateTime { offset_minutes: Some(i16::MIN), ..DateTime::parse_iso("2024-06-01T13:45:59").unwrap() };
|
||||
assert_eq!(d.to_iso(), "2024-06-01T13:45:59-546:08");
|
||||
assert_eq!(d.valid(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unicode_offsets_are_rejected_without_panicking() {
|
||||
for offset in ["+1€", "-1€", "+€1", "-€1"] {
|
||||
|
||||
Reference in New Issue
Block a user