Skip to content

From<SystemTime> for HttpDate panics on times before 1970 or after year 9999 #230

Description

@hey-jj

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions