in sdk/lib/_http/http_parser.dart [420:866]
void _doParse() {
assert(!_parserCalled);
_parserCalled = true;
if (_state == _State.CLOSED) {
throw HttpException("Data on closed connection");
}
if (_state == _State.FAILURE) {
throw HttpException("Data on failed connection");
}
while (_buffer != null &&
_index < _buffer!.length &&
_state != _State.FAILURE &&
_state != _State.UPGRADED) {
// Depending on _incoming, we either break on _bodyPaused or _paused.
if ((_incoming != null && _bodyPaused) ||
(_incoming == null && _paused)) {
_parserCalled = false;
return;
}
int index = _index;
int byte = _buffer![index];
_index = index + 1;
switch (_state) {
case _State.START:
if (byte == _Const.HTTP[0]) {
// Start parsing method or HTTP version.
_httpVersionIndex = 1;
_state = _State.METHOD_OR_RESPONSE_HTTP_VERSION;
} else {
// Start parsing method.
if (!_isTokenChar(byte)) {
throw HttpException("Invalid request method");
}
_addWithValidation(_method, byte);
if (!_requestParser) {
throw HttpException("Invalid response line");
}
_state = _State.REQUEST_LINE_METHOD;
}
break;
case _State.METHOD_OR_RESPONSE_HTTP_VERSION:
var httpVersionIndex = _httpVersionIndex!;
if (httpVersionIndex < _Const.HTTP.length &&
byte == _Const.HTTP[httpVersionIndex]) {
// Continue parsing HTTP version.
_httpVersionIndex = httpVersionIndex + 1;
} else if (httpVersionIndex == _Const.HTTP.length &&
byte == _CharCode.SLASH) {
// HTTP/ parsed. As method is a token this cannot be a
// method anymore.
_httpVersionIndex = httpVersionIndex + 1;
if (_requestParser) {
throw HttpException("Invalid request line");
}
_state = _State.RESPONSE_HTTP_VERSION;
} else {
// Did not parse HTTP version. Expect method instead.
for (int i = 0; i < httpVersionIndex; i++) {
_addWithValidation(_method, _Const.HTTP[i]);
}
if (byte == _CharCode.SP) {
_state = _State.REQUEST_LINE_URI;
} else {
_addWithValidation(_method, byte);
_httpVersion = _HttpVersion.UNDETERMINED;
if (!_requestParser) {
throw HttpException("Invalid response line");
}
_state = _State.REQUEST_LINE_METHOD;
}
}
break;
case _State.RESPONSE_HTTP_VERSION:
var httpVersionIndex = _httpVersionIndex!;
if (httpVersionIndex < _Const.HTTP1DOT.length) {
// Continue parsing HTTP version.
_expect(byte, _Const.HTTP1DOT[httpVersionIndex]);
_httpVersionIndex = httpVersionIndex + 1;
} else if (httpVersionIndex == _Const.HTTP1DOT.length &&
byte == _CharCode.ONE) {
// HTTP/1.1 parsed.
_httpVersion = _HttpVersion.HTTP11;
_persistentConnection = true;
_httpVersionIndex = httpVersionIndex + 1;
} else if (httpVersionIndex == _Const.HTTP1DOT.length &&
byte == _CharCode.ZERO) {
// HTTP/1.0 parsed.
_httpVersion = _HttpVersion.HTTP10;
_persistentConnection = false;
_httpVersionIndex = httpVersionIndex + 1;
} else if (httpVersionIndex == _Const.HTTP1DOT.length + 1) {
_expect(byte, _CharCode.SP);
// HTTP version parsed.
_state = _State.RESPONSE_LINE_STATUS_CODE;
} else {
throw HttpException(
"Invalid response line, failed to parse HTTP version");
}
break;
case _State.REQUEST_LINE_METHOD:
if (byte == _CharCode.SP) {
_state = _State.REQUEST_LINE_URI;
} else {
if (_Const.SEPARATOR_MAP[byte] ||
byte == _CharCode.CR ||
byte == _CharCode.LF) {
throw HttpException("Invalid request method");
}
_addWithValidation(_method, byte);
}
break;
case _State.REQUEST_LINE_URI:
if (byte == _CharCode.SP) {
if (_uriOrReasonPhrase.isEmpty) {
throw HttpException("Invalid request, empty URI");
}
_state = _State.REQUEST_LINE_HTTP_VERSION;
_httpVersionIndex = 0;
} else {
if (byte == _CharCode.CR || byte == _CharCode.LF) {
throw HttpException("Invalid request, unexpected $byte in URI");
}
_addWithValidation(_uriOrReasonPhrase, byte);
}
break;
case _State.REQUEST_LINE_HTTP_VERSION:
var httpVersionIndex = _httpVersionIndex!;
if (httpVersionIndex < _Const.HTTP1DOT.length) {
_expect(byte, _Const.HTTP11[httpVersionIndex]);
_httpVersionIndex = httpVersionIndex + 1;
} else if (_httpVersionIndex == _Const.HTTP1DOT.length) {
if (byte == _CharCode.ONE) {
// HTTP/1.1 parsed.
_httpVersion = _HttpVersion.HTTP11;
_persistentConnection = true;
_httpVersionIndex = httpVersionIndex + 1;
} else if (byte == _CharCode.ZERO) {
// HTTP/1.0 parsed.
_httpVersion = _HttpVersion.HTTP10;
_persistentConnection = false;
_httpVersionIndex = httpVersionIndex + 1;
} else {
throw HttpException("Invalid response, invalid HTTP version");
}
} else {
if (byte == _CharCode.CR) {
_state = _State.REQUEST_LINE_ENDING;
} else if (byte == _CharCode.LF) {
_state = _State.REQUEST_LINE_ENDING;
_index = _index - 1; // Make the new state see the LF again.
}
}
break;
case _State.REQUEST_LINE_ENDING:
_expect(byte, _CharCode.LF);
_messageType = _MessageType.REQUEST;
_state = _State.HEADER_START;
break;
case _State.RESPONSE_LINE_STATUS_CODE:
if (byte == _CharCode.SP) {
_state = _State.RESPONSE_LINE_REASON_PHRASE;
} else if (byte == _CharCode.CR) {
// Some HTTP servers do not follow the spec and send
// \r?\n right after the status code.
_state = _State.RESPONSE_LINE_ENDING;
} else if (byte == _CharCode.LF) {
_state = _State.RESPONSE_LINE_ENDING;
_index = _index - 1; // Make the new state see the LF again.
} else {
_statusCodeLength++;
if (byte < 0x30 || byte > 0x39) {
throw HttpException("Invalid response status code with $byte");
} else if (_statusCodeLength > 3) {
throw HttpException(
"Invalid response, status code is over 3 digits");
} else {
_statusCode = _statusCode * 10 + byte - 0x30;
}
}
break;
case _State.RESPONSE_LINE_REASON_PHRASE:
if (byte == _CharCode.CR) {
_state = _State.RESPONSE_LINE_ENDING;
} else if (byte == _CharCode.LF) {
_state = _State.RESPONSE_LINE_ENDING;
_index = _index - 1; // Make the new state see the LF again.
} else {
_addWithValidation(_uriOrReasonPhrase, byte);
}
break;
case _State.RESPONSE_LINE_ENDING:
_expect(byte, _CharCode.LF);
_messageType == _MessageType.RESPONSE;
// Check whether this response will never have a body.
if (_statusCode <= 199 || _statusCode == 204 || _statusCode == 304) {
_noMessageBody = true;
}
_state = _State.HEADER_START;
break;
case _State.HEADER_START:
_headers = _HttpHeaders(version!);
if (byte == _CharCode.CR) {
_state = _State.HEADER_ENDING;
} else if (byte == _CharCode.LF) {
_state = _State.HEADER_ENDING;
_index = _index - 1; // Make the new state see the LF again.
} else {
// Start of new header field.
_addWithValidation(_headerField, _toLowerCaseByte(byte));
_state = _State.HEADER_FIELD;
}
break;
case _State.HEADER_FIELD:
if (byte == _CharCode.COLON) {
_state = _State.HEADER_VALUE_START;
} else {
if (!_isTokenChar(byte)) {
throw HttpException("Invalid header field name, with $byte");
}
_addWithValidation(_headerField, _toLowerCaseByte(byte));
}
break;
case _State.HEADER_VALUE_START:
if (byte == _CharCode.CR) {
_state = _State.HEADER_VALUE_FOLD_OR_END_CR;
} 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.
_addWithValidation(_headerValue, byte);
_state = _State.HEADER_VALUE;
}
break;
case _State.HEADER_VALUE:
if (byte == _CharCode.CR) {
_state = _State.HEADER_VALUE_FOLD_OR_END_CR;
} else if (byte == _CharCode.LF) {
_state = _State.HEADER_VALUE_FOLD_OR_END;
} else {
_addWithValidation(_headerValue, byte);
}
break;
case _State.HEADER_VALUE_FOLD_OR_END_CR:
_expect(byte, _CharCode.LF);
_state = _State.HEADER_VALUE_FOLD_OR_END;
break;
case _State.HEADER_VALUE_FOLD_OR_END:
if (byte == _CharCode.SP || byte == _CharCode.HT) {
_state = _State.HEADER_VALUE_START;
} else {
String headerField = String.fromCharCodes(_headerField);
String headerValue = String.fromCharCodes(_headerValue);
const errorIfBothText = "Both Content-Length and Transfer-Encoding "
"are specified, at most one is allowed";
if (headerField == HttpHeaders.contentLengthHeader) {
// Content Length header should not have more than one occurrence
// or coexist with Transfer Encoding header.
if (_contentLength) {
throw HttpException("The Content-Length header occurred "
"more than once, at most one is allowed.");
} else if (_transferEncoding) {
throw HttpException(errorIfBothText);
}
_contentLength = true;
} else if (headerField == HttpHeaders.transferEncodingHeader) {
_transferEncoding = true;
if (_caseInsensitiveCompare("chunked".codeUnits, _headerValue)) {
_chunked = true;
}
if (_contentLength) {
throw HttpException(errorIfBothText);
}
}
var headers = _headers!;
if (headerField == HttpHeaders.connectionHeader) {
List<String> tokens = _tokenizeFieldValue(headerValue);
final bool isResponse = _messageType == _MessageType.RESPONSE;
final bool isUpgradeCode =
(_statusCode == HttpStatus.upgradeRequired) ||
(_statusCode == HttpStatus.switchingProtocols);
for (int i = 0; i < tokens.length; i++) {
final bool isUpgrade = _caseInsensitiveCompare(
"upgrade".codeUnits, tokens[i].codeUnits);
if ((isUpgrade && !isResponse) ||
(isUpgrade && isResponse && isUpgradeCode)) {
_connectionUpgrade = true;
}
headers._add(headerField, tokens[i]);
}
} else {
headers._add(headerField, headerValue);
}
_headerField.clear();
_headerValue.clear();
if (byte == _CharCode.CR) {
_state = _State.HEADER_ENDING;
} else if (byte == _CharCode.LF) {
_state = _State.HEADER_ENDING;
_index = _index - 1; // Make the new state see the LF again.
} else {
// Start of new header field.
_state = _State.HEADER_FIELD;
_addWithValidation(_headerField, _toLowerCaseByte(byte));
}
}
break;
case _State.HEADER_ENDING:
_expect(byte, _CharCode.LF);
if (_headersEnd()) {
return;
}
break;
case _State.CHUNK_SIZE_STARTING_CR:
if (byte == _CharCode.LF) {
_state = _State.CHUNK_SIZE_STARTING;
_index = _index - 1; // Make the new state see the LF again.
break;
}
_expect(byte, _CharCode.CR);
_state = _State.CHUNK_SIZE_STARTING;
break;
case _State.CHUNK_SIZE_STARTING:
_expect(byte, _CharCode.LF);
_state = _State.CHUNK_SIZE;
break;
case _State.CHUNK_SIZE:
if (byte == _CharCode.CR) {
_state = _State.CHUNK_SIZE_ENDING;
} else if (byte == _CharCode.LF) {
_state = _State.CHUNK_SIZE_ENDING;
_index = _index - 1; // Make the new state see the LF again.
} else if (byte == _CharCode.SEMI_COLON) {
_state = _State.CHUNK_SIZE_EXTENSION;
} else {
int value = _expectHexDigit(byte);
// Checks whether (_remainingContent * 16 + value) overflows.
if (_remainingContent > _chunkSizeLimit >> 4) {
throw HttpException('Chunk size overflows the integer');
}
_remainingContent = _remainingContent * 16 + value;
}
break;
case _State.CHUNK_SIZE_EXTENSION:
if (byte == _CharCode.CR) {
_state = _State.CHUNK_SIZE_ENDING;
} else if (byte == _CharCode.LF) {
_state = _State.CHUNK_SIZE_ENDING;
_index = _index - 1; // Make the new state see the LF again.
}
break;
case _State.CHUNK_SIZE_ENDING:
_expect(byte, _CharCode.LF);
if (_remainingContent > 0) {
_state = _State.BODY;
} else {
_state = _State.CHUNKED_BODY_DONE_CR;
}
break;
case _State.CHUNKED_BODY_DONE_CR:
if (byte == _CharCode.LF) {
_state = _State.CHUNKED_BODY_DONE;
_index = _index - 1; // Make the new state see the LF again.
break;
}
_expect(byte, _CharCode.CR);
break;
case _State.CHUNKED_BODY_DONE:
_expect(byte, _CharCode.LF);
_reset();
_closeIncoming();
break;
case _State.BODY:
// The body is not handled one byte at a time but in blocks.
_index = _index - 1;
var buffer = _buffer!;
int dataAvailable = buffer.length - _index;
if (_remainingContent >= 0 && dataAvailable > _remainingContent) {
dataAvailable = _remainingContent;
}
// Always present the data as a view. This way we can handle all
// cases like this, and the user will not experience different data
// typed (which could lead to polymorphic user code).
Uint8List data = Uint8List.view(
buffer.buffer, buffer.offsetInBytes + _index, dataAvailable);
_bodyController!.add(data);
if (_remainingContent != -1) {
_remainingContent -= data.length;
}
_index = _index + data.length;
if (_remainingContent == 0) {
if (!_chunked) {
_reset();
_closeIncoming();
} else {
_state = _State.CHUNK_SIZE_STARTING_CR;
}
}
break;
case _State.FAILURE:
// Should be unreachable.
assert(false);
break;
default:
// Should be unreachable.
assert(false);
break;
}
}
_parserCalled = false;
var buffer = _buffer;
if (buffer != null && _index == buffer.length) {
// If all data is parsed release the buffer and resume receiving
// data.
_releaseBuffer();
if (_state != _State.UPGRADED && _state != _State.FAILURE) {
_socketSubscription!.resume();
}
}
}