[
https://issues.apache.org/jira/browse/CAMEL-25051?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Claus Ibsen updated CAMEL-25051:
--------------------------------
Fix Version/s: 4.23.0
> 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
> Priority: Minor
> Fix For: 4.23.0
>
>
> 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)