diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ContentCompressionExec.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ContentCompressionExec.java index ac3cd5a559..0d88979479 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ContentCompressionExec.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/classic/ContentCompressionExec.java @@ -54,6 +54,7 @@ import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHeaders; +import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.http.config.Lookup; import org.apache.hc.core5.http.config.RegistryBuilder; import org.apache.hc.core5.http.message.BasicHeaderValueParser; @@ -77,14 +78,18 @@ @Internal public final class ContentCompressionExec implements ExecChainHandler { + public static final int MAX_CODEC_LIST_LEN = 5; + private final Header acceptEncoding; private final Lookup decoderRegistry; private final boolean ignoreUnknown; + private final int maxCodecListLen; public ContentCompressionExec( final List acceptEncoding, final Lookup decoderRegistry, - final boolean ignoreUnknown) { + final boolean ignoreUnknown, + final int maxCodecListLen) { final boolean brotliSupported = decoderRegistry == null && BrotliDecompressingEntity.isAvailable(); if (acceptEncoding != null) { @@ -112,10 +117,22 @@ public ContentCompressionExec( this.decoderRegistry = builder.build(); } this.ignoreUnknown = ignoreUnknown; + this.maxCodecListLen = maxCodecListLen; + } + + public ContentCompressionExec( + final List acceptEncoding, + final Lookup decoderRegistry, + final boolean ignoreUnknown) { + this(acceptEncoding, decoderRegistry, ignoreUnknown, MAX_CODEC_LIST_LEN); } public ContentCompressionExec(final boolean ignoreUnknown) { - this(null, null, ignoreUnknown); + this(null, null, ignoreUnknown, MAX_CODEC_LIST_LEN); + } + + public ContentCompressionExec(final int maxCodecListLen) { + this(null, null, true, maxCodecListLen); } /** @@ -128,7 +145,7 @@ public ContentCompressionExec(final boolean ignoreUnknown) { * */ public ContentCompressionExec() { - this(null, null, true); + this(null, null, true, MAX_CODEC_LIST_LEN); } @@ -158,6 +175,9 @@ public ClassicHttpResponse execute( if (contentEncoding != null) { final ParserCursor cursor = new ParserCursor(0, contentEncoding.length()); final HeaderElement[] codecs = BasicHeaderValueParser.INSTANCE.parseElements(contentEncoding, cursor); + if (maxCodecListLen > 0 && codecs.length > maxCodecListLen) { + throw new ProtocolException("Codec list exceeds maximum of " + maxCodecListLen + " elements"); + } for (final HeaderElement codec : codecs) { final String codecname = codec.getName().toLowerCase(Locale.ROOT); final InputStreamFactory decoderFactory = decoderRegistry.lookup(codecname); diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/classic/TestContentCompressionExec.java b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/classic/TestContentCompressionExec.java index 60eaa27f6f..13df4e32c8 100644 --- a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/classic/TestContentCompressionExec.java +++ b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/classic/TestContentCompressionExec.java @@ -40,6 +40,7 @@ import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.Method; +import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.http.io.entity.StringEntity; import org.apache.hc.core5.http.message.BasicClassicHttpRequest; import org.apache.hc.core5.http.message.BasicClassicHttpResponse; @@ -236,4 +237,35 @@ void testContentEncodingRequestParameter() throws Exception { Assertions.assertFalse(entity instanceof GzipDecompressingEntity); } + @Test + void testContentEncodingExceedsCodecListLenMax() throws Exception { + impl = new ContentCompressionExec(5); + + final ClassicHttpRequest request = new BasicClassicHttpRequest(Method.GET, host, "/"); + final ClassicHttpResponse response1 = new BasicClassicHttpResponse(200, "OK"); + final HttpEntity original1 = EntityBuilder.create() + .setText("encoded stuff") + .setContentEncoding("gzip,gzip,gzip,gzip,gzip") + .build(); + response1.setEntity(original1); + + Mockito.when(execChain.proceed(request, scope)).thenReturn(response1); + + final HttpEntity entity = response1.getEntity(); + Assertions.assertNotNull(entity); + Assertions.assertFalse(entity instanceof GzipDecompressingEntity); + + final ClassicHttpResponse response2 = new BasicClassicHttpResponse(200, "OK"); + final HttpEntity original2 = EntityBuilder.create() + .setText("encoded stuff") + .setContentEncoding("gzip,gzip,gzip,gzip,gzip,gzip") + .build(); + response2.setEntity(original2); + + Mockito.when(execChain.proceed(request, scope)).thenReturn(response2); + + final ProtocolException exception = Assertions.assertThrows(ProtocolException.class, () -> impl.execute(request, scope, execChain)); + Assertions.assertEquals("Codec list exceeds maximum of 5 elements", exception.getMessage()); + } + }