From 845c97631edfd7282533e7320fd3c79b839d8a54 Mon Sep 17 00:00:00 2001 From: Sai Asish Y Date: Sat, 25 Jul 2026 20:43:30 -0700 Subject: [PATCH] Clamp out-of-range SystemTime instead of panicking in date headers Signed-off-by: Sai Asish Y --- src/util/http_date.rs | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/util/http_date.rs b/src/util/http_date.rs index d0d7acf0..ad1d7856 100644 --- a/src/util/http_date.rs +++ b/src/util/http_date.rs @@ -1,6 +1,6 @@ use std::fmt; use std::str::FromStr; -use std::time::SystemTime; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; use bytes::Bytes; use http::header::HeaderValue; @@ -91,7 +91,17 @@ impl fmt::Display for HttpDate { impl From for HttpDate { fn from(sys: SystemTime) -> HttpDate { - HttpDate(sys.into()) + // `httpdate::HttpDate: From` panics for times before the + // Unix epoch or after year 9999. Callers pass an unrestricted + // `SystemTime` (e.g. a pre-1970 file mtime from a restored backup or a + // device with an unset clock), so clamp to the representable range + // instead of aborting. + const MAX_SECS: u64 = 253_402_300_799; // 9999-12-31T23:59:59Z + let secs = match sys.duration_since(UNIX_EPOCH) { + Ok(dur) => dur.as_secs().min(MAX_SECS), + Err(_) => 0, + }; + HttpDate((UNIX_EPOCH + Duration::from_secs(secs)).into()) } } @@ -148,4 +158,18 @@ mod tests { fn test_no_date() { assert!("this-is-no-date".parse::().is_err()); } + + #[test] + fn test_out_of_range_systemtime_does_not_panic() { + // Before the Unix epoch clamps to the epoch. + let before = UNIX_EPOCH - Duration::from_secs(1); + assert_eq!(HttpDate::from(before), HttpDate::from(UNIX_EPOCH)); + + // Far past year 9999 clamps to the last representable second. + let far = UNIX_EPOCH + Duration::from_secs(1_000_000_000_000); + assert_eq!( + HttpDate::from(far).to_string(), + "Fri, 31 Dec 9999 23:59:59 GMT" + ); + } }