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

davsclaus pushed a commit to branch fix/CAMEL-24147
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 3549c603e343e3697433ab045d6291856d35c239
Author: Claus Ibsen <[email protected]>
AuthorDate: Fri Jul 17 10:39:25 2026 +0200

    CAMEL-24147: camel-lra - URL-encode callback query parameters and fix 
parseQuery split
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    Signed-off-by: Claus Ibsen <[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&ampersand", 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&region=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");
+    }
+}

Reply via email to