shashank created CAMEL-25051:
--------------------------------
Summary: Message headers since 4.21: a header set as content-type
is stored and sent on as Content-Type (CaseInsensitiveMap replaces the key with
a known key of another case)
Key: CAMEL-25051
URL: https://issues.apache.org/jira/browse/CAMEL-25051
Project: Camel
Issue Type: Bug
Components: camel-core
Reporter: shashank
CAMEL-23691 (4.21.0, PR #23766) replaced the
{{TreeMap(String.CASE_INSENSITIVE_ORDER)}} behind {{CaseInsensitiveMap}}, the
default message headers map, by a hash table
({{core/camel-util/src/main/java/org/apache/camel/util/CaseInsensitiveMap.java}}).
It also added deduplication of well-known keys: {{put}} stores
{{deduplicateKey(key, hash)}} ({{:87-100}}), which returns the registered key
when {{knownEntries[idx].equalsIgnoreCase(key)}}. {{DefaultHeadersMapFactory}}
registers all {{Exchange}} constants ({{ExchangeConstantProvider.values()}}),
among them {{Content-Type}}, {{Content-Length}}, {{Content-Encoding}},
{{Transfer-Encoding}}, {{breadcrumbId}} and every {{Camel...}} name. So the
first put of a header whose name matches one of them ignoring case stores the
constant's spelling: a header set or received as {{content-type}} is stored,
iterated and sent on as {{Content-Type}}, and {{camelfilename}} becomes
{{CamelFileName}}. A later put with another case only replaces the value.
The key case was meant to be kept:
* PR #23766 lists under "After": "Original key case preserved (first-put case
wins, same as before)". It presents the deduplication as replacing deserialized
keys that match a known constant with the canonical interned reference, which
is a memory optimisation for equal strings.
* The class javadoc still says "A map that uses case insensitive keys, but
preserves the original key cases" (CAMEL-8095).
* The 4.21 upgrade guide only mentions "header key deduplication" (in the
camel-headersmap deprecation note).
The folding is written down in two places: the {{registerKnownKeys}} javadoc
("matches one of these strings (case-insensitive)") and
{{core/camel-core/src/test/java/org/apache/camel/util/CaseInsensitiveMapTest.java:662}}
({{testKnownKeyDeduplication}}, "case-insensitive dedup: different case should
still map to canonical"). So it is deliberate in the implementation. It
contradicts the stated contract and the PR's own description, though, and
nobody discussed it in the JIRA or the PR review.
The case of a header name matters wherever a component copies the names as they
are and the transport is case-sensitive. The Kafka path is checked in the code:
{{KafkaRecordProcessor}} sets the record header names on the message as they
are ({{in.setHeader(header.key(), ...)}}),
{{KafkaProducer.getPropagatedHeaders}} / {{getRecordHeader}} send
{{entry.getKey()}}, and {{KafkaHeaderFilterStrategy}} does not filter
{{Content-Type}}. So a Kafka to Kafka route now forwards a {{content-type}}
record header (for example a CloudEvents binary-mode message) as
{{Content-Type}}, and a consumer that reads {{content-type}} no longer finds
it. Other transports whose header names are case-sensitive are affected the
same way. I did not run this against a Kafka broker. The renaming itself is
reproduced below.
*Reproduction* (standalone program against main 65f315628,
{{CaseInsensitiveKeyCaseE2E.java}}):
{noformat}
new CaseInsensitiveMap() in an application with a CamelContext:
put content-type, content-length, camelfilename, x-custom
keySet() -> [Content-Type, Content-Length, CamelFileName, x-custom]
TreeMap(CASE_INSENSITIVE_ORDER), the map up to 4.20 -> [camelfilename,
content-type] (case kept)
template.sendBodyAndHeader("direct:in", "{}", "ce_id", "1") to
from("direct:in").setHeader("content-type", ...).setHeader("transfer-encoding",
...)
.setHeader("breadcrumbid", ...).setHeader("x-trace", ...).to("mock:out")
header names at mock:out -> [ce_id, Content-Type, Transfer-Encoding,
breadcrumbId, x-trace]
expected -> [ce_id, content-type, transfer-encoding,
breadcrumbid, x-trace]
get("CONTENT-TYPE") works (control).
{noformat}
A jqwik property ("keys keep their case",
{{R5CaseInsensitiveMapProperties.keysKeepTheirCase}}) fails with
{{content-type}}. A Lean model ({{R5CaseInsensitiveMap.lean}}) shows that
whenever a known key is the first match of a key ignoring case and differs from
it, {{put}} stores the other key ({{put_renames}}). It also proves that with an
exact-match comparison {{put}} always stores the caller's key
({{fix_preserves_key}}).
Affected: 4.21.0, 4.22.0 and main ({{deduplicateKey}} with
{{equalsIgnoreCase}}, checked at the tags). The camel-4.18.x and camel-4.14.x
branches still extend {{TreeMap}} and are not affected. If there is a 4.22.x
maintenance line, please consider a backport.
A user who needs the old map right away can plug a {{TreeMap}}-based map
through {{ExtendedCamelContext.setHeadersMapFactory}} (camel-headersmap itself
was removed in 4.23).
*Proposed fix:* in {{deduplicateKey}}, reuse the known key only when it is
equal ({{knownEntries[idx].equals(key)}}). This keeps the whole memory saving
the deduplication is for (the same constant arriving as another {{String}}
instance, for example after deserialization): a key in another case cannot
share the canonical instance anyway, and the hash is computed from the key, so
nothing else depends on it. Lookups stay case-insensitive and the first put
still decides the key case. Update the {{registerKnownKeys}} javadoc and the
second half of {{testKnownKeyDeduplication}} accordingly.
Compatibility, to be stated in the PR: from 4.21 to 4.22 some case-sensitive
checks on header names started to match lower-case names by accident, and they
go back to their 4.20 behaviour:
* {{CxfHeaderHelper.propagateCamelToCxf}}
({{Exchange.CONTENT_TYPE.equals(entry.getKey())}}, and a case-sensitive
{{HashMap}} for the name mapping): a header set as {{content-type}} is dropped
by the CXF header filter again instead of being used as the CXF message content
type.
* {{NettyHttpProducer.removeCamelHeaders}} ({{key.startsWith("Camel")}}): a
response header named {{camelfoo}} is kept again.
Neither is a security filter: {{DefaultHeaderFilterStrategy}} filters
{{Camel*}} case-insensitively.
_Filed with Claude Code on behalf of allthingssecurity._
--
This message was sent by Atlassian Jira
(v8.20.10#820010)