mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 11:06:11 -10:00
Ban lists: checked arithmetic for hand-edited dates
parse_date multiplied and added the fields of banned-players.json and
banned-ips.json dates as plain i64, so a huge year, month, day or time
panicked in a debug build and wrapped in a release one, turning a ban's
expiry into an arbitrary date. Every step is now checked, years are bounded
before days_from_civil, and a date out of range is None, like one that
doesn't parse. Lenient month overflow (month 13) still works.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01SJ3stH5q6PgMAVUSmJqKDe
(cherry picked from commit 184ea41194)
This commit is contained in:
1 parent
0b974a2f9c
commit
e42e8b5497
1 file changed
+40
-4
@@ -101,11 +101,24 @@ pub fn parse_date(s: &str) -> Option<i64> {
|
||||
}
|
||||
let offset =
|
||||
sign * (digits[..2].parse::<i64>().ok()? * 60 + digits[2..].parse::<i64>().ok()?);
|
||||
// Lenient month and day arithmetic.
|
||||
let months = y * 12 + (mo - 1);
|
||||
// Lenient month and day arithmetic, checked: the file is edited by hand, and a value past what
|
||||
// milliseconds since 1970 hold is no date (None, as an unparsable one) rather than a wrapped one.
|
||||
let months = y.checked_mul(12)?.checked_add(mo.checked_sub(1)?)?;
|
||||
let (year, month) = (months.div_euclid(12), months.rem_euclid(12) + 1);
|
||||
let days = days_from_civil(year, month as u32, 1) + da - 1;
|
||||
Some(((days * 86_400 + h * 3600 + mi * 60 + se) - offset * 60) * 1000)
|
||||
// Far beyond any i64 of milliseconds, and small enough for days_from_civil's own arithmetic.
|
||||
if !(-300_000_000..=300_000_000).contains(&year) {
|
||||
return None;
|
||||
}
|
||||
let days = days_from_civil(year, month as u32, 1)
|
||||
.checked_add(da)?
|
||||
.checked_sub(1)?;
|
||||
let seconds = days
|
||||
.checked_mul(86_400)?
|
||||
.checked_add(h.checked_mul(3600)?)?
|
||||
.checked_add(mi.checked_mul(60)?)?
|
||||
.checked_add(se)?
|
||||
.checked_sub(offset * 60)?;
|
||||
seconds.checked_mul(1000)
|
||||
}
|
||||
|
||||
/// One list entry kind.
|
||||
@@ -477,6 +490,29 @@ mod tests {
|
||||
assert_eq!(parse_date("forever"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dates_too_far_out_are_none_not_wrapped() {
|
||||
// Lenient overflow still works within range: month 13 is January of the next year.
|
||||
assert_eq!(
|
||||
parse_date("2026-13-01 00:00:00 +0000"),
|
||||
parse_date("2027-01-01 00:00:00 +0000")
|
||||
);
|
||||
for text in [
|
||||
"9223372036854775807-01-01 00:00:00 +0000",
|
||||
"2026-9223372036854775807-01 00:00:00 +0000",
|
||||
"2026-01-9223372036854775807 00:00:00 +0000",
|
||||
"2026-01-01 9223372036854775807:00:00 +0000",
|
||||
"2026-01-01 00:00:9223372036854775807 +0000",
|
||||
"-9223372036854775808-01-01 00:00:00 +0000",
|
||||
"300000001-01-01 00:00:00 +0000",
|
||||
"2026--9223372036854775808-01 00:00:00 +0000",
|
||||
] {
|
||||
assert_eq!(parse_date(text), None, "{text}");
|
||||
}
|
||||
// The far end of what milliseconds hold still parses (year 292 million).
|
||||
assert!(parse_date("292000000-01-01 00:00:00 +0000").is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_watcher_hears_what_vanillas_notification_service_does() {
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
Reference in new issue
Block a user