Skip to content

Commit 09a80d5

Browse files
HTTP/2: enforce strict 3-digit :status pseudo-header (#639)
Replace Integer.parseInt-based parsing with a strict 3-char digit check (first digit 1..5) to reject values like "+200" and "0200".
1 parent d36d531 commit 09a80d5

2 files changed

Lines changed: 38 additions & 6 deletions

File tree

httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/DefaultH2ResponseConverter.java

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -97,12 +97,7 @@ public HttpResponse convert(final List<Header> headers) throws HttpException {
9797
if (statusText == null) {
9898
throw new ProtocolException("Mandatory response header '%s' not found", H2PseudoResponseHeaders.STATUS);
9999
}
100-
final int statusCode;
101-
try {
102-
statusCode = Integer.parseInt(statusText);
103-
} catch (final NumberFormatException ex) {
104-
throw new ProtocolException("Invalid response status: " + statusText);
105-
}
100+
final int statusCode = parseStatusCode(statusText);
106101
final HttpResponse response = new BasicHttpResponse(statusCode, null);
107102
response.setVersion(HttpVersion.HTTP_2);
108103
for (int i = 0; i < messageHeaders.size(); i++) {
@@ -149,4 +144,20 @@ public List<Header> convert(final HttpResponse message) throws HttpException {
149144
return headers;
150145
}
151146

147+
private static int parseStatusCode(final String statusText) throws ProtocolException {
148+
if (statusText.length() != 3) {
149+
throw new ProtocolException("Invalid response status: " + statusText);
150+
}
151+
152+
final char c0 = statusText.charAt(0);
153+
final char c1 = statusText.charAt(1);
154+
final char c2 = statusText.charAt(2);
155+
156+
if (c0 < '1' || c0 > '5' || c1 < '0' || c1 > '9' || c2 < '0' || c2 > '9') {
157+
throw new ProtocolException("Invalid response status: " + statusText);
158+
}
159+
160+
return (c0 - '0') * 100 + (c1 - '0') * 10 + (c2 - '0');
161+
}
162+
152163
}

httpcore5-h2/src/test/java/org/apache/hc/core5/http2/impl/TestDefaultH2ResponseConverter.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
package org.apache.hc.core5.http2.impl;
2929

3030
import java.util.Arrays;
31+
import java.util.Collections;
3132
import java.util.List;
3233

3334
import org.apache.hc.core5.http.Header;
@@ -180,5 +181,25 @@ void testConvertFromFieldsMultipleCookies() throws Exception {
180181
Assertions.assertEquals("a=b; c=d; e=f", allHeaders[1].getValue());
181182
}
182183

184+
@Test
185+
void testConvertFromFieldsStatusCodeMustBeStrictThreeDigit() {
186+
final DefaultH2ResponseConverter converter = new DefaultH2ResponseConverter();
187+
188+
// Demonstrate why Integer.parseInt(...) is insufficient (it accepts non-3-digit formats).
189+
final int parsedPlus = Assertions.assertDoesNotThrow(() -> Integer.parseInt("+200"));
190+
Assertions.assertEquals(200, parsedPlus);
191+
192+
final int parsedLeadingZero = Assertions.assertDoesNotThrow(() -> Integer.parseInt("0200"));
193+
Assertions.assertEquals(200, parsedLeadingZero);
194+
195+
// Converter must be strict: :status is exactly 3 digits, in range 100..599.
196+
Assertions.assertThrows(HttpException.class,
197+
() -> converter.convert(Collections.singletonList(new BasicHeader(":status", "+200"))));
198+
Assertions.assertThrows(HttpException.class, () -> converter.convert(Collections.singletonList(new BasicHeader(":status", "0200"))));
199+
200+
Assertions.assertThrows(HttpException.class, () -> converter.convert(Collections.singletonList(new BasicHeader(":status", "099"))));
201+
Assertions.assertThrows(HttpException.class, () -> converter.convert(Collections.singletonList(new BasicHeader(":status", "600"))));
202+
}
203+
183204
}
184205

0 commit comments

Comments
 (0)