This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch backport/25084-to-camel-4.18.x in repository https://gitbox.apache.org/repos/asf/camel.git
commit b88fc64b5aa9afae6e7ca500046474be0fae1397 Author: Claus Ibsen <[email protected]> AuthorDate: Fri Jul 24 11:30:46 2026 +0200 CAMEL-24253: Endpoint DSL auto-wraps values with + or % in RAW() to prevent mangling Fixes a regression from 4.14.0 where + and % characters in Endpoint DSL option values were silently mangled during URI encoding/decoding round-trip. computeProperties() now auto-wraps string values containing + or % in RAW() so the URI parser preserves them verbatim. Closes #25084 Co-Authored-By: Claude Opus 4.6 <[email protected]> --- .../builder/endpoint/AbstractEndpointBuilder.java | 23 +++++++++- .../endpoint/FtpSpecialCharParameterTest.java | 49 ++++++++++++++++++++++ 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/dsl/camel-endpointdsl/src/main/java/org/apache/camel/builder/endpoint/AbstractEndpointBuilder.java b/dsl/camel-endpointdsl/src/main/java/org/apache/camel/builder/endpoint/AbstractEndpointBuilder.java index 5ee058bb54a9..27bd98f44de1 100644 --- a/dsl/camel-endpointdsl/src/main/java/org/apache/camel/builder/endpoint/AbstractEndpointBuilder.java +++ b/dsl/camel-endpointdsl/src/main/java/org/apache/camel/builder/endpoint/AbstractEndpointBuilder.java @@ -113,8 +113,7 @@ public class AbstractEndpointBuilder { changed = camelContext.getCamelContextExtension().resolvePropertyPlaceholders(text, true); } if (changed != null && !changed.startsWith(PropertiesComponent.PREFIX_OPTIONAL_TOKEN)) { - // resolve then use - params.put(key, changed); + params.put(key, wrapRawIfNeeded(changed)); } } else if (val instanceof Number || val instanceof Boolean || val instanceof Enum<?>) { params.put(key, val.toString()); @@ -128,6 +127,26 @@ public class AbstractEndpointBuilder { } } + /** + * Wraps the value in RAW() if it contains characters that would be mangled during URI encoding/decoding round-trip + * (+ is decoded as space by URLDecoder, % causes double-decode issues). + */ + private static String wrapRawIfNeeded(String value) { + if (value.startsWith("RAW(")) { + return value; + } + if (value.startsWith("#")) { + return value; + } + for (int i = 0; i < value.length(); i++) { + char ch = value.charAt(i); + if (ch == '+' || ch == '%') { + return "RAW(" + value + ")"; + } + } + return value; + } + @Override public String toString() { return getRawUri(); diff --git a/dsl/camel-endpointdsl/src/test/java/org/apache/camel/builder/endpoint/FtpSpecialCharParameterTest.java b/dsl/camel-endpointdsl/src/test/java/org/apache/camel/builder/endpoint/FtpSpecialCharParameterTest.java new file mode 100644 index 000000000000..35db9f352449 --- /dev/null +++ b/dsl/camel-endpointdsl/src/test/java/org/apache/camel/builder/endpoint/FtpSpecialCharParameterTest.java @@ -0,0 +1,49 @@ +/* + * 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.builder.endpoint; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.file.remote.FtpEndpoint; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +class FtpSpecialCharParameterTest extends BaseEndpointDslTest { + + @Test + void testPlusAndPercentPreservedWithoutManualRaw() throws Exception { + FtpEndpoint ftp = (FtpEndpoint) context.getEndpoints().stream() + .filter(e -> e.getEndpointUri().startsWith("ftp")) + .findFirst().get(); + assertNotNull(ftp); + assertEquals("foo+bar", ftp.getConfiguration().getUsername()); + assertEquals("baz+bang%ok", ftp.getConfiguration().getPassword()); + } + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new EndpointRouteBuilder() { + public void configure() throws Exception { + from(ftp("localhost:2121/inbox").username("foo+bar").password("baz+bang%ok").binary(true).delay(5000)) + .routeId("myroute").autoStartup(false) + .to(mock("result")); + } + }; + } + +}
