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"));
