This is an automated email from the ASF dual-hosted git repository.
markt-asf pushed a commit to branch 9.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/9.0.x by this push:
new 071ff133e1 Add (AI generated) tests for Default Servlet /
Accept-Encoding and fix the failures these tests exposed.
071ff133e1 is described below
commit 071ff133e157d445ae9d723be2ad8599c5ee4899
Author: Mark Thomas <[email protected]>
AuthorDate: Tue Oct 13 15:57:30 2026 +0100
Add (AI generated) tests for Default Servlet / Accept-Encoding and fix
the failures these tests exposed.
Co-authored-by: Claude Sonnet 5.5
---
.../apache/catalina/servlets/DefaultServlet.java | 31 ++-
java/org/apache/coyote/CompressionConfig.java | 27 +--
.../tomcat/util/http/parser/AcceptEncoding.java | 7 +-
.../apache/tomcat/util/http/parser/HttpParser.java | 21 +-
.../catalina/servlets/TestDefaultServlet.java | 97 +++++++++
test/org/apache/coyote/TestCompressionConfig.java | 54 ++++-
.../util/http/parser/TestAcceptEncoding.java | 221 +++++++++++++++++++++
webapps/docs/changelog.xml | 4 +
8 files changed, 427 insertions(+), 35 deletions(-)
diff --git a/java/org/apache/catalina/servlets/DefaultServlet.java
b/java/org/apache/catalina/servlets/DefaultServlet.java
index cb595790a6..980ddd83da 100644
--- a/java/org/apache/catalina/servlets/DefaultServlet.java
+++ b/java/org/apache/catalina/servlets/DefaultServlet.java
@@ -41,10 +41,12 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Enumeration;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
+import java.util.Set;
import java.util.TreeSet;
import java.util.function.Function;
@@ -1423,20 +1425,36 @@ public class DefaultServlet extends HttpServlet {
double bestResourceQuality = 0;
int bestResourcePreference = Integer.MAX_VALUE;
+ Set<String> excluded = new HashSet<>();
+ for (AcceptEncoding acceptEncoding : acceptEncodings) {
+ if (acceptEncoding.getQuality() == 0) {
+
excluded.add(acceptEncoding.getEncoding().toLowerCase(Locale.ENGLISH));
+ }
+ }
for (AcceptEncoding acceptEncoding : acceptEncodings) {
double quality = acceptEncoding.getQuality();
- if (quality >= bestResourceQuality) {
- String encoding = acceptEncoding.getEncoding();
- if ("identity".equalsIgnoreCase(encoding)) {
+ if (quality > 0 && quality >= bestResourceQuality) {
+ String encoding =
acceptEncoding.getEncoding().toLowerCase(Locale.ENGLISH);
+ if (excluded.contains(encoding)) {
+ continue;
+ }
+ if ("identity".equals(encoding)) {
bestResource = null;
bestResourceQuality = quality;
bestResourcePreference = Integer.MAX_VALUE;
continue;
}
if ("*".equals(encoding)) {
- bestResource = precompressedResources.get(0);
- bestResourceQuality = quality;
- bestResourcePreference = 0;
+ for (int i = 0; i < precompressedResources.size(); ++i) {
+ PrecompressedResource resource =
precompressedResources.get(i);
+ if
(excluded.contains(resource.format.encoding.toLowerCase(Locale.ENGLISH))) {
+ continue;
+ }
+ bestResource = resource;
+ bestResourceQuality = quality;
+ bestResourcePreference = i;
+ break;
+ }
continue;
}
for (int i = 0; i < precompressedResources.size(); ++i) {
@@ -1452,7 +1470,6 @@ public class DefaultServlet extends HttpServlet {
}
}
}
-
return bestResource;
}
diff --git a/java/org/apache/coyote/CompressionConfig.java
b/java/org/apache/coyote/CompressionConfig.java
index 43b77acc01..24fa9b1b44 100644
--- a/java/org/apache/coyote/CompressionConfig.java
+++ b/java/org/apache/coyote/CompressionConfig.java
@@ -75,9 +75,8 @@ public class CompressionConfig {
/**
- * Set the list of content encodings that indicate already-compressed
content.
- * When content is already encoded with one of these encodings,
compression will not be applied
- * to prevent double compression.
+ * Set the list of content encodings that indicate already-compressed
content. When content is already encoded with
+ * one of these encodings, compression will not be applied to prevent
double compression.
*
* @param encodings Comma-separated list of encoding names (e.g.,
"gzip,br,deflate")
*/
@@ -87,7 +86,7 @@ public class CompressionConfig {
StringTokenizer tokens = new StringTokenizer(encodings, ",");
while (tokens.hasMoreTokens()) {
String token = tokens.nextToken().trim();
- if(!token.isEmpty()) {
+ if (!token.isEmpty()) {
newEncodings.add(token);
}
}
@@ -170,8 +169,7 @@ public class CompressionConfig {
/**
- * Returns the compiled regular expression pattern for user agents that
should not
- * receive compressed responses.
+ * Returns the compiled regular expression pattern for user agents that
should not receive compressed responses.
*
* @return the compiled pattern, or {@code null} if not configured
*/
@@ -386,11 +384,12 @@ public class CompressionConfig {
// Therefore, set the Vary header to keep proxies happy
ResponseUtil.addVaryFieldName(responseHeaders, "accept-encoding");
- // Check if user-agent supports gzip encoding
- // Only interested in whether gzip encoding is supported. Other
- // encodings and weights can be ignored.
+ /*
+ * Check if user-agent supports gzip encoding. Only interested in
whether gzip encoding is supported. Other
+ * encodings can be ignored.
+ */
headerValues = request.getMimeHeaders().values("accept-encoding");
- while (!foundGzip && headerValues.hasMoreElements()) {
+ while (headerValues.hasMoreElements()) {
List<AcceptEncoding> acceptEncodings;
try {
acceptEncodings = AcceptEncoding.parse(new
StringReader(headerValues.nextElement()));
@@ -400,9 +399,13 @@ public class CompressionConfig {
}
for (AcceptEncoding acceptEncoding : acceptEncodings) {
- if ("gzip".equalsIgnoreCase(acceptEncoding.getEncoding()))
{
+ if (("gzip".equalsIgnoreCase(acceptEncoding.getEncoding())
||
+ "*".equals(acceptEncoding.getEncoding())) &&
acceptEncoding.getQuality() > 0) {
foundGzip = true;
- break;
+ }
+ if ("gzip".equalsIgnoreCase(acceptEncoding.getEncoding())
&& acceptEncoding.getQuality() == 0) {
+ // gzip is explicitly disabled
+ return false;
}
}
}
diff --git a/java/org/apache/tomcat/util/http/parser/AcceptEncoding.java
b/java/org/apache/tomcat/util/http/parser/AcceptEncoding.java
index e32398e130..b7e96db51e 100644
--- a/java/org/apache/tomcat/util/http/parser/AcceptEncoding.java
+++ b/java/org/apache/tomcat/util/http/parser/AcceptEncoding.java
@@ -60,7 +60,8 @@ public class AcceptEncoding {
/**
- * Parses the Accept-Encoding header value.
+ * Parses the Accept-Encoding header value. Encodings with invalid quality
values are ignored. Encodings with a
+ * quality value of zero are included since they may represent an
exclusion from a {@code *} entry.
*
* @param input The input reader
*
@@ -91,8 +92,8 @@ public class AcceptEncoding {
if (lookForSemiColon == SkipResult.FOUND) {
quality = HttpParser.readWeight(input, ',');
}
-
- if (quality > 0) {
+ // -1 indicates a malformed quality value - treat encoding as
invalid
+ if (quality >= 0) {
result.add(new AcceptEncoding(encoding, quality));
}
} while (true);
diff --git a/java/org/apache/tomcat/util/http/parser/HttpParser.java
b/java/org/apache/tomcat/util/http/parser/HttpParser.java
index d3cbdb9658..505ca7d789 100644
--- a/java/org/apache/tomcat/util/http/parser/HttpParser.java
+++ b/java/org/apache/tomcat/util/http/parser/HttpParser.java
@@ -835,17 +835,17 @@ public class HttpParser {
// No q value just whitespace
return 1;
} else if (c != 'q') {
- // Malformed. Use quality of zero so it is dropped.
+ // Malformed. Use quality of -1 so it is dropped.
skipUntil(input, c, delimiter);
- return 0;
+ return -1;
}
// RFC 7231 does not allow whitespace here but be tolerant
skipWhitespace(input);
c = input.read();
if (c != '=') {
- // Malformed. Use quality of zero so it is dropped.
+ // Malformed. Use quality of -1 so it is dropped.
skipUntil(input, c, delimiter);
- return 0;
+ return -1;
}
// RFC 7231 does not allow whitespace here but be tolerant
@@ -875,10 +875,9 @@ public class HttpParser {
c = input.read();
}
} else {
- // Malformed. Use quality of zero so it is dropped and skip until
- // EOF or the next delimiter
+ // Malformed. Use quality of -1 so it is dropped and skip until
EOF or the next delimiter
skipUntil(input, c, delimiter);
- return 0;
+ return -1;
}
if (c == 9 || c == 32) {
@@ -888,15 +887,15 @@ public class HttpParser {
// Must be at delimiter or EOF
if (c != delimiter && c != -1) {
- // Malformed. Use quality of zero so it is dropped and skip until
- // EOF or the next delimiter
+ // Malformed. Use quality of -1 so it is dropped and skip until
EOF or the next delimiter
skipUntil(input, c, delimiter);
- return 0;
+ return -1;
}
double result = Double.parseDouble(value.toString());
+ // Malformed. Use quality of -1 so it is dropped
if (result > 1) {
- return 0;
+ return -1;
}
return result;
}
diff --git a/test/org/apache/catalina/servlets/TestDefaultServlet.java
b/test/org/apache/catalina/servlets/TestDefaultServlet.java
index ad630e4b64..cbfdfeb450 100644
--- a/test/org/apache/catalina/servlets/TestDefaultServlet.java
+++ b/test/org/apache/catalina/servlets/TestDefaultServlet.java
@@ -448,6 +448,103 @@ public class TestDefaultServlet extends TomcatBaseTest {
Assert.assertTrue(responseHeaders.contains("vary: accept-encoding"));
}
+ /*
+ * Verify how the Accept-Encoding header is processed when selecting a
precompressed resource. The server
+ * preference is br followed by gzip.
+ */
+ @Test
+ public void testPrecompressedAcceptEncodingProcessing() throws Exception {
+
+ Tomcat tomcat = getTomcatInstance();
+
+ File appDir = new File("test/webapp");
+
+ long gzSize = new File(appDir, "index.html.gz").length();
+ long brSize = new File(appDir, "index.html.br").length();
+ long indexSize = new File(appDir, "index.html").length();
+
+ // app dir is relative to server home
+ Context ctxt = tomcat.addContext("", appDir.getAbsolutePath());
+ Wrapper defaultServlet = Tomcat.addServlet(ctxt, "default",
DefaultServlet.class.getName());
+ defaultServlet.addInitParameter("precompressed", "br=.br,gzip=.gz");
+ defaultServlet.addInitParameter("fileEncoding", "ISO-8859-1");
+
+ ctxt.addServletMapping("/", "default");
+ ctxt.addMimeMapping("html", "text/html");
+
+ tomcat.start();
+
+ TestCompressedClient client = new TestCompressedClient(getPort());
+
+ // Content coding names are case insensitive
+ assertPrecompressedResponse(client, "GZIP", "gzip", gzSize);
+ assertPrecompressedResponse(client, "Br", "br", brSize);
+
+ // q=0 means not acceptable
+ assertPrecompressedResponse(client, "gzip;q=0", null, indexSize);
+ assertPrecompressedResponse(client, "br;q=0,gzip", "gzip", gzSize);
+
+ // Weights that do not follow the RFC grammar are ignored
+ assertPrecompressedResponse(client, "br;q=Infinity,gzip", "gzip",
gzSize);
+ assertPrecompressedResponse(client, "br;q=5,gzip;q=0.5", "gzip",
gzSize);
+ assertPrecompressedResponse(client, "br;q=1e0,gzip;q=0.5", "gzip",
gzSize);
+ assertPrecompressedResponse(client, "br;q=NaN,gzip;q=0.5", "gzip",
gzSize);
+ assertPrecompressedResponse(client, "br;q=1d,gzip;q=0.5", "gzip",
gzSize);
+ assertPrecompressedResponse(client, "br;foo=bar,gzip;q=0.5", "gzip",
gzSize);
+
+ // Equal weights, so the server preference decides
+ assertPrecompressedResponse(client, "gzip, br", "br", brSize);
+ assertPrecompressedResponse(client, "gzip;q=1, br;q=1.0", "br",
brSize);
+
+ // Weights take precedence over server preference
+ assertPrecompressedResponse(client, "gzip, br;q=0.5", "gzip", gzSize);
+
+ // identity is case insensitive
+ assertPrecompressedResponse(client, "gzip;q=0.9, identity", null,
indexSize);
+ assertPrecompressedResponse(client, "gzip;q=0.9, Identity", null,
indexSize);
+
+ // Explicitly excluded encodings are not selected by "*"
+ assertPrecompressedResponse(client, "*;q=0.5, br;q=0", "gzip", gzSize);
+
+ // An explicit weight of zero is authoritative even if the same coding
is also listed with a positive weight
+ assertPrecompressedResponse(client, "gzip, gzip;q=0", null, indexSize);
+ assertPrecompressedResponse(client, "gzip;q=0, gzip", null, indexSize);
+ assertPrecompressedResponse(client, "GZIP, gzip;q=0", null, indexSize);
+ assertPrecompressedResponse(client, "br, br;q=0, gzip", "gzip",
gzSize);
+ assertPrecompressedResponse(client, "br;q=0, gzip, br", "gzip",
gzSize);
+ assertPrecompressedResponse(client, "br, gzip, br;q=0, gzip;q=0",
null, indexSize);
+ assertPrecompressedResponse(client, "*, gzip;q=0, br;q=0", null,
indexSize);
+ }
+
+
+ private void assertPrecompressedResponse(TestCompressedClient client,
String acceptEncoding,
+ String expectedEncoding, long expectedLength) throws Exception {
+ client.reset();
+ // @formatter:off
+ client.setRequest(new String[] {
+ "GET /index.html HTTP/1.1" + CRLF +
+ "Host: localhost" + CRLF +
+ "Connection: Close" + CRLF +
+ "Accept-Encoding: " + acceptEncoding + CRLF +
+ CRLF
+ });
+ // @formatter:on
+ client.connect();
+ client.processRequest();
+ String message = "Accept-Encoding: " + acceptEncoding;
+ Assert.assertTrue(message, client.isResponse200());
+ List<String> responseHeaders = client.getResponseHeaders();
+ if (expectedEncoding == null) {
+ for (String responseHeader : responseHeaders) {
+ Assert.assertFalse(message,
responseHeader.toLowerCase(Locale.ROOT).startsWith("content-encoding"));
+ }
+ } else {
+ Assert.assertTrue(message,
responseHeaders.contains("Content-Encoding: " + expectedEncoding));
+ }
+ Assert.assertTrue(message, responseHeaders.contains("Content-Length: "
+ expectedLength));
+ Assert.assertTrue(message, responseHeaders.contains("vary:
accept-encoding"));
+ }
+
/*
* Verify preferring of brotli in default configuration for actual Firefox
and Chrome requests.
*/
diff --git a/test/org/apache/coyote/TestCompressionConfig.java
b/test/org/apache/coyote/TestCompressionConfig.java
index 9f807abb13..76b5fd4392 100644
--- a/test/org/apache/coyote/TestCompressionConfig.java
+++ b/test/org/apache/coyote/TestCompressionConfig.java
@@ -20,6 +20,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
+import java.util.StringJoiner;
import org.junit.Assert;
import org.junit.Test;
@@ -30,7 +31,7 @@ import org.junit.runners.Parameterized.Parameter;
@RunWith(Parameterized.class)
public class TestCompressionConfig {
- @Parameterized.Parameters(name = "{index}: accept-encoding[{0}], ETag
[{1}], NoCompressionStrongETag[{2}], compress[{3}], useTE[{4}]")
+ @Parameterized.Parameters(name = "{index}: accept-encoding[{5}], ETag
[{1}], NoCompressionStrongETag[{2}], compress[{3}], useTE[{4}]")
public static Collection<Object[]> parameters() {
List<Object[]> parameterSets = new ArrayList<>();
@@ -68,7 +69,54 @@ public class TestCompressionConfig {
parameterSets.add(new Object[] { new String[] { "gzip" }, "W/",
Boolean.FALSE, Boolean.TRUE, Boolean.TRUE });
parameterSets.add(new Object[] { new String[] { "gzip" }, "XX",
Boolean.FALSE, Boolean.TRUE, Boolean.TRUE });
- return parameterSets;
+ // A weight of zero means not acceptable. A malformed weight is not
acceptable either.
+ for (Boolean useTE : new Boolean[] { Boolean.FALSE, Boolean.TRUE }) {
+ parameterSets.add(new Object[] { new String[] { "gzip;q=0" },
null, Boolean.TRUE, Boolean.FALSE, useTE });
+ parameterSets.add(new Object[] { new String[] { "gzip;q=0.0" },
null, Boolean.TRUE, Boolean.FALSE, useTE });
+ parameterSets.add(new Object[] { new String[] { "gzip;q=abc" },
null, Boolean.TRUE, Boolean.FALSE, useTE });
+ parameterSets.add(new Object[] { new String[] { "foo", "gzip;q=0"
}, null, Boolean.TRUE, Boolean.FALSE, useTE });
+ parameterSets.add(new Object[] { new String[] { "gzip;q=abc",
"gzip" }, null, Boolean.TRUE, Boolean.TRUE, useTE });
+ parameterSets.add(new Object[] { new String[] { "gzip;q=0.5" },
null, Boolean.TRUE, Boolean.TRUE, useTE });
+ }
+
+ // "*" matches any content coding that is not listed explicitly. It is
not meaningful for the TE header.
+ parameterSets.add(new Object[] { new String[] { "*" }, null,
Boolean.TRUE, Boolean.TRUE, Boolean.FALSE });
+ parameterSets.add(new Object[] { new String[] { "*;q=0.5" }, null,
Boolean.TRUE, Boolean.TRUE, Boolean.FALSE });
+ parameterSets.add(new Object[] { new String[] { "foo, *" }, null,
Boolean.TRUE, Boolean.TRUE, Boolean.FALSE });
+ parameterSets.add(new Object[] { new String[] { "*;q=0" }, null,
Boolean.TRUE, Boolean.FALSE, Boolean.FALSE });
+ parameterSets.add(new Object[] { new String[] { "*;q=abc" }, null,
Boolean.TRUE, Boolean.FALSE, Boolean.FALSE });
+ // An explicit entry takes precedence over "*", wherever it appears
+ parameterSets.add(new Object[] { new String[] { "gzip;q=0, *" }, null,
Boolean.TRUE, Boolean.FALSE, Boolean.FALSE });
+ parameterSets.add(new Object[] { new String[] { "*, gzip;q=0" }, null,
Boolean.TRUE, Boolean.FALSE, Boolean.FALSE });
+ parameterSets.add(new Object[] { new String[] { "gzip;q=0", "*" },
null, Boolean.TRUE, Boolean.FALSE, Boolean.FALSE });
+ parameterSets.add(new Object[] { new String[] { "*", "gzip;q=0" },
null, Boolean.TRUE, Boolean.FALSE, Boolean.FALSE });
+ parameterSets.add(new Object[] { new String[] { "*;q=0, gzip" }, null,
Boolean.TRUE, Boolean.TRUE, Boolean.FALSE });
+ parameterSets.add(new Object[] { new String[] { "*;q=0", "gzip" },
null, Boolean.TRUE, Boolean.TRUE, Boolean.FALSE });
+ parameterSets.add(new Object[] { new String[] { "gzip", "*;q=0" },
null, Boolean.TRUE, Boolean.TRUE, Boolean.FALSE });
+ // An explicit weight of zero is authoritative even if the same coding
is also listed with a positive weight
+ parameterSets.add(new Object[] { new String[] { "gzip, gzip;q=0" },
null, Boolean.TRUE, Boolean.FALSE, Boolean.FALSE });
+ parameterSets.add(new Object[] { new String[] { "gzip;q=0, gzip" },
null, Boolean.TRUE, Boolean.FALSE, Boolean.FALSE });
+ parameterSets.add(new Object[] { new String[] { "gzip;q=0.5, gzip;q=0"
}, null, Boolean.TRUE, Boolean.FALSE, Boolean.FALSE });
+ parameterSets.add(new Object[] { new String[] { "gzip", "gzip;q=0" },
null, Boolean.TRUE, Boolean.FALSE, Boolean.FALSE });
+ parameterSets.add(new Object[] { new String[] { "gzip;q=0", "gzip" },
null, Boolean.TRUE, Boolean.FALSE, Boolean.FALSE });
+ parameterSets.add(new Object[] { new String[] { "GZIP, gzip;q=0" },
null, Boolean.TRUE, Boolean.FALSE, Boolean.FALSE });
+ // "*" is not used for the TE header
+ parameterSets.add(new Object[] { new String[] { "*" }, null,
Boolean.TRUE, Boolean.FALSE, Boolean.TRUE });
+ parameterSets.add(new Object[] { new String[] { "*;q=0.5" }, null,
Boolean.TRUE, Boolean.FALSE, Boolean.TRUE });
+
+ // Add a description of the headers so the test name shows them rather
than an array reference. Each header is
+ // quoted so a single header with multiple values can be distinguished
from multiple headers.
+ List<Object[]> result = new ArrayList<>(parameterSets.size());
+ for (Object[] parameterSet : parameterSets) {
+ StringJoiner description = new StringJoiner(", ");
+ for (String header : (String[]) parameterSet[0]) {
+ description.add('"' + header + '"');
+ }
+ result.add(new Object[] { parameterSet[0], parameterSet[1],
parameterSet[2], parameterSet[3],
+ parameterSet[4], description.toString() });
+ }
+
+ return result;
}
@Parameter(0)
@@ -81,6 +129,8 @@ public class TestCompressionConfig {
public Boolean compress;
@Parameter(4)
public Boolean useTE;
+ @Parameter(5)
+ public String headersDescription;
@SuppressWarnings("deprecation")
@Test
diff --git a/test/org/apache/tomcat/util/http/parser/TestAcceptEncoding.java
b/test/org/apache/tomcat/util/http/parser/TestAcceptEncoding.java
new file mode 100644
index 0000000000..a127989801
--- /dev/null
+++ b/test/org/apache/tomcat/util/http/parser/TestAcceptEncoding.java
@@ -0,0 +1,221 @@
+/*
+ * 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.
+ */
+package org.apache.tomcat.util.http.parser;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class TestAcceptEncoding {
+
+ private static final double DELTA = 0.0001;
+
+
+ @Test
+ public void testSimple() throws Exception {
+ List<AcceptEncoding> actual = parse("gzip");
+
+ Assert.assertEquals(1, actual.size());
+ assertEntry(actual.get(0), "gzip", 1);
+ }
+
+
+ @Test
+ public void testEmpty() throws Exception {
+ Assert.assertTrue(parse("").isEmpty());
+ }
+
+
+ @Test
+ public void testMultiple() throws Exception {
+ List<AcceptEncoding> actual = parse("gzip;q=0.5, br, *;q=0.1");
+
+ Assert.assertEquals(3, actual.size());
+ assertEntry(actual.get(0), "gzip", 0.5);
+ assertEntry(actual.get(1), "br", 1);
+ assertEntry(actual.get(2), "*", 0.1);
+ }
+
+
+ @Test
+ public void testWeightWithThreeDecimalPlaces() throws Exception {
+ List<AcceptEncoding> actual = parse("gzip;q=0.123");
+
+ Assert.assertEquals(1, actual.size());
+ assertEntry(actual.get(0), "gzip", 0.123);
+ }
+
+
+ @Test
+ public void testWeightOfOne() throws Exception {
+ List<AcceptEncoding> actual = parse("gzip;q=1, br;q=1.0,
deflate;q=1.000");
+
+ Assert.assertEquals(3, actual.size());
+ assertEntry(actual.get(0), "gzip", 1);
+ assertEntry(actual.get(1), "br", 1);
+ assertEntry(actual.get(2), "deflate", 1);
+ }
+
+
+ /*
+ * An explicit weight of zero means "not acceptable". The entry must be
returned since it may exclude a coding that
+ * would otherwise be matched by "*".
+ */
+ @Test
+ public void testExplicitZeroWeightIsReturned() throws Exception {
+ List<AcceptEncoding> actual = parse("gzip;q=0");
+
+ Assert.assertEquals(1, actual.size());
+ assertEntry(actual.get(0), "gzip", 0);
+ }
+
+
+ @Test
+ public void testExplicitZeroWeightVariants() throws Exception {
+ List<AcceptEncoding> actual = parse("gzip;q=0.0, br;q=0.00,
deflate;q=0.000");
+
+ Assert.assertEquals(3, actual.size());
+ assertEntry(actual.get(0), "gzip", 0);
+ assertEntry(actual.get(1), "br", 0);
+ assertEntry(actual.get(2), "deflate", 0);
+ }
+
+
+ @Test
+ public void testExplicitZeroWeightWithWildcard() throws Exception {
+ List<AcceptEncoding> actual = parse("*;q=0.5, br;q=0");
+
+ Assert.assertEquals(2, actual.size());
+ assertEntry(actual.get(0), "*", 0.5);
+ assertEntry(actual.get(1), "br", 0);
+ }
+
+
+ @Test
+ public void testWildcardWithZeroWeight() throws Exception {
+ List<AcceptEncoding> actual = parse("*;q=0, gzip");
+
+ Assert.assertEquals(2, actual.size());
+ assertEntry(actual.get(0), "*", 0);
+ assertEntry(actual.get(1), "gzip", 1);
+ }
+
+
+ /*
+ * Malformed weights are ignored. The entry is dropped rather than
returned with a weight of zero so it can be
+ * distinguished from an explicit exclusion.
+ */
+ @Test
+ public void testMalformedWeightIsDropped() throws Exception {
+ List<AcceptEncoding> actual = parse("gzip;q=abc");
+
+ Assert.assertTrue(actual.isEmpty());
+ }
+
+
+ @Test
+ public void testMalformedWeights() throws Exception {
+ String[] malformed = new String[] { "gzip;q=abc", "gzip;q=",
"gzip;q=-1", "gzip;q=2", "gzip;q=5",
+ "gzip;q=1.5", "gzip;q=1.001", "gzip;q=Infinity", "gzip;q=NaN",
"gzip;q=1e0", "gzip;q=0x1p0",
+ "gzip;q=1d", "gzip;q=1f", "gzip;q=.5", "gzip;q=0.5x",
"gzip;foo=bar", "gzip;q", "gzip;x=0.5" };
+
+ for (String header : malformed) {
+ Assert.assertTrue(header, parse(header).isEmpty());
+ }
+ }
+
+
+ @Test
+ public void testParsingResumesAfterMalformedWeight() throws Exception {
+ List<AcceptEncoding> actual = parse("br;q=abc, gzip;q=0.5,
deflate;q=7, identity");
+
+ Assert.assertEquals(2, actual.size());
+ assertEntry(actual.get(0), "gzip", 0.5);
+ assertEntry(actual.get(1), "identity", 1);
+ }
+
+
+ @Test
+ public void testMalformedWeightBetweenValidEntries() throws Exception {
+ List<AcceptEncoding> actual = parse("gzip;q=0.9, br;q=Infinity,
deflate;q=0");
+
+ Assert.assertEquals(2, actual.size());
+ assertEntry(actual.get(0), "gzip", 0.9);
+ assertEntry(actual.get(1), "deflate", 0);
+ }
+
+
+ @Test
+ public void testInvalidTokenIsSkipped() throws Exception {
+ List<AcceptEncoding> actual = parse("<>, gzip");
+
+ Assert.assertEquals(1, actual.size());
+ assertEntry(actual.get(0), "gzip", 1);
+ }
+
+
+ @Test
+ public void testWhitespaceAroundWeight() throws Exception {
+ List<AcceptEncoding> actual = parse("gzip ; q=0.5 , br ;q=0");
+
+ Assert.assertEquals(2, actual.size());
+ assertEntry(actual.get(0), "gzip", 0.5);
+ assertEntry(actual.get(1), "br", 0);
+ }
+
+
+ @Test
+ public void testReadWeightNoWeight() throws Exception {
+ Assert.assertEquals(1, HttpParser.readWeight(new StringReader(""),
','), DELTA);
+ Assert.assertEquals(1, HttpParser.readWeight(new StringReader(","),
','), DELTA);
+ }
+
+
+ @Test
+ public void testReadWeightValid() throws Exception {
+ Assert.assertEquals(0, HttpParser.readWeight(new StringReader("q=0"),
','), DELTA);
+ Assert.assertEquals(0.5, HttpParser.readWeight(new
StringReader("q=0.5"), ','), DELTA);
+ Assert.assertEquals(1, HttpParser.readWeight(new StringReader("q=1"),
','), DELTA);
+ }
+
+
+ /*
+ * Malformed weights must be reported as -1 so they are distinguishable
from an explicit weight of zero.
+ */
+ @Test
+ public void testReadWeightMalformed() throws Exception {
+ String[] malformed = new String[] { "x=0.5", "q", "q:0.5", "q=abc",
"q=2", "q=1.5", "q=1.001", "q=0.5x" };
+
+ for (String weight : malformed) {
+ Assert.assertEquals(weight, -1, HttpParser.readWeight(new
StringReader(weight), ','), DELTA);
+ }
+ }
+
+
+ private static List<AcceptEncoding> parse(String header) throws
IOException {
+ return AcceptEncoding.parse(new StringReader(header));
+ }
+
+
+ private static void assertEntry(AcceptEncoding entry, String encoding,
double quality) {
+ Assert.assertEquals(encoding, entry.getEncoding());
+ Assert.assertEquals(quality, entry.getQuality(), DELTA);
+ }
+}
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index cef0211303..e1f473b808 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -242,6 +242,10 @@
Refactor the default Servlet <code>Accept-Encoding</code> handling to
use the common HTTP header parser for processing these headers. (markt)
</fix>
+ <fix>
+ Connector compression will now enable gzip compression with a request
+ header of the form <code>Accept-Encoding: *</code>. (markt)
+ </fix>
</changelog>
</subsection>
<subsection name="Coyote">
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]