Chromium Code Reviews| 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("$ha1:${nonce}:${nc}:${cnonce}:${qop}:$ha2".codeUnits ); | |
|
Anders Johnsen
2013/05/07 13:23:58
Nit: long line.
| |
| 209 } | |
| 210 Expect.equals(CryptoUtils.bytesToHex(hasher.close()), | |
| 211 header.parameters["response"]); | |
| 212 | |
| 213 // Add a bogus Proxy-Authentication-Info for testing. | |
| 214 var info = 'rspauth="77180d1ab3d6c9de084766977790f482", ' | |
| 215 'cnonce="8f971178", ' | |
| 216 'nc=000002c74, ' | |
| 217 'qop=auth'; | |
| 218 request.response.headers.set("Proxy-Authentication-Info", info); | |
| 121 } | 219 } |
| 122 } | 220 } |
| 123 } | 221 } |
| 124 // Open the connection from the proxy. | 222 // Open the connection from the proxy. |
| 125 if (request.method == "CONNECT") { | 223 if (request.method == "CONNECT") { |
| 126 var tmp = request.uri.toString().split(":"); | 224 var tmp = request.uri.toString().split(":"); |
| 127 Socket.connect(tmp[0], int.parse(tmp[1])) | 225 Socket.connect(tmp[0], int.parse(tmp[1])) |
| 128 .then((socket) { | 226 .then((socket) { |
| 129 request.response.reasonPhrase = "Connection established"; | 227 request.response.reasonPhrase = "Connection established"; |
| 130 request.response.detachSocket() | 228 request.response.detachSocket() |
| (...skipping 274 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 405 test(false); | 503 test(false); |
| 406 test(true); | 504 test(true); |
| 407 } | 505 } |
| 408 }); | 506 }); |
| 409 }); | 507 }); |
| 410 }); | 508 }); |
| 411 } | 509 } |
| 412 | 510 |
| 413 | 511 |
| 414 int testProxyAuthenticateCount = 0; | 512 int testProxyAuthenticateCount = 0; |
| 415 void testProxyAuthenticate() { | 513 Future testProxyAuthenticate(bool useDigestAuthentication) { |
| 514 testProxyAuthenticateCount = 0; | |
| 515 var completer = new Completer(); | |
| 516 | |
| 416 setupProxyServer().then((proxyServer) { | 517 setupProxyServer().then((proxyServer) { |
| 417 proxyServer.username = "test"; | |
| 418 proxyServer.password = "test"; | |
| 419 setupServer(1).then((server) { | 518 setupServer(1).then((server) { |
| 420 setupServer(1, secure: true).then((secureServer) { | 519 setupServer(1, secure: true).then((secureServer) { |
| 421 HttpClient client = new HttpClient(); | 520 HttpClient client = new HttpClient(); |
| 422 | 521 |
| 423 Completer step1 = new Completer(); | 522 Completer step1 = new Completer(); |
| 424 Completer step2 = new Completer(); | 523 Completer step2 = new Completer(); |
| 425 | 524 |
| 525 if (useDigestAuthentication) { | |
| 526 proxyServer.useDigestAuthentication("test", "test"); | |
| 527 } else { | |
| 528 proxyServer.useBasicAuthentication("test", "test"); | |
| 529 } | |
| 530 | |
| 426 // Test with no authentication. | 531 // Test with no authentication. |
| 427 client.findProxy = (Uri uri) { | 532 client.findProxy = (Uri uri) { |
| 428 return "PROXY localhost:${proxyServer.port}"; | 533 return "PROXY localhost:${proxyServer.port}"; |
| 429 }; | 534 }; |
| 430 | 535 |
| 431 const int loopCount = 2; | 536 const int loopCount = 2; |
| 432 for (int i = 0; i < loopCount; i++) { | 537 for (int i = 0; i < loopCount; i++) { |
| 433 test(bool secure) { | 538 test(bool secure) { |
| 434 String url = secure | 539 String url = secure |
| 435 ? "https://localhost:${secureServer.port}/$i" | 540 ? "https://localhost:${secureServer.port}/$i" |
| (...skipping 16 matching lines...) Expand all Loading... | |
| 452 step1.complete(null); | 557 step1.complete(null); |
| 453 } | 558 } |
| 454 }); | 559 }); |
| 455 } | 560 } |
| 456 | 561 |
| 457 test(false); | 562 test(false); |
| 458 test(true); | 563 test(true); |
| 459 } | 564 } |
| 460 step1.future.then((_) { | 565 step1.future.then((_) { |
| 461 testProxyAuthenticateCount = 0; | 566 testProxyAuthenticateCount = 0; |
| 462 client.findProxy = (Uri uri) { | 567 if (useDigestAuthentication) { |
| 463 return "PROXY test:test@localhost:${proxyServer.port}"; | 568 client.findProxy = (Uri uri) => "PROXY localhost:${proxyServer.port}"; |
| 464 }; | 569 client.addProxyCredentials( |
| 570 "localhost", | |
| 571 proxyServer.port, | |
| 572 "test", | |
| 573 new HttpClientDigestCredentials("test", "test")); | |
|
Anders Johnsen
2013/05/07 13:23:58
make user and pass not equal (here and other place
| |
| 574 } else { | |
| 575 client.findProxy = (Uri uri) { | |
| 576 return "PROXY test:test@localhost:${proxyServer.port}"; | |
| 577 }; | |
| 578 } | |
| 465 | 579 |
| 466 for (int i = 0; i < loopCount; i++) { | 580 for (int i = 0; i < loopCount; i++) { |
| 467 test(bool secure) { | 581 test(bool secure) { |
| 582 var path = useDigestAuthentication ? "A" : "$i"; | |
| 468 String url = secure | 583 String url = secure |
| 469 ? "https://localhost:${secureServer.port}/$i" | 584 ? "https://localhost:${secureServer.port}/$path" |
| 470 : "http://localhost:${server.port}/$i"; | 585 : "http://localhost:${server.port}/$path"; |
| 471 | 586 |
| 472 client.postUrl(Uri.parse(url)) | 587 client.postUrl(Uri.parse(url)) |
| 473 .then((HttpClientRequest clientRequest) { | 588 .then((HttpClientRequest clientRequest) { |
| 474 String content = "$i$i$i"; | 589 String content = "$i$i$i"; |
| 475 clientRequest.write(content); | 590 clientRequest.write(content); |
| 476 return clientRequest.close(); | 591 return clientRequest.close(); |
| 477 }) | 592 }) |
| 478 .then((HttpClientResponse response) { | 593 .then((HttpClientResponse response) { |
| 479 response.listen((_) {}, onDone: () { | 594 response.listen((_) {}, onDone: () { |
| 480 testProxyAuthenticateCount++; | 595 testProxyAuthenticateCount++; |
| (...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 524 response.listen((_) {}, onDone: () { | 639 response.listen((_) {}, onDone: () { |
| 525 testProxyAuthenticateCount++; | 640 testProxyAuthenticateCount++; |
| 526 Expect.equals(HttpStatus.OK, response.statusCode); | 641 Expect.equals(HttpStatus.OK, response.statusCode); |
| 527 if (testProxyAuthenticateCount == loopCount * 2) { | 642 if (testProxyAuthenticateCount == loopCount * 2) { |
| 528 Expect.equals(loopCount * 2, server.requestCount); | 643 Expect.equals(loopCount * 2, server.requestCount); |
| 529 Expect.equals(loopCount * 2, secureServer.requestCount); | 644 Expect.equals(loopCount * 2, secureServer.requestCount); |
| 530 proxyServer.shutdown(); | 645 proxyServer.shutdown(); |
| 531 server.shutdown(); | 646 server.shutdown(); |
| 532 secureServer.shutdown(); | 647 secureServer.shutdown(); |
| 533 client.close(); | 648 client.close(); |
| 649 completer.complete(null); | |
| 534 } | 650 } |
| 535 }); | 651 }); |
| 536 }); | 652 }); |
| 537 } | 653 } |
| 538 test(false); | 654 test(false); |
| 539 test(true); | 655 test(true); |
| 540 } | 656 } |
| 541 }); | 657 }); |
| 542 | 658 |
| 543 }); | 659 }); |
| 544 }); | 660 }); |
| 545 }); | 661 }); |
| 662 | |
| 663 return completer.future; | |
| 546 } | 664 } |
| 547 | 665 |
| 548 int testRealProxyDoneCount = 0; | 666 int testRealProxyDoneCount = 0; |
| 549 void testRealProxy() { | 667 void testRealProxy() { |
| 550 setupServer(1).then((server) { | 668 setupServer(1).then((server) { |
| 551 HttpClient client = new HttpClient(); | 669 HttpClient client = new HttpClient(); |
| 552 client.addProxyCredentials("localhost", | 670 client.addProxyCredentials("localhost", |
| 553 8080, | 671 8080, |
| 554 "test", | 672 "test", |
| 555 new HttpClientBasicCredentials("test", "test")); | 673 new HttpClientBasicCredentials("test", "test")); |
| (...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 632 password: 'dartdart'); | 750 password: 'dartdart'); |
| 633 } | 751 } |
| 634 | 752 |
| 635 main() { | 753 main() { |
| 636 InitializeSSL(); | 754 InitializeSSL(); |
| 637 testInvalidProxy(); | 755 testInvalidProxy(); |
| 638 testDirectProxy(); | 756 testDirectProxy(); |
| 639 testProxy(); | 757 testProxy(); |
| 640 testProxyChain(); | 758 testProxyChain(); |
| 641 testProxyFromEnviroment(); | 759 testProxyFromEnviroment(); |
| 642 testProxyAuthenticate(); | 760 // The two invocations of uses the same global variable for state - |
| 761 // run one after the other. | |
| 762 testProxyAuthenticate(false) | |
| 763 .then((_) => testProxyAuthenticate(true)); | |
| 643 // This test is not normally run. It can be used for locally testing | 764 // This test is not normally run. It can be used for locally testing |
| 644 // with a real proxy server (e.g. Apache). | 765 // with a real proxy server (e.g. Apache). |
| 645 //testRealProxy(); | 766 //testRealProxy(); |
| 646 //testRealProxyAuth(); | 767 //testRealProxyAuth(); |
| 647 } | 768 } |
| OLD | NEW |