[
https://issues.apache.org/jira/browse/CAMEL-25571?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Claus Ibsen resolved CAMEL-25571.
---------------------------------
Resolution: Fixed
Fixed by https://github.com/apache/camel/pull/27755 (merged to main as
b168e18c7e6f, 4.24.0).
_Claude Code on behalf of Claus Ibsen_
> camel-servlet, camel-jetty - a request body with Content-Encoding: gzip is
> passed to the route compressed when it arrives after the headers (Expect:
> 100-continue, or a later packet)
> -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
>
> Key: CAMEL-25571
> URL: https://issues.apache.org/jira/browse/CAMEL-25571
> Project: Camel
> Issue Type: Bug
> Components: camel-http-common
> Reporter: shashank
> Assignee: shashank
> Priority: Major
> Fix For: 4.24.0
>
>
> {{HttpConverter.toInputStream(HttpServletRequest, Exchange)}}
> (camel-http-common, lines 81-98 at main 5987a263107b) reads the request body
> for the consumers that use {{DefaultHttpBinding.parseBody}} (line 715,
> through {{HttpHelper.readRequestBodyFromServletRequest}}, line 163):
> camel-servlet and camel-jetty.
> {code:java}
> InputStream is = request.getInputStream();
> if (is != null && is.available() <= 0) {
> // there is no data, so we cannot uncompress etc.
> return is;
> }
> if (exchange == null || !exchange.getProperty(Exchange.SKIP_GZIP_ENCODING,
> Boolean.FALSE, Boolean.class)) {
> String contentEncoding = request.getHeader(Exchange.CONTENT_ENCODING);
> return GZIPHelper.uncompressGzip(contentEncoding, is);
> }
> {code}
> {{available()}} is the number of bytes that can be read without blocking. It
> is 0 for an empty body, but also while the body is still on its way:
> * a client that sends {{Expect: 100-continue}} waits for the interim response
> of the server before it sends the body, so no byte of the body can be there
> when the consumer starts to read it;
> * a client can send the body in a later packet than the headers (a slow or
> chunked upload).
> The route then gets the compressed bytes of a request with
> {{Content-Encoding: gzip}}, without an error. The same request is
> uncompressed when its first bytes happen to have arrived, so the result
> depends on timing.
> h3. History
> The check was added by CAMEL-5806 (commit 1ba5aa2f4b, 2015-02-16, 2.15.0: GET
> with a body), in {{HttpConverter}} and, as {{available() == 0}}, in
> {{DefaultHttpBinding.parseBody}}. CAMEL-8643 (2.15.3 / 2.16.0, commit
> 7790c6e0fe) reported the check in {{DefaultHttpBinding}} for the same reason
> ("on a slow or remote client the binary stream isn't always immediately
> available") and made it opt-in: {{eagerCheckContentAvailable}}, default
> false. That commit did not change {{HttpConverter}}, where the check is still
> unconditional. (CAMEL-1327 added the gzip support in 2009, without the check.)
> h3. Scope
> * Through {{HttpConverter}}: camel-servlet and camel-jetty, and what extends
> them (camel-atmosphere-websocket). In camel-spring-boot,
> {{SpringBootPlatformHttpBinding}} extends {{DefaultHttpBinding}} and calls
> {{super.parseBody}} when the platform-http endpoint has {{useStreaming=true}}
> (read on camel-spring-boot main, not run).
> * Not through it: camel-platform-http-vertx ({{VertxPlatformHttpConsumer}},
> Vert.x body handlers), camel-undertow
> ({{DefaultUndertowHttpBinding.readFromChannel}}), camel-netty-http
> ({{DefaultNettyHttpBinding}}); none of them depends on camel-http-common in
> main scope.
> h3. Reproduction
> New {{JettyGzipRequestExpectContinueTest}} (camel-jetty): a socket client
> sends
> {code}
> POST /gzip HTTP/1.1
> Content-Type: text/plain; charset=UTF-8
> Content-Encoding: gzip
> Content-Length: <n>
> Expect: 100-continue
> {code}
> waits until it has read {{HTTP/1.1 100 Continue}}, then sends the gzip body.
> The route is a jetty consumer followed by {{convertBodyTo(String.class,
> "UTF-8")}} and {{to("mock:result")}}. On main the mock gets the compressed
> bytes: {{mock://result Body of message: 0. Expected: <Hello World, sent with
> Content-Encoding: gzip> but was: <...>}} (in all 3 surefire runs: the first
> run and its 2 reruns). The test has no sleep: the client only sends the body
> after the interim response.
> New {{HttpConverterGzipTest}} (camel-http-common) calls the converter with a
> servlet stream whose {{available()}} is 0 and a gzip body; on main:
> {{expected: <Hello World> but was: <...>}} (the compressed bytes), with and
> without an exchange.
> The existing gzip request test of camel-jetty that is enabled,
> {{JettyContentTypeTest.testContentTypeWithGZipEncoding}} (camel-http
> producer), does not show the problem (it passes with the fix; not run
> separately on main); {{HttpGZipEncodingTest}} is {{@Disabled}}.
> h3. Proposed fix
> For a request with gzip content encoding (and without the
> {{CamelSkipGzipEncoding}} exchange property) the converter reads the first
> byte through a {{PushbackInputStream}}: at the end of the stream the body is
> empty and is passed on as it is, otherwise the byte is pushed back and the
> stream is wrapped in a {{GZIPInputStream}}. Requests without gzip content
> encoding, and exchanges with {{CamelSkipGzipEncoding}}, are not read in the
> converter, as before.
> Behaviour changes (all in the 4.24 upgrade guide):
> * a gzip request body that arrives after the headers is now uncompressed;
> * a body that is labelled gzip but is not gzip data already failed with
> {{ZipException}} in the converter when its bytes had arrived; when it arrived
> late, main passed the bytes to the route as they were (200), and now it fails
> in the same way (Jetty answers 500, the route is not called);
> * with {{disableStreamCache=true}} the converter now waits for the first byte
> of a gzip body before the route is called, where main returned at once when
> nothing had arrived (with stream caching the consumer already read the whole
> body). A request without a body does not wait: {{Content-Length: 0}} returns
> in {{DefaultHttpBinding.parseBody}} (line 699) before the converter, and a
> chunked request without data gives {{read() == -1}}.
> Tests with the fix: {{HttpConverterGzipTest}} 9 cases, of which 3 fail on
> main (the two late gzip bodies, and the late body that is not gzip) and 6
> pass on main too (available body, empty body, {{CamelSkipGzipEncoding}} late
> and available, no gzip header, a body that is not gzip and available);
> {{JettyGzipRequestExpectContinueTest}} 2 cases, both fail on main (the
> second, {{Expected 500 but was: HTTP/1.1 200 OK}}, pins the changed behaviour
> for a body that is not gzip); {{JettyGzipRequestEmptyBodyTest}} 2 cases with
> {{disableStreamCache=true}}, both pass on main too.
> Suites with the fix: camel-http-common 48 tests, 0 failures; camel-servlet
> 110 tests, 0 failures, 2 skipped; camel-jetty 403 tests, 1 failure and 1
> error, 23 skipped: {{JettyEmptyCharsetParameterTest}} and
> {{JettyXsltHttpTemplateTest}}, which fail in the same way with unchanged main
> code on the same machine.
> Found with a small Lean 4 model of the converter (request = gzip flag, skip
> flag, body, bytes arrived): "a gzip body is uncompressed unless it is empty
> or skipped" fails on main for every non-empty gzip body of which nothing has
> arrived; the fix is proved to meet it for every request and to equal main
> whenever a byte has arrived or the body is empty (the model has no body that
> is labelled gzip but is not gzip). Then confirmed with the Jetty consumer as
> above.
> Affected: main; camel-4.22.x, camel-4.18.x and camel-4.14.x have the same
> {{available() <= 0}} check (read, not run).
> Duplicate check (2026-10-12): JIRA text "gzip available servlet" (none),
> "HttpConverter gzip" (1: CAMEL-1327, which added the gzip support in 2009);
> CAMEL-5806 and CAMEL-8643 are both Resolved/Fixed and about
> {{DefaultHttpBinding}}, see History, "Content-Encoding gzip jetty consumer"
> (5, none about the request body), "gzip 100-continue" (none),
> "GZIPInputStream EOFException servlet" (none); GitHub pull requests "gzip
> HttpConverter": none; no open pull request touches {{HttpConverter}}.
> _Filed with Claude Code on behalf of allthingssecurity._
--
This message was sent by Atlassian Jira
(v8.20.10#820010)