Be more lean when parsing HTTP headers

Allow only LF instaed of CRLF when parsing headers. Sometimes old or
hacked up clients will send the headers like this.

R=kustermann@google.com
BUG=

Review URL: https://codereview.chromium.org//1078683002

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@44998 260f80e4-7a28-3924-810f-c04153c831b5
This commit is contained in:
sgjesse@google.com 2015-04-09 11:15:29 +00:00
parent 96fdc22856
commit c6e632bcdb
2 changed files with 147 additions and 78 deletions

View file

@ -334,6 +334,74 @@ class _HttpParser extends Stream<_HttpIncoming> {
}
}
// Process end of headers. Returns true if the parser should stop
// parsing and return. This will be in case of either an upgrade
// request or a request or response with an empty body.
bool _headersEnd() {
_headers._mutable = false;
_transferLength = _headers.contentLength;
// Ignore the Content-Length header if Transfer-Encoding
// is chunked (RFC 2616 section 4.4)
if (_chunked) _transferLength = -1;
// If a request message has neither Content-Length nor
// Transfer-Encoding the message must not have a body (RFC
// 2616 section 4.3).
if (_messageType == _MessageType.REQUEST &&
_transferLength < 0 &&
_chunked == false) {
_transferLength = 0;
}
if (_connectionUpgrade) {
_state = _State.UPGRADED;
_transferLength = 0;
}
_createIncoming(_transferLength);
if (_requestParser) {
_incoming.method =
new String.fromCharCodes(_method);
_incoming.uri =
Uri.parse(
new String.fromCharCodes(_uri_or_reason_phrase));
} else {
_incoming.statusCode = _statusCode;
_incoming.reasonPhrase =
new String.fromCharCodes(_uri_or_reason_phrase);
}
_method.clear();
_uri_or_reason_phrase.clear();
if (_connectionUpgrade) {
_incoming.upgraded = true;
_parserCalled = false;
var tmp = _incoming;
_closeIncoming();
_controller.add(tmp);
return true;
}
if (_transferLength == 0 ||
(_messageType == _MessageType.RESPONSE && _noMessageBody)) {
_reset();
var tmp = _incoming;
_closeIncoming();
_controller.add(tmp);
return false;
} else if (_chunked) {
_state = _State.CHUNK_SIZE;
_remainingContent = 0;
} else if (_transferLength > 0) {
_remainingContent = _transferLength;
_state = _State.BODY;
} else {
// Neither chunked nor content length. End of body
// indicated by close.
_state = _State.BODY;
}
_parserCalled = false;
_controller.add(_incoming);
return true;
}
// From RFC 2616.
// generic-message = start-line
// *(message-header CRLF)
@ -487,8 +555,13 @@ class _HttpParser extends Stream<_HttpIncoming> {
throw new HttpException("Invalid response line");
}
} else {
_expect(byte, _CharCode.CR);
_state = _State.REQUEST_LINE_ENDING;
if (byte == _CharCode.CR) {
_state = _State.REQUEST_LINE_ENDING;
} else {
_expect(byte, _CharCode.LF);
_messageType = _MessageType.REQUEST;
_state = _State.HEADER_START;
}
}
break;
@ -545,6 +618,12 @@ class _HttpParser extends Stream<_HttpIncoming> {
_headers = new _HttpHeaders(version);
if (byte == _CharCode.CR) {
_state = _State.HEADER_ENDING;
} else if (byte == _CharCode.LF) {
if (_headersEnd()) {
return;
} else {
break;
}
} else {
// Start of new header field.
_headerField.add(_toLowerCaseByte(byte));
@ -566,6 +645,8 @@ class _HttpParser extends Stream<_HttpIncoming> {
case _State.HEADER_VALUE_START:
if (byte == _CharCode.CR) {
_state = _State.HEADER_VALUE_FOLDING_OR_ENDING;
} else if (byte == _CharCode.LF) {
_state = _State.HEADER_VALUE_FOLD_OR_END;
} else if (byte != _CharCode.SP && byte != _CharCode.HT) {
// Start of new header value.
_headerValue.add(byte);
@ -576,6 +657,8 @@ class _HttpParser extends Stream<_HttpIncoming> {
case _State.HEADER_VALUE:
if (byte == _CharCode.CR) {
_state = _State.HEADER_VALUE_FOLDING_OR_ENDING;
} else if (byte == _CharCode.LF) {
_state = _State.HEADER_VALUE_FOLD_OR_END;
} else {
_headerValue.add(byte);
}
@ -613,6 +696,12 @@ class _HttpParser extends Stream<_HttpIncoming> {
if (byte == _CharCode.CR) {
_state = _State.HEADER_ENDING;
} else if (byte == _CharCode.LF) {
if (_headersEnd()) {
return;
} else {
break;
}
} else {
// Start of new header field.
_headerField.add(_toLowerCaseByte(byte));
@ -623,67 +712,11 @@ class _HttpParser extends Stream<_HttpIncoming> {
case _State.HEADER_ENDING:
_expect(byte, _CharCode.LF);
_headers._mutable = false;
_transferLength = _headers.contentLength;
// Ignore the Content-Length header if Transfer-Encoding
// is chunked (RFC 2616 section 4.4)
if (_chunked) _transferLength = -1;
// If a request message has neither Content-Length nor
// Transfer-Encoding the message must not have a body (RFC
// 2616 section 4.3).
if (_messageType == _MessageType.REQUEST &&
_transferLength < 0 &&
_chunked == false) {
_transferLength = 0;
}
if (_connectionUpgrade) {
_state = _State.UPGRADED;
_transferLength = 0;
}
_createIncoming(_transferLength);
if (_requestParser) {
_incoming.method =
new String.fromCharCodes(_method);
_incoming.uri =
Uri.parse(
new String.fromCharCodes(_uri_or_reason_phrase));
} else {
_incoming.statusCode = _statusCode;
_incoming.reasonPhrase =
new String.fromCharCodes(_uri_or_reason_phrase);
}
_method.clear();
_uri_or_reason_phrase.clear();
if (_connectionUpgrade) {
_incoming.upgraded = true;
_parserCalled = false;
var tmp = _incoming;
_closeIncoming();
_controller.add(tmp);
if (_headersEnd()) {
return;
}
if (_transferLength == 0 ||
(_messageType == _MessageType.RESPONSE && _noMessageBody)) {
_reset();
var tmp = _incoming;
_closeIncoming();
_controller.add(tmp);
break;
} else if (_chunked) {
_state = _State.CHUNK_SIZE;
_remainingContent = 0;
} else if (_transferLength > 0) {
_remainingContent = _transferLength;
_state = _State.BODY;
} else {
// Neither chunked nor content length. End of body
// indicated by close.
_state = _State.BODY;
break;
}
_parserCalled = false;
_controller.add(_incoming);
return;
case _State.CHUNK_SIZE_STARTING_CR:

View file

@ -150,6 +150,42 @@ class HttpParserTest {
testWrite(requestData, 1);
}
static void _testParseRequestLean(String request,
String expectedMethod,
String expectedUri,
{int expectedTransferLength: 0,
int expectedBytesReceived: 0,
Map expectedHeaders: null,
bool chunked: false,
bool upgrade: false,
int unparsedLength: 0,
bool connectionClose: false,
String expectedVersion: "1.1"}) {
_testParseRequest(request,
expectedMethod,
expectedUri,
expectedTransferLength: expectedTransferLength,
expectedBytesReceived: expectedBytesReceived,
expectedHeaders: expectedHeaders,
chunked: chunked,
upgrade: upgrade,
unparsedLength: unparsedLength,
connectionClose: connectionClose,
expectedVersion: expectedVersion);
// Same test but with only \n instead of \r\n terminating each header line.
_testParseRequest(request.replaceAll('\r', ''),
expectedMethod,
expectedUri,
expectedTransferLength: expectedTransferLength,
expectedBytesReceived: expectedBytesReceived,
expectedHeaders: expectedHeaders,
chunked: chunked,
upgrade: upgrade,
unparsedLength: unparsedLength,
connectionClose: connectionClose,
expectedVersion: expectedVersion);
}
static void _testParseInvalidRequest(String request) {
_HttpParser httpParser;
bool errorCalled;
@ -356,33 +392,33 @@ class HttpParserTest {
"SEARCH",
// Methods with HTTP prefix.
"H", "HT", "HTT", "HTTP", "HX", "HTX", "HTTX", "HTTPX"];
methods = ['GET'];
methods.forEach((method) {
request = "$method / HTTP/1.1\r\n\r\n";
_testParseRequest(request, method, "/");
_testParseRequestLean(request, method, "/");
request = "$method /index.html HTTP/1.1\r\n\r\n";
_testParseRequest(request, method, "/index.html");
_testParseRequestLean(request, method, "/index.html");
});
request = "GET / HTTP/1.0\r\n\r\n";
_testParseRequest(request, "GET", "/",
expectedVersion: "1.0",
connectionClose: true);
_testParseRequestLean(request, "GET", "/",
expectedVersion: "1.0",
connectionClose: true);
request = "GET / HTTP/1.0\r\nConnection: keep-alive\r\n\r\n";
_testParseRequest(request, "GET", "/", expectedVersion: "1.0");
_testParseRequestLean(request, "GET", "/", expectedVersion: "1.0");
request = """
POST /test HTTP/1.1\r
AAA: AAA\r
\r
""";
_testParseRequest(request, "POST", "/test");
_testParseRequestLean(request, "POST", "/test");
request = """
POST /test HTTP/1.1\r
\r
""";
_testParseRequest(request, "POST", "/test");
_testParseRequestLean(request, "POST", "/test");
request = """
POST /test HTTP/1.1\r
@ -393,7 +429,7 @@ X-Header-B: bbb\r
headers = new Map();
headers["header-a"] = "AAA";
headers["x-header-b"] = "bbb";
_testParseRequest(request, "POST", "/test", expectedHeaders: headers);
_testParseRequestLean(request, "POST", "/test", expectedHeaders: headers);
request = """
POST /test HTTP/1.1\r
@ -405,7 +441,7 @@ Empty-Header-2:\r
headers = new Map();
headers["empty-header-1"] = "";
headers["empty-header-2"] = "";
_testParseRequest(request, "POST", "/test", expectedHeaders: headers);
_testParseRequestLean(request, "POST", "/test", expectedHeaders: headers);
request = """
POST /test HTTP/1.1\r
@ -416,7 +452,7 @@ X-Header-B:\t \t bbb\r
headers = new Map();
headers["header-a"] = "AAA";
headers["x-header-b"] = "bbb";
_testParseRequest(request, "POST", "/test", expectedHeaders: headers);
_testParseRequestLean(request, "POST", "/test", expectedHeaders: headers);
request = """
POST /test HTTP/1.1\r
@ -431,18 +467,18 @@ X-Header-B: b\r
headers = new Map();
headers["header-a"] = "AAA";
headers["x-header-b"] = "bbb";
_testParseRequest(request, "POST", "/test", expectedHeaders: headers);
_testParseRequestLean(request, "POST", "/test", expectedHeaders: headers);
request = """
POST /test HTTP/1.1\r
Content-Length: 10\r
\r
0123456789""";
_testParseRequest(request,
"POST",
"/test",
expectedTransferLength: 10,
expectedBytesReceived: 10);
_testParseRequestLean(request,
"POST",
"/test",
expectedTransferLength: 10,
expectedBytesReceived: 10);
// Test connection close header.
request = "GET /test HTTP/1.1\r\nConnection: close\r\n\r\n";