From 04eb0a71b14add2adba25b827dc968de285a4ca2 Mon Sep 17 00:00:00 2001 From: Andrea Cosentino Date: Mon, 18 May 2026 17:36:20 +0200 Subject: [PATCH] CAMEL-23532: camel-vertx-websocket / camel-atmosphere-websocket / camel-iggy - use dedicated HeaderFilterStrategy aligned with sibling components Apply a HeaderFilterStrategy on the inbound header mapping of the vertx-websocket, atmosphere-websocket and iggy consumers, aligning them with the pattern already used by camel-coap (CAMEL-23222), camel-cometd (CAMEL-23507) and camel-nats (CAMEL-23515). vertx-websocket and iggy gain a new VertxWebsocketHeaderFilterStrategy / IggyHeaderFilterStrategy plus a headerFilterStrategy endpoint option; atmosphere-websocket reuses the HeaderFilterStrategy it already inherits from the HTTP/servlet stack. Includes unit tests and an upgrade-guide note. Closes #23285 Co-Authored-By: Claude Opus 4.7 (1M context) Signed-off-by: Andrea Cosentino (cherry picked from commit 1e776238202edb9d98972cff558e12b53e499647) --- .../apache/camel/catalog/components/iggy.json | 3 +- .../catalog/components/vertx-websocket.json | 9 +- .../websocket/WebsocketConsumer.java | 7 +- .../iggy/IggyEndpointConfigurer.java | 6 ++ .../iggy/IggyEndpointUriFactory.java | 3 +- .../org/apache/camel/component/iggy/iggy.json | 3 +- .../camel/component/iggy/IggyEndpoint.java | 23 ++++- .../component/iggy/IggyFetchRecords.java | 9 +- .../iggy/IggyHeaderFilterStrategy.java | 34 +++++++ .../iggy/IggyHeaderFilterStrategyTest.java | 53 ++++++++++ .../VertxWebsocketEndpointConfigurer.java | 6 ++ .../VertxWebsocketEndpointUriFactory.java | 3 +- .../vertx/websocket/vertx-websocket.json | 9 +- .../websocket/VertxWebsocketConsumer.java | 16 +++- .../websocket/VertxWebsocketEndpoint.java | 23 ++++- .../VertxWebsocketHeaderFilterStrategy.java | 34 +++++++ ...ertxWebsocketHeaderFilterStrategyTest.java | 53 ++++++++++ .../pages/camel-4x-upgrade-guide-4_18.adoc | 29 ++++++ .../dsl/IggyEndpointBuilderFactory.java | 96 +++++++++++++++++++ .../VertxWebsocketEndpointBuilderFactory.java | 96 +++++++++++++++++++ 20 files changed, 497 insertions(+), 18 deletions(-) create mode 100644 components/camel-iggy/src/main/java/org/apache/camel/component/iggy/IggyHeaderFilterStrategy.java create mode 100644 components/camel-iggy/src/test/java/org/apache/camel/component/iggy/IggyHeaderFilterStrategyTest.java create mode 100644 components/camel-vertx/camel-vertx-websocket/src/main/java/org/apache/camel/component/vertx/websocket/VertxWebsocketHeaderFilterStrategy.java create mode 100644 components/camel-vertx/camel-vertx-websocket/src/test/java/org/apache/camel/component/vertx/websocket/VertxWebsocketHeaderFilterStrategyTest.java diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/iggy.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/iggy.json index 4717f3565054b..bb09f157afe99 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/iggy.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/iggy.json @@ -80,6 +80,7 @@ "exchangePattern": { "index": 24, "kind": "parameter", "displayName": "Exchange Pattern", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "enum", "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." }, "partitioning": { "index": 25, "kind": "parameter", "displayName": "Partitioning", "group": "producer", "label": "producer", "required": false, "type": "object", "javaType": "org.apache.iggy.message.Partitioning", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "balanced", "configurationClass": "org.apache.camel.component.iggy.IggyConfiguration", "configurationField": "configuration", "description": "Partitioning strategy for message distribution" }, "lazyStartProducer": { "index": 26, "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." }, - "username": { "index": 27, "kind": "parameter", "displayName": "Username", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "configurationClass": "org.apache.camel.component.iggy.IggyConfiguration", "configurationField": "configuration", "description": "Iggy username" } + "headerFilterStrategy": { "index": 27, "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 header to and from Camel message." }, + "username": { "index": 28, "kind": "parameter", "displayName": "Username", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "configurationClass": "org.apache.camel.component.iggy.IggyConfiguration", "configurationField": "configuration", "description": "Iggy username" } } } diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/vertx-websocket.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/vertx-websocket.json index 032834ffc2ab9..7ef887130ceb3 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/vertx-websocket.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/vertx-websocket.json @@ -61,9 +61,10 @@ "sendToAll": { "index": 15, "kind": "parameter", "displayName": "Send To All", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "To send to all websocket subscribers. Can be used to configure at the endpoint level, instead of providing the VertxWebsocketConstants.SEND_TO_ALL header on the message. Note that when using this option, the host name specified for the vertx-websocket producer URI must match one used for an existing vertx-websocket consumer. Note that this option only applies when producing messages to endpoints hosted by the vertx-websocket consumer and not to an externally hosted WebSocket." }, "clientOptions": { "index": 16, "kind": "parameter", "displayName": "Client Options", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "object", "javaType": "io.vertx.core.http.HttpClientOptions", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "Sets customized options for configuring the WebSocket client used in the producer" }, "lazyStartProducer": { "index": 17, "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." }, - "allowOriginHeader": { "index": 18, "kind": "parameter", "displayName": "Allow Origin Header", "group": "security", "label": "security", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "Whether the WebSocket client should add the Origin header to the WebSocket handshake request." }, - "handshakeHeaders": { "index": 19, "kind": "parameter", "displayName": "Handshake Headers", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "java.util.Map", "prefix": "handshake.", "multiValue": true, "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "Headers to send in the HTTP handshake request. When the endpoint is a consumer, it only works when it consumes a remote host as a client (i.e. consumeAsClient is true). This is a multi-value option with prefix: handshake." }, - "originHeaderUrl": { "index": 20, "kind": "parameter", "displayName": "Origin Header Url", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "The value of the Origin header that the WebSocket client should use on the WebSocket handshake request. When not specified, the WebSocket client will automatically determine the value for the Origin from the request URL." }, - "sslContextParameters": { "index": 21, "kind": "parameter", "displayName": "Ssl Context Parameters", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "To configure security using SSLContextParameters" } + "headerFilterStrategy": { "index": 18, "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 header to and from Camel message." }, + "allowOriginHeader": { "index": 19, "kind": "parameter", "displayName": "Allow Origin Header", "group": "security", "label": "security", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "Whether the WebSocket client should add the Origin header to the WebSocket handshake request." }, + "handshakeHeaders": { "index": 20, "kind": "parameter", "displayName": "Handshake Headers", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "java.util.Map", "prefix": "handshake.", "multiValue": true, "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "Headers to send in the HTTP handshake request. When the endpoint is a consumer, it only works when it consumes a remote host as a client (i.e. consumeAsClient is true). This is a multi-value option with prefix: handshake." }, + "originHeaderUrl": { "index": 21, "kind": "parameter", "displayName": "Origin Header Url", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "The value of the Origin header that the WebSocket client should use on the WebSocket handshake request. When not specified, the WebSocket client will automatically determine the value for the Origin from the request URL." }, + "sslContextParameters": { "index": 22, "kind": "parameter", "displayName": "Ssl Context Parameters", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "To configure security using SSLContextParameters" } } } diff --git a/components/camel-atmosphere-websocket/src/main/java/org/apache/camel/component/atmosphere/websocket/WebsocketConsumer.java b/components/camel-atmosphere-websocket/src/main/java/org/apache/camel/component/atmosphere/websocket/WebsocketConsumer.java index 5fbfb09a5510a..628c46c4f4508 100644 --- a/components/camel-atmosphere-websocket/src/main/java/org/apache/camel/component/atmosphere/websocket/WebsocketConsumer.java +++ b/components/camel-atmosphere-websocket/src/main/java/org/apache/camel/component/atmosphere/websocket/WebsocketConsumer.java @@ -30,6 +30,7 @@ import org.apache.camel.Exchange; import org.apache.camel.Processor; import org.apache.camel.component.servlet.ServletConsumer; +import org.apache.camel.spi.HeaderFilterStrategy; import org.atmosphere.cpr.ApplicationConfig; import org.atmosphere.cpr.AtmosphereFramework; import org.atmosphere.cpr.AtmosphereFrameworkInitializer; @@ -103,8 +104,12 @@ public void sendEventNotification(String connectionKey, int eventType) { exchange.getIn().setHeader(WebsocketConstants.CONNECTION_KEY, connectionKey); exchange.getIn().setHeader(WebsocketConstants.EVENT_TYPE, eventType); + HeaderFilterStrategy headerFilterStrategy = getEndpoint().getHeaderFilterStrategy(); for (Map.Entry param : queryMap.entrySet()) { - exchange.getIn().setHeader(param.getKey(), param.getValue()); + if (headerFilterStrategy == null + || !headerFilterStrategy.applyFilterToExternalHeaders(param.getKey(), param.getValue(), exchange)) { + exchange.getIn().setHeader(param.getKey(), param.getValue()); + } } // use default consumer callback diff --git a/components/camel-iggy/src/generated/java/org/apache/camel/component/iggy/IggyEndpointConfigurer.java b/components/camel-iggy/src/generated/java/org/apache/camel/component/iggy/IggyEndpointConfigurer.java index 63cc1c3be4e25..2705db529fea7 100644 --- a/components/camel-iggy/src/generated/java/org/apache/camel/component/iggy/IggyEndpointConfigurer.java +++ b/components/camel-iggy/src/generated/java/org/apache/camel/component/iggy/IggyEndpointConfigurer.java @@ -43,6 +43,8 @@ public boolean configure(CamelContext camelContext, Object obj, String name, Obj case "exceptionHandler": target.setExceptionHandler(property(camelContext, org.apache.camel.spi.ExceptionHandler.class, value)); return true; case "exchangepattern": case "exchangePattern": target.setExchangePattern(property(camelContext, org.apache.camel.ExchangePattern.class, value)); return true; + case "headerfilterstrategy": + case "headerFilterStrategy": target.setHeaderFilterStrategy(property(camelContext, org.apache.camel.spi.HeaderFilterStrategy.class, value)); return true; case "host": target.getConfiguration().setHost(property(camelContext, java.lang.String.class, value)); return true; case "lazystartproducer": case "lazyStartProducer": target.setLazyStartProducer(property(camelContext, boolean.class, value)); return true; @@ -99,6 +101,8 @@ public Class getOptionType(String name, boolean ignoreCase) { case "exceptionHandler": return org.apache.camel.spi.ExceptionHandler.class; case "exchangepattern": case "exchangePattern": return org.apache.camel.ExchangePattern.class; + case "headerfilterstrategy": + case "headerFilterStrategy": return org.apache.camel.spi.HeaderFilterStrategy.class; case "host": return java.lang.String.class; case "lazystartproducer": case "lazyStartProducer": return boolean.class; @@ -156,6 +160,8 @@ public Object getOptionValue(Object obj, String name, boolean ignoreCase) { case "exceptionHandler": return target.getExceptionHandler(); case "exchangepattern": case "exchangePattern": return target.getExchangePattern(); + case "headerfilterstrategy": + case "headerFilterStrategy": return target.getHeaderFilterStrategy(); case "host": return target.getConfiguration().getHost(); case "lazystartproducer": case "lazyStartProducer": return target.isLazyStartProducer(); diff --git a/components/camel-iggy/src/generated/java/org/apache/camel/component/iggy/IggyEndpointUriFactory.java b/components/camel-iggy/src/generated/java/org/apache/camel/component/iggy/IggyEndpointUriFactory.java index c24043e1976a1..3ed6198c093a8 100644 --- a/components/camel-iggy/src/generated/java/org/apache/camel/component/iggy/IggyEndpointUriFactory.java +++ b/components/camel-iggy/src/generated/java/org/apache/camel/component/iggy/IggyEndpointUriFactory.java @@ -23,7 +23,7 @@ public class IggyEndpointUriFactory extends org.apache.camel.support.component.E private static final Set SECRET_PROPERTY_NAMES; private static final Map MULTI_VALUE_PREFIXES; static { - Set props = new HashSet<>(28); + Set props = new HashSet<>(29); props.add("autoCommit"); props.add("autoCreateStream"); props.add("autoCreateTopic"); @@ -34,6 +34,7 @@ public class IggyEndpointUriFactory extends org.apache.camel.support.component.E props.add("consumersCount"); props.add("exceptionHandler"); props.add("exchangePattern"); + props.add("headerFilterStrategy"); props.add("host"); props.add("lazyStartProducer"); props.add("maxTopicSize"); diff --git a/components/camel-iggy/src/generated/resources/META-INF/org/apache/camel/component/iggy/iggy.json b/components/camel-iggy/src/generated/resources/META-INF/org/apache/camel/component/iggy/iggy.json index 4717f3565054b..bb09f157afe99 100644 --- a/components/camel-iggy/src/generated/resources/META-INF/org/apache/camel/component/iggy/iggy.json +++ b/components/camel-iggy/src/generated/resources/META-INF/org/apache/camel/component/iggy/iggy.json @@ -80,6 +80,7 @@ "exchangePattern": { "index": 24, "kind": "parameter", "displayName": "Exchange Pattern", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "enum", "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." }, "partitioning": { "index": 25, "kind": "parameter", "displayName": "Partitioning", "group": "producer", "label": "producer", "required": false, "type": "object", "javaType": "org.apache.iggy.message.Partitioning", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "balanced", "configurationClass": "org.apache.camel.component.iggy.IggyConfiguration", "configurationField": "configuration", "description": "Partitioning strategy for message distribution" }, "lazyStartProducer": { "index": 26, "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." }, - "username": { "index": 27, "kind": "parameter", "displayName": "Username", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "configurationClass": "org.apache.camel.component.iggy.IggyConfiguration", "configurationField": "configuration", "description": "Iggy username" } + "headerFilterStrategy": { "index": 27, "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 header to and from Camel message." }, + "username": { "index": 28, "kind": "parameter", "displayName": "Username", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "configurationClass": "org.apache.camel.component.iggy.IggyConfiguration", "configurationField": "configuration", "description": "Iggy username" } } } diff --git a/components/camel-iggy/src/main/java/org/apache/camel/component/iggy/IggyEndpoint.java b/components/camel-iggy/src/main/java/org/apache/camel/component/iggy/IggyEndpoint.java index eb3cf94114854..a37d8c957423a 100644 --- a/components/camel-iggy/src/main/java/org/apache/camel/component/iggy/IggyEndpoint.java +++ b/components/camel-iggy/src/main/java/org/apache/camel/component/iggy/IggyEndpoint.java @@ -25,6 +25,8 @@ import org.apache.camel.Consumer; import org.apache.camel.Processor; import org.apache.camel.Producer; +import org.apache.camel.spi.HeaderFilterStrategy; +import org.apache.camel.spi.HeaderFilterStrategyAware; import org.apache.camel.spi.Metadata; import org.apache.camel.spi.UriEndpoint; import org.apache.camel.spi.UriParam; @@ -45,12 +47,15 @@ */ @UriEndpoint(firstVersion = "4.17.0", scheme = "iggy", title = "Iggy", syntax = "iggy:topicName", category = { Category.MESSAGING }, headersClass = IggyConstants.class) -public class IggyEndpoint extends DefaultEndpoint { +public class IggyEndpoint extends DefaultEndpoint implements HeaderFilterStrategyAware { private static final Logger LOG = LoggerFactory.getLogger(IggyEndpoint.class); @UriParam private IggyConfiguration configuration; + @UriParam(label = "advanced", + description = "To use a custom HeaderFilterStrategy to filter header to and from Camel message.") + private HeaderFilterStrategy headerFilterStrategy; @UriPath(description = "Name of the topic") @Metadata(required = true) private String topicName; @@ -162,6 +167,22 @@ public ExecutorService createExecutor() { "IggyConsumer[" + getTopicName() + "]", configuration.getConsumersCount()); } + @Override + public HeaderFilterStrategy getHeaderFilterStrategy() { + if (headerFilterStrategy == null) { + headerFilterStrategy = new IggyHeaderFilterStrategy(); + } + return headerFilterStrategy; + } + + /** + * To use a custom {@link org.apache.camel.spi.HeaderFilterStrategy} to filter header to and from Camel message. + */ + @Override + public void setHeaderFilterStrategy(HeaderFilterStrategy headerFilterStrategy) { + this.headerFilterStrategy = headerFilterStrategy; + } + public IggyConfiguration getConfiguration() { return configuration; } diff --git a/components/camel-iggy/src/main/java/org/apache/camel/component/iggy/IggyFetchRecords.java b/components/camel-iggy/src/main/java/org/apache/camel/component/iggy/IggyFetchRecords.java index dd7b35efa151c..18ad516096f79 100644 --- a/components/camel-iggy/src/main/java/org/apache/camel/component/iggy/IggyFetchRecords.java +++ b/components/camel-iggy/src/main/java/org/apache/camel/component/iggy/IggyFetchRecords.java @@ -25,6 +25,7 @@ import org.apache.camel.Exchange; import org.apache.camel.component.iggy.client.IggyClientConnectionPool; +import org.apache.camel.spi.HeaderFilterStrategy; import org.apache.camel.support.BridgeExceptionHandlerToErrorHandler; import org.apache.camel.support.task.Tasks; import org.apache.camel.support.task.budget.Budgets; @@ -175,7 +176,13 @@ private Exchange createExchange(org.apache.iggy.message.Message message) { hv -> hv.getValue().value() // TODO this way `HeaderKind kind` will be lost )); - exchange.getIn().setHeaders(stringUserHeaders); + HeaderFilterStrategy headerFilterStrategy = endpoint.getHeaderFilterStrategy(); + for (Map.Entry entry : stringUserHeaders.entrySet()) { + if (headerFilterStrategy == null + || !headerFilterStrategy.applyFilterToExternalHeaders(entry.getKey(), entry.getValue(), exchange)) { + exchange.getIn().setHeader(entry.getKey(), entry.getValue()); + } + } }); return exchange; diff --git a/components/camel-iggy/src/main/java/org/apache/camel/component/iggy/IggyHeaderFilterStrategy.java b/components/camel-iggy/src/main/java/org/apache/camel/component/iggy/IggyHeaderFilterStrategy.java new file mode 100644 index 0000000000000..58bb0f746b7e8 --- /dev/null +++ b/components/camel-iggy/src/main/java/org/apache/camel/component/iggy/IggyHeaderFilterStrategy.java @@ -0,0 +1,34 @@ +/* + * 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.iggy; + +import org.apache.camel.support.DefaultHeaderFilterStrategy; + +/** + * Default header filter strategy for Iggy endpoints. + *

