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

markt 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 7b51ac0f20 Optimise conversion of HTTP/2 header field names to lower 
case
7b51ac0f20 is described below

commit 7b51ac0f205a0a7906d8c1fcadc2065c7fca76e1
Author: Mark Thomas <[email protected]>
AuthorDate: Fri Apr 17 11:15:16 2026 +0100

    Optimise conversion of HTTP/2 header field names to lower case
---
 java/org/apache/coyote/http2/HPackHuffman.java     | 28 +++++++++++++++++-----
 java/org/apache/coyote/http2/Hpack.java            |  7 ++++--
 java/org/apache/coyote/http2/HpackEncoder.java     | 19 +++++++++++----
 test/org/apache/coyote/http2/TestHPackHuffman.java |  2 +-
 webapps/docs/changelog.xml                         |  4 ++++
 5 files changed, 46 insertions(+), 14 deletions(-)

diff --git a/java/org/apache/coyote/http2/HPackHuffman.java 
b/java/org/apache/coyote/http2/HPackHuffman.java
index 726dbba9a5..0b669a7590 100644
--- a/java/org/apache/coyote/http2/HPackHuffman.java
+++ b/java/org/apache/coyote/http2/HPackHuffman.java
@@ -19,6 +19,7 @@ package org.apache.coyote.http2;
 import java.nio.ByteBuffer;
 import java.util.Arrays;
 import java.util.HashSet;
+import java.util.Locale;
 import java.util.Set;
 
 import org.apache.tomcat.util.res.StringManager;
@@ -437,8 +438,29 @@ public class HPackHuffman {
      * @param forceLowercase If the string should be encoded in lower case
      *
      * @return true if encoding succeeded
+     *
+     * @deprecated Unused. This method will be removed in Tomcat 12 onwards.
      */
+    @Deprecated
     public static boolean encode(ByteBuffer buffer, String toEncode, boolean 
forceLowercase) {
+        if (forceLowercase) {
+            return encode(buffer, toEncode.toLowerCase(Locale.ENGLISH));
+        } else {
+            return encode(buffer, toEncode);
+        }
+    }
+
+
+    /**
+     * Encodes the given string into the buffer. If there is not enough space 
in the buffer, or the encoded version is
+     * bigger than the original it will return false and not modify the 
buffers position.
+     *
+     * @param buffer         The buffer to encode into
+     * @param toEncode       The string to encode
+     *
+     * @return true if encoding succeeded
+     */
+    public static boolean encode(ByteBuffer buffer, String toEncode) {
         if (buffer.remaining() <= toEncode.length()) {
             return false;
         }
@@ -453,9 +475,6 @@ public class HPackHuffman {
                 throw new IllegalArgumentException(
                         sm.getString("hpack.invalidCharacter", 
Character.toString(c), Integer.valueOf(c)));
             }
-            if (forceLowercase) {
-                c = Hpack.toLower(c);
-            }
             HuffmanCode code = HUFFMAN_CODES[c];
             length += code.length;
         }
@@ -469,9 +488,6 @@ public class HPackHuffman {
         byte currentBufferByte = 0;
         for (int i = 0; i < toEncode.length(); ++i) {
             char c = toEncode.charAt(i);
-            if (forceLowercase) {
-                c = Hpack.toLower(c);
-            }
             HuffmanCode code = HUFFMAN_CODES[c];
             if (code.length + bytePos <= 8) {
                 // it fits in the current byte
diff --git a/java/org/apache/coyote/http2/Hpack.java 
b/java/org/apache/coyote/http2/Hpack.java
index 238a1a3720..cbae7e78a7 100644
--- a/java/org/apache/coyote/http2/Hpack.java
+++ b/java/org/apache/coyote/http2/Hpack.java
@@ -217,7 +217,10 @@ final class Hpack {
         }
     }
 
-
+    /*
+     * Unused. Will be removed in Tomcat 12 onwards.
+     */
+    @Deprecated
     static char toLower(char c) {
         if (c >= 'A' && c <= 'Z') {
             return (char) (c + LOWER_DIFF);
@@ -225,7 +228,7 @@ final class Hpack {
         return c;
     }
 
+
     private Hpack() {
     }
-
 }
diff --git a/java/org/apache/coyote/http2/HpackEncoder.java 
b/java/org/apache/coyote/http2/HpackEncoder.java
index 391423ea8b..e4b99c5429 100644
--- a/java/org/apache/coyote/http2/HpackEncoder.java
+++ b/java/org/apache/coyote/http2/HpackEncoder.java
@@ -136,8 +136,11 @@ class HpackEncoder {
             }
         }
         while (it < currentHeaders.size()) {
-            // FIXME: Review lowercase policy
-            String headerName = 
headers.getName(it).toString().toLowerCase(Locale.US);
+            /*
+             * Need to ensure header names are lower case from this point 
onwards as table lookups etc. are
+             * case-sensitive.
+             */
+            String headerName = 
headers.getName(it).toString().toLowerCase(Locale.ENGLISH);
             boolean skip = false;
             if (firstPass) {
                 if (headerName.charAt(0) != ':') {
@@ -211,23 +214,29 @@ class HpackEncoder {
         return State.COMPLETE;
     }
 
+    /*
+     * headerName must be lower case by the time this method is called.
+     *
+     * The exception to the above rule is test cases which may deliberately 
use some upper case characters to test how
+     * Tomcat responds to such invalid input.
+     */
     private void writeHuffmanEncodableName(ByteBuffer target, String 
headerName) {
         if (hpackHeaderFunction.shouldUseHuffman(headerName)) {
-            if (HPackHuffman.encode(target, headerName, true)) {
+            if (HPackHuffman.encode(target, headerName)) {
                 return;
             }
         }
         target.put((byte) 0); // to use encodeInteger we need to place the 
first byte in the buffer.
         Hpack.encodeInteger(target, headerName.length(), 7);
         for (int j = 0; j < headerName.length(); ++j) {
-            target.put((byte) Hpack.toLower(headerName.charAt(j)));
+            target.put((byte) headerName.charAt(j));
         }
 
     }
 
     private void writeHuffmanEncodableValue(ByteBuffer target, String 
headerName, String val) {
         if (hpackHeaderFunction.shouldUseHuffman(headerName, val)) {
-            if (!HPackHuffman.encode(target, val, false)) {
+            if (!HPackHuffman.encode(target, val)) {
                 writeValueString(target, val);
             }
         } else {
diff --git a/test/org/apache/coyote/http2/TestHPackHuffman.java 
b/test/org/apache/coyote/http2/TestHPackHuffman.java
index 7b970d499b..5aaf6ac8f9 100644
--- a/test/org/apache/coyote/http2/TestHPackHuffman.java
+++ b/test/org/apache/coyote/http2/TestHPackHuffman.java
@@ -31,7 +31,7 @@ public class TestHPackHuffman {
     public void testValueLeftBrace() throws Exception {
         ByteBuffer buf = ByteBuffer.allocate(10);
         String data = "x-value{";
-        HPackHuffman.encode(buf, data, false);
+        HPackHuffman.encode(buf, data);
 
         buf.flip();
         // Remove the header byte (in Tomcat this is parsed before the bytes 
are passed to the HPACK decoder)
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 891a68f7bc..7b84f8059a 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -175,6 +175,10 @@
         decoding that could result in a valid header triggering an unexpected
         connection close. (markt)
       </fix>
+      <fix>
+        Refactor HTTP/2 HPACK encoding so field names are only converted to
+        lower case once during the encoding process. (markt)
+      </fix>
     </changelog>
   </subsection>
   <subsection name="Other">


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

Reply via email to