| OLD | NEW |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 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 | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 import "package:expect/expect.dart"; | 5 import "package:expect/expect.dart"; |
| 6 import "dart:async"; | 6 import "dart:async"; |
| 7 import 'dart:crypto'; | 7 import 'dart:crypto'; |
| 8 import "dart:io"; | 8 import "dart:io"; |
| 9 import "dart:uri"; | 9 import "dart:uri"; |
| 10 import 'dart:utf'; | 10 import 'dart:utf'; |
| (...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 73 {List<String> directRequestPaths: const <String>[], | 73 {List<String> directRequestPaths: const <String>[], |
| 74 secure: false}) { | 74 secure: false}) { |
| 75 Server server = new Server(proxyHops, directRequestPaths, secure); | 75 Server server = new Server(proxyHops, directRequestPaths, secure); |
| 76 return server.start(); | 76 return server.start(); |
| 77 } | 77 } |
| 78 | 78 |
| 79 class ProxyServer { | 79 class ProxyServer { |
| 80 HttpServer server; | 80 HttpServer server; |
| 81 HttpClient client; | 81 HttpClient client; |
| 82 int requestCount = 0; | 82 int requestCount = 0; |
| 83 String authScheme; |
| 84 String realm = "test"; |
| 83 String username; | 85 String username; |
| 84 String password; | 86 String password; |
| 85 | 87 |
| 88 var ha1; |
| 89 String serverAlgorithm = "MD5"; |
| 90 String serverQop = "auth"; |
| 91 Set ncs = new Set(); |
| 92 |
| 93 var nonce = "12345678"; // No need for random nonce in test. |
| 94 |
| 86 ProxyServer() : client = new HttpClient(); | 95 ProxyServer() : client = new HttpClient(); |
| 87 | 96 |
| 88 authenticationRequired(request) { | 97 void useBasicAuthentication(String username, String password) { |
| 98 this.username = username; |
| 99 this.password = password; |
| 100 authScheme = "Basic"; |
| 101 } |
| 102 |
| 103 void useDigestAuthentication(String username, String password) { |
| 104 this.username = username; |
| 105 this.password = password; |
| 106 authScheme = "Digest"; |
| 107 |
| 108 // Calculate ha1. |
| 109 var hasher = new MD5(); |
| 110 hasher.add("${username}:${realm}:${password}".codeUnits); |
| 111 ha1 = CryptoUtils.bytesToHex(hasher.close()); |
| 112 } |
| 113 |
| 114 basicAuthenticationRequired(request) { |
| 89 request.fold(null, (x, y) {}).then((_) { | 115 request.fold(null, (x, y) {}).then((_) { |
| 90 var response = request.response; | 116 var response = request.response; |
| 91 response.headers.set(HttpHeaders.PROXY_AUTHENTICATE, | 117 response.headers.set(HttpHeaders.PROXY_AUTHENTICATE, |
| 92 "Basic, realm=realm"); | 118 "Basic, realm=$realm"); |
| 93 response.statusCode = HttpStatus.PROXY_AUTHENTICATION_REQUIRED; | 119 response.statusCode = HttpStatus.PROXY_AUTHENTICATION_REQUIRED; |
| 94 response.close(); | 120 response.close(); |
| 95 }); | 121 }); |
| 96 } | 122 } |
| 97 | 123 |
| 124 digestAuthenticationRequired(request, {stale: false}) { |
| 125 request.fold(null, (x, y) {}).then((_) { |
| 126 var response = request.response; |
| 127 response.statusCode = HttpStatus.PROXY_AUTHENTICATION_REQUIRED; |
| 128 StringBuffer authHeader = new StringBuffer(); |
| 129 authHeader.write('Digest'); |
| 130 authHeader.write(', realm="$realm"'); |
| 131 authHeader.write(', nonce="$nonce"'); |
| 132 if (stale) authHeader.write(', stale="true"'); |
| 133 if (serverAlgorithm != null) { |
| 134 authHeader.write(', algorithm=$serverAlgorithm'); |
| 135 } |
| 136 if (serverQop != null) authHeader.write(', qop="$serverQop"'); |
| 137 response.headers.set(HttpHeaders.PROXY_AUTHENTICATE, authHeader); |
| 138 response.close(); |
| 139 }); |
| 140 } |
| 141 |
| 98 Future<ProxyServer> start() { | 142 Future<ProxyServer> start() { |
| 99 var x = new Completer(); | 143 var x = new Completer(); |
| 100 HttpServer.bind("localhost", 0).then((s) { | 144 HttpServer.bind("localhost", 0).then((s) { |
| 101 server = s; | 145 server = s; |
| 102 x.complete(this); | 146 x.complete(this); |
| 103 server.listen((HttpRequest request) { | 147 server.listen((HttpRequest request) { |
| 104 requestCount++; | 148 requestCount++; |
| 105 if (username != null && password != null) { | 149 if (username != null && password != null) { |
| 106 if (request.headers[HttpHeaders.PROXY_AUTHORIZATION] == null) { | 150 if (request.headers[HttpHeaders.PROXY_AUTHORIZATION] == null) { |
| 107 authenticationRequired(request); | 151 if (authScheme == "Digest") { |
| 152 digestAuthenticationRequired(request); |
| 153 } else { |
| 154 basicAuthenticationRequired(request); |
| 155 } |
| 108 return; | 156 return; |
| 109 } else { | 157 } else { |
| 110 Expect.equals( | 158 Expect.equals( |
| 111 1, request.headers[HttpHeaders.PROXY_AUTHORIZATION].length); | 159 1, request.headers[HttpHeaders.PROXY_AUTHORIZATION].length); |
| 112 String authorization = | 160 String authorization = |
| 113 request.headers[HttpHeaders.PROXY_AUTHORIZATION][0]; | 161 request.headers[HttpHeaders.PROXY_AUTHORIZATION][0]; |
| 114 List<String> tokens = authorization.split(" "); | 162 if (authScheme == "Basic") { |
| 115 Expect.equals("Basic", tokens[0]); | 163 List<String> tokens = authorization.split(" "); |
| 116 String auth = | 164 Expect.equals("Basic", tokens[0]); |
| 117 CryptoUtils.bytesToBase64(encodeUtf8("$username:$password")); | 165 String auth = |
| 118 if (auth != tokens[1]) { | 166 CryptoUtils.bytesToBase64(encodeUtf8("$username:$password")); |
| 119 authenticationRequired(request); | 167 if (auth != tokens[1]) { |
| 120 return; | 168 authenticationRequired(request); |
| 169 return; |
| 170 } |
| 171 } else { |
| 172 HeaderValue header = |
| 173 HeaderValue.parse( |
| 174 authorization, parameterSeparator: ","); |
| 175 Expect.equals("Digest", header.value); |
| 176 var uri = header.parameters["uri"]; |
| 177 var qop = header.parameters["qop"]; |
| 178 var cnonce = header.parameters["cnonce"]; |
| 179 var nc = header.parameters["nc"]; |
| 180 Expect.equals(username, header.parameters["username"]); |
| 181 Expect.equals(realm, header.parameters["realm"]); |
| 182 Expect.equals("MD5", header.parameters["algorithm"]); |
| 183 Expect.equals(nonce, header.parameters["nonce"]); |
| 184 Expect.equals(request.uri.toString(), uri); |
| 185 if (qop != null) { |
| 186 // A server qop of auth-int is downgraded to none by the client. |
| 187 Expect.equals("auth", serverQop); |
| 188 Expect.equals("auth", header.parameters["qop"]); |
| 189 Expect.isNotNull(cnonce); |
| 190 Expect.isNotNull(nc); |
| 191 Expect.isFalse(ncs.contains(nc)); |
| 192 ncs.add(nc); |
| 193 } else { |
| 194 Expect.isNull(cnonce); |
| 195 Expect.isNull(nc); |
| 196 } |
| 197 Expect.isNotNull(header.parameters["response"]); |
| 198 |
| 199 var hasher = new MD5(); |
| 200 hasher.add("${request.method}:${uri}".codeUnits); |
| 201 var ha2 = CryptoUtils.bytesToHex(hasher.close()); |
| 202 |
| 203 var x; |
| 204 hasher = new MD5(); |
| 205 if (qop == null || qop == "" || qop == "none") { |
| 206 hasher.add("$ha1:${nonce}:$ha2".codeUnits); |
| 207 } else { |
| 208 hasher.add( |
| 209 "$ha1:${nonce}:${nc}:${cnonce}:${qop}:$ha2".codeUnits); |
| 210 } |
| 211 Expect.equals(CryptoUtils.bytesToHex(hasher.close()), |
| 212 header.parameters["response"]); |
| 213 |
| 214 // Add a bogus Proxy-Authentication-Info for testing. |
| 215 var info = 'rspauth="77180d1ab3d6c9de084766977790f482", ' |
| 216 'cnonce="8f971178", ' |
| 217 'nc=000002c74, ' |
| 218 'qop=auth'; |
| 219 request.response.headers.set("Proxy-Authentication-Info", info); |
| 121 } | 220 } |
| 122 } | 221 } |
| 123 } | 222 } |
| 124 // Open the connection from the proxy. | 223 // Open the connection from the proxy. |
| 125 if (request.method == "CONNECT") { | 224 if (request.method == "CONNECT") { |
| 126 var tmp = request.uri.toString().split(":"); | 225 var tmp = request.uri.toString().split(":"); |
| 127 Socket.connect(tmp[0], int.parse(tmp[1])) | 226 Socket.connect(tmp[0], int.parse(tmp[1])) |
| 128 .then((socket) { | 227 .then((socket) { |
| 129 request.response.reasonPhrase = "Connection established"; | 228 request.response.reasonPhrase = "Connection established"; |
| 130 request.response.detachSocket() | 229 request.response.detachSocket() |
| (...skipping 274 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 405 test(false); | 504 test(false); |
| 406 test(true); | 505 test(true); |
| 407 } | 506 } |
| 408 }); | 507 }); |
| 409 }); | 508 }); |
| 410 }); | 509 }); |
| 411 } | 510 } |
| 412 | 511 |
| 413 | 512 |
| 414 int testProxyAuthenticateCount = 0; | 513 int testProxyAuthenticateCount = 0; |
| 415 void testProxyAuthenticate() { | 514 Future testProxyAuthenticate(bool useDigestAuthentication) { |
| 515 testProxyAuthenticateCount = 0; |
| 516 var completer = new Completer(); |
| 517 |
| 416 setupProxyServer().then((proxyServer) { | 518 setupProxyServer().then((proxyServer) { |
| 417 proxyServer.username = "test"; | |
| 418 proxyServer.password = "test"; | |
| 419 setupServer(1).then((server) { | 519 setupServer(1).then((server) { |
| 420 setupServer(1, secure: true).then((secureServer) { | 520 setupServer(1, secure: true).then((secureServer) { |
| 421 HttpClient client = new HttpClient(); | 521 HttpClient client = new HttpClient(); |
| 422 | 522 |
| 423 Completer step1 = new Completer(); | 523 Completer step1 = new Completer(); |
| 424 Completer step2 = new Completer(); | 524 Completer step2 = new Completer(); |
| 425 | 525 |
| 526 if (useDigestAuthentication) { |
| 527 proxyServer.useDigestAuthentication("dart", "password"); |
| 528 } else { |
| 529 proxyServer.useBasicAuthentication("dart", "password"); |
| 530 } |
| 531 |
| 426 // Test with no authentication. | 532 // Test with no authentication. |
| 427 client.findProxy = (Uri uri) { | 533 client.findProxy = (Uri uri) { |
| 428 return "PROXY localhost:${proxyServer.port}"; | 534 return "PROXY localhost:${proxyServer.port}"; |
| 429 }; | 535 }; |
| 430 | 536 |
| 431 const int loopCount = 2; | 537 const int loopCount = 2; |
| 432 for (int i = 0; i < loopCount; i++) { | 538 for (int i = 0; i < loopCount; i++) { |
| 433 test(bool secure) { | 539 test(bool secure) { |
| 434 String url = secure | 540 String url = secure |
| 435 ? "https://localhost:${secureServer.port}/$i" | 541 ? "https://localhost:${secureServer.port}/$i" |
| (...skipping 16 matching lines...) Expand all Loading... |
| 452 step1.complete(null); | 558 step1.complete(null); |
| 453 } | 559 } |
| 454 }); | 560 }); |
| 455 } | 561 } |
| 456 | 562 |
| 457 test(false); | 563 test(false); |
| 458 test(true); | 564 test(true); |
| 459 } | 565 } |
| 460 step1.future.then((_) { | 566 step1.future.then((_) { |
| 461 testProxyAuthenticateCount = 0; | 567 testProxyAuthenticateCount = 0; |
| 462 client.findProxy = (Uri uri) { | 568 if (useDigestAuthentication) { |
| 463 return "PROXY test:test@localhost:${proxyServer.port}"; | 569 client.findProxy = (Uri uri) => "PROXY localhost:${proxyServer.port}"; |
| 464 }; | 570 client.addProxyCredentials( |
| 571 "localhost", |
| 572 proxyServer.port, |
| 573 "test", |
| 574 new HttpClientDigestCredentials("dart", "password")); |
| 575 } else { |
| 576 client.findProxy = (Uri uri) { |
| 577 return "PROXY dart:password@localhost:${proxyServer.port}"; |
| 578 }; |
| 579 } |
| 465 | 580 |
| 466 for (int i = 0; i < loopCount; i++) { | 581 for (int i = 0; i < loopCount; i++) { |
| 467 test(bool secure) { | 582 test(bool secure) { |
| 583 var path = useDigestAuthentication ? "A" : "$i"; |
| 468 String url = secure | 584 String url = secure |
| 469 ? "https://localhost:${secureServer.port}/$i" | 585 ? "https://localhost:${secureServer.port}/$path" |
| 470 : "http://localhost:${server.port}/$i"; | 586 : "http://localhost:${server.port}/$path"; |
| 471 | 587 |
| 472 client.postUrl(Uri.parse(url)) | 588 client.postUrl(Uri.parse(url)) |
| 473 .then((HttpClientRequest clientRequest) { | 589 .then((HttpClientRequest clientRequest) { |
| 474 String content = "$i$i$i"; | 590 String content = "$i$i$i"; |
| 475 clientRequest.write(content); | 591 clientRequest.write(content); |
| 476 return clientRequest.close(); | 592 return clientRequest.close(); |
| 477 }) | 593 }) |
| 478 .then((HttpClientResponse response) { | 594 .then((HttpClientResponse response) { |
| 479 response.listen((_) {}, onDone: () { | 595 response.listen((_) {}, onDone: () { |
| 480 testProxyAuthenticateCount++; | 596 testProxyAuthenticateCount++; |
| (...skipping 16 matching lines...) Expand all Loading... |
| 497 testProxyAuthenticateCount = 0; | 613 testProxyAuthenticateCount = 0; |
| 498 client.findProxy = (Uri uri) { | 614 client.findProxy = (Uri uri) { |
| 499 return "PROXY localhost:${proxyServer.port}"; | 615 return "PROXY localhost:${proxyServer.port}"; |
| 500 }; | 616 }; |
| 501 | 617 |
| 502 client.authenticateProxy = (host, port, scheme, realm) { | 618 client.authenticateProxy = (host, port, scheme, realm) { |
| 503 client.addProxyCredentials( | 619 client.addProxyCredentials( |
| 504 "localhost", | 620 "localhost", |
| 505 proxyServer.port, | 621 proxyServer.port, |
| 506 "realm", | 622 "realm", |
| 507 new HttpClientBasicCredentials("test", "test")); | 623 new HttpClientBasicCredentials("dart", "password")); |
| 508 return new Future.value(true); | 624 return new Future.value(true); |
| 509 }; | 625 }; |
| 510 | 626 |
| 511 for (int i = 0; i < loopCount; i++) { | 627 for (int i = 0; i < loopCount; i++) { |
| 512 test(bool secure) { | 628 test(bool secure) { |
| 513 String url = secure | 629 String url = secure |
| 514 ? "https://localhost:${secureServer.port}/A" | 630 ? "https://localhost:${secureServer.port}/A" |
| 515 : "http://localhost:${server.port}/A"; | 631 : "http://localhost:${server.port}/A"; |
| 516 | 632 |
| 517 client.postUrl(Uri.parse(url)) | 633 client.postUrl(Uri.parse(url)) |
| 518 .then((HttpClientRequest clientRequest) { | 634 .then((HttpClientRequest clientRequest) { |
| 519 String content = "$i$i$i"; | 635 String content = "$i$i$i"; |
| 520 clientRequest.write(content); | 636 clientRequest.write(content); |
| 521 return clientRequest.close(); | 637 return clientRequest.close(); |
| 522 }) | 638 }) |
| 523 .then((HttpClientResponse response) { | 639 .then((HttpClientResponse response) { |
| 524 response.listen((_) {}, onDone: () { | 640 response.listen((_) {}, onDone: () { |
| 525 testProxyAuthenticateCount++; | 641 testProxyAuthenticateCount++; |
| 526 Expect.equals(HttpStatus.OK, response.statusCode); | 642 Expect.equals(HttpStatus.OK, response.statusCode); |
| 527 if (testProxyAuthenticateCount == loopCount * 2) { | 643 if (testProxyAuthenticateCount == loopCount * 2) { |
| 528 Expect.equals(loopCount * 2, server.requestCount); | 644 Expect.equals(loopCount * 2, server.requestCount); |
| 529 Expect.equals(loopCount * 2, secureServer.requestCount); | 645 Expect.equals(loopCount * 2, secureServer.requestCount); |
| 530 proxyServer.shutdown(); | 646 proxyServer.shutdown(); |
| 531 server.shutdown(); | 647 server.shutdown(); |
| 532 secureServer.shutdown(); | 648 secureServer.shutdown(); |
| 533 client.close(); | 649 client.close(); |
| 650 completer.complete(null); |
| 534 } | 651 } |
| 535 }); | 652 }); |
| 536 }); | 653 }); |
| 537 } | 654 } |
| 538 test(false); | 655 test(false); |
| 539 test(true); | 656 test(true); |
| 540 } | 657 } |
| 541 }); | 658 }); |
| 542 | 659 |
| 543 }); | 660 }); |
| 544 }); | 661 }); |
| 545 }); | 662 }); |
| 663 |
| 664 return completer.future; |
| 546 } | 665 } |
| 547 | 666 |
| 548 int testRealProxyDoneCount = 0; | 667 int testRealProxyDoneCount = 0; |
| 549 void testRealProxy() { | 668 void testRealProxy() { |
| 550 setupServer(1).then((server) { | 669 setupServer(1).then((server) { |
| 551 HttpClient client = new HttpClient(); | 670 HttpClient client = new HttpClient(); |
| 552 client.addProxyCredentials("localhost", | 671 client.addProxyCredentials( |
| 553 8080, | 672 "localhost", |
| 554 "test", | 673 8080, |
| 555 new HttpClientBasicCredentials("test", "test")); | 674 "test", |
| 675 new HttpClientBasicCredentials("dart", "password")); |
| 556 | 676 |
| 557 List<String> proxy = | 677 List<String> proxy = |
| 558 ["PROXY localhost:8080", | 678 ["PROXY localhost:8080", |
| 559 "PROXY localhost:8080; PROXY hede.hule.hest:8080", | 679 "PROXY localhost:8080; PROXY hede.hule.hest:8080", |
| 560 "PROXY hede.hule.hest:8080; PROXY localhost:8080", | 680 "PROXY hede.hule.hest:8080; PROXY localhost:8080", |
| 561 "PROXY localhost:8080; DIRECT"]; | 681 "PROXY localhost:8080; DIRECT"]; |
| 562 | 682 |
| 563 client.findProxy = (Uri uri) { | 683 client.findProxy = (Uri uri) { |
| 564 // Pick the proxy configuration based on the request path. | 684 // Pick the proxy configuration based on the request path. |
| 565 int index = int.parse(uri.path.substring(1)); | 685 int index = int.parse(uri.path.substring(1)); |
| (...skipping 20 matching lines...) Expand all Loading... |
| 586 } | 706 } |
| 587 }); | 707 }); |
| 588 } | 708 } |
| 589 | 709 |
| 590 int testRealProxyAuthDoneCount = 0; | 710 int testRealProxyAuthDoneCount = 0; |
| 591 void testRealProxyAuth() { | 711 void testRealProxyAuth() { |
| 592 setupServer(1).then((server) { | 712 setupServer(1).then((server) { |
| 593 HttpClient client = new HttpClient(); | 713 HttpClient client = new HttpClient(); |
| 594 | 714 |
| 595 List<String> proxy = | 715 List<String> proxy = |
| 596 ["PROXY test:test@localhost:8080", | 716 ["PROXY dart:password@localhost:8080", |
| 597 "PROXY test:test@localhost:8080; PROXY hede.hule.hest:8080", | 717 "PROXY dart:password@localhost:8080; PROXY hede.hule.hest:8080", |
| 598 "PROXY hede.hule.hest:8080; PROXY test:test@localhost:8080", | 718 "PROXY hede.hule.hest:8080; PROXY dart:password@localhost:8080", |
| 599 "PROXY test:test@localhost:8080; DIRECT"]; | 719 "PROXY dart:password@localhost:8080; DIRECT"]; |
| 600 | 720 |
| 601 client.findProxy = (Uri uri) { | 721 client.findProxy = (Uri uri) { |
| 602 // Pick the proxy configuration based on the request path. | 722 // Pick the proxy configuration based on the request path. |
| 603 int index = int.parse(uri.path.substring(1)); | 723 int index = int.parse(uri.path.substring(1)); |
| 604 return proxy[index]; | 724 return proxy[index]; |
| 605 }; | 725 }; |
| 606 | 726 |
| 607 for (int i = 0; i < proxy.length; i++) { | 727 for (int i = 0; i < proxy.length; i++) { |
| 608 client.getUrl(Uri.parse("http://localhost:${server.port}/$i")) | 728 client.getUrl(Uri.parse("http://localhost:${server.port}/$i")) |
| 609 .then((HttpClientRequest clientRequest) { | 729 .then((HttpClientRequest clientRequest) { |
| (...skipping 22 matching lines...) Expand all Loading... |
| 632 password: 'dartdart'); | 752 password: 'dartdart'); |
| 633 } | 753 } |
| 634 | 754 |
| 635 main() { | 755 main() { |
| 636 InitializeSSL(); | 756 InitializeSSL(); |
| 637 testInvalidProxy(); | 757 testInvalidProxy(); |
| 638 testDirectProxy(); | 758 testDirectProxy(); |
| 639 testProxy(); | 759 testProxy(); |
| 640 testProxyChain(); | 760 testProxyChain(); |
| 641 testProxyFromEnviroment(); | 761 testProxyFromEnviroment(); |
| 642 testProxyAuthenticate(); | 762 // The two invocations of uses the same global variable for state - |
| 763 // run one after the other. |
| 764 testProxyAuthenticate(false) |
| 765 .then((_) => testProxyAuthenticate(true)); |
| 643 // This test is not normally run. It can be used for locally testing | 766 // This test is not normally run. It can be used for locally testing |
| 644 // with a real proxy server (e.g. Apache). | 767 // with a real proxy server (e.g. Apache). |
| 645 //testRealProxy(); | 768 //testRealProxy(); |
| 646 //testRealProxyAuth(); | 769 //testRealProxyAuth(); |
| 647 } | 770 } |
| OLD | NEW |