| OLD | NEW |
| (Empty) |
| 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 | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 // Global constants. | |
| 6 class _Const { | |
| 7 // Bytes for "HTTP". | |
| 8 static const HTTP = const [72, 84, 84, 80]; | |
| 9 // Bytes for "HTTP/1.". | |
| 10 static const HTTP1DOT = const [72, 84, 84, 80, 47, 49, 46]; | |
| 11 // Bytes for "HTTP/1.0". | |
| 12 static const HTTP10 = const [72, 84, 84, 80, 47, 49, 46, 48]; | |
| 13 // Bytes for "HTTP/1.1". | |
| 14 static const HTTP11 = const [72, 84, 84, 80, 47, 49, 46, 49]; | |
| 15 | |
| 16 static const END_CHUNKED = const [0x30, 13, 10, 13, 10]; | |
| 17 | |
| 18 // Bytes for '()<>@,;:\\"/[]?={} \t'. | |
| 19 static const SEPARATORS = const [40, 41, 60, 62, 64, 44, 59, 58, 92, 34, 47, | |
| 20 91, 93, 63, 61, 123, 125, 32, 9]; | |
| 21 | |
| 22 // Bytes for '()<>@,;:\\"/[]?={} \t\r\n'. | |
| 23 static const SEPARATORS_AND_CR_LF = const [40, 41, 60, 62, 64, 44, 59, 58, 92, | |
| 24 34, 47, 91, 93, 63, 61, 123, 125, | |
| 25 32, 9, 13, 10]; | |
| 26 } | |
| 27 | |
| 28 | |
| 29 // Frequently used character codes. | |
| 30 class _CharCode { | |
| 31 static const int HT = 9; | |
| 32 static const int LF = 10; | |
| 33 static const int CR = 13; | |
| 34 static const int SP = 32; | |
| 35 static const int COMMA = 44; | |
| 36 static const int DASH = 45; | |
| 37 static const int SLASH = 47; | |
| 38 static const int ZERO = 48; | |
| 39 static const int ONE = 49; | |
| 40 static const int COLON = 58; | |
| 41 static const int SEMI_COLON = 59; | |
| 42 } | |
| 43 | |
| 44 | |
| 45 // States of the HTTP parser state machine. | |
| 46 class _State { | |
| 47 static const int START = 0; | |
| 48 static const int METHOD_OR_RESPONSE_HTTP_VERSION = 1; | |
| 49 static const int RESPONSE_HTTP_VERSION = 2; | |
| 50 static const int REQUEST_LINE_METHOD = 3; | |
| 51 static const int REQUEST_LINE_URI = 4; | |
| 52 static const int REQUEST_LINE_HTTP_VERSION = 5; | |
| 53 static const int REQUEST_LINE_ENDING = 6; | |
| 54 static const int RESPONSE_LINE_STATUS_CODE = 7; | |
| 55 static const int RESPONSE_LINE_REASON_PHRASE = 8; | |
| 56 static const int RESPONSE_LINE_ENDING = 9; | |
| 57 static const int HEADER_START = 10; | |
| 58 static const int HEADER_FIELD = 11; | |
| 59 static const int HEADER_VALUE_START = 12; | |
| 60 static const int HEADER_VALUE = 13; | |
| 61 static const int HEADER_VALUE_FOLDING_OR_ENDING = 14; | |
| 62 static const int HEADER_VALUE_FOLD_OR_END = 15; | |
| 63 static const int HEADER_ENDING = 16; | |
| 64 | |
| 65 static const int CHUNK_SIZE_STARTING_CR = 17; | |
| 66 static const int CHUNK_SIZE_STARTING_LF = 18; | |
| 67 static const int CHUNK_SIZE = 19; | |
| 68 static const int CHUNK_SIZE_EXTENSION = 20; | |
| 69 static const int CHUNK_SIZE_ENDING = 21; | |
| 70 static const int CHUNKED_BODY_DONE_CR = 22; | |
| 71 static const int CHUNKED_BODY_DONE_LF = 23; | |
| 72 static const int BODY = 24; | |
| 73 static const int CLOSED = 25; | |
| 74 static const int UPGRADED = 26; | |
| 75 static const int FAILURE = 27; | |
| 76 | |
| 77 static const int FIRST_BODY_STATE = CHUNK_SIZE_STARTING_CR; | |
| 78 } | |
| 79 | |
| 80 // HTTP version of the request or response being parsed. | |
| 81 class _HttpVersion { | |
| 82 static const int UNDETERMINED = 0; | |
| 83 static const int HTTP10 = 1; | |
| 84 static const int HTTP11 = 2; | |
| 85 } | |
| 86 | |
| 87 // States of the HTTP parser state machine. | |
| 88 class _MessageType { | |
| 89 static const int UNDETERMINED = 0; | |
| 90 static const int REQUEST = 1; | |
| 91 static const int RESPONSE = 0; | |
| 92 } | |
| 93 | |
| 94 | |
| 95 /** | |
| 96 * HTTP parser which parses the HTTP stream as data is supplied | |
| 97 * through the [:writeList:] and [:connectionClosed:] methods. As the | |
| 98 * data is parsed the following callbacks are called: | |
| 99 * | |
| 100 * [:requestStart:] | |
| 101 * [:responseStart:] | |
| 102 * [:headerReceived:] | |
| 103 * [:headersComplete:] | |
| 104 * [:dataReceived:] | |
| 105 * [:dataEnd:] | |
| 106 * [:error:] | |
| 107 * | |
| 108 * If an HTTP parser error occours it is possible to get an exception | |
| 109 * thrown from the [:writeList:] and [:connectionClosed:] methods if | |
| 110 * the error callback is not set. | |
| 111 * | |
| 112 * The connection upgrades (e.g. switching from HTTP/1.1 to the | |
| 113 * WebSocket protocol) is handled in a special way. If connection | |
| 114 * upgrade is specified in the headers, then on the callback to | |
| 115 * [:headersComplete:] the [:upgrade:] property on the [:HttpParser:] | |
| 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 | |
| 118 * [:dataReceived:] and [:dataEnd:] are not called in this case as | |
| 119 * there is no more HTTP data. After the upgrade the call to | |
| 120 * [:writeList:] causing the upgrade will return with the number of | |
| 121 * bytes parsed as HTTP. Any unparsed bytes is part of the protocol | |
| 122 * the connection is upgrading to and should be handled according to | |
| 123 * that protocol. | |
| 124 */ | |
| 125 class _HttpParser { | |
| 126 _HttpParser() { | |
| 127 _reset(); | |
| 128 } | |
| 129 | |
| 130 // From RFC 2616. | |
| 131 // generic-message = start-line | |
| 132 // *(message-header CRLF) | |
| 133 // CRLF | |
| 134 // [ message-body ] | |
| 135 // start-line = Request-Line | Status-Line | |
| 136 // Request-Line = Method SP Request-URI SP HTTP-Version CRLF | |
| 137 // Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF | |
| 138 // message-header = field-name ":" [ field-value ] | |
| 139 int writeList(List<int> buffer, int offset, int count) { | |
| 140 int index = offset; | |
| 141 int lastIndex = offset + count; | |
| 142 try { | |
| 143 if (_state == _State.CLOSED) { | |
| 144 throw new HttpParserException("Data on closed connection"); | |
| 145 } | |
| 146 if (_state == _State.UPGRADED) { | |
| 147 throw new HttpParserException("Data on upgraded connection"); | |
| 148 } | |
| 149 if (_state == _State.FAILURE) { | |
| 150 throw new HttpParserException("Data on failed connection"); | |
| 151 } | |
| 152 while ((index < lastIndex) && | |
| 153 _state != _State.FAILURE && | |
| 154 _state != _State.UPGRADED) { | |
| 155 int byte = buffer[index]; | |
| 156 switch (_state) { | |
| 157 case _State.START: | |
| 158 if (byte == _Const.HTTP[0]) { | |
| 159 // Start parsing method or HTTP version. | |
| 160 _httpVersionIndex = 1; | |
| 161 _state = _State.METHOD_OR_RESPONSE_HTTP_VERSION; | |
| 162 } else { | |
| 163 // Start parsing method. | |
| 164 if (!_isTokenChar(byte)) { | |
| 165 throw new HttpParserException("Invalid request method"); | |
| 166 } | |
| 167 _method_or_status_code.addCharCode(byte); | |
| 168 _state = _State.REQUEST_LINE_METHOD; | |
| 169 } | |
| 170 break; | |
| 171 | |
| 172 case _State.METHOD_OR_RESPONSE_HTTP_VERSION: | |
| 173 if (_httpVersionIndex < _Const.HTTP.length && | |
| 174 byte == _Const.HTTP[_httpVersionIndex]) { | |
| 175 // Continue parsing HTTP version. | |
| 176 _httpVersionIndex++; | |
| 177 } else if (_httpVersionIndex == _Const.HTTP.length && | |
| 178 byte == _CharCode.SLASH) { | |
| 179 // HTTP/ parsed. As method is a token this cannot be a | |
| 180 // method anymore. | |
| 181 _httpVersionIndex++; | |
| 182 _state = _State.RESPONSE_HTTP_VERSION; | |
| 183 } else { | |
| 184 // Did not parse HTTP version. Expect method instead. | |
| 185 for (int i = 0; i < _httpVersionIndex; i++) { | |
| 186 _method_or_status_code.addCharCode(_Const.HTTP[i]); | |
| 187 } | |
| 188 if (byte == _CharCode.SP) { | |
| 189 _state = _State.REQUEST_LINE_URI; | |
| 190 } else { | |
| 191 _method_or_status_code.addCharCode(byte); | |
| 192 _httpVersion = _HttpVersion.UNDETERMINED; | |
| 193 _state = _State.REQUEST_LINE_METHOD; | |
| 194 } | |
| 195 } | |
| 196 break; | |
| 197 | |
| 198 case _State.RESPONSE_HTTP_VERSION: | |
| 199 if (_httpVersionIndex < _Const.HTTP1DOT.length) { | |
| 200 // Continue parsing HTTP version. | |
| 201 _expect(byte, _Const.HTTP1DOT[_httpVersionIndex]); | |
| 202 _httpVersionIndex++; | |
| 203 } else if (_httpVersionIndex == _Const.HTTP1DOT.length && | |
| 204 byte == _CharCode.ONE) { | |
| 205 // HTTP/1.1 parsed. | |
| 206 _httpVersion = _HttpVersion.HTTP11; | |
| 207 _persistentConnection = true; | |
| 208 _httpVersionIndex++; | |
| 209 } else if (_httpVersionIndex == _Const.HTTP1DOT.length && | |
| 210 byte == _CharCode.ZERO) { | |
| 211 // HTTP/1.0 parsed. | |
| 212 _httpVersion = _HttpVersion.HTTP10; | |
| 213 _persistentConnection = false; | |
| 214 _httpVersionIndex++; | |
| 215 } else if (_httpVersionIndex == _Const.HTTP1DOT.length + 1) { | |
| 216 _expect(byte, _CharCode.SP); | |
| 217 // HTTP version parsed. | |
| 218 _state = _State.RESPONSE_LINE_STATUS_CODE; | |
| 219 } else { | |
| 220 throw new HttpParserException("Invalid response line"); | |
| 221 } | |
| 222 break; | |
| 223 | |
| 224 case _State.REQUEST_LINE_METHOD: | |
| 225 if (byte == _CharCode.SP) { | |
| 226 _state = _State.REQUEST_LINE_URI; | |
| 227 } else { | |
| 228 if (_Const.SEPARATORS_AND_CR_LF.indexOf(byte) != -1) { | |
| 229 throw new HttpParserException("Invalid request method"); | |
| 230 } | |
| 231 _method_or_status_code.addCharCode(byte); | |
| 232 } | |
| 233 break; | |
| 234 | |
| 235 case _State.REQUEST_LINE_URI: | |
| 236 if (byte == _CharCode.SP) { | |
| 237 if (_uri_or_reason_phrase.length == 0) { | |
| 238 throw new HttpParserException("Invalid request URI"); | |
| 239 } | |
| 240 _state = _State.REQUEST_LINE_HTTP_VERSION; | |
| 241 _httpVersionIndex = 0; | |
| 242 } else { | |
| 243 if (byte == _CharCode.CR || byte == _CharCode.LF) { | |
| 244 throw new HttpParserException("Invalid request URI"); | |
| 245 } | |
| 246 _uri_or_reason_phrase.addCharCode(byte); | |
| 247 } | |
| 248 break; | |
| 249 | |
| 250 case _State.REQUEST_LINE_HTTP_VERSION: | |
| 251 if (_httpVersionIndex < _Const.HTTP1DOT.length) { | |
| 252 _expect(byte, _Const.HTTP11[_httpVersionIndex]); | |
| 253 _httpVersionIndex++; | |
| 254 } else if (_httpVersionIndex == _Const.HTTP1DOT.length) { | |
| 255 if (byte == _CharCode.ONE) { | |
| 256 // HTTP/1.1 parsed. | |
| 257 _httpVersion = _HttpVersion.HTTP11; | |
| 258 _persistentConnection = true; | |
| 259 _httpVersionIndex++; | |
| 260 } else if (byte == _CharCode.ZERO) { | |
| 261 // HTTP/1.0 parsed. | |
| 262 _httpVersion = _HttpVersion.HTTP10; | |
| 263 _persistentConnection = false; | |
| 264 _httpVersionIndex++; | |
| 265 } else { | |
| 266 throw new HttpParserException("Invalid response line"); | |
| 267 } | |
| 268 } else { | |
| 269 _expect(byte, _CharCode.CR); | |
| 270 _state = _State.REQUEST_LINE_ENDING; | |
| 271 } | |
| 272 break; | |
| 273 | |
| 274 case _State.REQUEST_LINE_ENDING: | |
| 275 _expect(byte, _CharCode.LF); | |
| 276 _messageType = _MessageType.REQUEST; | |
| 277 if (requestStart != null) { | |
| 278 requestStart(_method_or_status_code.toString(), | |
| 279 _uri_or_reason_phrase.toString(), | |
| 280 version); | |
| 281 } | |
| 282 _method_or_status_code.clear(); | |
| 283 _uri_or_reason_phrase.clear(); | |
| 284 _state = _State.HEADER_START; | |
| 285 break; | |
| 286 | |
| 287 case _State.RESPONSE_LINE_STATUS_CODE: | |
| 288 if (byte == _CharCode.SP) { | |
| 289 if (_method_or_status_code.length != 3) { | |
| 290 throw new HttpParserException("Invalid response status code"); | |
| 291 } | |
| 292 _state = _State.RESPONSE_LINE_REASON_PHRASE; | |
| 293 } else { | |
| 294 if (byte < 0x30 && 0x39 < byte) { | |
| 295 throw new HttpParserException("Invalid response status code"); | |
| 296 } else { | |
| 297 _method_or_status_code.addCharCode(byte); | |
| 298 } | |
| 299 } | |
| 300 break; | |
| 301 | |
| 302 case _State.RESPONSE_LINE_REASON_PHRASE: | |
| 303 if (byte == _CharCode.CR) { | |
| 304 if (_uri_or_reason_phrase.length == 0) { | |
| 305 throw new HttpParserException("Invalid response reason phrase"); | |
| 306 } | |
| 307 _state = _State.RESPONSE_LINE_ENDING; | |
| 308 } else { | |
| 309 if (byte == _CharCode.CR || byte == _CharCode.LF) { | |
| 310 throw new HttpParserException("Invalid response reason phrase"); | |
| 311 } | |
| 312 _uri_or_reason_phrase.addCharCode(byte); | |
| 313 } | |
| 314 break; | |
| 315 | |
| 316 case _State.RESPONSE_LINE_ENDING: | |
| 317 _expect(byte, _CharCode.LF); | |
| 318 _messageType == _MessageType.RESPONSE; | |
| 319 int statusCode = parseInt(_method_or_status_code.toString()); | |
| 320 if (statusCode < 100 || statusCode > 599) { | |
| 321 throw new HttpParserException("Invalid response status code"); | |
| 322 } else { | |
| 323 // Check whether this response will never have a body. | |
| 324 _noMessageBody = | |
| 325 statusCode <= 199 || statusCode == 204 || statusCode == 304; | |
| 326 } | |
| 327 if (responseStart != null) { | |
| 328 responseStart(statusCode, _uri_or_reason_phrase.toString(), versio
n); | |
| 329 } | |
| 330 _method_or_status_code.clear(); | |
| 331 _uri_or_reason_phrase.clear(); | |
| 332 _state = _State.HEADER_START; | |
| 333 break; | |
| 334 | |
| 335 case _State.HEADER_START: | |
| 336 if (byte == _CharCode.CR) { | |
| 337 _state = _State.HEADER_ENDING; | |
| 338 } else { | |
| 339 // Start of new header field. | |
| 340 _headerField.addCharCode(_toLowerCase(byte)); | |
| 341 _state = _State.HEADER_FIELD; | |
| 342 } | |
| 343 break; | |
| 344 | |
| 345 case _State.HEADER_FIELD: | |
| 346 if (byte == _CharCode.COLON) { | |
| 347 _state = _State.HEADER_VALUE_START; | |
| 348 } else { | |
| 349 if (!_isTokenChar(byte)) { | |
| 350 throw new HttpParserException("Invalid header field name"); | |
| 351 } | |
| 352 _headerField.addCharCode(_toLowerCase(byte)); | |
| 353 } | |
| 354 break; | |
| 355 | |
| 356 case _State.HEADER_VALUE_START: | |
| 357 if (byte == _CharCode.CR) { | |
| 358 _state = _State.HEADER_VALUE_FOLDING_OR_ENDING; | |
| 359 } else if (byte != _CharCode.SP && byte != _CharCode.HT) { | |
| 360 // Start of new header value. | |
| 361 _headerValue.addCharCode(byte); | |
| 362 _state = _State.HEADER_VALUE; | |
| 363 } | |
| 364 break; | |
| 365 | |
| 366 case _State.HEADER_VALUE: | |
| 367 if (byte == _CharCode.CR) { | |
| 368 _state = _State.HEADER_VALUE_FOLDING_OR_ENDING; | |
| 369 } else { | |
| 370 _headerValue.addCharCode(byte); | |
| 371 } | |
| 372 break; | |
| 373 | |
| 374 case _State.HEADER_VALUE_FOLDING_OR_ENDING: | |
| 375 _expect(byte, _CharCode.LF); | |
| 376 _state = _State.HEADER_VALUE_FOLD_OR_END; | |
| 377 break; | |
| 378 | |
| 379 case _State.HEADER_VALUE_FOLD_OR_END: | |
| 380 if (byte == _CharCode.SP || byte == _CharCode.HT) { | |
| 381 _state = _State.HEADER_VALUE_START; | |
| 382 } else { | |
| 383 String headerField = _headerField.toString(); | |
| 384 String headerValue =_headerValue.toString(); | |
| 385 bool reportHeader = true; | |
| 386 if (headerField == "content-length" && !_chunked) { | |
| 387 // Ignore the Content-Length header if Transfer-Encoding | |
| 388 // is chunked (RFC 2616 section 4.4) | |
| 389 _contentLength = parseInt(headerValue); | |
| 390 } else if (headerField == "connection") { | |
| 391 List<String> tokens = _tokenizeFieldValue(headerValue); | |
| 392 for (int i = 0; i < tokens.length; i++) { | |
| 393 String token = tokens[i].toLowerCase(); | |
| 394 if (token == "keep-alive") { | |
| 395 _persistentConnection = true; | |
| 396 } else if (token == "close") { | |
| 397 _persistentConnection = false; | |
| 398 } else if (token == "upgrade") { | |
| 399 _connectionUpgrade = true; | |
| 400 } | |
| 401 if (headerReceived != null) { | |
| 402 headerReceived(headerField, token); | |
| 403 } | |
| 404 } | |
| 405 reportHeader = false; | |
| 406 } else if (headerField == "transfer-encoding" && | |
| 407 headerValue.toLowerCase() == "chunked") { | |
| 408 // Ignore the Content-Length header if Transfer-Encoding | |
| 409 // is chunked (RFC 2616 section 4.4) | |
| 410 _chunked = true; | |
| 411 _contentLength = -1; | |
| 412 } | |
| 413 if (reportHeader && headerReceived != null) { | |
| 414 headerReceived(headerField, headerValue); | |
| 415 } | |
| 416 _headerField.clear(); | |
| 417 _headerValue.clear(); | |
| 418 | |
| 419 if (byte == _CharCode.CR) { | |
| 420 _state = _State.HEADER_ENDING; | |
| 421 } else { | |
| 422 // Start of new header field. | |
| 423 _headerField.addCharCode(_toLowerCase(byte)); | |
| 424 _state = _State.HEADER_FIELD; | |
| 425 } | |
| 426 } | |
| 427 break; | |
| 428 | |
| 429 case _State.HEADER_ENDING: | |
| 430 _expect(byte, _CharCode.LF); | |
| 431 // If a request message has neither Content-Length nor | |
| 432 // Transfer-Encoding the message must not have a body (RFC | |
| 433 // 2616 section 4.3). | |
| 434 if (_messageType == _MessageType.REQUEST && | |
| 435 _contentLength < 0 && | |
| 436 _chunked == false) { | |
| 437 _contentLength = 0; | |
| 438 } | |
| 439 if (_connectionUpgrade) { | |
| 440 _state = _State.UPGRADED; | |
| 441 _unparsedData = | |
| 442 buffer.getRange(index + 1, count - (index + 1 - offset)); | |
| 443 if (headersComplete != null) headersComplete(); | |
| 444 } else { | |
| 445 if (headersComplete != null) headersComplete(); | |
| 446 if (_chunked) { | |
| 447 _state = _State.CHUNK_SIZE; | |
| 448 _remainingContent = 0; | |
| 449 } else if (_contentLength == 0 || | |
| 450 (_messageType == _MessageType.RESPONSE && | |
| 451 (_noMessageBody || _responseToMethod == "HEAD"))) { | |
| 452 // If there is no message body get ready to process the | |
| 453 // next request. | |
| 454 _bodyEnd(); | |
| 455 _reset(); | |
| 456 } else if (_contentLength > 0) { | |
| 457 _remainingContent = _contentLength; | |
| 458 _state = _State.BODY; | |
| 459 } else { | |
| 460 // Neither chunked nor content length. End of body | |
| 461 // indicated by close. | |
| 462 _state = _State.BODY; | |
| 463 } | |
| 464 } | |
| 465 break; | |
| 466 | |
| 467 case _State.CHUNK_SIZE_STARTING_CR: | |
| 468 _expect(byte, _CharCode.CR); | |
| 469 _state = _State.CHUNK_SIZE_STARTING_LF; | |
| 470 break; | |
| 471 | |
| 472 case _State.CHUNK_SIZE_STARTING_LF: | |
| 473 _expect(byte, _CharCode.LF); | |
| 474 _state = _State.CHUNK_SIZE; | |
| 475 break; | |
| 476 | |
| 477 case _State.CHUNK_SIZE: | |
| 478 if (byte == _CharCode.CR) { | |
| 479 _state = _State.CHUNK_SIZE_ENDING; | |
| 480 } else if (byte == _CharCode.SEMI_COLON) { | |
| 481 _state = _State.CHUNK_SIZE_EXTENSION; | |
| 482 } else { | |
| 483 int value = _expectHexDigit(byte); | |
| 484 _remainingContent = _remainingContent * 16 + value; | |
| 485 } | |
| 486 break; | |
| 487 | |
| 488 case _State.CHUNK_SIZE_EXTENSION: | |
| 489 if (byte == _CharCode.CR) { | |
| 490 _state = _State.CHUNK_SIZE_ENDING; | |
| 491 } | |
| 492 break; | |
| 493 | |
| 494 case _State.CHUNK_SIZE_ENDING: | |
| 495 _expect(byte, _CharCode.LF); | |
| 496 if (_remainingContent > 0) { | |
| 497 _state = _State.BODY; | |
| 498 } else { | |
| 499 _state = _State.CHUNKED_BODY_DONE_CR; | |
| 500 } | |
| 501 break; | |
| 502 | |
| 503 case _State.CHUNKED_BODY_DONE_CR: | |
| 504 _expect(byte, _CharCode.CR); | |
| 505 _state = _State.CHUNKED_BODY_DONE_LF; | |
| 506 break; | |
| 507 | |
| 508 case _State.CHUNKED_BODY_DONE_LF: | |
| 509 _expect(byte, _CharCode.LF); | |
| 510 _bodyEnd(); | |
| 511 _reset(); | |
| 512 break; | |
| 513 | |
| 514 case _State.BODY: | |
| 515 // The body is not handled one byte at a time but in blocks. | |
| 516 int dataAvailable = lastIndex - index; | |
| 517 List<int> data; | |
| 518 if (_remainingContent == null || | |
| 519 dataAvailable <= _remainingContent) { | |
| 520 data = new Uint8List(dataAvailable); | |
| 521 data.setRange(0, dataAvailable, buffer, index); | |
| 522 } else { | |
| 523 data = new Uint8List(_remainingContent); | |
| 524 data.setRange(0, _remainingContent, buffer, index); | |
| 525 } | |
| 526 | |
| 527 if (dataReceived != null) dataReceived(data); | |
| 528 if (_remainingContent != null) { | |
| 529 _remainingContent -= data.length; | |
| 530 } | |
| 531 index += data.length; | |
| 532 if (_remainingContent == 0) { | |
| 533 if (!_chunked) { | |
| 534 _bodyEnd(); | |
| 535 _reset(); | |
| 536 } else { | |
| 537 _state = _State.CHUNK_SIZE_STARTING_CR; | |
| 538 } | |
| 539 } | |
| 540 | |
| 541 // Hack - as we always do index++ below. | |
| 542 index--; | |
| 543 break; | |
| 544 | |
| 545 case _State.FAILURE: | |
| 546 // Should be unreachable. | |
| 547 assert(false); | |
| 548 break; | |
| 549 | |
| 550 default: | |
| 551 // Should be unreachable. | |
| 552 assert(false); | |
| 553 break; | |
| 554 } | |
| 555 | |
| 556 // Move to the next byte. | |
| 557 index++; | |
| 558 } | |
| 559 } catch (e) { | |
| 560 // Report the error through the error callback if any. Otherwise | |
| 561 // throw the error. | |
| 562 if (error != null) { | |
| 563 error(e); | |
| 564 _state = _State.FAILURE; | |
| 565 } else { | |
| 566 throw e; | |
| 567 } | |
| 568 } | |
| 569 | |
| 570 // Return the number of bytes parsed. | |
| 571 return index - offset; | |
| 572 } | |
| 573 | |
| 574 void connectionClosed() { | |
| 575 if (_state < _State.FIRST_BODY_STATE) { | |
| 576 _state = _State.FAILURE; | |
| 577 // Report the error through the error callback if any. Otherwise | |
| 578 // throw the error. | |
| 579 var e = new HttpParserException( | |
| 580 "Connection closed before full header was received"); | |
| 581 if (error != null) { | |
| 582 error(e); | |
| 583 return; | |
| 584 } | |
| 585 throw e; | |
| 586 } | |
| 587 | |
| 588 if (!_chunked && _contentLength == -1) { | |
| 589 if (_state != _State.START) { | |
| 590 if (dataEnd != null) dataEnd(true); | |
| 591 } | |
| 592 _state = _State.CLOSED; | |
| 593 } else { | |
| 594 _state = _State.FAILURE; | |
| 595 // Report the error through the error callback if any. Otherwise | |
| 596 // throw the error. | |
| 597 var e = new HttpParserException( | |
| 598 "Connection closed before full body was received"); | |
| 599 if (error != null) { | |
| 600 error(e); | |
| 601 return; | |
| 602 } | |
| 603 throw e; | |
| 604 } | |
| 605 } | |
| 606 | |
| 607 String get version { | |
| 608 switch (_httpVersion) { | |
| 609 case _HttpVersion.HTTP10: | |
| 610 return "1.0"; | |
| 611 case _HttpVersion.HTTP11: | |
| 612 return "1.1"; | |
| 613 } | |
| 614 return null; | |
| 615 } | |
| 616 | |
| 617 int get messageType => _messageType; | |
| 618 int get contentLength => _contentLength; | |
| 619 bool get upgrade => _connectionUpgrade && _state == _State.UPGRADED; | |
| 620 bool get persistentConnection => _persistentConnection; | |
| 621 | |
| 622 void set responseToMethod(String method) { _responseToMethod = method; } | |
| 623 | |
| 624 bool get isIdle => _state == _State.START; | |
| 625 | |
| 626 List<int> get unparsedData => _unparsedData; | |
| 627 | |
| 628 void _bodyEnd() { | |
| 629 if (dataEnd != null) { | |
| 630 dataEnd(_messageType == _MessageType.RESPONSE && !_persistentConnection); | |
| 631 } | |
| 632 } | |
| 633 | |
| 634 _reset() { | |
| 635 _state = _State.START; | |
| 636 _messageType = _MessageType.UNDETERMINED; | |
| 637 _headerField = new StringBuffer(); | |
| 638 _headerValue = new StringBuffer(); | |
| 639 _method_or_status_code = new StringBuffer(); | |
| 640 _uri_or_reason_phrase = new StringBuffer(); | |
| 641 | |
| 642 _httpVersion = _HttpVersion.UNDETERMINED; | |
| 643 _contentLength = -1; | |
| 644 _persistentConnection = false; | |
| 645 _connectionUpgrade = false; | |
| 646 _chunked = false; | |
| 647 | |
| 648 _noMessageBody = false; | |
| 649 _responseToMethod = null; | |
| 650 _remainingContent = null; | |
| 651 } | |
| 652 | |
| 653 bool _isTokenChar(int byte) { | |
| 654 return byte > 31 && byte < 128 && _Const.SEPARATORS.indexOf(byte) == -1; | |
| 655 } | |
| 656 | |
| 657 List<String> _tokenizeFieldValue(String headerValue) { | |
| 658 List<String> tokens = new List<String>(); | |
| 659 int start = 0; | |
| 660 int index = 0; | |
| 661 while (index < headerValue.length) { | |
| 662 if (headerValue[index] == ",") { | |
| 663 tokens.add(headerValue.substring(start, index)); | |
| 664 start = index + 1; | |
| 665 } else if (headerValue[index] == " " || headerValue[index] == "\t") { | |
| 666 start++; | |
| 667 } | |
| 668 index++; | |
| 669 } | |
| 670 tokens.add(headerValue.substring(start, index)); | |
| 671 return tokens; | |
| 672 } | |
| 673 | |
| 674 int _toLowerCase(int byte) { | |
| 675 final int aCode = "A".charCodeAt(0); | |
| 676 final int zCode = "Z".charCodeAt(0); | |
| 677 final int delta = "a".charCodeAt(0) - aCode; | |
| 678 return (aCode <= byte && byte <= zCode) ? byte + delta : byte; | |
| 679 } | |
| 680 | |
| 681 int _expect(int val1, int val2) { | |
| 682 if (val1 != val2) { | |
| 683 throw new HttpParserException("Failed to parse HTTP"); | |
| 684 } | |
| 685 } | |
| 686 | |
| 687 int _expectHexDigit(int byte) { | |
| 688 if (0x30 <= byte && byte <= 0x39) { | |
| 689 return byte - 0x30; // 0 - 9 | |
| 690 } else if (0x41 <= byte && byte <= 0x46) { | |
| 691 return byte - 0x41 + 10; // A - F | |
| 692 } else if (0x61 <= byte && byte <= 0x66) { | |
| 693 return byte - 0x61 + 10; // a - f | |
| 694 } else { | |
| 695 throw new HttpParserException("Failed to parse HTTP"); | |
| 696 } | |
| 697 } | |
| 698 | |
| 699 int _state; | |
| 700 int _httpVersionIndex; | |
| 701 int _messageType; | |
| 702 StringBuffer _method_or_status_code; | |
| 703 StringBuffer _uri_or_reason_phrase; | |
| 704 StringBuffer _headerField; | |
| 705 StringBuffer _headerValue; | |
| 706 | |
| 707 int _httpVersion; | |
| 708 int _contentLength; | |
| 709 bool _persistentConnection; | |
| 710 bool _connectionUpgrade; | |
| 711 bool _chunked; | |
| 712 | |
| 713 bool _noMessageBody; | |
| 714 String _responseToMethod; // Indicates the method used for the request. | |
| 715 int _remainingContent; | |
| 716 | |
| 717 List<int> _unparsedData; // Unparsed data after connection upgrade. | |
| 718 // Callbacks. | |
| 719 Function requestStart; | |
| 720 Function responseStart; | |
| 721 Function headerReceived; | |
| 722 Function headersComplete; | |
| 723 Function dataReceived; | |
| 724 Function dataEnd; | |
| 725 Function error; | |
| 726 } | |
| 727 | |
| 728 | |
| 729 class HttpParserException implements Exception { | |
| 730 const HttpParserException([String this.message = ""]); | |
| 731 String toString() => "HttpParserException: $message"; | |
| 732 final String message; | |
| 733 } | |
| OLD | NEW |