shashank created CAMEL-25059:
--------------------------------

             Summary: 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


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