Repository: netty/netty
Issue: IllegalReferenceCountException with compression enabled but not used if HttpResponse implements HttpContent and not LastHttpContent

Turns out my fix for https://github.com/netty/netty/issues/11092 was incomplete. It fixes the case where the content is compressed, but it will also fail if the client does not specify an Accept-Encoding.


Issue comments:

Pull request review comments:
Wrong annotation type
```suggestion
    @Test
```
```suggestion
    @Test
```

Patch that resolved the issue:
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpContentEncoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpContentEncoder.java
index e9c50260f34..49d263655de 100644
--- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpContentEncoder.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpContentEncoder.java
@@ -142,7 +142,7 @@ protected void encode(ChannelHandlerContext ctx, HttpObject msg, List<Object> ou
                     if (isFull) {
                         out.add(ReferenceCountUtil.retain(res));
                     } else {
-                        out.add(res);
+                        out.add(ReferenceCountUtil.retain(res));
                         // Pass through all following contents.
                         state = State.PASS_THROUGH;
                     }
@@ -165,7 +165,7 @@ protected void encode(ChannelHandlerContext ctx, HttpObject msg, List<Object> ou
                     if (isFull) {
                         out.add(ReferenceCountUtil.retain(res));
                     } else {
-                        out.add(res);
+                        out.add(ReferenceCountUtil.retain(res));
                         // Pass through all following contents.
                         state = State.PASS_THROUGH;
                     }


Test patch:
diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/HttpContentCompressorTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/HttpContentCompressorTest.java
index 5663e62ca6e..43184b8b001 100644
--- a/codec-http/src/test/java/io/netty/handler/codec/http/HttpContentCompressorTest.java
+++ b/codec-http/src/test/java/io/netty/handler/codec/http/HttpContentCompressorTest.java
@@ -44,6 +44,8 @@
 
 import org.junit.jupiter.api.Test;
 
+import java.nio.charset.StandardCharsets;
+
 import static io.netty.handler.codec.http.HttpHeadersTestUtils.of;
 import static org.hamcrest.CoreMatchers.instanceOf;
 import static org.hamcrest.CoreMatchers.is;
@@ -217,6 +219,65 @@ public void testChunkedContentWithAssembledResponse() throws Exception {
         assertThat(ch.readOutbound(), is(nullValue()));
     }
 
+    @Test
+    public void testChunkedContentWithAssembledResponseIdentityEncoding() throws Exception {
+        EmbeddedChannel ch = new EmbeddedChannel(new HttpContentCompressor());
+        ch.writeInbound(new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/"));
+
+        HttpResponse res = new AssembledHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK,
+                Unpooled.copiedBuffer("Hell", CharsetUtil.US_ASCII));
+        res.headers().set(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED);
+        ch.writeOutbound(res);
+
+        ch.writeOutbound(new DefaultHttpContent(Unpooled.copiedBuffer("o, w", CharsetUtil.US_ASCII)));
+        ch.writeOutbound(new DefaultLastHttpContent(Unpooled.copiedBuffer("orld", CharsetUtil.US_ASCII)));
+
+        HttpContent chunk;
+        chunk = ch.readOutbound();
+        assertThat(chunk.content().toString(StandardCharsets.UTF_8), is("Hell"));
+        chunk.release();
+
+        chunk = ch.readOutbound();
+        assertThat(chunk.content().toString(StandardCharsets.UTF_8), is("o, w"));
+        chunk.release();
+
+        chunk = ch.readOutbound();
+        assertThat(chunk.content().toString(StandardCharsets.UTF_8), is("orld"));
+        assertThat(chunk, is(instanceOf(LastHttpContent.class)));
+        chunk.release();
+
+        assertThat(ch.readOutbound(), is(nullValue()));
+    }
+
+    @Test
+    public void testContentWithAssembledResponseIdentityEncodingHttp10() throws Exception {
+        EmbeddedChannel ch = new EmbeddedChannel(new HttpContentCompressor());
+        ch.writeInbound(new DefaultFullHttpRequest(HttpVersion.HTTP_1_0, HttpMethod.GET, "/"));
+
+        HttpResponse res = new AssembledHttpResponse(HttpVersion.HTTP_1_0, HttpResponseStatus.OK,
+                Unpooled.copiedBuffer("Hell", CharsetUtil.US_ASCII));
+        ch.writeOutbound(res);
+
+        ch.writeOutbound(new DefaultHttpContent(Unpooled.copiedBuffer("o, w", CharsetUtil.US_ASCII)));
+        ch.writeOutbound(new DefaultLastHttpContent(Unpooled.copiedBuffer("orld", CharsetUtil.US_ASCII)));
+
+        HttpContent chunk;
+        chunk = ch.readOutbound();
+        assertThat(chunk.content().toString(StandardCharsets.UTF_8), is("Hell"));
+        chunk.release();
+
+        chunk = ch.readOutbound();
+        assertThat(chunk.content().toString(StandardCharsets.UTF_8), is("o, w"));
+        chunk.release();
+
+        chunk = ch.readOutbound();
+        assertThat(chunk.content().toString(StandardCharsets.UTF_8), is("orld"));
+        assertThat(chunk, is(instanceOf(LastHttpContent.class)));
+        chunk.release();
+
+        assertThat(ch.readOutbound(), is(nullValue()));
+    }
+
     @Test
     public void testChunkedContentWithTrailingHeader() throws Exception {
         EmbeddedChannel ch = new EmbeddedChannel(new HttpContentCompressor());