OLD | NEW |
| (Empty) |
1 // Copyright (c) 2013, 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 part of dart.io; | |
6 | |
7 // Global constants. | |
8 class _Const { | |
9 // Bytes for "HTTP". | |
10 static const HTTP = const [72, 84, 84, 80]; | |
11 // Bytes for "HTTP/1.". | |
12 static const HTTP1DOT = const [72, 84, 84, 80, 47, 49, 46]; | |
13 // Bytes for "HTTP/1.0". | |
14 static const HTTP10 = const [72, 84, 84, 80, 47, 49, 46, 48]; | |
15 // Bytes for "HTTP/1.1". | |
16 static const HTTP11 = const [72, 84, 84, 80, 47, 49, 46, 49]; | |
17 | |
18 static const bool T = true; | |
19 static const bool F = false; | |
20 // Loopup-map for the following characters: '()<>@,;:\\"/[]?={} \t'. | |
21 static const SEPARATOR_MAP = const [ | |
22 F,F,F,F,F,F,F,F,F,T,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,T,F,T,F,F, | |
23 F,F,F,T,T,F,F,T,F,F,T,F,F,F,F,F,F,F,F,F,F,T,T,T,T,T,T,T,F,F,F,F,F,F,F,F,F, | |
24 F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,T,T,T,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F, | |
25 F,F,F,F,F,F,F,F,F,F,F,F,T,F,T,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F, | |
26 F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F, | |
27 F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F, | |
28 F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F,F]; | |
29 } | |
30 | |
31 | |
32 // Frequently used character codes. | |
33 class _CharCode { | |
34 static const int HT = 9; | |
35 static const int LF = 10; | |
36 static const int CR = 13; | |
37 static const int SP = 32; | |
38 static const int AMPERSAND = 38; | |
39 static const int COMMA = 44; | |
40 static const int DASH = 45; | |
41 static const int SLASH = 47; | |
42 static const int ZERO = 48; | |
43 static const int ONE = 49; | |
44 static const int COLON = 58; | |
45 static const int SEMI_COLON = 59; | |
46 static const int EQUAL = 61; | |
47 } | |
48 | |
49 | |
50 // States of the HTTP parser state machine. | |
51 class _State { | |
52 static const int START = 0; | |
53 static const int METHOD_OR_RESPONSE_HTTP_VERSION = 1; | |
54 static const int RESPONSE_HTTP_VERSION = 2; | |
55 static const int REQUEST_LINE_METHOD = 3; | |
56 static const int REQUEST_LINE_URI = 4; | |
57 static const int REQUEST_LINE_HTTP_VERSION = 5; | |
58 static const int REQUEST_LINE_ENDING = 6; | |
59 static const int RESPONSE_LINE_STATUS_CODE = 7; | |
60 static const int RESPONSE_LINE_REASON_PHRASE = 8; | |
61 static const int RESPONSE_LINE_ENDING = 9; | |
62 static const int HEADER_START = 10; | |
63 static const int HEADER_FIELD = 11; | |
64 static const int HEADER_VALUE_START = 12; | |
65 static const int HEADER_VALUE = 13; | |
66 static const int HEADER_VALUE_FOLDING_OR_ENDING = 14; | |
67 static const int HEADER_VALUE_FOLD_OR_END = 15; | |
68 static const int HEADER_ENDING = 16; | |
69 | |
70 static const int CHUNK_SIZE_STARTING_CR = 17; | |
71 static const int CHUNK_SIZE_STARTING_LF = 18; | |
72 static const int CHUNK_SIZE = 19; | |
73 static const int CHUNK_SIZE_EXTENSION = 20; | |
74 static const int CHUNK_SIZE_ENDING = 21; | |
75 static const int CHUNKED_BODY_DONE_CR = 22; | |
76 static const int CHUNKED_BODY_DONE_LF = 23; | |
77 static const int BODY = 24; | |
78 static const int CLOSED = 25; | |
79 static const int UPGRADED = 26; | |
80 static const int FAILURE = 27; | |
81 | |
82 static const int FIRST_BODY_STATE = CHUNK_SIZE_STARTING_CR; | |
83 } | |
84 | |
85 // HTTP version of the request or response being parsed. | |
86 class _HttpVersion { | |
87 static const int UNDETERMINED = 0; | |
88 static const int HTTP10 = 1; | |
89 static const int HTTP11 = 2; | |
90 } | |
91 | |
92 // States of the HTTP parser state machine. | |
93 class _MessageType { | |
94 static const int UNDETERMINED = 0; | |
95 static const int REQUEST = 1; | |
96 static const int RESPONSE = 0; | |
97 } | |
98 | |
99 | |
100 /** | |
101 * The _HttpDetachedStreamSubscription takes a subscription and some extra data, | |
102 * and makes it possible to "inject" the data in from of other data events | |
103 * from the subscription. | |
104 * | |
105 * It does so by overriding pause/resume, so that once the | |
106 * _HttpDetachedStreamSubscription is resumed, it'll deliver the data before | |
107 * resuming the underlaying subscription. | |
108 */ | |
109 class _HttpDetachedStreamSubscription implements StreamSubscription<List<int>> { | |
110 StreamSubscription<List<int>> _subscription; | |
111 List<int> _injectData; | |
112 bool _isCanceled = false; | |
113 int _pauseCount = 1; | |
114 Function _userOnData; | |
115 bool _scheduled = false; | |
116 | |
117 _HttpDetachedStreamSubscription(this._subscription, | |
118 this._injectData, | |
119 this._userOnData); | |
120 | |
121 bool get isPaused => _subscription.isPaused; | |
122 | |
123 Future/*<T>*/ asFuture/*<T>*/([/*=T*/ futureValue]) => | |
124 _subscription.asFuture/*<T>*/(futureValue); | |
125 | |
126 Future cancel() { | |
127 _isCanceled = true; | |
128 _injectData = null; | |
129 return _subscription.cancel(); | |
130 } | |
131 | |
132 void onData(void handleData(List<int> data)) { | |
133 _userOnData = handleData; | |
134 _subscription.onData(handleData); | |
135 } | |
136 | |
137 void onDone(void handleDone()) { | |
138 _subscription.onDone(handleDone); | |
139 } | |
140 | |
141 void onError(Function handleError) { | |
142 _subscription.onError(handleError); | |
143 } | |
144 | |
145 void pause([Future resumeSignal]) { | |
146 if (_injectData == null) { | |
147 _subscription.pause(resumeSignal); | |
148 } else { | |
149 _pauseCount++; | |
150 if (resumeSignal != null) { | |
151 resumeSignal.whenComplete(resume); | |
152 } | |
153 } | |
154 } | |
155 | |
156 void resume() { | |
157 if (_injectData == null) { | |
158 _subscription.resume(); | |
159 } else { | |
160 _pauseCount--; | |
161 _maybeScheduleData(); | |
162 } | |
163 } | |
164 | |
165 void _maybeScheduleData() { | |
166 if (_scheduled) return; | |
167 if (_pauseCount != 0) return; | |
168 _scheduled = true; | |
169 scheduleMicrotask(() { | |
170 _scheduled = false; | |
171 if (_pauseCount > 0 || _isCanceled) return; | |
172 var data = _injectData; | |
173 _injectData = null; | |
174 // To ensure that 'subscription.isPaused' is false, we resume the | |
175 // subscription here. This is fine as potential events are delayed. | |
176 _subscription.resume(); | |
177 if (_userOnData != null) { | |
178 _userOnData(data); | |
179 } | |
180 }); | |
181 } | |
182 } | |
183 | |
184 | |
185 class _HttpDetachedIncoming extends Stream<List<int>> { | |
186 final StreamSubscription subscription; | |
187 final List<int> bufferedData; | |
188 | |
189 _HttpDetachedIncoming(this.subscription, this.bufferedData); | |
190 | |
191 StreamSubscription<List<int>> listen(void onData(List<int> event), | |
192 {Function onError, | |
193 void onDone(), | |
194 bool cancelOnError}) { | |
195 if (subscription != null) { | |
196 subscription | |
197 ..onData(onData) | |
198 ..onError(onError) | |
199 ..onDone(onDone); | |
200 if (bufferedData == null) { | |
201 return subscription..resume(); | |
202 } | |
203 return new _HttpDetachedStreamSubscription(subscription, | |
204 bufferedData, | |
205 onData) | |
206 ..resume(); | |
207 } else { | |
208 return new Stream.fromIterable(bufferedData) | |
209 .listen(onData, | |
210 onError: onError, | |
211 onDone: onDone, | |
212 cancelOnError: cancelOnError); | |
213 } | |
214 } | |
215 } | |
216 | |
217 | |
218 /** | |
219 * HTTP parser which parses the data stream given to [consume]. | |
220 * | |
221 * If an HTTP parser error occours, the parser will signal an error to either | |
222 * the current _HttpIncoming or the _parser itself. | |
223 * | |
224 * The connection upgrades (e.g. switching from HTTP/1.1 to the | |
225 * WebSocket protocol) is handled in a special way. If connection | |
226 * upgrade is specified in the headers, then on the callback to | |
227 * [:responseStart:] the [:upgrade:] property on the [:HttpParser:] | |
228 * object will be [:true:] indicating that from now on the protocol is | |
229 * not HTTP anymore and no more callbacks will happen, that is | |
230 * [:dataReceived:] and [:dataEnd:] are not called in this case as | |
231 * there is no more HTTP data. After the upgrade the method | |
232 * [:readUnparsedData:] can be used to read any remaining bytes in the | |
233 * HTTP parser which are part of the protocol the connection is | |
234 * upgrading to. These bytes cannot be processed by the HTTP parser | |
235 * and should be handled according to whatever protocol is being | |
236 * upgraded to. | |
237 */ | |
238 class _HttpParser extends Stream<_HttpIncoming> { | |
239 // State. | |
240 bool _parserCalled = false; | |
241 | |
242 // The data that is currently being parsed. | |
243 Uint8List _buffer; | |
244 int _index; | |
245 | |
246 final bool _requestParser; | |
247 int _state; | |
248 int _httpVersionIndex; | |
249 int _messageType; | |
250 int _statusCode = 0; | |
251 int _statusCodeLength = 0; | |
252 final List<int> _method = []; | |
253 final List<int> _uri_or_reason_phrase = []; | |
254 final List<int> _headerField = []; | |
255 final List<int> _headerValue = []; | |
256 | |
257 int _httpVersion; | |
258 int _transferLength = -1; | |
259 bool _persistentConnection; | |
260 bool _connectionUpgrade; | |
261 bool _chunked; | |
262 | |
263 bool _noMessageBody = false; | |
264 int _remainingContent = -1; | |
265 | |
266 _HttpHeaders _headers; | |
267 | |
268 // The current incoming connection. | |
269 _HttpIncoming _incoming; | |
270 StreamSubscription _socketSubscription; | |
271 bool _paused = true; | |
272 bool _bodyPaused = false; | |
273 StreamController<_HttpIncoming> _controller; | |
274 StreamController<List<int>> _bodyController; | |
275 | |
276 factory _HttpParser.requestParser() { | |
277 return new _HttpParser._(true); | |
278 } | |
279 | |
280 factory _HttpParser.responseParser() { | |
281 return new _HttpParser._(false); | |
282 } | |
283 | |
284 _HttpParser._(this._requestParser) { | |
285 _controller = new StreamController<_HttpIncoming>( | |
286 sync: true, | |
287 onListen: () { | |
288 _paused = false; | |
289 }, | |
290 onPause: () { | |
291 _paused = true; | |
292 _pauseStateChanged(); | |
293 }, | |
294 onResume: () { | |
295 _paused = false; | |
296 _pauseStateChanged(); | |
297 }, | |
298 onCancel: () { | |
299 if (_socketSubscription != null) { | |
300 _socketSubscription.cancel(); | |
301 } | |
302 }); | |
303 _reset(); | |
304 } | |
305 | |
306 | |
307 StreamSubscription<_HttpIncoming> listen(void onData(_HttpIncoming event), | |
308 {Function onError, | |
309 void onDone(), | |
310 bool cancelOnError}) { | |
311 return _controller.stream.listen(onData, | |
312 onError: onError, | |
313 onDone: onDone, | |
314 cancelOnError: cancelOnError); | |
315 } | |
316 | |
317 void listenToStream(Stream<List<int>> stream) { | |
318 // Listen to the stream and handle data accordingly. When a | |
319 // _HttpIncoming is created, _dataPause, _dataResume, _dataDone is | |
320 // given to provide a way of controlling the parser. | |
321 // TODO(ajohnsen): Remove _dataPause, _dataResume and _dataDone and clean up | |
322 // how the _HttpIncoming signals the parser. | |
323 _socketSubscription = stream.listen( | |
324 _onData, | |
325 onError: _controller.addError, | |
326 onDone: _onDone); | |
327 } | |
328 | |
329 void _parse() { | |
330 try { | |
331 _doParse(); | |
332 } catch (e, s) { | |
333 _state = _State.FAILURE; | |
334 _reportError(e, s); | |
335 } | |
336 } | |
337 | |
338 // Process end of headers. Returns true if the parser should stop | |
339 // parsing and return. This will be in case of either an upgrade | |
340 // request or a request or response with an empty body. | |
341 bool _headersEnd() { | |
342 _headers._mutable = false; | |
343 | |
344 _transferLength = _headers.contentLength; | |
345 // Ignore the Content-Length header if Transfer-Encoding | |
346 // is chunked (RFC 2616 section 4.4) | |
347 if (_chunked) _transferLength = -1; | |
348 | |
349 // If a request message has neither Content-Length nor | |
350 // Transfer-Encoding the message must not have a body (RFC | |
351 // 2616 section 4.3). | |
352 if (_messageType == _MessageType.REQUEST && | |
353 _transferLength < 0 && | |
354 _chunked == false) { | |
355 _transferLength = 0; | |
356 } | |
357 if (_connectionUpgrade) { | |
358 _state = _State.UPGRADED; | |
359 _transferLength = 0; | |
360 } | |
361 _createIncoming(_transferLength); | |
362 if (_requestParser) { | |
363 _incoming.method = | |
364 new String.fromCharCodes(_method); | |
365 _incoming.uri = | |
366 Uri.parse( | |
367 new String.fromCharCodes(_uri_or_reason_phrase)); | |
368 } else { | |
369 _incoming.statusCode = _statusCode; | |
370 _incoming.reasonPhrase = | |
371 new String.fromCharCodes(_uri_or_reason_phrase); | |
372 } | |
373 _method.clear(); | |
374 _uri_or_reason_phrase.clear(); | |
375 if (_connectionUpgrade) { | |
376 _incoming.upgraded = true; | |
377 _parserCalled = false; | |
378 var tmp = _incoming; | |
379 _closeIncoming(); | |
380 _controller.add(tmp); | |
381 return true; | |
382 } | |
383 if (_transferLength == 0 || | |
384 (_messageType == _MessageType.RESPONSE && _noMessageBody)) { | |
385 _reset(); | |
386 var tmp = _incoming; | |
387 _closeIncoming(); | |
388 _controller.add(tmp); | |
389 return false; | |
390 } else if (_chunked) { | |
391 _state = _State.CHUNK_SIZE; | |
392 _remainingContent = 0; | |
393 } else if (_transferLength > 0) { | |
394 _remainingContent = _transferLength; | |
395 _state = _State.BODY; | |
396 } else { | |
397 // Neither chunked nor content length. End of body | |
398 // indicated by close. | |
399 _state = _State.BODY; | |
400 } | |
401 _parserCalled = false; | |
402 _controller.add(_incoming); | |
403 return true; | |
404 } | |
405 | |
406 // From RFC 2616. | |
407 // generic-message = start-line | |
408 // *(message-header CRLF) | |
409 // CRLF | |
410 // [ message-body ] | |
411 // start-line = Request-Line | Status-Line | |
412 // Request-Line = Method SP Request-URI SP HTTP-Version CRLF | |
413 // Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF | |
414 // message-header = field-name ":" [ field-value ] | |
415 void _doParse() { | |
416 assert(!_parserCalled); | |
417 _parserCalled = true; | |
418 if (_state == _State.CLOSED) { | |
419 throw new HttpException("Data on closed connection"); | |
420 } | |
421 if (_state == _State.FAILURE) { | |
422 throw new HttpException("Data on failed connection"); | |
423 } | |
424 while (_buffer != null && | |
425 _index < _buffer.length && | |
426 _state != _State.FAILURE && | |
427 _state != _State.UPGRADED) { | |
428 // Depending on _incoming, we either break on _bodyPaused or _paused. | |
429 if ((_incoming != null && _bodyPaused) || | |
430 (_incoming == null && _paused)) { | |
431 _parserCalled = false; | |
432 return; | |
433 } | |
434 int byte = _buffer[_index++]; | |
435 switch (_state) { | |
436 case _State.START: | |
437 if (byte == _Const.HTTP[0]) { | |
438 // Start parsing method or HTTP version. | |
439 _httpVersionIndex = 1; | |
440 _state = _State.METHOD_OR_RESPONSE_HTTP_VERSION; | |
441 } else { | |
442 // Start parsing method. | |
443 if (!_isTokenChar(byte)) { | |
444 throw new HttpException("Invalid request method"); | |
445 } | |
446 _method.add(byte); | |
447 if (!_requestParser) { | |
448 throw new HttpException("Invalid response line"); | |
449 } | |
450 _state = _State.REQUEST_LINE_METHOD; | |
451 } | |
452 break; | |
453 | |
454 case _State.METHOD_OR_RESPONSE_HTTP_VERSION: | |
455 if (_httpVersionIndex < _Const.HTTP.length && | |
456 byte == _Const.HTTP[_httpVersionIndex]) { | |
457 // Continue parsing HTTP version. | |
458 _httpVersionIndex++; | |
459 } else if (_httpVersionIndex == _Const.HTTP.length && | |
460 byte == _CharCode.SLASH) { | |
461 // HTTP/ parsed. As method is a token this cannot be a | |
462 // method anymore. | |
463 _httpVersionIndex++; | |
464 if (_requestParser) { | |
465 throw new HttpException("Invalid request line"); | |
466 } | |
467 _state = _State.RESPONSE_HTTP_VERSION; | |
468 } else { | |
469 // Did not parse HTTP version. Expect method instead. | |
470 for (int i = 0; i < _httpVersionIndex; i++) { | |
471 _method.add(_Const.HTTP[i]); | |
472 } | |
473 if (byte == _CharCode.SP) { | |
474 _state = _State.REQUEST_LINE_URI; | |
475 } else { | |
476 _method.add(byte); | |
477 _httpVersion = _HttpVersion.UNDETERMINED; | |
478 if (!_requestParser) { | |
479 throw new HttpException("Invalid response line"); | |
480 } | |
481 _state = _State.REQUEST_LINE_METHOD; | |
482 } | |
483 } | |
484 break; | |
485 | |
486 case _State.RESPONSE_HTTP_VERSION: | |
487 if (_httpVersionIndex < _Const.HTTP1DOT.length) { | |
488 // Continue parsing HTTP version. | |
489 _expect(byte, _Const.HTTP1DOT[_httpVersionIndex]); | |
490 _httpVersionIndex++; | |
491 } else if (_httpVersionIndex == _Const.HTTP1DOT.length && | |
492 byte == _CharCode.ONE) { | |
493 // HTTP/1.1 parsed. | |
494 _httpVersion = _HttpVersion.HTTP11; | |
495 _persistentConnection = true; | |
496 _httpVersionIndex++; | |
497 } else if (_httpVersionIndex == _Const.HTTP1DOT.length && | |
498 byte == _CharCode.ZERO) { | |
499 // HTTP/1.0 parsed. | |
500 _httpVersion = _HttpVersion.HTTP10; | |
501 _persistentConnection = false; | |
502 _httpVersionIndex++; | |
503 } else if (_httpVersionIndex == _Const.HTTP1DOT.length + 1) { | |
504 _expect(byte, _CharCode.SP); | |
505 // HTTP version parsed. | |
506 _state = _State.RESPONSE_LINE_STATUS_CODE; | |
507 } else { | |
508 throw new HttpException("Invalid response line"); | |
509 } | |
510 break; | |
511 | |
512 case _State.REQUEST_LINE_METHOD: | |
513 if (byte == _CharCode.SP) { | |
514 _state = _State.REQUEST_LINE_URI; | |
515 } else { | |
516 if (_Const.SEPARATOR_MAP[byte] || | |
517 byte == _CharCode.CR || | |
518 byte == _CharCode.LF) { | |
519 throw new HttpException("Invalid request method"); | |
520 } | |
521 _method.add(byte); | |
522 } | |
523 break; | |
524 | |
525 case _State.REQUEST_LINE_URI: | |
526 if (byte == _CharCode.SP) { | |
527 if (_uri_or_reason_phrase.length == 0) { | |
528 throw new HttpException("Invalid request URI"); | |
529 } | |
530 _state = _State.REQUEST_LINE_HTTP_VERSION; | |
531 _httpVersionIndex = 0; | |
532 } else { | |
533 if (byte == _CharCode.CR || byte == _CharCode.LF) { | |
534 throw new HttpException("Invalid request URI"); | |
535 } | |
536 _uri_or_reason_phrase.add(byte); | |
537 } | |
538 break; | |
539 | |
540 case _State.REQUEST_LINE_HTTP_VERSION: | |
541 if (_httpVersionIndex < _Const.HTTP1DOT.length) { | |
542 _expect(byte, _Const.HTTP11[_httpVersionIndex]); | |
543 _httpVersionIndex++; | |
544 } else if (_httpVersionIndex == _Const.HTTP1DOT.length) { | |
545 if (byte == _CharCode.ONE) { | |
546 // HTTP/1.1 parsed. | |
547 _httpVersion = _HttpVersion.HTTP11; | |
548 _persistentConnection = true; | |
549 _httpVersionIndex++; | |
550 } else if (byte == _CharCode.ZERO) { | |
551 // HTTP/1.0 parsed. | |
552 _httpVersion = _HttpVersion.HTTP10; | |
553 _persistentConnection = false; | |
554 _httpVersionIndex++; | |
555 } else { | |
556 throw new HttpException("Invalid response line"); | |
557 } | |
558 } else { | |
559 if (byte == _CharCode.CR) { | |
560 _state = _State.REQUEST_LINE_ENDING; | |
561 } else { | |
562 _expect(byte, _CharCode.LF); | |
563 _messageType = _MessageType.REQUEST; | |
564 _state = _State.HEADER_START; | |
565 } | |
566 } | |
567 break; | |
568 | |
569 case _State.REQUEST_LINE_ENDING: | |
570 _expect(byte, _CharCode.LF); | |
571 _messageType = _MessageType.REQUEST; | |
572 _state = _State.HEADER_START; | |
573 break; | |
574 | |
575 case _State.RESPONSE_LINE_STATUS_CODE: | |
576 if (byte == _CharCode.SP) { | |
577 _state = _State.RESPONSE_LINE_REASON_PHRASE; | |
578 } else if (byte == _CharCode.CR) { | |
579 // Some HTTP servers does not follow the spec. and send | |
580 // \r\n right after the status code. | |
581 _state = _State.RESPONSE_LINE_ENDING; | |
582 } else { | |
583 _statusCodeLength++; | |
584 if ((byte < 0x30 && 0x39 < byte) || _statusCodeLength > 3) { | |
585 throw new HttpException("Invalid response status code"); | |
586 } else { | |
587 _statusCode = _statusCode * 10 + byte - 0x30; | |
588 } | |
589 } | |
590 break; | |
591 | |
592 case _State.RESPONSE_LINE_REASON_PHRASE: | |
593 if (byte == _CharCode.CR) { | |
594 _state = _State.RESPONSE_LINE_ENDING; | |
595 } else { | |
596 if (byte == _CharCode.CR || byte == _CharCode.LF) { | |
597 throw new HttpException("Invalid response reason phrase"); | |
598 } | |
599 _uri_or_reason_phrase.add(byte); | |
600 } | |
601 break; | |
602 | |
603 case _State.RESPONSE_LINE_ENDING: | |
604 _expect(byte, _CharCode.LF); | |
605 _messageType == _MessageType.RESPONSE; | |
606 if (_statusCode < 100 || _statusCode > 599) { | |
607 throw new HttpException("Invalid response status code"); | |
608 } else { | |
609 // Check whether this response will never have a body. | |
610 if (_statusCode <= 199 || _statusCode == 204 || | |
611 _statusCode == 304) { | |
612 _noMessageBody = true; | |
613 } | |
614 } | |
615 _state = _State.HEADER_START; | |
616 break; | |
617 | |
618 case _State.HEADER_START: | |
619 _headers = new _HttpHeaders(version); | |
620 if (byte == _CharCode.CR) { | |
621 _state = _State.HEADER_ENDING; | |
622 } else if (byte == _CharCode.LF) { | |
623 _state = _State.HEADER_ENDING; | |
624 _index--; // Make the new state see the LF again. | |
625 } else { | |
626 // Start of new header field. | |
627 _headerField.add(_toLowerCaseByte(byte)); | |
628 _state = _State.HEADER_FIELD; | |
629 } | |
630 break; | |
631 | |
632 case _State.HEADER_FIELD: | |
633 if (byte == _CharCode.COLON) { | |
634 _state = _State.HEADER_VALUE_START; | |
635 } else { | |
636 if (!_isTokenChar(byte)) { | |
637 throw new HttpException("Invalid header field name"); | |
638 } | |
639 _headerField.add(_toLowerCaseByte(byte)); | |
640 } | |
641 break; | |
642 | |
643 case _State.HEADER_VALUE_START: | |
644 if (byte == _CharCode.CR) { | |
645 _state = _State.HEADER_VALUE_FOLDING_OR_ENDING; | |
646 } else if (byte == _CharCode.LF) { | |
647 _state = _State.HEADER_VALUE_FOLD_OR_END; | |
648 } else if (byte != _CharCode.SP && byte != _CharCode.HT) { | |
649 // Start of new header value. | |
650 _headerValue.add(byte); | |
651 _state = _State.HEADER_VALUE; | |
652 } | |
653 break; | |
654 | |
655 case _State.HEADER_VALUE: | |
656 if (byte == _CharCode.CR) { | |
657 _state = _State.HEADER_VALUE_FOLDING_OR_ENDING; | |
658 } else if (byte == _CharCode.LF) { | |
659 _state = _State.HEADER_VALUE_FOLD_OR_END; | |
660 } else { | |
661 _headerValue.add(byte); | |
662 } | |
663 break; | |
664 | |
665 case _State.HEADER_VALUE_FOLDING_OR_ENDING: | |
666 _expect(byte, _CharCode.LF); | |
667 _state = _State.HEADER_VALUE_FOLD_OR_END; | |
668 break; | |
669 | |
670 case _State.HEADER_VALUE_FOLD_OR_END: | |
671 if (byte == _CharCode.SP || byte == _CharCode.HT) { | |
672 _state = _State.HEADER_VALUE_START; | |
673 } else { | |
674 String headerField = new String.fromCharCodes(_headerField); | |
675 String headerValue = new String.fromCharCodes(_headerValue); | |
676 if (headerField == "transfer-encoding" && | |
677 _caseInsensitiveCompare("chunked".codeUnits, _headerValue)) { | |
678 _chunked = true; | |
679 } | |
680 if (headerField == "connection") { | |
681 List<String> tokens = _tokenizeFieldValue(headerValue); | |
682 for (int i = 0; i < tokens.length; i++) { | |
683 if (_caseInsensitiveCompare("upgrade".codeUnits, | |
684 tokens[i].codeUnits)) { | |
685 _connectionUpgrade = true; | |
686 } | |
687 _headers._add(headerField, tokens[i]); | |
688 } | |
689 } else { | |
690 _headers._add(headerField, headerValue); | |
691 } | |
692 _headerField.clear(); | |
693 _headerValue.clear(); | |
694 | |
695 if (byte == _CharCode.CR) { | |
696 _state = _State.HEADER_ENDING; | |
697 } else if (byte == _CharCode.LF) { | |
698 _state = _State.HEADER_ENDING; | |
699 _index--; // Make the new state see the LF again. | |
700 } else { | |
701 // Start of new header field. | |
702 _headerField.add(_toLowerCaseByte(byte)); | |
703 _state = _State.HEADER_FIELD; | |
704 } | |
705 } | |
706 break; | |
707 | |
708 case _State.HEADER_ENDING: | |
709 _expect(byte, _CharCode.LF); | |
710 if (_headersEnd()) { | |
711 return; | |
712 } else { | |
713 break; | |
714 } | |
715 return; | |
716 | |
717 case _State.CHUNK_SIZE_STARTING_CR: | |
718 _expect(byte, _CharCode.CR); | |
719 _state = _State.CHUNK_SIZE_STARTING_LF; | |
720 break; | |
721 | |
722 case _State.CHUNK_SIZE_STARTING_LF: | |
723 _expect(byte, _CharCode.LF); | |
724 _state = _State.CHUNK_SIZE; | |
725 break; | |
726 | |
727 case _State.CHUNK_SIZE: | |
728 if (byte == _CharCode.CR) { | |
729 _state = _State.CHUNK_SIZE_ENDING; | |
730 } else if (byte == _CharCode.SEMI_COLON) { | |
731 _state = _State.CHUNK_SIZE_EXTENSION; | |
732 } else { | |
733 int value = _expectHexDigit(byte); | |
734 _remainingContent = _remainingContent * 16 + value; | |
735 } | |
736 break; | |
737 | |
738 case _State.CHUNK_SIZE_EXTENSION: | |
739 if (byte == _CharCode.CR) { | |
740 _state = _State.CHUNK_SIZE_ENDING; | |
741 } | |
742 break; | |
743 | |
744 case _State.CHUNK_SIZE_ENDING: | |
745 _expect(byte, _CharCode.LF); | |
746 if (_remainingContent > 0) { | |
747 _state = _State.BODY; | |
748 } else { | |
749 _state = _State.CHUNKED_BODY_DONE_CR; | |
750 } | |
751 break; | |
752 | |
753 case _State.CHUNKED_BODY_DONE_CR: | |
754 _expect(byte, _CharCode.CR); | |
755 _state = _State.CHUNKED_BODY_DONE_LF; | |
756 break; | |
757 | |
758 case _State.CHUNKED_BODY_DONE_LF: | |
759 _expect(byte, _CharCode.LF); | |
760 _reset(); | |
761 _closeIncoming(); | |
762 break; | |
763 | |
764 case _State.BODY: | |
765 // The body is not handled one byte at a time but in blocks. | |
766 _index--; | |
767 int dataAvailable = _buffer.length - _index; | |
768 if (_remainingContent >= 0 && dataAvailable > _remainingContent) { | |
769 dataAvailable = _remainingContent; | |
770 } | |
771 // Always present the data as a view. This way we can handle all | |
772 // cases like this, and the user will not experince different data | |
773 // typed (which could lead to polymorphic user code). | |
774 List<int> data = new Uint8List.view(_buffer.buffer, | |
775 _buffer.offsetInBytes + _index, | |
776 dataAvailable); | |
777 _bodyController.add(data); | |
778 if (_remainingContent != -1) { | |
779 _remainingContent -= data.length; | |
780 } | |
781 _index += data.length; | |
782 if (_remainingContent == 0) { | |
783 if (!_chunked) { | |
784 _reset(); | |
785 _closeIncoming(); | |
786 } else { | |
787 _state = _State.CHUNK_SIZE_STARTING_CR; | |
788 } | |
789 } | |
790 break; | |
791 | |
792 case _State.FAILURE: | |
793 // Should be unreachable. | |
794 assert(false); | |
795 break; | |
796 | |
797 default: | |
798 // Should be unreachable. | |
799 assert(false); | |
800 break; | |
801 } | |
802 } | |
803 | |
804 _parserCalled = false; | |
805 if (_buffer != null && _index == _buffer.length) { | |
806 // If all data is parsed release the buffer and resume receiving | |
807 // data. | |
808 _releaseBuffer(); | |
809 if (_state != _State.UPGRADED && _state != _State.FAILURE) { | |
810 _socketSubscription.resume(); | |
811 } | |
812 } | |
813 } | |
814 | |
815 void _onData(List<int> buffer) { | |
816 _socketSubscription.pause(); | |
817 assert(_buffer == null); | |
818 _buffer = buffer; | |
819 _index = 0; | |
820 _parse(); | |
821 } | |
822 | |
823 void _onDone() { | |
824 // onDone cancles the subscription. | |
825 _socketSubscription = null; | |
826 if (_state == _State.CLOSED || _state == _State.FAILURE) return; | |
827 | |
828 if (_incoming != null) { | |
829 if (_state != _State.UPGRADED && | |
830 !(_state == _State.START && !_requestParser) && | |
831 !(_state == _State.BODY && !_chunked && _transferLength == -1)) { | |
832 _bodyController.addError( | |
833 new HttpException("Connection closed while receiving data")); | |
834 } | |
835 _closeIncoming(true); | |
836 _controller.close(); | |
837 return; | |
838 } | |
839 // If the connection is idle the HTTP stream is closed. | |
840 if (_state == _State.START) { | |
841 if (!_requestParser) { | |
842 _reportError(new HttpException( | |
843 "Connection closed before full header was received")); | |
844 } | |
845 _controller.close(); | |
846 return; | |
847 } | |
848 | |
849 if (_state == _State.UPGRADED) { | |
850 _controller.close(); | |
851 return; | |
852 } | |
853 | |
854 if (_state < _State.FIRST_BODY_STATE) { | |
855 _state = _State.FAILURE; | |
856 // Report the error through the error callback if any. Otherwise | |
857 // throw the error. | |
858 _reportError(new HttpException( | |
859 "Connection closed before full header was received")); | |
860 _controller.close(); | |
861 return; | |
862 } | |
863 | |
864 if (!_chunked && _transferLength == -1) { | |
865 _state = _State.CLOSED; | |
866 } else { | |
867 _state = _State.FAILURE; | |
868 // Report the error through the error callback if any. Otherwise | |
869 // throw the error. | |
870 _reportError(new HttpException( | |
871 "Connection closed before full body was received")); | |
872 } | |
873 _controller.close(); | |
874 } | |
875 | |
876 String get version { | |
877 switch (_httpVersion) { | |
878 case _HttpVersion.HTTP10: | |
879 return "1.0"; | |
880 case _HttpVersion.HTTP11: | |
881 return "1.1"; | |
882 } | |
883 return null; | |
884 } | |
885 | |
886 int get messageType => _messageType; | |
887 int get transferLength => _transferLength; | |
888 bool get upgrade => _connectionUpgrade && _state == _State.UPGRADED; | |
889 bool get persistentConnection => _persistentConnection; | |
890 | |
891 void set isHead(bool value) { | |
892 if (value) _noMessageBody = true; | |
893 } | |
894 | |
895 _HttpDetachedIncoming detachIncoming() { | |
896 // Simulate detached by marking as upgraded. | |
897 _state = _State.UPGRADED; | |
898 return new _HttpDetachedIncoming(_socketSubscription, | |
899 readUnparsedData()); | |
900 } | |
901 | |
902 List<int> readUnparsedData() { | |
903 if (_buffer == null) return null; | |
904 if (_index == _buffer.length) return null; | |
905 var result = _buffer.sublist(_index); | |
906 _releaseBuffer(); | |
907 return result; | |
908 } | |
909 | |
910 void _reset() { | |
911 if (_state == _State.UPGRADED) return; | |
912 _state = _State.START; | |
913 _messageType = _MessageType.UNDETERMINED; | |
914 _headerField.clear(); | |
915 _headerValue.clear(); | |
916 _method.clear(); | |
917 _uri_or_reason_phrase.clear(); | |
918 | |
919 _statusCode = 0; | |
920 _statusCodeLength = 0; | |
921 | |
922 _httpVersion = _HttpVersion.UNDETERMINED; | |
923 _transferLength = -1; | |
924 _persistentConnection = false; | |
925 _connectionUpgrade = false; | |
926 _chunked = false; | |
927 | |
928 _noMessageBody = false; | |
929 _remainingContent = -1; | |
930 | |
931 _headers = null; | |
932 } | |
933 | |
934 void _releaseBuffer() { | |
935 _buffer = null; | |
936 _index = null; | |
937 } | |
938 | |
939 static bool _isTokenChar(int byte) { | |
940 return byte > 31 && byte < 128 && !_Const.SEPARATOR_MAP[byte]; | |
941 } | |
942 | |
943 static bool _isValueChar(int byte) { | |
944 return (byte > 31 && byte < 128) || (byte == _CharCode.SP) || | |
945 (byte == _CharCode.HT); | |
946 } | |
947 | |
948 static List<String> _tokenizeFieldValue(String headerValue) { | |
949 List<String> tokens = new List<String>(); | |
950 int start = 0; | |
951 int index = 0; | |
952 while (index < headerValue.length) { | |
953 if (headerValue[index] == ",") { | |
954 tokens.add(headerValue.substring(start, index)); | |
955 start = index + 1; | |
956 } else if (headerValue[index] == " " || headerValue[index] == "\t") { | |
957 start++; | |
958 } | |
959 index++; | |
960 } | |
961 tokens.add(headerValue.substring(start, index)); | |
962 return tokens; | |
963 } | |
964 | |
965 static int _toLowerCaseByte(int x) { | |
966 // Optimized version: | |
967 // - 0x41 is 'A' | |
968 // - 0x7f is ASCII mask | |
969 // - 26 is the number of alpha characters. | |
970 // - 0x20 is the delta between lower and upper chars. | |
971 return (((x - 0x41) & 0x7f) < 26) ? (x | 0x20) : x; | |
972 } | |
973 | |
974 // expected should already be lowercase. | |
975 bool _caseInsensitiveCompare(List<int> expected, List<int> value) { | |
976 if (expected.length != value.length) return false; | |
977 for (int i = 0; i < expected.length; i++) { | |
978 if (expected[i] != _toLowerCaseByte(value[i])) return false; | |
979 } | |
980 return true; | |
981 } | |
982 | |
983 int _expect(int val1, int val2) { | |
984 if (val1 != val2) { | |
985 throw new HttpException("Failed to parse HTTP"); | |
986 } | |
987 } | |
988 | |
989 int _expectHexDigit(int byte) { | |
990 if (0x30 <= byte && byte <= 0x39) { | |
991 return byte - 0x30; // 0 - 9 | |
992 } else if (0x41 <= byte && byte <= 0x46) { | |
993 return byte - 0x41 + 10; // A - F | |
994 } else if (0x61 <= byte && byte <= 0x66) { | |
995 return byte - 0x61 + 10; // a - f | |
996 } else { | |
997 throw new HttpException("Failed to parse HTTP"); | |
998 } | |
999 } | |
1000 | |
1001 void _createIncoming(int transferLength) { | |
1002 assert(_incoming == null); | |
1003 assert(_bodyController == null); | |
1004 assert(!_bodyPaused); | |
1005 var incoming; | |
1006 _bodyController = new StreamController<List<int>>( | |
1007 sync: true, | |
1008 onListen: () { | |
1009 if (incoming != _incoming) return; | |
1010 assert(_bodyPaused); | |
1011 _bodyPaused = false; | |
1012 _pauseStateChanged(); | |
1013 }, | |
1014 onPause: () { | |
1015 if (incoming != _incoming) return; | |
1016 assert(!_bodyPaused); | |
1017 _bodyPaused = true; | |
1018 _pauseStateChanged(); | |
1019 }, | |
1020 onResume: () { | |
1021 if (incoming != _incoming) return; | |
1022 assert(_bodyPaused); | |
1023 _bodyPaused = false; | |
1024 _pauseStateChanged(); | |
1025 }, | |
1026 onCancel: () { | |
1027 if (incoming != _incoming) return; | |
1028 if (_socketSubscription != null) { | |
1029 _socketSubscription.cancel(); | |
1030 } | |
1031 _closeIncoming(true); | |
1032 _controller.close(); | |
1033 }); | |
1034 incoming = _incoming = new _HttpIncoming( | |
1035 _headers, transferLength, _bodyController.stream); | |
1036 _bodyPaused = true; | |
1037 _pauseStateChanged(); | |
1038 } | |
1039 | |
1040 void _closeIncoming([bool closing = false]) { | |
1041 // Ignore multiple close (can happen in re-entrance). | |
1042 if (_incoming == null) return; | |
1043 var tmp = _incoming; | |
1044 tmp.close(closing); | |
1045 _incoming = null; | |
1046 if (_bodyController != null) { | |
1047 _bodyController.close(); | |
1048 _bodyController = null; | |
1049 } | |
1050 _bodyPaused = false; | |
1051 _pauseStateChanged(); | |
1052 } | |
1053 | |
1054 void _pauseStateChanged() { | |
1055 if (_incoming != null) { | |
1056 if (!_bodyPaused && !_parserCalled) { | |
1057 _parse(); | |
1058 } | |
1059 } else { | |
1060 if (!_paused && !_parserCalled) { | |
1061 _parse(); | |
1062 } | |
1063 } | |
1064 } | |
1065 | |
1066 void _reportError(error, [stackTrace]) { | |
1067 if (_socketSubscription != null) _socketSubscription.cancel(); | |
1068 _state = _State.FAILURE; | |
1069 _controller.addError(error, stackTrace); | |
1070 _controller.close(); | |
1071 } | |
1072 } | |
OLD | NEW |