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

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

commit 638759b26eb2ae7b5760232ced65b1147ec585ef
Author: Mark Thomas <[email protected]>
AuthorDate: Fri Feb 6 18:55:30 2026 +0000

    Add an HTTP configuration setting, noCompressionEncodings
    
    This can be used to control which content encodings will not be
    compressed when compression is enabled.
    Based on pull request #914 by Long9725.
---
 java/org/apache/coyote/CompressionConfig.java      | 34 ++++++++++++--
 .../coyote/http11/AbstractHttp11Protocol.java      |  9 ++++
 test/org/apache/coyote/TestCompressionConfig.java  | 54 ++++++++++++++--------
 .../apache/coyote/http11/TestHttp11Processor.java  | 30 +++++++++---
 webapps/docs/changelog.xml                         |  6 +++
 webapps/docs/config/http.xml                       | 12 +++++
 6 files changed, 116 insertions(+), 29 deletions(-)

diff --git a/java/org/apache/coyote/CompressionConfig.java 
b/java/org/apache/coyote/CompressionConfig.java
index c48dee1b13..2cf718936b 100644
--- a/java/org/apache/coyote/CompressionConfig.java
+++ b/java/org/apache/coyote/CompressionConfig.java
@@ -19,6 +19,7 @@ package org.apache.coyote;
 import java.io.IOException;
 import java.io.StringReader;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Enumeration;
 import java.util.HashSet;
 import java.util.List;
