[ 
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)

Reply via email to