[
https://issues.apache.org/jira/browse/CAMEL-25059?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18119921#comment-18119921
]
shashank commented on CAMEL-25059:
----------------------------------
PR: https://github.com/apache/camel/pull/26942
I cannot assign issues to myself; could a committer assign this to me
(smjainblr)? Thanks.
_Claude Code on behalf of allthingssecurity_
> CaseInsensitiveMap (message headers) since 4.21: keySet().remove(o) and
> removeAll(c) are case-sensitive, so they no longer remove a header stored
> with another case
> -------------------------------------------------------------------------------------------------------------------------------------------------------------------
>
> Key: CAMEL-25059
> URL: https://issues.apache.org/jira/browse/CAMEL-25059
> Project: Camel
> Issue Type: Bug
> Components: camel-core
> Reporter: shashank
> Priority: Minor
>
> CAMEL-23691 (4.21.0) replaced the {{TreeMap}} with
> {{String.CASE_INSENSITIVE_ORDER}} behind {{CaseInsensitiveMap}}, the default
> message headers map, by a hash table that extends {{AbstractMap}}
> ({{core/camel-util/src/main/java/org/apache/camel/util/CaseInsensitiveMap.java}}).
> The class overrides {{entrySet()}} but not {{keySet()}}. The key set of
> {{AbstractMap}} routes {{contains}} to {{containsKey}}, which is
> case-insensitive. It does not override {{remove}}, though, so
> {{AbstractCollection.remove}} walks the keys and compares them with
> {{equals}}, which is case-sensitive. {{AbstractSet.removeAll(c)}} calls that
> {{remove}} for each element of {{c}} when the set is larger than {{c}}.
> The message headers expose this view: the key set of
> {{CopyOnWriteHeadersMap}}
> ({{core/camel-support/src/main/java/org/apache/camel/support/CopyOnWriteHeadersMap.java}})
> delegates {{remove}}, {{removeAll}} and {{retainAll}} to it. So:
> * {{exchange.getMessage().getHeaders().keySet().remove("x-trace")}} returns
> {{false}} and leaves {{X-Trace}} in place.
> * {{keySet().removeAll(List.of("x-trace", "x-tenant"))}} removes nothing.
> {{Map.remove("x-trace")}} and {{keySet().contains("x-trace")}} work. A key
> set is a view of the map ("removal from the set removes the corresponding
> mapping"), and the keys of this map compare case-insensitively everywhere
> else. With the old {{TreeMap}}, {{keySet().remove(o)}} went to
> {{TreeMap.remove}}, which is case-insensitive.
> The effect is silent: code that removes headers through the key set keeps a
> header when the sender used another case, and header names from the outside
> arrive in any case (HTTP/2 lower-cases them, for example). That includes code
> that strips sensitive headers this way before it calls another system.
> Camel's own code does not remove headers through the key set: a {{git grep}}
> for {{keySet().remove}}, {{removeAll}}, {{retainAll}} and {{removeIf}} over
> the non-test code finds, on message headers, only the
> {{CopyOnWriteHeadersMap}} delegation. {{removeHeader}},
> {{removeHeaders(pattern)}}, {{Map.remove}} and the header filter strategies
> are case-insensitive and not affected.
> h3. Reproduction
> A standalone program against main 65f315628, with the same calls on a
> {{TreeMap(String.CASE_INSENSITIVE_ORDER)}} as the 4.20 control:
> {noformat}
> CaseInsensitiveMap with X-Trace, X-Tenant and Content-Type:
> keySet().remove("x-trace") -> false, X-Trace still there
> keySet().removeAll([x-trace, x-tenant]) -> keys left [X-Trace, X-Tenant,
> Content-Type]
> route: .process(e ->
> e.getMessage().getHeaders().keySet().removeAll(Arrays.asList("x-trace",
> "x-tenant")))
> headers after -> [X-Trace, X-Tenant, Accept]
> expected [Accept]
> route: .process(e -> e.getMessage().getHeaders().keySet().remove("x-trace"))
> headers after -> [X-Trace] expected []
> same calls on TreeMap(CASE_INSENSITIVE_ORDER), the map up to 4.20 -> removed
> controls: keySet().contains("x-trace") and Map.remove("x-trace") behave as
> with the TreeMap
> {noformat}
> Note on 4.20: the {{removeAll(c)}} of the {{TreeMap}} key set was
> size-dependent too. When {{c}} has at least as many elements as the map,
> {{AbstractSet.removeAll}} iterates the map and asks {{c.contains(key)}},
> which is case-sensitive for a {{List}}. So {{removeAll([x-trace, x-tenant,
> x-other])}} on a map with {{X-Trace}} and {{X-Tenant}} removed nothing on
> 4.20 either. {{retainAll(c)}} always used {{c.contains(key)}}, so it was
> case-sensitive on 4.20 and is on main too. The regression in 4.21 is
> {{remove(o)}}, and {{removeAll(c)}} with a smaller {{c}}.
> A Lean model of the key set proves that {{keySet().remove(o)}} changes
> nothing whenever no key is exactly {{o}}, whatever case-insensitive matches
> exist, and a jqwik property ("keySet().remove works like TreeMap") fails with
> a stored {{Transfer-Encoding}} and a removed {{transfer-encoding}}.
> Related: CAMEL-25051 (known header names lose their key case since 4.21) is
> another difference to the {{TreeMap}} in the same class; the two changes are
> independent.
> Affected: 4.21.0, 4.22.0 and main. The camel-4.18.x and camel-4.14.x branches
> still extend {{TreeMap}} and are not affected.
> h3. Proposed fix
> Override {{keySet()}} in {{CaseInsensitiveMap}} with a small view that uses
> the lookup of the map:
> * {{size}}, and {{contains}} via {{containsKey}};
> * {{remove(o)}} finds the key case-insensitively and removes that entry;
> * {{removeAll(c)}} removes each element of {{c}}, so the result does not
> depend on the sizes;
> * {{retainAll(c)}} keeps a key when {{c}} holds it ignoring case;
> * {{clear}}, and an iterator whose {{remove}} works ({{removeIf}} uses it).
> This makes {{removeAll}} and {{retainAll}} case-insensitive whatever the
> sizes, which is more consistent than 4.20 rather than identical to it.
> {{CopyOnWriteHeadersMap}} needs no change. {{entrySet().remove}} already
> finds the key case-insensitively, and {{values()}} compares values only.
> Tests: in {{CaseInsensitiveMapTest}}
> ({{core/camel-core/src/test/java/org/apache/camel/util/}}):
> {{keySet().remove("x-trace")}} removes {{X-Trace}}; {{removeAll}} with a
> smaller and with a larger collection; {{retainAll}}; the iterator's
> {{remove}} and {{removeIf}}. In {{DefaultMessageHeaderTest}}, one case
> through {{getHeaders().keySet()}} of a copied message covers the
> copy-on-write wrapper.
> Duplicate check (2026-09-27): JIRA {{text ~ "CaseInsensitiveMap"}} found
> CAMEL-23691, CAMEL-23693, CAMEL-23686 and older issues; {{text ~ "keySet" AND
> text ~ "case"}}, and {{text ~ keySet}} since 2026-06-01 (CAMEL-24241,
> CAMEL-24350, CAMEL-23971, CAMEL-23681, CAMEL-24622), found nothing related.
> GitHub PR searches {{CaseInsensitiveMap}} (#23766, #23779, #25768, #26217),
> {{keySet case insensitive}} and {{headers keySet}}: nothing related. Not
> reported.
> _Filed with Claude Code on behalf of allthingssecurity._
--
This message was sent by Atlassian Jira
(v8.20.10#820010)