You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by ol...@apache.org on 2025/12/16 20:09:07 UTC
(httpcomponents-client) 01/02: Limit the length of content codec list that can be processed automatically
This is an automated email from the ASF dual-hosted git repository.
olegk pushed a commit to branch 5.6.x
in repository https://gitbox.apache.org/repos/asf/httpcomponents-client.git
commit 77fa61aae9fe492eb81e321073441646856775e1
Author: Oleg Kalnichevski <ol...@apache.org>
AuthorDate: Mon Dec 15 10:23:19 2025 +0100
Limit the length of content codec list that can be processed automatically
---
.../hc/client5/http/impl/ContentCodingSupport.java | 10 +++++++
.../impl/async/ContentCompressionAsyncExec.java | 16 ++++++++---
.../http/impl/async/HttpAsyncClientBuilder.java | 2 +-
.../http/impl/classic/ContentCompressionExec.java | 19 +++++++++++--
.../methods/InflatingAsyncEntityConsumerTest.java | 2 +-
.../methods/InflatingBrotliDataConsumerTest.java | 2 +-
.../async/TestContentCompressionAsyncExec.java | 2 +-
.../impl/classic/TestContentCompressionExec.java | 31 ++++++++++++++++++++++
8 files changed, 75 insertions(+), 9 deletions(-)
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/ContentCodingSupport.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/ContentCodingSupport.java
index 441c0d329..5d794b9f3 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/ContentCodingSupport.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/ContentCodingSupport.java
@@ -28,12 +28,14 @@
package org.apache.hc.client5.http.impl;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import org.apache.hc.core5.annotation.Internal;
import org.apache.hc.core5.http.EntityDetails;
+import org.apache.hc.core5.http.ProtocolException;
import org.apache.hc.core5.http.message.MessageSupport;
import org.apache.hc.core5.http.message.ParserCursor;
@@ -43,6 +45,8 @@
@Internal
public final class ContentCodingSupport {
+ public static final int MAX_CODEC_LIST_LEN = 5;
+
private ContentCodingSupport() {
}
@@ -62,4 +66,10 @@ public static List<String> parseContentCodecs(final EntityDetails entityDetails)
return codecs;
}
+ public static void validate(final Collection<String> codecList, final int maxCodecListLen) throws ProtocolException {
+ if (maxCodecListLen > 0 && codecList.size() > maxCodecListLen) {
+ throw new ProtocolException("Codec list exceeds maximum of " + maxCodecListLen + " elements");
+ }
+ }
+
}
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/ContentCompressionAsyncExec.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/ContentCompressionAsyncExec.java
index d8c9a6aa4..04d5ed796 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/ContentCompressionAsyncExec.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/ContentCompressionAsyncExec.java
@@ -67,23 +67,29 @@ public final class ContentCompressionAsyncExec implements AsyncExecChainHandler
private final Lookup<UnaryOperator<AsyncDataConsumer>> decoders;
private final List<String> acceptTokens;
+ private final int maxCodecListLen;
public ContentCompressionAsyncExec(
final LinkedHashMap<String, UnaryOperator<AsyncDataConsumer>> decoderMap,
- final boolean ignoreUnknown) {
-
+ final int maxCodecListLen) {
Args.notEmpty(decoderMap, "Decoder map");
final RegistryBuilder<UnaryOperator<AsyncDataConsumer>> rb = RegistryBuilder.create();
decoderMap.forEach(rb::register);
this.decoders = rb.build();
this.acceptTokens = new ArrayList<>(decoderMap.keySet());
+ this.maxCodecListLen = maxCodecListLen;
+ }
+
+ public ContentCompressionAsyncExec(
+ final LinkedHashMap<String, UnaryOperator<AsyncDataConsumer>> decoderMap) {
+ this(decoderMap, ContentCodingSupport.MAX_CODEC_LIST_LEN);
}
/**
* Default: DEFLATE + GZIP (plus <code>x-gzip</code> alias).
*/
- public ContentCompressionAsyncExec() {
+ public ContentCompressionAsyncExec(final int maxCodecListLen) {
final LinkedHashMap<String, UnaryOperator<AsyncDataConsumer>> map = new LinkedHashMap<>();
map.put(ContentCoding.DEFLATE.token(), d -> new InflatingAsyncDataConsumer(d, null));
map.put(ContentCoding.GZIP.token(), InflatingGzipDataConsumer::new);
@@ -109,8 +115,12 @@ public ContentCompressionAsyncExec() {
this.decoders = rb.build();
this.acceptTokens = tokens;
+ this.maxCodecListLen = maxCodecListLen;
}
+ public ContentCompressionAsyncExec() {
+ this(ContentCodingSupport.MAX_CODEC_LIST_LEN);
+ }
@Override
public void execute(
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientBuilder.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientBuilder.java
index b2f55bba7..d2968a5bf 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientBuilder.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientBuilder.java
@@ -1100,7 +1100,7 @@ public CloseableHttpAsyncClient build() {
if (!contentCompressionDisabled) {
if (contentDecoderMap != null && !contentDecoderMap.isEmpty()) {
execChainDefinition.addFirst(
- new ContentCompressionAsyncExec(contentDecoderMap, true),
+ new ContentCompressionAsyncExec(contentDecoderMap),
ChainElement.COMPRESS.name());
} else {
execChainDefinition.addFirst(
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 f4da426ec..fc7ed56c0 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
@@ -72,16 +72,25 @@ public final class ContentCompressionExec implements ExecChainHandler {
private final Header acceptEncoding;
private final Lookup<UnaryOperator<HttpEntity>> decoderRegistry;
+ private final int maxCodecListLen;
public ContentCompressionExec(
final List<String> acceptEncoding,
- final Lookup<UnaryOperator<HttpEntity>> decoderRegistry) {
+ final Lookup<UnaryOperator<HttpEntity>> decoderRegistry,
+ final int maxCodecListLen) {
this.acceptEncoding = MessageSupport.headerOfTokens(HttpHeaders.ACCEPT_ENCODING,
Args.notEmpty(acceptEncoding, "Encoding list"));
this.decoderRegistry = Args.notNull(decoderRegistry, "Decoder register");
+ this.maxCodecListLen = maxCodecListLen;
}
- public ContentCompressionExec() {
+ public ContentCompressionExec(
+ final List<String> acceptEncoding,
+ final Lookup<UnaryOperator<HttpEntity>> decoderRegistry) {
+ this(acceptEncoding, decoderRegistry, ContentCodingSupport.MAX_CODEC_LIST_LEN);
+ }
+
+ public ContentCompressionExec(final int maxCodecListLen) {
final Map<ContentCoding, UnaryOperator<HttpEntity>> decoderMap = new EnumMap<>(ContentCoding.class);
for (final ContentCoding c : ContentCoding.values()) {
final UnaryOperator<HttpEntity> d = ContentCodecRegistry.decoder(c);
@@ -103,6 +112,11 @@ public ContentCompressionExec() {
}
this.acceptEncoding = MessageSupport.headerOfTokens(HttpHeaders.ACCEPT_ENCODING, acceptList);
this.decoderRegistry = builder.build();
+ this.maxCodecListLen = maxCodecListLen;
+ }
+
+ public ContentCompressionExec() {
+ this(ContentCodingSupport.MAX_CODEC_LIST_LEN);
}
@Override
@@ -128,6 +142,7 @@ public ClassicHttpResponse execute(
// check for zero length entity.
if (requestConfig.isContentCompressionEnabled() && entity != null && entity.getContentLength() != 0) {
final List<String> codecs = ContentCodingSupport.parseContentCodecs(entity);
+ ContentCodingSupport.validate(codecs, maxCodecListLen);
if (!codecs.isEmpty()) {
for (int i = codecs.size() - 1; i >= 0; i--) {
final String codec = codecs.get(i);
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/async/methods/InflatingAsyncEntityConsumerTest.java b/httpclient5/src/test/java/org/apache/hc/client5/http/async/methods/InflatingAsyncEntityConsumerTest.java
index fca30dc82..308bfe4cf 100644
--- a/httpclient5/src/test/java/org/apache/hc/client5/http/async/methods/InflatingAsyncEntityConsumerTest.java
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/async/methods/InflatingAsyncEntityConsumerTest.java
@@ -204,7 +204,7 @@ public Set<String> getTrailerNames() {
void unknownEncodingMapFlag() throws Exception {
final LinkedHashMap<String, UnaryOperator<AsyncDataConsumer>> map = new LinkedHashMap<>();
map.put("deflate", d -> new InflatingAsyncDataConsumer(d, null));
- final ContentCompressionAsyncExec exec = new ContentCompressionAsyncExec(map, false);
+ final ContentCompressionAsyncExec exec = new ContentCompressionAsyncExec(map);
assertNotNull(exec);
}
}
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/async/methods/InflatingBrotliDataConsumerTest.java b/httpclient5/src/test/java/org/apache/hc/client5/http/async/methods/InflatingBrotliDataConsumerTest.java
index 8446d2830..230e0936e 100644
--- a/httpclient5/src/test/java/org/apache/hc/client5/http/async/methods/InflatingBrotliDataConsumerTest.java
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/async/methods/InflatingBrotliDataConsumerTest.java
@@ -195,7 +195,7 @@ public Set<String> getTrailerNames() {
void registerInExec() {
final LinkedHashMap<String, UnaryOperator<AsyncDataConsumer>> map = new LinkedHashMap<>();
map.put("br", InflatingBrotliDataConsumer::new);
- final ContentCompressionAsyncExec exec = new ContentCompressionAsyncExec(map, false);
+ final ContentCompressionAsyncExec exec = new ContentCompressionAsyncExec(map);
assertNotNull(exec);
}
}
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/async/TestContentCompressionAsyncExec.java b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/async/TestContentCompressionAsyncExec.java
index a02ddb45d..8cf3a181d 100644
--- a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/async/TestContentCompressionAsyncExec.java
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/async/TestContentCompressionAsyncExec.java
@@ -163,7 +163,7 @@ public AsyncDataConsumer apply(final AsyncDataConsumer d) {
return new InflatingAsyncDataConsumer(d, null);
}
});
- impl = new ContentCompressionAsyncExec(map, /*ignoreUnknown*/ false);
+ impl = new ContentCompressionAsyncExec(map);
final HttpRequest request = new BasicHttpRequest(Method.GET, "/");
final AsyncExecCallback cb = executeAndCapture(request);
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 d9c5151d6..44711e82f 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,34 @@ 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);
+
+ 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());
+ }
+
}