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

luigidemasi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-kit.git

commit 30a7a2a2b7946a902695e4fecf4d5adfe80e68c7
Author: Andrea Cosentino <[email protected]>
AuthorDate: Mon Nov 2 12:44:15 2026 +0100

    Fix #137: preserve If-None-Match across validated redirect hops
    
    The manual redirect loop added in 182dcc91 rebuilt each hop request with
    Accept only, dropping the If-None-Match validator that fetchManifest() sets,
    so a manifest behind a redirect re-downloaded on every resolution instead of
    revalidating with 304. send() now re-sends the two request headers the
    downloader sets, Accept and If-None-Match, on every validated hop.
    
    Adds IndexDownloaderTest.manifestRedirectKeepsConditionalRevalidation: two
    resolutions through a loopback redirect; the target must receive
    If-None-Match and answer 304, and the archive must not download again.
    
    Co-Authored-By: Claude Fable 5.1 <[email protected]>
    Signed-off-by: Andrea Cosentino <[email protected]>
---
 .../camel/kit/knowledge/mcp/IndexDownloader.java   |  8 +++++-
 .../kit/knowledge/mcp/IndexDownloaderTest.java     | 31 ++++++++++++++++++++++
 2 files changed, 38 insertions(+), 1 deletion(-)

diff --git 
a/knowledge/mcp/src/main/java/org/apache/camel/kit/knowledge/mcp/IndexDownloader.java
 
b/knowledge/mcp/src/main/java/org/apache/camel/kit/knowledge/mcp/IndexDownloader.java
index ec940a26..cf2a0759 100644
--- 
a/knowledge/mcp/src/main/java/org/apache/camel/kit/knowledge/mcp/IndexDownloader.java
+++ 
b/knowledge/mcp/src/main/java/org/apache/camel/kit/knowledge/mcp/IndexDownloader.java
@@ -72,6 +72,8 @@ public class IndexDownloader {
     private static final Duration LOCK_TIMEOUT = Duration.ofSeconds(5);
     static final int FORMAT_VERSION = 1;
     static final String RELEASE_PREFIX = "knowledge-index-v" + FORMAT_VERSION 
+ "-";
+    /** Request headers re-sent on a validated redirect hop; the downloader 
sets no others. */
+    private static final List<String> FORWARDED_REQUEST_HEADERS = 
List.of("Accept", "If-None-Match");
     private static final int MAX_RELEASE_PAGES = 10;
     private static final int MAX_RELEASE_PAGE_BYTES = 2 * 1024 * 1024;
 
@@ -635,7 +637,11 @@ public class IndexDownloader {
                 throw new HttpTimeoutException("Index redirect handling timed 
out from " + request.uri());
             }
             HttpRequest.Builder builder = 
HttpRequest.newBuilder(next).timeout(Duration.ofNanos(remaining)).GET();
-            current.headers().firstValue("Accept").ifPresent(accept -> 
builder.header("Accept", accept));
+            // Carry only the request headers this downloader sets: content 
negotiation and the manifest validator,
+            // so a redirected manifest still revalidates with If-None-Match. 
Nothing else crosses a hop.
+            for (String name : FORWARDED_REQUEST_HEADERS) {
+                current.headers().firstValue(name).ifPresent(value -> 
builder.header(name, value));
+            }
             current = builder.build();
         }
     }
diff --git 
a/knowledge/mcp/src/test/java/org/apache/camel/kit/knowledge/mcp/IndexDownloaderTest.java
 
b/knowledge/mcp/src/test/java/org/apache/camel/kit/knowledge/mcp/IndexDownloaderTest.java
index 24707d10..36dfedf1 100644
--- 
a/knowledge/mcp/src/test/java/org/apache/camel/kit/knowledge/mcp/IndexDownloaderTest.java
+++ 
b/knowledge/mcp/src/test/java/org/apache/camel/kit/knowledge/mcp/IndexDownloaderTest.java
@@ -305,6 +305,37 @@ class IndexDownloaderTest {
         }
     }
 
+    @Test
+    void manifestRedirectKeepsConditionalRevalidation() throws Exception {
+        publish("v1", "index-bytes", null);
+        try (HttpFixture http = new HttpFixture()) {
+            int backendPort = http.server.getAddress().getPort();
+            HttpServer redirector = HttpServer.create(new 
InetSocketAddress("127.0.0.1", 0), 0);
+            redirector.createContext("/", exchange -> {
+                try (exchange) {
+                    exchange.getResponseHeaders()
+                            .set("Location", "http://127.0.0.1:"; + backendPort 
+ exchange.getRequestURI().getPath());
+                    exchange.sendResponseHeaders(302, -1);
+                }
+            });
+            redirector.start();
+            try {
+                String url = "http://127.0.0.1:"; + 
redirector.getAddress().getPort() + "/index.json";
+                Path installed = downloader(null, url).resolve().dir();
+                assertEquals(1, http.downloads.get());
+                assertNull(http.ifNoneMatch, "The first resolution has no 
validator to send");
+
+                assertEquals(installed, downloader(null, url).resolve().dir(),
+                        "a 304 behind the redirect must reuse the installed 
index");
+                assertEquals("\"v1\"", http.ifNoneMatch, "If-None-Match must 
survive the validated redirect hop");
+                assertEquals(1, http.notModified.get(), "the redirect target 
must answer 304");
+                assertEquals(1, http.downloads.get(), "HTTP 304 must not 
download the archive again");
+            } finally {
+                redirector.stop(0);
+            }
+        }
+    }
+
     @Test
     void releasesEndpointOverNonLoopbackHttpIsRejected() {
         IndexDownloader downloader = new 
IndexDownloader(discoveryConfig("http://example.org/releases";));

Reply via email to