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 |