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 = | |
| 442 buffer.getRange(index + 1, count - (index + 1 - offset)); | |
| 443 if (headersComplete != null) headersComplete(); | 443 if (headersComplete != null) headersComplete(); |
| 444 } else { | 444 } else { |
| 445 if (headersComplete != null) headersComplete(); | 445 if (headersComplete != null) headersComplete(); |
| 446 if (_chunked) { | 446 if (_chunked) { |
| 447 _state = _State.CHUNK_SIZE; | 447 _state = _State.CHUNK_SIZE; |
| 448 _remainingContent = 0; | 448 _remainingContent = 0; |
| 449 } else if (_contentLength == 0 || | 449 } else if (_contentLength == 0 || |
| 450 (_messageType == _MessageType.RESPONSE && | 450 (_messageType == _MessageType.RESPONSE && |
| 451 (_noMessageBody || _responseToMethod == "HEAD"))) { | 451 (_noMessageBody || _responseToMethod == "HEAD"))) { |
| 452 // If there is no message body get ready to process the | 452 // 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; | 506 break; |
| 507 | 507 |
| 508 case _State.CHUNKED_BODY_DONE_LF: | 508 case _State.CHUNKED_BODY_DONE_LF: |
| 509 _expect(byte, _CharCode.LF); | 509 _expect(byte, _CharCode.LF); |
| 510 _bodyEnd(); | 510 _bodyEnd(); |
| 511 _reset(); | 511 _reset(); |
| 512 break; | 512 break; |
| 513 | 513 |
| 514 case _State.BODY: | 514 case _State.BODY: |
| 515 // The body is not handled one byte at a time but in blocks. | 515 // The body is not handled one byte at a time but in blocks. |
| 516 int dataAvailable = lastIndex - index; | 516 _index--; |
| 517 int dataAvailable = _lastIndex - _index; | |
| 517 List<int> data; | 518 List<int> data; |
| 518 if (_remainingContent == null || | 519 if (_remainingContent == null || |
| 519 dataAvailable <= _remainingContent) { | 520 dataAvailable <= _remainingContent) { |
| 520 data = new Uint8List(dataAvailable); | 521 data = new Uint8List(dataAvailable); |
| 521 data.setRange(0, dataAvailable, buffer, index); | 522 data.setRange(0, dataAvailable, _buffer, _index); |
| 522 } else { | 523 } else { |
| 523 data = new Uint8List(_remainingContent); | 524 data = new Uint8List(_remainingContent); |
| 524 data.setRange(0, _remainingContent, buffer, index); | 525 data.setRange(0, _remainingContent, _buffer, _index); |
| 525 } | 526 } |
| 526 | 527 |
| 527 if (dataReceived != null) dataReceived(data); | 528 if (dataReceived != null) dataReceived(data); |
| 528 if (_remainingContent != null) { | 529 if (_remainingContent != null) { |
| 529 _remainingContent -= data.length; | 530 _remainingContent -= data.length; |
| 530 } | 531 } |
| 531 index += data.length; | 532 _index += data.length; |
| 532 if (_remainingContent == 0) { | 533 if (_remainingContent == 0) { |
| 533 if (!_chunked) { | 534 if (!_chunked) { |
| 534 _bodyEnd(); | 535 _bodyEnd(); |
| 535 _reset(); | 536 _reset(); |
| 536 } else { | 537 } else { |
| 537 _state = _State.CHUNK_SIZE_STARTING_CR; | 538 _state = _State.CHUNK_SIZE_STARTING_CR; |
| 538 } | 539 } |
| 539 } | 540 } |
| 540 | |
| 541 // Hack - as we always do index++ below. | |
| 542 index--; | |
| 543 break; | 541 break; |
| 544 | 542 |
| 545 case _State.FAILURE: | 543 case _State.FAILURE: |
| 546 // Should be unreachable. | 544 // Should be unreachable. |
| 547 assert(false); | 545 assert(false); |
| 548 break; | 546 break; |
| 549 | 547 |
| 550 default: | 548 default: |
| 551 // Should be unreachable. | 549 // Should be unreachable. |
| 552 assert(false); | 550 assert(false); |
| 553 break; | 551 break; |
| 554 } | 552 } |
| 555 | |
| 556 // Move to the next byte. | |
| 557 index++; | |
| 558 } | 553 } |
| 559 } catch (e) { | 554 } catch (e) { |
| 560 // Report the error through the error callback if any. Otherwise | 555 // Report the error through the error callback if any. Otherwise |
| 561 // throw the error. | 556 // throw the error. |
| 562 if (error != null) { | 557 if (error != null) { |
| 563 error(e); | 558 error(e); |
| 564 _state = _State.FAILURE; | 559 _state = _State.FAILURE; |
| 565 } else { | 560 } else { |
| 566 throw e; | 561 throw e; |
| 567 } | 562 } |
| 568 } | 563 } |
| 569 | 564 |
| 570 // Return the number of bytes parsed. | 565 // If all data is parsed there is no need to hold on to the buffer. |
| 571 return index - offset; | 566 if (_index == _lastIndex) _releaseBuffer(); |
|
Mads Ager (google)
2012/11/07 13:36:43
Can we make this stronger? Maybe add an assert tha
Søren Gjesse
2012/11/07 13:58:34
Changed to release buffer except when connection i
| |
| 567 } | |
| 568 | |
| 569 void writeList(List<int> buffer, int offset, int count) { | |
| 570 assert(_buffer == null); | |
| 571 _buffer = buffer; | |
| 572 _index = offset; | |
| 573 _lastIndex = offset + count; | |
| 574 _parse(); | |
| 572 } | 575 } |
| 573 | 576 |
| 574 void connectionClosed() { | 577 void connectionClosed() { |
| 575 if (_state < _State.FIRST_BODY_STATE) { | 578 if (_state < _State.FIRST_BODY_STATE) { |
| 576 _state = _State.FAILURE; | 579 _state = _State.FAILURE; |
| 577 // Report the error through the error callback if any. Otherwise | 580 // Report the error through the error callback if any. Otherwise |
| 578 // throw the error. | 581 // throw the error. |
| 579 var e = new HttpParserException( | 582 var e = new HttpParserException( |
| 580 "Connection closed before full header was received"); | 583 "Connection closed before full header was received"); |
| 581 if (error != null) { | 584 if (error != null) { |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 616 | 619 |
| 617 int get messageType => _messageType; | 620 int get messageType => _messageType; |
| 618 int get contentLength => _contentLength; | 621 int get contentLength => _contentLength; |
| 619 bool get upgrade => _connectionUpgrade && _state == _State.UPGRADED; | 622 bool get upgrade => _connectionUpgrade && _state == _State.UPGRADED; |
| 620 bool get persistentConnection => _persistentConnection; | 623 bool get persistentConnection => _persistentConnection; |
| 621 | 624 |
| 622 void set responseToMethod(String method) { _responseToMethod = method; } | 625 void set responseToMethod(String method) { _responseToMethod = method; } |
| 623 | 626 |
| 624 bool get isIdle => _state == _State.START; | 627 bool get isIdle => _state == _State.START; |
| 625 | 628 |
| 626 List<int> get unparsedData => _unparsedData; | 629 List<int> readUnparsedData() { |
| 630 if (_buffer == null) return []; | |
| 631 if (_index == _lastIndex) return []; | |
| 632 var result = _buffer.getRange(_index, _lastIndex - _index); | |
| 633 _releaseBuffer(); | |
| 634 return result; | |
| 635 } | |
| 627 | 636 |
| 628 void _bodyEnd() { | 637 void _bodyEnd() { |
| 629 if (dataEnd != null) { | 638 if (dataEnd != null) { |
| 630 dataEnd(_messageType == _MessageType.RESPONSE && !_persistentConnection); | 639 dataEnd(_messageType == _MessageType.RESPONSE && !_persistentConnection); |
| 631 } | 640 } |
| 632 } | 641 } |
| 633 | 642 |
| 634 _reset() { | 643 _reset() { |
| 635 _state = _State.START; | 644 _state = _State.START; |
| 636 _messageType = _MessageType.UNDETERMINED; | 645 _messageType = _MessageType.UNDETERMINED; |
| 637 _headerField = new StringBuffer(); | 646 _headerField = new StringBuffer(); |
| 638 _headerValue = new StringBuffer(); | 647 _headerValue = new StringBuffer(); |
| 639 _method_or_status_code = new StringBuffer(); | 648 _method_or_status_code = new StringBuffer(); |
| 640 _uri_or_reason_phrase = new StringBuffer(); | 649 _uri_or_reason_phrase = new StringBuffer(); |
| 641 | 650 |
| 642 _httpVersion = _HttpVersion.UNDETERMINED; | 651 _httpVersion = _HttpVersion.UNDETERMINED; |
| 643 _contentLength = -1; | 652 _contentLength = -1; |
| 644 _persistentConnection = false; | 653 _persistentConnection = false; |
| 645 _connectionUpgrade = false; | 654 _connectionUpgrade = false; |
| 646 _chunked = false; | 655 _chunked = false; |
| 647 | 656 |
| 648 _noMessageBody = false; | 657 _noMessageBody = false; |
| 649 _responseToMethod = null; | 658 _responseToMethod = null; |
| 650 _remainingContent = null; | 659 _remainingContent = null; |
| 651 } | 660 } |
| 652 | 661 |
| 662 _releaseBuffer() { | |
| 663 _buffer = null; | |
| 664 _index = null; | |
| 665 _lastIndex = null; | |
| 666 } | |
| 667 | |
| 653 bool _isTokenChar(int byte) { | 668 bool _isTokenChar(int byte) { |
| 654 return byte > 31 && byte < 128 && _Const.SEPARATORS.indexOf(byte) == -1; | 669 return byte > 31 && byte < 128 && _Const.SEPARATORS.indexOf(byte) == -1; |
| 655 } | 670 } |
| 656 | 671 |
| 657 List<String> _tokenizeFieldValue(String headerValue) { | 672 List<String> _tokenizeFieldValue(String headerValue) { |
| 658 List<String> tokens = new List<String>(); | 673 List<String> tokens = new List<String>(); |
| 659 int start = 0; | 674 int start = 0; |
| 660 int index = 0; | 675 int index = 0; |
| 661 while (index < headerValue.length) { | 676 while (index < headerValue.length) { |
| 662 if (headerValue[index] == ",") { | 677 if (headerValue[index] == ",") { |
| (...skipping 26 matching lines...) Expand all Loading... | |
| 689 return byte - 0x30; // 0 - 9 | 704 return byte - 0x30; // 0 - 9 |
| 690 } else if (0x41 <= byte && byte <= 0x46) { | 705 } else if (0x41 <= byte && byte <= 0x46) { |
| 691 return byte - 0x41 + 10; // A - F | 706 return byte - 0x41 + 10; // A - F |
| 692 } else if (0x61 <= byte && byte <= 0x66) { | 707 } else if (0x61 <= byte && byte <= 0x66) { |
| 693 return byte - 0x61 + 10; // a - f | 708 return byte - 0x61 + 10; // a - f |
| 694 } else { | 709 } else { |
| 695 throw new HttpParserException("Failed to parse HTTP"); | 710 throw new HttpParserException("Failed to parse HTTP"); |
| 696 } | 711 } |
| 697 } | 712 } |
| 698 | 713 |
| 714 // The data that is currently being parsed. | |
| 715 List<int> _buffer; | |
| 716 int _index; | |
| 717 int _lastIndex; | |
| 718 | |
| 699 int _state; | 719 int _state; |
| 700 int _httpVersionIndex; | 720 int _httpVersionIndex; |
| 701 int _messageType; | 721 int _messageType; |
| 702 StringBuffer _method_or_status_code; | 722 StringBuffer _method_or_status_code; |
| 703 StringBuffer _uri_or_reason_phrase; | 723 StringBuffer _uri_or_reason_phrase; |
| 704 StringBuffer _headerField; | 724 StringBuffer _headerField; |
| 705 StringBuffer _headerValue; | 725 StringBuffer _headerValue; |
| 706 | 726 |
| 707 int _httpVersion; | 727 int _httpVersion; |
| 708 int _contentLength; | 728 int _contentLength; |
| 709 bool _persistentConnection; | 729 bool _persistentConnection; |
| 710 bool _connectionUpgrade; | 730 bool _connectionUpgrade; |
| 711 bool _chunked; | 731 bool _chunked; |
| 712 | 732 |
| 713 bool _noMessageBody; | 733 bool _noMessageBody; |
| 714 String _responseToMethod; // Indicates the method used for the request. | 734 String _responseToMethod; // Indicates the method used for the request. |
| 715 int _remainingContent; | 735 int _remainingContent; |
| 716 | 736 |
| 717 List<int> _unparsedData; // Unparsed data after connection upgrade. | |
| 718 // Callbacks. | 737 // Callbacks. |
| 719 Function requestStart; | 738 Function requestStart; |
| 720 Function responseStart; | 739 Function responseStart; |
| 721 Function headerReceived; | 740 Function headerReceived; |
| 722 Function headersComplete; | 741 Function headersComplete; |
| 723 Function dataReceived; | 742 Function dataReceived; |
| 724 Function dataEnd; | 743 Function dataEnd; |
| 725 Function error; | 744 Function error; |
| 726 } | 745 } |
| 727 | 746 |
| 728 | 747 |
| 729 class HttpParserException implements Exception { | 748 class HttpParserException implements Exception { |
| 730 const HttpParserException([String this.message = ""]); | 749 const HttpParserException([String this.message = ""]); |
| 731 String toString() => "HttpParserException: $message"; | 750 String toString() => "HttpParserException: $message"; |
| 732 final String message; | 751 final String message; |
| 733 } | 752 } |
| OLD | NEW |