diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheSupport.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheSupport.java index f61e88eac3..0710dd13d2 100644 --- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheSupport.java +++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheSupport.java @@ -209,7 +209,9 @@ public static long deltaSeconds(final String s) { return ageValue; } catch (final NumberFormatException ignore) { } - return 0; + // A value that is not a valid delta-seconds token is invalid and must be treated as absent (RFC 9111 4.2.1), + // not folded into a usable 0 which would look like a genuine directive. + return -1; } } diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/CacheControlParserTest.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/CacheControlParserTest.java index 49f6c3609b..7f16c9b421 100644 --- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/CacheControlParserTest.java +++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/CacheControlParserTest.java @@ -62,7 +62,8 @@ void testParseSMaxAge() { void testParseInvalidCacheValue() { final Header header = new BasicHeader("Cache-Control", "max-age=invalid"); final ResponseCacheControl cacheControl = parser.parseResponse(Collections.singletonList(header).iterator()); - assertEquals(0L, cacheControl.getMaxAge()); + // A malformed delta-seconds value is invalid and must be treated as absent (-1), not a usable 0. + assertEquals(-1L, cacheControl.getMaxAge()); } @Test diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCacheSupport.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCacheSupport.java index ae8e9859f9..21a0009291 100644 --- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCacheSupport.java +++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCacheSupport.java @@ -106,7 +106,7 @@ void testParseDeltaSeconds() { Assertions.assertEquals(-1L, CacheSupport.deltaSeconds("-100")); Assertions.assertEquals(-1L, CacheSupport.deltaSeconds("")); Assertions.assertEquals(-1L, CacheSupport.deltaSeconds(null)); - Assertions.assertEquals(0L, CacheSupport.deltaSeconds("huh?")); + Assertions.assertEquals(-1L, CacheSupport.deltaSeconds("huh?")); Assertions.assertEquals(2147483648L, CacheSupport.deltaSeconds("2147483648")); Assertions.assertEquals(2147483648L, CacheSupport.deltaSeconds("2147483649")); Assertions.assertEquals(2147483648L, CacheSupport.deltaSeconds("214748364712")); diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestResponseCachingPolicy.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestResponseCachingPolicy.java index af8b5888c2..0326917f86 100644 --- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestResponseCachingPolicy.java +++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestResponseCachingPolicy.java @@ -935,6 +935,20 @@ void testSMaxageWithAuthorizationIsCacheable() { "Response with s-maxage and Authorization header should be cacheable in shared cache."); } + @Test + void testMalformedSMaxageWithAuthorizationNotCacheableBySharedCache() { + request = new BasicHttpRequest("GET", "/resource"); + request.setHeader(HttpHeaders.AUTHORIZATION, "Basic dXNlcjpwYXNzd2Q="); + response.setHeader("Expires", DateUtils.formatStandardDate(tenSecondsFromNow)); + response.setHeader("Cache-Control", "s-maxage=invalid"); + // Parse the actual header so the malformed value is handled the way it is on the wire. + responseCacheControl = CacheControlHeaderParser.INSTANCE.parse(response); + + final boolean isCacheable = policy.isResponseCacheable(requestCacheControl, responseCacheControl, request, response); + assertFalse(isCacheable, + "Response to an Authorization request with a malformed s-maxage must not be stored by a shared cache."); + } + @Test void testNoDirectivesWithAuthorizationNotCacheable() { request = new BasicHttpRequest("GET", "/resource");