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

Reply via email to