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 5abbaf87a90c CAMEL-25188: camel-util - URISupport.normalizeUri gives
the same uri when normalizing a normalized uri (space, = and # in values, + in
keys) (#27131)
5abbaf87a90c is described below
commit 5abbaf87a90c695ad1f0f040ed881e16e68b84e4
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu Oct 1 17:41:33 2026 +0200
CAMEL-25188: camel-util - URISupport.normalizeUri gives the same uri when
normalizing a normalized uri (space, = and # in values, + in keys) (#27131)
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
---
.../java/org/apache/camel/util/URISupport.java | 26 ++++++---
.../java/org/apache/camel/util/URISupportTest.java | 67 +++++++++++++++++++++-
.../ROOT/pages/camel-4x-upgrade-guide-4_23.adoc | 6 +-
3 files changed, 87 insertions(+), 12 deletions(-)
diff --git
a/core/camel-util/src/main/java/org/apache/camel/util/URISupport.java
b/core/camel-util/src/main/java/org/apache/camel/util/URISupport.java
index 25250ff857b6..004425829f16 100644
--- a/core/camel-util/src/main/java/org/apache/camel/util/URISupport.java
+++ b/core/camel-util/src/main/java/org/apache/camel/util/URISupport.java
@@ -900,6 +900,12 @@ public final class URISupport {
// createQueryString()/URLEncoder, which would needlessly
percent-encode characters that are
// legal unescaped in a URI query, such as ':' (eg host:port) or '/'
(eg produces=application/json)
query = buildSafeQueryString(keys, parameters);
+ if (query.indexOf('%') != -1) {
+ // a key or value needed a percent escape (such as = or # in a
value), and a uri with % is normalized by
+ // the complex normalizer, which form-encodes the whole query;
encode the same way here so normalizing a
+ // normalized uri gives the same uri (the fast parser only takes
uris without %, so all % come from here)
+ query = createQueryString(keys, parameters, true);
+ }
return buildUri(scheme, path, query);
}
@@ -939,7 +945,7 @@ public final class URISupport {
}
private static void appendSafeQueryStringParameter(String key, String
value, StringBuilder sb) {
- sb.append(key);
+ sb.append(safeEncodeQueryPart(key));
if (value == null) {
return;
}
@@ -950,15 +956,21 @@ public final class URISupport {
// need to replace % with %25 to avoid losing "%" when decoding
sb.append(URIScanner.replacePercent(value));
} else {
- // '&' and '=' are structurally significant in Camel's
key=value&key=value query syntax
- // and must stay escaped inside a value even though they are
otherwise legal, unescaped
- // characters in a URI query per RFC 3986 -
UnsafeUriCharactersEncoder does not escape them
- // as it is also used outside of this query-value context
- String encoded =
UnsafeUriCharactersEncoder.encode(value).replace("&", "%26").replace("=",
"%3D");
- sb.append(encoded);
+ sb.append(safeEncodeQueryPart(value));
}
}
+ private static String safeEncodeQueryPart(String text) {
+ // '&' and '=' are structurally significant in Camel's
key=value&key=value query syntax
+ // and must stay escaped inside a key or value even though they are
otherwise legal, unescaped
+ // characters in a URI query per RFC 3986 - UnsafeUriCharactersEncoder
does not escape them
+ // as it is also used outside of this query-value context
+ String encoded = UnsafeUriCharactersEncoder.encode(text).replace("&",
"%26").replace("=", "%3D");
+ // a space as +, as the complex normalizer (createQueryString) writes
it; the fast parser only takes uris
+ // without %, so %20 here is always a space
+ return encoded.replace("%20", "+");
+ }
+
private static String buildUri(String scheme, String path, String query) {
// must include :// to do a correct URI all components can work with
int len = scheme.length() + 3 + path.length();
diff --git
a/core/camel-util/src/test/java/org/apache/camel/util/URISupportTest.java
b/core/camel-util/src/test/java/org/apache/camel/util/URISupportTest.java
index 4cd22a9b2bf0..a8b6f46ff24a 100644
--- a/core/camel-util/src/test/java/org/apache/camel/util/URISupportTest.java
+++ b/core/camel-util/src/test/java/org/apache/camel/util/URISupportTest.java
@@ -148,6 +148,67 @@ public class URISupportTest {
assertEquals("http://www.google.com?q=S%C3%B8ren+Hansen", out);
}
+ @Test
+ public void testNormalizeSpaceTheSameInEverySpelling() throws Exception {
+ // CAMEL-25188: the fast and the complex normalizer both write a space
in a value as +
+
assertThat(URISupport.normalizeUri("log:foo?marker=a+b")).isEqualTo("log://foo?marker=a+b");
+
assertThat(URISupport.normalizeUri("log:foo?marker=a%20b")).isEqualTo("log://foo?marker=a+b");
+ assertThat(URISupport.normalizeUri("log:foo?marker=a
b")).isEqualTo("log://foo?marker=a+b");
+ assertThat(URISupport.normalizeUri("log:foo?showAll=true&marker=a+b"))
+ .isEqualTo("log://foo?marker=a+b&showAll=true");
+
assertThat(URISupport.normalizeUri("log:foo?marker=a++b")).isEqualTo("log://foo?marker=a++b");
+ }
+
+ @Test
+ public void testNormalizeValueWithPercentEscapeFormEncodesTheQuery()
throws Exception {
+ // CAMEL-25188: a value with = or # needs a percent escape, and a uri
with % is normalized by the complex
+ // normalizer, so the fast normalizer form-encodes the whole query the
same way, whatever the key order
+ assertThat(URISupport.normalizeUri("log:foo?secretKey=abc/def=="))
+ .isEqualTo("log://foo?secretKey=abc%2Fdef%3D%3D");
+
assertThat(URISupport.normalizeUri("log:foo?marker=a#b/c")).isEqualTo("log://foo?marker=a%23b%2Fc");
+
assertThat(URISupport.normalizeUri("jms:queue:foo?foo=bar&selector=somekey='somevalue'"))
+
.isEqualTo("jms://queue:foo?foo=bar&selector=somekey%3D%27somevalue%27");
+ // without a percent escape the query stays readable
+
assertThat(URISupport.normalizeUri("foo:bar?produces=application/json&host=http://h"))
+
.isEqualTo("foo://bar?host=http://h&produces=application/json");
+ }
+
+ @Test
+ public void testNormalizeSpaceInKey() throws Exception {
+ // CAMEL-25188: a + in a key is a space, written back as + as in a
value
+
assertThat(URISupport.normalizeUri("log:foo?a+b=1")).isEqualTo("log://foo?a+b=1");
+
assertThat(URISupport.normalizeUri("log:foo?a+b=1&c=2")).isEqualTo("log://foo?a+b=1&c=2");
+ }
+
+ @Test
+ public void testNormalizeTwiceGivesTheSameUri() throws Exception {
+ // CAMEL-25188: a normalized uri normalizes to itself, so endpoint
keys and lookups agree
+ String[] uris = {
+ "http://localhost:8080/foo?a=1&b=2",
+ "http://localhost:8080/foo?b=hello world&a=1",
+ "http://localhost:8080/foo?q=a+b",
+ "http://localhost:8080/foo?q=a%20b",
+ "http://localhost:8080/foo?q=a+b&x=1",
+ "ftp://[email protected]:21/dir?password=se+cret&binary=true",
+ "ftp://[email protected]:21/dir?password=RAW(se+cret)&binary=true",
+ "log:foo?level=INFO&showAll=true",
+ "timer:tick?period=1s&delay=2s",
+ "direct:start?b=\u00f8&a=1",
+ "file:target/in?include=.*\\.txt&noop=true",
+ "http://h/p?x=a&x=b&y=1",
+ "mock:a?b=x+y+z&a=1",
+ "jms:queue:foo?selector=somekey='somevalue'&foo=bar",
+ "log:foo?secretKey=abc/def==",
+
"log:foo?webhookExternalUrl=https://example.com/hook?token=abc",
+ "log:foo?marker=a#b/c",
+ "log:foo?a+b=1",
+ "foo:bar?host=http://h&produces=application/json&x=a=b" };
+ for (String uri : uris) {
+ String once = URISupport.normalizeUri(uri);
+ assertThat(URISupport.normalizeUri(once)).as("normalizing %s
twice", uri).isEqualTo(once);
+ }
+ }
+
@Test
public void testParseParametersURLEncodedValue() throws Exception {
String out =
URISupport.normalizeUri("http://www.google.com?q=S%C3%B8ren%20Hansen");
@@ -205,9 +266,9 @@ public class URISupportTest {
public void testNormalizeEndpointWithEqualSignInParameter() throws
Exception {
String out =
URISupport.normalizeUri("jms:queue:foo?selector=somekey='somevalue'&foo=bar");
assertNotNull(out);
- // Camel will safe encode the URI - '=' stays escaped as it is
structurally significant in
- // the query syntax, but the single quotes (legal unescaped in a URI
query) are left as-is
- assertEquals("jms://queue:foo?foo=bar&selector=somekey%3D'somevalue'",
out);
+ // Camel will safe encode the URI - a value with '=' needs a percent
escape, so the query is form-encoded
+ // as the complex normalizer does, and normalizing the uri again gives
the same uri
+
assertEquals("jms://queue:foo?foo=bar&selector=somekey%3D%27somevalue%27", out);
}
@Test
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
index 0e5931d385aa..181ec80ab258 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
@@ -953,8 +953,10 @@ reusing the cached one, with no error or log warning.
Normalization is now always order-independent. As part of the fix, the
encoding applied when rebuilding the
query string is also less aggressive: characters that are legal unescaped in a
URI query per RFC 3986
(`:`, `/`, `,`, `'`, etc. - for example a MIME type such as
`produces=application/json`, or a `host:port`
-value) are no longer percent-encoded, while `&` and `=` remain escaped inside
a value since they are
-structurally significant in Camel's own `key=value&key=value` query syntax.
+value) are no longer percent-encoded, as long as no key or value in the query
needs a percent escape.
+A value with `=` or `#` (for example `secretKey=abc/def==`) needs one, and
then the whole query is
+form-encoded as in earlier releases, the same way as an endpoint URI that is
already percent-encoded, so
+normalizing a normalized URI gives the same URI.
Code that asserts a literal, fully-normalized endpoint URI string containing
one of those characters in a
query value may need to update the expected string to the (now consistently)
unencoded form.