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 happend 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 |