Skip to content

Commit 489ddc5

Browse files
committed
Optimise conversion of HTTP/2 header field names to lower case
1 parent 2a4656d commit 489ddc5

5 files changed

Lines changed: 46 additions & 14 deletions

File tree

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

Lines changed: 22 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,29 @@ 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+
if (forceLowercase) {
447+
return encode(buffer, toEncode.toLowerCase(Locale.ENGLISH));
448+
} else {
449+
return encode(buffer, toEncode);
450+
}
451+
}
452+
453+
454+
/**
455+
* Encodes the given string into the buffer. If there is not enough space in the buffer, or the encoded version is
456+
* bigger than the original it will return false and not modify the buffers position.
457+
*
458+
* @param buffer The buffer to encode into
459+
* @param toEncode The string to encode
460+
*
461+
* @return true if encoding succeeded
462+
*/
463+
public static boolean encode(ByteBuffer buffer, String toEncode) {
442464
if (buffer.remaining() <= toEncode.length()) {
443465
return false;
444466
}
@@ -453,9 +475,6 @@ public static boolean encode(ByteBuffer buffer, String toEncode, boolean forceLo
453475
throw new IllegalArgumentException(
454476
sm.getString("hpack.invalidCharacter", Character.toString(c), Integer.valueOf(c)));
455477
}
456-
if (forceLowercase) {
457-
c = Hpack.toLower(c);
458-
}
459478
HuffmanCode code = HUFFMAN_CODES[c];
460479
length += code.length;
461480
}
@@ -469,9 +488,6 @@ public static boolean encode(ByteBuffer buffer, String toEncode, boolean forceLo
469488
byte currentBufferByte = 0;
470489
for (int i = 0; i < toEncode.length(); ++i) {
471490
char c = toEncode.charAt(i);
472-
if (forceLowercase) {
473-
c = Hpack.toLower(c);
474-
}
475491
HuffmanCode code = HUFFMAN_CODES[c];
476492
if (code.length + bytePos <= 8) {
477493
// 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
@@ -136,8 +136,11 @@ State encode(MimeHeaders headers, ByteBuffer target) {
136136
}
137137
}
138138
while (it < currentHeaders.size()) {
139-
// FIXME: Review lowercase policy
140-
String headerName = headers.getName(it).toString().toLowerCase(Locale.US);
139+
/*
140+
* Need to ensure header names are lower case from this point onwards as table lookups etc. are
141+
* case-sensitive.
142+
*/
143+
String headerName = headers.getName(it).toString().toLowerCase(Locale.ENGLISH);
141144
boolean skip = false;
142145
if (firstPass) {
143146
if (headerName.charAt(0) != ':') {
@@ -211,23 +214,29 @@ State encode(MimeHeaders headers, ByteBuffer target) {
211214
return State.COMPLETE;
212215
}
213216

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

226235
}
227236

228237
private void writeHuffmanEncodableValue(ByteBuffer target, String headerName, String val) {
229238
if (hpackHeaderFunction.shouldUseHuffman(headerName, val)) {
230-
if (!HPackHuffman.encode(target, val, false)) {
239+
if (!HPackHuffman.encode(target, val)) {
231240
writeValueString(target, val);
232241
}
233242
} 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
@@ -175,6 +175,10 @@
175175
decoding that could result in a valid header triggering an unexpected
176176
connection close. (markt)
177177
</fix>
178+
<fix>
179+
Refactor HTTP/2 HPACK encoding so field names are only converted to
180+
lower case once during the encoding process. (markt)
181+
</fix>
178182
</changelog>
179183
</subsection>
180184
<subsection name="Other">

0 commit comments

Comments
 (0)