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

remm 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 ac14646dba Update with transfer-encoding support
ac14646dba is described below

commit ac14646dba3221e8cd99c920ddcf0cec809c1fbe
Author: remm <r...@apache.org>
AuthorDate: Thu Feb 20 18:57:39 2025 +0100

    Update with transfer-encoding support
    
    Although there is no client browser support, and there may never be any,
    Transfer-Encoding is the right way to do compression the way Tomcat does
    it in GzipOutputFilter.
    This will be used if the client sends a TE: gzip header.
---
 java/org/apache/coyote/CompressionConfig.java     | 59 ++++++++++++----
 java/org/apache/tomcat/util/http/parser/TE.java   | 84 +++++++++++++++++++++++
 test/org/apache/coyote/TestCompressionConfig.java | 69 ++++++++++++++-----
 webapps/docs/changelog.xml                        |  5 ++
 4 files changed, 187 insertions(+), 30 deletions(-)

diff --git a/java/org/apache/coyote/CompressionConfig.java 
b/java/org/apache/coyote/CompressionConfig.java
index c7fe685f29..7bd2c186db 100644
--- a/java/org/apache/coyote/CompressionConfig.java
+++ b/java/org/apache/coyote/CompressionConfig.java
@@ -32,6 +32,7 @@ import org.apache.tomcat.util.buf.MessageBytes;
 import org.apache.tomcat.util.http.MimeHeaders;
 import org.apache.tomcat.util.http.ResponseUtil;
 import org.apache.tomcat.util.http.parser.AcceptEncoding;
+import org.apache.tomcat.util.http.parser.TE;
 import org.apache.tomcat.util.http.parser.TokenList;
 import org.apache.tomcat.util.res.StringManager;
 
@@ -219,6 +220,8 @@ public class CompressionConfig {
             return false;
         }
 
+        boolean useTE = false;
+
         MimeHeaders responseHeaders = response.getMimeHeaders();
 
         // Check if content is not already compressed
@@ -267,32 +270,55 @@ public class CompressionConfig {
             }
         }
 
-        // If processing reaches this far, the response might be compressed.
-        // 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.
-        Enumeration<String> headerValues = 
request.getMimeHeaders().values("accept-encoding");
+        Enumeration<String> headerValues = 
request.getMimeHeaders().values("TE");
         boolean foundGzip = false;
