Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(77)

Side by Side Diff: pkg/dev_compiler/tool/input_sdk/lib/io/http_impl.dart

Issue 2698353003: unfork DDC's copy of most SDK libraries (Closed)
Patch Set: revert core_patch Created 3 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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 const int _OUTGOING_BUFFER_SIZE = 8 * 1024;
8
9 class _HttpIncoming extends Stream<List<int>> {
10 final int _transferLength;
11 final Completer _dataCompleter = new Completer();
12 Stream<List<int>> _stream;
13
14 bool fullBodyRead = false;
15
16 // Common properties.
17 final _HttpHeaders headers;
18 bool upgraded = false;
19
20 // ClientResponse properties.
21 int statusCode;
22 String reasonPhrase;
23
24 // Request properties.
25 String method;
26 Uri uri;
27
28 bool hasSubscriber = false;
29
30 // The transfer length if the length of the message body as it
31 // appears in the message (RFC 2616 section 4.4). This can be -1 if
32 // the length of the massage body is not known due to transfer
33 // codings.
34 int get transferLength => _transferLength;
35
36 _HttpIncoming(this.headers, this._transferLength, this._stream);
37
38 StreamSubscription<List<int>> listen(void onData(List<int> event),
39 {Function onError,
40 void onDone(),
41 bool cancelOnError}) {
42 hasSubscriber = true;
43 return _stream
44 .handleError((error) {
45 throw new HttpException(error.message, uri: uri);
46 })
47 .listen(onData,
48 onError: onError,
49 onDone: onDone,
50 cancelOnError: cancelOnError);
51 }
52
53 // Is completed once all data have been received.
54 Future get dataDone => _dataCompleter.future;
55
56 void close(bool closing) {
57 fullBodyRead = true;
58 hasSubscriber = true;
59 _dataCompleter.complete(closing);
60 }
61 }
62
63 abstract class _HttpInboundMessage extends Stream<List<int>> {
64 final _HttpIncoming _incoming;
65 List<Cookie> _cookies;
66
67 _HttpInboundMessage(this._incoming);
68
69 List<Cookie> get cookies {
70 if (_cookies != null) return _cookies;
71 return _cookies = headers._parseCookies();
72 }
73
74 _HttpHeaders get headers => _incoming.headers;
75 String get protocolVersion => headers.protocolVersion;
76 int get contentLength => headers.contentLength;
77 bool get persistentConnection => headers.persistentConnection;
78 }
79
80
81 class _HttpRequest extends _HttpInboundMessage implements HttpRequest {
82 final HttpResponse response;
83
84 final _HttpServer _httpServer;
85
86 final _HttpConnection _httpConnection;
87
88 _HttpSession _session;
89
90 Uri _requestedUri;
91
92 _HttpRequest(this.response, _HttpIncoming _incoming, this._httpServer,
93 this._httpConnection) : super(_incoming) {
94 if (headers.protocolVersion == "1.1") {
95 response.headers
96 ..chunkedTransferEncoding = true
97 ..persistentConnection = headers.persistentConnection;
98 }
99
100 if (_httpServer._sessionManagerInstance != null) {
101 // Map to session if exists.
102 var sessionIds = cookies
103 .where((cookie) => cookie.name.toUpperCase() == _DART_SESSION_ID)
104 .map((cookie) => cookie.value);
105 for (var sessionId in sessionIds) {
106 _session = _httpServer._sessionManager.getSession(sessionId);
107 if (_session != null) {
108 _session._markSeen();
109 break;
110 }
111 }
112 }
113 }
114
115 StreamSubscription<List<int>> listen(void onData(List<int> event),
116 {Function onError,
117 void onDone(),
118 bool cancelOnError}) {
119 return _incoming.listen(onData,
120 onError: onError,
121 onDone: onDone,
122 cancelOnError: cancelOnError);
123 }
124
125 Uri get uri => _incoming.uri;
126
127 Uri get requestedUri {
128 if (_requestedUri == null) {
129 var proto = headers['x-forwarded-proto'];
130 var scheme = proto != null ? proto.first :
131 _httpConnection._socket is SecureSocket ? "https" : "http";
132 var hostList = headers['x-forwarded-host'];
133 String host;
134 if (hostList != null) {
135 host = hostList.first;
136 } else {
137 hostList = headers['host'];
138 if (hostList != null) {
139 host = hostList.first;
140 } else {
141 host = "${_httpServer.address.host}:${_httpServer.port}";
142 }
143 }
144 _requestedUri = Uri.parse("$scheme://$host$uri");
145 }
146 return _requestedUri;
147 }
148
149 String get method => _incoming.method;
150
151 HttpSession get session {
152 if (_session != null) {
153 if (_session._destroyed) {
154 // It's destroyed, clear it.
155 _session = null;
156 // Create new session object by calling recursive.
157 return session;
158 }
159 // It's already mapped, use it.
160 return _session;
161 }
162 // Create session, store it in connection, and return.
163 return _session = _httpServer._sessionManager.createSession();
164 }
165
166 HttpConnectionInfo get connectionInfo => _httpConnection.connectionInfo;
167
168 X509Certificate get certificate {
169 var socket = _httpConnection._socket;
170 if (socket is SecureSocket) return socket.peerCertificate;
171 return null;
172 }
173 }
174
175
176 class _HttpClientResponse
177 extends _HttpInboundMessage implements HttpClientResponse {
178 List<RedirectInfo> get redirects => _httpRequest._responseRedirects;
179
180 // The HttpClient this response belongs to.
181 final _HttpClient _httpClient;
182
183 // The HttpClientRequest of this response.
184 final _HttpClientRequest _httpRequest;
185
186 _HttpClientResponse(_HttpIncoming _incoming, this._httpRequest,
187 this._httpClient) : super(_incoming) {
188 // Set uri for potential exceptions.
189 _incoming.uri = _httpRequest.uri;
190 }
191
192 int get statusCode => _incoming.statusCode;
193 String get reasonPhrase => _incoming.reasonPhrase;
194
195 X509Certificate get certificate {
196 var socket = _httpRequest._httpClientConnection._socket;
197 if (socket is SecureSocket) return socket.peerCertificate;
198 throw new UnsupportedError("Socket is not a SecureSocket");
199 }
200
201 List<Cookie> get cookies {
202 if (_cookies != null) return _cookies;
203 _cookies = new List<Cookie>();
204 List<String> values = headers[HttpHeaders.SET_COOKIE];
205 if (values != null) {
206 values.forEach((value) {
207 _cookies.add(new Cookie.fromSetCookieValue(value));
208 });
209 }
210 return _cookies;
211 }
212
213 bool get isRedirect {
214 if (_httpRequest.method == "GET" || _httpRequest.method == "HEAD") {
215 return statusCode == HttpStatus.MOVED_PERMANENTLY ||
216 statusCode == HttpStatus.FOUND ||
217 statusCode == HttpStatus.SEE_OTHER ||
218 statusCode == HttpStatus.TEMPORARY_REDIRECT;
219 } else if (_httpRequest.method == "POST") {
220 return statusCode == HttpStatus.SEE_OTHER;
221 }
222 return false;
223 }
224
225 Future<HttpClientResponse> redirect([String method,
226 Uri url,
227 bool followLoops]) {
228 if (method == null) {
229 // Set method as defined by RFC 2616 section 10.3.4.
230 if (statusCode == HttpStatus.SEE_OTHER && _httpRequest.method == "POST") {
231 method = "GET";
232 } else {
233 method = _httpRequest.method;
234 }
235 }
236 if (url == null) {
237 String location = headers.value(HttpHeaders.LOCATION);
238 if (location == null) {
239 throw new StateError("Response has no Location header for redirect");
240 }
241 url = Uri.parse(location);
242 }
243 if (followLoops != true) {
244 for (var redirect in redirects) {
245 if (redirect.location == url) {
246 return new Future.error(
247 new RedirectException("Redirect loop detected", redirects));
248 }
249 }
250 }
251 return _httpClient._openUrlFromRequest(method, url, _httpRequest)
252 .then((request) {
253 request._responseRedirects
254 ..addAll(this.redirects)
255 ..add(new _RedirectInfo(statusCode, method, url));
256 return request.close();
257 });
258 }
259
260 StreamSubscription<List<int>> listen(void onData(List<int> event),
261 {Function onError,
262 void onDone(),
263 bool cancelOnError}) {
264 if (_incoming.upgraded) {
265 // If upgraded, the connection is already 'removed' form the client.
266 // Since listening to upgraded data is 'bogus', simply close and
267 // return empty stream subscription.
268 _httpRequest._httpClientConnection.destroy();
269 return new Stream.fromIterable([]).listen(null, onDone: onDone);
270 }
271 var stream = _incoming;
272 if (_httpClient.autoUncompress &&
273 headers.value(HttpHeaders.CONTENT_ENCODING) == "gzip") {
274 stream = stream.transform(GZIP.decoder);
275 }
276 return stream.listen(onData,
277 onError: onError,
278 onDone: onDone,
279 cancelOnError: cancelOnError);
280 }
281
282 Future<Socket> detachSocket() {
283 _httpClient._connectionClosed(_httpRequest._httpClientConnection);
284 return _httpRequest._httpClientConnection.detachSocket();
285 }
286
287 HttpConnectionInfo get connectionInfo => _httpRequest.connectionInfo;
288
289 bool get _shouldAuthenticateProxy {
290 // Only try to authenticate if there is a challenge in the response.
291 List<String> challenge = headers[HttpHeaders.PROXY_AUTHENTICATE];
292 return statusCode == HttpStatus.PROXY_AUTHENTICATION_REQUIRED &&
293 challenge != null && challenge.length == 1;
294 }
295
296 bool get _shouldAuthenticate {
297 // Only try to authenticate if there is a challenge in the response.
298 List<String> challenge = headers[HttpHeaders.WWW_AUTHENTICATE];
299 return statusCode == HttpStatus.UNAUTHORIZED &&
300 challenge != null && challenge.length == 1;
301 }
302
303 Future<HttpClientResponse> _authenticate(bool proxyAuth) {
304 Future<HttpClientResponse> retry() {
305 // Drain body and retry.
306 return drain().then((_) {
307 return _httpClient._openUrlFromRequest(_httpRequest.method,
308 _httpRequest.uri,
309 _httpRequest)
310 .then((request) => request.close());
311 });
312 }
313
314 List<String> authChallenge() {
315 return proxyAuth ? headers[HttpHeaders.PROXY_AUTHENTICATE]
316 : headers[HttpHeaders.WWW_AUTHENTICATE];
317 }
318
319 _Credentials findCredentials(_AuthenticationScheme scheme) {
320 return proxyAuth ? _httpClient._findProxyCredentials(_httpRequest._proxy,
321 scheme)
322 : _httpClient._findCredentials(_httpRequest.uri, scheme);
323 }
324
325 void removeCredentials(_Credentials cr) {
326 if (proxyAuth) {
327 _httpClient._removeProxyCredentials(cr);
328 } else {
329 _httpClient._removeCredentials(cr);
330 }
331 }
332
333 Future requestAuthentication(_AuthenticationScheme scheme, String realm) {
334 if (proxyAuth) {
335 if (_httpClient._authenticateProxy == null) {
336 return new Future.value(false);
337 }
338 var proxy = _httpRequest._proxy;
339 return _httpClient._authenticateProxy(proxy.host,
340 proxy.port,
341 scheme.toString(),
342 realm);
343 } else {
344 if (_httpClient._authenticate == null) {
345 return new Future.value(false);
346 }
347 return _httpClient._authenticate(_httpRequest.uri,
348 scheme.toString(),
349 realm);
350 }
351 }
352
353 List<String> challenge = authChallenge();
354 assert(challenge != null || challenge.length == 1);
355 _HeaderValue header =
356 _HeaderValue.parse(challenge[0], parameterSeparator: ",");
357 _AuthenticationScheme scheme =
358 new _AuthenticationScheme.fromString(header.value);
359 String realm = header.parameters["realm"];
360
361 // See if any matching credentials are available.
362 _Credentials cr = findCredentials(scheme);
363 if (cr != null) {
364 // For basic authentication don't retry already used credentials
365 // as they must have already been added to the request causing
366 // this authenticate response.
367 if (cr.scheme == _AuthenticationScheme.BASIC && !cr.used) {
368 // Credentials where found, prepare for retrying the request.
369 return retry();
370 }
371
372 // Digest authentication only supports the MD5 algorithm.
373 if (cr.scheme == _AuthenticationScheme.DIGEST &&
374 (header.parameters["algorithm"] == null ||
375 header.parameters["algorithm"].toLowerCase() == "md5")) {
376 if (cr.nonce == null || cr.nonce == header.parameters["nonce"]) {
377 // If the nonce is not set then this is the first authenticate
378 // response for these credentials. Set up authentication state.
379 if (cr.nonce == null) {
380 cr..nonce = header.parameters["nonce"]
381 ..algorithm = "MD5"
382 ..qop = header.parameters["qop"]
383 ..nonceCount = 0;
384 }
385 // Credentials where found, prepare for retrying the request.
386 return retry();
387 } else if (header.parameters["stale"] != null &&
388 header.parameters["stale"].toLowerCase() == "true") {
389 // If stale is true retry with new nonce.
390 cr.nonce = header.parameters["nonce"];
391 // Credentials where found, prepare for retrying the request.
392 return retry();
393 }
394 }
395 }
396
397 // Ask for more credentials if none found or the one found has
398 // already been used. If it has already been used it must now be
399 // invalid and is removed.
400 if (cr != null) {
401 removeCredentials(cr);
402 cr = null;
403 }
404 return requestAuthentication(scheme, realm).then((credsAvailable) {
405 if (credsAvailable) {
406 cr = _httpClient._findCredentials(_httpRequest.uri, scheme);
407 return retry();
408 } else {
409 // No credentials available, complete with original response.
410 return this;
411 }
412 });
413 }
414 }
415
416
417 abstract class _HttpOutboundMessage<T> extends _IOSinkImpl {
418 // Used to mark when the body should be written. This is used for HEAD
419 // requests and in error handling.
420 bool _encodingSet = false;
421
422 bool _bufferOutput = true;
423
424 final Uri _uri;
425 final _HttpOutgoing _outgoing;
426
427 final _HttpHeaders headers;
428
429 _HttpOutboundMessage(Uri uri,
430 String protocolVersion,
431 _HttpOutgoing outgoing,
432 {_HttpHeaders initialHeaders})
433 : _uri = uri,
434 headers = new _HttpHeaders(
435 protocolVersion,
436 defaultPortForScheme: uri.scheme == 'https' ?
437 HttpClient.DEFAULT_HTTPS_PORT :
438 HttpClient.DEFAULT_HTTP_PORT,
439 initialHeaders: initialHeaders),
440 _outgoing = outgoing,
441 super(outgoing, null) {
442 _outgoing.outbound = this;
443 _encodingMutable = false;
444 }
445
446 int get contentLength => headers.contentLength;
447 void set contentLength(int contentLength) {
448 headers.contentLength = contentLength;
449 }
450
451 bool get persistentConnection => headers.persistentConnection;
452 void set persistentConnection(bool p) {
453 headers.persistentConnection = p;
454 }
455
456 bool get bufferOutput => _bufferOutput;
457 void set bufferOutput(bool bufferOutput) {
458 if (_outgoing.headersWritten) throw new StateError("Header already sent");
459 _bufferOutput = bufferOutput;
460 }
461
462
463 Encoding get encoding {
464 if (_encodingSet && _outgoing.headersWritten) {
465 return _encoding;
466 }
467 var charset;
468 if (headers.contentType != null && headers.contentType.charset != null) {
469 charset = headers.contentType.charset;
470 } else {
471 charset = "iso-8859-1";
472 }
473 return Encoding.getByName(charset);
474 }
475
476 void add(List<int> data) {
477 if (data.length == 0) return;
478 super.add(data);
479 }
480
481 void write(Object obj) {
482 if (!_encodingSet) {
483 _encoding = encoding;
484 _encodingSet = true;
485 }
486 super.write(obj);
487 }
488
489 void _writeHeader();
490
491 bool get _isConnectionClosed => false;
492 }
493
494
495 class _HttpResponse extends _HttpOutboundMessage<HttpResponse>
496 implements HttpResponse {
497 int _statusCode = 200;
498 String _reasonPhrase;
499 List<Cookie> _cookies;
500 _HttpRequest _httpRequest;
501 Duration _deadline;
502 Timer _deadlineTimer;
503
504 _HttpResponse(Uri uri,
505 String protocolVersion,
506 _HttpOutgoing outgoing,
507 HttpHeaders defaultHeaders,
508 String serverHeader)
509 : super(uri, protocolVersion, outgoing, initialHeaders: defaultHeaders) {
510 if (serverHeader != null) headers.set('server', serverHeader);
511 }
512
513 bool get _isConnectionClosed => _httpRequest._httpConnection._isClosing;
514
515 List<Cookie> get cookies {
516 if (_cookies == null) _cookies = new List<Cookie>();
517 return _cookies;
518 }
519
520 int get statusCode => _statusCode;
521 void set statusCode(int statusCode) {
522 if (_outgoing.headersWritten) throw new StateError("Header already sent");
523 _statusCode = statusCode;
524 }
525
526 String get reasonPhrase => _findReasonPhrase(statusCode);
527 void set reasonPhrase(String reasonPhrase) {
528 if (_outgoing.headersWritten) throw new StateError("Header already sent");
529 _reasonPhrase = reasonPhrase;
530 }
531
532 Future redirect(Uri location, {int status: HttpStatus.MOVED_TEMPORARILY}) {
533 if (_outgoing.headersWritten) throw new StateError("Header already sent");
534 statusCode = status;
535 headers.set("location", location.toString());
536 return close();
537 }
538
539 Future<Socket> detachSocket({bool writeHeaders: true}) {
540 if (_outgoing.headersWritten) throw new StateError("Headers already sent");
541 deadline = null; // Be sure to stop any deadline.
542 var future = _httpRequest._httpConnection.detachSocket();
543 if (writeHeaders) {
544 var headersFuture = _outgoing.writeHeaders(drainRequest: false,
545 setOutgoing: false);
546 assert(headersFuture == null);
547 } else {
548 // Imitate having written the headers.
549 _outgoing.headersWritten = true;
550 }
551 // Close connection so the socket is 'free'.
552 close();
553 done.catchError((_) {
554 // Catch any error on done, as they automatically will be
555 // propagated to the websocket.
556 });
557 return future;
558 }
559
560 HttpConnectionInfo get connectionInfo => _httpRequest.connectionInfo;
561
562 Duration get deadline => _deadline;
563
564 void set deadline(Duration d) {
565 if (_deadlineTimer != null) _deadlineTimer.cancel();
566 _deadline = d;
567
568 if (_deadline == null) return;
569 _deadlineTimer = new Timer(_deadline, () {
570 _httpRequest._httpConnection.destroy();
571 });
572 }
573
574 void _writeHeader() {
575 Uint8List buffer = new Uint8List(_OUTGOING_BUFFER_SIZE);
576 int offset = 0;
577
578 void write(List<int> bytes) {
579 int len = bytes.length;
580 for (int i = 0; i < len; i++) {
581 buffer[offset + i] = bytes[i];
582 }
583 offset += len;
584 }
585
586 // Write status line.
587 if (headers.protocolVersion == "1.1") {
588 write(_Const.HTTP11);
589 } else {
590 write(_Const.HTTP10);
591 }
592 buffer[offset++] = _CharCode.SP;
593 write(statusCode.toString().codeUnits);
594 buffer[offset++] = _CharCode.SP;
595 write(reasonPhrase.codeUnits);
596 buffer[offset++] = _CharCode.CR;
597 buffer[offset++] = _CharCode.LF;
598
599 var session = _httpRequest._session;
600 if (session != null && !session._destroyed) {
601 // Mark as not new.
602 session._isNew = false;
603 // Make sure we only send the current session id.
604 bool found = false;
605 for (int i = 0; i < cookies.length; i++) {
606 if (cookies[i].name.toUpperCase() == _DART_SESSION_ID) {
607 cookies[i]
608 ..value = session.id
609 ..httpOnly = true
610 ..path = "/";
611 found = true;
612 }
613 }
614 if (!found) {
615 var cookie = new Cookie(_DART_SESSION_ID, session.id);
616 cookies.add(cookie
617 ..httpOnly = true
618 ..path = "/");
619 }
620 }
621 // Add all the cookies set to the headers.
622 if (_cookies != null) {
623 _cookies.forEach((cookie) {
624 headers.add(HttpHeaders.SET_COOKIE, cookie);
625 });
626 }
627
628 headers._finalize();
629
630 // Write headers.
631 offset = headers._write(buffer, offset);
632 buffer[offset++] = _CharCode.CR;
633 buffer[offset++] = _CharCode.LF;
634 _outgoing.setHeader(buffer, offset);
635 }
636
637 String _findReasonPhrase(int statusCode) {
638 if (_reasonPhrase != null) {
639 return _reasonPhrase;
640 }
641
642 switch (statusCode) {
643 case HttpStatus.CONTINUE: return "Continue";
644 case HttpStatus.SWITCHING_PROTOCOLS: return "Switching Protocols";
645 case HttpStatus.OK: return "OK";
646 case HttpStatus.CREATED: return "Created";
647 case HttpStatus.ACCEPTED: return "Accepted";
648 case HttpStatus.NON_AUTHORITATIVE_INFORMATION:
649 return "Non-Authoritative Information";
650 case HttpStatus.NO_CONTENT: return "No Content";
651 case HttpStatus.RESET_CONTENT: return "Reset Content";
652 case HttpStatus.PARTIAL_CONTENT: return "Partial Content";
653 case HttpStatus.MULTIPLE_CHOICES: return "Multiple Choices";
654 case HttpStatus.MOVED_PERMANENTLY: return "Moved Permanently";
655 case HttpStatus.FOUND: return "Found";
656 case HttpStatus.SEE_OTHER: return "See Other";
657 case HttpStatus.NOT_MODIFIED: return "Not Modified";
658 case HttpStatus.USE_PROXY: return "Use Proxy";
659 case HttpStatus.TEMPORARY_REDIRECT: return "Temporary Redirect";
660 case HttpStatus.BAD_REQUEST: return "Bad Request";
661 case HttpStatus.UNAUTHORIZED: return "Unauthorized";
662 case HttpStatus.PAYMENT_REQUIRED: return "Payment Required";
663 case HttpStatus.FORBIDDEN: return "Forbidden";
664 case HttpStatus.NOT_FOUND: return "Not Found";
665 case HttpStatus.METHOD_NOT_ALLOWED: return "Method Not Allowed";
666 case HttpStatus.NOT_ACCEPTABLE: return "Not Acceptable";
667 case HttpStatus.PROXY_AUTHENTICATION_REQUIRED:
668 return "Proxy Authentication Required";
669 case HttpStatus.REQUEST_TIMEOUT: return "Request Time-out";
670 case HttpStatus.CONFLICT: return "Conflict";
671 case HttpStatus.GONE: return "Gone";
672 case HttpStatus.LENGTH_REQUIRED: return "Length Required";
673 case HttpStatus.PRECONDITION_FAILED: return "Precondition Failed";
674 case HttpStatus.REQUEST_ENTITY_TOO_LARGE:
675 return "Request Entity Too Large";
676 case HttpStatus.REQUEST_URI_TOO_LONG: return "Request-URI Too Large";
677 case HttpStatus.UNSUPPORTED_MEDIA_TYPE: return "Unsupported Media Type";
678 case HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE:
679 return "Requested range not satisfiable";
680 case HttpStatus.EXPECTATION_FAILED: return "Expectation Failed";
681 case HttpStatus.INTERNAL_SERVER_ERROR: return "Internal Server Error";
682 case HttpStatus.NOT_IMPLEMENTED: return "Not Implemented";
683 case HttpStatus.BAD_GATEWAY: return "Bad Gateway";
684 case HttpStatus.SERVICE_UNAVAILABLE: return "Service Unavailable";
685 case HttpStatus.GATEWAY_TIMEOUT: return "Gateway Time-out";
686 case HttpStatus.HTTP_VERSION_NOT_SUPPORTED:
687 return "Http Version not supported";
688 default: return "Status $statusCode";
689 }
690 }
691 }
692
693
694 class _HttpClientRequest extends _HttpOutboundMessage<HttpClientResponse>
695 implements HttpClientRequest {
696 final String method;
697 final Uri uri;
698 final List<Cookie> cookies = new List<Cookie>();
699
700 // The HttpClient this request belongs to.
701 final _HttpClient _httpClient;
702 final _HttpClientConnection _httpClientConnection;
703
704 final Completer<HttpClientResponse> _responseCompleter
705 = new Completer<HttpClientResponse>();
706
707 final _Proxy _proxy;
708
709 Future<HttpClientResponse> _response;
710
711 // TODO(ajohnsen): Get default value from client?
712 bool _followRedirects = true;
713
714 int _maxRedirects = 5;
715
716 List<RedirectInfo> _responseRedirects = [];
717
718 _HttpClientRequest(_HttpOutgoing outgoing, Uri uri, this.method, this._proxy,
719 this._httpClient, this._httpClientConnection)
720 : uri = uri,
721 super(uri, "1.1", outgoing) {
722 // GET and HEAD have 'content-length: 0' by default.
723 if (method == "GET" || method == "HEAD") {
724 contentLength = 0;
725 } else {
726 headers.chunkedTransferEncoding = true;
727 }
728 }
729
730 Future<HttpClientResponse> get done {
731 if (_response == null) {
732 _response = Future.wait([_responseCompleter.future, super.done],
733 eagerError: true)
734 .then((list) => list[0]);
735 }
736 return _response;
737 }
738
739 Future<HttpClientResponse> close() {
740 super.close();
741 return done;
742 }
743
744 int get maxRedirects => _maxRedirects;
745 void set maxRedirects(int maxRedirects) {
746 if (_outgoing.headersWritten) throw new StateError("Request already sent");
747 _maxRedirects = maxRedirects;
748 }
749
750 bool get followRedirects => _followRedirects;
751 void set followRedirects(bool followRedirects) {
752 if (_outgoing.headersWritten) throw new StateError("Request already sent");
753 _followRedirects = followRedirects;
754 }
755
756 HttpConnectionInfo get connectionInfo => _httpClientConnection.connectionInfo;
757
758 void _onIncoming(_HttpIncoming incoming) {
759 var response = new _HttpClientResponse(incoming, this, _httpClient);
760 Future<HttpClientResponse> future;
761 if (followRedirects && response.isRedirect) {
762 if (response.redirects.length < maxRedirects) {
763 // Redirect and drain response.
764 future = response.drain().then((_) => response.redirect());
765 } else {
766 // End with exception, too many redirects.
767 future = response.drain()
768 .then((_) => new Future.error(
769 new RedirectException("Redirect limit exceeded",
770 response.redirects)));
771 }
772 } else if (response._shouldAuthenticateProxy) {
773 future = response._authenticate(true);
774 } else if (response._shouldAuthenticate) {
775 future = response._authenticate(false);
776 } else {
777 future = new Future<HttpClientResponse>.value(response);
778 }
779 future.then(
780 (v) => _responseCompleter.complete(v),
781 onError: _responseCompleter.completeError);
782 }
783
784 void _onError(error, StackTrace stackTrace) {
785 _responseCompleter.completeError(error, stackTrace);
786 }
787
788 // Generate the request URI based on the method and proxy.
789 String _requestUri() {
790 // Generate the request URI starting from the path component.
791 String uriStartingFromPath() {
792 String result = uri.path;
793 if (result.isEmpty) result = "/";
794 if (uri.hasQuery) {
795 result = "${result}?${uri.query}";
796 }
797 return result;
798 }
799
800 if (_proxy.isDirect) {
801 return uriStartingFromPath();
802 } else {
803 if (method == "CONNECT") {
804 // For the connect method the request URI is the host:port of
805 // the requested destination of the tunnel (see RFC 2817
806 // section 5.2)
807 return "${uri.host}:${uri.port}";
808 } else {
809 if (_httpClientConnection._proxyTunnel) {
810 return uriStartingFromPath();
811 } else {
812 return uri.removeFragment().toString();
813 }
814 }
815 }
816 }
817
818 void _writeHeader() {
819 Uint8List buffer = new Uint8List(_OUTGOING_BUFFER_SIZE);
820 int offset = 0;
821
822 void write(List<int> bytes) {
823 int len = bytes.length;
824 for (int i = 0; i < len; i++) {
825 buffer[offset + i] = bytes[i];
826 }
827 offset += len;
828 }
829
830 // Write the request method.
831 write(method.codeUnits);
832 buffer[offset++] = _CharCode.SP;
833 // Write the request URI.
834 write(_requestUri().codeUnits);
835 buffer[offset++] = _CharCode.SP;
836 // Write HTTP/1.1.
837 write(_Const.HTTP11);
838 buffer[offset++] = _CharCode.CR;
839 buffer[offset++] = _CharCode.LF;
840
841 // Add the cookies to the headers.
842 if (!cookies.isEmpty) {
843 StringBuffer sb = new StringBuffer();
844 for (int i = 0; i < cookies.length; i++) {
845 if (i > 0) sb.write("; ");
846 sb..write(cookies[i].name)..write("=")..write(cookies[i].value);
847 }
848 headers.add(HttpHeaders.COOKIE, sb.toString());
849 }
850
851 headers._finalize();
852
853 // Write headers.
854 offset = headers._write(buffer, offset);
855 buffer[offset++] = _CharCode.CR;
856 buffer[offset++] = _CharCode.LF;
857 _outgoing.setHeader(buffer, offset);
858 }
859 }
860
861 // Used by _HttpOutgoing as a target of a chunked converter for gzip
862 // compression.
863 class _HttpGZipSink extends ByteConversionSink {
864 final Function _consume;
865 _HttpGZipSink(this._consume);
866
867 void add(List<int> chunk) {
868 _consume(chunk);
869 }
870
871 void addSlice(List<int> chunk, int start, int end, bool isLast) {
872 if (chunk is Uint8List) {
873 _consume(new Uint8List.view(chunk.buffer, start, end - start));
874 } else {
875 _consume(chunk.sublist(start, end - start));
876 }
877 }
878
879 void close() {}
880 }
881
882
883 // The _HttpOutgoing handles all of the following:
884 // - Buffering
885 // - GZip compressionm
886 // - Content-Length validation.
887 // - Errors.
888 //
889 // Most notable is the GZip compression, that uses a double-buffering system,
890 // one before gzip (_gzipBuffer) and one after (_buffer).
891 class _HttpOutgoing implements StreamConsumer<List<int>> {
892 static const List<int> _footerAndChunk0Length =
893 const [_CharCode.CR, _CharCode.LF, 0x30, _CharCode.CR, _CharCode.LF,
894 _CharCode.CR, _CharCode.LF];
895
896 static const List<int> _chunk0Length =
897 const [0x30, _CharCode.CR, _CharCode.LF, _CharCode.CR, _CharCode.LF];
898
899 final Completer _doneCompleter = new Completer();
900 final Socket socket;
901
902 bool ignoreBody = false;
903 bool headersWritten = false;
904
905 Uint8List _buffer;
906 int _length = 0;
907
908 Future _closeFuture;
909
910 bool chunked = false;
911 int _pendingChunkedFooter = 0;
912
913 int contentLength;
914 int _bytesWritten = 0;
915
916 bool _gzip = false;
917 ByteConversionSink _gzipSink;
918 // _gzipAdd is set iff the sink is being added to. It's used to specify where
919 // gzipped data should be taken (sometimes a controller, sometimes a socket).
920 Function _gzipAdd;
921 Uint8List _gzipBuffer;
922 int _gzipBufferLength = 0;
923
924 bool _socketError = false;
925
926 _HttpOutboundMessage outbound;
927
928 _HttpOutgoing(this.socket);
929
930 // Returns either a future or 'null', if it was able to write headers
931 // immediately.
932 Future writeHeaders({bool drainRequest: true, bool setOutgoing: true}) {
933 Future write() {
934 try {
935 outbound._writeHeader();
936 } catch (_) {
937 // Headers too large.
938 return new Future.error(new HttpException(
939 "Headers size exceeded the of '$_OUTGOING_BUFFER_SIZE'"
940 " bytes"));
941 }
942 return null;
943 }
944
945 if (headersWritten) return null;
946 headersWritten = true;
947 Future drainFuture;
948 bool gzip = false;
949 if (outbound is _HttpResponse) {
950 // Server side.
951 _HttpResponse response = outbound;
952 if (response._httpRequest._httpServer.autoCompress &&
953 outbound.bufferOutput &&
954 outbound.headers.chunkedTransferEncoding) {
955 List acceptEncodings =
956 response._httpRequest.headers[HttpHeaders.ACCEPT_ENCODING];
957 List contentEncoding = outbound.headers[HttpHeaders.CONTENT_ENCODING];
958 if (acceptEncodings != null &&
959 acceptEncodings
960 .expand((list) => list.split(","))
961 .any((encoding) => encoding.trim().toLowerCase() == "gzip") &&
962 contentEncoding == null) {
963 outbound.headers.set(HttpHeaders.CONTENT_ENCODING, "gzip");
964 gzip = true;
965 }
966 }
967 if (drainRequest && !response._httpRequest._incoming.hasSubscriber) {
968 drainFuture = response._httpRequest.drain().catchError((_) {});
969 }
970 } else {
971 drainRequest = false;
972 }
973 if (ignoreBody) {
974 return write();
975 }
976 if (setOutgoing) {
977 int contentLength = outbound.headers.contentLength;
978 if (outbound.headers.chunkedTransferEncoding) {
979 chunked = true;
980 if (gzip) this.gzip = true;
981 } else if (contentLength >= 0) {
982 this.contentLength = contentLength;
983 }
984 }
985 if (drainFuture != null) {
986 return drainFuture.then((_) => write());
987 }
988 return write();
989 }
990
991
992 Future addStream(Stream<List<int>> stream) {
993 if (_socketError) {
994 stream.listen(null).cancel();
995 return new Future.value(outbound);
996 }
997 if (ignoreBody) {
998 stream.drain().catchError((_) {});
999 var future = writeHeaders();
1000 if (future != null) {
1001 return future.then((_) => close());
1002 }
1003 return close();
1004 }
1005 var sub;
1006 // Use new stream so we are able to pause (see below listen). The
1007 // alternative is to use stream.extand, but that won't give us a way of
1008 // pausing.
1009 var controller = new StreamController(
1010 onPause: () => sub.pause(),
1011 onResume: () => sub.resume(),
1012 sync: true);
1013
1014 void onData(data) {
1015 if (_socketError) return;
1016 if (data.length == 0) return;
1017 if (chunked) {
1018 if (_gzip) {
1019 _gzipAdd = controller.add;
1020 _addGZipChunk(data, _gzipSink.add);
1021 _gzipAdd = null;
1022 return;
1023 }
1024 _addChunk(_chunkHeader(data.length), controller.add);
1025 _pendingChunkedFooter = 2;
1026 } else {
1027 if (contentLength != null) {
1028 _bytesWritten += data.length;
1029 if (_bytesWritten > contentLength) {
1030 controller.addError(new HttpException(
1031 "Content size exceeds specified contentLength. "
1032 "$_bytesWritten bytes written while expected "
1033 "$contentLength. "
1034 "[${new String.fromCharCodes(data)}]"));
1035 return;
1036 }
1037 }
1038 }
1039 _addChunk(data, controller.add);
1040 }
1041
1042 sub = stream.listen(
1043 onData,
1044 onError: controller.addError,
1045 onDone: controller.close,
1046 cancelOnError: true);
1047 // Write headers now that we are listening to the stream.
1048 if (!headersWritten) {
1049 var future = writeHeaders();
1050 if (future != null) {
1051 // While incoming is being drained, the pauseFuture is non-null. Pause
1052 // output until it's drained.
1053 sub.pause(future);
1054 }
1055 }
1056 return socket.addStream(controller.stream)
1057 .then((_) {
1058 return outbound;
1059 }, onError: (error, stackTrace) {
1060 // Be sure to close it in case of an error.
1061 if (_gzip) _gzipSink.close();
1062 _socketError = true;
1063 _doneCompleter.completeError(error, stackTrace);
1064 if (_ignoreError(error)) {
1065 return outbound;
1066 } else {
1067 throw error;
1068 }
1069 });
1070 }
1071
1072 Future close() {
1073 // If we are already closed, return that future.
1074 if (_closeFuture != null) return _closeFuture;
1075 // If we earlier saw an error, return immediate. The notification to
1076 // _Http*Connection is already done.
1077 if (_socketError) return new Future.value(outbound);
1078 if (outbound._isConnectionClosed) return new Future.value(outbound);
1079 if (!headersWritten && !ignoreBody) {
1080 if (outbound.headers.contentLength == -1) {
1081 // If no body was written, ignoreBody is false (it's not a HEAD
1082 // request) and the content-length is unspecified, set contentLength to
1083 // 0.
1084 outbound.headers.chunkedTransferEncoding = false;
1085 outbound.headers.contentLength = 0;
1086 } else if (outbound.headers.contentLength > 0) {
1087 var error = new HttpException(
1088 "No content even though contentLength was specified to be "
1089 "greater than 0: ${outbound.headers.contentLength}.",
1090 uri: outbound._uri);
1091 _doneCompleter.completeError(error);
1092 return _closeFuture = new Future.error(error);
1093 }
1094 }
1095 // If contentLength was specified, validate it.
1096 if (contentLength != null) {
1097 if (_bytesWritten < contentLength) {
1098 var error = new HttpException(
1099 "Content size below specified contentLength. "
1100 " $_bytesWritten bytes written but expected "
1101 "$contentLength.",
1102 uri: outbound._uri);
1103 _doneCompleter.completeError(error);
1104 return _closeFuture = new Future.error(error);
1105 }
1106 }
1107
1108 Future finalize() {
1109 // In case of chunked encoding (and gzip), handle remaining gzip data and
1110 // append the 'footer' for chunked encoding.
1111 if (chunked) {
1112 if (_gzip) {
1113 _gzipAdd = socket.add;
1114 if (_gzipBufferLength > 0) {
1115 _gzipSink.add(new Uint8List.view(
1116 _gzipBuffer.buffer, 0, _gzipBufferLength));
1117 }
1118 _gzipBuffer = null;
1119 _gzipSink.close();
1120 _gzipAdd = null;
1121 }
1122 _addChunk(_chunkHeader(0), socket.add);
1123 }
1124 // Add any remaining data in the buffer.
1125 if (_length > 0) {
1126 socket.add(new Uint8List.view(_buffer.buffer, 0, _length));
1127 }
1128 // Clear references, for better GC.
1129 _buffer = null;
1130 // And finally flush it. As we support keep-alive, never close it from
1131 // here. Once the socket is flushed, we'll be able to reuse it (signaled
1132 // by the 'done' future).
1133 return socket.flush()
1134 .then((_) {
1135 _doneCompleter.complete(socket);
1136 return outbound;
1137 }, onError: (error, stackTrace) {
1138 _doneCompleter.completeError(error, stackTrace);
1139 if (_ignoreError(error)) {
1140 return outbound;
1141 } else {
1142 throw error;
1143 }
1144 });
1145 }
1146
1147 var future = writeHeaders();
1148 if (future != null) {
1149 return _closeFuture = future.whenComplete(finalize);
1150 }
1151 return _closeFuture = finalize();
1152 }
1153
1154 Future get done => _doneCompleter.future;
1155
1156 void setHeader(List<int> data, int length) {
1157 assert(_length == 0);
1158 assert(data.length == _OUTGOING_BUFFER_SIZE);
1159 _buffer = data;
1160 _length = length;
1161 }
1162
1163 void set gzip(bool value) {
1164 _gzip = value;
1165 if (_gzip) {
1166 _gzipBuffer = new Uint8List(_OUTGOING_BUFFER_SIZE);
1167 assert(_gzipSink == null);
1168 _gzipSink = new ZLibEncoder(gzip: true)
1169 .startChunkedConversion(
1170 new _HttpGZipSink((data) {
1171 // We are closing down prematurely, due to an error. Discard.
1172 if (_gzipAdd == null) return;
1173 _addChunk(_chunkHeader(data.length), _gzipAdd);
1174 _pendingChunkedFooter = 2;
1175 _addChunk(data, _gzipAdd);
1176 }));
1177 }
1178 }
1179
1180 bool _ignoreError(error)
1181 => (error is SocketException || error is TlsException) &&
1182 outbound is HttpResponse;
1183
1184 void _addGZipChunk(chunk, void add(List<int> data)) {
1185 if (!outbound.bufferOutput) {
1186 add(chunk);
1187 return;
1188 }
1189 if (chunk.length > _gzipBuffer.length - _gzipBufferLength) {
1190 add(new Uint8List.view(
1191 _gzipBuffer.buffer, 0, _gzipBufferLength));
1192 _gzipBuffer = new Uint8List(_OUTGOING_BUFFER_SIZE);
1193 _gzipBufferLength = 0;
1194 }
1195 if (chunk.length > _OUTGOING_BUFFER_SIZE) {
1196 add(chunk);
1197 } else {
1198 _gzipBuffer.setRange(_gzipBufferLength,
1199 _gzipBufferLength + chunk.length,
1200 chunk);
1201 _gzipBufferLength += chunk.length;
1202 }
1203 }
1204
1205 void _addChunk(chunk, void add(List<int> data)) {
1206 if (!outbound.bufferOutput) {
1207 if (_buffer != null) {
1208 // If _buffer is not null, we have not written the header yet. Write
1209 // it now.
1210 add(new Uint8List.view(_buffer.buffer, 0, _length));
1211 _buffer = null;
1212 _length = 0;
1213 }
1214 add(chunk);
1215 return;
1216 }
1217 if (chunk.length > _buffer.length - _length) {
1218 add(new Uint8List.view(_buffer.buffer, 0, _length));
1219 _buffer = new Uint8List(_OUTGOING_BUFFER_SIZE);
1220 _length = 0;
1221 }
1222 if (chunk.length > _OUTGOING_BUFFER_SIZE) {
1223 add(chunk);
1224 } else {
1225 _buffer.setRange(_length, _length + chunk.length, chunk);
1226 _length += chunk.length;
1227 }
1228 }
1229
1230 List<int> _chunkHeader(int length) {
1231 const hexDigits = const [0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
1232 0x38, 0x39, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46];
1233 if (length == 0) {
1234 if (_pendingChunkedFooter == 2) return _footerAndChunk0Length;
1235 return _chunk0Length;
1236 }
1237 int size = _pendingChunkedFooter;
1238 int len = length;
1239 // Compute a fast integer version of (log(length + 1) / log(16)).ceil().
1240 while (len > 0) {
1241 size++;
1242 len >>= 4;
1243 }
1244 var footerAndHeader = new Uint8List(size + 2);
1245 if (_pendingChunkedFooter == 2) {
1246 footerAndHeader[0] = _CharCode.CR;
1247 footerAndHeader[1] = _CharCode.LF;
1248 }
1249 int index = size;
1250 while (index > _pendingChunkedFooter) {
1251 footerAndHeader[--index] = hexDigits[length & 15];
1252 length = length >> 4;
1253 }
1254 footerAndHeader[size + 0] = _CharCode.CR;
1255 footerAndHeader[size + 1] = _CharCode.LF;
1256 return footerAndHeader;
1257 }
1258 }
1259
1260 class _HttpClientConnection {
1261 final String key;
1262 final Socket _socket;
1263 final bool _proxyTunnel;
1264 final SecurityContext _context;
1265 final _HttpParser _httpParser;
1266 StreamSubscription _subscription;
1267 final _HttpClient _httpClient;
1268 bool _dispose = false;
1269 Timer _idleTimer;
1270 bool closed = false;
1271 Uri _currentUri;
1272
1273 Completer<_HttpIncoming> _nextResponseCompleter;
1274 Future _streamFuture;
1275
1276 _HttpClientConnection(this.key, this._socket, this._httpClient,
1277 [this._proxyTunnel = false, this._context])
1278 : _httpParser = new _HttpParser.responseParser() {
1279 _httpParser.listenToStream(_socket);
1280
1281 // Set up handlers on the parser here, so we are sure to get 'onDone' from
1282 // the parser.
1283 _subscription = _httpParser.listen(
1284 (incoming) {
1285 // Only handle one incoming response at the time. Keep the
1286 // stream paused until the response have been processed.
1287 _subscription.pause();
1288 // We assume the response is not here, until we have send the request.
1289 if (_nextResponseCompleter == null) {
1290 throw new HttpException(
1291 "Unexpected response (unsolicited response without request).",
1292 uri: _currentUri);
1293 }
1294
1295 // Check for status code '100 Continue'. In that case just
1296 // consume that response as the final response will follow
1297 // it. There is currently no API for the client to wait for
1298 // the '100 Continue' response.
1299 if (incoming.statusCode == 100) {
1300 incoming.drain().then((_) {
1301 _subscription.resume();
1302 }).catchError((error, [StackTrace stackTrace]) {
1303 _nextResponseCompleter.completeError(
1304 new HttpException(error.message, uri: _currentUri),
1305 stackTrace);
1306 _nextResponseCompleter = null;
1307 });
1308 } else {
1309 _nextResponseCompleter.complete(incoming);
1310 _nextResponseCompleter = null;
1311 }
1312 },
1313 onError: (error, [StackTrace stackTrace]) {
1314 if (_nextResponseCompleter != null) {
1315 _nextResponseCompleter.completeError(
1316 new HttpException(error.message, uri: _currentUri),
1317 stackTrace);
1318 _nextResponseCompleter = null;
1319 }
1320 },
1321 onDone: () {
1322 if (_nextResponseCompleter != null) {
1323 _nextResponseCompleter.completeError(new HttpException(
1324 "Connection closed before response was received",
1325 uri: _currentUri));
1326 _nextResponseCompleter = null;
1327 }
1328 close();
1329 });
1330 }
1331
1332 _HttpClientRequest send(Uri uri, int port, String method, _Proxy proxy) {
1333 if (closed) {
1334 throw new HttpException(
1335 "Socket closed before request was sent", uri: uri);
1336 }
1337 _currentUri = uri;
1338 // Start with pausing the parser.
1339 _subscription.pause();
1340 _ProxyCredentials proxyCreds; // Credentials used to authorize proxy.
1341 _SiteCredentials creds; // Credentials used to authorize this request.
1342 var outgoing = new _HttpOutgoing(_socket);
1343 // Create new request object, wrapping the outgoing connection.
1344 var request = new _HttpClientRequest(outgoing,
1345 uri,
1346 method,
1347 proxy,
1348 _httpClient,
1349 this);
1350 // For the Host header an IPv6 address must be enclosed in []'s.
1351 var host = uri.host;
1352 if (host.contains(':')) host = "[$host]";
1353 request.headers
1354 ..host = host
1355 ..port = port
1356 .._add(HttpHeaders.ACCEPT_ENCODING, "gzip");
1357 if (_httpClient.userAgent != null) {
1358 request.headers._add('user-agent', _httpClient.userAgent);
1359 }
1360 if (proxy.isAuthenticated) {
1361 // If the proxy configuration contains user information use that
1362 // for proxy basic authorization.
1363 String auth = _CryptoUtils.bytesToBase64(
1364 UTF8.encode("${proxy.username}:${proxy.password}"));
1365 request.headers.set(HttpHeaders.PROXY_AUTHORIZATION, "Basic $auth");
1366 } else if (!proxy.isDirect && _httpClient._proxyCredentials.length > 0) {
1367 proxyCreds = _httpClient._findProxyCredentials(proxy);
1368 if (proxyCreds != null) {
1369 proxyCreds.authorize(request);
1370 }
1371 }
1372 if (uri.userInfo != null && !uri.userInfo.isEmpty) {
1373 // If the URL contains user information use that for basic
1374 // authorization.
1375 String auth =
1376 _CryptoUtils.bytesToBase64(UTF8.encode(uri.userInfo));
1377 request.headers.set(HttpHeaders.AUTHORIZATION, "Basic $auth");
1378 } else {
1379 // Look for credentials.
1380 creds = _httpClient._findCredentials(uri);
1381 if (creds != null) {
1382 creds.authorize(request);
1383 }
1384 }
1385 // Start sending the request (lazy, delayed until the user provides
1386 // data).
1387 _httpParser.isHead = method == "HEAD";
1388 _streamFuture = outgoing.done
1389 .then((s) {
1390 // Request sent, set up response completer.
1391 _nextResponseCompleter = new Completer();
1392
1393 // Listen for response.
1394 _nextResponseCompleter.future
1395 .then((incoming) {
1396 _currentUri = null;
1397 incoming.dataDone.then((closing) {
1398 if (incoming.upgraded) {
1399 _httpClient._connectionClosed(this);
1400 startTimer();
1401 return;
1402 }
1403 if (closed) return;
1404 if (!closing &&
1405 !_dispose &&
1406 incoming.headers.persistentConnection &&
1407 request.persistentConnection) {
1408 // Return connection, now we are done.
1409 _httpClient._returnConnection(this);
1410 _subscription.resume();
1411 } else {
1412 destroy();
1413 }
1414 });
1415 // For digest authentication if proxy check if the proxy
1416 // requests the client to start using a new nonce for proxy
1417 // authentication.
1418 if (proxyCreds != null &&
1419 proxyCreds.scheme == _AuthenticationScheme.DIGEST) {
1420 var authInfo = incoming.headers["proxy-authentication-info"];
1421 if (authInfo != null && authInfo.length == 1) {
1422 var header =
1423 _HeaderValue.parse(
1424 authInfo[0], parameterSeparator: ',');
1425 var nextnonce = header.parameters["nextnonce"];
1426 if (nextnonce != null) proxyCreds.nonce = nextnonce;
1427 }
1428 }
1429 // For digest authentication check if the server requests the
1430 // client to start using a new nonce.
1431 if (creds != null &&
1432 creds.scheme == _AuthenticationScheme.DIGEST) {
1433 var authInfo = incoming.headers["authentication-info"];
1434 if (authInfo != null && authInfo.length == 1) {
1435 var header =
1436 _HeaderValue.parse(
1437 authInfo[0], parameterSeparator: ',');
1438 var nextnonce = header.parameters["nextnonce"];
1439 if (nextnonce != null) creds.nonce = nextnonce;
1440 }
1441 }
1442 request._onIncoming(incoming);
1443 })
1444 // If we see a state error, we failed to get the 'first'
1445 // element.
1446 .catchError((error) {
1447 throw new HttpException(
1448 "Connection closed before data was received", uri: uri);
1449 }, test: (error) => error is StateError)
1450 .catchError((error, stackTrace) {
1451 // We are done with the socket.
1452 destroy();
1453 request._onError(error, stackTrace);
1454 });
1455
1456 // Resume the parser now we have a handler.
1457 _subscription.resume();
1458 return s;
1459 }, onError: (e) {
1460 destroy();
1461 });
1462 return request;
1463 }
1464
1465 Future<Socket> detachSocket() {
1466 return _streamFuture.then(
1467 (_) => new _DetachedSocket(_socket, _httpParser.detachIncoming()));
1468 }
1469
1470 void destroy() {
1471 closed = true;
1472 _httpClient._connectionClosed(this);
1473 _socket.destroy();
1474 }
1475
1476 void close() {
1477 closed = true;
1478 _httpClient._connectionClosed(this);
1479 _streamFuture
1480 // TODO(ajohnsen): Add timeout.
1481 .then((_) => _socket.destroy());
1482 }
1483
1484 Future<_HttpClientConnection> createProxyTunnel(host, port, proxy, callback) {
1485 _HttpClientRequest request =
1486 send(new Uri(host: host, port: port),
1487 port,
1488 "CONNECT",
1489 proxy);
1490 if (proxy.isAuthenticated) {
1491 // If the proxy configuration contains user information use that
1492 // for proxy basic authorization.
1493 String auth = _CryptoUtils.bytesToBase64(
1494 UTF8.encode("${proxy.username}:${proxy.password}"));
1495 request.headers.set(HttpHeaders.PROXY_AUTHORIZATION, "Basic $auth");
1496 }
1497 return request.close()
1498 .then((response) {
1499 if (response.statusCode != HttpStatus.OK) {
1500 throw "Proxy failed to establish tunnel "
1501 "(${response.statusCode} ${response.reasonPhrase})";
1502 }
1503 var socket = (response as _HttpClientResponse)._httpRequest
1504 ._httpClientConnection._socket;
1505 return SecureSocket.secure(
1506 socket,
1507 host: host,
1508 context: _context,
1509 onBadCertificate: callback);
1510 })
1511 .then((secureSocket) {
1512 String key = _HttpClientConnection.makeKey(true, host, port);
1513 return new _HttpClientConnection(
1514 key, secureSocket, request._httpClient, true);
1515 });
1516 }
1517
1518 HttpConnectionInfo get connectionInfo => _HttpConnectionInfo.create(_socket);
1519
1520 static makeKey(bool isSecure, String host, int port) {
1521 return isSecure ? "ssh:$host:$port" : "$host:$port";
1522 }
1523
1524 void stopTimer() {
1525 if (_idleTimer != null) {
1526 _idleTimer.cancel();
1527 _idleTimer = null;
1528 }
1529 }
1530
1531 void startTimer() {
1532 assert(_idleTimer == null);
1533 _idleTimer = new Timer(
1534 _httpClient.idleTimeout,
1535 () {
1536 _idleTimer = null;
1537 close();
1538 });
1539 }
1540 }
1541
1542 class _ConnectionInfo {
1543 final _HttpClientConnection connection;
1544 final _Proxy proxy;
1545
1546 _ConnectionInfo(this.connection, this.proxy);
1547 }
1548
1549
1550 class _ConnectionTarget {
1551 // Unique key for this connection target.
1552 final String key;
1553 final String host;
1554 final int port;
1555 final bool isSecure;
1556 final SecurityContext context;
1557 final Set<_HttpClientConnection> _idle = new HashSet();
1558 final Set<_HttpClientConnection> _active = new HashSet();
1559 final Queue _pending = new ListQueue();
1560 int _connecting = 0;
1561
1562 _ConnectionTarget(this.key,
1563 this.host,
1564 this.port,
1565 this.isSecure,
1566 this.context);
1567
1568 bool get isEmpty => _idle.isEmpty && _active.isEmpty && _connecting == 0;
1569
1570 bool get hasIdle => _idle.isNotEmpty;
1571
1572 bool get hasActive => _active.isNotEmpty || _connecting > 0;
1573
1574 _HttpClientConnection takeIdle() {
1575 assert(hasIdle);
1576 _HttpClientConnection connection = _idle.first;
1577 _idle.remove(connection);
1578 connection.stopTimer();
1579 _active.add(connection);
1580 return connection;
1581 }
1582
1583 _checkPending() {
1584 if (_pending.isNotEmpty) {
1585 _pending.removeFirst()();
1586 }
1587 }
1588
1589 void addNewActive(_HttpClientConnection connection) {
1590 _active.add(connection);
1591 }
1592
1593 void returnConnection(_HttpClientConnection connection) {
1594 assert(_active.contains(connection));
1595 _active.remove(connection);
1596 _idle.add(connection);
1597 connection.startTimer();
1598 _checkPending();
1599 }
1600
1601 void connectionClosed(_HttpClientConnection connection) {
1602 assert(!_active.contains(connection) || !_idle.contains(connection));
1603 _active.remove(connection);
1604 _idle.remove(connection);
1605 _checkPending();
1606 }
1607
1608 void close(bool force) {
1609 for (var c in _idle.toList()) {
1610 c.close();
1611 }
1612 if (force) {
1613 for (var c in _active.toList()) {
1614 c.destroy();
1615 }
1616 }
1617 }
1618
1619 Future<_ConnectionInfo> connect(String uriHost,
1620 int uriPort,
1621 _Proxy proxy,
1622 _HttpClient client) {
1623 if (hasIdle) {
1624 var connection = takeIdle();
1625 client._connectionsChanged();
1626 return new Future.value(new _ConnectionInfo(connection, proxy));
1627 }
1628 if (client.maxConnectionsPerHost != null &&
1629 _active.length + _connecting >= client.maxConnectionsPerHost) {
1630 var completer = new Completer();
1631 _pending.add(() {
1632 connect(uriHost, uriPort, proxy, client)
1633 .then(completer.complete, onError: completer.completeError);
1634 });
1635 return completer.future;
1636 }
1637 var currentBadCertificateCallback = client._badCertificateCallback;
1638
1639 bool callback(X509Certificate certificate) {
1640 if (currentBadCertificateCallback == null) return false;
1641 return currentBadCertificateCallback(certificate, uriHost, uriPort);
1642 }
1643
1644 Future socketFuture = (isSecure && proxy.isDirect
1645 ? SecureSocket.connect(host,
1646 port,
1647 context: context,
1648 onBadCertificate: callback)
1649 : Socket.connect(host, port));
1650 _connecting++;
1651 return socketFuture.then((socket) {
1652 _connecting--;
1653 socket.setOption(SocketOption.TCP_NODELAY, true);
1654 var connection =
1655 new _HttpClientConnection(key, socket, client, false, context);
1656 if (isSecure && !proxy.isDirect) {
1657 connection._dispose = true;
1658 return connection.createProxyTunnel(uriHost, uriPort, proxy, callback)
1659 .then((tunnel) {
1660 client._getConnectionTarget(uriHost, uriPort, true)
1661 .addNewActive(tunnel);
1662 return new _ConnectionInfo(tunnel, proxy);
1663 });
1664 } else {
1665 addNewActive(connection);
1666 return new _ConnectionInfo(connection, proxy);
1667 }
1668 }, onError: (error) {
1669 _connecting--;
1670 _checkPending();
1671 throw error;
1672 });
1673 }
1674 }
1675
1676 typedef bool BadCertificateCallback(X509Certificate cr, String host, int port);
1677
1678 class _HttpClient implements HttpClient {
1679 bool _closing = false;
1680 bool _closingForcefully = false;
1681 final Map<String, _ConnectionTarget> _connectionTargets
1682 = new HashMap<String, _ConnectionTarget>();
1683 final List<_Credentials> _credentials = [];
1684 final List<_ProxyCredentials> _proxyCredentials = [];
1685 final SecurityContext _context;
1686 Function _authenticate;
1687 Function _authenticateProxy;
1688 Function _findProxy = HttpClient.findProxyFromEnvironment;
1689 Duration _idleTimeout = const Duration(seconds: 15);
1690 BadCertificateCallback _badCertificateCallback;
1691
1692 Duration get idleTimeout => _idleTimeout;
1693
1694 int maxConnectionsPerHost;
1695
1696 bool autoUncompress = true;
1697
1698 String userAgent = _getHttpVersion();
1699
1700 _HttpClient(SecurityContext this._context);
1701
1702 void set idleTimeout(Duration timeout) {
1703 _idleTimeout = timeout;
1704 for (var c in _connectionTargets.values) {
1705 for (var idle in c._idle) {
1706 // Reset timer. This is fine, as it's not happening often.
1707 idle.stopTimer();
1708 idle.startTimer();
1709 }
1710 }
1711 }
1712
1713 set badCertificateCallback(bool callback(X509Certificate cert,
1714 String host,
1715 int port)) {
1716 _badCertificateCallback = callback;
1717 }
1718
1719
1720 Future<HttpClientRequest> open(String method,
1721 String host,
1722 int port,
1723 String path) {
1724 const int hashMark = 0x23;
1725 const int questionMark = 0x3f;
1726 int fragmentStart = path.length;
1727 int queryStart = path.length;
1728 for (int i = path.length - 1; i >= 0; i--) {
1729 var char = path.codeUnitAt(i);
1730 if (char == hashMark) {
1731 fragmentStart = i;
1732 queryStart = i;
1733 } else if (char == questionMark) {
1734 queryStart = i;
1735 }
1736 }
1737 String query = null;
1738 if (queryStart < fragmentStart) {
1739 query = path.substring(queryStart + 1, fragmentStart);
1740 path = path.substring(0, queryStart);
1741 }
1742 Uri uri = new Uri(scheme: "http", host: host, port: port,
1743 path: path, query: query);
1744 return _openUrl(method, uri);
1745 }
1746
1747 Future<HttpClientRequest> openUrl(String method, Uri url)
1748 => _openUrl(method, url);
1749
1750 Future<HttpClientRequest> get(String host, int port, String path)
1751 => open("get", host, port, path);
1752
1753 Future<HttpClientRequest> getUrl(Uri url) => _openUrl("get", url);
1754
1755 Future<HttpClientRequest> post(String host, int port, String path)
1756 => open("post", host, port, path);
1757
1758 Future<HttpClientRequest> postUrl(Uri url) => _openUrl("post", url);
1759
1760 Future<HttpClientRequest> put(String host, int port, String path)
1761 => open("put", host, port, path);
1762
1763 Future<HttpClientRequest> putUrl(Uri url) => _openUrl("put", url);
1764
1765 Future<HttpClientRequest> delete(String host, int port, String path)
1766 => open("delete", host, port, path);
1767
1768 Future<HttpClientRequest> deleteUrl(Uri url) => _openUrl("delete", url);
1769
1770 Future<HttpClientRequest> head(String host, int port, String path)
1771 => open("head", host, port, path);
1772
1773 Future<HttpClientRequest> headUrl(Uri url) => _openUrl("head", url);
1774
1775 Future<HttpClientRequest> patch(String host, int port, String path)
1776 => open("patch", host, port, path);
1777
1778 Future<HttpClientRequest> patchUrl(Uri url) => _openUrl("patch", url);
1779
1780 void close({bool force: false}) {
1781 _closing = true;
1782 _closingForcefully = force;
1783 _closeConnections(_closingForcefully);
1784 assert(!_connectionTargets.values.any((s) => s.hasIdle));
1785 assert(!force ||
1786 !_connectionTargets.values.any((s) => s._active.isNotEmpty));
1787 }
1788
1789 set authenticate(Future<bool> f(Uri url, String scheme, String realm)) {
1790 _authenticate = f;
1791 }
1792
1793 void addCredentials(Uri url, String realm, HttpClientCredentials cr) {
1794 _credentials.add(new _SiteCredentials(url, realm, cr));
1795 }
1796
1797 set authenticateProxy(
1798 Future<bool> f(String host, int port, String scheme, String realm)) {
1799 _authenticateProxy = f;
1800 }
1801
1802 void addProxyCredentials(String host,
1803 int port,
1804 String realm,
1805 HttpClientCredentials cr) {
1806 _proxyCredentials.add(new _ProxyCredentials(host, port, realm, cr));
1807 }
1808
1809 set findProxy(String f(Uri uri)) => _findProxy = f;
1810
1811 Future<_HttpClientRequest> _openUrl(String method, Uri uri) {
1812 // Ignore any fragments on the request URI.
1813 uri = uri.removeFragment();
1814
1815 if (method == null) {
1816 throw new ArgumentError(method);
1817 }
1818 if (method != "CONNECT") {
1819 if (uri.host.isEmpty) {
1820 throw new ArgumentError("No host specified in URI $uri");
1821 } else if (uri.scheme != "http" && uri.scheme != "https") {
1822 throw new ArgumentError(
1823 "Unsupported scheme '${uri.scheme}' in URI $uri");
1824 }
1825 }
1826
1827 bool isSecure = (uri.scheme == "https");
1828 int port = uri.port;
1829 if (port == 0) {
1830 port = isSecure ?
1831 HttpClient.DEFAULT_HTTPS_PORT :
1832 HttpClient.DEFAULT_HTTP_PORT;
1833 }
1834 // Check to see if a proxy server should be used for this connection.
1835 var proxyConf = const _ProxyConfiguration.direct();
1836 if (_findProxy != null) {
1837 // TODO(sgjesse): Keep a map of these as normally only a few
1838 // configuration strings will be used.
1839 try {
1840 proxyConf = new _ProxyConfiguration(_findProxy(uri));
1841 } catch (error, stackTrace) {
1842 return new Future.error(error, stackTrace);
1843 }
1844 }
1845 return _getConnection(uri.host, port, proxyConf, isSecure)
1846 .then((_ConnectionInfo info) {
1847
1848 _HttpClientRequest send(_ConnectionInfo info) {
1849 return info.connection.send(uri,
1850 port,
1851 method.toUpperCase(),
1852 info.proxy);
1853 }
1854
1855 // If the connection was closed before the request was sent, create
1856 // and use another connection.
1857 if (info.connection.closed) {
1858 return _getConnection(uri.host, port, proxyConf, isSecure)
1859 .then(send);
1860 }
1861 return send(info);
1862 });
1863 }
1864
1865 Future<_HttpClientRequest> _openUrlFromRequest(String method,
1866 Uri uri,
1867 _HttpClientRequest previous) {
1868 // If the new URI is relative (to either '/' or some sub-path),
1869 // construct a full URI from the previous one.
1870 Uri resolved = previous.uri.resolveUri(uri);
1871 return _openUrl(method, resolved).then((_HttpClientRequest request) {
1872
1873 request
1874 // Only follow redirects if initial request did.
1875 ..followRedirects = previous.followRedirects
1876 // Allow same number of redirects.
1877 ..maxRedirects = previous.maxRedirects;
1878 // Copy headers.
1879 for (var header in previous.headers._headers.keys) {
1880 if (request.headers[header] == null) {
1881 request.headers.set(header, previous.headers[header]);
1882 }
1883 }
1884 return request
1885 ..headers.chunkedTransferEncoding = false
1886 ..contentLength = 0;
1887 });
1888 }
1889
1890 // Return a live connection to the idle pool.
1891 void _returnConnection(_HttpClientConnection connection) {
1892 _connectionTargets[connection.key].returnConnection(connection);
1893 _connectionsChanged();
1894 }
1895
1896 // Remove a closed connnection from the active set.
1897 void _connectionClosed(_HttpClientConnection connection) {
1898 connection.stopTimer();
1899 var connectionTarget = _connectionTargets[connection.key];
1900 if (connectionTarget != null) {
1901 connectionTarget.connectionClosed(connection);
1902 if (connectionTarget.isEmpty) {
1903 _connectionTargets.remove(connection.key);
1904 }
1905 _connectionsChanged();
1906 }
1907 }
1908
1909 void _connectionsChanged() {
1910 if (_closing) {
1911 _closeConnections(_closingForcefully);
1912 }
1913 }
1914
1915 void _closeConnections(bool force) {
1916 for (var connectionTarget in _connectionTargets.values.toList()) {
1917 connectionTarget.close(force);
1918 }
1919 }
1920
1921 _ConnectionTarget _getConnectionTarget(String host, int port, bool isSecure) {
1922 String key = _HttpClientConnection.makeKey(isSecure, host, port);
1923 return _connectionTargets.putIfAbsent(key, () {
1924 return new _ConnectionTarget(key, host, port, isSecure, _context);
1925 });
1926 }
1927
1928 // Get a new _HttpClientConnection, from the matching _ConnectionTarget.
1929 Future<_ConnectionInfo> _getConnection(String uriHost,
1930 int uriPort,
1931 _ProxyConfiguration proxyConf,
1932 bool isSecure) {
1933 Iterator<_Proxy> proxies = proxyConf.proxies.iterator;
1934
1935 Future<_ConnectionInfo> connect(error) {
1936 if (!proxies.moveNext()) return new Future.error(error);
1937 _Proxy proxy = proxies.current;
1938 String host = proxy.isDirect ? uriHost: proxy.host;
1939 int port = proxy.isDirect ? uriPort: proxy.port;
1940 return _getConnectionTarget(host, port, isSecure)
1941 .connect(uriHost, uriPort, proxy, this)
1942 // On error, continue with next proxy.
1943 .catchError(connect);
1944 }
1945 // Make sure we go through the event loop before taking a
1946 // connection from the pool. For long-running synchronous code the
1947 // server might have closed the connection, so this lowers the
1948 // probability of getting a connection that was already closed.
1949 return new Future(() => connect(new HttpException("No proxies given")));
1950 }
1951
1952 _SiteCredentials _findCredentials(Uri url, [_AuthenticationScheme scheme]) {
1953 // Look for credentials.
1954 _SiteCredentials cr =
1955 _credentials.fold(null, (_SiteCredentials prev, value) {
1956 var siteCredentials = value as _SiteCredentials;
1957 if (siteCredentials.applies(url, scheme)) {
1958 if (prev == null) return value;
1959 return siteCredentials.uri.path.length > prev.uri.path.length
1960 ? siteCredentials
1961 : prev;
1962 } else {
1963 return prev;
1964 }
1965 });
1966 return cr;
1967 }
1968
1969 _ProxyCredentials _findProxyCredentials(_Proxy proxy,
1970 [_AuthenticationScheme scheme]) {
1971 // Look for credentials.
1972 var it = _proxyCredentials.iterator;
1973 while (it.moveNext()) {
1974 if (it.current.applies(proxy, scheme)) {
1975 return it.current;
1976 }
1977 }
1978 return null;
1979 }
1980
1981 void _removeCredentials(_Credentials cr) {
1982 int index = _credentials.indexOf(cr);
1983 if (index != -1) {
1984 _credentials.removeAt(index);
1985 }
1986 }
1987
1988 void _removeProxyCredentials(_Credentials cr) {
1989 int index = _proxyCredentials.indexOf(cr);
1990 if (index != -1) {
1991 _proxyCredentials.removeAt(index);
1992 }
1993 }
1994
1995 static String _findProxyFromEnvironment(Uri url,
1996 Map<String, String> environment) {
1997 checkNoProxy(String option) {
1998 if (option == null) return null;
1999 Iterator<String> names = option.split(",").map((s) => s.trim()).iterator;
2000 while (names.moveNext()) {
2001 var name = names.current;
2002 if ((name.startsWith("[") &&
2003 name.endsWith("]") &&
2004 "[${url.host}]" == name) ||
2005 (name.isNotEmpty &&
2006 url.host.endsWith(name))) {
2007 return "DIRECT";
2008 }
2009 }
2010 return null;
2011 }
2012
2013 checkProxy(String option) {
2014 if (option == null) return null;
2015 option = option.trim();
2016 if (option.isEmpty) return null;
2017 int pos = option.indexOf("://");
2018 if (pos >= 0) {
2019 option = option.substring(pos + 3);
2020 }
2021 pos = option.indexOf("/");
2022 if (pos >= 0) {
2023 option = option.substring(0, pos);
2024 }
2025 // Add default port if no port configured.
2026 if (option.indexOf("[") == 0) {
2027 var pos = option.lastIndexOf(":");
2028 if (option.indexOf("]") > pos) option = "$option:1080";
2029 } else {
2030 if (option.indexOf(":") == -1) option = "$option:1080";
2031 }
2032 return "PROXY $option";
2033 }
2034
2035 // Default to using the process current environment.
2036 if (environment == null) environment = _platformEnvironmentCache;
2037
2038 String proxyCfg;
2039
2040 String noProxy = environment["no_proxy"];
2041 if (noProxy == null) noProxy = environment["NO_PROXY"];
2042 if ((proxyCfg = checkNoProxy(noProxy)) != null) {
2043 return proxyCfg;
2044 }
2045
2046 if (url.scheme == "http") {
2047 String proxy = environment["http_proxy"];
2048 if (proxy == null) proxy = environment["HTTP_PROXY"];
2049 if ((proxyCfg = checkProxy(proxy)) != null) {
2050 return proxyCfg;
2051 }
2052 } else if (url.scheme == "https") {
2053 String proxy = environment["https_proxy"];
2054 if (proxy == null) proxy = environment["HTTPS_PROXY"];
2055 if ((proxyCfg = checkProxy(proxy)) != null) {
2056 return proxyCfg;
2057 }
2058 }
2059 return "DIRECT";
2060 }
2061
2062 static Map<String, String> _platformEnvironmentCache = Platform.environment;
2063 }
2064
2065
2066 class _HttpConnection
2067 extends LinkedListEntry<_HttpConnection> with _ServiceObject {
2068 static const _ACTIVE = 0;
2069 static const _IDLE = 1;
2070 static const _CLOSING = 2;
2071 static const _DETACHED = 3;
2072
2073 // Use HashMap, as we don't need to keep order.
2074 static Map<int, _HttpConnection> _connections =
2075 new HashMap<int, _HttpConnection>();
2076
2077 final _socket;
2078 final _HttpServer _httpServer;
2079 final _HttpParser _httpParser;
2080 int _state = _IDLE;
2081 StreamSubscription _subscription;
2082 bool _idleMark = false;
2083 Future _streamFuture;
2084
2085 _HttpConnection(this._socket, this._httpServer)
2086 : _httpParser = new _HttpParser.requestParser() {
2087 try { _socket._owner = this; } catch (_) { print(_); }
2088 _connections[_serviceId] = this;
2089 _httpParser.listenToStream(_socket);
2090 _subscription = _httpParser.listen(
2091 (incoming) {
2092 _httpServer._markActive(this);
2093 // If the incoming was closed, close the connection.
2094 incoming.dataDone.then((closing) {
2095 if (closing) destroy();
2096 });
2097 // Only handle one incoming request at the time. Keep the
2098 // stream paused until the request has been send.
2099 _subscription.pause();
2100 _state = _ACTIVE;
2101 var outgoing = new _HttpOutgoing(_socket);
2102 var response = new _HttpResponse(incoming.uri,
2103 incoming.headers.protocolVersion,
2104 outgoing,
2105 _httpServer.defaultResponseHeaders,
2106 _httpServer.serverHeader);
2107 var request = new _HttpRequest(response, incoming, _httpServer, this);
2108 _streamFuture = outgoing.done
2109 .then((_) {
2110 response.deadline = null;
2111 if (_state == _DETACHED) return;
2112 if (response.persistentConnection &&
2113 request.persistentConnection &&
2114 incoming.fullBodyRead &&
2115 !_httpParser.upgrade &&
2116 !_httpServer.closed) {
2117 _state = _IDLE;
2118 _idleMark = false;
2119 _httpServer._markIdle(this);
2120 // Resume the subscription for incoming requests as the
2121 // request is now processed.
2122 _subscription.resume();
2123 } else {
2124 // Close socket, keep-alive not used or body sent before
2125 // received data was handled.
2126 destroy();
2127 }
2128 }, onError: (_) {
2129 destroy();
2130 });
2131 outgoing.ignoreBody = request.method == "HEAD";
2132 response._httpRequest = request;
2133 _httpServer._handleRequest(request);
2134 },
2135 onDone: () {
2136 destroy();
2137 },
2138 onError: (error) {
2139 // Ignore failed requests that was closed before headers was received.
2140 destroy();
2141 });
2142 }
2143
2144 void markIdle() {
2145 _idleMark = true;
2146 }
2147
2148 bool get isMarkedIdle => _idleMark;
2149
2150 void destroy() {
2151 if (_state == _CLOSING || _state == _DETACHED) return;
2152 _state = _CLOSING;
2153 _socket.destroy();
2154 _httpServer._connectionClosed(this);
2155 _connections.remove(_serviceId);
2156 }
2157
2158 Future<Socket> detachSocket() {
2159 _state = _DETACHED;
2160 // Remove connection from server.
2161 _httpServer._connectionClosed(this);
2162
2163 _HttpDetachedIncoming detachedIncoming = _httpParser.detachIncoming();
2164
2165 return _streamFuture.then((_) {
2166 _connections.remove(_serviceId);
2167 return new _DetachedSocket(_socket, detachedIncoming);
2168 });
2169 }
2170
2171 HttpConnectionInfo get connectionInfo => _HttpConnectionInfo.create(_socket);
2172
2173 bool get _isActive => _state == _ACTIVE;
2174 bool get _isIdle => _state == _IDLE;
2175 bool get _isClosing => _state == _CLOSING;
2176 bool get _isDetached => _state == _DETACHED;
2177
2178 String get _serviceTypePath => 'io/http/serverconnections';
2179 String get _serviceTypeName => 'HttpServerConnection';
2180
2181 Map _toJSON(bool ref) {
2182 var name = "${_socket.address.host}:${_socket.port} <-> "
2183 "${_socket.remoteAddress.host}:${_socket.remotePort}";
2184 var r = <String, dynamic>{
2185 'id': _servicePath,
2186 'type': _serviceType(ref),
2187 'name': name,
2188 'user_name': name,
2189 };
2190 if (ref) {
2191 return r;
2192 }
2193 r['server'] = _httpServer._toJSON(true);
2194 try {
2195 r['socket'] = _socket._toJSON(true);
2196 } catch (_) {
2197 r['socket'] = {
2198 'id': _servicePath,
2199 'type': '@Socket',
2200 'name': 'UserSocket',
2201 'user_name': 'UserSocket',
2202 };
2203 }
2204 switch (_state) {
2205 case _ACTIVE: r['state'] = "Active"; break;
2206 case _IDLE: r['state'] = "Idle"; break;
2207 case _CLOSING: r['state'] = "Closing"; break;
2208 case _DETACHED: r['state'] = "Detached"; break;
2209 default: r['state'] = 'Unknown'; break;
2210 }
2211 return r;
2212 }
2213 }
2214
2215
2216 // HTTP server waiting for socket connections.
2217 class _HttpServer
2218 extends Stream<HttpRequest> with _ServiceObject
2219 implements HttpServer {
2220 // Use default Map so we keep order.
2221 static Map<int, _HttpServer> _servers = new Map<int, _HttpServer>();
2222
2223 String serverHeader;
2224 final HttpHeaders defaultResponseHeaders = _initDefaultResponseHeaders();
2225 bool autoCompress = false;
2226
2227 Duration _idleTimeout;
2228 Timer _idleTimer;
2229
2230 static Future<HttpServer> bind(
2231 address, int port, int backlog, bool v6Only, bool shared) {
2232 return ServerSocket.bind(
2233 address, port, backlog: backlog, v6Only: v6Only, shared: shared)
2234 .then((socket) {
2235 return new _HttpServer._(socket, true);
2236 });
2237 }
2238
2239 static Future<HttpServer> bindSecure(address,
2240 int port,
2241 SecurityContext context,
2242 int backlog,
2243 bool v6Only,
2244 bool requestClientCertificate,
2245 bool shared) {
2246 return SecureServerSocket.bind(
2247 address,
2248 port,
2249 context,
2250 backlog: backlog,
2251 v6Only: v6Only,
2252 requestClientCertificate: requestClientCertificate,
2253 shared: shared)
2254 .then((socket) {
2255 return new _HttpServer._(socket, true);
2256 });
2257 }
2258
2259 _HttpServer._(this._serverSocket, this._closeServer) {
2260 _controller = new StreamController<HttpRequest>(sync: true,
2261 onCancel: close);
2262 idleTimeout = const Duration(seconds: 120);
2263 _servers[_serviceId] = this;
2264 _serverSocket._owner = this;
2265 }
2266
2267 _HttpServer.listenOn(this._serverSocket) : _closeServer = false {
2268 _controller = new StreamController<HttpRequest>(sync: true,
2269 onCancel: close);
2270 idleTimeout = const Duration(seconds: 120);
2271 _servers[_serviceId] = this;
2272 try { _serverSocket._owner = this; } catch (_) {}
2273 }
2274
2275 static HttpHeaders _initDefaultResponseHeaders() {
2276 var defaultResponseHeaders = new _HttpHeaders('1.1');
2277 defaultResponseHeaders.contentType = ContentType.TEXT;
2278 defaultResponseHeaders.set('X-Frame-Options', 'SAMEORIGIN');
2279 defaultResponseHeaders.set('X-Content-Type-Options', 'nosniff');
2280 defaultResponseHeaders.set('X-XSS-Protection', '1; mode=block');
2281 return defaultResponseHeaders;
2282 }
2283
2284 Duration get idleTimeout => _idleTimeout;
2285
2286 void set idleTimeout(Duration duration) {
2287 if (_idleTimer != null) {
2288 _idleTimer.cancel();
2289 _idleTimer = null;
2290 }
2291 _idleTimeout = duration;
2292 if (_idleTimeout != null) {
2293 _idleTimer = new Timer.periodic(_idleTimeout, (_) {
2294 for (var idle in _idleConnections.toList()) {
2295 if (idle.isMarkedIdle) {
2296 idle.destroy();
2297 } else {
2298 idle.markIdle();
2299 }
2300 }
2301 });
2302 }
2303 }
2304
2305 StreamSubscription<HttpRequest> listen(void onData(HttpRequest event),
2306 {Function onError,
2307 void onDone(),
2308 bool cancelOnError}) {
2309 _serverSocket.listen(
2310 (Socket socket) {
2311 socket.setOption(SocketOption.TCP_NODELAY, true);
2312 // Accept the client connection.
2313 _HttpConnection connection = new _HttpConnection(socket, this);
2314 _idleConnections.add(connection);
2315 },
2316 onError: (error, stackTrace) {
2317 // Ignore HandshakeExceptions as they are bound to a single request,
2318 // and are not fatal for the server.
2319 if (error is! HandshakeException) {
2320 _controller.addError(error, stackTrace);
2321 }
2322 },
2323 onDone: _controller.close);
2324 return _controller.stream.listen(onData,
2325 onError: onError,
2326 onDone: onDone,
2327 cancelOnError: cancelOnError);
2328 }
2329
2330 Future close({bool force: false}) {
2331 closed = true;
2332 Future result;
2333 if (_serverSocket != null && _closeServer) {
2334 result = _serverSocket.close();
2335 } else {
2336 result = new Future.value();
2337 }
2338 idleTimeout = null;
2339 if (force) {
2340 for (var c in _activeConnections.toList()) {
2341 c.destroy();
2342 }
2343 assert(_activeConnections.isEmpty);
2344 }
2345 for (var c in _idleConnections.toList()) {
2346 c.destroy();
2347 }
2348 _maybePerformCleanup();
2349 return result;
2350 }
2351
2352 void _maybePerformCleanup() {
2353 if (closed &&
2354 _idleConnections.isEmpty &&
2355 _activeConnections.isEmpty &&
2356 _sessionManagerInstance != null) {
2357 _sessionManagerInstance.close();
2358 _sessionManagerInstance = null;
2359 _servers.remove(_serviceId);
2360 }
2361 }
2362
2363 int get port {
2364 if (closed) throw new HttpException("HttpServer is not bound to a socket");
2365 return _serverSocket.port;
2366 }
2367
2368 InternetAddress get address {
2369 if (closed) throw new HttpException("HttpServer is not bound to a socket");
2370 return _serverSocket.address;
2371 }
2372
2373 set sessionTimeout(int timeout) {
2374 _sessionManager.sessionTimeout = timeout;
2375 }
2376
2377 void _handleRequest(_HttpRequest request) {
2378 if (!closed) {
2379 _controller.add(request);
2380 } else {
2381 request._httpConnection.destroy();
2382 }
2383 }
2384
2385 void _connectionClosed(_HttpConnection connection) {
2386 // Remove itself from either idle or active connections.
2387 connection.unlink();
2388 _maybePerformCleanup();
2389 }
2390
2391 void _markIdle(_HttpConnection connection) {
2392 _activeConnections.remove(connection);
2393 _idleConnections.add(connection);
2394 }
2395
2396 void _markActive(_HttpConnection connection) {
2397 _idleConnections.remove(connection);
2398 _activeConnections.add(connection);
2399 }
2400
2401 _HttpSessionManager get _sessionManager {
2402 // Lazy init.
2403 if (_sessionManagerInstance == null) {
2404 _sessionManagerInstance = new _HttpSessionManager();
2405 }
2406 return _sessionManagerInstance;
2407 }
2408
2409 HttpConnectionsInfo connectionsInfo() {
2410 HttpConnectionsInfo result = new HttpConnectionsInfo();
2411 result.total = _activeConnections.length + _idleConnections.length;
2412 _activeConnections.forEach((_HttpConnection conn) {
2413 if (conn._isActive) {
2414 result.active++;
2415 } else {
2416 assert(conn._isClosing);
2417 result.closing++;
2418 }
2419 });
2420 _idleConnections.forEach((_HttpConnection conn) {
2421 result.idle++;
2422 assert(conn._isIdle);
2423 });
2424 return result;
2425 }
2426
2427 String get _serviceTypePath => 'io/http/servers';
2428 String get _serviceTypeName => 'HttpServer';
2429
2430 Map<String, dynamic> _toJSON(bool ref) {
2431 var r = <String, dynamic>{
2432 'id': _servicePath,
2433 'type': _serviceType(ref),
2434 'name': '${address.host}:$port',
2435 'user_name': '${address.host}:$port',
2436 };
2437 if (ref) {
2438 return r;
2439 }
2440 try {
2441 r['socket'] = _serverSocket._toJSON(true);
2442 } catch (_) {
2443 r['socket'] = {
2444 'id': _servicePath,
2445 'type': '@Socket',
2446 'name': 'UserSocket',
2447 'user_name': 'UserSocket',
2448 };
2449 }
2450 r['port'] = port;
2451 r['address'] = address.host;
2452 r['active'] = _activeConnections.map((c) => c._toJSON(true)).toList();
2453 r['idle'] = _idleConnections.map((c) => c._toJSON(true)).toList();
2454 r['closed'] = closed;
2455 return r;
2456 }
2457
2458 _HttpSessionManager _sessionManagerInstance;
2459
2460 // Indicated if the http server has been closed.
2461 bool closed = false;
2462
2463 // The server listen socket. Untyped as it can be both ServerSocket and
2464 // SecureServerSocket.
2465 final _serverSocket;
2466 final bool _closeServer;
2467
2468 // Set of currently connected clients.
2469 final LinkedList<_HttpConnection> _activeConnections
2470 = new LinkedList<_HttpConnection>();
2471 final LinkedList<_HttpConnection> _idleConnections
2472 = new LinkedList<_HttpConnection>();
2473 StreamController<HttpRequest> _controller;
2474 }
2475
2476
2477 class _ProxyConfiguration {
2478 static const String PROXY_PREFIX = "PROXY ";
2479 static const String DIRECT_PREFIX = "DIRECT";
2480
2481 _ProxyConfiguration(String configuration) : proxies = new List<_Proxy>() {
2482 if (configuration == null) {
2483 throw new HttpException("Invalid proxy configuration $configuration");
2484 }
2485 List<String> list = configuration.split(";");
2486 list.forEach((String proxy) {
2487 proxy = proxy.trim();
2488 if (!proxy.isEmpty) {
2489 if (proxy.startsWith(PROXY_PREFIX)) {
2490 String username;
2491 String password;
2492 // Skip the "PROXY " prefix.
2493 proxy = proxy.substring(PROXY_PREFIX.length).trim();
2494 // Look for proxy authentication.
2495 int at = proxy.indexOf("@");
2496 if (at != -1) {
2497 String userinfo = proxy.substring(0, at).trim();
2498 proxy = proxy.substring(at + 1).trim();
2499 int colon = userinfo.indexOf(":");
2500 if (colon == -1 || colon == 0 || colon == proxy.length - 1) {
2501 throw new HttpException(
2502 "Invalid proxy configuration $configuration");
2503 }
2504 username = userinfo.substring(0, colon).trim();
2505 password = userinfo.substring(colon + 1).trim();
2506 }
2507 // Look for proxy host and port.
2508 int colon = proxy.lastIndexOf(":");
2509 if (colon == -1 || colon == 0 || colon == proxy.length - 1) {
2510 throw new HttpException(
2511 "Invalid proxy configuration $configuration");
2512 }
2513 String host = proxy.substring(0, colon).trim();
2514 if (host.startsWith("[") && host.endsWith("]")) {
2515 host = host.substring(1, host.length - 1);
2516 }
2517 String portString = proxy.substring(colon + 1).trim();
2518 int port;
2519 try {
2520 port = int.parse(portString);
2521 } on FormatException catch (e) {
2522 throw new HttpException(
2523 "Invalid proxy configuration $configuration, "
2524 "invalid port '$portString'");
2525 }
2526 proxies.add(new _Proxy(host, port, username, password));
2527 } else if (proxy.trim() == DIRECT_PREFIX) {
2528 proxies.add(new _Proxy.direct());
2529 } else {
2530 throw new HttpException("Invalid proxy configuration $configuration");
2531 }
2532 }
2533 });
2534 }
2535
2536 const _ProxyConfiguration.direct()
2537 : proxies = const [const _Proxy.direct()];
2538
2539 final List<_Proxy> proxies;
2540 }
2541
2542
2543 class _Proxy {
2544 final String host;
2545 final int port;
2546 final String username;
2547 final String password;
2548 final bool isDirect;
2549
2550 const _Proxy(this.host, this.port, this.username, this.password)
2551 : isDirect = false;
2552 const _Proxy.direct() : host = null, port = null,
2553 username = null, password = null, isDirect = true;
2554
2555 bool get isAuthenticated => username != null;
2556 }
2557
2558
2559 class _HttpConnectionInfo implements HttpConnectionInfo {
2560 InternetAddress remoteAddress;
2561 int remotePort;
2562 int localPort;
2563
2564 static _HttpConnectionInfo create(Socket socket) {
2565 if (socket == null) return null;
2566 try {
2567 _HttpConnectionInfo info = new _HttpConnectionInfo();
2568 return info
2569 ..remoteAddress = socket.remoteAddress
2570 ..remotePort = socket.remotePort
2571 ..localPort = socket.port;
2572 } catch (e) { }
2573 return null;
2574 }
2575 }
2576
2577
2578 class _DetachedSocket extends Stream<List<int>> implements Socket {
2579 final Stream<List<int>> _incoming;
2580 final _socket;
2581
2582 _DetachedSocket(this._socket, this._incoming);
2583
2584 StreamSubscription<List<int>> listen(void onData(List<int> event),
2585 {Function onError,
2586 void onDone(),
2587 bool cancelOnError}) {
2588 return _incoming.listen(onData,
2589 onError: onError,
2590 onDone: onDone,
2591 cancelOnError: cancelOnError);
2592 }
2593
2594 Encoding get encoding => _socket.encoding;
2595
2596 void set encoding(Encoding value) {
2597 _socket.encoding = value;
2598 }
2599
2600 void write(Object obj) { _socket.write(obj); }
2601
2602 void writeln([Object obj = ""]) { _socket.writeln(obj); }
2603
2604 void writeCharCode(int charCode) { _socket.writeCharCode(charCode); }
2605
2606 void writeAll(Iterable objects, [String separator = ""]) {
2607 _socket.writeAll(objects, separator);
2608 }
2609
2610 void add(List<int> bytes) { _socket.add(bytes); }
2611
2612 void addError(error, [StackTrace stackTrace]) =>
2613 _socket.addError(error, stackTrace);
2614
2615 Future<Socket> addStream(Stream<List<int>> stream) {
2616 return _socket.addStream(stream);
2617 }
2618
2619 void destroy() { _socket.destroy(); }
2620
2621 Future flush() => _socket.flush();
2622
2623 Future close() => _socket.close();
2624
2625 Future<Socket> get done => _socket.done;
2626
2627 int get port => _socket.port;
2628
2629 InternetAddress get address => _socket.address;
2630
2631 InternetAddress get remoteAddress => _socket.remoteAddress;
2632
2633 int get remotePort => _socket.remotePort;
2634
2635 bool setOption(SocketOption option, bool enabled) {
2636 return _socket.setOption(option, enabled);
2637 }
2638
2639 Map _toJSON(bool ref) => _socket._toJSON(ref);
2640 void set _owner(owner) { _socket._owner = owner; }
2641 }
2642
2643
2644 class _AuthenticationScheme {
2645 final int _scheme;
2646
2647 static const UNKNOWN = const _AuthenticationScheme(-1);
2648 static const BASIC = const _AuthenticationScheme(0);
2649 static const DIGEST = const _AuthenticationScheme(1);
2650
2651 const _AuthenticationScheme(this._scheme);
2652
2653 factory _AuthenticationScheme.fromString(String scheme) {
2654 if (scheme.toLowerCase() == "basic") return BASIC;
2655 if (scheme.toLowerCase() == "digest") return DIGEST;
2656 return UNKNOWN;
2657 }
2658
2659 String toString() {
2660 if (this == BASIC) return "Basic";
2661 if (this == DIGEST) return "Digest";
2662 return "Unknown";
2663 }
2664 }
2665
2666
2667 abstract class _Credentials {
2668 _HttpClientCredentials credentials;
2669 String realm;
2670 bool used = false;
2671
2672 // Digest specific fields.
2673 String ha1;
2674 String nonce;
2675 String algorithm;
2676 String qop;
2677 int nonceCount;
2678
2679 _Credentials(this.credentials, this.realm) {
2680 if (credentials.scheme == _AuthenticationScheme.DIGEST) {
2681 // Calculate the H(A1) value once. There is no mentioning of
2682 // username/password encoding in RFC 2617. However there is an
2683 // open draft for adding an additional accept-charset parameter to
2684 // the WWW-Authenticate and Proxy-Authenticate headers, see
2685 // http://tools.ietf.org/html/draft-reschke-basicauth-enc-06. For
2686 // now always use UTF-8 encoding.
2687 _HttpClientDigestCredentials creds = credentials;
2688 var hasher = new _MD5()
2689 ..add(UTF8.encode(creds.username))
2690 ..add([_CharCode.COLON])
2691 ..add(realm.codeUnits)
2692 ..add([_CharCode.COLON])
2693 ..add(UTF8.encode(creds.password));
2694 ha1 = _CryptoUtils.bytesToHex(hasher.close());
2695 }
2696 }
2697
2698 _AuthenticationScheme get scheme => credentials.scheme;
2699
2700 void authorize(HttpClientRequest request);
2701 }
2702
2703 class _SiteCredentials extends _Credentials {
2704 Uri uri;
2705
2706 _SiteCredentials(this.uri, realm, _HttpClientCredentials creds)
2707 : super(creds, realm);
2708
2709 bool applies(Uri uri, _AuthenticationScheme scheme) {
2710 if (scheme != null && credentials.scheme != scheme) return false;
2711 if (uri.host != this.uri.host) return false;
2712 int thisPort =
2713 this.uri.port == 0 ? HttpClient.DEFAULT_HTTP_PORT : this.uri.port;
2714 int otherPort = uri.port == 0 ? HttpClient.DEFAULT_HTTP_PORT : uri.port;
2715 if (otherPort != thisPort) return false;
2716 return uri.path.startsWith(this.uri.path);
2717 }
2718
2719 void authorize(HttpClientRequest request) {
2720 // Digest credentials cannot be used without a nonce from the
2721 // server.
2722 if (credentials.scheme == _AuthenticationScheme.DIGEST &&
2723 nonce == null) {
2724 return;
2725 }
2726 credentials.authorize(this, request);
2727 used = true;
2728 }
2729 }
2730
2731
2732 class _ProxyCredentials extends _Credentials {
2733 String host;
2734 int port;
2735
2736 _ProxyCredentials(this.host,
2737 this.port,
2738 realm,
2739 _HttpClientCredentials creds)
2740 : super(creds, realm);
2741
2742 bool applies(_Proxy proxy, _AuthenticationScheme scheme) {
2743 if (scheme != null && credentials.scheme != scheme) return false;
2744 return proxy.host == host && proxy.port == port;
2745 }
2746
2747 void authorize(HttpClientRequest request) {
2748 // Digest credentials cannot be used without a nonce from the
2749 // server.
2750 if (credentials.scheme == _AuthenticationScheme.DIGEST &&
2751 nonce == null) {
2752 return;
2753 }
2754 credentials.authorizeProxy(this, request);
2755 }
2756 }
2757
2758
2759 abstract class _HttpClientCredentials implements HttpClientCredentials {
2760 _AuthenticationScheme get scheme;
2761 void authorize(_Credentials credentials, HttpClientRequest request);
2762 void authorizeProxy(_ProxyCredentials credentials, HttpClientRequest request);
2763 }
2764
2765
2766 class _HttpClientBasicCredentials
2767 extends _HttpClientCredentials
2768 implements HttpClientBasicCredentials {
2769 String username;
2770 String password;
2771
2772 _HttpClientBasicCredentials(this.username, this.password);
2773
2774 _AuthenticationScheme get scheme => _AuthenticationScheme.BASIC;
2775
2776 String authorization() {
2777 // There is no mentioning of username/password encoding in RFC
2778 // 2617. However there is an open draft for adding an additional
2779 // accept-charset parameter to the WWW-Authenticate and
2780 // Proxy-Authenticate headers, see
2781 // http://tools.ietf.org/html/draft-reschke-basicauth-enc-06. For
2782 // now always use UTF-8 encoding.
2783 String auth =
2784 _CryptoUtils.bytesToBase64(UTF8.encode("$username:$password"));
2785 return "Basic $auth";
2786 }
2787
2788 void authorize(_Credentials _, HttpClientRequest request) {
2789 request.headers.set(HttpHeaders.AUTHORIZATION, authorization());
2790 }
2791
2792 void authorizeProxy(_ProxyCredentials _, HttpClientRequest request) {
2793 request.headers.set(HttpHeaders.PROXY_AUTHORIZATION, authorization());
2794 }
2795 }
2796
2797
2798 class _HttpClientDigestCredentials
2799 extends _HttpClientCredentials
2800 implements HttpClientDigestCredentials {
2801 String username;
2802 String password;
2803
2804 _HttpClientDigestCredentials(this.username, this.password);
2805
2806 _AuthenticationScheme get scheme => _AuthenticationScheme.DIGEST;
2807
2808 String authorization(_Credentials credentials, _HttpClientRequest request) {
2809 String requestUri = request._requestUri();
2810 _MD5 hasher = new _MD5()
2811 ..add(request.method.codeUnits)
2812 ..add([_CharCode.COLON])
2813 ..add(requestUri.codeUnits);
2814 var ha2 = _CryptoUtils.bytesToHex(hasher.close());
2815
2816 String qop;
2817 String cnonce;
2818 String nc;
2819 var x;
2820 hasher = new _MD5()
2821 ..add(credentials.ha1.codeUnits)
2822 ..add([_CharCode.COLON]);
2823 if (credentials.qop == "auth") {
2824 qop = credentials.qop;
2825 cnonce = _CryptoUtils.bytesToHex(_IOCrypto.getRandomBytes(4));
2826 ++credentials.nonceCount;
2827 nc = credentials.nonceCount.toRadixString(16);
2828 nc = "00000000".substring(0, 8 - nc.length + 1) + nc;
2829 hasher
2830 ..add(credentials.nonce.codeUnits)
2831 ..add([_CharCode.COLON])
2832 ..add(nc.codeUnits)
2833 ..add([_CharCode.COLON])
2834 ..add(cnonce.codeUnits)
2835 ..add([_CharCode.COLON])
2836 ..add(credentials.qop.codeUnits)
2837 ..add([_CharCode.COLON])
2838 ..add(ha2.codeUnits);
2839 } else {
2840 hasher
2841 ..add(credentials.nonce.codeUnits)
2842 ..add([_CharCode.COLON])
2843 ..add(ha2.codeUnits);
2844 }
2845 var response = _CryptoUtils.bytesToHex(hasher.close());
2846
2847 StringBuffer buffer = new StringBuffer()
2848 ..write('Digest ')
2849 ..write('username="$username"')
2850 ..write(', realm="${credentials.realm}"')
2851 ..write(', nonce="${credentials.nonce}"')
2852 ..write(', uri="$requestUri"')
2853 ..write(', algorithm="${credentials.algorithm}"');
2854 if (qop == "auth") {
2855 buffer
2856 ..write(', qop="$qop"')
2857 ..write(', cnonce="$cnonce"')
2858 ..write(', nc="$nc"');
2859 }
2860 buffer.write(', response="$response"');
2861 return buffer.toString();
2862 }
2863
2864 void authorize(_Credentials credentials, HttpClientRequest request) {
2865 request.headers.set(HttpHeaders.AUTHORIZATION,
2866 authorization(credentials, request));
2867 }
2868
2869 void authorizeProxy(_ProxyCredentials credentials,
2870 HttpClientRequest request) {
2871 request.headers.set(HttpHeaders.PROXY_AUTHORIZATION,
2872 authorization(credentials, request));
2873 }
2874 }
2875
2876
2877 class _RedirectInfo implements RedirectInfo {
2878 final int statusCode;
2879 final String method;
2880 final Uri location;
2881 const _RedirectInfo(this.statusCode, this.method, this.location);
2882 }
2883
2884 String _getHttpVersion() {
2885 var version = Platform.version;
2886 // Only include major and minor version numbers.
2887 int index = version.indexOf('.', version.indexOf('.') + 1);
2888 version = version.substring(0, index);
2889 return 'Dart/$version (dart:io)';
2890 }
OLDNEW
« no previous file with comments | « pkg/dev_compiler/tool/input_sdk/lib/io/http_headers.dart ('k') | pkg/dev_compiler/tool/input_sdk/lib/io/http_parser.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698