[ 
https://issues.apache.org/jira/browse/CAMEL-24524?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Work on CAMEL-24524 started by Claus Ibsen.
-------------------------------------------
> camel-core: URISupport.normalizeUri fast path skips value encoding when 
> parameters are already sorted, causing duplicate endpoints
> ----------------------------------------------------------------------------------------------------------------------------------
>
>                 Key: CAMEL-24524
>                 URL: https://issues.apache.org/jira/browse/CAMEL-24524
>             Project: Camel
>          Issue Type: Bug
>            Reporter: Claus Ibsen
>            Assignee: Claus Ibsen
>            Priority: Major
>
> h3. Summary
> {{URISupport.normalizeUri()}} is used to compute the registry/cache key for 
> Camel endpoints, and is documented to reorder query parameters alphabetically 
> so that two endpoint URIs differing only in parameter order are treated as 
> identical. This holds for most URIs, but breaks whenever a parameter value 
> contains a character that requires URL-encoding (e.g. a colon in a 
> {{host:port}} value). In that case, the *original* parameter order silently 
> affects the normalized output, so two logically identical URIs can normalize 
> to two different strings - causing duplicate endpoints (and duplicate 
> producers/consumers/connections) to be created silently, with no error or 
> warning.
> Originally reported/analyzed here: 
> https://tushar-c23.github.io/posts/camel-uri-normalization/
> h3. Reproduction
> {code:java}
> String uri1 = "kafka:mytopic?brokers=localhost:19092&groupId=mygroup"; // 
> already alphabetical (brokers < groupId)
> String uri2 = "kafka:mytopic?groupId=mygroup&brokers=localhost:19092"; // 
> needs reordering
> System.out.println(URISupport.normalizeUri(uri1));
> System.out.println(URISupport.normalizeUri(uri2));
> {code}
> Actual output (verified on {{main}}, camel-util 4.22.1-SNAPSHOT):
> {noformat}
> kafka://mytopic?brokers=localhost:19092&groupId=mygroup
> kafka://mytopic?brokers=localhost%3A19092&groupId=mygroup
> {noformat}
> The two normalized strings differ (raw {{:}} vs {{%3A}}), even though the 
> input URIs are semantically identical. Any code that keys a map/registry by 
> the normalized URI (e.g. the endpoint cache) will treat these as two 
> different endpoints.
> h3. Root cause
> {{URISupport.normalizeUri()}} has two code paths (see 
> {{core/camel-util/src/main/java/org/apache/camel/util/URISupport.java}}):
> * *Fast path* - {{doFastNormalizeUri()}} -> {{buildReorderingParameters()}} 
> (~line 799). This path only rebuilds the query string via 
> {{createQueryString(array, parameters, true)}} - the *only* place encoding 
> (via {{URLEncoder.encode}}) is applied - when it detects that the parameter 
> keys are *not* already in alphabetical order:
> {code:java}
> boolean sort = false;
> String prev = null;
> for (String key : entries) {
>     if (prev != null) {
>         int comp = key.compareTo(prev);
>         if (comp < 0) {
>             sort = true;
>             break;
>         }
>     }
>     prev = key;
> }
> if (sort) {
>     // only place where createQueryString(...) / encoding happens
>     query = URISupport.createQueryString(array, parameters, true);
> }
> {code}
> If the keys are already sorted, or the query has a single parameter (no 
> {{&}}), the original raw query string is returned completely untouched - i.e. 
> *not encoded at all*.
> * *Complex/legacy path* - {{doComplexNormalizeUri()}} (used when the URI 
> contains characters outside the fast parser's "safe" set). This path *always* 
> calls {{createQueryString(...)}} regardless of whether reordering was needed, 
> so it always produces a consistently encoded, canonical result. This path is 
> *not* affected by this bug.
> Because a colon is considered "safe" by 
> {{UnsafeUriCharactersEncoder.isSafeFastParser()}} (so URIs containing it 
> still take the fast path), but {{URLEncoder.encode()}} (used inside 
> {{createQueryString}}) *does* encode colons, the fast path's output for the 
> same logical URI depends on whether the original parameter order happened to 
> require a rebuild.
> This was introduced as a performance optimization in CAMEL-14648 (2020) - the 
> intent was purely to avoid rebuilding the query string when reordering wasn't 
> necessary; encoding consistency was not part of the design consideration at 
> the time. It is not a deliberate behavioral decision.
> Checked for duplicates: CAMEL-24187 (already fixed, released in 
> 4.22.0/4.18.4) covers a related but distinct bug - non-idempotent 
> double-normalization of {{%}} characters. It does not cover this 
> parameter-order/encoding inconsistency.
> h3. Impact
> * Silent duplicate {{Endpoint}} instances (and therefore duplicate 
> producers/consumers, connections, threads) for logically identical URIs, with 
> no exception or log warning.
> * Reported real-world impact: a Kafka producer leak where varying parameter 
> order (due to a colon in the broker address) caused multiple 
> {{KafkaProducer}} instances to be created for what should have been a single 
> shared endpoint.
> * Affects any component/DSL usage where endpoint URIs are hand-assembled with 
> parameters in non-canonical order and at least one value requires encoding 
> (colons, spaces, etc.). Endpoints built via the fluent Java DSL (which stores 
> parameters in a {{TreeMap}}, always alphabetical) are not affected in 
> practice, but this is incidental, not guaranteed by contract.
> h3. Suggested fix direction
> Align the fast path's behavior with the complex path's guarantee that the 
> result is always canonically encoded, without regressing the performance win 
> from CAMEL-14648 for the common case:
> * When scanning parameter keys to decide whether a re-sort is needed, also 
> detect whether any value requires encoding (e.g. by comparing each value to 
> its encoded form, or checking against the same "unsafe" character set used 
> elsewhere). If either condition is true, rebuild via 
> {{createQueryString(...)}}.
> * Alternatively (simpler, slightly more conservative on performance): always 
> rebuild the query via {{createQueryString(...)}} in 
> {{buildReorderingParameters()}}, only keeping the sort-detection to choose 
> whether the keys array itself needs sorting first. This removes the "skip 
> rebuild" fast-path micro-optimization but keeps the larger win of using the 
> lightweight {{CamelURIParser}} instead of {{java.net.URI}}.
> * Also fix the single-parameter short-circuit (query with no {{&}}) which 
> currently returns the raw, unencoded value verbatim - for consistency with 
> the complex path, which always encodes single-parameter queries too.
> A regression test should assert that {{URISupport.normalizeUri()}} produces 
> the *same* output for two URIs that differ only in parameter order, for 
> values containing characters requiring encoding (colon, space, etc.), 
> including the single-parameter case.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to