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 {