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)

Reply via email to