[ 
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)

Reply via email to