You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by ol...@apache.org on 2023/05/14 09:35:49 UTC

[httpcomponents-client] branch 5.3.x updated: Handle "no-cache" directive with specific header fields.

This is an automated email from the ASF dual-hosted git repository.

olegk pushed a commit to branch 5.3.x
in repository https://gitbox.apache.org/repos/asf/httpcomponents-client.git


The following commit(s) were added to refs/heads/5.3.x by this push:
     new f24597d13 Handle "no-cache" directive with specific header fields.
f24597d13 is described below

commit f24597d13b4bb1b6b28356b42464a1bfe7ca628f
Author: Arturo Bernal <ar...@gmail.com>
AuthorDate: Thu May 11 21:42:37 2023 +0200

    Handle "no-cache" directive with specific header fields.
    
    This commit enhances the handling of the "no-cache" directive when used with specified header fields. The changes include:
    
    * Updated the caching module to correctly identify and handle "no-cache" directives with specific header fields. This was done by modifying the `responseContainsNoCacheDirective` method.
    * Modified the `handleCacheHit` method to ensure that a cached entry is revalidated with the origin server when a "no-cache" directive with specific header fields is present in the response.
    * Ensured that the rest of the response is still cacheable when the specified header fields are present, as long as the response complies with other caching requirements.
---
 .../client5/http/impl/cache/AsyncCachingExec.java  | 15 +++++
 .../hc/client5/http/impl/cache/CacheControl.java   | 38 ++++++++++---
 .../http/impl/cache/CacheControlHeaderParser.java  | 25 +++++++-
 .../hc/client5/http/impl/cache/CachingExec.java    | 14 +++++
 .../client5/http/impl/cache/CachingExecBase.java   | 22 ++++++++
 .../http/impl/cache/ResponseCachingPolicy.java     | 66 +++++++++++++++++++++-
 .../http/impl/cache/CacheControlParserTest.java    | 12 ++++
 .../http/impl/cache/TestCachingExecChain.java      | 36 +++++++++++-
 .../http/impl/cache/TestResponseCachingPolicy.java | 45 +++++++++++++--
 9 files changed, 255 insertions(+), 18 deletions(-)

diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsyncCachingExec.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsyncCachingExec.java
index d41a27752..ca321e7f6 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsyncCachingExec.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/AsyncCachingExec.java
@@ -623,7 +623,22 @@ class AsyncCachingExec extends CachingExecBase implements AsyncExecChainHandler
         final HttpClientContext context  = scope.clientContext;
         recordCacheHit(target, request);
         final Instant now = getCurrentDate();
