[ 
https://issues.apache.org/jira/browse/HTTPCLIENT-2426?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Szczepanski Dominik updated HTTPCLIENT-2426:
--------------------------------------------
    Description: 
h2. How to reproduce

Make an async HTTP/1.1 request to an endpoint that responds with a 302 redirect 
where the intermediate response includes {{Content-Encoding: gzip}}. The I/O 
reactor thread dies with:

{code}
java.lang.NullPointerException
    at 
org.apache.hc.client5.http.async.methods.InflatingGzipDataConsumer.streamEnd(InflatingGzipDataConsumer.java:148)
    at 
org.apache.hc.client5.http.impl.async.HttpAsyncMainClientExec$1.failed(...)
    ...
    at org.apache.hc.core5.reactor.IOReactorWorker.run(IOReactorWorker.java:44)
{code}


h2. Why it fails

When {{AsyncRedirectExec}} detects a redirect it returns {{null}} from 
{{handleResponse()}} — which I think is intentional with the meaning "discard 
this response body". The problem is that {{ContentCompressionAsyncExec}} passes 
the null downstream straight into {{InflatingGzipDataConsumer}}:

{code:java}
AsyncDataConsumer downstream = cb.handleResponse(rsp, 
wrapEntityDetails(details));
for (int i = codecs.size() - 1; i >= 0; i--) {
    final String codec = codecs.get(i);
    final UnaryOperator<AsyncDataConsumer> op = decoders.lookup(codec);
    if (op != null) {
        downstream = op.apply(downstream);
    } else {
        throw new HttpException("Unsupported Content-Encoding: " + codec);
    }
}
{code}

So when the redirect response has {{Content-Encoding: gzip}}, 
{{InflatingGzipDataConsumer}} gets instantiated with {{null}} as 
{{downstream}}. The class has no null checks, so once the body is drained and 
{{streamEnd()}} or {{releaseResources()}} is called it NPEs.

h2. Fix

Add null guards on {{downstream}} in {{updateCapacity()}}, {{consume()}}, 
{{streamEnd()}}, and {{releaseResources()}}. When {{downstream}} is null the 
body is being discarded anyway, so skipping the delegation seems to be the 
right behaviour.


  was:
## How to reproduce

Make an async HTTP/1.1 request to an endpoint that responds with a 302 redirect 
where the intermediate response includes `Content-Encoding: gzip`. The I/O 
reactor thread dies with:

```
java.lang.NullPointerException
    at 
org.apache.hc.client5.http.async.methods.InflatingGzipDataConsumer.streamEnd(InflatingGzipDataConsumer.java:148)
    at 
org.apache.hc.client5.http.impl.async.HttpAsyncMainClientExec$1.failed(...)
    ...
    at org.apache.hc.core5.reactor.IOReactorWorker.run(IOReactorWorker.java:44)
```

## Why it fails

When `AsyncRedirectExec` detects a redirect it returns `null` from 
`handleResponse()` - which I think is intentional with the meaning "discard 
this response body". The problem is that `ContentCompressionAsyncExec` passes 
the null downstream straight into `InflatingGzipDataConsumer`:

```java
AsyncDataConsumer downstream = cb.handleResponse(rsp, 
wrapEntityDetails(details));
for (int i = codecs.size() - 1; i >= 0; i--) {
    final String codec = codecs.get(i);
    final UnaryOperator<AsyncDataConsumer> op = decoders.lookup(codec);
    if (op != null) {
        downstream = op.apply(downstream);
    } else {
        throw new HttpException("Unsupported Content-Encoding: " + codec);
    }
}
```

So when the redirect response has `Content-Encoding: gzip`, 
`InflatingGzipDataConsumer` gets instantiated with `null` as `downstream`. The 
class has no null checks, so once the body is drained and `streamEnd()` or 
`releaseResources()` is called it NPEs.

## Fix

Add null guards on `downstream` in `updateCapacity()`, `consume()`, 
`streamEnd()`, and `releaseResources()`. When `downstream` is null the body is 
being discarded anyway, so skipping the delegation is the right behaviour.

 


> NullPointerException in InflatingGzipDataConsumer kills I/O reactor when 
> following redirects with Content-Encoding: gzip
> ------------------------------------------------------------------------------------------------------------------------
>
>                 Key: HTTPCLIENT-2426
>                 URL: https://issues.apache.org/jira/browse/HTTPCLIENT-2426
>             Project: HttpComponents HttpClient
>          Issue Type: Bug
>          Components: HttpClient (async)
>    Affects Versions: 5.6.1
>            Reporter: Szczepanski Dominik
>            Priority: Major
>   Original Estimate: 4h
>  Remaining Estimate: 4h
>
> h2. How to reproduce
> Make an async HTTP/1.1 request to an endpoint that responds with a 302 
> redirect where the intermediate response includes {{Content-Encoding: gzip}}. 
> The I/O reactor thread dies with:
> {code}
> java.lang.NullPointerException
>     at 
> org.apache.hc.client5.http.async.methods.InflatingGzipDataConsumer.streamEnd(InflatingGzipDataConsumer.java:148)
>     at 
> org.apache.hc.client5.http.impl.async.HttpAsyncMainClientExec$1.failed(...)
>     ...
>     at 
> org.apache.hc.core5.reactor.IOReactorWorker.run(IOReactorWorker.java:44)
> {code}
> h2. Why it fails
> When {{AsyncRedirectExec}} detects a redirect it returns {{null}} from 
> {{handleResponse()}} — which I think is intentional with the meaning "discard 
> this response body". The problem is that {{ContentCompressionAsyncExec}} 
> passes the null downstream straight into {{InflatingGzipDataConsumer}}:
> {code:java}
> AsyncDataConsumer downstream = cb.handleResponse(rsp, 
> wrapEntityDetails(details));
> for (int i = codecs.size() - 1; i >= 0; i--) {
>     final String codec = codecs.get(i);
>     final UnaryOperator<AsyncDataConsumer> op = decoders.lookup(codec);
>     if (op != null) {
>         downstream = op.apply(downstream);
>     } else {
>         throw new HttpException("Unsupported Content-Encoding: " + codec);
>     }
> }
> {code}
> So when the redirect response has {{Content-Encoding: gzip}}, 
> {{InflatingGzipDataConsumer}} gets instantiated with {{null}} as 
> {{downstream}}. The class has no null checks, so once the body is drained and 
> {{streamEnd()}} or {{releaseResources()}} is called it NPEs.
> h2. Fix
> Add null guards on {{downstream}} in {{updateCapacity()}}, {{consume()}}, 
> {{streamEnd()}}, and {{releaseResources()}}. When {{downstream}} is null the 
> body is being discarded anyway, so skipping the delegation seems to be the 
> right behaviour.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to