allthingssecurity opened a new pull request, #26931: URL: https://github.com/apache/camel/pull/26931
# Description [CAMEL-25051](https://issues.apache.org/jira/browse/CAMEL-25051) Since 4.21 (CAMEL-23691, #23766), `CaseInsensitiveMap.put` stores a registered "known" key in place of the caller's key when the two are equal ignoring case (`deduplicateKey` uses `equalsIgnoreCase`). `DefaultHeadersMapFactory` registers every `Exchange` constant, among them `Content-Type`, `Content-Length`, `Content-Encoding`, `Transfer-Encoding`, `breadcrumbId` and all `Camel*` names. So a message header set or received as `content-type` is stored, iterated and sent on as `Content-Type`, and `camelfilename` becomes `CamelFileName`: ``` template.sendBodyAndHeader("direct:in", body, "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 on main: [ce_id, Content-Type, Transfer-Encoding, breadcrumbId, x-trace] expected (as with the TreeMap up to 4.20): [ce_id, content-type, transfer-encoding, breadcrumbid, x-trace] ``` The key case was meant to be kept: - The class javadoc says "A map that uses case insensitive keys, but preserves the original key cases" (CAMEL-8095). - #23766 lists under "After": "Original key case preserved (first-put case wins, same as before)", and 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 `registerKnownKeys` javadoc and `CaseInsensitiveMapTest.testKnownKeyDeduplication` do describe the case folding. Neither the JIRA, the PR review nor the 4.21 upgrade guide mention it, though, and it changes header names on the wire. Kafka is one example: `KafkaRecordProcessor` puts the record header names into the message as they are, and `KafkaProducer.getPropagatedHeaders` sends `entry.getKey()`. So a Kafka to Kafka route now forwards a `content-type` record header as `Content-Type`, and Kafka header names are case-sensitive. The same applies to other transports whose header names are case-sensitive. I checked the Kafka path in the code but did not run it against a broker. The camel-4.18.x and 4.14.x branches still use the `TreeMap` and are not affected. This change: - `deduplicateKey` reuses the canonical key only when it is `equals` to the caller's key. That keeps the full memory benefit. A key that differs in case could never share the canonical `String` instance anyway, and the hash is computed from the key, so no other state depends on it. Lookups stay case-insensitive, and the first put still decides the key case. - The `registerKnownKeys` javadoc now says "equal" and that the key case is kept. - Upgrade guide 4.23: a short section saying that header names keep their case again, as in 4.20. Compatibility: from 4.21 to 4.22, some case-sensitive checks on header names started to match lower-case names by accident. With this change they go back to their 4.20 behaviour: - `CxfHeaderHelper.propagateCamelToCxf` checks `Exchange.CONTENT_TYPE.equals(entry.getKey())` and maps the name through a case-sensitive `HashMap`. A header set as `content-type` is therefore dropped by the CXF header filter again, as in 4.20, instead of being used as the CXF message content type. - `NettyHttpProducer.removeCamelHeaders` uses `key.startsWith("Camel")`, so a response header named `camelfoo` is kept again. Neither is a security filter: `DefaultHeaderFilterStrategy` matches `Camel*` case-insensitively. I did not run the camel-cxf or camel-netty-http tests. Their dependencies (CXF, Jetty, Spring, several Camel components) are not in my offline repository, so I left them to CI. Tests: - `CaseInsensitiveMapTest.testKnownKeyDeduplication`: the second half now asserts that `put("camelcharsetname")` keeps `camelcharsetname`, and that `get("CamelCharsetName")` still finds it. The first half is unchanged: a `new String("CamelCharsetName")` is still replaced by the canonical instance. - New `CaseInsensitiveMapTest.testKnownKeyKeepsKeyCase`: `content-type` and `camelfilename` keep their case in `keySet()`, `entrySet()` and a copy. `get("Content-Type")` and `get("CONTENT-TYPE")` work. A later `put("Content-Type", ...)` only replaces the value (first put wins). `new String("Content-Type")` still shares the canonical instance (`assertSame`). - Both tests restore the global known keys (`ExchangeConstantProvider.values()`) in a `finally`, so they no longer leave a 3-key table for the rest of the surefire fork. - New `DefaultMessageHeaderTest.testKnownHeaderNameKeepsKeyCase`: through a real message and its copy. Without the change in `CaseInsensitiveMap`, the three tests fail (for example `expected: <[content-type, camelfilename, Content-Length]> but was: <[Content-Type, CamelFileName, Content-Length]>`). With it, `*CaseInsensitive*,*Header*,*Message*,*Simple*,*Exchange*,*Mock*,*Copy*` in camel-util, camel-support, camel-core and camel-console pass: 1422 tests, 0 failures. Found with a Lean model of `put` with the known-key table. It shows that `put` stores another key whenever a known key matches the given one ignoring case but differs from it, and it proves that the exact-match variant always stores the caller's key. I then reproduced the renaming against the real classes and a route, with a `TreeMap(CASE_INSENSITIVE_ORDER)` as the 4.20 control. # Target - [x] I checked that the commit is targeting the correct branch (Camel 4 uses the `main` branch) # Tracking - [x] If this is a large change, bug fix, or code improvement, I checked there is a [JIRA issue](https://issues.apache.org/jira/browse/CAMEL) filed for the change (usually before you start working on it). # Apache Camel coding standards and style - [x] I checked that each commit in the pull request has a meaningful subject line and body. - [ ] I have run `mvn clean install -DskipTests` locally from root folder and I have committed all auto-generated changes. (I built and tested the affected modules, including the formatter and import-sort plugins. I did not run the full root build.) # AI-assisted contributions - [x] If this PR includes AI-generated code, commits have proper co-authorship attribution (e.g., `Co-authored-by` trailers) and the PR description identifies the AI tool used. This PR was prepared with Claude Code (Claude Opus 5.5). The commit carries a `Co-Authored-By` trailer. _Claude Code on behalf of allthingssecurity_ 🤖 Generated with [Claude Code](https://claude.com/claude-code) -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
