From: Christopher Faulet Date: Wed, 21 Oct 2026 09:02:42 +0000 (+0000) Subject: BUG/MINOR: cache: do not ignore null lifetimes not written as "max-age=0" X-Git-Url: https://git.kaiwu.me/%7B@url%7D?a=commitdiff_plain;h=ae2150c57f00757dad10eb863d8dba49c1218816;p=haproxy.git BUG/MINOR: cache: do not ignore null lifetimes not written as "max-age=0" A response carrying "Cache-Control: max-age=00", "max-age=\"0\"" or "s-maxage=00, max-age=60" is delivered from the cache for the whole max-age of the cache (60 seconds by default) or for the max-age, while its null lifetime means it must not be reused without revalidation. Clients thus receive outdated contents. Such forms are unusual but valid (RFC 9111#1.2.2 and #5.2 allow leading zeroes and quoted values). http_check_response_for_cacheability() only recognizes the exact forms "max-age=0" and "s-maxage=0" as null lifetimes. Other forms are taken as freshness information, and then http_calc_maxage() only considers values strictly greater than zero, so a null value is ignored and the next source of lifetime is used: max-age after s-maxage, Expires, or the cache's max-age. http_calc_maxage() now uses s-maxage and max-age as soon as they were found, including when they are null, and the store action does not store a response whose remaining lifetime is null, since it would already be expired. This last check also avoids storing responses with an Expires date in the past, which were stored expired. This patch may be backported to all supported versions. It depends on the previous fixes to http_calc_maxage() ("BUG/MINOR: cache: reduce the max-age lifetime by the response's Age" and "BUG/MINOR: cache: compare the Age to the heuristic lifetime when none is given"), but only for context, the change is easy to adapt. --- diff --git a/src/cache.c b/src/cache.c index ff75bcfe1..21d34c001 100644 --- a/src/cache.c +++ b/src/cache.c @@ -1121,15 +1121,16 @@ int http_calc_maxage(struct stream *s, struct cache *cache, int *true_maxage, in if (age_relative) - *age_relative = (smaxage > 0 || maxage > 0 || expires < 0); + *age_relative = (smaxage >= 0 || maxage >= 0 || expires < 0); - if (smaxage > 0) { + /* note: a null value is a valid lifetime, it must not be ignored */ + if (smaxage >= 0) { if (true_maxage) *true_maxage = smaxage; return MIN(smaxage, cache->maxage); } - if (maxage > 0) { + if (maxage >= 0) { if (true_maxage) *true_maxage = maxage; return MIN(maxage, cache->maxage); @@ -1846,6 +1847,10 @@ enum act_return http_action_store_cache(struct act_rule *rule, struct proxy *px, goto out; } + /* Do not store a response which would already be expired */ + if (effective_maxage <= 0) + goto out; + /* Build a last-modified time that will be stored in the cache_entry and * compared to a future If-Modified-Since client header. */ object->last_modified = get_last_modified_time(htx);