Skip to content

Commit 4bca305

Browse files
committed
Optimise conversion of HTTP/2 header field names to lower case
1 parent 5644b11 commit 4bca305

5 files changed

Lines changed: 42 additions & 14 deletions

File tree

java/org/apache/coyote/http2/HPackHuffman.java

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.nio.ByteBuffer;
2020
import java.util.Arrays;
2121
import java.util.HashSet;
22+
import java.util.Locale;
2223
import java.util.Set;
2324

2425
import org.apache.tomcat.util.res.StringManager;
@@ -437,8 +438,25 @@ public static void decode(ByteBuffer data, int length, StringBuilder target) thr
437438
* @param forceLowercase If the string should be encoded in lower case
438439
*
439440
* @return true if encoding succeeded
441+
*
442+
* @deprecated Unused. This method will be removed in Tomcat 12 onwards.
440443
*/
444+
@Deprecated
441445
public static boolean encode(ByteBuffer buffer, String toEncode, boolean forceLowercase) {
446+
return encode(buffer, toEncode.toLowerCase(Locale.ENGLISH));
447+
}
448+
449+
450+
/**
451+
* Encodes the given string into the buffer. If there is not enough space in the buffer, or the encoded version is
452+
* bigger than the original it will return false and not modify the buffers position.
453+
*
454+
* @param buffer The buffer to encode into
455+
* @param toEncode The string to encode
456+
*
457+
* @return true if encoding succeeded
458+
*/
459+
public static boolean encode(ByteBuffer buffer, String toEncode) {
442460
if (buffer.remaining() <= toEncode.length()) {
443461
return false;
444462
}
@@ -453,9 +471,6 @@ public static boolean encode(ByteBuffer buffer, String toEncode, boolean forceLo
453471
throw new IllegalArgumentException(
454472
sm.getString("hpack.invalidCharacter", Character.toString(c), Integer.valueOf(c)));
455473
}
456-
if (forceLowercase) {
457-
c = Hpack.toLower(c);
458-
}
459474
HuffmanCode code = HUFFMAN_CODES[c];
460475
length += code.length;
461476
}
@@ -469,9 +484,6 @@ public static boolean encode(ByteBuffer buffer, String toEncode, boolean forceLo
469484
byte currentBufferByte = 0;
470485
for (int i = 0; i < toEncode.length(); ++i) {
471486
char c = toEncode.charAt(i);
472-
if (forceLowercase) {
473-
c = Hpack.toLower(c);
474-
}
475487
HuffmanCode code = HUFFMAN_CODES[c];
476488
if (code.length + bytePos <= 8) {
477489
// it fits in the current byte

java/org/apache/coyote/http2/Hpack.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,15 +217,18 @@ static void encodeInteger(ByteBuffer source, int value, int n) {
217217
}
218218
}
219219

220-
220+
/*
221+
* Unused. Will be removed in Tomcat 12 onwards.
222+
*/
223+
@Deprecated
221224
static char toLower(char c) {
222225
if (c >= 'A' && c <= 'Z') {
223226
return (char) (c + LOWER_DIFF);
224227
}
225228
return c;
226229
}
227230

231+
228232
private Hpack() {
229233
}
230-
231234
}

java/org/apache/coyote/http2/HpackEncoder.java

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,11 @@ State encode(MimeHeaders headers, ByteBuffer target) {
133133
}
134134
}
135135
while (it < currentHeaders.size()) {
136-
// FIXME: Review lowercase policy
137-
String headerName = headers.getName(it).toString().toLowerCase(Locale.US);
136+
/*
137+
* Need to ensure header names are lower case from this point onwards as table lookups etc. are
138+
* case-sensitive.
139+
*/
140+
String headerName = headers.getName(it).toString().toLowerCase(Locale.ENGLISH);
138141
boolean skip = false;
139142
if (firstPass) {
140143
if (headerName.charAt(0) != ':') {
@@ -208,23 +211,29 @@ State encode(MimeHeaders headers, ByteBuffer target) {
208211
return State.COMPLETE;
209212
}
210213

214+
/*
215+
* headerName must be lower case by the time this method is called.
216+
*
217+
* The exception to the above rule is test cases which may deliberately use some upper case characters to test how
218+
* Tomcat responds to such invalid input.
219+
*/
211220
private void writeHuffmanEncodableName(ByteBuffer target, String headerName) {
212221
if (hpackHeaderFunction.shouldUseHuffman(headerName)) {
213-
if (HPackHuffman.encode(target, headerName, true)) {
222+
if (HPackHuffman.encode(target, headerName)) {
214223
return;
215224
}
216225
}
217226
target.put((byte) 0); // to use encodeInteger we need to place the first byte in the buffer.
218227
Hpack.encodeInteger(target, headerName.length(), 7);
219228
for (int j = 0; j < headerName.length(); ++j) {
220-
target.put((byte) Hpack.toLower(headerName.charAt(j)));
229+
target.put((byte) headerName.charAt(j));
221230
}
222231

223232
}
224233

225234
private void writeHuffmanEncodableValue(ByteBuffer target, String headerName, String val) {
226235
if (hpackHeaderFunction.shouldUseHuffman(headerName, val)) {
227-
if (!HPackHuffman.encode(target, val, false)) {
236+
if (!HPackHuffman.encode(target, val)) {
228237
writeValueString(target, val);
229238
}
230239
} else {

test/org/apache/coyote/http2/TestHPackHuffman.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public class TestHPackHuffman {
3131
public void testValueLeftBrace() throws Exception {
3232
ByteBuffer buf = ByteBuffer.allocate(10);
3333
String data = "x-value{";
34-
HPackHuffman.encode(buf, data, false);
34+
HPackHuffman.encode(buf, data);
3535

3636
buf.flip();
3737
// Remove the header byte (in Tomcat this is parsed before the bytes are passed to the HPACK decoder)

webapps/docs/changelog.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,10 @@
282282
decoding that could result in a valid header triggering an unexpected
283283
connection close. (markt)
284284
</fix>
285+
<fix>
286+
Refactor HTTP/2 HPACK encoding so field names are only converted to
287+
lower case once during the encoding process. (markt)
288+
</fix>
285289
</changelog>
286290
</subsection>
287291
<subsection name="Jasper">

0 commit comments

Comments
 (0)