+        // TE and accept-encoding seem to have equivalent syntax
         while (!foundGzip && headerValues.hasMoreElements()) {
-            List<AcceptEncoding> acceptEncodings = null;
+            List<TE> tes = null;
             try {
-                acceptEncodings = AcceptEncoding.parse(new 
StringReader(headerValues.nextElement()));
+                tes = TE.parse(new StringReader(headerValues.nextElement()));
             } catch (IOException ioe) {
                 // If there is a problem reading the header, disable 
compression
                 return false;
             }
 
-            for (AcceptEncoding acceptEncoding : acceptEncodings) {
-                if ("gzip".equalsIgnoreCase(acceptEncoding.getEncoding())) {
+            for (TE te : tes) {
+                if ("gzip".equalsIgnoreCase(te.getEncoding())) {
+                    useTE = true;
                     foundGzip = true;
                     break;
                 }
             }
         }
 
+        if (!useTE) {
+            // If processing reaches this far, the response might be 
compressed.
+            // 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.
+            headerValues = request.getMimeHeaders().values("accept-encoding");
+            foundGzip = false;
+            while (!foundGzip && headerValues.hasMoreElements()) {
+                List<AcceptEncoding> acceptEncodings = null;
+                try {
+                    acceptEncodings = AcceptEncoding.parse(new 
StringReader(headerValues.nextElement()));
+                } catch (IOException ioe) {
+                    // If there is a problem reading the header, disable 
compression
+                    return false;
+                }
+
+                for (AcceptEncoding acceptEncoding : acceptEncodings) {
+                    if ("gzip".equalsIgnoreCase(acceptEncoding.getEncoding())) 
{
+                        foundGzip = true;
+                        break;
+                    }
+                }
+            }
+        }
+
         if (!foundGzip) {
             return false;
         }
@@ -316,8 +342,13 @@ public class CompressionConfig {
 
         // Compressed content length is unknown so mark it as such.
         response.setContentLength(-1);
-        // Configure the content encoding for compressed content
-        responseHeaders.setValue("Content-Encoding").setString("gzip");
+        if (useTE) {
+            // Configure the transfer encoding for compressed content
+            responseHeaders.addValue("Transfer-Encoding").setString("gzip");
+        } else {
+            // Configure the content encoding for compressed content
+            responseHeaders.setValue("Content-Encoding").setString("gzip");
+        }
 
         return true;
     }
diff --git a/java/org/apache/tomcat/util/http/parser/TE.java 
b/java/org/apache/tomcat/util/http/parser/TE.java
new file mode 100644
index 0000000000..b2792448fa
--- /dev/null
+++ b/java/org/apache/tomcat/util/http/parser/TE.java
@@ -0,0 +1,84 @@
+/*
+ *  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.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class TE {
+
+    private final String encoding;
+    private final Map<String,String> parameters;
+
+    protected TE(String encoding, Map<String,String> parameters) {
+        this.encoding = encoding;
+        this.parameters = parameters;
+    }
+
+    public String getEncoding() {
+        return encoding;
+    }
+
+    public Map<String,String> getParameters() {
+        return parameters;
+    }
+
+
+    public static List<TE> parse(StringReader input) throws IOException {
+
+        List<TE> result = new ArrayList<>();
+
+        do {
+            String encoding = HttpParser.readToken(input);
+            if (encoding == null) {
+                // Invalid encoding, skip to the next one
+                HttpParser.skipUntil(input, 0, ',');
+                continue;
+            }
+
+            if (encoding.length() == 0) {
+                // No more data to read
+                break;
+            }
+
+            Map<String,String> parameters = null;
+
+            // See if a quality has been provided
+            while (HttpParser.skipConstant(input, ";") == SkipResult.FOUND) {
+                String name = HttpParser.readToken(input);
+                String value = null;
+                if (HttpParser.skipConstant(input, "=") == SkipResult.FOUND) {
+                    value = HttpParser.readTokenOrQuotedString(input, true);
+                }
+                if (name != null && value != null) {
+                    if (parameters == null) {
+                        parameters = new HashMap<>();
+                    }
+                    parameters.put(name, value);
+                }
+            }
+
+            result.add(new TE(encoding, parameters));
+        } while (true);
+
+        return result;
+    }
+}
diff --git a/test/org/apache/coyote/TestCompressionConfig.java 
b/test/org/apache/coyote/TestCompressionConfig.java
index 71b1c00d93..86a6f702d9 100644
--- a/test/org/apache/coyote/TestCompressionConfig.java
+++ b/test/org/apache/coyote/TestCompressionConfig.java
@@ -29,24 +29,39 @@ 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}]")
+    @Parameterized.Parameters(name = "{index}: accept-encoding[{0}], ETag 
[{1}], NoCompressionStrongETag[{2}], compress[{3}], useTE[{4}]")
     public static Collection<Object[]> parameters() {
         List<Object[]> parameterSets = new ArrayList<>();
 
-        parameterSets.add(new Object[] { new String[] {  },              null, 
Boolean.TRUE,  Boolean.FALSE });
-        parameterSets.add(new Object[] { new String[] { "gzip" },        null, 
Boolean.TRUE,  Boolean.TRUE });
-        parameterSets.add(new Object[] { new String[] { "xgzip" },       null, 
Boolean.TRUE,  Boolean.FALSE });
-        parameterSets.add(new Object[] { new String[] { "<>gzip" },      null, 
Boolean.TRUE,  Boolean.FALSE });
-        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.TRUE,  Boolean.FALSE, Boolean.FALSE });
+        parameterSets.add(new Object[] { new String[] { "gzip" },        null, 
Boolean.TRUE,  Boolean.TRUE,  Boolean.FALSE });
+        parameterSets.add(new Object[] { new String[] { "xgzip" },       null, 
Boolean.TRUE,  Boolean.FALSE, Boolean.FALSE });
+        parameterSets.add(new Object[] { new String[] { "<>gzip" },      null, 
Boolean.TRUE,  Boolean.FALSE, Boolean.FALSE });
+        parameterSets.add(new Object[] { new String[] { "foo", "gzip" }, null, 
Boolean.TRUE,  Boolean.TRUE,  Boolean.FALSE });
+        parameterSets.add(new Object[] { new String[] { "<>", "gzip" },  null, 
Boolean.TRUE,  Boolean.TRUE,  Boolean.FALSE });
 
-        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.FALSE });
+        parameterSets.add(new Object[] { new String[] { "gzip" },        null, 
Boolean.TRUE,  Boolean.TRUE,  Boolean.FALSE });
+        parameterSets.add(new Object[] { new String[] { "gzip" },        "W/", 
Boolean.TRUE,  Boolean.TRUE,  Boolean.FALSE });
+        parameterSets.add(new Object[] { new String[] { "gzip" },        "XX", 
Boolean.TRUE,  Boolean.FALSE, Boolean.FALSE });
 
-        parameterSets.add(new Object[] { new String[] { "gzip" },        null, 
Boolean.FALSE, Boolean.TRUE });
-        parameterSets.add(new Object[] { new String[] { "gzip" },        "W/", 
Boolean.FALSE, Boolean.TRUE });
-        parameterSets.add(new Object[] { new String[] { "gzip" },        "XX", 
Boolean.FALSE, Boolean.TRUE });
+        parameterSets.add(new Object[] { new String[] { "gzip" },        null, 
Boolean.FALSE, Boolean.TRUE,  Boolean.FALSE });
+        parameterSets.add(new Object[] { new String[] { "gzip" },        "W/", 
Boolean.FALSE, Boolean.TRUE,  Boolean.FALSE });
+        parameterSets.add(new Object[] { new String[] { "gzip" },        "XX", 
Boolean.FALSE, Boolean.TRUE,  Boolean.FALSE });
+
+        parameterSets.add(new Object[] { new String[] {  },              null, 
Boolean.TRUE,  Boolean.FALSE, Boolean.TRUE });
+        parameterSets.add(new Object[] { new String[] { "gzip" },        null, 
Boolean.TRUE,  Boolean.TRUE,  Boolean.TRUE });
+        parameterSets.add(new Object[] { new String[] { "xgzip" },       null, 
Boolean.TRUE,  Boolean.FALSE, Boolean.TRUE });
+        parameterSets.add(new Object[] { new String[] { "<>gzip" },      null, 
Boolean.TRUE,  Boolean.FALSE, Boolean.TRUE });
+        parameterSets.add(new Object[] { new String[] { "foo", "gzip" }, null, 
Boolean.TRUE,  Boolean.TRUE,  Boolean.TRUE });
+        parameterSets.add(new Object[] { new String[] { "<>", "gzip" },  null, 
Boolean.TRUE,  Boolean.TRUE,  Boolean.TRUE });
+
+        parameterSets.add(new Object[] { new String[] { "gzip" },        null, 
Boolean.TRUE,  Boolean.TRUE,  Boolean.TRUE });
+        parameterSets.add(new Object[] { new String[] { "gzip" },        "W/", 
Boolean.TRUE,  Boolean.TRUE,  Boolean.TRUE });
+        parameterSets.add(new Object[] { new String[] { "gzip" },        "XX", 
Boolean.TRUE,  Boolean.FALSE, Boolean.TRUE });
+
+        parameterSets.add(new Object[] { new String[] { "gzip" },        null, 
Boolean.FALSE, Boolean.TRUE,  Boolean.TRUE });
+        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;
     }
@@ -59,6 +74,8 @@ public class TestCompressionConfig {
     public Boolean noCompressionStrongETag;
     @Parameter(3)
     public Boolean compress;
+    @Parameter(4)
+    public Boolean useTE;
 
     @SuppressWarnings("deprecation")
     @Test
@@ -73,14 +90,34 @@ public class TestCompressionConfig {
         Response response = new Response();
 
         for (String header : headers) {
-            
request.getMimeHeaders().addValue("accept-encoding").setString(header);
+            if (useTE.booleanValue()) {
+                request.getMimeHeaders().addValue("TE").setString(header);
+            } else {
+                
request.getMimeHeaders().addValue("accept-encoding").setString(header);
+            }
         }
 
         if (eTag != null) {
             response.getMimeHeaders().addValue("ETag").setString(eTag);
         }
 
-
-        Assert.assertEquals(compress, 
Boolean.valueOf(compressionConfig.useCompression(request, response)));
+        boolean useCompression = compressionConfig.useCompression(request, 
response);
+        Assert.assertEquals(compress, Boolean.valueOf(useCompression));
+
+        if (useTE.booleanValue()) {
+            
Assert.assertNull(response.getMimeHeaders().getHeader("Content-Encoding"));
+            if (useCompression) {
+                Assert.assertEquals("gzip", 
response.getMimeHeaders().getHeader("Transfer-Encoding"));
+            } else {
+                
Assert.assertNull(response.getMimeHeaders().getHeader("Transfer-Encoding"));
+            }
+        } else {
+            
Assert.assertNull(response.getMimeHeaders().getHeader("Transfer-Encoding"));
+            if (useCompression) {
+                Assert.assertEquals("gzip", 
response.getMimeHeaders().getHeader("Content-Encoding"));
+            } else {
+                
Assert.assertNull(response.getMimeHeaders().getHeader("Content-Encoding"));
+            }
+        }
     }
 }
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 71ed6b28ba..75295317af 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -134,6 +134,11 @@
         compressed using <code>compress</code>, <code>deflate</code> or
         <code>zstd</code>. (remm)
       </fix>
+      <update>
+        Use <code>Transfer-Encoding</code> for compression rather than
+        <code>Content-Encoding</code> if the client submits a <code>TE</code>
+        header containing <code>gzip</code>. (remm)
+      </update>
     </changelog>
   </subsection>
   <subsection name="Other">


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org
For additional commands, e-mail: dev-h...@tomcat.apache.org

Reply via email to