shashank created CAMEL-25685:
--------------------------------
Summary: camel-jetty - the query parameters of a request are
silently not mapped to headers when Jetty cannot read its body parameters (a
form-urlencoded body over 200000 bytes, or any multipart request with
enableMultipartFilter=false)
Key: CAMEL-25685
URL: https://issues.apache.org/jira/browse/CAMEL-25685
Project: Camel
Issue Type: Bug
Components: camel-jetty
Reporter: shashank
Assignee: shashank
The jetty consumer maps the query parameters of a request to headers in
{{AttachmentHttpBinding.populateRequestParameters}} (camel-jetty, package
{{org.apache.camel.component.jetty12}}, lines 122-166 on main cd2ce899764e)
through {{request.getParameterNames()}}. Jetty 12
({{ServletApiRequest.getParameters()}}, 12.1.14, read with javap) reads the
body parameters first and the query parameters after them, and throws for the
whole request when it cannot read the body parameters. The {{catch (Exception
e)}} around the loop only logs at DEBUG ("Failed to populate request
parameters, likely due to multipart parsing failure"), so the route gets the
exchange without any query parameter and the client gets 200.
{{CamelHttpQuery}} still holds the raw query string; the headers of the
parameters are missing.
Two requests that were measured take this path (any other failure of
{{getParameterNames()}}, for example an I/O error while Jetty reads the form,
goes the same way):
* *A form-urlencoded POST whose Content-Length is over Jetty's
{{maxFormContentSize}} (200000 bytes by default)*, with default endpoint
options. Jetty throws {{BadMessageException("Unable to parse form content")}}.
The form fields themselves are not affected: Camel maps them from the body in
{{DefaultHttpBinding.readFormUrlEncodedBody}}. (With
{{mapHttpMessageFormUrlEncodedBody=false}} the route gets no form field even
for a small form, so on this path Jetty only ever supplies the query
parameters; measured, not root-caused.) Only the query parameters are lost.
Example: {{POST /upload?customerId=42}} with a form of 250 KB (for example a
base64 file in a form field): {{header("customerId")}} is null in the route.
* *Any multipart request on an endpoint with {{enableMultipartFilter=false}}*,
whatever its size. This is the case the catch was written for (the comment says
the failure is expected there), but the query parameters are dropped with the
multipart body parameters.
The number of form keys is not a trigger: a form with 5000 keys is mapped
completely (the keys come from Camel's own form parsing).
h3. Other consumers, same request (form of 250000 bytes with {{?q=1&q2=x}},
default options, measured on main)
||consumer||form of 199000 bytes||form of 250000 bytes||form of 3 MB||form with
1002 / 5000 keys||
|camel-jetty|200, all mapped|200, *query parameters missing*|200, *query
parameters missing*|200, all mapped|
|camel-servlet (in the module's Undertow servlet container)|200, all
mapped|200, all mapped|413|200, all mapped|
|camel-undertow|200, all mapped|200, all mapped|413|500|
|camel-platform-http-vertx|400|400|400|400|
So the other consumers either map everything or fail the request; only
camel-jetty routes the exchange without data the client sent, without an error
or a log line above DEBUG.
h3. Reproduction
New {{HttpBindingQueryParametersUnreadableBodyTest}} (camel-jetty), with a JDK
{{HttpClient}}. On main:
* {{POST /form?id=42&tag=a&tag=b&text=hello+world}}, form
{{name=camel&data=<250000 x>}}: {{expected: <42> but was: <null>}} (the form
fields {{name}} and {{data}} are mapped);
* the same with {{?id=42&CamelHttpResponseCode=500}}: {{expected: <42> but was:
<null>}};
* a multipart request to an endpoint with {{enableMultipartFilter=false}} and
{{?id=42}}: {{expected: <42> but was: <null>}}.
h3. Proposed fix
When {{getParameterNames()}} itself fails, decode the query string with Jetty's
{{UrlEncoded.decodeQuery}} (UTF-8, as Jetty's default query encoding) and map
its parameters the same way as the normal path (values sanitized with
{{HttpHelper.sanitizeLog}}, the header filter strategy applied, repeated names
as a list). The fallback only runs when the exception came from
{{getParameterNames()}} itself, so nothing is mapped twice. A query that cannot
be decoded (for example {{%E9}}) is still skipped with the DEBUG log, as
before; that case (malformed client input) is not changed. The behaviour for
requests that Jetty reads without error does not change.
No option or default changes. The only difference users see is that these
requests now get their query parameter headers, as every other request to the
same endpoint does; for multipart requests with {{enableMultipartFilter=false}}
these headers are missing on every branch that has the catch. No upgrade guide
entry proposed (can be added if wanted).
Not changed: REST DSL endpoints on jetty ({{JettyRestHttpBinding}}, which
forces {{enableMultipartFilter=false}}) go through
{{DefaultHttpBinding.populateRequestParameters}} without this catch; there a
250 KB form or a multipart request answers 500 (measured). That is a loud
failure, not data loss, and is left for a separate discussion.
Tests: the three above fail on main and pass with the fix; a fourth
({{?id=%E9}} with a large form, not mapped) passes on main and pins the
unchanged path. camel-jetty with the fix (camel-http-base and camel-http-common
built in the same reactor): 407 run, 23 skipped, 1 failure and 1 error that
occur the same way without the change on this machine
({{JettyXsltHttpTemplateTest}}: the stylesheet is fetched from {{0.0.0.0}};
{{JettyEmptyCharsetParameterTest}}: 500, the module was tested against a local
camel-support snapshot older than main).
How it was found: a follow-up of an earlier differential test of the HTTP
consumers, which had shown that an invalid query escape makes camel-jetty drop
all parameters through the same catch (malformed input, kept out of this
ticket). The form limits of Jetty go through the same call; the table above was
then measured with the same request against each consumer.
Affected: main (cd2ce899764e; tested). camel-4.23.x (Jetty 12.1.14),
camel-4.22.x and camel-4.18.x (Jetty 12.1.12) have the same catch (read, not
run). camel-4.14.x (Jetty 12.0) has no catch there, so the exception is not
swallowed (read, not run).
Duplicate check (2026-10-15): JIRA text "maxFormContentSize", "maxFormKeys"
(nothing), "AttachmentHttpBinding" (CAMEL-25634 and older multipart tickets,
none about query parameters); GitHub pull requests "AttachmentHttpBinding"
(only the open PR of CAMEL-25634, which changes the attachment branch inside
the same {{try}}; the two changes merge without conflict),
"maxFormContentSize", "maxFormKeys" (nothing).
_Filed with Claude Code on behalf of allthingssecurity._
--
This message was sent by Atlassian Jira
(v8.20.10#820010)