+ * Filters out Camel internal headers (starting with "Camel" or "camel") in both directions to prevent external Iggy + * message producers from injecting internal Camel headers via message user-headers. + */ +public class IggyHeaderFilterStrategy extends DefaultHeaderFilterStrategy { + + public IggyHeaderFilterStrategy() { + setLowerCase(true); + setOutFilterStartsWith(CAMEL_FILTER_STARTS_WITH); + setInFilterStartsWith(CAMEL_FILTER_STARTS_WITH); + } +} diff --git a/components/camel-iggy/src/test/java/org/apache/camel/component/iggy/IggyHeaderFilterStrategyTest.java b/components/camel-iggy/src/test/java/org/apache/camel/component/iggy/IggyHeaderFilterStrategyTest.java new file mode 100644 index 0000000000000..59a3a2d7303ee --- /dev/null +++ b/components/camel-iggy/src/test/java/org/apache/camel/component/iggy/IggyHeaderFilterStrategyTest.java @@ -0,0 +1,53 @@ +/* + * 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.iggy; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class IggyHeaderFilterStrategyTest { + + private final IggyHeaderFilterStrategy strategy = new IggyHeaderFilterStrategy(); + + @Test + void inboundCamelHeadersAreFiltered() { + assertTrue(strategy.applyFilterToExternalHeaders("CamelHttpUri", "http://evil.example", null)); + assertTrue(strategy.applyFilterToExternalHeaders("CamelFileName", "../../etc/passwd", null)); + assertTrue(strategy.applyFilterToExternalHeaders("CamelBeanMethodName", "evilMethod", null)); + } + + @Test + void inboundLowercaseCamelHeadersAreFiltered() { + assertTrue(strategy.applyFilterToExternalHeaders("camelHttpUri", "http://evil.example", null)); + assertTrue(strategy.applyFilterToExternalHeaders("camelfilename", "../../etc/passwd", null)); + } + + @Test + void outboundCamelHeadersAreFiltered() { + assertTrue(strategy.applyFilterToCamelHeaders("CamelHttpUri", "value", null)); + assertTrue(strategy.applyFilterToCamelHeaders("camelHttpUri", "value", null)); + } + + @Test + void nonCamelHeadersPassThrough() { + assertFalse(strategy.applyFilterToExternalHeaders("Content-Type", "application/json", null)); + assertFalse(strategy.applyFilterToExternalHeaders("X-Request-Id", "abc-123", null)); + assertFalse(strategy.applyFilterToCamelHeaders("Content-Type", "application/json", null)); + } +} diff --git a/components/camel-vertx/camel-vertx-websocket/src/generated/java/org/apache/camel/component/vertx/websocket/VertxWebsocketEndpointConfigurer.java b/components/camel-vertx/camel-vertx-websocket/src/generated/java/org/apache/camel/component/vertx/websocket/VertxWebsocketEndpointConfigurer.java index 1fea496c4e88f..907f01d0bf1e0 100644 --- a/components/camel-vertx/camel-vertx-websocket/src/generated/java/org/apache/camel/component/vertx/websocket/VertxWebsocketEndpointConfigurer.java +++ b/components/camel-vertx/camel-vertx-websocket/src/generated/java/org/apache/camel/component/vertx/websocket/VertxWebsocketEndpointConfigurer.java @@ -43,6 +43,8 @@ public boolean configure(CamelContext camelContext, Object obj, String name, Obj case "fireWebSocketConnectionEvents": target.getConfiguration().setFireWebSocketConnectionEvents(property(camelContext, boolean.class, value)); return true; case "handshakeheaders": case "handshakeHeaders": target.getConfiguration().setHandshakeHeaders(property(camelContext, java.util.Map.class, value)); return true; + case "headerfilterstrategy": + case "headerFilterStrategy": target.setHeaderFilterStrategy(property(camelContext, org.apache.camel.spi.HeaderFilterStrategy.class, value)); return true; case "lazystartproducer": case "lazyStartProducer": target.setLazyStartProducer(property(camelContext, boolean.class, value)); return true; case "maxreconnectattempts": @@ -87,6 +89,8 @@ public Class getOptionType(String name, boolean ignoreCase) { case "fireWebSocketConnectionEvents": return boolean.class; case "handshakeheaders": case "handshakeHeaders": return java.util.Map.class; + case "headerfilterstrategy": + case "headerFilterStrategy": return org.apache.camel.spi.HeaderFilterStrategy.class; case "lazystartproducer": case "lazyStartProducer": return boolean.class; case "maxreconnectattempts": @@ -132,6 +136,8 @@ public Object getOptionValue(Object obj, String name, boolean ignoreCase) { case "fireWebSocketConnectionEvents": return target.getConfiguration().isFireWebSocketConnectionEvents(); case "handshakeheaders": case "handshakeHeaders": return target.getConfiguration().getHandshakeHeaders(); + case "headerfilterstrategy": + case "headerFilterStrategy": return target.getHeaderFilterStrategy(); case "lazystartproducer": case "lazyStartProducer": return target.isLazyStartProducer(); case "maxreconnectattempts": diff --git a/components/camel-vertx/camel-vertx-websocket/src/generated/java/org/apache/camel/component/vertx/websocket/VertxWebsocketEndpointUriFactory.java b/components/camel-vertx/camel-vertx-websocket/src/generated/java/org/apache/camel/component/vertx/websocket/VertxWebsocketEndpointUriFactory.java index 7b8ef2d7e9427..6d1617211cdef 100644 --- a/components/camel-vertx/camel-vertx-websocket/src/generated/java/org/apache/camel/component/vertx/websocket/VertxWebsocketEndpointUriFactory.java +++ b/components/camel-vertx/camel-vertx-websocket/src/generated/java/org/apache/camel/component/vertx/websocket/VertxWebsocketEndpointUriFactory.java @@ -23,7 +23,7 @@ public class VertxWebsocketEndpointUriFactory extends org.apache.camel.support.c private static final Set SECRET_PROPERTY_NAMES; private static final Map MULTI_VALUE_PREFIXES; static { - Set props = new HashSet<>(22); + Set props = new HashSet<>(23); props.add("allowOriginHeader"); props.add("allowedOriginPattern"); props.add("bridgeErrorHandler"); @@ -34,6 +34,7 @@ public class VertxWebsocketEndpointUriFactory extends org.apache.camel.support.c props.add("exchangePattern"); props.add("fireWebSocketConnectionEvents"); props.add("handshakeHeaders"); + props.add("headerFilterStrategy"); props.add("host"); props.add("lazyStartProducer"); props.add("maxReconnectAttempts"); diff --git a/components/camel-vertx/camel-vertx-websocket/src/generated/resources/META-INF/org/apache/camel/component/vertx/websocket/vertx-websocket.json b/components/camel-vertx/camel-vertx-websocket/src/generated/resources/META-INF/org/apache/camel/component/vertx/websocket/vertx-websocket.json index 032834ffc2ab9..7ef887130ceb3 100644 --- a/components/camel-vertx/camel-vertx-websocket/src/generated/resources/META-INF/org/apache/camel/component/vertx/websocket/vertx-websocket.json +++ b/components/camel-vertx/camel-vertx-websocket/src/generated/resources/META-INF/org/apache/camel/component/vertx/websocket/vertx-websocket.json @@ -61,9 +61,10 @@ "sendToAll": { "index": 15, "kind": "parameter", "displayName": "Send To All", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "To send to all websocket subscribers. Can be used to configure at the endpoint level, instead of providing the VertxWebsocketConstants.SEND_TO_ALL header on the message. Note that when using this option, the host name specified for the vertx-websocket producer URI must match one used for an existing vertx-websocket consumer. Note that this option only applies when producing messages to endpoints hosted by the vertx-websocket consumer and not to an externally hosted WebSocket." }, "clientOptions": { "index": 16, "kind": "parameter", "displayName": "Client Options", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "object", "javaType": "io.vertx.core.http.HttpClientOptions", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "Sets customized options for configuring the WebSocket client used in the producer" }, "lazyStartProducer": { "index": 17, "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." }, - "allowOriginHeader": { "index": 18, "kind": "parameter", "displayName": "Allow Origin Header", "group": "security", "label": "security", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "Whether the WebSocket client should add the Origin header to the WebSocket handshake request." }, - "handshakeHeaders": { "index": 19, "kind": "parameter", "displayName": "Handshake Headers", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "java.util.Map", "prefix": "handshake.", "multiValue": true, "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "Headers to send in the HTTP handshake request. When the endpoint is a consumer, it only works when it consumes a remote host as a client (i.e. consumeAsClient is true). This is a multi-value option with prefix: handshake." }, - "originHeaderUrl": { "index": 20, "kind": "parameter", "displayName": "Origin Header Url", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "The value of the Origin header that the WebSocket client should use on the WebSocket handshake request. When not specified, the WebSocket client will automatically determine the value for the Origin from the request URL." }, - "sslContextParameters": { "index": 21, "kind": "parameter", "displayName": "Ssl Context Parameters", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "To configure security using SSLContextParameters" } + "headerFilterStrategy": { "index": 18, "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 header to and from Camel message." }, + "allowOriginHeader": { "index": 19, "kind": "parameter", "displayName": "Allow Origin Header", "group": "security", "label": "security", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "Whether the WebSocket client should add the Origin header to the WebSocket handshake request." }, + "handshakeHeaders": { "index": 20, "kind": "parameter", "displayName": "Handshake Headers", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "java.util.Map", "prefix": "handshake.", "multiValue": true, "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "Headers to send in the HTTP handshake request. When the endpoint is a consumer, it only works when it consumes a remote host as a client (i.e. consumeAsClient is true). This is a multi-value option with prefix: handshake." }, + "originHeaderUrl": { "index": 21, "kind": "parameter", "displayName": "Origin Header Url", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "The value of the Origin header that the WebSocket client should use on the WebSocket handshake request. When not specified, the WebSocket client will automatically determine the value for the Origin from the request URL." }, + "sslContextParameters": { "index": 22, "kind": "parameter", "displayName": "Ssl Context Parameters", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration", "configurationField": "configuration", "description": "To configure security using SSLContextParameters" } } } diff --git a/components/camel-vertx/camel-vertx-websocket/src/main/java/org/apache/camel/component/vertx/websocket/VertxWebsocketConsumer.java b/components/camel-vertx/camel-vertx-websocket/src/main/java/org/apache/camel/component/vertx/websocket/VertxWebsocketConsumer.java index 235bbbc4c3638..98194669d9a3e 100644 --- a/components/camel-vertx/camel-vertx-websocket/src/main/java/org/apache/camel/component/vertx/websocket/VertxWebsocketConsumer.java +++ b/components/camel-vertx/camel-vertx-websocket/src/main/java/org/apache/camel/component/vertx/websocket/VertxWebsocketConsumer.java @@ -25,6 +25,7 @@ import org.apache.camel.Exchange; import org.apache.camel.Message; import org.apache.camel.Processor; +import org.apache.camel.spi.HeaderFilterStrategy; import org.apache.camel.support.DefaultConsumer; /** @@ -106,10 +107,21 @@ protected void populateExchangeHeaders( message.setHeader(VertxWebsocketConstants.REMOTE_ADDRESS, remote); message.setHeader(VertxWebsocketConstants.CONNECTION_KEY, connectionKey); message.setHeader(VertxWebsocketConstants.EVENT, event); + HeaderFilterStrategy headerFilterStrategy = getEndpoint().getHeaderFilterStrategy(); routingContext.queryParams() - .forEach((name, value) -> VertxWebsocketHelper.appendHeader(headers, name, value)); + .forEach((name, value) -> { + if (headerFilterStrategy == null + || !headerFilterStrategy.applyFilterToExternalHeaders(name, value, exchange)) { + VertxWebsocketHelper.appendHeader(headers, name, value); + } + }); routingContext.pathParams() - .forEach((name, value) -> VertxWebsocketHelper.appendHeader(headers, name, value)); + .forEach((name, value) -> { + if (headerFilterStrategy == null + || !headerFilterStrategy.applyFilterToExternalHeaders(name, value, exchange)) { + VertxWebsocketHelper.appendHeader(headers, name, value); + } + }); } protected void processExchange(Exchange exchange, RoutingContext routingContext) { diff --git a/components/camel-vertx/camel-vertx-websocket/src/main/java/org/apache/camel/component/vertx/websocket/VertxWebsocketEndpoint.java b/components/camel-vertx/camel-vertx-websocket/src/main/java/org/apache/camel/component/vertx/websocket/VertxWebsocketEndpoint.java index 28c6cadce0c90..46439641d075d 100644 --- a/components/camel-vertx/camel-vertx-websocket/src/main/java/org/apache/camel/component/vertx/websocket/VertxWebsocketEndpoint.java +++ b/components/camel-vertx/camel-vertx-websocket/src/main/java/org/apache/camel/component/vertx/websocket/VertxWebsocketEndpoint.java @@ -36,6 +36,8 @@ import org.apache.camel.Producer; import org.apache.camel.component.vertx.common.VertxHelper; import org.apache.camel.spi.EndpointServiceLocation; +import org.apache.camel.spi.HeaderFilterStrategy; +import org.apache.camel.spi.HeaderFilterStrategyAware; import org.apache.camel.spi.UriEndpoint; import org.apache.camel.spi.UriParam; import org.apache.camel.support.DefaultEndpoint; @@ -52,12 +54,15 @@ @UriEndpoint(firstVersion = "3.5.0", scheme = "vertx-websocket", title = "Vert.x WebSocket", syntax = "vertx-websocket:host:port/path", category = { Category.HTTP, Category.NETWORKING }, headersClass = VertxWebsocketConstants.class, lenientProperties = true) -public class VertxWebsocketEndpoint extends DefaultEndpoint implements EndpointServiceLocation { +public class VertxWebsocketEndpoint extends DefaultEndpoint implements EndpointServiceLocation, HeaderFilterStrategyAware { private static final Logger LOG = LoggerFactory.getLogger(VertxWebsocketEndpoint.class); @UriParam private VertxWebsocketConfiguration configuration; + @UriParam(label = "advanced", + description = "To use a custom HeaderFilterStrategy to filter header to and from Camel message.") + private HeaderFilterStrategy headerFilterStrategy; private HttpClient client; private WebSocket webSocket; @@ -124,6 +129,22 @@ public VertxWebsocketConfiguration getConfiguration() { return configuration; } + @Override + public HeaderFilterStrategy getHeaderFilterStrategy() { + if (headerFilterStrategy == null) { + headerFilterStrategy = new VertxWebsocketHeaderFilterStrategy(); + } + return headerFilterStrategy; + } + + /** + * To use a custom {@link org.apache.camel.spi.HeaderFilterStrategy} to filter header to and from Camel message. + */ + @Override + public void setHeaderFilterStrategy(HeaderFilterStrategy headerFilterStrategy) { + this.headerFilterStrategy = headerFilterStrategy; + } + protected Vertx getVertx() { return getComponent().getVertx(); } diff --git a/components/camel-vertx/camel-vertx-websocket/src/main/java/org/apache/camel/component/vertx/websocket/VertxWebsocketHeaderFilterStrategy.java b/components/camel-vertx/camel-vertx-websocket/src/main/java/org/apache/camel/component/vertx/websocket/VertxWebsocketHeaderFilterStrategy.java new file mode 100644 index 0000000000000..232de276eb633 --- /dev/null +++ b/components/camel-vertx/camel-vertx-websocket/src/main/java/org/apache/camel/component/vertx/websocket/VertxWebsocketHeaderFilterStrategy.java @@ -0,0 +1,34 @@ +/* + * 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.vertx.websocket; + +import org.apache.camel.support.DefaultHeaderFilterStrategy; + +/** + * Default header filter strategy for Vert.x WebSocket endpoints. + *

+ * Filters out Camel internal headers (starting with "Camel" or "camel") in both directions to prevent external + * WebSocket clients from injecting internal Camel headers via query or path parameters. + */ +public class VertxWebsocketHeaderFilterStrategy extends DefaultHeaderFilterStrategy { + + public VertxWebsocketHeaderFilterStrategy() { + setLowerCase(true); + setOutFilterStartsWith(CAMEL_FILTER_STARTS_WITH); + setInFilterStartsWith(CAMEL_FILTER_STARTS_WITH); + } +} diff --git a/components/camel-vertx/camel-vertx-websocket/src/test/java/org/apache/camel/component/vertx/websocket/VertxWebsocketHeaderFilterStrategyTest.java b/components/camel-vertx/camel-vertx-websocket/src/test/java/org/apache/camel/component/vertx/websocket/VertxWebsocketHeaderFilterStrategyTest.java new file mode 100644 index 0000000000000..3f2d7f1ffc26f --- /dev/null +++ b/components/camel-vertx/camel-vertx-websocket/src/test/java/org/apache/camel/component/vertx/websocket/VertxWebsocketHeaderFilterStrategyTest.java @@ -0,0 +1,53 @@ +/* + * 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.vertx.websocket; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class VertxWebsocketHeaderFilterStrategyTest { + + private final VertxWebsocketHeaderFilterStrategy strategy = new VertxWebsocketHeaderFilterStrategy(); + + @Test + void inboundCamelHeadersAreFiltered() { + assertTrue(strategy.applyFilterToExternalHeaders("CamelHttpUri", "http://evil.example", null)); + assertTrue(strategy.applyFilterToExternalHeaders("CamelFileName", "../../etc/passwd", null)); + assertTrue(strategy.applyFilterToExternalHeaders("CamelBeanMethodName", "evilMethod", null)); + } + + @Test + void inboundLowercaseCamelHeadersAreFiltered() { + assertTrue(strategy.applyFilterToExternalHeaders("camelHttpUri", "http://evil.example", null)); + assertTrue(strategy.applyFilterToExternalHeaders("camelfilename", "../../etc/passwd", null)); + } + + @Test + void outboundCamelHeadersAreFiltered() { + assertTrue(strategy.applyFilterToCamelHeaders("CamelHttpUri", "value", null)); + assertTrue(strategy.applyFilterToCamelHeaders("camelHttpUri", "value", null)); + } + + @Test + void nonCamelHeadersPassThrough() { + assertFalse(strategy.applyFilterToExternalHeaders("Content-Type", "application/json", null)); + assertFalse(strategy.applyFilterToExternalHeaders("X-Request-Id", "abc-123", null)); + assertFalse(strategy.applyFilterToCamelHeaders("Content-Type", "application/json", null)); + } +} diff --git a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_18.adoc b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_18.adoc index bae55ac54afd4..e046867ada9d7 100644 --- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_18.adoc +++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_18.adoc @@ -324,3 +324,32 @@ have been renamed accordingly: * `ccbTtl()` -> `couchbaseTtl()` * `ccbDdn()` -> `couchbaseDesignDocumentName()` * `ccbVn()` -> `couchbaseViewName()` + +=== camel-vertx-websocket + +The `vertx-websocket` consumer now applies a `HeaderFilterStrategy` to the WebSocket query and +path parameters before mapping them into the Camel message headers. The new default +`VertxWebsocketHeaderFilterStrategy` filters headers starting with `Camel` / `camel` +(case-insensitive) in both the inbound and outbound directions, aligning the component with the +rest of the Camel component catalog (`camel-coap`, `camel-kafka`, `camel-nats`, ...). A new +`headerFilterStrategy` endpoint option is available; routes that relied on receiving +`Camel`-prefixed header names from WebSocket query or path parameters can supply a custom +`headerFilterStrategy` to restore the previous behaviour. + +=== camel-atmosphere-websocket + +The `atmosphere-websocket` consumer now applies the endpoint `HeaderFilterStrategy` to the +WebSocket query parameters before mapping them into the Camel message headers. The inherited +default `HttpHeaderFilterStrategy` filters headers starting with `Camel` / `camel` +(case-insensitive). Routes that relied on receiving `Camel`-prefixed header names from WebSocket +query parameters can supply a custom `headerFilterStrategy` to restore the previous behaviour. + +=== camel-iggy + +The `iggy` consumer now applies a `HeaderFilterStrategy` to the Iggy message user-headers before +mapping them into the Camel message headers. The new default `IggyHeaderFilterStrategy` filters +headers starting with `Camel` / `camel` (case-insensitive) in both the inbound and outbound +directions, aligning the component with the rest of the Camel component catalog. A new +`headerFilterStrategy` endpoint option is available; routes that relied on receiving +`Camel`-prefixed user-header names from Iggy messages can supply a custom `headerFilterStrategy` +to restore the previous behaviour. diff --git a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/IggyEndpointBuilderFactory.java b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/IggyEndpointBuilderFactory.java index 6410c9f3d558e..bc8b908444abf 100644 --- a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/IggyEndpointBuilderFactory.java +++ b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/IggyEndpointBuilderFactory.java @@ -728,6 +728,38 @@ default AdvancedIggyEndpointConsumerBuilder exchangePattern(String exchangePatte doSetProperty("exchangePattern", exchangePattern); return this; } + /** + * To use a custom HeaderFilterStrategy to filter header to and from + * Camel message. + * + * The option is a: + * org.apache.camel.spi.HeaderFilterStrategy type. + * + * Group: advanced + * + * @param headerFilterStrategy the value to set + * @return the dsl builder + */ + default AdvancedIggyEndpointConsumerBuilder headerFilterStrategy(org.apache.camel.spi.HeaderFilterStrategy headerFilterStrategy) { + doSetProperty("headerFilterStrategy", headerFilterStrategy); + return this; + } + /** + * To use a custom HeaderFilterStrategy to filter header to and from + * Camel message. + * + * 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 AdvancedIggyEndpointConsumerBuilder headerFilterStrategy(String headerFilterStrategy) { + doSetProperty("headerFilterStrategy", headerFilterStrategy); + return this; + } } /** @@ -1168,6 +1200,38 @@ default AdvancedIggyEndpointProducerBuilder lazyStartProducer(String lazyStartPr doSetProperty("lazyStartProducer", lazyStartProducer); return this; } + /** + * To use a custom HeaderFilterStrategy to filter header to and from + * Camel message. + * + * The option is a: + * org.apache.camel.spi.HeaderFilterStrategy type. + * + * Group: advanced + * + * @param headerFilterStrategy the value to set + * @return the dsl builder + */ + default AdvancedIggyEndpointProducerBuilder headerFilterStrategy(org.apache.camel.spi.HeaderFilterStrategy headerFilterStrategy) { + doSetProperty("headerFilterStrategy", headerFilterStrategy); + return this; + } + /** + * To use a custom HeaderFilterStrategy to filter header to and from + * Camel message. + * + * 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 AdvancedIggyEndpointProducerBuilder headerFilterStrategy(String headerFilterStrategy) { + doSetProperty("headerFilterStrategy", headerFilterStrategy); + return this; + } } /** @@ -1534,6 +1598,38 @@ default IggyEndpointBuilder basic() { return (IggyEndpointBuilder) this; } + /** + * To use a custom HeaderFilterStrategy to filter header to and from + * Camel message. + * + * The option is a: + * org.apache.camel.spi.HeaderFilterStrategy type. + * + * Group: advanced + * + * @param headerFilterStrategy the value to set + * @return the dsl builder + */ + default AdvancedIggyEndpointBuilder headerFilterStrategy(org.apache.camel.spi.HeaderFilterStrategy headerFilterStrategy) { + doSetProperty("headerFilterStrategy", headerFilterStrategy); + return this; + } + /** + * To use a custom HeaderFilterStrategy to filter header to and from + * Camel message. + * + * 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 AdvancedIggyEndpointBuilder headerFilterStrategy(String headerFilterStrategy) { + doSetProperty("headerFilterStrategy", headerFilterStrategy); + return this; + } } public interface IggyBuilders { diff --git a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/VertxWebsocketEndpointBuilderFactory.java b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/VertxWebsocketEndpointBuilderFactory.java index 637db5f262203..ebc10c4f609ec 100644 --- a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/VertxWebsocketEndpointBuilderFactory.java +++ b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/VertxWebsocketEndpointBuilderFactory.java @@ -531,6 +531,38 @@ default AdvancedVertxWebsocketEndpointConsumerBuilder serverOptions(String serve doSetProperty("serverOptions", serverOptions); return this; } + /** + * To use a custom HeaderFilterStrategy to filter header to and from + * Camel message. + * + * The option is a: + * org.apache.camel.spi.HeaderFilterStrategy type. + * + * Group: advanced + * + * @param headerFilterStrategy the value to set + * @return the dsl builder + */ + default AdvancedVertxWebsocketEndpointConsumerBuilder headerFilterStrategy(org.apache.camel.spi.HeaderFilterStrategy headerFilterStrategy) { + doSetProperty("headerFilterStrategy", headerFilterStrategy); + return this; + } + /** + * To use a custom HeaderFilterStrategy to filter header to and from + * Camel message. + * + * 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 AdvancedVertxWebsocketEndpointConsumerBuilder headerFilterStrategy(String headerFilterStrategy) { + doSetProperty("headerFilterStrategy", headerFilterStrategy); + return this; + } } /** @@ -812,6 +844,38 @@ default AdvancedVertxWebsocketEndpointProducerBuilder lazyStartProducer(String l doSetProperty("lazyStartProducer", lazyStartProducer); return this; } + /** + * To use a custom HeaderFilterStrategy to filter header to and from + * Camel message. + * + * The option is a: + * org.apache.camel.spi.HeaderFilterStrategy type. + * + * Group: advanced + * + * @param headerFilterStrategy the value to set + * @return the dsl builder + */ + default AdvancedVertxWebsocketEndpointProducerBuilder headerFilterStrategy(org.apache.camel.spi.HeaderFilterStrategy headerFilterStrategy) { + doSetProperty("headerFilterStrategy", headerFilterStrategy); + return this; + } + /** + * To use a custom HeaderFilterStrategy to filter header to and from + * Camel message. + * + * 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 AdvancedVertxWebsocketEndpointProducerBuilder headerFilterStrategy(String headerFilterStrategy) { + doSetProperty("headerFilterStrategy", headerFilterStrategy); + return this; + } } /** @@ -960,6 +1024,38 @@ default VertxWebsocketEndpointBuilder basic() { return (VertxWebsocketEndpointBuilder) this; } + /** + * To use a custom HeaderFilterStrategy to filter header to and from + * Camel message. + * + * The option is a: + * org.apache.camel.spi.HeaderFilterStrategy type. + * + * Group: advanced + * + * @param headerFilterStrategy the value to set + * @return the dsl builder + */ + default AdvancedVertxWebsocketEndpointBuilder headerFilterStrategy(org.apache.camel.spi.HeaderFilterStrategy headerFilterStrategy) { + doSetProperty("headerFilterStrategy", headerFilterStrategy); + return this; + } + /** + * To use a custom HeaderFilterStrategy to filter header to and from + * Camel message. + * + * 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 AdvancedVertxWebsocketEndpointBuilder headerFilterStrategy(String headerFilterStrategy) { + doSetProperty("headerFilterStrategy", headerFilterStrategy); + return this; + } } public interface VertxWebsocketBuilders {