@@ -47,6 +48,35 @@ public class CompressionConfig {
             
"text/javascript,application/javascript,application/json,application/xml";
     private String[] compressibleMimeTypes = null;
     private int compressionMinSize = 2048;
+    private Set<String> noCompressionEncodings =
+            new HashSet<>(Arrays.asList("br", "compress", "dcb", "dcz", 
"deflate", "gzip", "pack200-gzip", "zstd"));
+
+
+    public String getNoCompressionEncodings() {
+        return String.join(",", noCompressionEncodings);
+    }
+
+
+    /**
+     * 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.dflate")
+     */
+    public void setNoCompressionEncodings(String encodings) {
+        Set<String> newEncodings = new HashSet<>();
+        if (encodings != null && !encodings.isEmpty()) {
+            StringTokenizer tokens = new StringTokenizer(encodings, ",");
+            while (tokens.hasMoreTokens()) {
+                String token = tokens.nextToken().trim();
+                if(!token.isEmpty()) {
+                    newEncodings.add(token);
+                }
+            }
+        }
+        this.noCompressionEncodings = newEncodings;
+    }
 
 
     /**
@@ -210,9 +240,7 @@ public class CompressionConfig {
             if (tokens.contains("identity")) {
                 // If identity, do not do content modifications
                 useContentEncoding = false;
-            } else if (tokens.contains("br") || tokens.contains("compress") || 
tokens.contains("dcb") ||
-                    tokens.contains("dcz") || tokens.contains("deflate") || 
tokens.contains("gzip") ||
-                    tokens.contains("pack200-gzip") || 
tokens.contains("zstd")) {
+            } else if 
(noCompressionEncodings.stream().anyMatch(tokens::contains)) {
                 // Content should not be compressed twice
                 return false;
             }
diff --git a/java/org/apache/coyote/http11/AbstractHttp11Protocol.java 
b/java/org/apache/coyote/http11/AbstractHttp11Protocol.java
index 3b778a5057..f5ee4821d3 100644
--- a/java/org/apache/coyote/http11/AbstractHttp11Protocol.java
+++ b/java/org/apache/coyote/http11/AbstractHttp11Protocol.java
@@ -340,6 +340,15 @@ public abstract class AbstractHttp11Protocol<S> extends 
AbstractProtocol<S> {
     }
 
 
+    public String getNoCompressionEncodings() {
+        return compressionConfig.getNoCompressionEncodings();
+    }
+
+    public void setNoCompressionEncodings(String encodings) {
+        compressionConfig.setNoCompressionEncodings(encodings);
+    }
+
+
     public boolean useCompression(Request request, Response response) {
         return compressionConfig.useCompression(request, response);
     }
diff --git a/test/org/apache/coyote/TestCompressionConfig.java 
b/test/org/apache/coyote/TestCompressionConfig.java
index ae16c02f8f..be336ec7e6 100644
--- a/test/org/apache/coyote/TestCompressionConfig.java
+++ b/test/org/apache/coyote/TestCompressionConfig.java
@@ -17,6 +17,7 @@
 package org.apache.coyote;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
 
@@ -33,29 +34,30 @@ public class TestCompressionConfig {
     public static Collection<Object[]> parameters() {
         List<Object[]> parameterSets = new ArrayList<>();
 
-        parameterSets.add(new Object[] { new String[] {  },              null, 
Boolean.FALSE, Boolean.FALSE });
-        parameterSets.add(new Object[] { new String[] { "gzip" },        null, 
Boolean.TRUE,  Boolean.FALSE });
-        parameterSets.add(new Object[] { new String[] { "xgzip" },       null, 
Boolean.FALSE, Boolean.FALSE });
-        parameterSets.add(new Object[] { new String[] { "<>gzip" },      null, 
Boolean.FALSE, Boolean.FALSE });
-        parameterSets.add(new Object[] { new String[] { "foo", "gzip" }, null, 
Boolean.TRUE,  Boolean.FALSE });
-        parameterSets.add(new Object[] { new String[] { "<>", "gzip" },  null, 
Boolean.TRUE,  Boolean.FALSE });
+        parameterSets.add(new Object[] { new String[] {}, null, Boolean.FALSE, 
Boolean.FALSE });
+        parameterSets.add(new Object[] { new String[] { "gzip" }, null, 
Boolean.TRUE, Boolean.FALSE });
+        parameterSets.add(new Object[] { new String[] { "xgzip" }, null, 
Boolean.FALSE, Boolean.FALSE });
+        parameterSets.add(new Object[] { new String[] { "<>gzip" }, null, 
Boolean.FALSE, Boolean.FALSE });
+        parameterSets.add(new Object[] { new String[] { "foo", "gzip" }, null, 
Boolean.TRUE, Boolean.FALSE });
+        parameterSets.add(new Object[] { new String[] { "<>", "gzip" }, null, 
Boolean.TRUE, Boolean.FALSE });
 
-        parameterSets.add(new Object[] { new String[] { "gzip" },        null, 
Boolean.TRUE,  Boolean.FALSE });
-        parameterSets.add(new Object[] { new String[] { "gzip" },        "W/", 
Boolean.TRUE,  Boolean.FALSE });
-        parameterSets.add(new Object[] { new String[] { "gzip" },        "XX", 
Boolean.FALSE, Boolean.FALSE });
+        parameterSets.add(new Object[] { new String[] { "gzip" }, null, 
Boolean.TRUE, Boolean.FALSE });
+        parameterSets.add(new Object[] { new String[] { "gzip" }, "W/", 
Boolean.TRUE, Boolean.FALSE });
+        parameterSets.add(new Object[] { new String[] { "gzip" }, "XX", 
Boolean.FALSE, Boolean.FALSE });
 
-        parameterSets.add(new Object[] { new String[] {  },              null, 
Boolean.FALSE, Boolean.TRUE });
-        parameterSets.add(new Object[] { new String[] { "gzip" },        null, 
Boolean.TRUE,  Boolean.TRUE });
-        parameterSets.add(new Object[] { new String[] { "xgzip" },       null, 
Boolean.FALSE, Boolean.TRUE });
-        parameterSets.add(new Object[] { new String[] { "<>gzip" },      null, 
Boolean.FALSE, Boolean.TRUE });
-        parameterSets.add(new Object[] { new String[] { "foo", "gzip" }, null, 
Boolean.TRUE,  Boolean.TRUE });
-        parameterSets.add(new Object[] { new String[] { "<>", "gzip" },  null, 
Boolean.TRUE,  Boolean.TRUE });
+        parameterSets.add(new Object[] { new String[] {}, null, Boolean.FALSE, 
Boolean.TRUE });
+        parameterSets.add(new Object[] { new String[] { "gzip" }, null, 
Boolean.TRUE, Boolean.TRUE });
+        parameterSets.add(new Object[] { new String[] { "xgzip" }, null, 
Boolean.FALSE, Boolean.TRUE });
+        parameterSets.add(new Object[] { new String[] { "<>gzip" }, null, 
Boolean.FALSE, Boolean.TRUE });
+        parameterSets.add(new Object[] { new String[] { "foo", "gzip" }, null, 
Boolean.TRUE, Boolean.TRUE });
+        parameterSets.add(new Object[] { new String[] { "<>", "gzip" }, null, 
Boolean.TRUE, Boolean.TRUE });
 
-        parameterSets.add(new Object[] { new String[] { "gzip" },        null, 
Boolean.TRUE,  Boolean.TRUE });
-        parameterSets.add(new Object[] { new String[] { "gzip" },        "W/", 
Boolean.TRUE,  Boolean.TRUE });
-        parameterSets.add(new Object[] { new String[] { "gzip" },        "XX", 
Boolean.TRUE, Boolean.TRUE });
+        parameterSets.add(new Object[] { new String[] { "gzip" }, null, 
Boolean.TRUE, Boolean.TRUE });
+        parameterSets.add(new Object[] { new String[] { "gzip" }, "W/", 
Boolean.TRUE, Boolean.TRUE });
+        parameterSets.add(new Object[] { new String[] { "gzip" }, "XX", 
Boolean.TRUE, Boolean.TRUE });
 
-        parameterSets.add(new Object[] { new String[] { "foobar;foo=bar, 
gzip;bla=\"quoted\"" }, "XX", Boolean.TRUE, Boolean.TRUE });
+        parameterSets.add(new Object[] { new String[] { "foobar;foo=bar, 
gzip;bla=\"quoted\"" }, "XX", Boolean.TRUE,
+                Boolean.TRUE });
 
         return parameterSets;
     }
@@ -110,4 +112,18 @@ public class TestCompressionConfig {
             }
         }
     }
+
+
+    @Test
+    public void testNoCompressionEncodings() {
+        CompressionConfig config = new CompressionConfig();
+        String encodings = config.getNoCompressionEncodings();
+        Assert.assertTrue(Arrays.asList("br", "compress", "dcb", "dcz", 
"deflate", "gzip", "pack200-gzip", "zstd")
+                .stream().anyMatch(encodings::contains));
+
+        config.setNoCompressionEncodings("br");
+        String newEncodings = config.getNoCompressionEncodings();
+        Assert.assertTrue(newEncodings.contains("br"));
+        Assert.assertFalse(newEncodings.contains("gzip"));
+    }
 }
diff --git a/test/org/apache/coyote/http11/TestHttp11Processor.java 
b/test/org/apache/coyote/http11/TestHttp11Processor.java
index cc00ac4d84..fd77108f0d 100644
--- a/test/org/apache/coyote/http11/TestHttp11Processor.java
+++ b/test/org/apache/coyote/http11/TestHttp11Processor.java
@@ -32,6 +32,7 @@ import java.net.SocketAddress;
 import java.nio.CharBuffer;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -421,7 +422,7 @@ public class TestHttp11Processor extends TomcatBaseTest {
         tomcat.start();
 
         ByteChunk responseBody = new ByteChunk();
-        Map<String, List<String>> responseHeaders = new HashMap<>();
+        Map<String,List<String>> responseHeaders = new HashMap<>();
         int rc = getUrl("http://localhost:"; + getPort() + "/test", 
responseBody, responseHeaders);
 
         Assert.assertEquals(HttpServletResponse.SC_OK, rc);
@@ -445,7 +446,7 @@ public class TestHttp11Processor extends TomcatBaseTest {
         tomcat.start();
 
         ByteChunk responseBody = new ByteChunk();
-        Map<String, List<String>> responseHeaders = new HashMap<>();
+        Map<String,List<String>> responseHeaders = new HashMap<>();
         int rc = getUrl("http://localhost:"; + getPort() + "/test", 
responseBody, responseHeaders);
 
         Assert.assertEquals(HttpServletResponse.SC_OK, rc);
@@ -854,11 +855,11 @@ public class TestHttp11Processor extends TomcatBaseTest {
         tomcat.start();
 
         ByteChunk getBody = new ByteChunk();
-        Map<String, List<String>> getHeaders = new HashMap<>();
+        Map<String,List<String>> getHeaders = new HashMap<>();
         int getStatus = getUrl("http://localhost:"; + getPort() + "/test", 
getBody, getHeaders);
 
         ByteChunk headBody = new ByteChunk();
-        Map<String, List<String>> headHeaders = new HashMap<>();
+        Map<String,List<String>> headHeaders = new HashMap<>();
         int headStatus = getUrl("http://localhost:"; + getPort() + "/test", 
headBody, headHeaders);
 
         Assert.assertEquals(HttpServletResponse.SC_OK, getStatus);
@@ -997,7 +998,7 @@ public class TestHttp11Processor extends TomcatBaseTest {
         tomcat.start();
 
         ByteChunk responseBody = new ByteChunk();
-        Map<String, List<String>> responseHeaders = new HashMap<>();
+        Map<String,List<String>> responseHeaders = new HashMap<>();
         int rc = getUrl("http://localhost:"; + getPort() + "/test", 
responseBody, responseHeaders);
 
         Assert.assertEquals(HttpServletResponse.SC_RESET_CONTENT, rc);
@@ -2149,7 +2150,6 @@ public class TestHttp11Processor extends TomcatBaseTest {
     }
 
 
-
     private static class EarlyHintsServlet extends HttpServlet {
 
         private static final long serialVersionUID = 1L;
@@ -2165,6 +2165,7 @@ public class TestHttp11Processor extends TomcatBaseTest {
             this.useSendError = useSendError;
             this.errorString = errorString;
         }
+
         @Override
         protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
throws ServletException, IOException {
             resp.addHeader("Link", "</style.css>; rel=preload; as=style");
@@ -2185,4 +2186,19 @@ public class TestHttp11Processor extends TomcatBaseTest {
             resp.getWriter().write("OK");
         }
     }
-}
+
+
+    @Test
+    public void testNoCompressionEncodings() {
+        Http11NioProtocol protocol = new Http11NioProtocol();
+        String encodings = protocol.getNoCompressionEncodings();
+        Assert.assertTrue(Arrays.asList("br", "compress", "dcb", "dcz", 
"deflate", "gzip", "pack200-gzip", "zstd")
+                .stream().anyMatch(encodings::contains));
+
+        protocol.setNoCompressionEncodings("br");
+
+        String newEncodings = protocol.getNoCompressionEncodings();
+        Assert.assertTrue(newEncodings.contains("br"));
+        Assert.assertFalse(newEncodings.contains("gzip"));
+    }
+}
\ No newline at end of file
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 83d23edd62..a470fac154 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -227,6 +227,12 @@
         <bug>69938</bug>: Avoid changing the closed state of TLS channel when
         resetting it after close. (remm)
       </fix>
+      <add>
+        Add an HTTP configuration setting, <code>noCompressionEncodings</code>,
+        that can be used to control which content encodings will not be
+        compressed when compression is enabled. Based on pull request
+        <pr>914</pr> by Long9725. (markt)
+      </add>
     </changelog>
   </subsection>
   <subsection name="Jasper">
diff --git a/webapps/docs/config/http.xml b/webapps/docs/config/http.xml
index deb1ed5c3c..443183508f 100644
--- a/webapps/docs/config/http.xml
+++ b/webapps/docs/config/http.xml
@@ -651,6 +651,18 @@
       used.</p>
     </attribute>
 
+    <attribute name="noCompressionEncodings" requried="false">
+      <p>A comma-separated list of content encodings that indicate
+      already-compressed content. When the response already has a
+      <code>Content-Encoding</code> header with one of these values, 
compression
+      will not be applied to prevent double compression. This attribute is only
+      used if <strong>compression</strong> is set to <code>on</code> or
+      <code>force</code>.</p>
+      <p>If not specified, the default values is
+      <code>br,compress,dcb,dcz,deflate,gzip,pack2000-gzip,zstd</code>, which
+      includes all commonly used compression algorithms.</p>
+    </attribute>
+
     <attribute name="noCompressionUserAgents" required="false">
       <p>The value is a regular expression (using <code>java.util.regex</code>)
       matching the <code>user-agent</code> header of HTTP clients for which


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to