Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More strict parsing of initial line / http headers #10058

Merged
merged 2 commits into from
Feb 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -752,13 +752,13 @@ private static String[] splitInitialLine(AppendableCharSequence sb) {
int cStart;
int cEnd;

aStart = findNonWhitespace(sb, 0);
aEnd = findWhitespace(sb, aStart);
aStart = findNonSPLenient(sb, 0);
aEnd = findSPLenient(sb, aStart);

bStart = findNonWhitespace(sb, aEnd);
bEnd = findWhitespace(sb, bStart);
bStart = findNonSPLenient(sb, aEnd);
bEnd = findSPLenient(sb, bStart);

cStart = findNonWhitespace(sb, bEnd);
cStart = findNonSPLenient(sb, bEnd);
cEnd = findEndOfString(sb);

return new String[] {
Expand All @@ -775,7 +775,7 @@ private void splitHeader(AppendableCharSequence sb) {
int valueStart;
int valueEnd;

nameStart = findNonWhitespace(sb, 0);
nameStart = findNonWhitespace(sb, 0, false);
for (nameEnd = nameStart; nameEnd < length; nameEnd ++) {
char ch = sb.charAtUnsafe(nameEnd);
// https://tools.ietf.org/html/rfc7230#section-3.2.4
Expand All @@ -792,7 +792,7 @@ private void splitHeader(AppendableCharSequence sb) {
// is done in the DefaultHttpHeaders implementation.
//
// In the case of decoding a response we will "skip" the whitespace.
(!isDecodingRequest() && Character.isWhitespace(ch))) {
(!isDecodingRequest() && isOWS(ch))) {
break;
}
}
Expand All @@ -810,7 +810,7 @@ private void splitHeader(AppendableCharSequence sb) {
}

name = sb.subStringUnsafe(nameStart, nameEnd);
valueStart = findNonWhitespace(sb, colonEnd);
valueStart = findNonWhitespace(sb, colonEnd, true);
if (valueStart == length) {
value = EMPTY_VALUE;
} else {
Expand All @@ -819,19 +819,45 @@ private void splitHeader(AppendableCharSequence sb) {
}
}

private static int findNonWhitespace(AppendableCharSequence sb, int offset) {
private static int findNonSPLenient(AppendableCharSequence sb, int offset) {
for (int result = offset; result < sb.length(); ++result) {
if (!Character.isWhitespace(sb.charAtUnsafe(result))) {
char c = sb.charAtUnsafe(result);
// See https://tools.ietf.org/html/rfc7230#section-3.5
if (isSPLenient(c)) {
continue;
}
if (Character.isWhitespace(c)) {
// Any other whitespace delimiter is invalid
throw new IllegalArgumentException("Invalid separator");
}
return result;
}
return sb.length();
}

private static int findSPLenient(AppendableCharSequence sb, int offset) {
for (int result = offset; result < sb.length(); ++result) {
if (isSPLenient(sb.charAtUnsafe(result))) {
return result;
}
}
return sb.length();
}

private static int findWhitespace(AppendableCharSequence sb, int offset) {
private static boolean isSPLenient(char c) {
// See https://tools.ietf.org/html/rfc7230#section-3.5
return c == ' ' || c == (char) 0x09 || c == (char) 0x0B || c == (char) 0x0C || c == (char) 0x0D;
}

private static int findNonWhitespace(AppendableCharSequence sb, int offset, boolean validateOWS) {
for (int result = offset; result < sb.length(); ++result) {
if (Character.isWhitespace(sb.charAtUnsafe(result))) {
char c = sb.charAtUnsafe(result);
if (!Character.isWhitespace(c)) {
return result;
} else if (validateOWS && !isOWS(c)) {
// Only OWS is supported for whitespace
throw new IllegalArgumentException("Invalid separator, only a single space or horizontal tab allowed," +
" but received a '" + c + "'");
}
}
return sb.length();
Expand All @@ -846,6 +872,10 @@ private static int findEndOfString(AppendableCharSequence sb) {
return 0;
}

private static boolean isOWS(char ch) {
return ch == ' ' || ch == (char) 0x09;
}

private static class HeaderParser implements ByteProcessor {
private final AppendableCharSequence seq;
private final int maxLength;
Expand Down Expand Up @@ -875,10 +905,13 @@ public void reset() {
@Override
public boolean process(byte value) throws Exception {
char nextByte = (char) (value & 0xFF);
if (nextByte == HttpConstants.CR) {
return true;
}
if (nextByte == HttpConstants.LF) {
int len = seq.length();
// Drop CR if we had a CRLF pair
if (len >= 1 && seq.charAtUnsafe(len - 1) == HttpConstants.CR) {
-- size;
seq.setLength(len - 1);
}
return false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,30 @@ public void testMultipleContentLengthHeadersWithFolding() {
testInvalidHeaders0(requestStr);
}

@Test
public void testContentLengthAndTransferEncodingHeadersWithVerticalTab() {
testContentLengthAndTransferEncodingHeadersWithInvalidSeparator((char) 0x0b, false);
testContentLengthAndTransferEncodingHeadersWithInvalidSeparator((char) 0x0b, true);
}

@Test
public void testContentLengthAndTransferEncodingHeadersWithCR() {
testContentLengthAndTransferEncodingHeadersWithInvalidSeparator((char) 0x0d, false);
testContentLengthAndTransferEncodingHeadersWithInvalidSeparator((char) 0x0d, true);
}

private static void testContentLengthAndTransferEncodingHeadersWithInvalidSeparator(
char separator, boolean extraLine) {
String requestStr = "POST / HTTP/1.1\r\n" +
"Host: example.com\r\n" +
"Connection: close\r\n" +
"Content-Length: 9\r\n" +
"Transfer-Encoding:" + separator + "chunked\r\n\r\n" +
(extraLine ? "0\r\n\r\n" : "") +
"something\r\n\r\n";
testInvalidHeaders0(requestStr);
}

@Test
public void testContentLengthHeaderAndChunked() {
String requestStr = "POST / HTTP/1.1\r\n" +
Expand All @@ -408,7 +432,6 @@ public void testContentLengthHeaderAndChunked() {
"Content-Length: 5\r\n" +
"Transfer-Encoding: chunked\r\n\r\n" +
"0\r\n\r\n";

EmbeddedChannel channel = new EmbeddedChannel(new HttpRequestDecoder());
assertTrue(channel.writeInbound(Unpooled.copiedBuffer(requestStr, CharsetUtil.US_ASCII)));
HttpRequest request = channel.readInbound();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public void testMaxHeaderSize1() {
final int maxHeaderSize = 8192;

final EmbeddedChannel ch = new EmbeddedChannel(new HttpResponseDecoder(4096, maxHeaderSize, 8192));
final char[] bytes = new char[maxHeaderSize / 2 - 2];
final char[] bytes = new char[maxHeaderSize / 2 - 4];
Arrays.fill(bytes, 'a');

ch.writeInbound(Unpooled.copiedBuffer("HTTP/1.1 200 OK\r\n", CharsetUtil.US_ASCII));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ private AppendableCharSequence(char[] chars) {
pos = chars.length;
}

public void setLength(int length) {
if (length < 0 || length > pos) {
throw new IllegalArgumentException("length: " + length + " (length: >= 0, <= " + pos + ')');
}
this.pos = length;
}

@Override
public int length() {
return pos;
Expand Down