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
new file mode 100644
index 0000000000..441c0d3295
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/ContentCodingSupport.java
@@ -0,0 +1,65 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * .
+ *
+ */
+
+package org.apache.hc.client5.http.impl;
+
+import java.util.ArrayList;
+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.message.MessageSupport;
+import org.apache.hc.core5.http.message.ParserCursor;
+
+/**
+ * @since 5.6
+ */
+@Internal
+public final class ContentCodingSupport {
+
+ private ContentCodingSupport() {
+ }
+
+ public static List parseContentCodecs(final EntityDetails entityDetails) {
+ if (entityDetails == null || entityDetails.getContentEncoding() == null) {
+ return Collections.emptyList();
+ }
+ final String contentEncoding = entityDetails.getContentEncoding();
+ final ParserCursor cursor = new ParserCursor(0, contentEncoding.length());
+ final List codecs = new ArrayList<>();
+ MessageSupport.parseTokens(contentEncoding, cursor, token -> {
+ final String codec = token.toLowerCase(Locale.ROOT);
+ if (!codec.isEmpty() && !"identity".equals(codec)) {
+ codecs.add(codec);
+ }
+ });
+ return codecs;
+ }
+
+}
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 7f1f2153e2..dea6d7d7e3 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
@@ -29,7 +29,7 @@
import java.io.IOException;
import java.util.Arrays;
import java.util.LinkedHashMap;
-import java.util.Locale;
+import java.util.List;
import java.util.Set;
import java.util.function.UnaryOperator;
@@ -39,21 +39,19 @@
import org.apache.hc.client5.http.async.methods.InflatingAsyncDataConsumer;
import org.apache.hc.client5.http.async.methods.InflatingGzipDataConsumer;
import org.apache.hc.client5.http.entity.compress.ContentCoding;
+import org.apache.hc.client5.http.impl.ContentCodingSupport;
import org.apache.hc.client5.http.protocol.HttpClientContext;
import org.apache.hc.core5.annotation.Contract;
import org.apache.hc.core5.annotation.Internal;
import org.apache.hc.core5.annotation.ThreadingBehavior;
import org.apache.hc.core5.http.EntityDetails;
-import org.apache.hc.core5.http.HeaderElement;
import org.apache.hc.core5.http.HttpException;
import org.apache.hc.core5.http.HttpHeaders;
import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.HttpResponse;
import org.apache.hc.core5.http.config.Lookup;
import org.apache.hc.core5.http.config.RegistryBuilder;
-import org.apache.hc.core5.http.message.BasicHeaderValueParser;
import org.apache.hc.core5.http.message.MessageSupport;
-import org.apache.hc.core5.http.message.ParserCursor;
import org.apache.hc.core5.http.nio.AsyncDataConsumer;
import org.apache.hc.core5.http.nio.AsyncEntityProducer;
import org.apache.hc.core5.util.Args;
@@ -119,23 +117,16 @@ public AsyncDataConsumer handleResponse(final HttpResponse rsp,
return cb.handleResponse(rsp, details);
}
- final String coding = details != null ? details.getContentEncoding() : null;
-
- if (coding != null) {
+ final List codecs = ContentCodingSupport.parseContentCodecs(details);
+ if (!codecs.isEmpty()) {
AsyncDataConsumer downstream = cb.handleResponse(rsp, wrapEntityDetails(details));
-
- final HeaderElement[] el = BasicHeaderValueParser.INSTANCE
- .parseElements(coding, new ParserCursor(0, coding.length()));
- for (int i = el.length - 1; i >= 0; i--) {
- final String token = el[i].getName().toLowerCase(Locale.ROOT);
- if ("identity".equals(token) || token.isEmpty()) {
- continue;
- }
- final UnaryOperator op = decoders.lookup(token);
+ for (int i = codecs.size() - 1; i >= 0; i--) {
+ final String codec = codecs.get(i);
+ final UnaryOperator op = decoders.lookup(codec);
if (op != null) {
downstream = op.apply(downstream);
} else {
- throw new HttpException("Unsupported Content-Encoding: " + token);
+ throw new HttpException("Unsupported Content-Encoding: " + codec);
}
}
rsp.removeHeaders(HttpHeaders.CONTENT_ENCODING);
@@ -143,6 +134,7 @@ public AsyncDataConsumer handleResponse(final HttpResponse rsp,
rsp.removeHeaders(HttpHeaders.CONTENT_MD5);
return downstream;
}
+
return cb.handleResponse(rsp, details);
}
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 c6491ec3e5..e3dca2d090 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
@@ -31,7 +31,6 @@
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
-import java.util.Locale;
import java.util.Map;
import java.util.function.UnaryOperator;
@@ -40,6 +39,7 @@
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.entity.compress.ContentCodecRegistry;
import org.apache.hc.client5.http.entity.compress.ContentCoding;
+import org.apache.hc.client5.http.impl.ContentCodingSupport;
import org.apache.hc.client5.http.protocol.HttpClientContext;
import org.apache.hc.core5.annotation.Contract;
import org.apache.hc.core5.annotation.Internal;
@@ -47,15 +47,12 @@
import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.Header;
-import org.apache.hc.core5.http.HeaderElement;
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.config.Lookup;
import org.apache.hc.core5.http.config.RegistryBuilder;
-import org.apache.hc.core5.http.message.BasicHeaderValueParser;
import org.apache.hc.core5.http.message.MessageSupport;
-import org.apache.hc.core5.http.message.ParserCursor;
import org.apache.hc.core5.util.Args;
/**
@@ -130,22 +127,20 @@ public ClassicHttpResponse execute(
// entity can be null in case of 304 Not Modified, 204 No Content or similar
// check for zero length entity.
if (requestConfig.isContentCompressionEnabled() && entity != null && entity.getContentLength() != 0) {
- final String contentEncoding = entity.getContentEncoding();
- if (contentEncoding != null) {
- final ParserCursor cursor = new ParserCursor(0, contentEncoding.length());
- final HeaderElement[] codecs = BasicHeaderValueParser.INSTANCE.parseElements(contentEncoding, cursor);
- for (final HeaderElement codec : codecs) {
- final String codecname = codec.getName().toLowerCase(Locale.ROOT);
- final UnaryOperator decoder = decoderRegistry.lookup(codecname);
+ final List codecs = ContentCodingSupport.parseContentCodecs(entity);
+ if (!codecs.isEmpty()) {
+ for (int i = codecs.size() - 1; i >= 0; i--) {
+ final String codec = codecs.get(i);
+ final UnaryOperator decoder = decoderRegistry.lookup(codec);
if (decoder != null) {
response.setEntity(decoder.apply(response.getEntity()));
- response.removeHeaders(HttpHeaders.CONTENT_LENGTH);
- response.removeHeaders(HttpHeaders.CONTENT_ENCODING);
- response.removeHeaders(HttpHeaders.CONTENT_MD5);
- } else if (!"identity".equals(codecname)) {
- throw new HttpException("Unsupported Content-Encoding: " + codec.getName());
+ } else {
+ throw new HttpException("Unsupported Content-Encoding: " + codec);
}
}
+ response.removeHeaders(HttpHeaders.CONTENT_LENGTH);
+ response.removeHeaders(HttpHeaders.CONTENT_ENCODING);
+ response.removeHeaders(HttpHeaders.CONTENT_MD5);
}
}
return response;
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/ContentCodingSupportTest.java b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/ContentCodingSupportTest.java
new file mode 100644
index 0000000000..24a5e72648
--- /dev/null
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/ContentCodingSupportTest.java
@@ -0,0 +1,109 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * .
+ *
+ */
+package org.apache.hc.client5.http.impl;
+
+import java.util.Set;
+
+import org.apache.hc.core5.http.EntityDetails;
+import org.apache.hc.core5.http.impl.BasicEntityDetails;
+import org.hamcrest.MatcherAssert;
+import org.hamcrest.Matchers;
+import org.junit.jupiter.api.Test;
+
+class ContentCodingSupportTest {
+
+ static class MockEntityDetails implements EntityDetails {
+
+ private final String contentEncoding;
+
+ MockEntityDetails(final String contentEncoding) {
+ this.contentEncoding = contentEncoding;
+ }
+
+ @Override
+ public long getContentLength() {
+ return -1;
+ }
+
+ @Override
+ public String getContentType() {
+ return null;
+ }
+
+ @Override
+ public String getContentEncoding() {
+ return contentEncoding;
+ }
+
+ @Override
+ public boolean isChunked() {
+ return false;
+ }
+
+ @Override
+ public Set getTrailerNames() {
+ return null;
+ }
+
+ }
+
+ @Test
+ void testNoEntity() {
+ MatcherAssert.assertThat(
+ ContentCodingSupport.parseContentCodecs(null),
+ Matchers.empty());
+ }
+
+ @Test
+ void testNotEncoded() {
+ MatcherAssert.assertThat(ContentCodingSupport.parseContentCodecs(
+ new BasicEntityDetails(-1, null)),
+ Matchers.empty());
+ }
+
+ @Test
+ void testNotEncodedNoise() {
+ MatcherAssert.assertThat(ContentCodingSupport.parseContentCodecs(
+ new MockEntityDetails(", ,, , ")),
+ Matchers.empty());
+ }
+
+ @Test
+ void testIdentityEncoded() {
+ MatcherAssert.assertThat(ContentCodingSupport.parseContentCodecs(
+ new MockEntityDetails("identity,,,identity")),
+ Matchers.empty());
+ }
+
+ @Test
+ void testEncodedMultipleCodes() {
+ MatcherAssert.assertThat(ContentCodingSupport.parseContentCodecs(
+ new MockEntityDetails("This,,that, \"This and That\"")),
+ Matchers.contains("this", "that", "\"this and that\""));
+ }
+
+}