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

oscerd pushed a commit to branch camel-4.18.x
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/camel-4.18.x by this push:
     new 5c7a8dbf1f1e CAMEL-24084: Apply header filter strategy to 
structured-mode CloudEvent extension headers (4.18.x backport) (#24753)
5c7a8dbf1f1e is described below

commit 5c7a8dbf1f1ed92bb4f289a50cc1765095054112
Author: Andrea Cosentino <[email protected]>
AuthorDate: Thu Jul 16 10:47:05 2026 +0200

    CAMEL-24084: Apply header filter strategy to structured-mode CloudEvent 
extension headers (4.18.x backport) (#24753)
    
    CAMEL-24084: Apply header filter strategy to structured-mode CloudEvent 
extension headers
    
    The Knative consumer filters inbound HTTP headers through 
KnativeHttpHeaderFilterStrategy, but
    structured content mode (application/cloudevents+json) mapped the remaining 
event fields
    (extensions) onto the message headers without a HeaderFilterStrategy, 
inconsistent with the
    binary/HTTP-header path. Route structured-mode extension fields through a 
HeaderFilterStrategy so
    Camel* headers (matched case-insensitively) are filtered consistently on 
both content modes.
    
    Backport to camel-4.18.x: the strategy sets 
setInFilterStartsWith(CAMEL_FILTER_STARTS_WITH)
    explicitly, since DefaultHeaderFilterStrategy on this branch does not 
filter Camel* inbound by default.
    
    Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
---
 .../knative/ce/AbstractCloudEventProcessor.java    | 25 ++++++++++
 .../component/knative/ce/CloudEventProcessors.java | 22 ++++-----
 .../component/knative/http/KnativeHttpTest.java    | 55 ++++++++++++++++++++++
 3 files changed, 89 insertions(+), 13 deletions(-)

diff --git 
a/components/camel-knative/camel-knative-component/src/main/java/org/apache/camel/component/knative/ce/AbstractCloudEventProcessor.java
 
b/components/camel-knative/camel-knative-component/src/main/java/org/apache/camel/component/knative/ce/AbstractCloudEventProcessor.java
index 0e7f258d3383..ea472be7a736 100644
--- 
a/components/camel-knative/camel-knative-component/src/main/java/org/apache/camel/component/knative/ce/AbstractCloudEventProcessor.java
+++ 
b/components/camel-knative/camel-knative-component/src/main/java/org/apache/camel/component/knative/ce/AbstractCloudEventProcessor.java
@@ -19,16 +19,20 @@ package org.apache.camel.component.knative.ce;
 import java.io.InputStream;
 import java.time.ZonedDateTime;
 import java.time.format.DateTimeFormatter;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
 import java.util.function.Supplier;
 
 import org.apache.camel.Exchange;
+import org.apache.camel.Message;
 import org.apache.camel.Processor;
 import org.apache.camel.cloudevents.CloudEvent;
 import org.apache.camel.component.knative.KnativeEndpoint;
 import org.apache.camel.component.knative.spi.Knative;
 import org.apache.camel.component.knative.spi.KnativeResource;
+import org.apache.camel.spi.HeaderFilterStrategy;
+import org.apache.camel.support.DefaultHeaderFilterStrategy;
 import org.apache.camel.util.StringHelper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -36,10 +40,20 @@ import org.slf4j.LoggerFactory;
 abstract class AbstractCloudEventProcessor implements CloudEventProcessor {
     private final CloudEvent cloudEvent;
 
+    // Filters internal Camel headers (Camel*/camel*) from structured-mode 
CloudEvent extensions, keeping the
+    // extension-to-header mapping consistent with the binary content-mode 
path (see KnativeHttpHeaderFilterStrategy).
+    private final HeaderFilterStrategy headerFilterStrategy = 
createHeaderFilterStrategy();
+
     protected AbstractCloudEventProcessor(CloudEvent cloudEvent) {
         this.cloudEvent = cloudEvent;
     }
 
+    private static HeaderFilterStrategy createHeaderFilterStrategy() {
+        DefaultHeaderFilterStrategy strategy = new 
DefaultHeaderFilterStrategy();
+        
strategy.setInFilterStartsWith(DefaultHeaderFilterStrategy.CAMEL_FILTER_STARTS_WITH);
+        return strategy;
+    }
+
     @Override
     public CloudEvent cloudEvent() {
         return cloudEvent;
@@ -72,6 +86,17 @@ abstract class AbstractCloudEventProcessor implements 
CloudEventProcessor {
 
     protected abstract void decodeStructuredContent(Exchange exchange, 
Map<String, Object> content);
 
+    /**
+     * Maps a structured-mode CloudEvent extension field to a message header, 
applying the header filter strategy so
+     * that internal Camel ({@code Camel*}) headers are filtered consistently 
with the binary content-mode path.
+     */
+    protected void mapExtensionAsHeader(Message message, Exchange exchange, 
String key, Object value) {
+        final String headerName = key.toLowerCase(Locale.US);
+        if (!headerFilterStrategy.applyFilterToExternalHeaders(headerName, 
value, exchange)) {
+            message.setHeader(headerName, value);
+        }
+    }
+
     @Override
     public Processor producer(KnativeEndpoint endpoint, KnativeResource 
service) {
         final CloudEvent ce = cloudEvent();
diff --git 
a/components/camel-knative/camel-knative-component/src/main/java/org/apache/camel/component/knative/ce/CloudEventProcessors.java
 
b/components/camel-knative/camel-knative-component/src/main/java/org/apache/camel/component/knative/ce/CloudEventProcessors.java
index 7ee1e20859f7..dcedceb29543 100644
--- 
a/components/camel-knative/camel-knative-component/src/main/java/org/apache/camel/component/knative/ce/CloudEventProcessors.java
+++ 
b/components/camel-knative/camel-knative-component/src/main/java/org/apache/camel/component/knative/ce/CloudEventProcessors.java
@@ -16,7 +16,6 @@
  */
 package org.apache.camel.component.knative.ce;
 
-import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
 
@@ -52,11 +51,10 @@ public enum CloudEventProcessors implements 
CloudEventProcessor {
             }
 
             //
-            // Map every remaining field as it is (extensions).
+            // Map every remaining field as it is (extensions), applying the 
header filter
+            // strategy so internal Camel headers are filtered consistently 
with binary mode.
             //
-            content.forEach((key, val) -> {
-                message.setHeader(key.toLowerCase(Locale.US), val);
-            });
+            content.forEach((key, val) -> mapExtensionAsHeader(message, 
exchange, key, val));
         }
     }),
     v1_0_1(new AbstractCloudEventProcessor(CloudEvents.v1_0_1) {
@@ -79,11 +77,10 @@ public enum CloudEventProcessors implements 
CloudEventProcessor {
             }
 
             //
-            // Map every remaining field as it is (extensions).
+            // Map every remaining field as it is (extensions), applying the 
header filter
+            // strategy so internal Camel headers are filtered consistently 
with binary mode.
             //
-            content.forEach((key, val) -> {
-                message.setHeader(key.toLowerCase(Locale.US), val);
-            });
+            content.forEach((key, val) -> mapExtensionAsHeader(message, 
exchange, key, val));
         }
     }),
     v1_0_2(new AbstractCloudEventProcessor(CloudEvents.v1_0_2) {
@@ -106,11 +103,10 @@ public enum CloudEventProcessors implements 
CloudEventProcessor {
             }
 
             //
-            // Map every remaining field as it is (extensions).
+            // Map every remaining field as it is (extensions), applying the 
header filter
+            // strategy so internal Camel headers are filtered consistently 
with binary mode.
             //
-            content.forEach((key, val) -> {
-                message.setHeader(key.toLowerCase(Locale.US), val);
-            });
+            content.forEach((key, val) -> mapExtensionAsHeader(message, 
exchange, key, val));
         }
     });
 
diff --git 
a/components/camel-knative/camel-knative-http/src/test/java/org/apache/camel/component/knative/http/KnativeHttpTest.java
 
b/components/camel-knative/camel-knative-http/src/test/java/org/apache/camel/component/knative/http/KnativeHttpTest.java
index cf64d5c696f1..3f1191717b7c 100644
--- 
a/components/camel-knative/camel-knative-http/src/test/java/org/apache/camel/component/knative/http/KnativeHttpTest.java
+++ 
b/components/camel-knative/camel-knative-http/src/test/java/org/apache/camel/component/knative/http/KnativeHttpTest.java
@@ -370,6 +370,61 @@ public class KnativeHttpTest {
         mock.assertIsSatisfied();
     }
 
+    @ParameterizedTest
+    @EnumSource(CloudEvents.class)
+    void testConsumeStructuredContentFiltersInternalHeaders(CloudEvent ce) 
throws Exception {
+        configureKnativeComponent(
+                context,
+                ce,
+                sourceEndpoint(
+                        "myEndpoint",
+                        Map.of(
+                                Knative.KNATIVE_CLOUD_EVENT_TYPE, 
"org.apache.camel.event",
+                                Knative.CONTENT_TYPE, "text/plain")));
+
+        RouteBuilder.addRoutes(context, b -> {
+            b.from("knative:endpoint/myEndpoint")
+                    .to("mock:ce");
+        });
+
+        context.start();
+
+        MockEndpoint mock = context.getEndpoint("mock:ce", MockEndpoint.class);
+        mock.expectedBodiesReceived("test");
+        mock.expectedMessageCount(1);
+        // a regular CloudEvent extension must still be propagated as a 
message header
+        mock.expectedHeaderReceived("myextension", "myvalue");
+        // an extension that resolves to an internal Camel header must be 
filtered out and must not
+        // be able to inject a framework control header (headers are matched 
case-insensitively)
+        mock.expectedMessagesMatches(e -> 
e.getMessage().getHeader(Exchange.FILE_NAME) == null);
+
+        if (Objects.equals(CloudEvents.v1_0.version(), ce.version())
+                || Objects.equals(CloudEvents.v1_0_1.version(), ce.version())
+                || Objects.equals(CloudEvents.v1_0_2.version(), ce.version())) 
{
+            given()
+                    .contentType(Knative.MIME_STRUCTURED_CONTENT_MODE)
+                    .body(
+                            Map.of(
+                                    "specversion", ce.version(),
+                                    "type", "org.apache.camel.event",
+                                    "id", "myEventID",
+                                    "source", "/somewhere",
+                                    "datacontenttype", "text/plain",
+                                    "myextension", "myvalue",
+                                    "camelfilename", "injected.txt",
+                                    "data", "test"),
+                            ObjectMapperType.JACKSON_2)
+                    .when()
+                    .post()
+                    .then()
+                    .statusCode(200);
+        } else {
+            throw new IllegalArgumentException("Unknown CloudEvent spec: " + 
ce.version());
+        }
+
+        mock.assertIsSatisfied();
+    }
+
     @ParameterizedTest
     @EnumSource(CloudEvents.class)
     void testConsumeContent(CloudEvent ce) throws Exception {

Reply via email to