| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 part of dart.io; | |
| 6 | |
| 7 // Global constants. | |
| 8 class _Const { | |
| 9 // Bytes for "HTTP". | |
| 10 static const HTTP = const [72, 84, 84, 80]; | |
| 11 // Bytes for "HTTP/1.". | |
| 12 static const HTTP1DOT = const [72, 84, 84, 80, 47, 49, 46]; | |
| 13 // Bytes for "HTTP/1.0". | |
| 14 static const HTTP10 = const [72, 84, 84, 80, 47, 49, 46, 48]; | |
| 15 // Bytes for "HTTP/1.1". | |
| 16 static const HTTP11 = const [72, 84, 84, 80, 47, 49, 46, 49]; | |
| 17 | |
| 18 static const bool T = true; | |
| 19 static const bool F = false; | |
| 20 // Loopup-map for the following characters: '()<>@,;:\\"/[]?={} \t'. | |
| 21 static const SEPARATOR_MAP = const [ | |
| 22 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, | |
| 23 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, | |
| 24 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, | |
| 25 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, | |
| 26 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, | |
| 27 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, | |
| 28 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]; | |
| 29 } | |
| 30 | |
| 31 | |
| 32 // Frequently used character codes. | |
| 33 class _CharCode { | |
| 34 static const int HT = 9; | |
| 35 static const int LF = 10; | |
| 36 static const int CR = 13; | |
| 37 static const int SP = 32; | |
| 38 static const int AMPERSAND = 38; | |
| 39 static const int COMMA = 44; | |
| 40 static const int DASH = 45; | |
| 41 static const int SLASH = 47; | |
| 42 static const int ZERO = 48; | |
| 43 static const int ONE = 49; | |
| 44 static const int COLON = 58; | |
| 45 static const int SEMI_COLON = 59; | |
| 46 static const int EQUAL = 61; | |
| 47 } | |
| 48 | |
| 49 | |
| 50 // States of the HTTP parser state machine. | |
| 51 class _State { | |
| 52 static const int START = 0; | |
| 53 static const int METHOD_OR_RESPONSE_HTTP_VERSION = 1; | |
| 54 static const int RESPONSE_HTTP_VERSION = 2; | |
| 55 static const int REQUEST_LINE_METHOD = 3; | |
| 56 static const int REQUEST_LINE_URI = 4; | |
| 57 static const int REQUEST_LINE_HTTP_VERSION = 5; | |
| 58 static const int REQUEST_LINE_ENDING = 6; | |
| 59 static const int RESPONSE_LINE_STATUS_CODE = 7; | |
| 60 static const int RESPONSE_LINE_REASON_PHRASE = 8; | |
| 61 static const int RESPONSE_LINE_ENDING = 9; | |
| 62 static const int HEADER_START = 10; | |
| 63 static const int HEADER_FIELD = 11; | |
| 64 static const int HEADER_VALUE_START = 12; | |
| 65 static const int HEADER_VALUE = 13; | |
| 66 static const int HEADER_VALUE_FOLDING_OR_ENDING = 14; | |
| 67 static const int HEADER_VALUE_FOLD_OR_END = 15; | |
| 68 static const int HEADER_ENDING = 16; | |
| 69 | |
| 70 static const int CHUNK_SIZE_STARTING_CR = 17; | |
| 71 static const int CHUNK_SIZE_STARTING_LF = 18; | |
| 72 static const int CHUNK_SIZE = 19; | |
| 73 static const int CHUNK_SIZE_EXTENSION = 20; | |
| 74 static const int CHUNK_SIZE_ENDING = 21; | |
| 75 static const int CHUNKED_BODY_DONE_CR = 22; | |
| 76 static const int CHUNKED_BODY_DONE_LF = 23; | |
| 77 static const int BODY = 24; | |
| 78 static const int CLOSED = 25; | |
| 79 static const int UPGRADED = 26; | |
| 80 static const int FAILURE = 27; | |
| 81 | |
| 82 static const int FIRST_BODY_STATE = CHUNK_SIZE_STARTING_CR; | |
| 83 } | |
| 84 | |
| 85 // HTTP version of the request or response being parsed. | |
| 86 class _HttpVersion { | |
| 87 static const int UNDETERMINED = 0; | |
| 88 static const int HTTP10 = 1; | |
| 89 static const int HTTP11 = 2; | |
| 90 } | |
| 91 | |
| 92 // States of the HTTP parser state machine. | |
| 93 class _MessageType { | |
| 94 static const int UNDETERMINED = 0; | |
| 95 static const int REQUEST = 1; | |
| 96 static const int RESPONSE = 0; | |
| 97 } | |
| 98 | |
| 99 | |
| 100 /** | |
| 101 * The _HttpDetachedStreamSubscription takes a subscription and some extra data, | |
| 102 * and makes it possible to "inject" the data in from of other data events | |
| 103 * from the subscription. | |
| 104 * | |
| 105 * It does so by overriding pause/resume, so that once the | |
| 106 * _HttpDetachedStreamSubscription is resumed, it'll deliver the data before | |
| 107 * resuming the underlaying subscription. | |
| 108 */ | |
| 109 class _HttpDetachedStreamSubscription implements StreamSubscription<List<int>> { | |
| 110 StreamSubscription<List<int>> _subscription; | |
| 111 List<int> _injectData; | |
| 112 bool _isCanceled = false; | |
| 113 int _pauseCount = 1; | |
| 114 Function _userOnData; | |
| 115 bool _scheduled = false; | |
| 116 | |
| 117 _HttpDetachedStreamSubscription(this._subscription, | |
| 118 this._injectData, | |
| 119 this._userOnData); | |
| 120 | |
| 121 bool get isPaused => _subscription.isPaused; | |
| 122 | |
| 123 Future/*<T>*/ asFuture/*<T>*/([/*=T*/ futureValue]) => | |
| 124 _subscription.asFuture/*<T>*/(futureValue); | |
| 125 | |
| 126 Future cancel() { | |
| 127 _isCanceled = true; | |
| 128 _injectData = null; | |
| 129 return _subscription.cancel(); | |
| 130 } | |
| 131 | |
| 132 void onData(void handleData(List<int> data)) { | |
| 133 _userOnData = handleData; | |
| 134 _subscription.onData(handleData); | |
| 135 } | |
| 136 | |
| 137 void onDone(void handleDone()) { | |
| 138 _subscription.onDone(handleDone); | |
| 139 } | |
| 140 | |
| 141 void onError(Function handleError) { | |
| 142 _subscription.onError(handleError); | |
| 143 } | |
| 144 | |
| 145 void pause([Future resumeSignal]) { | |
| 146 if (_injectData == null) { | |
| 147 _subscription.pause(resumeSignal); | |
| 148 } else { | |
| 149 _pauseCount++; | |
| 150 if (resumeSignal != null) { | |
| 151 resumeSignal.whenComplete(resume); | |
| 152 } | |
| 153 } | |
| 154 } | |
| 155 | |
| 156 void resume() { | |
| 157 if (_injectData == null) { | |
| 158 _subscription.resume(); | |
| 159 } else { | |
| 160 _pauseCount--; | |
| 161 _maybeScheduleData(); | |
| 162 } | |
| 163 } | |
| 164 | |
| 165 void _maybeScheduleData() { | |
| 166 if (_scheduled) return; | |
| 167 if (_pauseCount != 0) return; | |
| 168 _scheduled = true; | |
| 169 scheduleMicrotask(() { | |
| 170 _scheduled = false; | |
| 171 if (_pauseCount > 0 || _isCanceled) return; | |
| 172 var data = _injectData; | |
| 173 _injectData = null; | |
| 174 // To ensure that 'subscription.isPaused' is false, we resume the | |
| 175 // subscription here. This is fine as potential events are delayed. | |
| 176 _subscription.resume(); | |
| 177 if (_userOnData != null) { | |
| 178 _userOnData(data); | |
| 179 } | |
| 180 }); | |
| 181 } | |
| 182 } | |
| 183 | |
| 184 | |
| 185 class _HttpDetachedIncoming extends Stream<List<int>> { | |
| 186 final StreamSubscription subscription; | |
| 187 final List<int> bufferedData; | |
| 188 | |
| 189 _HttpDetachedIncoming(this.subscription, this.bufferedData); | |
| 190 | |
| 191 StreamSubscription<List<int>> listen(void onData(List<int> event), | |
| 192 {Function onError, | |
| 193 void onDone(), | |
| 194 bool cancelOnError}) { | |
| 195 if (subscription != null) { | |
| 196 subscription | |
| 197 ..onData(onData) | |
| 198 ..onError(onError) | |
| 199 ..onDone(onDone); | |
| 200 if (bufferedData == null) { | |
| 201 return subscription..resume(); | |
| 202 } | |
| 203 return new _HttpDetachedStreamSubscription(subscription, | |
| 204 bufferedData, | |
| 205 onData) | |
| 206 ..resume(); | |
| 207 } else { | |
| 208 return new Stream.fromIterable(bufferedData) | |
| 209 .listen(onData, | |
| 210 onError: onError, | |
| 211 onDone: onDone, | |
| 212 cancelOnError: cancelOnError); | |
| 213 } | |
| 214 } | |
| 215 } | |
| 216 | |
| 217 | |
| 218 /** | |
| 219 * HTTP parser which parses the data stream given to [consume]. | |
| 220 * | |
| 221 * If an HTTP parser error occours, the parser will signal an error to either | |
| 222 * the current _HttpIncoming or the _parser itself. | |
| 223 * | |
| 224 * The connection upgrades (e.g. switching from HTTP/1.1 to the | |
| 225 * WebSocket protocol) is handled in a special way. If connection | |
| 226 * upgrade is specified in the headers, then on the callback to | |
| 227 * [:responseStart:] the [:upgrade:] property on the [:HttpParser:] | |
| 228 * object will be [:true:] indicating that from now on the protocol is | |
| 229 * not HTTP anymore and no more callbacks will happen, that is | |
| 230 * [:dataReceived:] and [:dataEnd:] are not called in this case as | |
| 231 * there is no more HTTP data. After the upgrade the method | |
| 232 * [:readUnparsedData:] can be used to read any remaining bytes in the | |
| 233 * HTTP parser which are part of the protocol the connection is | |
| 234 * upgrading to. These bytes cannot be processed by the HTTP parser | |
| 235 * and should be handled according to whatever protocol is being | |
| 236 * upgraded to. | |
| 237 */ | |
| 238 class _HttpParser extends Stream<_HttpIncoming> { | |
| 239 // State. | |
| 240 bool _parserCalled = false; | |
| 241 | |
| 242 // The data that is currently being parsed. | |
| 243 Uint8List _buffer; | |
| 244 int _index; | |
| 245 | |
| 246 final bool _requestParser; | |
| 247 int _state; | |
| 248 int _httpVersionIndex; | |
| 249 int _messageType; | |
| 250 int _statusCode = 0; | |
| 251 int _statusCodeLength = 0; | |
| 252 final List<int> _method = []; | |
| 253 final List<int> _uri_or_reason_phrase = []; | |
| 254 final List<int> _headerField = []; | |
| 255 final List<int> _headerValue = []; | |
| 256 | |
| 257 int _httpVersion; | |
| 258 int _transferLength = -1; | |
| 259 bool _persistentConnection; | |
| 260 bool _connectionUpgrade; | |
| 261 bool _chunked; | |
| 262 | |
| 263 bool _noMessageBody = false; | |
| 264 int _remainingContent = -1; | |
| 265 | |
| 266 _HttpHeaders _headers; | |
| 267 | |
| 268 // The current incoming connection. | |
| 269 _HttpIncoming _incoming; | |
| 270 StreamSubscription _socketSubscription; | |
| 271 bool _paused = true; | |
| 272 bool _bodyPaused = false; | |
| 273 StreamController<_HttpIncoming> _controller; | |
| 274 StreamController<List<int>> _bodyController; | |
| 275 | |
| 276 factory _HttpParser.requestParser() { | |
| 277 return new _HttpParser._(true); | |
| 278 } | |
| 279 | |
| 280 factory _HttpParser.responseParser() { | |
| 281 return new _HttpParser._(false); | |
| 282 } | |
| 283 | |
| 284 _HttpParser._(this._requestParser) { | |
| 285 _controller = new StreamController<_HttpIncoming>( | |
| 286 sync: true, | |
| 287 onListen: () { | |
| 288 _paused = false; | |
| 289 }, | |
| 290 onPause: () { | |
| 291 _paused = true; | |
| 292 _pauseStateChanged(); | |
| 293 }, | |
| 294 onResume: () { | |
| 295 _paused = false; | |
| 296 _pauseStateChanged(); | |
| 297 }, | |
| 298 onCancel: () { | |
| 299 if (_socketSubscription != null) { | |
| 300 _socketSubscription.cancel(); | |
| 301 } | |
| 302 }); | |
| 303 _reset(); | |
| 304 } | |
| 305 | |
| 306 | |
| 307 StreamSubscription<_HttpIncoming> listen(void onData(_HttpIncoming event), | |
| 308 {Function onError, | |
| 309 void onDone(), | |
| 310 bool cancelOnError}) { | |
| 311 return _controller.stream.listen(onData, | |
| 312 onError: onError, | |
| 313 onDone: onDone, | |
| 314 cancelOnError: cancelOnError); | |
| 315 } | |
| 316 | |
| 317 void listenToStream(Stream<List<int>> stream) { | |
| 318 // Listen to the stream and handle data accordingly. When a | |
| 319 // _HttpIncoming is created, _dataPause, _dataResume, _dataDone is | |
| 320 // given to provide a way of controlling the parser. | |
| 321 // TODO(ajohnsen): Remove _dataPause, _dataResume and _dataDone and clean up | |
| 322 // how the _HttpIncoming signals the parser. | |
| 323 _socketSubscription = stream.listen( | |
| 324 _onData, | |
| 325 onError: _controller.addError, | |
| 326 onDone: _onDone); | |
| 327 } | |
| 328 | |
| 329 void _parse() { | |
| 330 try { | |
| 331 _doParse(); | |
| 332 } catch (e, s) { | |
| 333 _state = _State.FAILURE; | |
| 334 _reportError(e, s); | |
| 335 } | |
| 336 } | |
| 337 | |
| 338 // Process end of headers. Returns true if the parser should stop | |
| 339 // parsing and return. This will be in case of either an upgrade | |
| 340 // request or a request or response with an empty body. | |
| 341 bool _headersEnd() { | |
| 342 _headers._mutable = false; | |
| 343 | |
| 344 _transferLength = _headers.contentLength; | |
| 345 // Ignore the Content-Length header if Transfer-Encoding | |
| 346 // is chunked (RFC 2616 section 4.4) | |
| 347 if (_chunked) _transferLength = -1; | |
| 348 | |
| 349 // If a request message has neither Content-Length nor | |
| 350 // Transfer-Encoding the message must not have a body (RFC | |
| 351 // 2616 section 4.3). | |
| 352 if (_messageType == _MessageType.REQUEST && | |
| 353 _transferLength < 0 && | |
| 354 _chunked == false) { | |
| 355 _transferLength = 0; | |
| 356 } | |
| 357 if (_connectionUpgrade) { | |
| 358 _state = _State.UPGRADED; | |
| 359 _transferLength = 0; | |
| 360 } | |
| 361 _createIncoming(_transferLength); | |
| 362 if (_requestParser) { | |
| 363 _incoming.method = | |
| 364 new String.fromCharCodes(_method); | |
| 365 _incoming.uri = | |
| 366 Uri.parse( | |
| 367 new String.fromCharCodes(_uri_or_reason_phrase)); | |
| 368 } else { | |
| 369 _incoming.statusCode = _statusCode; | |
| 370 _incoming.reasonPhrase = | |
| 371 new String.fromCharCodes(_uri_or_reason_phrase); | |
| 372 } | |
| 373 _method.clear(); | |
| 374 _uri_or_reason_phrase.clear(); | |
| 375 if (_connectionUpgrade) { | |
| 376 _incoming.upgraded = true; | |
| 377 _parserCalled = false; | |
| 378 var tmp = _incoming; | |
| 379 _closeIncoming(); | |
| 380 _controller.add(tmp); | |
| 381 return true; | |
| 382 } | |
| 383 if (_transferLength == 0 || | |
| 384 (_messageType == _MessageType.RESPONSE && _noMessageBody)) { | |
| 385 _reset(); | |
| 386 var tmp = _incoming; | |
| 387 _closeIncoming(); | |
| 388 _controller.add(tmp); | |
| 389 return false; | |
| 390 } else if (_chunked) { | |
| 391 _state = _State.CHUNK_SIZE; | |
| 392 _remainingContent = 0; | |
| 393 } else if (_transferLength > 0) { | |
| 394 _remainingContent = _transferLength; | |
| 395 _state = _State.BODY; | |
| 396 } else { | |
| 397 // Neither chunked nor content length. End of body | |
| 398 // indicated by close. | |
| 399 _state = _State.BODY; | |
| 400 } | |
| 401 _parserCalled = false; | |
| 402 _controller.add(_incoming); | |
| 403 return true; | |
| 404 } | |
| 405 | |
| 406 // From RFC 2616. | |
| 407 // generic-message = start-line | |
| 408 // *(message-header CRLF) | |
| 409 // CRLF | |
| 410 // [ message-body ] | |
| 411 // start-line = Request-Line | Status-Line | |
| 412 // Request-Line = Method SP Request-URI SP HTTP-Version CRLF | |
| 413 // Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF | |
| 414 // message-header = field-name ":" [ field-value ] | |
| 415 void _doParse() { | |
| 416 assert(!_parserCalled); | |
| 417 _parserCalled = true; | |
| 418 if (_state == _State.CLOSED) { | |
| 419 throw new HttpException("Data on closed connection"); | |
| 420 } | |
| 421 if (_state == _State.FAILURE) { | |
| 422 throw new HttpException("Data on failed connection"); | |
| 423 } | |
| 424 while (_buffer != null && | |
| 425 _index < _buffer.length && | |
| 426 _state != _State.FAILURE && | |
| 427 _state != _State.UPGRADED) { | |
| 428 // Depending on _incoming, we either break on _bodyPaused or _paused. | |
| 429 if ((_incoming != null && _bodyPaused) || | |
| 430 (_incoming == null && _paused)) { | |
| 431 _parserCalled = false; | |
| 432 return; | |
| 433 } | |
| 434 int byte = _buffer[_index++]; | |
| 435 switch (_state) { | |
| 436 case _State.START: | |
| 437 if (byte == _Const.HTTP[0]) { | |
| 438 // Start parsing method or HTTP version. | |
| 439 _httpVersionIndex = 1; | |
| 440 _state = _State.METHOD_OR_RESPONSE_HTTP_VERSION; | |
| 441 } else { | |
| 442 // Start parsing method. | |
| 443 if (!_isTokenChar(byte)) { | |
| 444 throw new HttpException("Invalid request method"); | |
| 445 } | |
| 446 _method.add(byte); | |
| 447 if (!_requestParser) { | |
| 448 throw new HttpException("Invalid response line"); | |
| 449 } | |
| 450 _state = _State.REQUEST_LINE_METHOD; | |
| 451 } | |
| 452 break; | |
| 453 | |
| 454 case _State.METHOD_OR_RESPONSE_HTTP_VERSION: | |
| 455 if (_httpVersionIndex < _Const.HTTP.length && | |
| 456 byte == _Const.HTTP[_httpVersionIndex]) { | |
| 457 // Continue parsing HTTP version. | |
| 458 _httpVersionIndex++; | |
| 459 } else if (_httpVersionIndex == _Const.HTTP.length && | |
| 460 byte == _CharCode.SLASH) { | |
| 461 // HTTP/ parsed. As method is a token this cannot be a | |
| 462 // method anymore. | |
| 463 _httpVersionIndex++; | |
| 464 if (_requestParser) { | |
| 465 throw new HttpException("Invalid request line"); | |
| 466 } | |
| 467 _state = _State.RESPONSE_HTTP_VERSION; | |
| 468 } else { | |
| 469 // Did not parse HTTP version. Expect method instead. | |
| 470 for (int i = 0; i < _httpVersionIndex; i++) { | |
| 471 _method.add(_Const.HTTP[i]); | |
| 472 } | |
| 473 if (byte == _CharCode.SP) { | |
| 474 _state = _State.REQUEST_LINE_URI; | |
| 475 } else { | |
| 476 _method.add(byte); | |
| 477 _httpVersion = _HttpVersion.UNDETERMINED; | |
| 478 if (!_requestParser) { | |
| 479 throw new HttpException("Invalid response line"); | |
| 480 } | |
| 481 _state = _State.REQUEST_LINE_METHOD; | |
| 482 } | |
| 483 } | |
| 484 break; | |
| 485 | |
| 486 case _State.RESPONSE_HTTP_VERSION: | |
| 487 if (_httpVersionIndex < _Const.HTTP1DOT.length) { | |
| 488 // Continue parsing HTTP version. | |
| 489 _expect(byte, _Const.HTTP1DOT[_httpVersionIndex]); | |
| 490 _httpVersionIndex++; | |
| 491 } else if (_httpVersionIndex == _Const.HTTP1DOT.length && | |
| 492 byte == _CharCode.ONE) { | |
| 493 // HTTP/1.1 parsed. | |
| 494 _httpVersion = _HttpVersion.HTTP11; | |
| 495 _persistentConnection = true; | |
| 496 _httpVersionIndex++; | |
| 497 } else if (_httpVersionIndex == _Const.HTTP1DOT.length && | |
| 498 byte == _CharCode.ZERO) { | |
| 499 // HTTP/1.0 parsed. | |
| 500 _httpVersion = _HttpVersion.HTTP10; | |
| 501 _persistentConnection = false; | |
| 502 _httpVersionIndex++; | |
| 503 } else if (_httpVersionIndex == _Const.HTTP1DOT.length + 1) { | |
| 504 _expect(byte, _CharCode.SP); | |
| 505 // HTTP version parsed. | |
| 506 _state = _State.RESPONSE_LINE_STATUS_CODE; | |
| 507 } else { | |
| 508 throw new HttpException("Invalid response line"); | |
| 509 } | |
| 510 break; | |
| 511 | |
| 512 case _State.REQUEST_LINE_METHOD: | |
| 513 if (byte == _CharCode.SP) { | |
| 514 _state = _State.REQUEST_LINE_URI; | |
| 515 } else { | |
| 516 if (_Const.SEPARATOR_MAP[byte] || | |
| 517 byte == _CharCode.CR || | |
| 518 byte == _CharCode.LF) { | |
| 519 throw new HttpException("Invalid request method"); | |
| 520 } | |
| 521 _method.add(byte); | |
| 522 } | |
| 523 break; | |
| 524 | |
| 525 case _State.REQUEST_LINE_URI: | |
| 526 if (byte == _CharCode.SP) { | |
| 527 if (_uri_or_reason_phrase.length == 0) { | |
| 528 throw new HttpException("Invalid request URI"); | |
| 529 } | |
| 530 _state = _State.REQUEST_LINE_HTTP_VERSION; | |
| 531 _httpVersionIndex = 0; | |
| 532 } else { | |
| 533 if (byte == _CharCode.CR || byte == _CharCode.LF) { | |
| 534 throw new HttpException("Invalid request URI"); | |
| 535 } | |
| 536 _uri_or_reason_phrase.add(byte); | |
| 537 } | |
| 538 break; | |
| 539 | |
| 540 case _State.REQUEST_LINE_HTTP_VERSION: | |
| 541 if (_httpVersionIndex < _Const.HTTP1DOT.length) { | |
| 542 _expect(byte, _Const.HTTP11[_httpVersionIndex]); | |
| 543 _httpVersionIndex++; | |
| 544 } else if (_httpVersionIndex == _Const.HTTP1DOT.length) { | |
| 545 if (byte == _CharCode.ONE) { | |
| 546 // HTTP/1.1 parsed. | |
| 547 _httpVersion = _HttpVersion.HTTP11; | |
| 548 _persistentConnection = true; | |
| 549 _httpVersionIndex++; | |
| 550 } else if (byte == _CharCode.ZERO) { | |
| 551 // HTTP/1.0 parsed. | |
| 552 _httpVersion = _HttpVersion.HTTP10; | |
| 553 _persistentConnection = false; | |
| 554 _httpVersionIndex++; | |
| 555 } else { | |
| 556 throw new HttpException("Invalid response line"); | |
| 557 } | |
| 558 } else { | |
| 559 if (byte == _CharCode.CR) { | |
| 560 _state = _State.REQUEST_LINE_ENDING; | |
| 561 } else { | |
| 562 _expect(byte, _CharCode.LF); | |
| 563 _messageType = _MessageType.REQUEST; | |
| 564 _state = _State.HEADER_START; | |
| 565 } | |
| 566 } | |
| 567 break; | |
| 568 | |
| 569 case _State.REQUEST_LINE_ENDING: | |
| 570 _expect(byte, _CharCode.LF); | |
| 571 _messageType = _MessageType.REQUEST; | |
| 572 _state = _State.HEADER_START; | |
| 573 break; | |
| 574 | |
| 575 case _State.RESPONSE_LINE_STATUS_CODE: | |
| 576 if (byte == _CharCode.SP) { | |
| 577 _state = _State.RESPONSE_LINE_REASON_PHRASE; | |
| 578 } else if (byte == _CharCode.CR) { | |
| 579 // Some HTTP servers does not follow the spec. and send | |
| 580 // \r\n right after the status code. | |
| 581 _state = _State.RESPONSE_LINE_ENDING; | |
| 582 } else { | |
| 583 _statusCodeLength++; | |
| 584 if ((byte < 0x30 && 0x39 < byte) || _statusCodeLength > 3) { | |
| 585 throw new HttpException("Invalid response status code"); | |
| 586 } else { | |
| 587 _statusCode = _statusCode * 10 + byte - 0x30; | |
| 588 } | |
| 589 } | |
| 590 break; | |
| 591 | |
| 592 case _State.RESPONSE_LINE_REASON_PHRASE: | |
| 593 if (byte == _CharCode.CR) { | |
| 594 _state = _State.RESPONSE_LINE_ENDING; | |
| 595 } else { | |
| 596 if (byte == _CharCode.CR || byte == _CharCode.LF) { | |
| 597 throw new HttpException("Invalid response reason phrase"); | |
| 598 } | |
| 599 _uri_or_reason_phrase.add(byte); | |
| 600 } | |
| 601 break; | |
| 602 | |
| 603 case _State.RESPONSE_LINE_ENDING: | |
| 604 _expect(byte, _CharCode.LF); | |
| 605 _messageType == _MessageType.RESPONSE; | |
| 606 if (_statusCode < 100 || _statusCode > 599) { | |
| 607 throw new HttpException("Invalid response status code"); | |
| 608 } else { | |
| 609 // Check whether this response will never have a body. | |
| 610 if (_statusCode <= 199 || _statusCode == 204 || | |
| 611 _statusCode == 304) { | |
| 612 _noMessageBody = true; | |
| 613 } | |
| 614 } | |
| 615 _state = _State.HEADER_START; | |
| 616 break; | |
| 617 | |
| 618 case _State.HEADER_START: | |
| 619 _headers = new _HttpHeaders(version); | |
| 620 if (byte == _CharCode.CR) { | |
| 621 _state = _State.HEADER_ENDING; | |
| 622 } else if (byte == _CharCode.LF) { | |
| 623 _state = _State.HEADER_ENDING; | |
| 624 _index--; // Make the new state see the LF again. | |
| 625 } else { | |
| 626 // Start of new header field. | |
| 627 _headerField.add(_toLowerCaseByte(byte)); | |
| 628 _state = _State.HEADER_FIELD; | |
| 629 } | |
| 630 break; | |
| 631 | |
| 632 case _State.HEADER_FIELD: | |
| 633 if (byte == _CharCode.COLON) { | |
| 634 _state = _State.HEADER_VALUE_START; | |
| 635 } else { | |
| 636 if (!_isTokenChar(byte)) { | |
| 637 throw new HttpException("Invalid header field name"); | |
| 638 } | |
| 639 _headerField.add(_toLowerCaseByte(byte)); | |
| 640 } | |
| 641 break; | |
| 642 | |
| 643 case _State.HEADER_VALUE_START: | |
| 644 if (byte == _CharCode.CR) { | |
| 645 _state = _State.HEADER_VALUE_FOLDING_OR_ENDING; | |
| 646 } else if (byte == _CharCode.LF) { | |
| 647 _state = _State.HEADER_VALUE_FOLD_OR_END; | |
| 648 } else if (byte != _CharCode.SP && byte != _CharCode.HT) { | |
| 649 // Start of new header value. | |
| 650 _headerValue.add(byte); | |
| 651 _state = _State.HEADER_VALUE; | |
| 652 } | |
| 653 break; | |
| 654 | |
| 655 case _State.HEADER_VALUE: | |
| 656 if (byte == _CharCode.CR) { | |
| 657 _state = _State.HEADER_VALUE_FOLDING_OR_ENDING; | |
| 658 } else if (byte == _CharCode.LF) { | |
| 659 _state = _State.HEADER_VALUE_FOLD_OR_END; | |
| 660 } else { | |
| 661 _headerValue.add(byte); | |
| 662 } | |
| 663 break; | |
| 664 | |
| 665 case _State.HEADER_VALUE_FOLDING_OR_ENDING: | |
| 666 _expect(byte, _CharCode.LF); | |
| 667 _state = _State.HEADER_VALUE_FOLD_OR_END; | |
| 668 break; | |
| 669 | |
| 670 case _State.HEADER_VALUE_FOLD_OR_END: | |
| 671 if (byte == _CharCode.SP || byte == _CharCode.HT) { | |
| 672 _state = _State.HEADER_VALUE_START; | |
| 673 } else { | |
| 674 String headerField = new String.fromCharCodes(_headerField); | |
| 675 String headerValue = new String.fromCharCodes(_headerValue); | |
| 676 if (headerField == "transfer-encoding" && | |
| 677 _caseInsensitiveCompare("chunked".codeUnits, _headerValue)) { | |
| 678 _chunked = true; | |
| 679 } | |
| 680 if (headerField == "connection") { | |
| 681 List<String> tokens = _tokenizeFieldValue(headerValue); | |
| 682 for (int i = 0; i < tokens.length; i++) { | |
| 683 if (_caseInsensitiveCompare("upgrade".codeUnits, | |
| 684 tokens[i].codeUnits)) { | |
| 685 _connectionUpgrade = true; | |
| 686 } | |
| 687 _headers._add(headerField, tokens[i]); | |
| 688 } | |
| 689 } else { | |
| 690 _headers._add(headerField, headerValue); | |
| 691 } | |
| 692 _headerField.clear(); | |
| 693 _headerValue.clear(); | |
| 694 | |
| 695 if (byte == _CharCode.CR) { | |
| 696 _state = _State.HEADER_ENDING; | |
| 697 } else if (byte == _CharCode.LF) { | |
| 698 _state = _State.HEADER_ENDING; | |
| 699 _index--; // Make the new state see the LF again. | |
| 700 } else { | |
| 701 // Start of new header field. | |
| 702 _headerField.add(_toLowerCaseByte(byte)); | |
| 703 _state = _State.HEADER_FIELD; | |
| 704 } | |
| 705 } | |
| 706 break; | |
| 707 | |
| 708 case _State.HEADER_ENDING: | |
| 709 _expect(byte, _CharCode.LF); | |
| 710 if (_headersEnd()) { | |
| 711 return; | |
| 712 } else { | |
| 713 break; | |
| 714 } | |
| 715 return; | |
| 716 | |
| 717 case _State.CHUNK_SIZE_STARTING_CR: | |
| 718 _expect(byte, _CharCode.CR); | |
| 719 _state = _State.CHUNK_SIZE_STARTING_LF; | |
| 720 break; | |
| 721 | |
| 722 case _State.CHUNK_SIZE_STARTING_LF: | |
| 723 _expect(byte, _CharCode.LF); | |
| 724 _state = _State.CHUNK_SIZE; | |
| 725 break; | |
| 726 | |
| 727 case _State.CHUNK_SIZE: | |
| 728 if (byte == _CharCode.CR) { | |
| 729 _state = _State.CHUNK_SIZE_ENDING; | |
| 730 } else if (byte == _CharCode.SEMI_COLON) { | |
| 731 _state = _State.CHUNK_SIZE_EXTENSION; | |
| 732 } else { | |
| 733 int value = _expectHexDigit(byte); | |
| 734 _remainingContent = _remainingContent * 16 + value; | |
| 735 } | |
| 736 break; | |
| 737 | |
| 738 case _State.CHUNK_SIZE_EXTENSION: | |
| 739 if (byte == _CharCode.CR) { | |
| 740 _state = _State.CHUNK_SIZE_ENDING; | |
| 741 } | |
| 742 break; | |
| 743 | |
| 744 case _State.CHUNK_SIZE_ENDING: | |
| 745 _expect(byte, _CharCode.LF); | |
| 746 if (_remainingContent > 0) { | |
| 747 _state = _State.BODY; | |
| 748 } else { | |
| 749 _state = _State.CHUNKED_BODY_DONE_CR; | |
| 750 } | |
| 751 break; | |
| 752 | |
| 753 case _State.CHUNKED_BODY_DONE_CR: | |
| 754 _expect(byte, _CharCode.CR); | |
| 755 _state = _State.CHUNKED_BODY_DONE_LF; | |
| 756 break; | |
| 757 | |
| 758 case _State.CHUNKED_BODY_DONE_LF: | |
| 759 _expect(byte, _CharCode.LF); | |
| 760 _reset(); | |
| 761 _closeIncoming(); | |
| 762 break; | |
| 763 | |
| 764 case _State.BODY: | |
| 765 // The body is not handled one byte at a time but in blocks. | |
| 766 _index--; | |
| 767 int dataAvailable = _buffer.length - _index; | |
| 768 if (_remainingContent >= 0 && dataAvailable > _remainingContent) { | |
| 769 dataAvailable = _remainingContent; | |
| 770 } | |
| 771 // Always present the data as a view. This way we can handle all | |
| 772 // cases like this, and the user will not experince different data | |
| 773 // typed (which could lead to polymorphic user code). | |
| 774 List<int> data = new Uint8List.view(_buffer.buffer, | |
| 775 _buffer.offsetInBytes + _index, | |
| 776 dataAvailable); | |
| 777 _bodyController.add(data); | |
| 778 if (_remainingContent != -1) { | |
| 779 _remainingContent -= data.length; | |
| 780 } | |
| 781 _index += data.length; | |
| 782 if (_remainingContent == 0) { | |
| 783 if (!_chunked) { | |
| 784 _reset(); | |
| 785 _closeIncoming(); | |
| 786 } else { | |
| 787 _state = _State.CHUNK_SIZE_STARTING_CR; | |
| 788 } | |
| 789 } | |
| 790 break; | |
| 791 | |
| 792 case _State.FAILURE: | |
| 793 // Should be unreachable. | |
| 794 assert(false); | |
| 795 break; | |
| 796 | |
| 797 default: | |
| 798 // Should be unreachable. | |
| 799 assert(false); | |
| 800 break; | |
| 801 } | |
| 802 } | |
| 803 | |
| 804 _parserCalled = false; | |
| 805 if (_buffer != null && _index == _buffer.length) { | |
| 806 // If all data is parsed release the buffer and resume receiving | |
| 807 // data. | |
| 808 _releaseBuffer(); | |
| 809 if (_state != _State.UPGRADED && _state != _State.FAILURE) { | |
| 810 _socketSubscription.resume(); | |
| 811 } | |
| 812 } | |
| 813 } | |
| 814 | |
| 815 void _onData(List<int> buffer) { | |
| 816 _socketSubscription.pause(); | |
| 817 assert(_buffer == null); | |
| 818 _buffer = buffer; | |
| 819 _index = 0; | |
| 820 _parse(); | |
| 821 } | |
| 822 | |
| 823 void _onDone() { | |
| 824 // onDone cancles the subscription. | |
| 825 _socketSubscription = null; | |
| 826 if (_state == _State.CLOSED || _state == _State.FAILURE) return; | |
| 827 | |
| 828 if (_incoming != null) { | |
| 829 if (_state != _State.UPGRADED && | |
| 830 !(_state == _State.START && !_requestParser) && | |
| 831 !(_state == _State.BODY && !_chunked && _transferLength == -1)) { | |
| 832 _bodyController.addError( | |
| 833 new HttpException("Connection closed while receiving data")); | |
| 834 } | |
| 835 _closeIncoming(true); | |
| 836 _controller.close(); | |
| 837 return; | |
| 838 } | |
| 839 // If the connection is idle the HTTP stream is closed. | |
| 840 if (_state == _State.START) { | |
| 841 if (!_requestParser) { | |
| 842 _reportError(new HttpException( | |
| 843 "Connection closed before full header was received")); | |
| 844 } | |
| 845 _controller.close(); | |
| 846 return; | |
| 847 } | |
| 848 | |
| 849 if (_state == _State.UPGRADED) { | |
| 850 _controller.close(); | |
| 851 return; | |
| 852 } | |
| 853 | |
| 854 if (_state < _State.FIRST_BODY_STATE) { | |
| 855 _state = _State.FAILURE; | |
| 856 // Report the error through the error callback if any. Otherwise | |
| 857 // throw the error. | |
| 858 _reportError(new HttpException( | |
| 859 "Connection closed before full header was received")); | |
| 860 _controller.close(); | |
| 861 return; | |
| 862 } | |
| 863 | |
| 864 if (!_chunked && _transferLength == -1) { | |
| 865 _state = _State.CLOSED; | |
| 866 } else { | |
| 867 _state = _State.FAILURE; | |
| 868 // Report the error through the error callback if any. Otherwise | |
| 869 // throw the error. | |
| 870 _reportError(new HttpException( | |
| 871 "Connection closed before full body was received")); | |
| 872 } | |
| 873 _controller.close(); | |
| 874 } | |
| 875 | |
| 876 String get version { | |
| 877 switch (_httpVersion) { | |
| 878 case _HttpVersion.HTTP10: | |
| 879 return "1.0"; | |
| 880 case _HttpVersion.HTTP11: | |
| 881 return "1.1"; | |
| 882 } | |
| 883 return null; | |
| 884 } | |
| 885 | |
| 886 int get messageType => _messageType; | |
| 887 int get transferLength => _transferLength; | |
| 888 bool get upgrade => _connectionUpgrade && _state == _State.UPGRADED; | |
| 889 bool get persistentConnection => _persistentConnection; | |
| 890 | |
| 891 void set isHead(bool value) { | |
| 892 if (value) _noMessageBody = true; | |
| 893 } | |
| 894 | |
| 895 _HttpDetachedIncoming detachIncoming() { | |
| 896 // Simulate detached by marking as upgraded. | |
| 897 _state = _State.UPGRADED; | |
| 898 return new _HttpDetachedIncoming(_socketSubscription, | |
| 899 readUnparsedData()); | |
| 900 } | |
| 901 | |
| 902 List<int> readUnparsedData() { | |
| 903 if (_buffer == null) return null; | |
| 904 if (_index == _buffer.length) return null; | |
| 905 var result = _buffer.sublist(_index); | |
| 906 _releaseBuffer(); | |
| 907 return result; | |
| 908 } | |
| 909 | |
| 910 void _reset() { | |
| 911 if (_state == _State.UPGRADED) return; | |
| 912 _state = _State.START; | |
| 913 _messageType = _MessageType.UNDETERMINED; | |
| 914 _headerField.clear(); | |
| 915 _headerValue.clear(); | |
| 916 _method.clear(); | |
| 917 _uri_or_reason_phrase.clear(); | |
| 918 | |
| 919 _statusCode = 0; | |
| 920 _statusCodeLength = 0; | |
| 921 | |
| 922 _httpVersion = _HttpVersion.UNDETERMINED; | |
| 923 _transferLength = -1; | |
| 924 _persistentConnection = false; | |
| 925 _connectionUpgrade = false; | |
| 926 _chunked = false; | |
| 927 | |
| 928 _noMessageBody = false; | |
| 929 _remainingContent = -1; | |
| 930 | |
| 931 _headers = null; | |
| 932 } | |
| 933 | |
| 934 void _releaseBuffer() { | |
| 935 _buffer = null; | |
| 936 _index = null; | |
| 937 } | |
| 938 | |
| 939 static bool _isTokenChar(int byte) { | |
| 940 return byte > 31 && byte < 128 && !_Const.SEPARATOR_MAP[byte]; | |
| 941 } | |
| 942 | |
| 943 static bool _isValueChar(int byte) { | |
| 944 return (byte > 31 && byte < 128) || (byte == _CharCode.SP) || | |
| 945 (byte == _CharCode.HT); | |
| 946 } | |
| 947 | |
| 948 static List<String> _tokenizeFieldValue(String headerValue) { | |
| 949 List<String> tokens = new List<String>(); | |
| 950 int start = 0; | |
| 951 int index = 0; | |
| 952 while (index < headerValue.length) { | |
| 953 if (headerValue[index] == ",") { | |
| 954 tokens.add(headerValue.substring(start, index)); | |
| 955 start = index + 1; | |
| 956 } else if (headerValue[index] == " " || headerValue[index] == "\t") { | |
| 957 start++; | |
| 958 } | |
| 959 index++; | |
| 960 } | |
| 961 tokens.add(headerValue.substring(start, index)); | |
| 962 return tokens; | |
| 963 } | |
| 964 | |
| 965 static int _toLowerCaseByte(int x) { | |
| 966 // Optimized version: | |
| 967 // - 0x41 is 'A' | |
| 968 // - 0x7f is ASCII mask | |
| 969 // - 26 is the number of alpha characters. | |
| 970 // - 0x20 is the delta between lower and upper chars. | |
| 971 return (((x - 0x41) & 0x7f) < 26) ? (x | 0x20) : x; | |
| 972 } | |
| 973 | |
| 974 // expected should already be lowercase. | |
| 975 bool _caseInsensitiveCompare(List<int> expected, List<int> value) { | |
| 976 if (expected.length != value.length) return false; | |
| 977 for (int i = 0; i < expected.length; i++) { | |
| 978 if (expected[i] != _toLowerCaseByte(value[i])) return false; | |
| 979 } | |
| 980 return true; | |
| 981 } | |
| 982 | |
| 983 int _expect(int val1, int val2) { | |
| 984 if (val1 != val2) { | |
| 985 throw new HttpException("Failed to parse HTTP"); | |
| 986 } | |
| 987 } | |
| 988 | |
| 989 int _expectHexDigit(int byte) { | |
| 990 if (0x30 <= byte && byte <= 0x39) { | |
| 991 return byte - 0x30; // 0 - 9 | |
| 992 } else if (0x41 <= byte && byte <= 0x46) { | |
| 993 return byte - 0x41 + 10; // A - F | |
| 994 } else if (0x61 <= byte && byte <= 0x66) { | |
| 995 return byte - 0x61 + 10; // a - f | |
| 996 } else { | |
| 997 throw new HttpException("Failed to parse HTTP"); | |
| 998 } | |
| 999 } | |
| 1000 | |
| 1001 void _createIncoming(int transferLength) { | |
| 1002 assert(_incoming == null); | |
| 1003 assert(_bodyController == null); | |
| 1004 assert(!_bodyPaused); | |
| 1005 var incoming; | |
| 1006 _bodyController = new StreamController<List<int>>( | |
| 1007 sync: true, | |
| 1008 onListen: () { | |
| 1009 if (incoming != _incoming) return; | |
| 1010 assert(_bodyPaused); | |
| 1011 _bodyPaused = false; | |
| 1012 _pauseStateChanged(); | |
| 1013 }, | |
| 1014 onPause: () { | |
| 1015 if (incoming != _incoming) return; | |
| 1016 assert(!_bodyPaused); | |
| 1017 _bodyPaused = true; | |
| 1018 _pauseStateChanged(); | |
| 1019 }, | |
| 1020 onResume: () { | |
| 1021 if (incoming != _incoming) return; | |
| 1022 assert(_bodyPaused); | |
| 1023 _bodyPaused = false; | |
| 1024 _pauseStateChanged(); | |
| 1025 }, | |
| 1026 onCancel: () { | |
| 1027 if (incoming != _incoming) return; | |
| 1028 if (_socketSubscription != null) { | |
| 1029 _socketSubscription.cancel(); | |
| 1030 } | |
| 1031 _closeIncoming(true); | |
| 1032 _controller.close(); | |
| 1033 }); | |
| 1034 incoming = _incoming = new _HttpIncoming( | |
| 1035 _headers, transferLength, _bodyController.stream); | |
| 1036 _bodyPaused = true; | |
| 1037 _pauseStateChanged(); | |
| 1038 } | |
| 1039 | |
| 1040 void _closeIncoming([bool closing = false]) { | |
| 1041 // Ignore multiple close (can happen in re-entrance). | |
| 1042 if (_incoming == null) return; | |
| 1043 var tmp = _incoming; | |
| 1044 tmp.close(closing); | |
| 1045 _incoming = null; | |
| 1046 if (_bodyController != null) { | |
| 1047 _bodyController.close(); | |
| 1048 _bodyController = null; | |
| 1049 } | |
| 1050 _bodyPaused = false; | |
| 1051 _pauseStateChanged(); | |
| 1052 } | |
| 1053 | |
| 1054 void _pauseStateChanged() { | |
| 1055 if (_incoming != null) { | |
| 1056 if (!_bodyPaused && !_parserCalled) { | |
| 1057 _parse(); | |
| 1058 } | |
| 1059 } else { | |
| 1060 if (!_paused && !_parserCalled) { | |
| 1061 _parse(); | |
| 1062 } | |
| 1063 } | |
| 1064 } | |
| 1065 | |
| 1066 void _reportError(error, [stackTrace]) { | |
| 1067 if (_socketSubscription != null) _socketSubscription.cancel(); | |
| 1068 _state = _State.FAILURE; | |
| 1069 _controller.addError(error, stackTrace); | |
| 1070 _controller.close(); | |
| 1071 } | |
| 1072 } | |
| OLD | NEW |