Skip to content

Commit b46415f

Browse files
committed
[UNDERTOW-2697] Handle properly followup values after Hpack.encode overflow
1 parent c8299de commit b46415f

2 files changed

Lines changed: 106 additions & 3 deletions

File tree

core/src/main/java/io/undertow/protocols/http2/HpackEncoder.java

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,9 @@ public boolean shouldUseHuffman(HttpString header) {
9595

9696
private byte[] overflowData;
9797
private int overflowPos;
98-
private int overflowLength;
98+
private int overflowLength = -1;
99+
// hold last iterator intex over values, its used in case of overflow to kickstart from next entry if there is more than one
100+
private int headersValueIterator = -1;
99101

100102
static {
101103
Map<HttpString, TableEntry[]> map = new HashMap<>();
@@ -151,6 +153,7 @@ public State encode(HeaderMap headers, ByteBuffer target) {
151153
target.put(overflowData[i]);
152154
}
153155
overflowData = null;
156+
overflowLength = -1;
154157
}
155158

156159
long it = headersIterator;
@@ -163,7 +166,9 @@ public State encode(HeaderMap headers, ByteBuffer target) {
163166
if (headers != currentHeaders) {
164167
throw new IllegalStateException();
165168
}
166-
it = headers.fiNext(it);
169+
if( this.headersValueIterator == -1) {
170+
it = headers.fiNext(it);
171+
}
167172
}
168173
while (it != -1) {
169174
HeaderValues values = headers.fiCurrent(it);
@@ -182,7 +187,10 @@ public State encode(HeaderMap headers, ByteBuffer target) {
182187
skip = true;
183188
}
184189
if (!skip) {
185-
for (int i = 0; i < values.size(); ++i) {
190+
//this is done to restart after overflow.
191+
int valuesStartIndex = this.headersValueIterator == -1 ? 0 : this.headersValueIterator + 1;
192+
this.headersValueIterator = -1;
193+
for (int i = valuesStartIndex; i < values.size(); ++i) {
186194

187195
HttpString headerName = values.getHeaderName();
188196
int required = 11 + headerName.length(); //we use 11 to make sure we have enough room for the variable length itegers
@@ -242,6 +250,10 @@ public State encode(HeaderMap headers, ByteBuffer target) {
242250
if(overflowing) {
243251
this.headersIterator = it;
244252
this.overflowLength = current.position();
253+
if(values.size() > 1 && values.size() > i) {
254+
this.headersValueIterator = i;
255+
//set this only in case we have more, otherwise single entry will be handled properly.
256+
}
245257
return State.OVERFLOW;
246258
}
247259

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
* JBoss, Home of Professional Open Source.
3+
* Copyright 2026 Red Hat, Inc., and individual contributors
4+
* as indicated by the @author tags.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
package io.undertow.protocols.http2;
20+
21+
import io.undertow.protocols.http2.HpackDecoder.HeaderEmitter;
22+
import io.undertow.testutils.category.UnitTest;
23+
import io.undertow.util.HeaderMap;
24+
import io.undertow.util.HttpString;
25+
26+
import org.junit.Assert;
27+
import org.junit.Test;
28+
import org.junit.experimental.categories.Category;
29+
30+
import java.nio.ByteBuffer;
31+
import java.util.Arrays;
32+
import java.util.concurrent.atomic.AtomicInteger;
33+
34+
/**
35+
* @author Kanatoko
36+
*/
37+
@Category(UnitTest.class)
38+
public class HpackOverflowUnitTestCase {
39+
40+
@Test
41+
public void testStringLiteralContainingEOS() throws HpackException {
42+
final int countOfSameNames = testImpl("X-Header-1", "X-Header-1");
43+
final int countOfDifferentNames = testImpl("X-Header-1", "X-Header-2");
44+
Assert.assertEquals(2, countOfSameNames);
45+
Assert.assertEquals(2, countOfDifferentNames);
46+
}
47+
48+
private static int testImpl(final String name1, final String name2) {
49+
final HeaderMap headerMap = new HeaderMap();
50+
headerMap.add(new HttpString(name1), getStr(300, (byte) 0x41));// overflow
51+
headerMap.add(new HttpString(name2), getStr(150, (byte) 0x42));// skipped if name1 equals name2
52+
final HpackEncoder encoder = new HpackEncoder(4096);
53+
final ByteBuffer buffer1 = ByteBuffer.allocate(256);
54+
55+
// overflow
56+
HpackEncoder.State result = encoder.encode(headerMap, buffer1);
57+
58+
Assert.assertEquals(HpackEncoder.State.OVERFLOW, result);
59+
final ByteBuffer buffer2 = ByteBuffer.allocate(512);
60+
61+
// complete
62+
result = encoder.encode(headerMap, buffer2);
63+
Assert.assertEquals(HpackEncoder.State.COMPLETE, result);
64+
65+
final HpackDecoder decoder = new HpackDecoder(4096);
66+
67+
final AtomicInteger count = new AtomicInteger();
68+
try {
69+
buffer2.flip();
70+
decoder.setHeaderEmitter(new HeaderEmitter() {
71+
72+
@Override
73+
public void emitHeader(HttpString name, String value, boolean neverIndex) throws HpackException {
74+
count.incrementAndGet();
75+
76+
}
77+
});
78+
decoder.decode(buffer2, false);
79+
} catch (HpackException e) {
80+
e.printStackTrace();
81+
}
82+
return count.get();
83+
}
84+
85+
private static String getStr(final int length, byte content) {
86+
final byte[] buf = new byte[length];
87+
Arrays.fill(buf, content);
88+
return new String(buf);
89+
}
90+
91+
}

0 commit comments

Comments
 (0)