From b002ab4002e0fb3d63d3ca7bc08819279c4014d5 Mon Sep 17 00:00:00 2001 From: Alexis Date: Mon, 20 Jul 2026 11:27:26 +0200 Subject: [PATCH] fix(controller): Recover from invalid dates --- cachecontrol/controller.py | 10 ++++++---- tests/test_cache_control.py | 17 +++++++++++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/cachecontrol/controller.py b/cachecontrol/controller.py index 733684ff..03b22185 100644 --- a/cachecontrol/controller.py +++ b/cachecontrol/controller.py @@ -219,7 +219,9 @@ def cached_request(self, request: PreparedRequest) -> HTTPResponse | Literal[Fal now = time.time() time_tuple = parsedate_tz(headers["date"]) - assert time_tuple is not None + if time_tuple is None: + logger.debug("Ignoring cached response: invalid date") + return False date = calendar.timegm(time_tuple[:6]) current_age = max(0, now - date) logger.debug("Current age based on date: %i", current_age) @@ -358,8 +360,7 @@ def cache_response( if "date" in response_headers: time_tuple = parsedate_tz(response_headers["date"]) - assert time_tuple is not None - date = calendar.timegm(time_tuple[:6]) + date = calendar.timegm(time_tuple[:6]) if time_tuple is not None else 0 else: date = 0 @@ -430,7 +431,8 @@ def cache_response( # the cache. elif "date" in response_headers: time_tuple = parsedate_tz(response_headers["date"]) - assert time_tuple is not None + if time_tuple is None: + return date = calendar.timegm(time_tuple[:6]) # cache when there is a max-age > 0 max_age = cc.get("max-age") diff --git a/tests/test_cache_control.py b/tests/test_cache_control.py index 40cac5b5..9d3d4d8e 100644 --- a/tests/test_cache_control.py +++ b/tests/test_cache_control.py @@ -63,6 +63,13 @@ def test_no_cache_with_no_date(self, cc): assert not cc.cache.set.called + def test_no_cache_with_malformed_date(self, cc): + # An unparseable date must not crash; treat it like a missing date + resp = self.resp({"cache-control": "max-age=3600", "date": "garbage"}) + cc.cache_response(self.req(), resp) + + assert not cc.cache.set.called + def test_no_cache_with_wrong_sized_body(self, cc): # When the body is the wrong size, then we don't want to cache it # because it is obviously broken. @@ -305,3 +312,13 @@ def test_cached_request_with_bad_max_age_headers_not_returned(self): self.c.cache = DictCache({self.url: resp}) assert not self.req({}) + + def test_cached_request_with_malformed_date_not_returned(self): + # A cached entry with an unparseable date must not crash the lookup + resp = Mock( + headers={"cache-control": "max-age=3600", "date": "garbage"}, status=200 + ) + + self.c.cache = DictCache({self.url: resp}) + + assert not self.req({})