| 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/1.0". | 7 // Bytes for "HTTP/1.0". |
| 8 static final HTTP10 = const [72, 84, 84, 80, 47, 49, 46, 48]; | 8 static final HTTP10 = const [72, 84, 84, 80, 47, 49, 46, 48]; |
| 9 // Bytes for "HTTP/1.1". | 9 // Bytes for "HTTP/1.1". |
| 10 static final HTTP11 = const [72, 84, 84, 80, 47, 49, 46, 49]; | 10 static final HTTP11 = const [72, 84, 84, 80, 47, 49, 46, 49]; |
| (...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 54 | 54 |
| 55 static final int CHUNK_SIZE_STARTING_CR = 16; | 55 static final int CHUNK_SIZE_STARTING_CR = 16; |
| 56 static final int CHUNK_SIZE_STARTING_LF = 17; | 56 static final int CHUNK_SIZE_STARTING_LF = 17; |
| 57 static final int CHUNK_SIZE = 18; | 57 static final int CHUNK_SIZE = 18; |
| 58 static final int CHUNK_SIZE_EXTENSION = 19; | 58 static final int CHUNK_SIZE_EXTENSION = 19; |
| 59 static final int CHUNK_SIZE_ENDING = 20; | 59 static final int CHUNK_SIZE_ENDING = 20; |
| 60 static final int CHUNKED_BODY_DONE_CR = 21; | 60 static final int CHUNKED_BODY_DONE_CR = 21; |
| 61 static final int CHUNKED_BODY_DONE_LF = 22; | 61 static final int CHUNKED_BODY_DONE_LF = 22; |
| 62 static final int BODY = 23; | 62 static final int BODY = 23; |
| 63 static final int CLOSED = 24; | 63 static final int CLOSED = 24; |
| 64 static final int FAILURE = 25; | 64 static final int UPGRADED = 25; |
| 65 static final int FAILURE = 26; |
| 65 | 66 |
| 66 static final int FIRST_BODY_STATE = CHUNK_SIZE_STARTING_CR; | 67 static final int FIRST_BODY_STATE = CHUNK_SIZE_STARTING_CR; |
| 67 } | 68 } |
| 68 | 69 |
| 69 | 70 |
| 70 // States of the HTTP parser state machine. | 71 // States of the HTTP parser state machine. |
| 71 class _MessageType { | 72 class _MessageType { |
| 72 static final int UNDETERMINED = 0; | 73 static final int UNDETERMINED = 0; |
| 73 static final int REQUEST = 1; | 74 static final int REQUEST = 1; |
| 74 static final int RESPONSE = 0; | 75 static final int RESPONSE = 0; |
| (...skipping 10 matching lines...) Expand all Loading... |
| 85 * [:headerReceived:] | 86 * [:headerReceived:] |
| 86 * [:headersComplete:] | 87 * [:headersComplete:] |
| 87 * [:dataReceived:] | 88 * [:dataReceived:] |
| 88 * [:dataEnd:] | 89 * [:dataEnd:] |
| 89 * [:error:] | 90 * [:error:] |
| 90 * | 91 * |
| 91 * If an HTTP parser error occours it is possible to get an exception | 92 * If an HTTP parser error occours it is possible to get an exception |
| 92 * thrown from the [:writeList:] and [:connectionClosed:] methods if | 93 * thrown from the [:writeList:] and [:connectionClosed:] methods if |
| 93 * the error callback is not set. | 94 * the error callback is not set. |
| 94 * | 95 * |
| 95 * For keep-alive connections where the underlying connection stays | 96 * The connection upgrades (e.g. switching from HTTP/1.1 to the |
| 96 * open the [:connectionClosed:] method would not be called. | 97 * WebSocket protocol) is handled in a special way. If connection |
| 98 * upgrade is specified in the headers, then on the callback to |
| 99 * [:headersComplete:] the [:upgrade:] property on the [:HttpParser:] |
| 100 * object will be [:true:] indicating that from now on the protocol is |
| 101 * not HTTP anymore and no more callbacks will happen, that is |
| 102 * [:dataReceived:] and [:dataEnd:] are not called in this case as |
| 103 * there is no more HTTP data. After the upgrade the call to |
| 104 * [:writeList:] causing the upgrade will return with the number of |
| 105 * bytes parsed as HTTP. Any unparsed bytes is part of the protocol |
| 106 * the connection is upgrading to and should be handled according to |
| 107 * that protocol. |
| 97 */ | 108 */ |
| 98 class _HttpParser { | 109 class _HttpParser { |
| 99 _HttpParser() { | 110 _HttpParser() { |
| 100 _reset(); | 111 _reset(); |
| 101 } | 112 } |
| 102 | 113 |
| 103 // From RFC 2616. | 114 // From RFC 2616. |
| 104 // generic-message = start-line | 115 // generic-message = start-line |
| 105 // *(message-header CRLF) | 116 // *(message-header CRLF) |
| 106 // CRLF | 117 // CRLF |
| 107 // [ message-body ] | 118 // [ message-body ] |
| 108 // start-line = Request-Line | Status-Line | 119 // start-line = Request-Line | Status-Line |
| 109 // Request-Line = Method SP Request-URI SP HTTP-Version CRLF | 120 // Request-Line = Method SP Request-URI SP HTTP-Version CRLF |
| 110 // Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF | 121 // Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF |
| 111 // message-header = field-name ":" [ field-value ] | 122 // message-header = field-name ":" [ field-value ] |
| 112 int writeList(List<int> buffer, int offset, int count) { | 123 int writeList(List<int> buffer, int offset, int count) { |
| 113 int index = offset; | 124 int index = offset; |
| 114 int lastIndex = offset + count; | 125 int lastIndex = offset + count; |
| 115 try { | 126 try { |
| 116 if (_state == _State.CLOSED) { | 127 if (_state == _State.CLOSED) { |
| 117 throw new HttpParserException("Data on closed connection"); | 128 throw new HttpParserException("Data on closed connection"); |
| 118 } | 129 } |
| 119 while ((index < lastIndex) && _state != _State.FAILURE) { | 130 if (_state == _State.UPGRADED) { |
| 131 throw new HttpParserException("Data on upgraded connection"); |
| 132 } |
| 133 while ((index < lastIndex) && _state != _State.FAILURE && _state != _State
.UPGRADED) { |
| 120 int byte = buffer[index]; | 134 int byte = buffer[index]; |
| 121 switch (_state) { | 135 switch (_state) { |
| 122 case _State.START: | 136 case _State.START: |
| 123 if (byte == _Const.HTTP11[0]) { | 137 if (byte == _Const.HTTP11[0]) { |
| 124 // Start parsing HTTP method. | 138 // Start parsing HTTP method. |
| 125 _httpVersionIndex = 1; | 139 _httpVersionIndex = 1; |
| 126 _state = _State.METHOD_OR_HTTP_VERSION; | 140 _state = _State.METHOD_OR_HTTP_VERSION; |
| 127 } else { | 141 } else { |
| 128 // Start parsing method. | 142 // Start parsing method. |
| 129 if (_Const.SEPARATORS_AND_CR_LF.indexOf(byte) != -1) { | 143 if (_Const.SEPARATORS_AND_CR_LF.indexOf(byte) != -1) { |
| (...skipping 163 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 293 if (byte == _CharCode.SP || byte == _CharCode.HT) { | 307 if (byte == _CharCode.SP || byte == _CharCode.HT) { |
| 294 _state = _State.HEADER_VALUE_START; | 308 _state = _State.HEADER_VALUE_START; |
| 295 } else { | 309 } else { |
| 296 String headerField = _headerField.toString(); | 310 String headerField = _headerField.toString(); |
| 297 String headerValue =_headerValue.toString(); | 311 String headerValue =_headerValue.toString(); |
| 298 // Ignore the Content-Length header if Transfer-Encoding | 312 // Ignore the Content-Length header if Transfer-Encoding |
| 299 // is chunked (RFC 2616 section 4.4) | 313 // is chunked (RFC 2616 section 4.4) |
| 300 if (headerField == "content-length" && !_chunked) { | 314 if (headerField == "content-length" && !_chunked) { |
| 301 _contentLength = Math.parseInt(headerValue); | 315 _contentLength = Math.parseInt(headerValue); |
| 302 } else if (headerField == "connection") { | 316 } else if (headerField == "connection") { |
| 303 if (headerValue == "keep-alive") { | 317 List<String> tokens = _tokenizeFieldValue(headerValue); |
| 304 _keepAlive = true; | 318 for (int i = 0; i < tokens.length; i++) { |
| 305 } else if (headerValue == "close") { | 319 String token = tokens[i].toLowerCase(); |
| 306 _connectionClose = true; | 320 if (token == "keep-alive") { |
| 321 _keepAlive = true; |
| 322 } else if (token == "close") { |
| 323 _connectionClose = true; |
| 324 } else if (token == "upgrade") { |
| 325 _connectionUpgrade = true; |
| 326 } |
| 307 } | 327 } |
| 308 } else if (headerField == "transfer-encoding" && | 328 } else if (headerField == "transfer-encoding" && |
| 309 headerValue == "chunked") { | 329 headerValue == "chunked") { |
| 310 _chunked = true; | 330 _chunked = true; |
| 311 _contentLength = -1; | 331 _contentLength = -1; |
| 312 } | 332 } |
| 313 if (headerReceived != null) { | 333 if (headerReceived != null) { |
| 314 headerReceived(headerField, headerValue); | 334 headerReceived(headerField, headerValue); |
| 315 } | 335 } |
| 316 _headerField.clear(); | 336 _headerField.clear(); |
| 317 _headerValue.clear(); | 337 _headerValue.clear(); |
| 318 | 338 |
| 319 if (byte == _CharCode.CR) { | 339 if (byte == _CharCode.CR) { |
| 320 _state = _State.HEADER_ENDING; | 340 _state = _State.HEADER_ENDING; |
| 321 } else { | 341 } else { |
| 322 // Start of new header field. | 342 // Start of new header field. |
| 323 _headerField.addCharCode(_toLowerCase(byte)); | 343 _headerField.addCharCode(_toLowerCase(byte)); |
| 324 _state = _State.HEADER_FIELD; | 344 _state = _State.HEADER_FIELD; |
| 325 } | 345 } |
| 326 } | 346 } |
| 327 break; | 347 break; |
| 328 | 348 |
| 329 case _State.HEADER_ENDING: | 349 case _State.HEADER_ENDING: |
| 330 _expect(byte, _CharCode.LF); | 350 _expect(byte, _CharCode.LF); |
| 331 if (headersComplete != null) headersComplete(); | 351 if (_connectionUpgrade) { |
| 332 if (_chunked) { | 352 _state = _State.UPGRADED; |
| 333 _state = _State.CHUNK_SIZE; | 353 if (headersComplete != null) headersComplete(); |
| 334 _remainingContent = 0; | |
| 335 } else if (_contentLength == 0 || | |
| 336 (_messageType == _MessageType.REQUEST && | |
| 337 _contentLength == -1) || | |
| 338 (_messageType == _MessageType.RESPONSE && | |
| 339 (_noMessageBody || _responseToMethod == "HEAD"))) { | |
| 340 // If there is no message body get ready to process the | |
| 341 // next request. | |
| 342 _bodyEnd(); | |
| 343 _reset(); | |
| 344 } else if (_contentLength > 0) { | |
| 345 _remainingContent = _contentLength; | |
| 346 _state = _State.BODY; | |
| 347 } else { | 354 } else { |
| 348 // Neither chunked nor content length. End of body | 355 if (headersComplete != null) headersComplete(); |
| 349 // indicated by close. | 356 if (_chunked) { |
| 350 _state = _State.BODY; | 357 _state = _State.CHUNK_SIZE; |
| 358 _remainingContent = 0; |
| 359 } else if (_contentLength == 0 || |
| 360 (_messageType == _MessageType.REQUEST && |
| 361 _contentLength == -1) || |
| 362 (_messageType == _MessageType.RESPONSE && |
| 363 (_noMessageBody || _responseToMethod == "HEAD"))) { |
| 364 // If there is no message body get ready to process the |
| 365 // next request. |
| 366 _bodyEnd(); |
| 367 _reset(); |
| 368 } else if (_contentLength > 0) { |
| 369 _remainingContent = _contentLength; |
| 370 _state = _State.BODY; |
| 371 } else { |
| 372 // Neither chunked nor content length. End of body |
| 373 // indicated by close. |
| 374 _state = _State.BODY; |
| 375 } |
| 351 } | 376 } |
| 352 break; | 377 break; |
| 353 | 378 |
| 354 case _State.CHUNK_SIZE_STARTING_CR: | 379 case _State.CHUNK_SIZE_STARTING_CR: |
| 355 _expect(byte, _CharCode.CR); | 380 _expect(byte, _CharCode.CR); |
| 356 _state = _State.CHUNK_SIZE_STARTING_LF; | 381 _state = _State.CHUNK_SIZE_STARTING_LF; |
| 357 break; | 382 break; |
| 358 | 383 |
| 359 case _State.CHUNK_SIZE_STARTING_LF: | 384 case _State.CHUNK_SIZE_STARTING_LF: |
| 360 _expect(byte, _CharCode.LF); | 385 _expect(byte, _CharCode.LF); |
| (...skipping 126 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 487 error(e); | 512 error(e); |
| 488 return; | 513 return; |
| 489 } | 514 } |
| 490 throw e; | 515 throw e; |
| 491 } | 516 } |
| 492 } | 517 } |
| 493 | 518 |
| 494 int get messageType() => _messageType; | 519 int get messageType() => _messageType; |
| 495 int get contentLength() => _contentLength; | 520 int get contentLength() => _contentLength; |
| 496 bool get keepAlive() => _keepAlive; | 521 bool get keepAlive() => _keepAlive; |
| 522 bool get upgrade() => _connectionUpgrade && _state == _State.UPGRADED; |
| 497 | 523 |
| 498 void set responseToMethod(String method) => _responseToMethod = method; | 524 void set responseToMethod(String method) => _responseToMethod = method; |
| 499 | 525 |
| 500 bool get isIdle() => _state == _State.START; | 526 bool get isIdle() => _state == _State.START; |
| 501 | 527 |
| 502 void _bodyEnd() { | 528 void _bodyEnd() { |
| 503 if (dataEnd != null) { | 529 if (dataEnd != null) { |
| 504 dataEnd(_messageType == _MessageType.RESPONSE && _connectionClose); | 530 dataEnd(_messageType == _MessageType.RESPONSE && _connectionClose); |
| 505 } | 531 } |
| 506 } | 532 } |
| 507 | 533 |
| 508 _reset() { | 534 _reset() { |
| 509 _state = _State.START; | 535 _state = _State.START; |
| 510 _messageType = _MessageType.UNDETERMINED; | 536 _messageType = _MessageType.UNDETERMINED; |
| 511 _headerField = new StringBuffer(); | 537 _headerField = new StringBuffer(); |
| 512 _headerValue = new StringBuffer(); | 538 _headerValue = new StringBuffer(); |
| 513 _method_or_status_code = new StringBuffer(); | 539 _method_or_status_code = new StringBuffer(); |
| 514 _uri_or_reason_phrase = new StringBuffer(); | 540 _uri_or_reason_phrase = new StringBuffer(); |
| 515 | 541 |
| 516 _contentLength = -1; | 542 _contentLength = -1; |
| 517 _keepAlive = false; | 543 _keepAlive = false; |
| 518 _connectionClose = false; | 544 _connectionClose = false; |
| 545 _connectionUpgrade = false; |
| 519 _chunked = false; | 546 _chunked = false; |
| 520 | 547 |
| 521 _noMessageBody = false; | 548 _noMessageBody = false; |
| 522 _responseToMethod = null; | 549 _responseToMethod = null; |
| 523 _remainingContent = null; | 550 _remainingContent = null; |
| 524 } | 551 } |
| 525 | 552 |
| 526 bool _isTokenChar(int byte) { | 553 bool _isTokenChar(int byte) { |
| 527 return byte > 31 && byte < 128 && _Const.SEPARATORS.indexOf(byte) == -1; | 554 return byte > 31 && byte < 128 && _Const.SEPARATORS.indexOf(byte) == -1; |
| 528 } | 555 } |
| 529 | 556 |
| 557 List<String> _tokenizeFieldValue(String headerValue) { |
| 558 List<String> tokens = new List<String>(); |
| 559 int start = 0; |
| 560 int index = 0; |
| 561 while (index < headerValue.length) { |
| 562 if (headerValue[index] == ",") { |
| 563 tokens.add(headerValue.substring(start, index)); |
| 564 start = index + 1; |
| 565 } else if (headerValue[index] == " " || headerValue[index] == "\t") { |
| 566 start++; |
| 567 } |
| 568 index++; |
| 569 } |
| 570 tokens.add(headerValue.substring(start, index)); |
| 571 return tokens; |
| 572 } |
| 573 |
| 530 int _toLowerCase(int byte) { | 574 int _toLowerCase(int byte) { |
| 531 final int aCode = "A".charCodeAt(0); | 575 final int aCode = "A".charCodeAt(0); |
| 532 final int zCode = "Z".charCodeAt(0); | 576 final int zCode = "Z".charCodeAt(0); |
| 533 final int delta = "a".charCodeAt(0) - aCode; | 577 final int delta = "a".charCodeAt(0) - aCode; |
| 534 return (aCode <= byte && byte <= zCode) ? byte + delta : byte; | 578 return (aCode <= byte && byte <= zCode) ? byte + delta : byte; |
| 535 } | 579 } |
| 536 | 580 |
| 537 int _expect(int val1, int val2) { | 581 int _expect(int val1, int val2) { |
| 538 if (val1 != val2) { | 582 if (val1 != val2) { |
| 539 throw new HttpParserException("Failed to parse HTTP"); | 583 throw new HttpParserException("Failed to parse HTTP"); |
| (...skipping 16 matching lines...) Expand all Loading... |
| 556 int _httpVersionIndex; | 600 int _httpVersionIndex; |
| 557 int _messageType; | 601 int _messageType; |
| 558 StringBuffer _method_or_status_code; | 602 StringBuffer _method_or_status_code; |
| 559 StringBuffer _uri_or_reason_phrase; | 603 StringBuffer _uri_or_reason_phrase; |
| 560 StringBuffer _headerField; | 604 StringBuffer _headerField; |
| 561 StringBuffer _headerValue; | 605 StringBuffer _headerValue; |
| 562 | 606 |
| 563 int _contentLength; | 607 int _contentLength; |
| 564 bool _keepAlive; | 608 bool _keepAlive; |
| 565 bool _connectionClose; | 609 bool _connectionClose; |
| 610 bool _connectionUpgrade; |
| 566 bool _chunked; | 611 bool _chunked; |
| 567 | 612 |
| 568 bool _noMessageBody; | 613 bool _noMessageBody; |
| 569 String _responseToMethod; // Indicates the method used for the request. | 614 String _responseToMethod; // Indicates the method used for the request. |
| 570 int _remainingContent; | 615 int _remainingContent; |
| 571 | 616 |
| 572 // Callbacks. | 617 // Callbacks. |
| 573 Function requestStart; | 618 Function requestStart; |
| 574 Function responseStart; | 619 Function responseStart; |
| 575 Function headerReceived; | 620 Function headerReceived; |
| 576 Function headersComplete; | 621 Function headersComplete; |
| 577 Function dataReceived; | 622 Function dataReceived; |
| 578 Function dataEnd; | 623 Function dataEnd; |
| 579 Function error; | 624 Function error; |
| 580 } | 625 } |
| 581 | 626 |
| 582 | 627 |
| 583 class HttpParserException implements Exception { | 628 class HttpParserException implements Exception { |
| 584 const HttpParserException([String this.message = ""]); | 629 const HttpParserException([String this.message = ""]); |
| 585 String toString() => "HttpParserException: $message"; | 630 String toString() => "HttpParserException: $message"; |
| 586 final String message; | 631 final String message; |
| 587 } | 632 } |
| OLD | NEW |