shashank created CAMEL-25034:
--------------------------------
Summary: convertBodyTo(type, charset) ignores the charset when the
message has a CamelCharsetName header, and leaves it on the exchange when the
conversion fails
Key: CAMEL-25034
URL: https://issues.apache.org/jira/browse/CAMEL-25034
Project: Camel
Issue Type: Bug
Components: camel-core
Reporter: shashank
{{convertBodyTo(String.class, "UTF-8")}} (and {{convertHeaderTo}} /
{{convertVariableTo}} with a charset) is the documented way to convert with a
given charset. {{ConvertBodyProcessor.process}}
({{ConvertBodyProcessor.java:119-173}}) passes the charset to the type
converters by setting the exchange *property* {{CamelCharsetName}}, converts,
and then restores the old property:
{code:java}
originalCharsetName = exchange.getProperty(ExchangePropertyKey.CHARSET_NAME,
String.class);
// override existing charset with configured charset as that is what the user
// have explicit configured and expects to be used
exchange.setProperty(ExchangePropertyKey.CHARSET_NAME, charset);
value = old.getMandatoryBody(type); // :143
...
// remove or restore charset when we are done as we should not propagate that
(:163-171, not in a finally)
{code}
Two problems:
# The converters read the charset with
{{ExchangeHelper.getCharsetName/getCharset}}
({{ExchangeHelper.java:958-1001}}), where the {{CamelCharsetName}} *header*
"takes precedence" over the property. When the message carries that header (the
HL7 data format sets it, {{HL7DataFormat.java:153}}, and user routes set it),
the configured charset is silently ignored and the body is decoded with the
header's charset.
# The restore is skipped when the conversion throws. The configured charset
then stays on the exchange, and the error handler, {{onException}} and the dead
letter channel convert the body with it.
{{ConvertHeaderProcessor.java:132-160}} and
{{ConvertVariableProcessor.java:157-185}} have the same code.
*Reproduction* (standalone program against main dbdd4b381):
{noformat}
from("direct:conv").convertBodyTo(String.class, "UTF-8")
body = "café" as UTF-8 bytes, no header -> café
(correct)
body = "café" as UTF-8 bytes, header CamelCharsetName=ISO-8859-1 -> café
expected café
from("direct:fail").onException(Exception.class).handled(true).setBody(simple("${exchangeProperty.CamelCharsetName}")).end()
.convertBodyTo(Integer.class, "UTF-16")
body "abc" -> UTF-16
expected (empty)
{noformat}
Property-based tests (jqwik): with the header, {{convertBodyTo(String.class,
"UTF-8")}} of UTF-8 bytes fails for random Latin-1 text (shrunk sample {{à}});
without the header it passes 500 tries; after a failing conversion the property
is left behind (shrunk sample {{a}}).
A Lean model proves the following:
* Whenever a header with a different charset is present, the configured charset
is not used ({{header_always_wins}}), and every failing conversion leaves the
configured charset in the property ({{failure_always_leaks}}).
* Without the header, a successful conversion uses the configured charset and
restores the property ({{no_header_ok}}), and without a configured charset
nothing changes ({{unconfigured_ok}}), so the fix only changes the two cases
above.
The same property-based code is in camel-2.25.4 and camel-3.0.0
({{ConvertBodyProcessor}}), camel-4.0.0 and camel-4.14.0, and the header
precedence in {{ExchangeHelper.getCharsetName}} is the same since 3.0, so all
maintained versions are affected. CAMEL-12279 (2.21) added the restore of the
original property, but not in a {{finally}} block and not for the header.
*Proposed fix:* in the three processors:
* if the message has a {{CamelCharsetName}} header, remember it and remove it
(or set it to the configured charset) for the duration of the conversion, the
same way as the property;
* restore the header and the property in a {{finally}} block.
Alternatively, pass the charset to the conversion directly instead of through
the exchange, but the type converters only read it from the exchange today.
Tests: {{ConvertBodyTest}} with a {{CamelCharsetName=ISO-8859-1}} header and
{{convertBodyTo(String.class, "UTF-8")}} of UTF-8 bytes with non-ASCII text;
and a failing {{convertBodyTo(Integer.class, "UTF-16")}} followed by a check
that the exchange property is unchanged.
_Filed with Claude Code on behalf of allthingssecurity._
--
This message was sent by Atlassian Jira
(v8.20.10#820010)