| Index: pkg/dev_compiler/tool/input_sdk/lib/io/http_parser.dart
|
| diff --git a/pkg/dev_compiler/tool/input_sdk/lib/io/http_parser.dart b/pkg/dev_compiler/tool/input_sdk/lib/io/http_parser.dart
|
| deleted file mode 100644
|
| index adef61fad5552972e137a0091469ede602a5be1e..0000000000000000000000000000000000000000
|
| --- a/pkg/dev_compiler/tool/input_sdk/lib/io/http_parser.dart
|
| +++ /dev/null
|
| @@ -1,1072 +0,0 @@
|
| -// 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 happen 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();
|
| - }
|
| -}
|
|
|