Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 // Global constants. | 5 // Global constants. |
| 6 class _Const { | 6 class _Const { |
| 7 // Bytes for "HTTP". | 7 // Bytes for "HTTP". |
| 8 static const HTTP = const [72, 84, 84, 80]; | 8 static const HTTP = const [72, 84, 84, 80]; |
| 9 // Bytes for "HTTP/1.". | 9 // Bytes for "HTTP/1.". |
| 10 static const HTTP1DOT = const [72, 84, 84, 80, 47, 49, 46]; | 10 static const HTTP1DOT = const [72, 84, 84, 80, 47, 49, 46]; |
| (...skipping 98 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 109 * thrown from the [:writeList:] and [:connectionClosed:] methods if | 109 * thrown from the [:writeList:] and [:connectionClosed:] methods if |
| 110 * the error callback is not set. | 110 * the error callback is not set. |
| 111 * | 111 * |
| 112 * The connection upgrades (e.g. switching from HTTP/1.1 to the | 112 * The connection upgrades (e.g. switching from HTTP/1.1 to the |
| 113 * WebSocket protocol) is handled in a special way. If connection | 113 * WebSocket protocol) is handled in a special way. If connection |
| 114 * upgrade is specified in the headers, then on the callback to | 114 * upgrade is specified in the headers, then on the callback to |
| 115 * [:headersComplete:] the [:upgrade:] property on the [:HttpParser:] | 115 * [:headersComplete:] the [:upgrade:] property on the [:HttpParser:] |
| 116 * object will be [:true:] indicating that from now on the protocol is | 116 * object will be [:true:] indicating that from now on the protocol is |
| 117 * not HTTP anymore and no more callbacks will happen, that is | 117 * not HTTP anymore and no more callbacks will happen, that is |
| 118 * [:dataReceived:] and [:dataEnd:] are not called in this case as | 118 * [:dataReceived:] and [:dataEnd:] are not called in this case as |
| 119 * there is no more HTTP data. After the upgrade the call to | 119 * there is no more HTTP data. After the upgrade the method |
| 120 * [:writeList:] causing the upgrade will return with the number of | 120 * [:readUnparsedData:] can be used to read any remaining bytes in the |
| 121 * bytes parsed as HTTP. Any unparsed bytes is part of the protocol | 121 * HTTP parser which are part of the protocol the connection is |
| 122 * the connection is upgrading to and should be handled according to | 122 * upgrading to. These bytes cannot be processed by the HTTP parser |
| 123 * that protocol. | 123 * and should be handled according to whatever protocol is being |
| 124 * upgraded to. | |
| 124 */ | 125 */ |
| 125 class _HttpParser { | 126 class _HttpParser { |
| 126 _HttpParser() { | 127 _HttpParser() { |
| 127 _reset(); | 128 _reset(); |
| 128 } | 129 } |
| 129 | 130 |
| 130 // From RFC 2616. | 131 // From RFC 2616. |
| 131 // generic-message = start-line | 132 // generic-message = start-line |
| 132 // *(message-header CRLF) | 133 // *(message-header CRLF) |
| 133 // CRLF | 134 // CRLF |
| 134 // [ message-body ] | 135 // [ message-body ] |
| 135 // start-line = Request-Line | Status-Line | 136 // start-line = Request-Line | Status-Line |
| 136 // Request-Line = Method SP Request-URI SP HTTP-Version CRLF | 137 // Request-Line = Method SP Request-URI SP HTTP-Version CRLF |
| 137 // Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF | 138 // Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF |
| 138 // message-header = field-name ":" [ field-value ] | 139 // message-header = field-name ":" [ field-value ] |
| 139 int writeList(List<int> buffer, int offset, int count) { | 140 void _parse() { |
| 140 int index = offset; | |
| 141 int lastIndex = offset + count; | |
| 142 try { | 141 try { |
| 143 if (_state == _State.CLOSED) { | 142 if (_state == _State.CLOSED) { |
| 144 throw new HttpParserException("Data on closed connection"); | 143 throw new HttpParserException("Data on closed connection"); |
| 145 } | 144 } |
| 146 if (_state == _State.UPGRADED) { | 145 if (_state == _State.UPGRADED) { |
| 147 throw new HttpParserException("Data on upgraded connection"); | 146 throw new HttpParserException("Data on upgraded connection"); |
| 148 } | 147 } |
| 149 if (_state == _State.FAILURE) { | 148 if (_state == _State.FAILURE) { |
| 150 throw new HttpParserException("Data on failed connection"); | 149 throw new HttpParserException("Data on failed connection"); |
| 151 } | 150 } |
| 152 while ((index < lastIndex) && | 151 while (_buffer != null && |
| 152 _index < _lastIndex && | |
| 153 _state != _State.FAILURE && | 153 _state != _State.FAILURE && |
| 154 _state != _State.UPGRADED) { | 154 _state != _State.UPGRADED) { |
| 155 int byte = buffer[index]; | 155 int byte = _buffer[_index]; |
| 156 switch (_state) { | 156 switch (_state) { |
| 157 case _State.START: | 157 case _State.START: |
| 158 if (byte == _Const.HTTP[0]) { | 158 if (byte == _Const.HTTP[0]) { |
| 159 // Start parsing method or HTTP version. | 159 // Start parsing method or HTTP version. |
| 160 _httpVersionIndex = 1; | 160 _httpVersionIndex = 1; |
| 161 _state = _State.METHOD_OR_RESPONSE_HTTP_VERSION; | 161 _state = _State.METHOD_OR_RESPONSE_HTTP_VERSION; |
| 162 } else { | 162 } else { |
| 163 // Start parsing method. | 163 // Start parsing method. |
| 164 if (!_isTokenChar(byte)) { | 164 if (!_isTokenChar(byte)) { |
| 165 throw new HttpParserException("Invalid request method"); | 165 throw new HttpParserException("Invalid request method"); |
| (...skipping 152 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 318 _messageType == _MessageType.RESPONSE; | 318 _messageType == _MessageType.RESPONSE; |
| 319 int statusCode = parseInt(_method_or_status_code.toString()); | 319 int statusCode = parseInt(_method_or_status_code.toString()); |
| 320 if (statusCode < 100 || statusCode > 599) { | 320 if (statusCode < 100 || statusCode > 599) { |
| 321 throw new HttpParserException("Invalid response status code"); | 321 throw new HttpParserException("Invalid response status code"); |
| 322 } else { | 322 } else { |
| 323 // Check whether this response will never have a body. | 323 // Check whether this response will never have a body. |
| 324 _noMessageBody = | 324 _noMessageBody = |
| 325 statusCode <= 199 || statusCode == 204 || statusCode == 304; | 325 statusCode <= 199 || statusCode == 204 || statusCode == 304; |
| 326 } | 326 } |
| 327 if (responseStart != null) { | 327 if (responseStart != null) { |
| 328 responseStart(statusCode, _uri_or_reason_phrase.toString(), versio n); | 328 responseStart(statusCode, |
| 329 _uri_or_reason_phrase.toString(), | |
| 330 version); | |
| 329 } | 331 } |
| 330 _method_or_status_code.clear(); | 332 _method_or_status_code.clear(); |
| 331 _uri_or_reason_phrase.clear(); | 333 _uri_or_reason_phrase.clear(); |
| 332 _state = _State.HEADER_START; | 334 _state = _State.HEADER_START; |
| 333 break; | 335 break; |
| 334 | 336 |
| 335 case _State.HEADER_START: | 337 case _State.HEADER_START: |
| 336 if (byte == _CharCode.CR) { | 338 if (byte == _CharCode.CR) { |
| 337 _state = _State.HEADER_ENDING; | 339 _state = _State.HEADER_ENDING; |
| 338 } else { | 340 } else { |
| (...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 431 // If a request message has neither Content-Length nor | 433 // If a request message has neither Content-Length nor |
| 432 // Transfer-Encoding the message must not have a body (RFC | 434 // Transfer-Encoding the message must not have a body (RFC |
| 433 // 2616 section 4.3). | 435 // 2616 section 4.3). |
| 434 if (_messageType == _MessageType.REQUEST && | 436 if (_messageType == _MessageType.REQUEST && |
| 435 _contentLength < 0 && | 437 _contentLength < 0 && |
| 436 _chunked == false) { | 438 _chunked == false) { |
| 437 _contentLength = 0; | 439 _contentLength = 0; |
| 438 } | 440 } |
| 439 if (_connectionUpgrade) { | 441 if (_connectionUpgrade) { |
| 440 _state = _State.UPGRADED; | 442 _state = _State.UPGRADED; |
| 441 _unparsedData = | 443 // Make sure that readUnparsedData will return the correct data. |
| 442 buffer.getRange(index + 1, count - (index + 1 - offset)); | 444 _index++; |
|
Mads Ager (google)
2012/11/07 13:02:51
The reason for incrementing here and then avoiding
Søren Gjesse
2012/11/07 13:31:14
Yes, but thats not good. Changed the handling of _
| |
| 443 if (headersComplete != null) headersComplete(); | 445 if (headersComplete != null) headersComplete(); |
| 444 } else { | 446 } else { |
| 445 if (headersComplete != null) headersComplete(); | 447 if (headersComplete != null) headersComplete(); |
| 446 if (_chunked) { | 448 if (_chunked) { |
| 447 _state = _State.CHUNK_SIZE; | 449 _state = _State.CHUNK_SIZE; |
| 448 _remainingContent = 0; | 450 _remainingContent = 0; |
| 449 } else if (_contentLength == 0 || | 451 } else if (_contentLength == 0 || |
| 450 (_messageType == _MessageType.RESPONSE && | 452 (_messageType == _MessageType.RESPONSE && |
| 451 (_noMessageBody || _responseToMethod == "HEAD"))) { | 453 (_noMessageBody || _responseToMethod == "HEAD"))) { |
| 452 // If there is no message body get ready to process the | 454 // If there is no message body get ready to process the |
| (...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 506 break; | 508 break; |
| 507 | 509 |
| 508 case _State.CHUNKED_BODY_DONE_LF: | 510 case _State.CHUNKED_BODY_DONE_LF: |
| 509 _expect(byte, _CharCode.LF); | 511 _expect(byte, _CharCode.LF); |
| 510 _bodyEnd(); | 512 _bodyEnd(); |
| 511 _reset(); | 513 _reset(); |
| 512 break; | 514 break; |
| 513 | 515 |
| 514 case _State.BODY: | 516 case _State.BODY: |
| 515 // The body is not handled one byte at a time but in blocks. | 517 // The body is not handled one byte at a time but in blocks. |
| 516 int dataAvailable = lastIndex - index; | 518 int dataAvailable = _lastIndex - _index; |
| 517 List<int> data; | 519 List<int> data; |
| 518 if (_remainingContent == null || | 520 if (_remainingContent == null || |
| 519 dataAvailable <= _remainingContent) { | 521 dataAvailable <= _remainingContent) { |
| 520 data = new Uint8List(dataAvailable); | 522 data = new Uint8List(dataAvailable); |
| 521 data.setRange(0, dataAvailable, buffer, index); | 523 data.setRange(0, dataAvailable, _buffer, _index); |
| 522 } else { | 524 } else { |
| 523 data = new Uint8List(_remainingContent); | 525 data = new Uint8List(_remainingContent); |
| 524 data.setRange(0, _remainingContent, buffer, index); | 526 data.setRange(0, _remainingContent, _buffer, _index); |
| 525 } | 527 } |
| 526 | 528 |
| 527 if (dataReceived != null) dataReceived(data); | 529 if (dataReceived != null) dataReceived(data); |
| 528 if (_remainingContent != null) { | 530 if (_remainingContent != null) { |
| 529 _remainingContent -= data.length; | 531 _remainingContent -= data.length; |
| 530 } | 532 } |
| 531 index += data.length; | 533 _index += data.length; |
| 532 if (_remainingContent == 0) { | 534 if (_remainingContent == 0) { |
| 533 if (!_chunked) { | 535 if (!_chunked) { |
| 534 _bodyEnd(); | 536 _bodyEnd(); |
| 535 _reset(); | 537 _reset(); |
| 536 } else { | 538 } else { |
| 537 _state = _State.CHUNK_SIZE_STARTING_CR; | 539 _state = _State.CHUNK_SIZE_STARTING_CR; |
| 538 } | 540 } |
| 539 } | 541 } |
| 540 | 542 |
| 541 // Hack - as we always do index++ below. | 543 // Hack - as we always do _index++ below. |
| 542 index--; | 544 _index--; |
| 543 break; | 545 break; |
| 544 | 546 |
| 545 case _State.FAILURE: | 547 case _State.FAILURE: |
| 546 // Should be unreachable. | 548 // Should be unreachable. |
| 547 assert(false); | 549 assert(false); |
| 548 break; | 550 break; |
| 549 | 551 |
| 550 default: | 552 default: |
| 551 // Should be unreachable. | 553 // Should be unreachable. |
| 552 assert(false); | 554 assert(false); |
| 553 break; | 555 break; |
| 554 } | 556 } |
| 555 | 557 |
| 556 // Move to the next byte. | 558 // Move to the next byte. |
| 557 index++; | 559 if (_state != _State.UPGRADED) _index++; |
| 558 } | 560 } |
| 559 } catch (e) { | 561 } catch (e) { |
| 560 // Report the error through the error callback if any. Otherwise | 562 // Report the error through the error callback if any. Otherwise |
| 561 // throw the error. | 563 // throw the error. |
| 562 if (error != null) { | 564 if (error != null) { |
| 563 error(e); | 565 error(e); |
| 564 _state = _State.FAILURE; | 566 _state = _State.FAILURE; |
| 565 } else { | 567 } else { |
| 566 throw e; | 568 throw e; |
| 567 } | 569 } |
| 568 } | 570 } |
| 569 | 571 |
| 570 // Return the number of bytes parsed. | 572 // If all data is parsed there is not need to hold on to the buffer. |
|
Mads Ager (google)
2012/11/07 13:02:51
there is not need -> there is no need
Søren Gjesse
2012/11/07 13:31:14
Done.
| |
| 571 return index - offset; | 573 if (_index == _lastIndex) _releaseBuffer(); |
| 574 } | |
| 575 | |
| 576 void writeList(List<int> buffer, int offset, int count) { | |
| 577 assert(_buffer == null); | |
| 578 _buffer = buffer; | |
| 579 _index = offset; | |
| 580 _lastIndex = offset + count; | |
| 581 _parse(); | |
| 572 } | 582 } |
| 573 | 583 |
| 574 void connectionClosed() { | 584 void connectionClosed() { |
| 575 if (_state < _State.FIRST_BODY_STATE) { | 585 if (_state < _State.FIRST_BODY_STATE) { |
| 576 _state = _State.FAILURE; | 586 _state = _State.FAILURE; |
| 577 // Report the error through the error callback if any. Otherwise | 587 // Report the error through the error callback if any. Otherwise |
| 578 // throw the error. | 588 // throw the error. |
| 579 var e = new HttpParserException( | 589 var e = new HttpParserException( |
| 580 "Connection closed before full header was received"); | 590 "Connection closed before full header was received"); |
| 581 if (error != null) { | 591 if (error != null) { |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 616 | 626 |
| 617 int get messageType => _messageType; | 627 int get messageType => _messageType; |
| 618 int get contentLength => _contentLength; | 628 int get contentLength => _contentLength; |
| 619 bool get upgrade => _connectionUpgrade && _state == _State.UPGRADED; | 629 bool get upgrade => _connectionUpgrade && _state == _State.UPGRADED; |
| 620 bool get persistentConnection => _persistentConnection; | 630 bool get persistentConnection => _persistentConnection; |
| 621 | 631 |
| 622 void set responseToMethod(String method) { _responseToMethod = method; } | 632 void set responseToMethod(String method) { _responseToMethod = method; } |
| 623 | 633 |
| 624 bool get isIdle => _state == _State.START; | 634 bool get isIdle => _state == _State.START; |
| 625 | 635 |
| 626 List<int> get unparsedData => _unparsedData; | 636 List<int> readUnparsedData() { |
| 637 if (_buffer == null) return []; | |
| 638 if (_index == _lastIndex) return []; | |
| 639 var result = _buffer.getRange(_index, _lastIndex - _index); | |
| 640 _releaseBuffer(); | |
| 641 return result; | |
| 642 } | |
| 627 | 643 |
| 628 void _bodyEnd() { | 644 void _bodyEnd() { |
| 629 if (dataEnd != null) { | 645 if (dataEnd != null) { |
| 630 dataEnd(_messageType == _MessageType.RESPONSE && !_persistentConnection); | 646 dataEnd(_messageType == _MessageType.RESPONSE && !_persistentConnection); |
| 631 } | 647 } |
| 632 } | 648 } |
| 633 | 649 |
| 634 _reset() { | 650 _reset() { |
| 635 _state = _State.START; | 651 _state = _State.START; |
| 636 _messageType = _MessageType.UNDETERMINED; | 652 _messageType = _MessageType.UNDETERMINED; |
| 637 _headerField = new StringBuffer(); | 653 _headerField = new StringBuffer(); |
| 638 _headerValue = new StringBuffer(); | 654 _headerValue = new StringBuffer(); |
| 639 _method_or_status_code = new StringBuffer(); | 655 _method_or_status_code = new StringBuffer(); |
| 640 _uri_or_reason_phrase = new StringBuffer(); | 656 _uri_or_reason_phrase = new StringBuffer(); |
| 641 | 657 |
| 642 _httpVersion = _HttpVersion.UNDETERMINED; | 658 _httpVersion = _HttpVersion.UNDETERMINED; |
| 643 _contentLength = -1; | 659 _contentLength = -1; |
| 644 _persistentConnection = false; | 660 _persistentConnection = false; |
| 645 _connectionUpgrade = false; | 661 _connectionUpgrade = false; |
| 646 _chunked = false; | 662 _chunked = false; |
| 647 | 663 |
| 648 _noMessageBody = false; | 664 _noMessageBody = false; |
| 649 _responseToMethod = null; | 665 _responseToMethod = null; |
| 650 _remainingContent = null; | 666 _remainingContent = null; |
| 651 } | 667 } |
| 652 | 668 |
| 669 _releaseBuffer() { | |
| 670 _buffer = null; | |
| 671 _index = null; | |
| 672 _lastIndex = null; | |
| 673 } | |
| 674 | |
| 653 bool _isTokenChar(int byte) { | 675 bool _isTokenChar(int byte) { |
| 654 return byte > 31 && byte < 128 && _Const.SEPARATORS.indexOf(byte) == -1; | 676 return byte > 31 && byte < 128 && _Const.SEPARATORS.indexOf(byte) == -1; |
| 655 } | 677 } |
| 656 | 678 |
| 657 List<String> _tokenizeFieldValue(String headerValue) { | 679 List<String> _tokenizeFieldValue(String headerValue) { |
| 658 List<String> tokens = new List<String>(); | 680 List<String> tokens = new List<String>(); |
| 659 int start = 0; | 681 int start = 0; |
| 660 int index = 0; | 682 int index = 0; |
| 661 while (index < headerValue.length) { | 683 while (index < headerValue.length) { |
| 662 if (headerValue[index] == ",") { | 684 if (headerValue[index] == ",") { |
| (...skipping 26 matching lines...) Expand all Loading... | |
| 689 return byte - 0x30; // 0 - 9 | 711 return byte - 0x30; // 0 - 9 |
| 690 } else if (0x41 <= byte && byte <= 0x46) { | 712 } else if (0x41 <= byte && byte <= 0x46) { |
| 691 return byte - 0x41 + 10; // A - F | 713 return byte - 0x41 + 10; // A - F |
| 692 } else if (0x61 <= byte && byte <= 0x66) { | 714 } else if (0x61 <= byte && byte <= 0x66) { |
| 693 return byte - 0x61 + 10; // a - f | 715 return byte - 0x61 + 10; // a - f |
| 694 } else { | 716 } else { |
| 695 throw new HttpParserException("Failed to parse HTTP"); | 717 throw new HttpParserException("Failed to parse HTTP"); |
| 696 } | 718 } |
| 697 } | 719 } |
| 698 | 720 |
| 721 // The data currently parsed. | |
|
Mads Ager (google)
2012/11/07 13:02:51
The data that is currently being parsed?
Søren Gjesse
2012/11/07 13:31:14
Done.
| |
| 722 List<int> _buffer; | |
| 723 int _index; | |
| 724 int _lastIndex; | |
| 725 | |
| 699 int _state; | 726 int _state; |
| 700 int _httpVersionIndex; | 727 int _httpVersionIndex; |
| 701 int _messageType; | 728 int _messageType; |
| 702 StringBuffer _method_or_status_code; | 729 StringBuffer _method_or_status_code; |
| 703 StringBuffer _uri_or_reason_phrase; | 730 StringBuffer _uri_or_reason_phrase; |
| 704 StringBuffer _headerField; | 731 StringBuffer _headerField; |
| 705 StringBuffer _headerValue; | 732 StringBuffer _headerValue; |
| 706 | 733 |
| 707 int _httpVersion; | 734 int _httpVersion; |
| 708 int _contentLength; | 735 int _contentLength; |
| 709 bool _persistentConnection; | 736 bool _persistentConnection; |
| 710 bool _connectionUpgrade; | 737 bool _connectionUpgrade; |
| 711 bool _chunked; | 738 bool _chunked; |
| 712 | 739 |
| 713 bool _noMessageBody; | 740 bool _noMessageBody; |
| 714 String _responseToMethod; // Indicates the method used for the request. | 741 String _responseToMethod; // Indicates the method used for the request. |
| 715 int _remainingContent; | 742 int _remainingContent; |
| 716 | 743 |
| 717 List<int> _unparsedData; // Unparsed data after connection upgrade. | |
| 718 // Callbacks. | 744 // Callbacks. |
| 719 Function requestStart; | 745 Function requestStart; |
| 720 Function responseStart; | 746 Function responseStart; |
| 721 Function headerReceived; | 747 Function headerReceived; |
| 722 Function headersComplete; | 748 Function headersComplete; |
| 723 Function dataReceived; | 749 Function dataReceived; |
| 724 Function dataEnd; | 750 Function dataEnd; |
| 725 Function error; | 751 Function error; |
| 726 } | 752 } |
| 727 | 753 |
| 728 | 754 |
| 729 class HttpParserException implements Exception { | 755 class HttpParserException implements Exception { |
| 730 const HttpParserException([String this.message = ""]); | 756 const HttpParserException([String this.message = ""]); |
| 731 String toString() => "HttpParserException: $message"; | 757 String toString() => "HttpParserException: $message"; |
| 732 final String message; | 758 final String message; |
| 733 } | 759 } |
| OLD | NEW |