This is an automated email from the ASF dual-hosted git repository.

markt-asf pushed a commit to branch 10.1.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/10.1.x by this push:
     new 4f78109ef0 Add (AI generated) tests for Default Servlet / 
Accept-Encoding and fix the failures these tests exposed.
4f78109ef0 is described below

commit 4f78109ef0fdf2287060be80a8fb41dfc56475bb
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 878f391c3c..bfb2f7697a 100644
--- a/java/org/apache/catalina/servlets/DefaultServlet.java
+++ b/java/org/apache/catalina/servlets/DefaultServlet.java
@@ -41,8 +41,10 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Comparator;
 import java.util.Enumeration;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
+import java.util.Set;
 import java.util.TreeSet;
 import java.util.function.Function;
 
@@ -1512,20 +1514,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) {
@@ -1541,7 +1559,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 6d903bd13b..f12bfed31b 100644
--- a/java/org/apache/coyote/CompressionConfig.java
+++ b/java/org/apache/coyote/CompressionConfig.java
@@ -74,9 +74,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")
      */
@@ -86,7 +85,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);
                 }
             }
@@ -169,8 +168,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
      */
@@ -357,11 +355,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()));
@@ -371,9 +370,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 c325f7955e..f0271bf9f3 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 be336ec7e6..97da348ab1 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}], compress[{2}], useTE[{3}]")
+    @Parameterized.Parameters(name = "{index}: accept-encoding[{4}], ETag 
[{1}], compress[{2}], useTE[{3}]")
     public static Collection<Object[]> parameters() {
         List<Object[]> parameterSets = new ArrayList<>();
 
@@ -59,7 +60,54 @@ public class TestCompressionConfig {
         parameterSets.add(new Object[] { new String[] { "foobar;foo=bar, 
gzip;bla=\"quoted\"" }, "XX", 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.FALSE, useTE });
+            parameterSets.add(new Object[] { new String[] { "gzip;q=0.0" }, 
null, Boolean.FALSE, useTE });
+            parameterSets.add(new Object[] { new String[] { "gzip;q=abc" }, 
null, Boolean.FALSE, useTE });
+            parameterSets.add(new Object[] { new String[] { "foo", "gzip;q=0" 
}, null, Boolean.FALSE, useTE });
+            parameterSets.add(new Object[] { new String[] { "gzip;q=abc", 
"gzip" }, null, Boolean.TRUE, useTE });
+            parameterSets.add(new Object[] { new String[] { "gzip;q=0.5" }, 
null, 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.FALSE });
+        parameterSets.add(new Object[] { new String[] { "*;q=0.5" }, null, 
Boolean.TRUE, Boolean.FALSE });
+        parameterSets.add(new Object[] { new String[] { "foo, *" }, null, 
Boolean.TRUE, Boolean.FALSE });
+        parameterSets.add(new Object[] { new String[] { "*;q=0" }, null, 
Boolean.FALSE, Boolean.FALSE });
+        parameterSets.add(new Object[] { new String[] { "*;q=abc" }, null, 
Boolean.FALSE, Boolean.FALSE });
+        // An explicit entry takes precedence over "*", wherever it appears
+        parameterSets.add(new Object[] { new String[] { "gzip;q=0, *" }, null, 
Boolean.FALSE, Boolean.FALSE });
+        parameterSets.add(new Object[] { new String[] { "*, gzip;q=0" }, null, 
Boolean.FALSE, Boolean.FALSE });
+        parameterSets.add(new Object[] { new String[] { "gzip;q=0", "*" }, 
null, Boolean.FALSE, Boolean.FALSE });
+        parameterSets.add(new Object[] { new String[] { "*", "gzip;q=0" }, 
null, Boolean.FALSE, Boolean.FALSE });
+        parameterSets.add(new Object[] { new String[] { "*;q=0, gzip" }, null, 
Boolean.TRUE, Boolean.FALSE });
+        parameterSets.add(new Object[] { new String[] { "*;q=0", "gzip" }, 
null, Boolean.TRUE, Boolean.FALSE });
+        parameterSets.add(new Object[] { new String[] { "gzip", "*;q=0" }, 
null, 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.FALSE, Boolean.FALSE });
+        parameterSets.add(new Object[] { new String[] { "gzip;q=0, gzip" }, 
null, Boolean.FALSE, Boolean.FALSE });
+        parameterSets.add(new Object[] { new String[] { "gzip;q=0.5, gzip;q=0" 
}, null, Boolean.FALSE, Boolean.FALSE });
+        parameterSets.add(new Object[] { new String[] { "gzip", "gzip;q=0" }, 
null, Boolean.FALSE, Boolean.FALSE });
+        parameterSets.add(new Object[] { new String[] { "gzip;q=0", "gzip" }, 
null, Boolean.FALSE, Boolean.FALSE });
+        parameterSets.add(new Object[] { new String[] { "GZIP, gzip;q=0" }, 
null, Boolean.FALSE, Boolean.FALSE });
+        // "*" is not used for the TE header
+        parameterSets.add(new Object[] { new String[] { "*" }, null, 
Boolean.FALSE, Boolean.TRUE });
+        parameterSets.add(new Object[] { new String[] { "*;q=0.5" }, null, 
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],
+                    description.toString() });
+        }
+
+        return result;
     }
 
     @Parameter(0)
@@ -70,6 +118,8 @@ public class TestCompressionConfig {
     public Boolean compress;
     @Parameter(3)
     public Boolean useTE;
+    @Parameter(4)
+    public String headersDescription;
 
     @Test
     public void testUseCompression() throws Exception {
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 bdf8cebf8d..c9a5b45189 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -253,6 +253,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]

Reply via email to