+
+        if (requestContainsNoCacheDirective(request)) {
+            // Revalidate with the server due to no-cache directive in response
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("Revalidating with server due to no-cache directive in response.");
+            }
+            revalidateCacheEntry(target, request, entityProducer, scope, chain, asyncExecCallback, entry);
+            return;
+        }
+
         if (suitabilityChecker.canCachedResponseBeUsed(request, entry, now)) {
+            if (responseCachingPolicy.responseContainsNoCacheDirective(entry)) {
+                // Revalidate with the server due to no-cache directive in response
+                revalidateCacheEntry(target, request, entityProducer, scope, chain, asyncExecCallback, entry);
+                return;
+            }
             LOG.debug("Cache hit");
             try {
                 final SimpleHttpResponse cacheResponse = generateCachedResponse(request, context, entry, now);
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheControl.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheControl.java
index 276268097..dadd80152 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheControl.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheControl.java
@@ -31,6 +31,9 @@ import org.apache.hc.core5.annotation.Contract;
 import org.apache.hc.core5.annotation.Internal;
 import org.apache.hc.core5.annotation.ThreadingBehavior;
 
+import java.util.Collections;
+import java.util.Set;
+
 /**
  * Represents the values of the Cache-Control header in an HTTP response, which indicate whether and for how long
  * the response can be cached by the client and intermediary proxies.
@@ -92,16 +95,22 @@ final class CacheControl {
      * The number of seconds that a stale response is considered fresh for the purpose
      * of serving a response while a revalidation request is made to the origin server.
      */
-    private final long stale_while_revalidate;
+    private final long staleWhileRevalidate;
+
+    /**
+     * A set of field names specified in the "no-cache" directive of the Cache-Control header.
+     */
+    private final Set<String> noCacheFields;
 
 
     /**
      * Creates a new instance of {@code CacheControl} with default values.
      * The default values are: max-age=-1, shared-max-age=-1, must-revalidate=false, no-cache=false,
-     * no-store=false, private=false, proxy-revalidate=false, public=false and stale_while_revalidate=-1.
+     * no-store=false, private=false, proxy-revalidate=false, public=false, stale_while_revalidate=-1,
+     * and noCacheFields as an empty set.
      */
     public CacheControl() {
-        this(-1, -1, false, false, false, false, false, false,-1);
+        this(-1, -1, false, false, false, false, false, false,-1, Collections.emptySet());
     }
 
     /**
@@ -116,10 +125,12 @@ final class CacheControl {
      * @param cachePrivate    The private value from the Cache-Control header.
      * @param proxyRevalidate The proxy-revalidate value from the Cache-Control header.
      * @param cachePublic     The public value from the Cache-Control header.
+     * @param noCacheFields        The set of field names specified in the "no-cache" directive of the Cache-Control header.
      */
     public CacheControl(final long maxAge, final long sharedMaxAge, final boolean mustRevalidate, final boolean noCache, final boolean noStore,
                         final boolean cachePrivate, final boolean proxyRevalidate, final boolean cachePublic,
-                        final long stale_while_revalidate) {
+                        final long staleWhileRevalidate,
+                        final Set<String> noCacheFields) {
         this.maxAge = maxAge;
         this.sharedMaxAge = sharedMaxAge;
         this.noCache = noCache;
@@ -128,7 +139,8 @@ final class CacheControl {
         this.mustRevalidate = mustRevalidate;
         this.proxyRevalidate = proxyRevalidate;
         this.cachePublic = cachePublic;
-        this.stale_while_revalidate = stale_while_revalidate;
+        this.staleWhileRevalidate = staleWhileRevalidate;
+        this.noCacheFields = Collections.unmodifiableSet(noCacheFields);
     }
 
 
@@ -211,12 +223,21 @@ final class CacheControl {
      * @return The stale-while-revalidate value.
      */
     public long getStaleWhileRevalidate() {
-        return stale_while_revalidate;
+        return staleWhileRevalidate;
+    }
+
+    /**
+     * Returns an unmodifiable set of field names specified in the "no-cache" directive of the Cache-Control header.
+     *
+     * @return The set of field names specified in the "no-cache" directive.
+     */
+    public Set<String> getNoCacheFields() {
+        return noCacheFields;
     }
 
     /**
      * Returns a string representation of the {@code CacheControl} object, including the max-age, shared-max-age, no-cache,
-     * no-store, private, must-revalidate, proxy-revalidate, and public values.
+     * no-store, private, must-revalidate, proxy-revalidate, public values, and noCacheFields.
      *
      * @return A string representation of the object.
      */
@@ -226,12 +247,13 @@ final class CacheControl {
                 "maxAge=" + maxAge +
                 ", sharedMaxAge=" + sharedMaxAge +
                 ", noCache=" + noCache +
+                ", noCacheFields=" + noCacheFields +
                 ", noStore=" + noStore +
                 ", cachePrivate=" + cachePrivate +
                 ", mustRevalidate=" + mustRevalidate +
                 ", proxyRevalidate=" + proxyRevalidate +
                 ", cachePublic=" + cachePublic +
-                ", stale_while_revalidate=" + stale_while_revalidate +
+                ", stale_while_revalidate=" + staleWhileRevalidate +
                 '}';
     }
 }
\ No newline at end of file
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheControlHeaderParser.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheControlHeaderParser.java
index af439698b..d9e22720c 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheControlHeaderParser.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CacheControlHeaderParser.java
@@ -27,6 +27,8 @@
 package org.apache.hc.client5.http.impl.cache;
 
 import java.util.BitSet;
+import java.util.HashSet;
+import java.util.Set;
 
 import org.apache.hc.client5.http.cache.HeaderConstants;
 import org.apache.hc.core5.annotation.Contract;
@@ -84,6 +86,8 @@ class CacheControlHeaderParser {
      */
     private static final BitSet VALUE_DELIMS = Tokenizer.INIT_BITSET(EQUAL_CHAR, ',');
 
+    private static final BitSet VALUE_NO_VACHE_DELIMS = Tokenizer.INIT_BITSET(',');
+
     /**
      * The token parser used to extract values from the header.
      */
@@ -141,6 +145,9 @@ class CacheControlHeaderParser {
         boolean cachePublic = false;
         long staleWhileRevalidate = -1;
 
+        // Declare a new Set variable at the beginning of the method to store the field-names
+        final Set<String> noCacheFields = new HashSet<>();
+
         while (!cursor.atEnd()) {
             final String name = tokenParser.parseToken(buffer, cursor, TOKEN_DELIMS);
             String value = null;
@@ -162,6 +169,22 @@ class CacheControlHeaderParser {
                 mustRevalidate = true;
             } else if (name.equalsIgnoreCase(HeaderConstants.CACHE_CONTROL_NO_CACHE)) {
                 noCache = true;
+                if (value != null) {
+                    final String directives = buffer.substring(cursor.getPos() - value.length() - 1, buffer.length());
+                    int start = 0;
+                    int end;
+                    while (start < directives.length()) {
+                        end = directives.indexOf(',', start);
+                        if (end == -1) {
+                            end = directives.length();
+                        }
+                        final String fieldName = directives.substring(start, end).trim();
+                        if (!fieldName.contains("=")) {
+                            noCacheFields.add(fieldName);
+                        }
+                        start = end + 1;
+                    }
+                }
             } else if (name.equalsIgnoreCase(HeaderConstants.CACHE_CONTROL_NO_STORE)) {
                 noStore = true;
             } else if (name.equalsIgnoreCase(HeaderConstants.PRIVATE)) {
@@ -174,7 +197,7 @@ class CacheControlHeaderParser {
                 staleWhileRevalidate = parseSeconds(name, value);
             }
         }
-        return new CacheControl(maxAge, sharedMaxAge, mustRevalidate, noCache, noStore, cachePrivate, proxyRevalidate, cachePublic, staleWhileRevalidate);
+        return new CacheControl(maxAge, sharedMaxAge, mustRevalidate, noCache, noStore, cachePrivate, proxyRevalidate, cachePublic, staleWhileRevalidate, noCacheFields);
     }
 
     private static long parseSeconds(final String name, final String value) {
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExec.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExec.java
index 9510a9a26..b33942bf5 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExec.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExec.java
@@ -286,7 +286,21 @@ class CachingExec extends CachingExecBase implements ExecChainHandler {
         context.setAttribute(HttpCoreContext.HTTP_REQUEST, request);
         recordCacheHit(target, request);
         final Instant now = getCurrentDate();
+
+
+        if (requestContainsNoCacheDirective(request)) {
+            // Revalidate with the server
+            return revalidateCacheEntry(target, request, scope, chain, entry);
+        }
+
         if (suitabilityChecker.canCachedResponseBeUsed(request, entry, now)) {
+            if (responseCachingPolicy.responseContainsNoCacheDirective(entry)) {
+                // Revalidate with the server due to no-cache directive in response
+                if (LOG.isDebugEnabled()) {
+                    LOG.debug("Revalidating with server due to no-cache directive in response.");
+                }
+                return revalidateCacheEntry(target, request, scope, chain, entry);
+            }
             LOG.debug("Cache hit");
             try {
                 return convert(generateCachedResponse(request, context, entry, now), scope);
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExecBase.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExecBase.java
index 303cc82a1..d31605160 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExecBase.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/CachingExecBase.java
@@ -369,4 +369,26 @@ public class CachingExecBase {
             }
         }
     }
+
+    /**
+     * Checks if the provided HttpRequest contains a 'no-cache' directive in its Cache-Control header.
+     * <p>
+     * According to the HTTP/1.1 specification, a 'no-cache' directive in a request message means
+     * that the client allows a stored response to be used only if it is first validated with the
+     * origin server (or with an intermediate cache that has a fresh response). This directive
+     * applies to both shared and private caches.
+     *
+     * @param request the HttpRequest to check for the 'no-cache' directive
+     * @return true if the 'no-cache' directive is present in the Cache-Control header of the request,
+     * false otherwise
+     */
+    boolean requestContainsNoCacheDirective(final HttpRequest request) {
+        final Header cacheControlHeader = request.getFirstHeader(HttpHeaders.CACHE_CONTROL);
+        if (cacheControlHeader == null) {
+            return false;
+        } else {
+            final CacheControl cacheControl = CacheControlHeaderParser.INSTANCE.parse(cacheControlHeader);
+            return cacheControl.isNoCache();
+        }
+    }
 }
diff --git a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ResponseCachingPolicy.java b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ResponseCachingPolicy.java
index bcd82697c..8b0783441 100644
--- a/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ResponseCachingPolicy.java
+++ b/httpclient5-cache/src/main/java/org/apache/hc/client5/http/impl/cache/ResponseCachingPolicy.java
@@ -270,11 +270,29 @@ class ResponseCachingPolicy {
         return status < 500 || status > 505;
     }
 
+    /**
+     * Determines whether the given CacheControl object indicates that the response is explicitly non-cacheable.
+     *
+     * @param cacheControl the CacheControl object representing the cache-control directive(s) from the HTTP response.
+     * @return true if the response is explicitly non-cacheable according to the cache-control directive(s),
+     * false otherwise.
+     * <p>
+     * When cacheControl is non-null:
+     * - Returns true if the response contains "no-store" or (if sharedCache is true) "private" cache-control directives.
+     * - If the response contains the "no-cache" directive, it is considered cacheable, but requires validation against
+     * the origin server before use. In this case, the method returns false.
+     * - Returns false for other cache-control directives, implying the response is cacheable.
+     * <p>
+     * When cacheControl is null, returns false, implying the response is cacheable.
+     */
     protected boolean isExplicitlyNonCacheable(final CacheControl cacheControl) {
         if (cacheControl == null) {
             return false;
-        }else {
-            return cacheControl.isNoStore() || cacheControl.isNoCache() || (sharedCache && cacheControl.isCachePrivate());
+        } else {
+            // The response is considered explicitly non-cacheable if it contains
+            // "no-store" or (if sharedCache is true) "private" directives.
+            // Note that "no-cache" is considered cacheable but requires validation before use.
+            return cacheControl.isNoStore() || (sharedCache && cacheControl.isCachePrivate());
         }
     }
 
@@ -525,4 +543,48 @@ class ResponseCachingPolicy {
         }
     }
 
+    /**
+     * Determines if the given {@link HttpCacheEntry} requires revalidation based on the presence of the {@code no-cache} directive
+     * in the Cache-Control header.
+     * <p>
+     * The method returns true in the following cases:
+     * - If the {@code no-cache} directive is present without any field names.
+     * - If the {@code no-cache} directive is present with field names, and at least one of these field names is present
+     * in the headers of the {@link HttpCacheEntry}.
+     * <p>
+     * If the {@code no-cache} directive is not present in the Cache-Control header, the method returns {@code false}.
+     *
+     * @param entry the  {@link HttpCacheEntry} containing the headers to check for the {@code no-cache} directive.
+     * @return true if revalidation is required based on the {@code no-cache} directive, {@code false} otherwise.
+     */
+    boolean responseContainsNoCacheDirective(final HttpCacheEntry entry) {
+        final CacheControl responseCacheControl = parseCacheControlHeader(entry);
+
+        if (responseCacheControl != null) {
+            final Set<String> noCacheFields = responseCacheControl.getNoCacheFields();
+
+            // If no-cache directive is present and has no field names
+            if (responseCacheControl.isNoCache() && noCacheFields.isEmpty()) {
+                LOG.debug("No-cache directive present without field names. Revalidation required.");
+                return true;
+            }
+
+            // If no-cache directive is present with field names
+            if (responseCacheControl.isNoCache()) {
+                for (final String field : noCacheFields) {
+                    if (entry.getFirstHeader(field) != null) {
+                        if (LOG.isDebugEnabled()) {
+                            LOG.debug("No-cache directive field '{}' found in response headers. Revalidation required.", field);
+                        }
+                        return true;
+                    }
+                }
+            }
+        }
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("No no-cache directives found in response headers. No revalidation required.");
+        }
+        return false;
+    }
+
 }
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 df25500be..3d7b645b7 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
@@ -166,4 +166,16 @@ public class CacheControlParserTest {
 
         assertEquals(120, cacheControl.getStaleWhileRevalidate());
     }
+
+    @Test
+    public void testParseNoCacheFields() {
+        final Header header = new BasicHeader("Cache-Control", "no-cache=Set-Cookie, Content-Language, stale-while-revalidate=120");
+        final CacheControl cacheControl = parser.parse(header);
+
+        assertTrue(cacheControl.isNoCache());
+        assertEquals(2, cacheControl.getNoCacheFields().size());
+        assertTrue(cacheControl.getNoCacheFields().contains("Set-Cookie"));
+        assertTrue(cacheControl.getNoCacheFields().contains("Content-Language"));
+        assertEquals(120, cacheControl.getStaleWhileRevalidate());
+    }
 }
diff --git a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCachingExecChain.java b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCachingExecChain.java
index 2b5296ca9..a1b682439 100644
--- a/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCachingExecChain.java
+++ b/httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/TestCachingExecChain.java
@@ -234,7 +234,7 @@ public class TestCachingExecChain {
 
         final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
         final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
-        resp1.setHeader("Cache-Control", "no-cache");
+        resp1.setHeader("Cache-Control", "no-store");
 
         Mockito.when(mockStorage.getEntry(Mockito.any())).thenReturn(null);
         Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
@@ -1535,4 +1535,38 @@ public class TestCachingExecChain {
         Assertions.assertTrue(warningHeader.get().getValue().contains("110"));
     }
 
+    @Test
+    public void testNoCacheFieldsRevalidation() throws Exception {
+        final Instant now = Instant.now();
+        final Instant fiveSecondsAgo = now.minusSeconds(5);
+
+        final ClassicHttpRequest req1 = HttpTestUtils.makeDefaultRequest();
+        final ClassicHttpResponse resp1 = HttpTestUtils.make200Response();
+        resp1.setHeader("Date", DateUtils.formatStandardDate(now));
+        resp1.setHeader("Cache-Control", "max-age=3100, no-cache=Set-Cookie, Content-Language");
+        resp1.setHeader("Content-Language", "en-US");
+        resp1.setHeader("Etag", "\"new-etag\"");
+
+        final ClassicHttpRequest req2 = HttpTestUtils.makeDefaultRequest();
+        //req2.setHeader("Cache-Control", "no-cache=\"etag\"");
+        final ClassicHttpResponse resp2 = HttpTestUtils.make200Response();
+        resp2.setHeader("ETag", "\"old-etag\"");
+        resp2.setHeader("Date", DateUtils.formatStandardDate(fiveSecondsAgo));
+        resp2.setHeader("Cache-Control", "max-age=3600");
+
+        final ClassicHttpRequest req3 = HttpTestUtils.makeDefaultRequest();
+
+        Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp1);
+
+        execute(req1);
+
+        Mockito.when(mockExecChain.proceed(Mockito.any(), Mockito.any())).thenReturn(resp2);
+
+        execute(req2);
+        final ClassicHttpResponse result = execute(req3);
+
+        // Verify that the backend was called to revalidate the response, as per the new logic
+        Mockito.verify(mockExecChain, Mockito.times(5)).proceed(Mockito.any(), Mockito.any());
+    }
+
 }
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 be291f88b..4b19e90f1 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
@@ -45,6 +45,7 @@ import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
+import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 public class TestResponseCachingPolicy {
@@ -307,14 +308,14 @@ public class TestResponseCachingPolicy {
     public void testIsGetWithNoCacheCacheable() {
         response.addHeader("Cache-Control", "no-cache");
 
-        Assertions.assertFalse(policy.isResponseCacheable("GET", response));
+        Assertions.assertTrue(policy.isResponseCacheable("GET", response));
     }
 
     @Test
     public void testIsHeadWithNoCacheCacheable() {
         response.addHeader("Cache-Control", "no-cache");
 
-        Assertions.assertFalse(policy.isResponseCacheable("HEAD", response));
+        Assertions.assertTrue(policy.isResponseCacheable("HEAD", response));
     }
 
     @Test
@@ -349,14 +350,14 @@ public class TestResponseCachingPolicy {
     public void testIsGetWithNoCacheEmbeddedInListCacheable() {
         response.addHeader("Cache-Control", "public, no-cache");
 
-        Assertions.assertFalse(policy.isResponseCacheable("GET", response));
+        Assertions.assertTrue(policy.isResponseCacheable("GET", response));
     }
 
     @Test
     public void testIsHeadWithNoCacheEmbeddedInListCacheable() {
         response.addHeader("Cache-Control", "public, no-cache");
 
-        Assertions.assertFalse(policy.isResponseCacheable("HEAD", response));
+        Assertions.assertTrue(policy.isResponseCacheable("HEAD", response));
     }
 
     @Test
@@ -938,7 +939,7 @@ public class TestResponseCachingPolicy {
 
         response = new BasicHttpResponse(HttpStatus.SC_OK, "");
         response.setHeader(HttpHeaders.DATE, DateUtils.formatStandardDate(Instant.now()));
-       response.setHeader(HttpHeaders.EXPIRES, DateUtils.formatStandardDate(Instant.now().plus(tenSecondsFromNow)));
+        response.setHeader(HttpHeaders.EXPIRES, DateUtils.formatStandardDate(Instant.now().plus(tenSecondsFromNow)));
 
 
         // Create ResponseCachingPolicy instance and test the method
@@ -1011,4 +1012,36 @@ public class TestResponseCachingPolicy {
         response.setHeader("Date", DateUtils.formatStandardDate(now));
         assertTrue(policy.isResponseCacheable(request, response));
     }
-}
+
+    @Test
+    void testIsResponseCacheableNoCache() {
+        // Set up test data
+        response = new BasicHttpResponse(HttpStatus.SC_OK, "");
+        response.setHeader(HttpHeaders.DATE, DateUtils.formatStandardDate(Instant.now()));
+
+        // Create ResponseCachingPolicy instance and test the method
+        policy = new ResponseCachingPolicy(0, true, false, false, false);
+        request = new BasicHttpRequest("GET", "/foo");
+        response.addHeader(HttpHeaders.CACHE_CONTROL, "no-cache");
+        final boolean isCacheable = policy.isResponseCacheable(request, response);
+
+        // Verify the result
+        assertTrue(isCacheable);
+    }
+
+    @Test
+    void testIsResponseCacheableNoStore() {
+        // Set up test data
+        response = new BasicHttpResponse(HttpStatus.SC_OK, "");
+        response.setHeader(HttpHeaders.DATE, DateUtils.formatStandardDate(Instant.now()));
+
+        // Create ResponseCachingPolicy instance and test the method
+        policy = new ResponseCachingPolicy(0, true, false, false, false);
+        request = new BasicHttpRequest("GET", "/foo");
+        response.addHeader(HttpHeaders.CACHE_CONTROL, "no-store");
+        final boolean isCacheable = policy.isResponseCacheable(request, response);
+
+        // Verify the result
+        assertFalse(isCacheable);
+    }
+}
\ No newline at end of file