From 29d164d56d564273b3575f8b2de79e5fccf620d1 Mon Sep 17 00:00:00 2001 From: Arturo Bernal Date: Sat, 14 Nov 2026 09:12:40 +0100 Subject: [PATCH] mod_autoindex: Don't clamp a pre-1970 last-modified date when sorting. make_autoindex_entry() clamped any negative apr_time_t mtime to 0 when the listing's sort key was K_LAST_MOD, so a file or directory actually modified before 1970 was displayed as 1970-01-01 in a listing sorted by date (?C=M) -- while the same entry in the default, unsorted order (which doesn't hit that code path) already showed its real date. The clamp dates back to the 1999 import of the Apache 1.3 codebase, from an era before apr_time_t's signed 64-bit microseconds-since-epoch could reliably represent dates before 1970. The module's own sentinel for "no mtime available" is the literal value -1 (checked with `lm != -1` at both call sites that format it for display), not "any negative value", so clamping every negative value here only corrupts genuine pre-1970 timestamps; it was never needed to guard the sentinel. Reproduced on a real build: a file with mtime touch -d 1958-01-01 showed "1958-01-01" in the default listing and "1970-01-01" once sorted with ?C=M. After removing the clamp, both orderings show the real 1958 date. PR 66182 --- changes-entries/66182.txt | 3 ++ modules/generators/mod_autoindex.c | 9 +---- test/modules/generators/test_002_autoindex.py | 37 +++++++++++++++++++ 3 files changed, 42 insertions(+), 7 deletions(-) create mode 100644 changes-entries/66182.txt diff --git a/changes-entries/66182.txt b/changes-entries/66182.txt new file mode 100644 index 00000000000..95112c102ee --- /dev/null +++ b/changes-entries/66182.txt @@ -0,0 +1,3 @@ + *) mod_autoindex: Fix a FancyIndexing listing sorted by last-modified + date (?C=M) showing 1970-01-01 for files modified before 1970, + instead of their real date. PR 66182 [Arturo Bernal] diff --git a/modules/generators/mod_autoindex.c b/modules/generators/mod_autoindex.c index 37bed118b06..a6ff4119b11 100644 --- a/modules/generators/mod_autoindex.c +++ b/modules/generators/mod_autoindex.c @@ -1416,14 +1416,9 @@ static struct ent *make_autoindex_entry(const apr_finfo_t *dirent, } ap_destroy_sub_req(rr); /* - * We don't need to take any special action for the file size key. - * If we did, it would go here. + * We don't need to take any special action for the file size or + * last-modified keys. If we did, it would go here. */ - if (keyid == K_LAST_MOD) { - if (p->lm < 0) { - p->lm = 0; - } - } return (p); } diff --git a/test/modules/generators/test_002_autoindex.py b/test/modules/generators/test_002_autoindex.py index 14d05bdc3f8..767f5f2abbb 100644 --- a/test/modules/generators/test_002_autoindex.py +++ b/test/modules/generators/test_002_autoindex.py @@ -1,9 +1,15 @@ +import calendar +import os import textwrap import pytest from pyhttpd.conf import HttpdConf +# A modification time before the 1970 epoch, to check that mod_autoindex +# does not clamp it when sorting by last-modified (BZ 66182). +OLD_MTIME = calendar.timegm((1958, 1, 1, 0, 0, 0, 0, 0, 0)) + class TestAutoindex: @@ -17,11 +23,23 @@ def _class_scope(self, env): Options +Indexes IndexOptions FancyIndexing ScanHTMLTitles + + Options +Indexes + IndexOptions FancyIndexing + """), }) conf.add_vhost(domains=[domain], port=env.http_port, doc_root="htdocs/autoindex") conf.install() + + oldmtime_dir = os.path.join(env.server_docs_dir, "autoindex", "oldmtime") + os.makedirs(oldmtime_dir, exist_ok=True) + oldfile = os.path.join(oldmtime_dir, "oldfile.txt") + with open(oldfile, "w") as f: + f.write("old file\n") + os.utime(oldfile, (OLD_MTIME, OLD_MTIME)) + assert env.apache_restart() == 0 def test_generators_002_01_scan_title_bare_amp(self, env): @@ -32,3 +50,22 @@ def test_generators_002_01_scan_title_bare_amp(self, env): r = env.curl_get(env.mkurl("http", "autoindex", "/listing/")) assert r.response["status"] == 200 assert "AT&T" in r.response["body"].decode() + + def test_generators_002_02_old_mtime_not_clamped_when_sorted(self, env): + """A file modified before 1970 keeps its real date when the listing + is explicitly sorted by last-modified (?C=M). Before the fix, + make_autoindex_entry() clamped any negative apr_time_t to 0 when + keyid == K_LAST_MOD, displaying 1970-01-01 instead of the real + 1958 date -- even though the unsorted, default-order listing (which + does not hit that code path) already showed it correctly.""" + unsorted = env.curl_get(env.mkurl("http", "autoindex", "/oldmtime/")) + assert unsorted.response["status"] == 200 + unsorted_body = unsorted.response["body"].decode() + assert "1958-01-01" in unsorted_body + + sorted_by_date = env.curl_get( + env.mkurl("http", "autoindex", "/oldmtime/?C=M;O=A")) + assert sorted_by_date.response["status"] == 200 + sorted_body = sorted_by_date.response["body"].decode() + assert "1958-01-01" in sorted_body + assert "1970-01-01" not in sorted_body