You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@camel.apache.org by "Claus Ibsen (Jira)" <ji...@apache.org> on 2026/10/02 14:20:00 UTC
[jira] [Assigned] (CAMEL-25059) 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
[ https://issues.apache.org/jira/browse/CAMEL-25059?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel ]
Claus Ibsen reassigned CAMEL-25059:
-----------------------------------
Assignee: shashank
> 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
> Assignee: shashank
> Priority: Minor
> Fix For: 4.23.0
>
>
> 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)