shashank created CAMEL-25033:
--------------------------------
Summary: camel-bean: a parameter bound from ${body} or ${header.x}
in the method name loses its surrounding quotes, and the text "null" becomes a
Java null
Key: CAMEL-25033
URL: https://issues.apache.org/jira/browse/CAMEL-25033
Project: Camel
Issue Type: Bug
Components: camel-core
Reporter: shashank
The method option can bind parameters from Simple expressions, for example
{{bean(MyService.class, "process(${body})")}} or
{{to("bean:foo?method=check(${header.ETag})")}}. {{bean-binding.adoc}}
("Parameter binding using method option") documents two different kinds of
value:
* a String literal in quotes, such as {{'World'}}, is passed "without quotes";
* a Simple token, such as {{${header.high}}}, is passed as the value of that
header.
{{MethodInfo.ParameterExpression}} does not keep them apart. It evaluates every
parameter with the Simple language and then post-processes the *result*:
{code:java}
// MethodInfo.java:728
if ("null".equals(parameterValue)) {
return Void.TYPE; // the parameter becomes null
}
...
// MethodInfo.java:789-793 convertParameterValue
if (parameterValue instanceof String string) {
parameterValue = StringHelper.removeLeadingAndEndingQuotes(string);
}
{code}
{{removeLeadingAndEndingQuotes}} ({{StringHelper.java:161-179}}) trims the
value and removes the first and last char when both are {{'}} or both are
{{"}}. So the quote handling that is meant for the literal text of the method
name is also applied to message data:
* An HTTP {{ETag}} / {{If-None-Match}} value {{"33a64df5"}} reaches the method
as {{33a64df5}}.
* A CSV line {{"ACME, Inc.","42"}} (for example after
{{split(body().tokenize("\n"))}}) reaches the method as {{ACME, Inc.","42}}.
* A JSON string body {{"text"}} loses its quotes. A body {{'quoted'}} becomes
{{quoted}}, and {{ 'x' }} becomes {{x}}.
* A header or body whose value is the four-letter text {{null}} is passed as
Java {{null}}.
There is no error or log. The method gets different data than the message holds.
*Reproduction* (standalone program against main dbdd4b381, bean {{echo(String
s)}} returns {{[s]}}):
{noformat}
echo(${header.v}) header v = "33a64df551425fcc55e4d42a148795d9" ->
[33a64df551425fcc55e4d42a148795d9] expected
["33a64df551425fcc55e4d42a148795d9"]
to("bean:echo?method=echo(${header.v})") same header -> same
result
echo(${body}) body "ACME, Inc.","42" -> [ACME,
Inc.","42] expected ["ACME, Inc.","42"]
echo(${body}) body 'quoted' ->
[quoted]
echo(${body}) body "just a JSON string" -> [just
a JSON string]
echo(${body}) body " 'x' " -> [x]
echo(${body}) body '' -> []
two(${header.a}, ${header.b}) a='x' b="y" -> [x|y]
expected ['x'|"y"]
echo(${header.v}) header v = null (the text) -> method
receives null expected [null]
controls: echo (no parameters in the method name) with the ETag body ->
["33a…"]; echo('World') -> [World];
echo(${header.v}) with v = abc -> [abc]
workaround: echo('${header.v}') with the ETag header -> ["33a…"], with v = null
(the text) -> [null]
{noformat}
A property-based test (jqwik) over random header values fails for every quoted
value (shrunk sample {{''}}) and, over an alphabet with quotes and the letters
of {{null}}, shrinks to {{'aaaa'}}. The control property (letters only) passes
500 tries.
A Lean model proves the following:
* For every string {{w}}, a {{${...}}} value {{"w"}} or {{'w'}} is bound as
{{w}} ({{dquoted_value_corrupted}}, {{squoted_value_corrupted}}), so {{w}} and
{{'w'}} cannot be told apart ({{not_injective}}).
* A value that {{removeLeadingAndEndingQuotes}} leaves unchanged and that is
not the text {{null}} is bound correctly today ({{unchanged_value_ok}}), and
literal parameters are already correct ({{literal_ok}}). The defect is exactly:
quoted data and the text {{null}}.
* The fix below, which decides on the parameter text instead of the evaluated
value, meets the documented behaviour for every token ({{fix_meets_spec}}).
The same {{removeLeadingAndEndingQuotes}} call after the Simple evaluation is
in camel-2.25.4 ({{MethodInfo.java:798}}), camel-3.0.0 ({{:719}}), camel-4.0.0
({{:713}}) and camel-4.14.0 ({{:762}}), so all maintained versions are affected.
*Proposed fix:* decide what to do from the parameter *text* of the method name,
before evaluating it:
{code:java}
String exp = ...; // the parameter text,
e.g. 'World', ${header.x}, null, 5
boolean nullKeyword = "null".equals(exp.trim());
boolean quotedLiteral = StringHelper.isQuoted(exp.trim());
if (nullKeyword) {
return Void.TYPE;
}
if (quotedLiteral) {
exp = StringHelper.removeLeadingAndEndingQuotes(exp); // the quotes
belong to the method name syntax
}
Object parameterValue = evaluateSimpleExpression(exchange, index, exp);
// no removeLeadingAndEndingQuotes and no "null" check on parameterValue
{code}
Then {{'World'}} gives {{World}}, {{'${body}'}} gives the body, {{${body}}}
gives the body unchanged, and {{null}} still passes {{null}}. A {{${...}}} that
evaluates to Java {{null}} keeps passing {{null}} (use a marker object instead
of the String {{"null"}} in {{evaluateSimpleExpression}}).
Tests: {{BeanParameterValueTest}} with {{echo(${header.v})}} for the header
values {{"abc"}}, {{'abc'}} and {{null}} (the text); they must reach the method
unchanged. The existing literal tests ({{bar('Camel', true)}},
{{doSomething('Hello World', 2)}}, {{put('isMaster','true')}}) must keep
passing.
Related, lower severity (same parser, could be fixed in the same change):
{{StringQuoteHelper.splitSafeQuote}} ({{MethodInfo.java:612}}) splits at every
comma outside quotes, including commas inside {{${...}}}.
{{echo(${body.substring(0, 3)})}} and {{two(${body}, ${header.v.replace('a',
'b')})}} fail with {{SimpleParserException: missing } to close the function}}.
CAMEL-24967 fixed the same problem for Simple function arguments with a
depth-aware splitter.
_Filed with Claude Code on behalf of allthingssecurity._
--
This message was sent by Atlassian Jira
(v8.20.10#820010)