This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new ec619f2e17df CAMEL-24147: camel-lra - URL-encode callback query
parameters and fix parseQuery split
ec619f2e17df is described below
commit ec619f2e17df25b8e9e362dd5780c32861cb9637
Author: Claus Ibsen <[email protected]>
AuthorDate: Fri Jul 17 11:31:06 2026 +0200
CAMEL-24147: camel-lra - URL-encode callback query parameters and fix
parseQuery split
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---
.../apache/camel/service/lra/LRASagaRoutes.java | 4 +-
.../apache/camel/service/lra/LRAUrlBuilder.java | 8 +-
.../camel/service/lra/LRASagaRoutesTest.java | 30 ++++++
.../camel/service/lra/LRAUrlBuilderTest.java | 110 +++++++++++++++++++++
4 files changed, 149 insertions(+), 3 deletions(-)
diff --git
a/components/camel-lra/src/main/java/org/apache/camel/service/lra/LRASagaRoutes.java
b/components/camel-lra/src/main/java/org/apache/camel/service/lra/LRASagaRoutes.java
index 4d442cff869c..b2538ac31c67 100644
---
a/components/camel-lra/src/main/java/org/apache/camel/service/lra/LRASagaRoutes.java
+++
b/components/camel-lra/src/main/java/org/apache/camel/service/lra/LRASagaRoutes.java
@@ -69,8 +69,8 @@ public class LRASagaRoutes extends RouteBuilder {
// first, split by parameter separator '&'
// then collect the map with the variable name '[0]' and value
'[1]', both url decoded
result = Arrays.stream(queryStr.split("&")).collect(
- Collectors.toMap(element ->
decode(saveArrayAccess(element.split("="), 0)),
- element ->
decode(saveArrayAccess(element.split("="), 1))));
+ Collectors.toMap(element ->
decode(saveArrayAccess(element.split("=", 2), 0)),
+ element ->
decode(saveArrayAccess(element.split("=", 2), 1))));
} else {
LOG.debug("query param is empty, nothing to parse.");
diff --git
a/components/camel-lra/src/main/java/org/apache/camel/service/lra/LRAUrlBuilder.java
b/components/camel-lra/src/main/java/org/apache/camel/service/lra/LRAUrlBuilder.java
index e3f4bf80c9c4..b222ec250356 100644
---
a/components/camel-lra/src/main/java/org/apache/camel/service/lra/LRAUrlBuilder.java
+++
b/components/camel-lra/src/main/java/org/apache/camel/service/lra/LRAUrlBuilder.java
@@ -16,6 +16,8 @@
*/
package org.apache.camel.service.lra;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Optional;
@@ -93,7 +95,7 @@ public class LRAUrlBuilder {
} else {
copy.query += "&";
}
- copy.query += toNonnullString(key) + "=" + toNonnullString(value);
+ copy.query += encode(toNonnullString(key)) + "=" +
encode(toNonnullString(value));
return copy;
}
@@ -116,6 +118,10 @@ public class LRAUrlBuilder {
return first + "/" + second;
}
+ private String encode(String value) {
+ return URLEncoder.encode(value, StandardCharsets.UTF_8);
+ }
+
private String toNonnullString(Object obj) {
return obj != null ? obj.toString() : "";
}
diff --git
a/components/camel-lra/src/test/java/org/apache/camel/service/lra/LRASagaRoutesTest.java
b/components/camel-lra/src/test/java/org/apache/camel/service/lra/LRASagaRoutesTest.java
index c9762505cf6e..022bec7885d5 100644
---
a/components/camel-lra/src/test/java/org/apache/camel/service/lra/LRASagaRoutesTest.java
+++
b/components/camel-lra/src/test/java/org/apache/camel/service/lra/LRASagaRoutesTest.java
@@ -79,6 +79,36 @@ public class LRASagaRoutesTest {
"query parameter value for name 'key2' has unexpected
content");
}
+ @DisplayName("Tests parseQuery() preserves values containing '='
characters")
+ @Test
+ void testParseQueryWithEqualsInValue() throws InvocationTargetException,
IllegalAccessException, NoSuchMethodException {
+
+ Map<String, String> testResult = (Map<String, String>)
getParseQueryMethod()
+ .invoke(new LRASagaRoutes(null),
+ "key1=value%3Dwith%3Dequals&key2=normal");
+
+ Assertions.assertEquals(2, testResult.size(), "query parameter count
must be two");
+ Assertions.assertEquals("value=with=equals", testResult.get("key1"),
+ "value containing '=' characters must be preserved");
+ Assertions.assertEquals("normal", testResult.get("key2"));
+ }
+
+ @DisplayName("Tests parseQuery() decodes URL-encoded special characters
correctly")
+ @Test
+ void testParseQueryWithEncodedSpecialChars()
+ throws InvocationTargetException, IllegalAccessException,
NoSuchMethodException {
+
+ Map<String, String> testResult = (Map<String, String>)
getParseQueryMethod()
+ .invoke(new LRASagaRoutes(null),
+ "key1=value+with%26ampersand&key2=a%2Bb");
+
+ Assertions.assertEquals(2, testResult.size(), "query parameter count
must be two");
+ Assertions.assertEquals("value with&ersand", testResult.get("key1"),
+ "encoded '&' and '+' must be decoded correctly");
+ Assertions.assertEquals("a+b", testResult.get("key2"),
+ "encoded '+' must be decoded correctly");
+ }
+
@DisplayName("Tests parseQuery() to handle incorrect query string (no
value is given)")
@Test
void testParseQuerySuccessEncoded() throws InvocationTargetException,
IllegalAccessException, NoSuchMethodException {
diff --git
a/components/camel-lra/src/test/java/org/apache/camel/service/lra/LRAUrlBuilderTest.java
b/components/camel-lra/src/test/java/org/apache/camel/service/lra/LRAUrlBuilderTest.java
new file mode 100644
index 000000000000..3af26e8a1507
--- /dev/null
+++
b/components/camel-lra/src/test/java/org/apache/camel/service/lra/LRAUrlBuilderTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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.service.lra;
+
+import java.lang.reflect.Method;
+import java.net.URI;
+import java.util.Map;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class LRAUrlBuilderTest {
+
+ @DisplayName("Tests that query() URL-encodes special characters in values")
+ @Test
+ void testQueryEncodesSpecialCharacters() {
+ String url = new LRAUrlBuilder()
+ .host("http://localhost:8080")
+ .path("lra-participant")
+ .query("key", "value with spaces&and=special+chars#hash")
+ .build();
+
+
assertTrue(url.contains("key=value+with+spaces%26and%3Dspecial%2Bchars%23hash"),
+ "Special characters must be URL-encoded in query values: " +
url);
+ }
+
+ @DisplayName("Tests that compensation URI with query params is properly
encoded")
+ @Test
+ void testCompensationUriWithQueryParams() {
+ String url = new LRAUrlBuilder()
+ .host("http://localhost:8080")
+ .path("lra-participant")
+
.compensation("seda:cancelOrder?concurrentConsumers=2&size=1000")
+ .build();
+
+ URI uri = URI.create(url);
+ assertNotNull(uri.getQuery(), "URL must have a valid query string");
+
+ String query = uri.getRawQuery();
+ assertTrue(query.contains("Camel-Saga-Compensate="),
+ "Query must contain the compensation key");
+
assertTrue(query.contains("seda%3AcancelOrder%3FconcurrentConsumers%3D2%26size%3D1000"),
+ "Compensation URI must be fully encoded: " + query);
+ }
+
+ @DisplayName("Tests full round-trip: LRAUrlBuilder encodes, parseQuery
decodes back to original")
+ @Test
+ void testCallbackUrlRoundTrip() throws Exception {
+ String compensationUri =
"seda:cancelOrder?concurrentConsumers=2&size=1000";
+ String completionUri = "direct:complete";
+ String optionValue = "ACME®ion=EU";
+
+ String url = new LRAUrlBuilder()
+ .host("http://localhost:8080")
+ .path("lra-participant")
+ .query("myOption", optionValue)
+ .compensation(compensationUri)
+ .completion(completionUri)
+ .path("compensate")
+ .build();
+
+ URI uri = URI.create(url);
+ String rawQuery = uri.getRawQuery();
+
+ Method parseQuery =
LRASagaRoutes.class.getDeclaredMethod("parseQuery", String.class);
+ parseQuery.setAccessible(true);
+ @SuppressWarnings("unchecked")
+ Map<String, String> parsed = (Map<String, String>)
parseQuery.invoke(new LRASagaRoutes(null), rawQuery);
+
+ assertEquals(compensationUri, parsed.get("Camel-Saga-Compensate"),
+ "Compensation URI must survive encode/decode round-trip");
+ assertEquals(completionUri, parsed.get("Camel-Saga-Complete"),
+ "Completion URI must survive encode/decode round-trip");
+ assertEquals(optionValue, parsed.get("myOption"),
+ "Option value with special characters must survive
encode/decode round-trip");
+ }
+
+ @DisplayName("Tests that simple direct: URIs still work after encoding")
+ @Test
+ void testSimpleDirectUri() {
+ String url = new LRAUrlBuilder()
+ .host("http://localhost:8080")
+ .path("lra-participant")
+ .compensation("direct://saga1_participant1_compensate")
+ .completion("direct://saga1_participant1_complete")
+ .path("compensate")
+ .build();
+
+ URI uri = URI.create(url);
+ assertNotNull(uri.getQuery(), "URL must have a valid query string");
+ }
+}