From d67abb66c55ee39bfc6286ef7fdf06b1241314db Mon Sep 17 00:00:00 2001 From: Francois de Parscau Date: Mon, 12 Oct 2026 18:00:57 +0200 Subject: [PATCH 1/5] fix(DPE-4430): CAMEL-23597 align Exchange header prefix constants with Camel naming convention --- .../src/main/docs/solr-component.adoc | 4 +- .../camel/component/solr/SolrConstants.java | 4 +- .../solr/SolrComponentTestSupport.java | 2 +- .../component/solr/SolrServerMissingTest.java | 2 +- .../solr/SolrUpdateAutocommitTest.java | 37 ++++++++++--------- .../camel/component/solr/SolrUpdateTest.java | 37 ++++++++++--------- 6 files changed, 44 insertions(+), 42 deletions(-) diff --git a/components/camel-solr/src/main/docs/solr-component.adoc b/components/camel-solr/src/main/docs/solr-component.adoc index c4e2258f3c3ae..8aba35b927f7b 100644 --- a/components/camel-solr/src/main/docs/solr-component.adoc +++ b/components/camel-solr/src/main/docs/solr-component.adoc @@ -69,7 +69,7 @@ the following. Some operations also require the message body to be set. |======================================================================= |Operation |Message body |Description -|`INSERT/INSERT_STREAMING` |n/a |adds an index using message headers (must be prefixed with "SolrField.") +|`INSERT/INSERT_STREAMING` |n/a |adds an index using message headers (must be prefixed with "CamelSolrField.") |`INSERT/INSERT_STREAMING` |File |adds an index using the given File (using `ContentStreamUpdateRequest`) @@ -127,7 +127,7 @@ from("direct:commit") INSERT - + ${body} diff --git a/components/camel-solr/src/main/java/org/apache/camel/component/solr/SolrConstants.java b/components/camel-solr/src/main/java/org/apache/camel/component/solr/SolrConstants.java index fff9447770b2d..04d9f8d1fd880 100644 --- a/components/camel-solr/src/main/java/org/apache/camel/component/solr/SolrConstants.java +++ b/components/camel-solr/src/main/java/org/apache/camel/component/solr/SolrConstants.java @@ -25,7 +25,7 @@ public final class SolrConstants { public static final String CLIENT = "CamelSolrClient"; @Metadata(description = "The collection to execute the request again.", javaType = "String") public static final String COLLECTION = "CamelSolrCollection"; - public static final String FIELD = "SolrField."; + public static final String FIELD = "CamelSolrField."; @Metadata(description = "The operation to perform.", javaType = "String") public static final String OPERATION = "SolrOperation"; public static final String OPERATION_COMMIT = "COMMIT"; @@ -39,7 +39,7 @@ public final class SolrConstants { public static final String OPERATION_DELETE_BY_ID = "DELETE_BY_ID"; public static final String OPERATION_DELETE_BY_QUERY = "DELETE_BY_QUERY"; public static final String OPERATION_QUERY = "QUERY"; - public static final String PARAM = "SolrParam."; + public static final String PARAM = "CamelSolrParam."; @Metadata(description = "The query to execute.", javaType = "String") public static final String QUERY_STRING = "CamelSolrQueryString"; @Metadata(description = "The content type.", javaType = "String") diff --git a/components/camel-solr/src/test/java/org/apache/camel/component/solr/SolrComponentTestSupport.java b/components/camel-solr/src/test/java/org/apache/camel/component/solr/SolrComponentTestSupport.java index ffaf7beec9d4f..a3357f84cb55b 100644 --- a/components/camel-solr/src/test/java/org/apache/camel/component/solr/SolrComponentTestSupport.java +++ b/components/camel-solr/src/test/java/org/apache/camel/component/solr/SolrComponentTestSupport.java @@ -71,7 +71,7 @@ String solrRouteAutocommitUri() { protected void solrInsertTestEntry(String id) { Map headers = new HashMap<>(); headers.put(SolrConstants.OPERATION, SolrConstants.OPERATION_INSERT); - headers.put("SolrField.id", id); + headers.put("CamelSolrField.id", id); template.sendBodyAndHeaders("direct:start", "", headers); } diff --git a/components/camel-solr/src/test/java/org/apache/camel/component/solr/SolrServerMissingTest.java b/components/camel-solr/src/test/java/org/apache/camel/component/solr/SolrServerMissingTest.java index 40c1451c5a618..6f9e6510305dc 100644 --- a/components/camel-solr/src/test/java/org/apache/camel/component/solr/SolrServerMissingTest.java +++ b/components/camel-solr/src/test/java/org/apache/camel/component/solr/SolrServerMissingTest.java @@ -28,7 +28,7 @@ public class SolrServerMissingTest extends SolrTestSupport { public void indexSingleDocumentToNonexistentServer() { Exchange exchange = createExchangeWithBody(null); exchange.getIn().setHeader(SolrConstants.OPERATION, SolrConstants.OPERATION_INSERT); - exchange.getIn().setHeader("SolrField.id", "MA147LL/A"); + exchange.getIn().setHeader("CamelSolrField.id", "MA147LL/A"); template.send("direct:start", exchange); diff --git a/components/camel-solr/src/test/java/org/apache/camel/component/solr/SolrUpdateAutocommitTest.java b/components/camel-solr/src/test/java/org/apache/camel/component/solr/SolrUpdateAutocommitTest.java index cba1918b26673..4e174e377a6b1 100644 --- a/components/camel-solr/src/test/java/org/apache/camel/component/solr/SolrUpdateAutocommitTest.java +++ b/components/camel-solr/src/test/java/org/apache/camel/component/solr/SolrUpdateAutocommitTest.java @@ -110,7 +110,7 @@ public void testInsertStreaming() throws Exception { Exchange exchange = createExchangeWithBody(null); exchange.getIn().setHeader(SolrConstants.OPERATION, SolrConstants.OPERATION_INSERT_STREAMING); - exchange.getIn().setHeader("SolrField.id", "MA147LL/A"); + exchange.getIn().setHeader("CamelSolrField.id", "MA147LL/A"); template.send("direct:startAutoCommit", exchange); Thread.sleep(500); @@ -124,7 +124,7 @@ public void testInsertStreaming() throws Exception { public void indexSingleDocumentOnlyWithId() throws Exception { Exchange exchange = createExchangeWithBody(null); exchange.getIn().setHeader(SolrConstants.OPERATION, SolrConstants.OPERATION_INSERT); - exchange.getIn().setHeader("SolrField.id", "MA147LL/A"); + exchange.getIn().setHeader("CamelSolrField.id", "MA147LL/A"); template.send("direct:startAutoCommit", exchange); @@ -139,7 +139,7 @@ public void indexSingleDocumentOnlyWithId() throws Exception { public void caughtSolrExceptionIsHandledElegantly() { Exchange exchange = createExchangeWithBody(null); exchange.getIn().setHeader(SolrConstants.OPERATION, SolrConstants.OPERATION_INSERT); - exchange.getIn().setHeader("SolrField.name", "Missing required field throws exception."); + exchange.getIn().setHeader("CamelSolrField.name", "Missing required field throws exception."); template.send("direct:startAutoCommit", exchange); @@ -151,9 +151,9 @@ public void caughtSolrExceptionIsHandledElegantly() { public void setHeadersAsSolrFields() throws Exception { Exchange exchange = createExchangeWithBody("Body is ignored"); exchange.getIn().setHeader(SolrConstants.OPERATION, SolrConstants.OPERATION_INSERT); - exchange.getIn().setHeader("SolrField.id", "MA147LL/A"); - exchange.getIn().setHeader("SolrField.name", "Apple 60 GB iPod with Video Playback Black"); - exchange.getIn().setHeader("SolrField.manu", "Apple Computer Inc."); + exchange.getIn().setHeader("CamelSolrField.id", "MA147LL/A"); + exchange.getIn().setHeader("CamelSolrField.name", "Apple 60 GB iPod with Video Playback Black"); + exchange.getIn().setHeader("CamelSolrField.manu", "Apple Computer Inc."); template.send("direct:startAutoCommit", exchange); @@ -172,8 +172,8 @@ public void setMultiValuedFieldInHeader() throws Exception { String[] categories = { "electronics", "apple" }; Exchange exchange = createExchangeWithBody("Test body for iPod."); exchange.getIn().setHeader(SolrConstants.OPERATION, SolrConstants.OPERATION_INSERT); - exchange.getIn().setHeader("SolrField.id", "MA147LL/A"); - exchange.getIn().setHeader("SolrField.cat", categories); + exchange.getIn().setHeader("CamelSolrField.id", "MA147LL/A"); + exchange.getIn().setHeader("CamelSolrField.cat", categories); template.send("direct:startAutoCommit", exchange); @@ -191,9 +191,9 @@ public void setMultiValuedFieldInHeader() throws Exception { public void indexDocuments() throws Exception { Exchange exchange = createExchangeWithBody(null); exchange.getIn().setHeader(SolrConstants.OPERATION, SolrConstants.OPERATION_INSERT); - exchange.getIn().setHeader("SolrField.id", "MA147LL/A"); - exchange.getIn().setHeader("SolrField.name", "Apple 60 GB iPod with Video Playback Black"); - exchange.getIn().setHeader("SolrField.manu", "Apple Computer Inc."); + exchange.getIn().setHeader("CamelSolrField.id", "MA147LL/A"); + exchange.getIn().setHeader("CamelSolrField.name", "Apple 60 GB iPod with Video Playback Black"); + exchange.getIn().setHeader("CamelSolrField.manu", "Apple Computer Inc."); template.send("direct:startAutoCommit", exchange); QueryResponse afterCommitResponse = executeSolrQuery("*:*"); @@ -205,9 +205,9 @@ public void indexDocuments() throws Exception { public void invalidSolrParametersAreIgnored() throws Exception { Exchange exchange = createExchangeWithBody(null); exchange.getIn().setHeader(SolrConstants.OPERATION, SolrConstants.OPERATION_INSERT); - exchange.getIn().setHeader("SolrField.id", "MA147LL/A"); - exchange.getIn().setHeader("SolrField.name", "Apple 60 GB iPod with Video Playback Black"); - exchange.getIn().setHeader("SolrParam.invalid-param", "this is ignored"); + exchange.getIn().setHeader("CamelSolrField.id", "MA147LL/A"); + exchange.getIn().setHeader("CamelSolrField.name", "Apple 60 GB iPod with Video Playback Black"); + exchange.getIn().setHeader("CamelSolrParam.invalid-param", "this is ignored"); template.send("direct:startAutoCommit", exchange); @@ -299,9 +299,10 @@ public void indexDocumentsToCSVUpdateHandlerWithParameters() throws Exception { Exchange exchange = createExchangeWithBody(new File("src/test/resources/data/books.csv")); exchange.getIn().setHeader(SolrConstants.OPERATION, SolrConstants.OPERATION_INSERT); exchange.getIn().setHeader(SolrConstants.PARAM + UpdateParams.ASSUME_CONTENT_TYPE, "text/csv"); - exchange.getIn().setHeader("SolrParam.fieldnames", "id,cat,name,price,inStock,author_t,series_t,sequence_i,genre_s"); - exchange.getIn().setHeader("SolrParam.skip", "cat,sequence_i,genre_s"); - exchange.getIn().setHeader("SolrParam.skipLines", 1); + exchange.getIn().setHeader("CamelSolrParam.fieldnames", + "id,cat,name,price,inStock,author_t,series_t,sequence_i,genre_s"); + exchange.getIn().setHeader("CamelSolrParam.skip", "cat,sequence_i,genre_s"); + exchange.getIn().setHeader("CamelSolrParam.skipLines", 1); template.send("direct:startAutoCommit", exchange); @@ -320,7 +321,7 @@ public void indexPDFDocumentToExtractingRequestHandler() throws Exception { Exchange exchange = createExchangeWithBody(new File("src/test/resources/data/tutorial.pdf")); exchange.getIn().setHeader(SolrConstants.OPERATION, SolrConstants.OPERATION_INSERT); - exchange.getIn().setHeader("SolrParam.literal.id", "tutorial.pdf"); + exchange.getIn().setHeader("CamelSolrParam.literal.id", "tutorial.pdf"); template.send("direct:startAutoCommit", exchange); diff --git a/components/camel-solr/src/test/java/org/apache/camel/component/solr/SolrUpdateTest.java b/components/camel-solr/src/test/java/org/apache/camel/component/solr/SolrUpdateTest.java index e0a34d9898290..b4fa6859785ea 100644 --- a/components/camel-solr/src/test/java/org/apache/camel/component/solr/SolrUpdateTest.java +++ b/components/camel-solr/src/test/java/org/apache/camel/component/solr/SolrUpdateTest.java @@ -124,7 +124,7 @@ public void testInsertStreaming() throws Exception { Exchange exchange = createExchangeWithBody(null); exchange.getIn().setHeader(SolrConstants.OPERATION, SolrConstants.OPERATION_INSERT_STREAMING); - exchange.getIn().setHeader("SolrField.id", "MA147LL/A"); + exchange.getIn().setHeader("CamelSolrField.id", "MA147LL/A"); template.send("direct:start", exchange); Thread.sleep(500); @@ -140,7 +140,7 @@ public void testInsertStreaming() throws Exception { public void indexSingleDocumentOnlyWithId() throws Exception { Exchange exchange = createExchangeWithBody(null); exchange.getIn().setHeader(SolrConstants.OPERATION, SolrConstants.OPERATION_INSERT); - exchange.getIn().setHeader("SolrField.id", "MA147LL/A"); + exchange.getIn().setHeader("CamelSolrField.id", "MA147LL/A"); template.send("direct:start", exchange); solrCommit(); @@ -156,7 +156,7 @@ public void indexSingleDocumentOnlyWithId() throws Exception { public void caughtSolrExceptionIsHandledElegantly() { Exchange exchange = createExchangeWithBody(null); exchange.getIn().setHeader(SolrConstants.OPERATION, SolrConstants.OPERATION_INSERT); - exchange.getIn().setHeader("SolrField.name", "Missing required field throws exception."); + exchange.getIn().setHeader("CamelSolrField.name", "Missing required field throws exception."); template.send("direct:start", exchange); @@ -168,9 +168,9 @@ public void caughtSolrExceptionIsHandledElegantly() { public void setHeadersAsSolrFields() throws Exception { Exchange exchange = createExchangeWithBody("Body is ignored"); exchange.getIn().setHeader(SolrConstants.OPERATION, SolrConstants.OPERATION_INSERT); - exchange.getIn().setHeader("SolrField.id", "MA147LL/A"); - exchange.getIn().setHeader("SolrField.name", "Apple 60 GB iPod with Video Playback Black"); - exchange.getIn().setHeader("SolrField.manu", "Apple Computer Inc."); + exchange.getIn().setHeader("CamelSolrField.id", "MA147LL/A"); + exchange.getIn().setHeader("CamelSolrField.name", "Apple 60 GB iPod with Video Playback Black"); + exchange.getIn().setHeader("CamelSolrField.manu", "Apple Computer Inc."); template.send("direct:start", exchange); solrCommit(); @@ -190,8 +190,8 @@ public void setMultiValuedFieldInHeader() throws Exception { String[] categories = { "electronics", "apple" }; Exchange exchange = createExchangeWithBody("Test body for iPod."); exchange.getIn().setHeader(SolrConstants.OPERATION, SolrConstants.OPERATION_INSERT); - exchange.getIn().setHeader("SolrField.id", "MA147LL/A"); - exchange.getIn().setHeader("SolrField.cat", categories); + exchange.getIn().setHeader("CamelSolrField.id", "MA147LL/A"); + exchange.getIn().setHeader("CamelSolrField.cat", categories); template.send("direct:start", exchange); solrCommit(); @@ -210,9 +210,9 @@ public void setMultiValuedFieldInHeader() throws Exception { public void indexDocumentsAndThenCommit() throws Exception { Exchange exchange = createExchangeWithBody(null); exchange.getIn().setHeader(SolrConstants.OPERATION, SolrConstants.OPERATION_INSERT); - exchange.getIn().setHeader("SolrField.id", "MA147LL/A"); - exchange.getIn().setHeader("SolrField.name", "Apple 60 GB iPod with Video Playback Black"); - exchange.getIn().setHeader("SolrField.manu", "Apple Computer Inc."); + exchange.getIn().setHeader("CamelSolrField.id", "MA147LL/A"); + exchange.getIn().setHeader("CamelSolrField.name", "Apple 60 GB iPod with Video Playback Black"); + exchange.getIn().setHeader("CamelSolrField.manu", "Apple Computer Inc."); template.send("direct:start", exchange); QueryResponse response = executeSolrQuery("*:*"); @@ -230,9 +230,9 @@ public void indexDocumentsAndThenCommit() throws Exception { public void invalidSolrParametersAreIgnored() throws Exception { Exchange exchange = createExchangeWithBody(null); exchange.getIn().setHeader(SolrConstants.OPERATION, SolrConstants.OPERATION_INSERT); - exchange.getIn().setHeader("SolrField.id", "MA147LL/A"); - exchange.getIn().setHeader("SolrField.name", "Apple 60 GB iPod with Video Playback Black"); - exchange.getIn().setHeader("SolrParam.invalid-param", "this is ignored"); + exchange.getIn().setHeader("CamelSolrField.id", "MA147LL/A"); + exchange.getIn().setHeader("CamelSolrField.name", "Apple 60 GB iPod with Video Playback Black"); + exchange.getIn().setHeader("CamelSolrParam.invalid-param", "this is ignored"); template.send("direct:start", exchange); solrCommit(); @@ -325,9 +325,10 @@ public void indexDocumentsToCSVUpdateHandlerWithParameters() throws Exception { Exchange exchange = createExchangeWithBody(new File("src/test/resources/data/books.csv")); exchange.getIn().setHeader(SolrConstants.OPERATION, SolrConstants.OPERATION_INSERT); exchange.getIn().setHeader(SolrConstants.PARAM + UpdateParams.ASSUME_CONTENT_TYPE, "text/csv"); - exchange.getIn().setHeader("SolrParam.fieldnames", "id,cat,name,price,inStock,author_t,series_t,sequence_i,genre_s"); - exchange.getIn().setHeader("SolrParam.skip", "cat,sequence_i,genre_s"); - exchange.getIn().setHeader("SolrParam.skipLines", 1); + exchange.getIn().setHeader("CamelSolrParam.fieldnames", + "id,cat,name,price,inStock,author_t,series_t,sequence_i,genre_s"); + exchange.getIn().setHeader("CamelSolrParam.skip", "cat,sequence_i,genre_s"); + exchange.getIn().setHeader("CamelSolrParam.skipLines", 1); template.send("direct:start", exchange); solrCommit(); @@ -346,7 +347,7 @@ public void indexPDFDocumentToExtractingRequestHandler() throws Exception { Exchange exchange = createExchangeWithBody(new File("src/test/resources/data/tutorial.pdf")); exchange.getIn().setHeader(SolrConstants.OPERATION, SolrConstants.OPERATION_INSERT); - exchange.getIn().setHeader("SolrParam.literal.id", "tutorial.pdf"); + exchange.getIn().setHeader("CamelSolrParam.literal.id", "tutorial.pdf"); template.send("direct:start", exchange); solrCommit(); From 23d76ea333aec666da82b6a64b06891204f2edee Mon Sep 17 00:00:00 2001 From: Andrea Cosentino Date: Fri, 10 Jul 2026 15:19:30 +0200 Subject: [PATCH 2/5] [backport camel-4.14.x] CAMEL-23942: contain Azure Storage Blob/DataLake fileDir downloads within the configured directory (#24582) Signed-off-by: Andrea Cosentino Co-authored-by: Claude Opus 4.8 (1M context) --- .../storage/blob/operations/BlobOperations.java | 17 ++++++++++++++++- .../blob/operations/BlobOperationsTest.java | 13 +++++++++++++ .../operations/DataLakeFileOperations.java | 17 ++++++++++++++++- .../operations/DataLakeFileOperationTest.java | 14 ++++++++++++++ 4 files changed, 59 insertions(+), 2 deletions(-) diff --git a/components/camel-azure/camel-azure-storage-blob/src/main/java/org/apache/camel/component/azure/storage/blob/operations/BlobOperations.java b/components/camel-azure/camel-azure-storage-blob/src/main/java/org/apache/camel/component/azure/storage/blob/operations/BlobOperations.java index 9d3716bdebc5b..b6e5ec1da11c3 100644 --- a/components/camel-azure/camel-azure-storage-blob/src/main/java/org/apache/camel/component/azure/storage/blob/operations/BlobOperations.java +++ b/components/camel-azure/camel-azure-storage-blob/src/main/java/org/apache/camel/component/azure/storage/blob/operations/BlobOperations.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.nio.file.Path; import java.time.Duration; import java.time.OffsetDateTime; import java.util.Collections; @@ -129,7 +130,7 @@ public BlobOperationResponse downloadBlobToFile(final Exchange exchange) { throw new IllegalArgumentException("In order to download a blob, you will need to specify the fileDir in the URI"); } - final File fileToDownload = new File(fileDir, client.getBlobName()); + final File fileToDownload = resolveWithinDirectory(fileDir, client.getBlobName()); final BlobCommonRequestOptions commonRequestOptions = getCommonRequestOptions(exchange); final BlobRange blobRange = configurationProxy.getBlobRange(exchange); final ParallelTransferOptions parallelTransferOptions = configurationProxy.getParallelTransferOptions(exchange); @@ -484,4 +485,18 @@ private void closeInputStreamIfNeeded(InputStream inputStream) throws IOExceptio inputStream.close(); } } + + private static File resolveWithinDirectory(String fileDir, String name) { + final File target = new File(fileDir, name); + // normalize lexically (removes ./ and ../ segments) and compare on path-segment boundaries so a sibling + // directory whose name merely extends fileDir is not considered contained + final Path normalizedDir = new File(fileDir).toPath().normalize(); + final Path normalizedTarget = target.toPath().normalize(); + if (!normalizedTarget.startsWith(normalizedDir)) { + throw new IllegalArgumentException( + "Cannot download to file '" + name + + "' as it resolves outside the configured fileDir directory: " + fileDir); + } + return target; + } } diff --git a/components/camel-azure/camel-azure-storage-blob/src/test/java/org/apache/camel/component/azure/storage/blob/operations/BlobOperationsTest.java b/components/camel-azure/camel-azure-storage-blob/src/test/java/org/apache/camel/component/azure/storage/blob/operations/BlobOperationsTest.java index 801ae39fd115a..34c544ee677d6 100644 --- a/components/camel-azure/camel-azure-storage-blob/src/test/java/org/apache/camel/component/azure/storage/blob/operations/BlobOperationsTest.java +++ b/components/camel-azure/camel-azure-storage-blob/src/test/java/org/apache/camel/component/azure/storage/blob/operations/BlobOperationsTest.java @@ -119,6 +119,19 @@ void testGetBlob() throws IOException { assertEquals("tag1", response3.getHeaders().get(BlobConstants.E_TAG)); } + @Test + void testDownloadBlobToFileRejectsPathTraversalName() { + // a blob name is influenced by whoever writes to the container and may contain ../ segments; + // the download must stay within the configured fileDir + configuration.setFileDir("/tmp/camel-azure-blob-download"); + when(client.getBlobName()).thenReturn("../escaped/PROOF_PWNED"); + + final BlobOperations operations = new BlobOperations(configuration, client); + final Exchange exchange = new DefaultExchange(context); + + assertThrows(IllegalArgumentException.class, () -> operations.downloadBlobToFile(exchange)); + } + @Test void testUploadBlockBlob() throws Exception { // mocking diff --git a/components/camel-azure/camel-azure-storage-datalake/src/main/java/org/apache/camel/component/azure/storage/datalake/operations/DataLakeFileOperations.java b/components/camel-azure/camel-azure-storage-datalake/src/main/java/org/apache/camel/component/azure/storage/datalake/operations/DataLakeFileOperations.java index 72ab8856a898e..6a2c92a403af1 100644 --- a/components/camel-azure/camel-azure-storage-datalake/src/main/java/org/apache/camel/component/azure/storage/datalake/operations/DataLakeFileOperations.java +++ b/components/camel-azure/camel-azure-storage-datalake/src/main/java/org/apache/camel/component/azure/storage/datalake/operations/DataLakeFileOperations.java @@ -21,6 +21,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.nio.file.OpenOption; +import java.nio.file.Path; import java.time.Duration; import java.time.OffsetDateTime; import java.util.Map; @@ -106,7 +107,7 @@ public DataLakeOperationResponse downloadToFile(final Exchange exchange) { throw new IllegalArgumentException("to download a file, you need to specify the fileDir in the URI"); } final DataLakeFileClientWrapper fileClientWrapper = getFileClientWrapper(exchange); - final File recieverFile = new File(fileDir, fileClientWrapper.getFileName()); + final File recieverFile = resolveWithinDirectory(fileDir, fileClientWrapper.getFileName()); final FileCommonRequestOptions commonRequestOptions = getCommonRequestOptions(exchange); final FileRange fileRange = configurationProxy.getFileRange(exchange); final ParallelTransferOptions parallelTransferOptions = configurationProxy.getParallelTransferOptions(exchange); @@ -253,4 +254,18 @@ private DataLakeFileClientWrapper getFileClientWrapper(final Exchange exchange) final DataLakeFileClient fileClient = configurationProxy.getFileClient(exchange); return null == fileClient ? client : new DataLakeFileClientWrapper(fileClient); } + + private static File resolveWithinDirectory(String fileDir, String name) { + final File target = new File(fileDir, name); + // normalize lexically (removes ./ and ../ segments) and compare on path-segment boundaries so a sibling + // directory whose name merely extends fileDir is not considered contained + final Path normalizedDir = new File(fileDir).toPath().normalize(); + final Path normalizedTarget = target.toPath().normalize(); + if (!normalizedTarget.startsWith(normalizedDir)) { + throw new IllegalArgumentException( + "Cannot download to file '" + name + + "' as it resolves outside the configured fileDir directory: " + fileDir); + } + return target; + } } diff --git a/components/camel-azure/camel-azure-storage-datalake/src/test/java/org/apache/camel/component/azure/storage/datalake/operations/DataLakeFileOperationTest.java b/components/camel-azure/camel-azure-storage-datalake/src/test/java/org/apache/camel/component/azure/storage/datalake/operations/DataLakeFileOperationTest.java index 0c3380d43b7f2..d2db2a7ac0f86 100644 --- a/components/camel-azure/camel-azure-storage-datalake/src/test/java/org/apache/camel/component/azure/storage/datalake/operations/DataLakeFileOperationTest.java +++ b/components/camel-azure/camel-azure-storage-datalake/src/test/java/org/apache/camel/component/azure/storage/datalake/operations/DataLakeFileOperationTest.java @@ -44,6 +44,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; @@ -93,6 +94,19 @@ void testGetFile() throws IOException { assertNotNull(secondResponse.getHeaders()); } + @Test + void testDownloadToFileRejectsPathTraversalName() { + // a file name is influenced by whoever writes to the file system and may contain ../ segments; + // the download must stay within the configured fileDir + configuration.setFileDir("/tmp/camel-azure-datalake-download"); + when(client.getFileName()).thenReturn("../escaped/PROOF_PWNED"); + + final DataLakeFileOperations operations = new DataLakeFileOperations(configuration, client); + final Exchange exchange = new DefaultExchange(context); + + assertThrows(IllegalArgumentException.class, () -> operations.downloadToFile(exchange)); + } + @Test void testUploadFile() throws Exception { final OffsetDateTime time = OffsetDateTime.now(); From 18479c25dd0d00c99bfebdd7cd7638f5a083e5c6 Mon Sep 17 00:00:00 2001 From: Croway Date: Thu, 14 May 2026 11:14:51 +0200 Subject: [PATCH 3/5] CAMEL-23507: camel-cometd: Integrate HeaderFilterStrategy for inbound header mapping --- .../camel/catalog/components/cometd.json | 3 +- .../camel/catalog/components/cometds.json | 3 +- .../cometd/CometdEndpointConfigurer.java | 6 ++ .../cometd/CometdEndpointUriFactory.java | 3 +- .../apache/camel/component/cometd/cometd.json | 3 +- .../camel/component/cometd/cometds.json | 3 +- .../camel/component/cometd/CometdBinding.java | 40 ++++++--- .../component/cometd/CometdConsumer.java | 3 + .../component/cometd/CometdEndpoint.java | 11 +++ .../cometd/CometdHeaderFilterStrategy.java | 28 ++++++ .../component/cometd/CometdProducer.java | 8 +- .../component/cometd/CometBindingTest.java | 32 +++++++ .../dsl/CometdEndpointBuilderFactory.java | 90 +++++++++++++++++++ 13 files changed, 214 insertions(+), 19 deletions(-) create mode 100644 components/camel-cometd/src/main/java/org/apache/camel/component/cometd/CometdHeaderFilterStrategy.java diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/cometd.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/cometd.json index 8f1e8acb0d5b8..9cfb20d62f50d 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/cometd.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/cometd.json @@ -58,6 +58,7 @@ "exceptionHandler": { "index": 15, "kind": "parameter", "displayName": "Exception Handler", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "object", "javaType": "org.apache.camel.spi.ExceptionHandler", "optionalPrefix": "consumer.", "deprecated": false, "autowired": false, "secret": false, "description": "To let the consumer use a custom ExceptionHandler. Notice if the option bridgeErrorHandler is enabled then this option is not in use. By default the consumer will deal with exceptions, that will be logged at WARN or ERROR level and ignored." }, "exchangePattern": { "index": 16, "kind": "parameter", "displayName": "Exchange Pattern", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "object", "javaType": "org.apache.camel.ExchangePattern", "enum": [ "InOnly", "InOut" ], "deprecated": false, "autowired": false, "secret": false, "description": "Sets the exchange pattern when the consumer creates an exchange." }, "disconnectLocalSession": { "index": 17, "kind": "parameter", "displayName": "Disconnect Local Session", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to disconnect local sessions after publishing a message to its channel. Disconnecting local session is needed as they are not swept by default by CometD, and therefore you can run out of memory." }, - "lazyStartProducer": { "index": 18, "kind": "parameter", "displayName": "Lazy Start Producer", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may otherwise fail during starting and cause the route to fail being started. By deferring this startup to be lazy then the startup failure can be handled during routing messages via Camel's routing error handlers. Beware that when the first message is processed then creating and starting the producer may take a little time and prolong the total processing time of the processing." } + "lazyStartProducer": { "index": 18, "kind": "parameter", "displayName": "Lazy Start Producer", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may otherwise fail during starting and cause the route to fail being started. By deferring this startup to be lazy then the startup failure can be handled during routing messages via Camel's routing error handlers. Beware that when the first message is processed then creating and starting the producer may take a little time and prolong the total processing time of the processing." }, + "headerFilterStrategy": { "index": 19, "kind": "parameter", "displayName": "Header Filter Strategy", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.spi.HeaderFilterStrategy", "deprecated": false, "autowired": false, "secret": false, "description": "To use a custom HeaderFilterStrategy to filter headers." } } } diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/cometds.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/cometds.json index 06f568c2b0fb4..730a27e9c5588 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/cometds.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/cometds.json @@ -58,6 +58,7 @@ "exceptionHandler": { "index": 15, "kind": "parameter", "displayName": "Exception Handler", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "object", "javaType": "org.apache.camel.spi.ExceptionHandler", "optionalPrefix": "consumer.", "deprecated": false, "autowired": false, "secret": false, "description": "To let the consumer use a custom ExceptionHandler. Notice if the option bridgeErrorHandler is enabled then this option is not in use. By default the consumer will deal with exceptions, that will be logged at WARN or ERROR level and ignored." }, "exchangePattern": { "index": 16, "kind": "parameter", "displayName": "Exchange Pattern", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "object", "javaType": "org.apache.camel.ExchangePattern", "enum": [ "InOnly", "InOut" ], "deprecated": false, "autowired": false, "secret": false, "description": "Sets the exchange pattern when the consumer creates an exchange." }, "disconnectLocalSession": { "index": 17, "kind": "parameter", "displayName": "Disconnect Local Session", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to disconnect local sessions after publishing a message to its channel. Disconnecting local session is needed as they are not swept by default by CometD, and therefore you can run out of memory." }, - "lazyStartProducer": { "index": 18, "kind": "parameter", "displayName": "Lazy Start Producer", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may otherwise fail during starting and cause the route to fail being started. By deferring this startup to be lazy then the startup failure can be handled during routing messages via Camel's routing error handlers. Beware that when the first message is processed then creating and starting the producer may take a little time and prolong the total processing time of the processing." } + "lazyStartProducer": { "index": 18, "kind": "parameter", "displayName": "Lazy Start Producer", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may otherwise fail during starting and cause the route to fail being started. By deferring this startup to be lazy then the startup failure can be handled during routing messages via Camel's routing error handlers. Beware that when the first message is processed then creating and starting the producer may take a little time and prolong the total processing time of the processing." }, + "headerFilterStrategy": { "index": 19, "kind": "parameter", "displayName": "Header Filter Strategy", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.spi.HeaderFilterStrategy", "deprecated": false, "autowired": false, "secret": false, "description": "To use a custom HeaderFilterStrategy to filter headers." } } } diff --git a/components/camel-cometd/src/generated/java/org/apache/camel/component/cometd/CometdEndpointConfigurer.java b/components/camel-cometd/src/generated/java/org/apache/camel/component/cometd/CometdEndpointConfigurer.java index 93a57c276d725..fe410ffa6fc05 100644 --- a/components/camel-cometd/src/generated/java/org/apache/camel/component/cometd/CometdEndpointConfigurer.java +++ b/components/camel-cometd/src/generated/java/org/apache/camel/component/cometd/CometdEndpointConfigurer.java @@ -39,6 +39,8 @@ public boolean configure(CamelContext camelContext, Object obj, String name, Obj case "exchangePattern": target.setExchangePattern(property(camelContext, org.apache.camel.ExchangePattern.class, value)); return true; case "filterpath": case "filterPath": target.setFilterPath(property(camelContext, java.lang.String.class, value)); return true; + case "headerfilterstrategy": + case "headerFilterStrategy": target.setHeaderFilterStrategy(property(camelContext, org.apache.camel.spi.HeaderFilterStrategy.class, value)); return true; case "interval": target.setInterval(property(camelContext, int.class, value)); return true; case "jsoncommented": case "jsonCommented": target.setJsonCommented(property(camelContext, boolean.class, value)); return true; @@ -76,6 +78,8 @@ public Class getOptionType(String name, boolean ignoreCase) { case "exchangePattern": return org.apache.camel.ExchangePattern.class; case "filterpath": case "filterPath": return java.lang.String.class; + case "headerfilterstrategy": + case "headerFilterStrategy": return org.apache.camel.spi.HeaderFilterStrategy.class; case "interval": return int.class; case "jsoncommented": case "jsonCommented": return boolean.class; @@ -114,6 +118,8 @@ public Object getOptionValue(Object obj, String name, boolean ignoreCase) { case "exchangePattern": return target.getExchangePattern(); case "filterpath": case "filterPath": return target.getFilterPath(); + case "headerfilterstrategy": + case "headerFilterStrategy": return target.getHeaderFilterStrategy(); case "interval": return target.getInterval(); case "jsoncommented": case "jsonCommented": return target.isJsonCommented(); diff --git a/components/camel-cometd/src/generated/java/org/apache/camel/component/cometd/CometdEndpointUriFactory.java b/components/camel-cometd/src/generated/java/org/apache/camel/component/cometd/CometdEndpointUriFactory.java index a0abf8693716f..582a3a95eaee9 100644 --- a/components/camel-cometd/src/generated/java/org/apache/camel/component/cometd/CometdEndpointUriFactory.java +++ b/components/camel-cometd/src/generated/java/org/apache/camel/component/cometd/CometdEndpointUriFactory.java @@ -24,7 +24,7 @@ public class CometdEndpointUriFactory extends org.apache.camel.support.component private static final Set SECRET_PROPERTY_NAMES; private static final Set MULTI_VALUE_PREFIXES; static { - Set props = new HashSet<>(19); + Set props = new HashSet<>(20); props.add("allowedOrigins"); props.add("baseResource"); props.add("bridgeErrorHandler"); @@ -34,6 +34,7 @@ public class CometdEndpointUriFactory extends org.apache.camel.support.component props.add("exceptionHandler"); props.add("exchangePattern"); props.add("filterPath"); + props.add("headerFilterStrategy"); props.add("host"); props.add("interval"); props.add("jsonCommented"); diff --git a/components/camel-cometd/src/generated/resources/META-INF/org/apache/camel/component/cometd/cometd.json b/components/camel-cometd/src/generated/resources/META-INF/org/apache/camel/component/cometd/cometd.json index 8f1e8acb0d5b8..9cfb20d62f50d 100644 --- a/components/camel-cometd/src/generated/resources/META-INF/org/apache/camel/component/cometd/cometd.json +++ b/components/camel-cometd/src/generated/resources/META-INF/org/apache/camel/component/cometd/cometd.json @@ -58,6 +58,7 @@ "exceptionHandler": { "index": 15, "kind": "parameter", "displayName": "Exception Handler", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "object", "javaType": "org.apache.camel.spi.ExceptionHandler", "optionalPrefix": "consumer.", "deprecated": false, "autowired": false, "secret": false, "description": "To let the consumer use a custom ExceptionHandler. Notice if the option bridgeErrorHandler is enabled then this option is not in use. By default the consumer will deal with exceptions, that will be logged at WARN or ERROR level and ignored." }, "exchangePattern": { "index": 16, "kind": "parameter", "displayName": "Exchange Pattern", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "object", "javaType": "org.apache.camel.ExchangePattern", "enum": [ "InOnly", "InOut" ], "deprecated": false, "autowired": false, "secret": false, "description": "Sets the exchange pattern when the consumer creates an exchange." }, "disconnectLocalSession": { "index": 17, "kind": "parameter", "displayName": "Disconnect Local Session", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to disconnect local sessions after publishing a message to its channel. Disconnecting local session is needed as they are not swept by default by CometD, and therefore you can run out of memory." }, - "lazyStartProducer": { "index": 18, "kind": "parameter", "displayName": "Lazy Start Producer", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may otherwise fail during starting and cause the route to fail being started. By deferring this startup to be lazy then the startup failure can be handled during routing messages via Camel's routing error handlers. Beware that when the first message is processed then creating and starting the producer may take a little time and prolong the total processing time of the processing." } + "lazyStartProducer": { "index": 18, "kind": "parameter", "displayName": "Lazy Start Producer", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may otherwise fail during starting and cause the route to fail being started. By deferring this startup to be lazy then the startup failure can be handled during routing messages via Camel's routing error handlers. Beware that when the first message is processed then creating and starting the producer may take a little time and prolong the total processing time of the processing." }, + "headerFilterStrategy": { "index": 19, "kind": "parameter", "displayName": "Header Filter Strategy", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.spi.HeaderFilterStrategy", "deprecated": false, "autowired": false, "secret": false, "description": "To use a custom HeaderFilterStrategy to filter headers." } } } diff --git a/components/camel-cometd/src/generated/resources/META-INF/org/apache/camel/component/cometd/cometds.json b/components/camel-cometd/src/generated/resources/META-INF/org/apache/camel/component/cometd/cometds.json index 06f568c2b0fb4..730a27e9c5588 100644 --- a/components/camel-cometd/src/generated/resources/META-INF/org/apache/camel/component/cometd/cometds.json +++ b/components/camel-cometd/src/generated/resources/META-INF/org/apache/camel/component/cometd/cometds.json @@ -58,6 +58,7 @@ "exceptionHandler": { "index": 15, "kind": "parameter", "displayName": "Exception Handler", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "object", "javaType": "org.apache.camel.spi.ExceptionHandler", "optionalPrefix": "consumer.", "deprecated": false, "autowired": false, "secret": false, "description": "To let the consumer use a custom ExceptionHandler. Notice if the option bridgeErrorHandler is enabled then this option is not in use. By default the consumer will deal with exceptions, that will be logged at WARN or ERROR level and ignored." }, "exchangePattern": { "index": 16, "kind": "parameter", "displayName": "Exchange Pattern", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "object", "javaType": "org.apache.camel.ExchangePattern", "enum": [ "InOnly", "InOut" ], "deprecated": false, "autowired": false, "secret": false, "description": "Sets the exchange pattern when the consumer creates an exchange." }, "disconnectLocalSession": { "index": 17, "kind": "parameter", "displayName": "Disconnect Local Session", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to disconnect local sessions after publishing a message to its channel. Disconnecting local session is needed as they are not swept by default by CometD, and therefore you can run out of memory." }, - "lazyStartProducer": { "index": 18, "kind": "parameter", "displayName": "Lazy Start Producer", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may otherwise fail during starting and cause the route to fail being started. By deferring this startup to be lazy then the startup failure can be handled during routing messages via Camel's routing error handlers. Beware that when the first message is processed then creating and starting the producer may take a little time and prolong the total processing time of the processing." } + "lazyStartProducer": { "index": 18, "kind": "parameter", "displayName": "Lazy Start Producer", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may otherwise fail during starting and cause the route to fail being started. By deferring this startup to be lazy then the startup failure can be handled during routing messages via Camel's routing error handlers. Beware that when the first message is processed then creating and starting the producer may take a little time and prolong the total processing time of the processing." }, + "headerFilterStrategy": { "index": 19, "kind": "parameter", "displayName": "Header Filter Strategy", "group": "advanced", "label": "advanced", "required": false, "type": "object", "javaType": "org.apache.camel.spi.HeaderFilterStrategy", "deprecated": false, "autowired": false, "secret": false, "description": "To use a custom HeaderFilterStrategy to filter headers." } } } diff --git a/components/camel-cometd/src/main/java/org/apache/camel/component/cometd/CometdBinding.java b/components/camel-cometd/src/main/java/org/apache/camel/component/cometd/CometdBinding.java index 3afacbcc499f0..7e104a212d1c1 100644 --- a/components/camel-cometd/src/main/java/org/apache/camel/component/cometd/CometdBinding.java +++ b/components/camel-cometd/src/main/java/org/apache/camel/component/cometd/CometdBinding.java @@ -24,6 +24,7 @@ import org.apache.camel.CamelContext; import org.apache.camel.Exchange; import org.apache.camel.Message; +import org.apache.camel.spi.HeaderFilterStrategy; import org.apache.camel.spi.Metadata; import org.apache.camel.support.DefaultMessage; import org.cometd.bayeux.server.ServerChannel; @@ -48,6 +49,7 @@ public class CometdBinding { private static final Logger LOG = LoggerFactory.getLogger(CometdBinding.class); private final BayeuxServerImpl bayeux; + private final HeaderFilterStrategy headerFilterStrategy; private boolean enableSessionHeader; public CometdBinding(BayeuxServerImpl bayeux) { @@ -55,8 +57,13 @@ public CometdBinding(BayeuxServerImpl bayeux) { } public CometdBinding(BayeuxServerImpl bayeux, boolean enableSessionHeader) { + this(bayeux, enableSessionHeader, new CometdHeaderFilterStrategy()); + } + + public CometdBinding(BayeuxServerImpl bayeux, boolean enableSessionHeader, HeaderFilterStrategy headerFilterStrategy) { this.bayeux = bayeux; this.enableSessionHeader = enableSessionHeader; + this.headerFilterStrategy = headerFilterStrategy; } public ServerMessage.Mutable createCometdMessage(ServerChannel channel, ServerSession serverSession, Message camelMessage) { @@ -81,7 +88,14 @@ public Message createCamelMessage( message.setBody(data); Map headers = getHeadersFromMessage(cometdMessage); if (headers != null) { - message.setHeaders(headers); + for (Entry entry : headers.entrySet()) { + String name = entry.getKey(); + Object value = entry.getValue(); + if (headerFilterStrategy == null + || !headerFilterStrategy.applyFilterToExternalHeaders(name, value, null)) { + message.setHeader(name, value); + } + } } message.setHeader(COMETD_CLIENT_ID_HEADER_NAME, remote.getId()); @@ -116,20 +130,20 @@ private void addSessionAttributesToMessageHeaders(ServerSession remote, Message public void addHeadersToMessage(ServerMessage.Mutable cometdMessage, Message camelMessage) { if (camelMessage.hasHeaders()) { - Map ext = cometdMessage.getExt(true); - ext.put(HEADERS_FIELD, filterHeaders(camelMessage.getHeaders())); - } - } - - //TODO: do something in the style of JMS where they have header Strategies? - private Object filterHeaders(Map headers) { - Map map = new HashMap<>(); - for (Entry entry : headers.entrySet()) { - if (entry != null && entry.getKey() != null) { - map.put(entry.getKey(), entry.getValue()); + Map filtered = new HashMap<>(); + for (Entry entry : camelMessage.getHeaders().entrySet()) { + if (entry != null && entry.getKey() != null) { + if (headerFilterStrategy == null + || !headerFilterStrategy.applyFilterToCamelHeaders(entry.getKey(), entry.getValue(), null)) { + filtered.put(entry.getKey(), entry.getValue()); + } + } + } + if (!filtered.isEmpty()) { + Map ext = cometdMessage.getExt(true); + ext.put(HEADERS_FIELD, filtered); } } - return map; } @SuppressWarnings("unchecked") diff --git a/components/camel-cometd/src/main/java/org/apache/camel/component/cometd/CometdConsumer.java b/components/camel-cometd/src/main/java/org/apache/camel/component/cometd/CometdConsumer.java index 5f557476005a5..406324965ec21 100644 --- a/components/camel-cometd/src/main/java/org/apache/camel/component/cometd/CometdConsumer.java +++ b/components/camel-cometd/src/main/java/org/apache/camel/component/cometd/CometdConsumer.java @@ -91,6 +91,9 @@ public ConsumerService(String channel, BayeuxServerImpl bayeux, CometdConsumer c private CometdBinding createBinding(BayeuxServerImpl bayeux) { boolean enableSessionHeaders = endpoint != null && endpoint.isSessionHeadersEnabled(); + if (endpoint != null && endpoint.getHeaderFilterStrategy() != null) { + return new CometdBinding(bayeux, enableSessionHeaders, endpoint.getHeaderFilterStrategy()); + } return new CometdBinding(bayeux, enableSessionHeaders); } diff --git a/components/camel-cometd/src/main/java/org/apache/camel/component/cometd/CometdEndpoint.java b/components/camel-cometd/src/main/java/org/apache/camel/component/cometd/CometdEndpoint.java index a731b7c86a606..59c9d709cd63e 100644 --- a/components/camel-cometd/src/main/java/org/apache/camel/component/cometd/CometdEndpoint.java +++ b/components/camel-cometd/src/main/java/org/apache/camel/component/cometd/CometdEndpoint.java @@ -24,6 +24,7 @@ import org.apache.camel.Processor; import org.apache.camel.Producer; import org.apache.camel.spi.EndpointServiceLocation; +import org.apache.camel.spi.HeaderFilterStrategy; import org.apache.camel.spi.Metadata; import org.apache.camel.spi.UriEndpoint; import org.apache.camel.spi.UriParam; @@ -77,6 +78,8 @@ public class CometdEndpoint extends DefaultEndpoint implements EndpointServiceLo private String filterPath; @UriParam(label = "producer") private boolean disconnectLocalSession; + @UriParam(label = "advanced", description = "To use a custom HeaderFilterStrategy to filter headers.") + private HeaderFilterStrategy headerFilterStrategy; public CometdEndpoint(CometdComponent component, String uri, String remaining) { super(uri, component); @@ -289,4 +292,12 @@ public boolean isDisconnectLocalSession() { public void setDisconnectLocalSession(boolean disconnectLocalSession) { this.disconnectLocalSession = disconnectLocalSession; } + + public HeaderFilterStrategy getHeaderFilterStrategy() { + return headerFilterStrategy; + } + + public void setHeaderFilterStrategy(HeaderFilterStrategy headerFilterStrategy) { + this.headerFilterStrategy = headerFilterStrategy; + } } diff --git a/components/camel-cometd/src/main/java/org/apache/camel/component/cometd/CometdHeaderFilterStrategy.java b/components/camel-cometd/src/main/java/org/apache/camel/component/cometd/CometdHeaderFilterStrategy.java new file mode 100644 index 0000000000000..3c6c6d89532bb --- /dev/null +++ b/components/camel-cometd/src/main/java/org/apache/camel/component/cometd/CometdHeaderFilterStrategy.java @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.cometd; + +import org.apache.camel.support.DefaultHeaderFilterStrategy; + +public class CometdHeaderFilterStrategy extends DefaultHeaderFilterStrategy { + + public CometdHeaderFilterStrategy() { + setLowerCase(true); + setInFilterStartsWith(CAMEL_FILTER_STARTS_WITH); + setOutFilterStartsWith(CAMEL_FILTER_STARTS_WITH); + } +} diff --git a/components/camel-cometd/src/main/java/org/apache/camel/component/cometd/CometdProducer.java b/components/camel-cometd/src/main/java/org/apache/camel/component/cometd/CometdProducer.java index b9750bf502b5b..af20b0f9b8d1c 100644 --- a/components/camel-cometd/src/main/java/org/apache/camel/component/cometd/CometdProducer.java +++ b/components/camel-cometd/src/main/java/org/apache/camel/component/cometd/CometdProducer.java @@ -49,8 +49,14 @@ public void doStart() throws Exception { endpoint.connect(this); // should probably look into synchronization for this. if (service == null) { + CometdBinding binding; + if (endpoint.getHeaderFilterStrategy() != null) { + binding = new CometdBinding(bayeux, false, endpoint.getHeaderFilterStrategy()); + } else { + binding = new CometdBinding(bayeux); + } service = new ProducerService( - getBayeux(), new CometdBinding(bayeux), endpoint.getPath(), this, getEndpoint().isDisconnectLocalSession()); + getBayeux(), binding, endpoint.getPath(), this, getEndpoint().isDisconnectLocalSession()); } } diff --git a/components/camel-cometd/src/test/java/org/apache/camel/component/cometd/CometBindingTest.java b/components/camel-cometd/src/test/java/org/apache/camel/component/cometd/CometBindingTest.java index 705260fd1b1fb..f8e0bb25223b8 100644 --- a/components/camel-cometd/src/test/java/org/apache/camel/component/cometd/CometBindingTest.java +++ b/components/camel-cometd/src/test/java/org/apache/camel/component/cometd/CometBindingTest.java @@ -17,7 +17,9 @@ package org.apache.camel.component.cometd; import java.util.Arrays; +import java.util.HashMap; import java.util.HashSet; +import java.util.Map; import java.util.Set; import org.apache.camel.CamelContext; @@ -33,6 +35,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.when; @@ -126,4 +129,33 @@ void testSubscriptionHeadersPassed() { result.getHeader(CometdBinding.COMETD_SUBSCRIPTION_HEADER_NAME)); } + @Test + void testInboundCamelHeadersAreFiltered() { + Map camelHeaders = new HashMap<>(); + camelHeaders.put("CamelSqlQuery", "SELECT * FROM secrets"); + camelHeaders.put("CamelHttpUri", "http://evil.com"); + camelHeaders.put("CamelFileName", "../../etc/passwd"); + camelHeaders.put("X-App-Header", "normal-value"); + + Map ext = new HashMap<>(); + ext.put(CometdBinding.HEADERS_FIELD, camelHeaders); + when(cometdMessage.getExt()).thenReturn(ext); + when(remote.getId()).thenReturn("test-client-id"); + + // act + Message result = testObj.createCamelMessage(camelContext, remote, cometdMessage, null); + + // assert: Camel* control headers from ext must be filtered out + assertNull(result.getHeader("CamelSqlQuery"), "CamelSqlQuery should be filtered"); + assertNull(result.getHeader("CamelHttpUri"), "CamelHttpUri should be filtered"); + assertNull(result.getHeader("CamelFileName"), "CamelFileName should be filtered"); + + // assert: non-Camel headers from ext must pass through + assertNotNull(result.getHeader("X-App-Header"), "X-App-Header should pass through"); + assertEquals("normal-value", result.getHeader("X-App-Header")); + + // assert: CometdClientId is set by the binding itself (not from ext), so it must be present + assertNotNull(result.getHeader(CometdBinding.COMETD_CLIENT_ID_HEADER_NAME)); + } + } diff --git a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/CometdEndpointBuilderFactory.java b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/CometdEndpointBuilderFactory.java index 8204f732d2c7b..622bb9330d22c 100644 --- a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/CometdEndpointBuilderFactory.java +++ b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/CometdEndpointBuilderFactory.java @@ -473,6 +473,36 @@ default AdvancedCometdEndpointConsumerBuilder exchangePattern(String exchangePat doSetProperty("exchangePattern", exchangePattern); return this; } + /** + * To use a custom HeaderFilterStrategy to filter headers. + * + * The option is a: + * org.apache.camel.spi.HeaderFilterStrategy type. + * + * Group: advanced + * + * @param headerFilterStrategy the value to set + * @return the dsl builder + */ + default AdvancedCometdEndpointConsumerBuilder headerFilterStrategy(org.apache.camel.spi.HeaderFilterStrategy headerFilterStrategy) { + doSetProperty("headerFilterStrategy", headerFilterStrategy); + return this; + } + /** + * To use a custom HeaderFilterStrategy to filter headers. + * + * The option will be converted to a + * org.apache.camel.spi.HeaderFilterStrategy type. + * + * Group: advanced + * + * @param headerFilterStrategy the value to set + * @return the dsl builder + */ + default AdvancedCometdEndpointConsumerBuilder headerFilterStrategy(String headerFilterStrategy) { + doSetProperty("headerFilterStrategy", headerFilterStrategy); + return this; + } } /** @@ -844,6 +874,36 @@ default AdvancedCometdEndpointProducerBuilder lazyStartProducer(String lazyStart doSetProperty("lazyStartProducer", lazyStartProducer); return this; } + /** + * To use a custom HeaderFilterStrategy to filter headers. + * + * The option is a: + * org.apache.camel.spi.HeaderFilterStrategy type. + * + * Group: advanced + * + * @param headerFilterStrategy the value to set + * @return the dsl builder + */ + default AdvancedCometdEndpointProducerBuilder headerFilterStrategy(org.apache.camel.spi.HeaderFilterStrategy headerFilterStrategy) { + doSetProperty("headerFilterStrategy", headerFilterStrategy); + return this; + } + /** + * To use a custom HeaderFilterStrategy to filter headers. + * + * The option will be converted to a + * org.apache.camel.spi.HeaderFilterStrategy type. + * + * Group: advanced + * + * @param headerFilterStrategy the value to set + * @return the dsl builder + */ + default AdvancedCometdEndpointProducerBuilder headerFilterStrategy(String headerFilterStrategy) { + doSetProperty("headerFilterStrategy", headerFilterStrategy); + return this; + } } /** @@ -1139,6 +1199,36 @@ default CometdEndpointBuilder basic() { return (CometdEndpointBuilder) this; } + /** + * To use a custom HeaderFilterStrategy to filter headers. + * + * The option is a: + * org.apache.camel.spi.HeaderFilterStrategy type. + * + * Group: advanced + * + * @param headerFilterStrategy the value to set + * @return the dsl builder + */ + default AdvancedCometdEndpointBuilder headerFilterStrategy(org.apache.camel.spi.HeaderFilterStrategy headerFilterStrategy) { + doSetProperty("headerFilterStrategy", headerFilterStrategy); + return this; + } + /** + * To use a custom HeaderFilterStrategy to filter headers. + * + * The option will be converted to a + * org.apache.camel.spi.HeaderFilterStrategy type. + * + * Group: advanced + * + * @param headerFilterStrategy the value to set + * @return the dsl builder + */ + default AdvancedCometdEndpointBuilder headerFilterStrategy(String headerFilterStrategy) { + doSetProperty("headerFilterStrategy", headerFilterStrategy); + return this; + } } public interface CometdBuilders { From e77909ad3e488ae89bc332db861cf437a2496291 Mon Sep 17 00:00:00 2001 From: Francois de Parscau Date: Mon, 12 Oct 2026 18:15:24 +0200 Subject: [PATCH 4/5] fix(DPE-4430): update version for cherry picks --- Jenkinsfile.talend | 3 +++ components/camel-azure/camel-azure-storage-blob/pom.xml | 2 ++ components/camel-cometd/pom.xml | 2 ++ components/camel-solr/pom.xml | 5 +++++ pom.xml | 6 ++++++ 5 files changed, 18 insertions(+) diff --git a/Jenkinsfile.talend b/Jenkinsfile.talend index 179aec12c7123..e94a1a7ee10a9 100644 --- a/Jenkinsfile.talend +++ b/Jenkinsfile.talend @@ -23,7 +23,9 @@ org.apache.camel:camel-as2-api,\ org.apache.camel:camel-aws2-kinesis,\ org.apache.camel:camel-aws2-s3,\ org.apache.camel:camel-aws2-sqs,\ +org.apache.camel:camel-azure-storage-blob,\ org.apache.camel:camel-coap,\ +org.apache.camel:camel-cometd,\ org.apache.camel:camel-consul,\ org.apache.camel:camel-crypto,\ org.apache.camel:camel-cxf-common,\ @@ -79,6 +81,7 @@ org.apache.camel:camel-salesforce,\ org.apache.camel:camel-sjms,\ org.apache.camel:camel-sjms2,\ org.apache.camel:camel-smb,\ +org.apache.camel:camel-solr,\ org.apache.camel:camel-spring,\ org.apache.camel:camel-sql,\ org.apache.camel:camel-tika,\ diff --git a/components/camel-azure/camel-azure-storage-blob/pom.xml b/components/camel-azure/camel-azure-storage-blob/pom.xml index 6ea123eed36c1..ec5a0a60d0a31 100644 --- a/components/camel-azure/camel-azure-storage-blob/pom.xml +++ b/components/camel-azure/camel-azure-storage-blob/pom.xml @@ -31,8 +31,10 @@ Camel :: Azure :: Storage Blob Camel Azure Blob Storage Service Component + ${revision} + ${camel-azure-storage-blob.tesb.version} true true diff --git a/components/camel-cometd/pom.xml b/components/camel-cometd/pom.xml index 275ed1c9f12a1..fb1f765aac513 100644 --- a/components/camel-cometd/pom.xml +++ b/components/camel-cometd/pom.xml @@ -30,8 +30,10 @@ jar Camel :: Cometd Camel Cometd support based on Jetty + ${revision} + ${camel-cometd.tesb.version} diff --git a/components/camel-solr/pom.xml b/components/camel-solr/pom.xml index 39df0e3da7a24..0df32c04be4c0 100644 --- a/components/camel-solr/pom.xml +++ b/components/camel-solr/pom.xml @@ -29,6 +29,11 @@ jar Camel :: Solr Camel Solr Support + ${revision} + + + ${camel-solr.tesb.version} + diff --git a/pom.xml b/pom.xml index 48cb4c26678d1..1d3eca2cbc4e2 100644 --- a/pom.xml +++ b/pom.xml @@ -122,7 +122,9 @@ 4.8.1.20250320 4.8.1.20250320 4.8.10-4.14.7-1 + 4.8.10-4.14.9-1 4.8.1.20260608 + 4.8.10-4.14.8-1 4.8.1.20260608 4.8.1.20250320 4.8.1.20250320 @@ -168,6 +170,7 @@ 4.8.10-4.14.7-1 4.8.10-4.14.7-1 4.8.1.20250320 + 4.8.10-4.14.8-1 4.8.1.20250320 4.8.1.20251208 4.8.10-4.14.7-1 @@ -358,7 +361,9 @@ ${camel-aws2-kinesis.tesb.version} ${camel-aws2-s3.tesb.version} ${camel-aws2-sqs.tesb.version} + ${camel-azure-storage-blob.tesb.version} ${camel-coap.tesb.version} + ${camel-cometd.tesb.version} ${camel-consul.tesb.version} ${camel-crypto.tesb.version} ${camel-debezium-common.tesb.version} @@ -404,6 +409,7 @@ ${camel-sjms.tesb.version} ${camel-sjms2.tesb.version} ${camel-smb.tesb.version} + ${camel-solr.tesb.version} ${camel-sql.tesb.version} ${camel-tika.tesb.version} ${camel-tracing.tesb.version} From daaf0c959d86965619d0d26f640ad2364338c063 Mon Sep 17 00:00:00 2001 From: Francois de Parscau Date: Mon, 12 Oct 2026 18:30:50 +0200 Subject: [PATCH 5/5] fix(DPE-4430): update version for camel-azure-storage-datalake --- Jenkinsfile.talend | 1 + components/camel-azure/camel-azure-storage-datalake/pom.xml | 5 +++++ pom.xml | 2 ++ 3 files changed, 8 insertions(+) diff --git a/Jenkinsfile.talend b/Jenkinsfile.talend index e94a1a7ee10a9..5a012c20267a9 100644 --- a/Jenkinsfile.talend +++ b/Jenkinsfile.talend @@ -24,6 +24,7 @@ org.apache.camel:camel-aws2-kinesis,\ org.apache.camel:camel-aws2-s3,\ org.apache.camel:camel-aws2-sqs,\ org.apache.camel:camel-azure-storage-blob,\ +org.apache.camel:camel-azure-storage-datalake,\ org.apache.camel:camel-coap,\ org.apache.camel:camel-cometd,\ org.apache.camel:camel-consul,\ diff --git a/components/camel-azure/camel-azure-storage-datalake/pom.xml b/components/camel-azure/camel-azure-storage-datalake/pom.xml index 34cbfc7bb5534..9e7aac5be7126 100644 --- a/components/camel-azure/camel-azure-storage-datalake/pom.xml +++ b/components/camel-azure/camel-azure-storage-datalake/pom.xml @@ -31,6 +31,11 @@ Camel :: Azure :: Storage Datalake Camel Azure Datalake Component + ${revision} + + + ${camel-azure-storage-datalake.tesb.version} + diff --git a/pom.xml b/pom.xml index 1d3eca2cbc4e2..0fb1a4d1d98a7 100644 --- a/pom.xml +++ b/pom.xml @@ -123,6 +123,7 @@ 4.8.1.20250320 4.8.10-4.14.7-1 4.8.10-4.14.9-1 + 4.8.10-4.14.9-1 4.8.1.20260608 4.8.10-4.14.8-1 4.8.1.20260608 @@ -362,6 +363,7 @@ ${camel-aws2-s3.tesb.version} ${camel-aws2-sqs.tesb.version} ${camel-azure-storage-blob.tesb.version} + ${camel-azure-storage-datalake.tesb.version} ${camel-coap.tesb.version} ${camel-cometd.tesb.version} ${camel-consul.tesb.version}