Converting a SystemTime earlier than the Unix epoch, or later than year 9999, panics. The date header APIs listed under Scope all funnel into that conversion, so a pre-1970 file mtime aborts the request handler in an optimized build.
Dependencies: headers = "=0.4.1" and http = "1".
Reproducer
use headers::{Expires, Header, HeaderMapExt, IfModifiedSince, LastModified};
use std::panic::catch_unwind;
use std::time::{Duration, UNIX_EPOCH};
fn main() {
let pre = UNIX_EPOCH - Duration::from_secs(1); // a file mtime from 1969
let far = UNIX_EPOCH + Duration::from_secs(1_000_000_000_000);
let mut m = http::HeaderMap::new();
m.append(
IfModifiedSince::name(),
"Mon, 07 Nov 1994 08:48:37 GMT".parse().unwrap(),
);
let ims: IfModifiedSince = m.typed_get().unwrap();
// Each call panics on its own. catch_unwind keeps the process alive so
// that one run shows all three.
let _ = catch_unwind(|| ims.is_modified(pre));
let _ = catch_unwind(|| LastModified::from(pre));
let _ = catch_unwind(|| Expires::from(far));
}
Observed
cargo run --release:
panicked at 'all times should be after the epoch: SystemTimeError(1s)'
panicked at 'all times should be after the epoch: SystemTimeError(1s)'
panicked at 'date must be before year 9999'
Expected
impl From<SystemTime> for LastModified and IfModifiedSince::is_modified(&self, last_modified: SystemTime) take an unrestricted std::time::SystemTime. Their signatures and docs give no valid range and declare no panic. Either the out-of-range input gets handled or the panic gets documented. LastModified::from(metadata.modified()?) is the canonical static-file-server call, and pre-1970 mtimes show up after tar/zip extraction, on restored backups, and on devices with an unset clock.
Root cause
src/util/http_date.rs:93, which calls httpdate's From<SystemTime> and its .expect("all times should be after the epoch").
Call sites that reach it: src/common/date.rs:37, src/common/last_modified.rs:41, src/common/expires.rs:41, src/common/if_modified_since.rs:41 and :47, src/common/if_unmodified_since.rs:42 and :48, src/common/retry_after.rs:46, src/common/if_range.rs:59.
Scope
Date, LastModified, Expires, IfModifiedSince, IfUnmodifiedSince, RetryAfter::date, IfRange::date, IfModifiedSince::is_modified, and IfUnmodifiedSince::precondition_passes all funnel into the same conversion. A SystemTime below the epoch or past year 9999 passed to any of them triggers the panic. The reproducer above exercises three of these. Debug and release builds both panic.
Converting a
SystemTimeearlier than the Unix epoch, or later than year 9999, panics. The date header APIs listed under Scope all funnel into that conversion, so a pre-1970 file mtime aborts the request handler in an optimized build.Dependencies:
headers = "=0.4.1"andhttp = "1".Reproducer
Observed
cargo run --release:Expected
impl From<SystemTime> for LastModifiedandIfModifiedSince::is_modified(&self, last_modified: SystemTime)take an unrestrictedstd::time::SystemTime. Their signatures and docs give no valid range and declare no panic. Either the out-of-range input gets handled or the panic gets documented.LastModified::from(metadata.modified()?)is the canonical static-file-server call, and pre-1970 mtimes show up after tar/zip extraction, on restored backups, and on devices with an unset clock.Root cause
src/util/http_date.rs:93, which calls httpdate's
From<SystemTime>and its.expect("all times should be after the epoch").Call sites that reach it: src/common/date.rs:37, src/common/last_modified.rs:41, src/common/expires.rs:41, src/common/if_modified_since.rs:41 and :47, src/common/if_unmodified_since.rs:42 and :48, src/common/retry_after.rs:46, src/common/if_range.rs:59.
Scope
Date,LastModified,Expires,IfModifiedSince,IfUnmodifiedSince,RetryAfter::date,IfRange::date,IfModifiedSince::is_modified, andIfUnmodifiedSince::precondition_passesall funnel into the same conversion. ASystemTimebelow the epoch or past year 9999 passed to any of them triggers the panic. The reproducer above exercises three of these. Debug and release builds both panic.