Migrated http_proxy_test and some test certificates from SDK. (#44)

* Migrated http_proxy_test and some test certificates from SDK.

* Fixed Platform path resolution issues.
diff --git a/test/certificates/README b/test/certificates/README
new file mode 100644
index 0000000..6ecd518
--- /dev/null
+++ b/test/certificates/README
@@ -0,0 +1,70 @@
+    This directory, tests/standalone/io/certificates, contains the
+X509 TLS certificates and private keys needed to run tests of Dart's
+secure networking code.  The SecureSocket and SecureServer classes
+are tested by making TLS (formerly called SSL) connections, secured
+by certificates from a self-signed test root authority.
+
+The certificates are created by running ../create_sample_certificates.sh
+in a bash or sh shell, with the openssl tools installed.  Run the script
+twice to create the untrusted_* files.
+
+PEM files:
+
+server_chain.pem:
+  Contains the chain of certificates, from the self-signed
+test certificate authority, through the intermediate CA, to the server
+certificate, used on the server side of a test connection.
+
+server_key.pem:
+  Contains the private key for the server certificate
+
+trusted_certs.pem:
+  Contains the self-signed certificate of the test certificate authority.
+This certificate is set as "trusted" by the client side of the connection
+in its SecurityContext object, so that a verified TLS connection to the
+server can be made.
+
+untrusted_server_chain.pem:
+  Contains a chain of certificates, from a different self-signed
+test certificate authority, through an intermediate CA, to a server
+certificate, used on the server side of a test connection that is intended
+to fail because the client does not accept this certificate authority
+
+untrusted_server_key.pem:
+  Contains the private key for the untrusted server certificate
+in untrusted_server_chain.pem
+
+*_malformed.pem:
+  Truncated PEM formatted certificates used to test error handling.
+
+PKCS12 files:
+
+server_key.12:
+  Created with:
+  $ openssl pkcs12 -export -inkey server_key.pem -out server_key.p12 -nocerts
+  with password 'dartdart'
+
+server_chain.p12:
+  Created with:
+  $ openssl pkcs12 -export -in server_chain.pem -out server_chain.p12 -nokeys
+  with password 'dartdart'
+
+client1_key.p12:
+  Created with:
+  $ openssl pkcs12 -export -inkey client1_key.pem -out client1_key.p12 -nocerts
+  with password 'dartdart'
+
+client1.p12:
+  Created with:
+  $ openssl pkcs12 -export -in client1.pem -out client1.p12 -nokeys
+  with password 'dartdart'
+
+trusted_certs.p12:
+  Created with:
+  $ openssl pkcs12 -export -in trusted_certs.pem -out trusted_certs.p12 -nokeys
+  with password 'dartdart'
+
+client_authority.p12:
+  Created with:
+  $ openssl pkcs12 -export -in client_authority.pem -out client_authority.p12 -nokeys
+  with password 'dartdart'
diff --git a/test/certificates/server_chain.pem b/test/certificates/server_chain.pem
new file mode 100644
index 0000000..341a86f
--- /dev/null
+++ b/test/certificates/server_chain.pem
@@ -0,0 +1,59 @@
+-----BEGIN CERTIFICATE-----
+MIIDZDCCAkygAwIBAgIBATANBgkqhkiG9w0BAQsFADAgMR4wHAYDVQQDDBVpbnRl
+cm1lZGlhdGVhdXRob3JpdHkwHhcNMTUxMDI3MTAyNjM1WhcNMjUxMDI0MTAyNjM1
+WjAUMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
+ggEKAoIBAQCkg/Qr8RQeLTOSgCkyiEX2ztgkgscX8hKGHEHdvlkmVK3JVEIIwkvu
+/Y9LtHZUia3nPAgqEEbexzTENZjSCcC0V6I2XW/e5tIE3rO0KLZyhtZhN/2SfJ6p
+KbOh0HLr1VtkKJGp1tzUmHW/aZI32pK60ZJ/N917NLPCJpCaL8+wHo3+w3oNqln6
+oJsfgxy9SUM8Bsc9WMYKMUdqLO1QKs1A5YwqZuO7Mwj+4LY2QDixC7Ua7V9YAPo2
+1SBeLvMCHbYxSPCuxcZ/kDkgax/DF9u7aZnGhMImkwBka0OQFvpfjKtTIuoobTpe
+PAG7MQYXk4RjnjdyEX/9XAQzvNo1CDObAgMBAAGjgbQwgbEwPAYDVR0RBDUwM4IJ
+bG9jYWxob3N0ggkxMjcuMC4wLjGCAzo6MYcEfwAAAYcQAAAAAAAAAAAAAAAAAAAA
+ATAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBSvhJo6taTggJQBukEvMo/PDk8tKTAf
+BgNVHSMEGDAWgBS98L4T5RaIToE3DkBRsoeWPil0eDAOBgNVHQ8BAf8EBAMCA6gw
+EwYDVR0lBAwwCgYIKwYBBQUHAwEwDQYJKoZIhvcNAQELBQADggEBAHLOt0mL2S4A
+B7vN7KsfQeGlVgZUVlEjem6kqBh4fIzl4CsQuOO8oJ0FlO1z5JAIo98hZinymJx1
+phBVpyGIKakT/etMH0op5evLe9dD36VA3IM/FEv5ibk35iGnPokiJXIAcdHd1zam
+YaTHRAnZET5S03+7BgRTKoRuszhbvuFz/vKXaIAnVNOF4Gf2NUJ/Ax7ssJtRkN+5
+UVxe8TZVxzgiRv1uF6NTr+J8PDepkHCbJ6zEQNudcFKAuC56DN1vUe06gRDrNbVq
+2JHEh4pRfMpdsPCrS5YHBjVq/XHtFHgwDR6g0WTwSUJvDeM4OPQY5f61FB0JbFza
+PkLkXmoIod8=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDLjCCAhagAwIBAgIBAjANBgkqhkiG9w0BAQsFADAYMRYwFAYDVQQDDA1yb290
+YXV0aG9yaXR5MB4XDTE1MTAyNzEwMjYzNVoXDTI1MTAyNDEwMjYzNVowIDEeMBwG
+A1UEAwwVaW50ZXJtZWRpYXRlYXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOC
+AQ8AMIIBCgKCAQEA6GndRFiXk+2q+Ig7ZOWKKGta+is8137qyXz+eVFs5sA0ajMN
+ZBAMWS0TIXw/Yks+y6fEcV/tfv91k1eUN4YXPcoxTdDF97d2hO9wxumeYOMnQeDy
+VZVDKQBZ+jFMeI+VkNpMEdmsLErpZDGob/1dC8tLEuR6RuRR8X6IDGMPOCMw1jLK
+V1bQjPtzqKadTscfjLuKxuLgspJdTrzsu6hdcl1mm8K6CjTY2HNXWxs1yYmwfuQ2
+Z4/8sOMNqFqLjN+ChD7pksTMq7IosqGiJzi2bpd5f44ek/k822Y0ATncJHk4h1Z+
+kZBnW6kgcLna1gDri9heRwSZ+M8T8nlHgIMZIQIDAQABo3sweTASBgNVHRMBAf8E
+CDAGAQH/AgEAMB0GA1UdDgQWBBS98L4T5RaIToE3DkBRsoeWPil0eDAfBgNVHSME
+GDAWgBRxD5DQHTmtpDFKDOiMf5FAi6vfbzAOBgNVHQ8BAf8EBAMCAgQwEwYDVR0l
+BAwwCgYIKwYBBQUHAwEwDQYJKoZIhvcNAQELBQADggEBAD+4KpUeV5mUPw5IG/7w
+eOXnUpeS96XFGuS1JuFo/TbgntPWSPyo+rD4GrPIkUXyoHaMCDd2UBEjyGbBIKlB
+NZA3RJOAEp7DTkLNK4RFn/OEcLwG0J5brL7kaLRO4vwvItVIdZ2XIqzypRQTc0MG
+MmF08zycnSlaN01ryM67AsMhwdHqVa+uXQPo8R8sdFGnZ33yywTYD73FeImXilQ2
+rDnFUVqmrW1fjl0Fi4rV5XI0EQiPrzKvRtmF8ZqjGATPOsRd64cwQX6V+P5hNeIR
+9pba6td7AbNGausHfacRYMyoGJWWWkFPd+7jWOCPqW7Fk1tmBgdB8GzXa3inWIRM
+RUE=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIC+zCCAeOgAwIBAgIBATANBgkqhkiG9w0BAQsFADAYMRYwFAYDVQQDDA1yb290
+YXV0aG9yaXR5MB4XDTE1MTAyNzEwMjYzNFoXDTI1MTAyNDEwMjYzNFowGDEWMBQG
+A1UEAwwNcm9vdGF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBAMl+dcraUM/E7E6zl7+7hK9oUJYXJLnfiMtP/TRFVbH4+2aEN8vXzPbzKdR3
+FfaHczXQTwnTCaYA4u4uSDvSOsFFEfxEwYORsdKmQEM8nGpVX2NVvKsMcGIhh8kh
+ZwJfkMIOcAxmGIHGdMhF8VghonJ8uGiuqktxdfpARq0g3fqIjDHsF9/LpfshUfk9
+wsRyTF0yr90U/dsfnE+u8l7GvVl8j2Zegp0sagAGtLaNv7tP17AibqEGg2yDBrBN
+9r9ihe4CqMjx+Q2kQ2S9Gz2V2ReO/n6vm2VQxsPRB/lV/9jh7cUcS0/9mggLYrDy
+cq1v7rLLQrWuxMz1E3gOhyCYJ38CAwEAAaNQME4wHQYDVR0OBBYEFHEPkNAdOa2k
+MUoM6Ix/kUCLq99vMB8GA1UdIwQYMBaAFHEPkNAdOa2kMUoM6Ix/kUCLq99vMAwG
+A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBABrhjnWC6b+z9Kw73C/niOwo
+9sPdufjS6tb0sCwDjt3mjvE4NdNWt+/+ZOugW6dqtvqhtqZM1q0u9pJkNwIrqgFD
+ZHcfNaf31G6Z2YE+Io7woTVw6fFobg/EFo+a/qwbvWL26McmiRL5yiSBjVjpX4a5
+kdZ+aPQUCBaLrTWwlCDqzSVIULWUQvveRWbToMFKPNID58NtEpymAx3Pgir7YjV9
+UnlU2l5vZrh1PTCqZxvC/IdRESUfW80LdHaeyizRUP+6vKxGgSz2MRuYINjbd6GO
+hGiCpWlwziW2xLV1l2qSRLko2kIafLZP18N0ThM9zKbU5ps9NgFOf//wqSGtLaE=
+-----END CERTIFICATE-----
diff --git a/test/certificates/server_key.pem b/test/certificates/server_key.pem
new file mode 100644
index 0000000..895b7d2
--- /dev/null
+++ b/test/certificates/server_key.pem
@@ -0,0 +1,29 @@
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIIE4zAcBgoqhkiG9w0BDAEBMA4ECBMCjlg8JYZ4AgIIAASCBMFd9cBoZ5xcTock
+AVQcg/HzYJtMceKn1gtMDdC7mmXuyN0shoxhG4BpQInHkFARL+nenesXFxEm4X5e
+L603Pcgw72/ratxVpTW7hPMjiLTEBqza0GjQm7Sarbdy+Vzdp/6XFrAcPfFl1juY
+oyYzbozPsvFHz3Re44y1KmI4HAzU/qkjJUbNTTiPPVI2cDP6iYN2XXxBb1wwp8jR
+iqdZqFG7lU/wvPEbD7BVPpmJBHWNG681zb4ea5Zn4hW8UaxpiIBiaH0/IWc2SVZd
+RliAFo3NEsGxCcsnBo/n00oudGbOJxdOp7FbH5hJpeqX2WhCyJRxIeHOWmeuMAet
+03HFriiEmJ99m2nEJN1x0A3QUUM7ji6vZAb4qb1dyq7LlX4M2aaqixRnaTcQkapf
+DOxX35DEBXSKrDpyWp6Rx4wNpUyi1TKyhaVnYgD3Gn0VfC/2w86gSFlrf9PMYGM0
+PvFxTDzTyjOuPBRa728gZOGXgDOL7qvdInU/opVew7kFeRQHXxHzFCLK5dD+Vrig
+5fS3m0++f55ODkxqHXB8gbXbd3GMmsW6MrGpU7VsCNtbVPdSMW0FalovEB0M+2lj
+1VfuvL+0F5huTe+BgZAt6xgET/CIcZXdNMRPVhraqUjqWtI9Rdk4STPCpU1rDkjG
+YDl/fo4W2T6qQWFUpiC9IvVVGkVxaqfZZ4Qu+V5xPUi6vk95QiTNkN1t+m+sCCgS
+Llkea8Um0aHMy33Lj3NsfL0LMrnpniqcAks8BvcgIZwk1VRqcj7BQVCygJSYrmAR
+DBhMpjWlXuSggnyVPuduZDtnTN+8lCHLOKL3a3bDb6ySaKX49Km6GutDLfpDtEA0
+3mQvmEG4XVm7zy+AlN72qFbtSLDRi/D/uQh2q/ZrFQLOBQBQB56TvEbKouLimUDM
+ascQA3aUyhOE7e+d02NOFIFTozwc/C//CIFeA+ZEwxyfha/3Bor6Jez7PC/eHNxZ
+w7YMXzPW9NhcCcerhYGebuCJxLwzqJ+IGdukjKsGV2ytWDoB2xZiJNu096j4RKcq
+YSJoen0R7IH8N4eDujXR8m9kAl724Uqs1OoAs4VNICvzTutbsgVZ6Z+NMOcfnPw9
+jZkFhot16w8znD+OmhBR7/bzOLpaeUhk7EhNq5M6U0NNWx3WwkDlvU/jx+6/EQe3
+iLEHptH2HYBF1xscaKGbtKNtuQsfdzgWpOX0qK2YbK3yCKvL/xIm1DQmDZDKkWdW
+VNh8oGV1H96CivWlvxhAgXKz9F/83CjMw8YXRk7RJvWR4vtNvXFAvGkFIYCN9Jv9
+p+1ukaYoxSLGBik907I6gWSHqumJiCprUyAX/bVfZfNiYh4hzeA3lhwxZSax3JG4
+7QFPvyepOmF/3AAzS/Pusx6jOZnuCMCkfQi6Wpem1o3s4x+fP7kz00Xuj01ErucM
+S10ixfIh84kXBN3dTRDtDdeCyoMsBKO0W5jDBBlWL02YfdF6Opo1Q4cPh2DYgXMh
+XEszNZSK5LB0y+f3A6Kdx/hkZzHVvMONA70OyrkoZzGyWENhcB0c7ntTJyPPD2qM
+s0HRA2VwF/0ypU3OKERM1Ua5NSkTgvnnVTlV9GO90Tkn5v4fxdl8NzIuJLyGguTP
+Xc0tRM34Lg==
+-----END ENCRYPTED PRIVATE KEY-----
diff --git a/test/certificates/trusted_certs.pem b/test/certificates/trusted_certs.pem
new file mode 100644
index 0000000..8b5bf3e
--- /dev/null
+++ b/test/certificates/trusted_certs.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC+zCCAeOgAwIBAgIBATANBgkqhkiG9w0BAQsFADAYMRYwFAYDVQQDDA1yb290
+YXV0aG9yaXR5MB4XDTE1MTAyNzEwMjYzNFoXDTI1MTAyNDEwMjYzNFowGDEWMBQG
+A1UEAwwNcm9vdGF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBAMl+dcraUM/E7E6zl7+7hK9oUJYXJLnfiMtP/TRFVbH4+2aEN8vXzPbzKdR3
+FfaHczXQTwnTCaYA4u4uSDvSOsFFEfxEwYORsdKmQEM8nGpVX2NVvKsMcGIhh8kh
+ZwJfkMIOcAxmGIHGdMhF8VghonJ8uGiuqktxdfpARq0g3fqIjDHsF9/LpfshUfk9
+wsRyTF0yr90U/dsfnE+u8l7GvVl8j2Zegp0sagAGtLaNv7tP17AibqEGg2yDBrBN
+9r9ihe4CqMjx+Q2kQ2S9Gz2V2ReO/n6vm2VQxsPRB/lV/9jh7cUcS0/9mggLYrDy
+cq1v7rLLQrWuxMz1E3gOhyCYJ38CAwEAAaNQME4wHQYDVR0OBBYEFHEPkNAdOa2k
+MUoM6Ix/kUCLq99vMB8GA1UdIwQYMBaAFHEPkNAdOa2kMUoM6Ix/kUCLq99vMAwG
+A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBABrhjnWC6b+z9Kw73C/niOwo
+9sPdufjS6tb0sCwDjt3mjvE4NdNWt+/+ZOugW6dqtvqhtqZM1q0u9pJkNwIrqgFD
+ZHcfNaf31G6Z2YE+Io7woTVw6fFobg/EFo+a/qwbvWL26McmiRL5yiSBjVjpX4a5
+kdZ+aPQUCBaLrTWwlCDqzSVIULWUQvveRWbToMFKPNID58NtEpymAx3Pgir7YjV9
+UnlU2l5vZrh1PTCqZxvC/IdRESUfW80LdHaeyizRUP+6vKxGgSz2MRuYINjbd6GO
+hGiCpWlwziW2xLV1l2qSRLko2kIafLZP18N0ThM9zKbU5ps9NgFOf//wqSGtLaE=
+-----END CERTIFICATE-----
diff --git a/test/http_proxy_test.dart b/test/http_proxy_test.dart
new file mode 100644
index 0000000..4df58be
--- /dev/null
+++ b/test/http_proxy_test.dart
@@ -0,0 +1,491 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io' show Directory, File, Platform, SecurityContext, Socket;
+
+import 'package:convert/convert.dart';
+import 'package:crypto/crypto.dart';
+import 'package:http_io/http_io.dart';
+import 'package:test/test.dart';
+
+String localFile(path) {
+  final localPath = "${Directory.current.path}/test/$path";
+  if (!(new File(localPath).existsSync())) {
+    return Platform.script.resolve(path).toFilePath();
+  }
+  return localPath;
+}
+
+final SecurityContext serverContext = new SecurityContext()
+  ..useCertificateChain(localFile('certificates/server_chain.pem'))
+  ..usePrivateKey(localFile('certificates/server_key.pem'),
+      password: 'dartdart');
+
+final SecurityContext clientContext = new SecurityContext()
+  ..setTrustedCertificates(localFile('certificates/trusted_certs.pem'));
+
+class Server {
+  HttpServer server;
+  bool secure;
+  int proxyHops;
+  List<String> directRequestPaths;
+  int requestCount = 0;
+
+  Server(this.proxyHops, this.directRequestPaths, this.secure);
+
+  Future<Server> start() {
+    return (secure
+        ? HttpServer.bindSecure("localhost", 0, serverContext)
+        : HttpServer.bind("localhost", 0)).then((s) {
+      server = s;
+      server.listen(requestHandler);
+      return this;
+    });
+  }
+
+  void requestHandler(HttpRequest request) {
+    var response = request.response;
+    requestCount++;
+    // Check whether a proxy or direct connection is expected.
+    bool direct = directRequestPaths.fold(
+        false, (prev, path) => prev ? prev : path == request.uri.path);
+    if (!secure && !direct && proxyHops > 0) {
+      expect(request.headers[HttpHeaders.VIA], isNotNull);
+      expect(1, equals(request.headers[HttpHeaders.VIA].length));
+      expect(proxyHops,
+          equals(request.headers[HttpHeaders.VIA][0].split(",").length));
+    } else {
+      expect(request.headers[HttpHeaders.VIA], isNull);
+    }
+    var body = new StringBuffer();
+    onRequestComplete() {
+      String path = request.uri.path.substring(1);
+      if (path != "A") {
+        String content = "$path$path$path";
+        expect(content, equals(body.toString()));
+      }
+      response.write(request.uri.path);
+      response.close();
+    }
+
+    request.listen((data) {
+      body.write(new String.fromCharCodes(data));
+    }, onDone: onRequestComplete);
+  }
+
+  void shutdown() {
+    server.close();
+  }
+
+  int get port => server.port;
+}
+
+Future<Server> setupServer(int proxyHops,
+    {List<String> directRequestPaths: const <String>[], secure: false}) {
+  Server server = new Server(proxyHops, directRequestPaths, secure);
+  return server.start();
+}
+
+class ProxyServer {
+  final bool ipV6;
+  HttpServer server;
+  HttpClient client;
+  int requestCount = 0;
+  String authScheme;
+  String realm = "test";
+  String username;
+  String password;
+
+  var ha1;
+  String serverAlgorithm = "MD5";
+  String serverQop = "auth";
+  Set ncs = new Set();
+
+  var nonce = "12345678"; // No need for random nonce in test.
+
+  ProxyServer({this.ipV6: false}) : client = new HttpClient();
+
+  void useBasicAuthentication(String username, String password) {
+    this.username = username;
+    this.password = password;
+    authScheme = "Basic";
+  }
+
+  basicAuthenticationRequired(request) {
+    request.fold(null, (x, y) {}).then((_) {
+      var response = request.response;
+      response.headers
+          .set(HttpHeaders.PROXY_AUTHENTICATE, "Basic, realm=$realm");
+      response.statusCode = HttpStatus.PROXY_AUTHENTICATION_REQUIRED;
+      response.close();
+    });
+  }
+
+  digestAuthenticationRequired(request, {stale: false}) {
+    request.fold(null, (x, y) {}).then((_) {
+      var response = request.response;
+      response.statusCode = HttpStatus.PROXY_AUTHENTICATION_REQUIRED;
+      StringBuffer authHeader = new StringBuffer();
+      authHeader.write('Digest');
+      authHeader.write(', realm="$realm"');
+      authHeader.write(', nonce="$nonce"');
+      if (stale) authHeader.write(', stale="true"');
+      if (serverAlgorithm != null) {
+        authHeader.write(', algorithm=$serverAlgorithm');
+      }
+      if (serverQop != null) authHeader.write(', qop="$serverQop"');
+      response.headers.set(HttpHeaders.PROXY_AUTHENTICATE, authHeader);
+      response.close();
+    });
+  }
+
+  Future<ProxyServer> start() {
+    var x = new Completer<ProxyServer>();
+    var host = ipV6 ? "::1" : "localhost";
+    HttpServer.bind(host, 0).then((s) {
+      server = s;
+      x.complete(this);
+      server.listen((HttpRequest request) {
+        requestCount++;
+        if (username != null && password != null) {
+          if (request.headers[HttpHeaders.PROXY_AUTHORIZATION] == null) {
+            if (authScheme == "Digest") {
+              digestAuthenticationRequired(request);
+            } else {
+              basicAuthenticationRequired(request);
+            }
+            return;
+          } else {
+            expect(
+                1,
+                equals(
+                    request.headers[HttpHeaders.PROXY_AUTHORIZATION].length));
+            String authorization =
+                request.headers[HttpHeaders.PROXY_AUTHORIZATION][0];
+            if (authScheme == "Basic") {
+              List<String> tokens = authorization.split(" ");
+              expect("Basic", equals(tokens[0]));
+              String auth = base64.encode(utf8.encode("$username:$password"));
+              if (auth != tokens[1]) {
+                basicAuthenticationRequired(request);
+                return;
+              }
+            } else {
+              HeaderValue header =
+                  HeaderValue.parse(authorization, parameterSeparator: ",");
+              expect("Digest", equals(header.value));
+              var uri = header.parameters["uri"];
+              var qop = header.parameters["qop"];
+              var cnonce = header.parameters["cnonce"];
+              var nc = header.parameters["nc"];
+              expect(username, equals(header.parameters["username"]));
+              expect(realm, equals(header.parameters["realm"]));
+              expect("MD5", equals(header.parameters["algorithm"]));
+              expect(nonce, equals(header.parameters["nonce"]));
+              expect(request.uri.toString(), equals(uri));
+              if (qop != null) {
+                // A server qop of auth-int is downgraded to none by the client.
+                expect("auth", equals(serverQop));
+                expect("auth", equals(header.parameters["qop"]));
+                expect(cnonce, isNotNull);
+                expect(nc, isNotNull);
+                expect(ncs.contains(nc), isFalse);
+                ncs.add(nc);
+              } else {
+                expect(cnonce, isNull);
+                expect(nc, isNull);
+              }
+              expect(header.parameters["response"], isNotNull);
+
+              var digest = md5.convert("${request.method}:${uri}".codeUnits);
+              var ha2 = hex.encode(digest.bytes);
+
+              if (qop == null || qop == "" || qop == "none") {
+                digest = md5.convert("$ha1:${nonce}:$ha2".codeUnits);
+              } else {
+                digest = md5.convert(
+                    "$ha1:${nonce}:${nc}:${cnonce}:${qop}:$ha2".codeUnits);
+              }
+              expect(hex.encode(digest.bytes),
+                  equals(header.parameters["response"]));
+
+              // Add a bogus Proxy-Authentication-Info for testing.
+              var info = 'rspauth="77180d1ab3d6c9de084766977790f482", '
+                  'cnonce="8f971178", '
+                  'nc=000002c74, '
+                  'qop=auth';
+              request.response.headers.set("Proxy-Authentication-Info", info);
+            }
+          }
+        }
+        // Open the connection from the proxy.
+        if (request.method == "CONNECT") {
+          var tmp = request.uri.toString().split(":");
+          Socket.connect(tmp[0], int.parse(tmp[1])).then((socket) {
+            request.response.reasonPhrase = "Connection established";
+            request.response.detachSocket().then((detached) {
+              socket.pipe(detached);
+              detached.pipe(socket);
+            });
+          });
+        } else {
+          client
+              .openUrl(request.method, request.uri)
+              .then((HttpClientRequest clientRequest) {
+            // Forward all headers.
+            request.headers.forEach((String name, List<String> values) {
+              values.forEach((String value) {
+                if (name != "content-length" && name != "via") {
+                  clientRequest.headers.add(name, value);
+                }
+              });
+            });
+            // Special handling of Content-Length and Via.
+            clientRequest.contentLength = request.contentLength;
+            List<String> via = request.headers[HttpHeaders.VIA];
+            String viaPrefix = via == null ? "" : "${via[0]}, ";
+            clientRequest.headers
+                .add(HttpHeaders.VIA, "${viaPrefix}1.1 localhost:$port");
+            // Copy all content.
+            return request.pipe(clientRequest);
+          }).then((clientResponse) {
+            (clientResponse as HttpClientResponse).pipe(request.response);
+          });
+        }
+      });
+    });
+    return x.future;
+  }
+
+  void shutdown() {
+    server.close();
+    client.close();
+  }
+
+  int get port => server.port;
+}
+
+Future<ProxyServer> setupProxyServer({ipV6: false}) {
+  ProxyServer proxyServer = new ProxyServer(ipV6: ipV6);
+  return proxyServer.start();
+}
+
+testInvalidProxy() {
+  HttpClient client = new HttpClient(context: clientContext);
+
+  client.findProxy = (Uri uri) => "";
+  client
+      .getUrl(Uri.parse("http://www.google.com/test"))
+      .catchError((error) {}, test: (e) => e is HttpException);
+
+  client.findProxy = (Uri uri) => "XXX";
+  client
+      .getUrl(Uri.parse("http://www.google.com/test"))
+      .catchError((error) {}, test: (e) => e is HttpException);
+
+  client.findProxy = (Uri uri) => "PROXY www.google.com";
+  client
+      .getUrl(Uri.parse("http://www.google.com/test"))
+      .catchError((error) {}, test: (e) => e is HttpException);
+
+  client.findProxy = (Uri uri) => "PROXY www.google.com:http";
+  client
+      .getUrl(Uri.parse("http://www.google.com/test"))
+      .catchError((error) {}, test: (e) => e is HttpException);
+}
+
+int testDirectDoneCount = 0;
+Future<Null> testDirectProxy() {
+  final completer = new Completer<Null>();
+  setupServer(0).then((server) {
+    HttpClient client = new HttpClient(context: clientContext);
+    List<String> proxy = [
+      "DIRECT",
+      " DIRECT ",
+      "DIRECT ;",
+      " DIRECT ; ",
+      ";DIRECT",
+      " ; DIRECT ",
+      ";;DIRECT;;"
+    ];
+
+    client.findProxy = (Uri uri) {
+      int index = int.parse(uri.path.substring(1));
+      return proxy[index];
+    };
+
+    for (int i = 0; i < proxy.length; i++) {
+      client
+          .getUrl(Uri.parse("http://localhost:${server.port}/$i"))
+          .then((HttpClientRequest clientRequest) {
+        String content = "$i$i$i";
+        clientRequest.contentLength = content.length;
+        clientRequest.write(content);
+        return clientRequest.close();
+      }).then((HttpClientResponse response) {
+        response.listen((_) {}, onDone: () {
+          testDirectDoneCount++;
+          if (testDirectDoneCount == proxy.length) {
+            expect(proxy.length, equals(server.requestCount));
+            server.shutdown();
+            client.close();
+            completer.complete();
+          }
+        });
+      });
+    }
+  });
+  return completer.future;
+}
+
+int testProxyDoneCount = 0;
+Future<Null> testProxy() {
+  final completer = new Completer<Null>();
+  setupProxyServer().then((proxyServer) {
+    setupServer(1, directRequestPaths: ["/4"]).then((server) {
+      setupServer(1, directRequestPaths: ["/4"], secure: true)
+          .then((secureServer) {
+        HttpClient client = new HttpClient(context: clientContext);
+
+        List<String> proxy;
+        if (Platform.operatingSystem == "windows") {
+          proxy = [
+            "PROXY localhost:${proxyServer.port}",
+            "PROXY localhost:${proxyServer.port}; PROXY hede.hule.hest:8080",
+            "PROXY localhost:${proxyServer.port}",
+            ""
+                " PROXY localhost:${proxyServer.port}",
+            "DIRECT",
+            "PROXY localhost:${proxyServer.port}; DIRECT"
+          ];
+        } else {
+          proxy = [
+            "PROXY localhost:${proxyServer.port}",
+            "PROXY localhost:${proxyServer.port}; PROXY hede.hule.hest:8080",
+            "PROXY hede.hule.hest:8080; PROXY localhost:${proxyServer.port}",
+            "PROXY hede.hule.hest:8080; PROXY hede.hule.hest:8181;"
+                " PROXY localhost:${proxyServer.port}",
+            "PROXY hede.hule.hest:8080; PROXY hede.hule.hest:8181; DIRECT",
+            "PROXY localhost:${proxyServer.port}; DIRECT"
+          ];
+        }
+        client.findProxy = (Uri uri) {
+          // Pick the proxy configuration based on the request path.
+          int index = int.parse(uri.path.substring(1));
+          return proxy[index];
+        };
+
+        for (int i = 0; i < proxy.length; i++) {
+          test(bool secure) {
+            String url = secure
+                ? "https://localhost:${secureServer.port}/$i"
+                : "http://localhost:${server.port}/$i";
+
+            client
+                .postUrl(Uri.parse(url))
+                .then((HttpClientRequest clientRequest) {
+              String content = "$i$i$i";
+              clientRequest.write(content);
+              return clientRequest.close();
+            }).then((HttpClientResponse response) {
+              response.listen((_) {}, onDone: () {
+                testProxyDoneCount++;
+                if (testProxyDoneCount == proxy.length * 2) {
+                  expect(proxy.length, equals(server.requestCount));
+                  expect(proxy.length, equals(secureServer.requestCount));
+                  proxyServer.shutdown();
+                  server.shutdown();
+                  secureServer.shutdown();
+                  client.close();
+                  completer.complete();
+                }
+              });
+            });
+          }
+
+          test(false);
+          test(true);
+        }
+      });
+    });
+  });
+  return completer.future;
+}
+
+int testProxyChainDoneCount = 0;
+Future<Null> testProxyChain() {
+  final completer = new Completer<Null>();
+  // Setup two proxy servers having the first using the second as its proxy.
+  setupProxyServer().then((proxyServer1) {
+    setupProxyServer().then((proxyServer2) {
+      proxyServer1.client.findProxy =
+          (_) => "PROXY localhost:${proxyServer2.port}";
+
+      setupServer(2, directRequestPaths: ["/4"]).then((server) {
+        HttpClient client = new HttpClient(context: clientContext);
+
+        List<String> proxy;
+        if (Platform.operatingSystem == "windows") {
+          proxy = [
+            "PROXY localhost:${proxyServer1.port}",
+            "PROXY localhost:${proxyServer1.port}; PROXY hede.hule.hest:8080",
+            "PROXY localhost:${proxyServer1.port}",
+            "PROXY localhost:${proxyServer1.port}",
+            "DIRECT",
+            "PROXY localhost:${proxyServer1.port}; DIRECT"
+          ];
+        } else {
+          proxy = [
+            "PROXY localhost:${proxyServer1.port}",
+            "PROXY localhost:${proxyServer1.port}; PROXY hede.hule.hest:8080",
+            "PROXY hede.hule.hest:8080; PROXY localhost:${proxyServer1.port}",
+            "PROXY hede.hule.hest:8080; PROXY hede.hule.hest:8181;"
+                " PROXY localhost:${proxyServer1.port}",
+            "PROXY hede.hule.hest:8080; PROXY hede.hule.hest:8181; DIRECT",
+            "PROXY localhost:${proxyServer1.port}; DIRECT"
+          ];
+        }
+
+        client.findProxy = (Uri uri) {
+          // Pick the proxy configuration based on the request path.
+          int index = int.parse(uri.path.substring(1));
+          return proxy[index];
+        };
+
+        for (int i = 0; i < proxy.length; i++) {
+          client
+              .getUrl(Uri.parse("http://localhost:${server.port}/$i"))
+              .then((HttpClientRequest clientRequest) {
+            String content = "$i$i$i";
+            clientRequest.contentLength = content.length;
+            clientRequest.write(content);
+            return clientRequest.close();
+          }).then((HttpClientResponse response) {
+            response.listen((_) {}, onDone: () {
+              testProxyChainDoneCount++;
+              if (testProxyChainDoneCount == proxy.length) {
+                expect(proxy.length, equals(server.requestCount));
+                proxyServer1.shutdown();
+                proxyServer2.shutdown();
+                server.shutdown();
+                client.close();
+                completer.complete();
+              }
+            });
+          });
+        }
+      });
+    });
+  });
+  return completer.future;
+}
+
+main() {
+  test('invalidProxy', testInvalidProxy);
+  test('directProxy', testDirectProxy);
+  test('proxy', testProxy);
+  test('proxyChain', testProxyChain);
+}