Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(186)

Unified Diff: tool/input_sdk/lib/io/http_parser.dart

Issue 1976103003: Migrate dart2js stubs for dart:io (Closed) Base URL: https://github.com/dart-lang/dev_compiler.git@master
Patch Set: Created 4 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « tool/input_sdk/lib/io/http_impl.dart ('k') | tool/input_sdk/lib/io/http_session.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: tool/input_sdk/lib/io/http_parser.dart
diff --git a/tool/input_sdk/lib/io/http_parser.dart b/tool/input_sdk/lib/io/http_parser.dart
new file mode 100644
index 0000000000000000000000000000000000000000..5eb96ce350bf05f0d93f61857154f223aca33bde
--- /dev/null
+++ b/tool/input_sdk/lib/io/http_parser.dart
@@ -0,0 +1,1072 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+part of dart.io;
+
+// Global constants.
+class _Const {
+ // Bytes for "HTTP".
+ static const HTTP = const [72, 84, 84, 80];
+ // Bytes for "HTTP/1.".
+ static const HTTP1DOT = const [72, 84, 84, 80, 47, 49, 46];
+ // Bytes for "HTTP/1.0".
+ static const HTTP10 = const [72, 84, 84, 80, 47, 49, 46, 48];
+ // Bytes for "HTTP/1.1".
+ static const HTTP11 = const [72, 84, 84, 80, 47, 49, 46, 49];
+
+ static const bool T = true;
+ static const bool F = false;
+ // Loopup-map for the following characters: '()<>@,;:\\"/[]?={} \t'.
+ static const SEPARATOR_MAP = const [
+ F,F,F,F,F,F,F,F,F,T,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,T,F,T,F,F,
+ F,F,F,T,T,F,F,T,F,F,T,F,F,F,F,F,F,F,F,F,F,T,T,T,T,T,T,T,F,F,F,F,F,F,F,F,F,
+ F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,T,T,T,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,
+ F,F,F,F,F,F,F,F,F,F,F,F,T,F,T,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,
+ F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,
+ F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,
+ F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F];
+}
+
+
+// Frequently used character codes.
+class _CharCode {
+ static const int HT = 9;
+ static const int LF = 10;
+ static const int CR = 13;
+ static const int SP = 32;
+ static const int AMPERSAND = 38;
+ static const int COMMA = 44;
+ static const int DASH = 45;
+ static const int SLASH = 47;
+ static const int ZERO = 48;
+ static const int ONE = 49;
+ static const int COLON = 58;
+ static const int SEMI_COLON = 59;
+ static const int EQUAL = 61;
+}
+
+
+// States of the HTTP parser state machine.
+class _State {
+ static const int START = 0;
+ static const int METHOD_OR_RESPONSE_HTTP_VERSION = 1;
+ static const int RESPONSE_HTTP_VERSION = 2;
+ static const int REQUEST_LINE_METHOD = 3;
+ static const int REQUEST_LINE_URI = 4;
+ static const int REQUEST_LINE_HTTP_VERSION = 5;
+ static const int REQUEST_LINE_ENDING = 6;
+ static const int RESPONSE_LINE_STATUS_CODE = 7;
+ static const int RESPONSE_LINE_REASON_PHRASE = 8;
+ static const int RESPONSE_LINE_ENDING = 9;
+ static const int HEADER_START = 10;
+ static const int HEADER_FIELD = 11;
+ static const int HEADER_VALUE_START = 12;
+ static const int HEADER_VALUE = 13;
+ static const int HEADER_VALUE_FOLDING_OR_ENDING = 14;
+ static const int HEADER_VALUE_FOLD_OR_END = 15;
+ static const int HEADER_ENDING = 16;
+
+ static const int CHUNK_SIZE_STARTING_CR = 17;
+ static const int CHUNK_SIZE_STARTING_LF = 18;
+ static const int CHUNK_SIZE = 19;
+ static const int CHUNK_SIZE_EXTENSION = 20;
+ static const int CHUNK_SIZE_ENDING = 21;
+ static const int CHUNKED_BODY_DONE_CR = 22;
+ static const int CHUNKED_BODY_DONE_LF = 23;
+ static const int BODY = 24;
+ static const int CLOSED = 25;
+ static const int UPGRADED = 26;
+ static const int FAILURE = 27;
+
+ static const int FIRST_BODY_STATE = CHUNK_SIZE_STARTING_CR;
+}
+
+// HTTP version of the request or response being parsed.
+class _HttpVersion {
+ static const int UNDETERMINED = 0;
+ static const int HTTP10 = 1;
+ static const int HTTP11 = 2;
+}
+
+// States of the HTTP parser state machine.
+class _MessageType {
+ static const int UNDETERMINED = 0;
+ static const int REQUEST = 1;
+ static const int RESPONSE = 0;
+}
+
+
+/**
+ * The _HttpDetachedStreamSubscription takes a subscription and some extra data,
+ * and makes it possible to "inject" the data in from of other data events
+ * from the subscription.
+ *
+ * It does so by overriding pause/resume, so that once the
+ * _HttpDetachedStreamSubscription is resumed, it'll deliver the data before
+ * resuming the underlaying subscription.
+ */
+class _HttpDetachedStreamSubscription implements StreamSubscription<List<int>> {
+ StreamSubscription<List<int>> _subscription;
+ List<int> _injectData;
+ bool _isCanceled = false;
+ int _pauseCount = 1;
+ Function _userOnData;
+ bool _scheduled = false;
+
+ _HttpDetachedStreamSubscription(this._subscription,
+ this._injectData,
+ this._userOnData);
+
+ bool get isPaused => _subscription.isPaused;
+
+ Future/*<T>*/ asFuture/*<T>*/([/*=T*/ futureValue]) =>
+ _subscription.asFuture/*<T>*/(futureValue);
+
+ Future cancel() {
+ _isCanceled = true;
+ _injectData = null;
+ return _subscription.cancel();
+ }
+
+ void onData(void handleData(List<int> data)) {
+ _userOnData = handleData;
+ _subscription.onData(handleData);
+ }
+
+ void onDone(void handleDone()) {
+ _subscription.onDone(handleDone);
+ }
+
+ void onError(Function handleError) {
+ _subscription.onError(handleError);
+ }
+
+ void pause([Future resumeSignal]) {
+ if (_injectData == null) {
+ _subscription.pause(resumeSignal);
+ } else {
+ _pauseCount++;
+ if (resumeSignal != null) {
+ resumeSignal.whenComplete(resume);
+ }
+ }
+ }
+
+ void resume() {
+ if (_injectData == null) {
+ _subscription.resume();
+ } else {
+ _pauseCount--;
+ _maybeScheduleData();
+ }
+ }
+
+ void _maybeScheduleData() {
+ if (_scheduled) return;
+ if (_pauseCount != 0) return;
+ _scheduled = true;
+ scheduleMicrotask(() {
+ _scheduled = false;
+ if (_pauseCount > 0 || _isCanceled) return;
+ var data = _injectData;
+ _injectData = null;
+ // To ensure that 'subscription.isPaused' is false, we resume the
+ // subscription here. This is fine as potential events are delayed.
+ _subscription.resume();
+ if (_userOnData != null) {
+ _userOnData(data);
+ }
+ });
+ }
+}
+
+
+class _HttpDetachedIncoming extends Stream<List<int>> {
+ final StreamSubscription subscription;
+ final List<int> bufferedData;
+
+ _HttpDetachedIncoming(this.subscription, this.bufferedData);
+
+ StreamSubscription<List<int>> listen(void onData(List<int> event),
+ {Function onError,
+ void onDone(),
+ bool cancelOnError}) {
+ if (subscription != null) {
+ subscription
+ ..onData(onData)
+ ..onError(onError)
+ ..onDone(onDone);
+ if (bufferedData == null) {
+ return subscription..resume();
+ }
+ return new _HttpDetachedStreamSubscription(subscription,
+ bufferedData,
+ onData)
+ ..resume();
+ } else {
+ return new Stream.fromIterable(bufferedData)
+ .listen(onData,
+ onError: onError,
+ onDone: onDone,
+ cancelOnError: cancelOnError);
+ }
+ }
+}
+
+
+/**
+ * HTTP parser which parses the data stream given to [consume].
+ *
+ * If an HTTP parser error occours, the parser will signal an error to either
+ * the current _HttpIncoming or the _parser itself.
+ *
+ * The connection upgrades (e.g. switching from HTTP/1.1 to the
+ * WebSocket protocol) is handled in a special way. If connection
+ * upgrade is specified in the headers, then on the callback to
+ * [:responseStart:] the [:upgrade:] property on the [:HttpParser:]
+ * object will be [:true:] indicating that from now on the protocol is
+ * not HTTP anymore and no more callbacks will happen, that is
+ * [:dataReceived:] and [:dataEnd:] are not called in this case as
+ * there is no more HTTP data. After the upgrade the method
+ * [:readUnparsedData:] can be used to read any remaining bytes in the
+ * HTTP parser which are part of the protocol the connection is
+ * upgrading to. These bytes cannot be processed by the HTTP parser
+ * and should be handled according to whatever protocol is being
+ * upgraded to.
+ */
+class _HttpParser extends Stream<_HttpIncoming> {
+ // State.
+ bool _parserCalled = false;
+
+ // The data that is currently being parsed.
+ Uint8List _buffer;
+ int _index;
+
+ final bool _requestParser;
+ int _state;
+ int _httpVersionIndex;
+ int _messageType;
+ int _statusCode = 0;
+ int _statusCodeLength = 0;
+ final List<int> _method = [];
+ final List<int> _uri_or_reason_phrase = [];
+ final List<int> _headerField = [];
+ final List<int> _headerValue = [];
+
+ int _httpVersion;
+ int _transferLength = -1;
+ bool _persistentConnection;
+ bool _connectionUpgrade;
+ bool _chunked;
+
+ bool _noMessageBody = false;
+ int _remainingContent = -1;
+
+ _HttpHeaders _headers;
+
+ // The current incoming connection.
+ _HttpIncoming _incoming;
+ StreamSubscription _socketSubscription;
+ bool _paused = true;
+ bool _bodyPaused = false;
+ StreamController<_HttpIncoming> _controller;
+ StreamController<List<int>> _bodyController;
+
+ factory _HttpParser.requestParser() {
+ return new _HttpParser._(true);
+ }
+
+ factory _HttpParser.responseParser() {
+ return new _HttpParser._(false);
+ }
+
+ _HttpParser._(this._requestParser) {
+ _controller = new StreamController<_HttpIncoming>(
+ sync: true,
+ onListen: () {
+ _paused = false;
+ },
+ onPause: () {
+ _paused = true;
+ _pauseStateChanged();
+ },
+ onResume: () {
+ _paused = false;
+ _pauseStateChanged();
+ },
+ onCancel: () {
+ if (_socketSubscription != null) {
+ _socketSubscription.cancel();
+ }
+ });
+ _reset();
+ }
+
+
+ StreamSubscription<_HttpIncoming> listen(void onData(_HttpIncoming event),
+ {Function onError,
+ void onDone(),
+ bool cancelOnError}) {
+ return _controller.stream.listen(onData,
+ onError: onError,
+ onDone: onDone,
+ cancelOnError: cancelOnError);
+ }
+
+ void listenToStream(Stream<List<int>> stream) {
+ // Listen to the stream and handle data accordingly. When a
+ // _HttpIncoming is created, _dataPause, _dataResume, _dataDone is
+ // given to provide a way of controlling the parser.
+ // TODO(ajohnsen): Remove _dataPause, _dataResume and _dataDone and clean up
+ // how the _HttpIncoming signals the parser.
+ _socketSubscription = stream.listen(
+ _onData,
+ onError: _controller.addError,
+ onDone: _onDone);
+ }
+
+ void _parse() {
+ try {
+ _doParse();
+ } catch (e, s) {
+ _state = _State.FAILURE;
+ _reportError(e, s);
+ }
+ }
+
+ // 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)
+ // CRLF
+ // [ message-body ]
+ // start-line = Request-Line | Status-Line
+ // Request-Line = Method SP Request-URI SP HTTP-Version CRLF
+ // Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF
+ // message-header = field-name ":" [ field-value ]
+ void _doParse() {
+ assert(!_parserCalled);
+ _parserCalled = true;
+ if (_state == _State.CLOSED) {
+ throw new HttpException("Data on closed connection");
+ }
+ if (_state == _State.FAILURE) {
+ throw new 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 byte = _buffer[_index++];
+ 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 new HttpException("Invalid request method");
+ }
+ _method.add(byte);
+ if (!_requestParser) {
+ throw new HttpException("Invalid response line");
+ }
+ _state = _State.REQUEST_LINE_METHOD;
+ }
+ break;
+
+ case _State.METHOD_OR_RESPONSE_HTTP_VERSION:
+ if (_httpVersionIndex < _Const.HTTP.length &&
+ byte == _Const.HTTP[_httpVersionIndex]) {
+ // Continue parsing HTTP version.
+ _httpVersionIndex++;
+ } else if (_httpVersionIndex == _Const.HTTP.length &&
+ byte == _CharCode.SLASH) {
+ // HTTP/ parsed. As method is a token this cannot be a
+ // method anymore.
+ _httpVersionIndex++;
+ if (_requestParser) {
+ throw new 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++) {
+ _method.add(_Const.HTTP[i]);
+ }
+ if (byte == _CharCode.SP) {
+ _state = _State.REQUEST_LINE_URI;
+ } else {
+ _method.add(byte);
+ _httpVersion = _HttpVersion.UNDETERMINED;
+ if (!_requestParser) {
+ throw new HttpException("Invalid response line");
+ }
+ _state = _State.REQUEST_LINE_METHOD;
+ }
+ }
+ break;
+
+ case _State.RESPONSE_HTTP_VERSION:
+ if (_httpVersionIndex < _Const.HTTP1DOT.length) {
+ // Continue parsing HTTP version.
+ _expect(byte, _Const.HTTP1DOT[_httpVersionIndex]);
+ _httpVersionIndex++;
+ } else if (_httpVersionIndex == _Const.HTTP1DOT.length &&
+ byte == _CharCode.ONE) {
+ // HTTP/1.1 parsed.
+ _httpVersion = _HttpVersion.HTTP11;
+ _persistentConnection = true;
+ _httpVersionIndex++;
+ } else if (_httpVersionIndex == _Const.HTTP1DOT.length &&
+ byte == _CharCode.ZERO) {
+ // HTTP/1.0 parsed.
+ _httpVersion = _HttpVersion.HTTP10;
+ _persistentConnection = false;
+ _httpVersionIndex++;
+ } else if (_httpVersionIndex == _Const.HTTP1DOT.length + 1) {
+ _expect(byte, _CharCode.SP);
+ // HTTP version parsed.
+ _state = _State.RESPONSE_LINE_STATUS_CODE;
+ } else {
+ throw new HttpException("Invalid response line");
+ }
+ 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 new HttpException("Invalid request method");
+ }
+ _method.add(byte);
+ }
+ break;
+
+ case _State.REQUEST_LINE_URI:
+ if (byte == _CharCode.SP) {
+ if (_uri_or_reason_phrase.length == 0) {
+ throw new HttpException("Invalid request URI");
+ }
+ _state = _State.REQUEST_LINE_HTTP_VERSION;
+ _httpVersionIndex = 0;
+ } else {
+ if (byte == _CharCode.CR || byte == _CharCode.LF) {
+ throw new HttpException("Invalid request URI");
+ }
+ _uri_or_reason_phrase.add(byte);
+ }
+ break;
+
+ case _State.REQUEST_LINE_HTTP_VERSION:
+ if (_httpVersionIndex < _Const.HTTP1DOT.length) {
+ _expect(byte, _Const.HTTP11[_httpVersionIndex]);
+ _httpVersionIndex++;
+ } else if (_httpVersionIndex == _Const.HTTP1DOT.length) {
+ if (byte == _CharCode.ONE) {
+ // HTTP/1.1 parsed.
+ _httpVersion = _HttpVersion.HTTP11;
+ _persistentConnection = true;
+ _httpVersionIndex++;
+ } else if (byte == _CharCode.ZERO) {
+ // HTTP/1.0 parsed.
+ _httpVersion = _HttpVersion.HTTP10;
+ _persistentConnection = false;
+ _httpVersionIndex++;
+ } else {
+ throw new HttpException("Invalid response line");
+ }
+ } else {
+ if (byte == _CharCode.CR) {
+ _state = _State.REQUEST_LINE_ENDING;
+ } else {
+ _expect(byte, _CharCode.LF);
+ _messageType = _MessageType.REQUEST;
+ _state = _State.HEADER_START;
+ }
+ }
+ 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 does not follow the spec. and send
+ // \r\n right after the status code.
+ _state = _State.RESPONSE_LINE_ENDING;
+ } else {
+ _statusCodeLength++;
+ if ((byte < 0x30 && 0x39 < byte) || _statusCodeLength > 3) {
+ throw new HttpException("Invalid response status code");
+ } 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.CR || byte == _CharCode.LF) {
+ throw new HttpException("Invalid response reason phrase");
+ }
+ _uri_or_reason_phrase.add(byte);
+ }
+ break;
+
+ case _State.RESPONSE_LINE_ENDING:
+ _expect(byte, _CharCode.LF);
+ _messageType == _MessageType.RESPONSE;
+ if (_statusCode < 100 || _statusCode > 599) {
+ throw new HttpException("Invalid response status code");
+ } else {
+ // 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 = new _HttpHeaders(version);
+ if (byte == _CharCode.CR) {
+ _state = _State.HEADER_ENDING;
+ } else if (byte == _CharCode.LF) {
+ _state = _State.HEADER_ENDING;
+ _index--; // Make the new state see the LF again.
+ } else {
+ // Start of new header field.
+ _headerField.add(_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 new HttpException("Invalid header field name");
+ }
+ _headerField.add(_toLowerCaseByte(byte));
+ }
+ break;
+
+ 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);
+ _state = _State.HEADER_VALUE;
+ }
+ break;
+
+ 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);
+ }
+ break;
+
+ case _State.HEADER_VALUE_FOLDING_OR_ENDING:
+ _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 = new String.fromCharCodes(_headerField);
+ String headerValue = new String.fromCharCodes(_headerValue);
+ if (headerField == "transfer-encoding" &&
+ _caseInsensitiveCompare("chunked".codeUnits, _headerValue)) {
+ _chunked = true;
+ }
+ if (headerField == "connection") {
+ List<String> tokens = _tokenizeFieldValue(headerValue);
+ for (int i = 0; i < tokens.length; i++) {
+ if (_caseInsensitiveCompare("upgrade".codeUnits,
+ tokens[i].codeUnits)) {
+ _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--; // Make the new state see the LF again.
+ } else {
+ // Start of new header field.
+ _headerField.add(_toLowerCaseByte(byte));
+ _state = _State.HEADER_FIELD;
+ }
+ }
+ break;
+
+ case _State.HEADER_ENDING:
+ _expect(byte, _CharCode.LF);
+ if (_headersEnd()) {
+ return;
+ } else {
+ break;
+ }
+ return;
+
+ case _State.CHUNK_SIZE_STARTING_CR:
+ _expect(byte, _CharCode.CR);
+ _state = _State.CHUNK_SIZE_STARTING_LF;
+ break;
+
+ case _State.CHUNK_SIZE_STARTING_LF:
+ _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.SEMI_COLON) {
+ _state = _State.CHUNK_SIZE_EXTENSION;
+ } else {
+ int value = _expectHexDigit(byte);
+ _remainingContent = _remainingContent * 16 + value;
+ }
+ break;
+
+ case _State.CHUNK_SIZE_EXTENSION:
+ if (byte == _CharCode.CR) {
+ _state = _State.CHUNK_SIZE_ENDING;
+ }
+ 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:
+ _expect(byte, _CharCode.CR);
+ _state = _State.CHUNKED_BODY_DONE_LF;
+ break;
+
+ case _State.CHUNKED_BODY_DONE_LF:
+ _expect(byte, _CharCode.LF);
+ _reset();
+ _closeIncoming();
+ break;
+
+ case _State.BODY:
+ // The body is not handled one byte at a time but in blocks.
+ _index--;
+ 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 experince different data
+ // typed (which could lead to polymorphic user code).
+ List<int> data = new Uint8List.view(_buffer.buffer,
+ _buffer.offsetInBytes + _index,
+ dataAvailable);
+ _bodyController.add(data);
+ if (_remainingContent != -1) {
+ _remainingContent -= data.length;
+ }
+ _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;
+ 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();
+ }
+ }
+ }
+
+ void _onData(List<int> buffer) {
+ _socketSubscription.pause();
+ assert(_buffer == null);
+ _buffer = buffer;
+ _index = 0;
+ _parse();
+ }
+
+ void _onDone() {
+ // onDone cancles the subscription.
+ _socketSubscription = null;
+ if (_state == _State.CLOSED || _state == _State.FAILURE) return;
+
+ if (_incoming != null) {
+ if (_state != _State.UPGRADED &&
+ !(_state == _State.START && !_requestParser) &&
+ !(_state == _State.BODY && !_chunked && _transferLength == -1)) {
+ _bodyController.addError(
+ new HttpException("Connection closed while receiving data"));
+ }
+ _closeIncoming(true);
+ _controller.close();
+ return;
+ }
+ // If the connection is idle the HTTP stream is closed.
+ if (_state == _State.START) {
+ if (!_requestParser) {
+ _reportError(new HttpException(
+ "Connection closed before full header was received"));
+ }
+ _controller.close();
+ return;
+ }
+
+ if (_state == _State.UPGRADED) {
+ _controller.close();
+ return;
+ }
+
+ if (_state < _State.FIRST_BODY_STATE) {
+ _state = _State.FAILURE;
+ // Report the error through the error callback if any. Otherwise
+ // throw the error.
+ _reportError(new HttpException(
+ "Connection closed before full header was received"));
+ _controller.close();
+ return;
+ }
+
+ if (!_chunked && _transferLength == -1) {
+ _state = _State.CLOSED;
+ } else {
+ _state = _State.FAILURE;
+ // Report the error through the error callback if any. Otherwise
+ // throw the error.
+ _reportError(new HttpException(
+ "Connection closed before full body was received"));
+ }
+ _controller.close();
+ }
+
+ String get version {
+ switch (_httpVersion) {
+ case _HttpVersion.HTTP10:
+ return "1.0";
+ case _HttpVersion.HTTP11:
+ return "1.1";
+ }
+ return null;
+ }
+
+ int get messageType => _messageType;
+ int get transferLength => _transferLength;
+ bool get upgrade => _connectionUpgrade && _state == _State.UPGRADED;
+ bool get persistentConnection => _persistentConnection;
+
+ void set isHead(bool value) {
+ if (value) _noMessageBody = true;
+ }
+
+ _HttpDetachedIncoming detachIncoming() {
+ // Simulate detached by marking as upgraded.
+ _state = _State.UPGRADED;
+ return new _HttpDetachedIncoming(_socketSubscription,
+ readUnparsedData());
+ }
+
+ List<int> readUnparsedData() {
+ if (_buffer == null) return null;
+ if (_index == _buffer.length) return null;
+ var result = _buffer.sublist(_index);
+ _releaseBuffer();
+ return result;
+ }
+
+ void _reset() {
+ if (_state == _State.UPGRADED) return;
+ _state = _State.START;
+ _messageType = _MessageType.UNDETERMINED;
+ _headerField.clear();
+ _headerValue.clear();
+ _method.clear();
+ _uri_or_reason_phrase.clear();
+
+ _statusCode = 0;
+ _statusCodeLength = 0;
+
+ _httpVersion = _HttpVersion.UNDETERMINED;
+ _transferLength = -1;
+ _persistentConnection = false;
+ _connectionUpgrade = false;
+ _chunked = false;
+
+ _noMessageBody = false;
+ _remainingContent = -1;
+
+ _headers = null;
+ }
+
+ void _releaseBuffer() {
+ _buffer = null;
+ _index = null;
+ }
+
+ static bool _isTokenChar(int byte) {
+ return byte > 31 && byte < 128 && !_Const.SEPARATOR_MAP[byte];
+ }
+
+ static bool _isValueChar(int byte) {
+ return (byte > 31 && byte < 128) || (byte == _CharCode.SP) ||
+ (byte == _CharCode.HT);
+ }
+
+ static List<String> _tokenizeFieldValue(String headerValue) {
+ List<String> tokens = new List<String>();
+ int start = 0;
+ int index = 0;
+ while (index < headerValue.length) {
+ if (headerValue[index] == ",") {
+ tokens.add(headerValue.substring(start, index));
+ start = index + 1;
+ } else if (headerValue[index] == " " || headerValue[index] == "\t") {
+ start++;
+ }
+ index++;
+ }
+ tokens.add(headerValue.substring(start, index));
+ return tokens;
+ }
+
+ static int _toLowerCaseByte(int x) {
+ // Optimized version:
+ // - 0x41 is 'A'
+ // - 0x7f is ASCII mask
+ // - 26 is the number of alpha characters.
+ // - 0x20 is the delta between lower and upper chars.
+ return (((x - 0x41) & 0x7f) < 26) ? (x | 0x20) : x;
+ }
+
+ // expected should already be lowercase.
+ bool _caseInsensitiveCompare(List<int> expected, List<int> value) {
+ if (expected.length != value.length) return false;
+ for (int i = 0; i < expected.length; i++) {
+ if (expected[i] != _toLowerCaseByte(value[i])) return false;
+ }
+ return true;
+ }
+
+ int _expect(int val1, int val2) {
+ if (val1 != val2) {
+ throw new HttpException("Failed to parse HTTP");
+ }
+ }
+
+ int _expectHexDigit(int byte) {
+ if (0x30 <= byte && byte <= 0x39) {
+ return byte - 0x30; // 0 - 9
+ } else if (0x41 <= byte && byte <= 0x46) {
+ return byte - 0x41 + 10; // A - F
+ } else if (0x61 <= byte && byte <= 0x66) {
+ return byte - 0x61 + 10; // a - f
+ } else {
+ throw new HttpException("Failed to parse HTTP");
+ }
+ }
+
+ void _createIncoming(int transferLength) {
+ assert(_incoming == null);
+ assert(_bodyController == null);
+ assert(!_bodyPaused);
+ var incoming;
+ _bodyController = new StreamController<List<int>>(
+ sync: true,
+ onListen: () {
+ if (incoming != _incoming) return;
+ assert(_bodyPaused);
+ _bodyPaused = false;
+ _pauseStateChanged();
+ },
+ onPause: () {
+ if (incoming != _incoming) return;
+ assert(!_bodyPaused);
+ _bodyPaused = true;
+ _pauseStateChanged();
+ },
+ onResume: () {
+ if (incoming != _incoming) return;
+ assert(_bodyPaused);
+ _bodyPaused = false;
+ _pauseStateChanged();
+ },
+ onCancel: () {
+ if (incoming != _incoming) return;
+ if (_socketSubscription != null) {
+ _socketSubscription.cancel();
+ }
+ _closeIncoming(true);
+ _controller.close();
+ });
+ incoming = _incoming = new _HttpIncoming(
+ _headers, transferLength, _bodyController.stream);
+ _bodyPaused = true;
+ _pauseStateChanged();
+ }
+
+ void _closeIncoming([bool closing = false]) {
+ // Ignore multiple close (can happend in re-entrance).
+ if (_incoming == null) return;
+ var tmp = _incoming;
+ tmp.close(closing);
+ _incoming = null;
+ if (_bodyController != null) {
+ _bodyController.close();
+ _bodyController = null;
+ }
+ _bodyPaused = false;
+ _pauseStateChanged();
+ }
+
+ void _pauseStateChanged() {
+ if (_incoming != null) {
+ if (!_bodyPaused && !_parserCalled) {
+ _parse();
+ }
+ } else {
+ if (!_paused && !_parserCalled) {
+ _parse();
+ }
+ }
+ }
+
+ void _reportError(error, [stackTrace]) {
+ if (_socketSubscription != null) _socketSubscription.cancel();
+ _state = _State.FAILURE;
+ _controller.addError(error, stackTrace);
+ _controller.close();
+ }
+}
« no previous file with comments | « tool/input_sdk/lib/io/http_impl.dart ('k') | tool/input_sdk/lib/io/http_session.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698