|  | // Copyright (c) 2013, 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. | 
|  | // | 
|  | // VMOptions= | 
|  | // VMOptions=--short_socket_read | 
|  | // VMOptions=--short_socket_write | 
|  | // VMOptions=--short_socket_read --short_socket_write | 
|  | // | 
|  | // OtherResources=certificates/server_chain.pem | 
|  | // OtherResources=certificates/server_key.pem | 
|  | // OtherResources=certificates/trusted_certs.pem | 
|  |  | 
|  | // @dart = 2.9 | 
|  |  | 
|  | import "dart:async"; | 
|  | import "dart:convert"; | 
|  | import "dart:io"; | 
|  | import "dart:typed_data"; | 
|  |  | 
|  | import "package:async_helper/async_helper.dart"; | 
|  | import "package:crypto/crypto.dart"; | 
|  | import "package:expect/expect.dart"; | 
|  |  | 
|  | const WEB_SOCKET_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; | 
|  |  | 
|  | const String HOST_NAME = 'localhost'; | 
|  |  | 
|  | String localFile(path) => Platform.script.resolve(path).toFilePath(); | 
|  |  | 
|  | SecurityContext serverContext = new SecurityContext() | 
|  | ..useCertificateChain(localFile('certificates/server_chain.pem')) | 
|  | ..usePrivateKey(localFile('certificates/server_key.pem'), | 
|  | password: 'dartdart'); | 
|  |  | 
|  | SecurityContext clientContext = new SecurityContext() | 
|  | ..setTrustedCertificates(localFile('certificates/trusted_certs.pem')); | 
|  |  | 
|  | /** | 
|  | * A SecurityConfiguration lets us run the tests over HTTP or HTTPS. | 
|  | */ | 
|  | class SecurityConfiguration { | 
|  | final bool secure; | 
|  |  | 
|  | SecurityConfiguration({bool this.secure}); | 
|  |  | 
|  | Future<HttpServer> createServer({int backlog: 0}) => secure | 
|  | ? HttpServer.bindSecure(HOST_NAME, 0, serverContext, backlog: backlog) | 
|  | : HttpServer.bind(HOST_NAME, 0, backlog: backlog); | 
|  |  | 
|  | Future<WebSocket> createClient(int port, | 
|  | {String user, Map<String, Object> headers, String customUserAgent}) => | 
|  | WebSocket.connect( | 
|  | '${secure ? "wss" : "ws"}://${user is Null ? "" : "$user@"}$HOST_NAME:$port/', | 
|  | headers: headers, | 
|  | customClient: secure | 
|  | ? (HttpClient(context: clientContext) | 
|  | ..userAgent = customUserAgent) | 
|  | : null); | 
|  |  | 
|  | checkCloseStatus(webSocket, closeStatus, closeReason) { | 
|  | Expect.equals( | 
|  | closeStatus == null ? WebSocketStatus.noStatusReceived : closeStatus, | 
|  | webSocket.closeCode); | 
|  | Expect.equals( | 
|  | closeReason == null ? "" : closeReason, webSocket.closeReason); | 
|  | } | 
|  |  | 
|  | void testRequestResponseClientCloses(int totalConnections, int closeStatus, | 
|  | String closeReason, int numberOfMessages) { | 
|  | assert(numberOfMessages >= 1); | 
|  |  | 
|  | asyncStart(); | 
|  | createServer().then((server) { | 
|  | server.transform(new WebSocketTransformer()).listen((webSocket) { | 
|  | asyncStart(); | 
|  | webSocket.listen(webSocket.add, onDone: () { | 
|  | checkCloseStatus(webSocket, closeStatus, closeReason); | 
|  | asyncEnd(); | 
|  | }); | 
|  | }, onDone: () { | 
|  | asyncEnd(); | 
|  | }); | 
|  |  | 
|  | int closeCount = 0; | 
|  | String messageText = "Hello, world!"; | 
|  | for (int i = 0; i < totalConnections; i++) { | 
|  | asyncStart(); | 
|  | createClient(server.port).then((webSocket) { | 
|  | webSocket.add(messageText); | 
|  | webSocket.listen((message) { | 
|  | numberOfMessages--; | 
|  | Expect.equals(messageText, message); | 
|  |  | 
|  | if (numberOfMessages > 0) { | 
|  | webSocket.add(message); | 
|  | } else { | 
|  | webSocket.close(closeStatus, closeReason); | 
|  | } | 
|  | }, onDone: () { | 
|  | checkCloseStatus(webSocket, closeStatus, closeReason); | 
|  | closeCount++; | 
|  | if (closeCount == totalConnections) { | 
|  | server.close(); | 
|  | } | 
|  | asyncEnd(); | 
|  | }); | 
|  | }); | 
|  | } | 
|  | }); | 
|  | } | 
|  |  | 
|  | void testRequestResponseServerCloses( | 
|  | int totalConnections, int closeStatus, String closeReason) { | 
|  | createServer().then((server) { | 
|  | int closeCount = 0; | 
|  | server.transform(new WebSocketTransformer()).listen((webSocket) { | 
|  | String messageText = "Hello, world!"; | 
|  | int messageCount = 0; | 
|  | webSocket.listen((message) { | 
|  | messageCount++; | 
|  | if (messageCount < 10) { | 
|  | Expect.equals(messageText, message); | 
|  | webSocket.add(message); | 
|  | } else { | 
|  | webSocket.close(closeStatus, closeReason); | 
|  | } | 
|  | }, onDone: () { | 
|  | checkCloseStatus(webSocket, closeStatus, closeReason); | 
|  | closeCount++; | 
|  | if (closeCount == totalConnections) { | 
|  | server.close(); | 
|  | } | 
|  | }); | 
|  | webSocket.add(messageText); | 
|  | }); | 
|  |  | 
|  | for (int i = 0; i < totalConnections; i++) { | 
|  | createClient(server.port).then((webSocket) { | 
|  | webSocket.listen(webSocket.add, onDone: () { | 
|  | checkCloseStatus(webSocket, closeStatus, closeReason); | 
|  | }); | 
|  | }); | 
|  | } | 
|  | }); | 
|  | } | 
|  |  | 
|  | void testMessageLength(int messageLength) { | 
|  | createServer().then((server) { | 
|  | Uint8List originalMessage = new Uint8List(messageLength); | 
|  | server.transform(new WebSocketTransformer()).listen((webSocket) { | 
|  | webSocket.listen((message) { | 
|  | Expect.listEquals(originalMessage, message); | 
|  | webSocket.add(message); | 
|  | }); | 
|  | }); | 
|  |  | 
|  | createClient(server.port).then((webSocket) { | 
|  | webSocket.listen((message) { | 
|  | Expect.listEquals(originalMessage, message); | 
|  | webSocket.close(); | 
|  | }, onDone: server.close); | 
|  | webSocket.add(originalMessage); | 
|  | }); | 
|  | }); | 
|  | } | 
|  |  | 
|  | void testCloseNoListen() { | 
|  | createServer().then((server) { | 
|  | server.transform(new WebSocketTransformer()).listen((webSocket) { | 
|  | server.close(); | 
|  | webSocket.close(); | 
|  | }); | 
|  |  | 
|  | createClient(server.port).then((webSocket) { | 
|  | webSocket.close(); | 
|  | }); | 
|  | }); | 
|  | } | 
|  |  | 
|  | void testCancelThenClose() { | 
|  | createServer().then((server) { | 
|  | server.transform(new WebSocketTransformer()).listen((webSocket) { | 
|  | webSocket.listen(null).cancel(); | 
|  | webSocket.close(); | 
|  | server.close(); | 
|  | }); | 
|  |  | 
|  | createClient(server.port).then((webSocket) { | 
|  | webSocket.close(); | 
|  | }); | 
|  | }); | 
|  | } | 
|  |  | 
|  | void testCloseThenCancel() { | 
|  | createServer().then((server) { | 
|  | server.transform(new WebSocketTransformer()).listen((webSocket) { | 
|  | var subscription = webSocket.listen(null); | 
|  | webSocket.close(); | 
|  | subscription.cancel(); | 
|  | server.close(); | 
|  | }); | 
|  |  | 
|  | createClient(server.port).then((webSocket) { | 
|  | webSocket.close(); | 
|  | }); | 
|  | }); | 
|  | } | 
|  |  | 
|  | void testListenAfterClose() { | 
|  | createServer().then((server) { | 
|  | server.transform(new WebSocketTransformer()).listen((webSocket) { | 
|  | server.close(); | 
|  | webSocket.close(); | 
|  | Expect.throws(() => webSocket.drain()); | 
|  | }); | 
|  |  | 
|  | createClient(server.port).then((webSocket) { | 
|  | webSocket.close(); | 
|  | Expect.throws(() => webSocket.drain()); | 
|  | }); | 
|  | }); | 
|  | } | 
|  |  | 
|  | void testDoubleCloseClient() { | 
|  | createServer().then((server) { | 
|  | server.transform(new WebSocketTransformer()).listen((webSocket) { | 
|  | server.close(); | 
|  | webSocket.listen((_) {}, onDone: webSocket.close); | 
|  | }); | 
|  |  | 
|  | createClient(server.port).then((webSocket) { | 
|  | webSocket.listen((_) {}, onDone: webSocket.close); | 
|  | webSocket.close(); | 
|  | }); | 
|  | }); | 
|  | } | 
|  |  | 
|  | void testDoubleCloseServer() { | 
|  | createServer().then((server) { | 
|  | server.transform(new WebSocketTransformer()).listen((webSocket) { | 
|  | server.close(); | 
|  | webSocket.listen((_) {}, onDone: webSocket.close); | 
|  | webSocket.close(); | 
|  | }); | 
|  |  | 
|  | createClient(server.port).then((webSocket) { | 
|  | webSocket.listen((_) {}, onDone: webSocket.close); | 
|  | }); | 
|  | }); | 
|  | } | 
|  |  | 
|  | void testImmediateCloseServer() { | 
|  | createServer().then((server) { | 
|  | server.listen((request) { | 
|  | WebSocketTransformer.upgrade(request).then((webSocket) { | 
|  | webSocket.listen((_) { | 
|  | Expect.fail("Unexpected message"); | 
|  | }, onDone: server.close); | 
|  | webSocket.close(); | 
|  | }); | 
|  | }); | 
|  |  | 
|  | createClient(server.port).then((webSocket) { | 
|  | webSocket.listen((_) { | 
|  | Expect.fail("Unexpected message"); | 
|  | }, onDone: webSocket.close); | 
|  | }); | 
|  | }); | 
|  | } | 
|  |  | 
|  | void testImmediateCloseClient() { | 
|  | createServer().then((server) { | 
|  | server.listen((request) { | 
|  | WebSocketTransformer.upgrade(request).then((webSocket) { | 
|  | webSocket.listen((_) { | 
|  | Expect.fail("Unexpected message"); | 
|  | }, onDone: () { | 
|  | server.close(); | 
|  | webSocket.close(); | 
|  | }); | 
|  | }); | 
|  | }); | 
|  |  | 
|  | createClient(server.port).then((webSocket) { | 
|  | webSocket.listen((_) { | 
|  | Expect.fail("Unexpected message"); | 
|  | }, onDone: webSocket.close); | 
|  | webSocket.close(); | 
|  | }); | 
|  | }); | 
|  | } | 
|  |  | 
|  | void testNoUpgrade() { | 
|  | createServer().then((server) { | 
|  | // Create a server which always responds with notFound. | 
|  | server.listen((request) { | 
|  | request.response.statusCode = HttpStatus.notFound; | 
|  | request.response.close(); | 
|  | }); | 
|  |  | 
|  | createClient(server.port).catchError((error) { | 
|  | server.close(); | 
|  | }); | 
|  | }); | 
|  | } | 
|  |  | 
|  | void testUsePOST() { | 
|  | asyncStart(); | 
|  | createServer().then((server) { | 
|  | server.transform(new WebSocketTransformer()).listen((webSocket) { | 
|  | Expect.fail("No connection expected"); | 
|  | }, onError: (e) { | 
|  | asyncEnd(); | 
|  | }); | 
|  |  | 
|  | final client = HttpClient(context: secure ? clientContext : null); | 
|  | client | 
|  | .postUrl(Uri.parse( | 
|  | "${secure ? 'https:' : 'http:'}//$HOST_NAME:${server.port}/")) | 
|  | .then((request) => request.close()) | 
|  | .then((response) { | 
|  | Expect.equals(HttpStatus.badRequest, response.statusCode); | 
|  | client.close(); | 
|  | server.close(); | 
|  | }); | 
|  | }); | 
|  | } | 
|  |  | 
|  | void testConnections( | 
|  | int totalConnections, int closeStatus, String closeReason) { | 
|  | createServer().then((server) { | 
|  | int closeCount = 0; | 
|  | server.transform(new WebSocketTransformer()).listen((webSocket) { | 
|  | String messageText = "Hello, world!"; | 
|  | int messageCount = 0; | 
|  | webSocket.listen((message) { | 
|  | messageCount++; | 
|  | if (messageCount < 10) { | 
|  | Expect.equals(messageText, message); | 
|  | webSocket.add(message); | 
|  | } else { | 
|  | webSocket.close(closeStatus, closeReason); | 
|  | } | 
|  | }, onDone: () { | 
|  | checkCloseStatus(webSocket, closeStatus, closeReason); | 
|  | closeCount++; | 
|  | if (closeCount == totalConnections) { | 
|  | server.close(); | 
|  | } | 
|  | }); | 
|  | webSocket.add(messageText); | 
|  | }); | 
|  |  | 
|  | void webSocketConnection() { | 
|  | bool onopenCalled = false; | 
|  | int onmessageCalled = 0; | 
|  | bool oncloseCalled = false; | 
|  |  | 
|  | createClient(server.port).then((webSocket) { | 
|  | Expect.isFalse(onopenCalled); | 
|  | Expect.equals(0, onmessageCalled); | 
|  | Expect.isFalse(oncloseCalled); | 
|  | onopenCalled = true; | 
|  | Expect.equals(WebSocket.open, webSocket.readyState); | 
|  | webSocket.listen((message) { | 
|  | onmessageCalled++; | 
|  | Expect.isTrue(onopenCalled); | 
|  | Expect.isFalse(oncloseCalled); | 
|  | Expect.equals(WebSocket.open, webSocket.readyState); | 
|  | webSocket.add(message); | 
|  | }, onDone: () { | 
|  | Expect.isTrue(onopenCalled); | 
|  | Expect.equals(10, onmessageCalled); | 
|  | Expect.isFalse(oncloseCalled); | 
|  | oncloseCalled = true; | 
|  | Expect.equals(3002, webSocket.closeCode); | 
|  | Expect.equals("Got tired", webSocket.closeReason); | 
|  | Expect.equals(WebSocket.closed, webSocket.readyState); | 
|  | }); | 
|  | }); | 
|  | } | 
|  |  | 
|  | for (int i = 0; i < totalConnections; i++) { | 
|  | webSocketConnection(); | 
|  | } | 
|  | }); | 
|  | } | 
|  |  | 
|  | testIndividualUpgrade(int connections) { | 
|  | asyncStart(); | 
|  | createServer().then((server) { | 
|  | server.listen((request) { | 
|  | if (WebSocketTransformer.isUpgradeRequest(request)) { | 
|  | WebSocketTransformer.upgrade(request).then((webSocket) { | 
|  | webSocket.listen((_) { | 
|  | webSocket.close(); | 
|  | }); | 
|  | webSocket.add("Hello"); | 
|  | }); | 
|  | } else { | 
|  | Expect.isFalse(WebSocketTransformer.isUpgradeRequest(request)); | 
|  | request.response.statusCode = HttpStatus.ok; | 
|  | request.response.close(); | 
|  | } | 
|  | }); | 
|  |  | 
|  | var futures = <Future>[]; | 
|  |  | 
|  | var wsProtocol = '${secure ? "wss" : "ws"}'; | 
|  | var baseWsUrl = '$wsProtocol://$HOST_NAME:${server.port}/'; | 
|  | var httpProtocol = '${secure ? "https" : "http"}'; | 
|  | var baseHttpUrl = '$httpProtocol://$HOST_NAME:${server.port}/'; | 
|  | final client = HttpClient(context: secure ? clientContext : null); | 
|  |  | 
|  | for (int i = 0; i < connections; i++) { | 
|  | var completer = new Completer(); | 
|  | futures.add(completer.future); | 
|  | createClient(server.port).then((websocket) { | 
|  | websocket.listen((_) { | 
|  | websocket.close(); | 
|  | }, onDone: completer.complete); | 
|  | }); | 
|  |  | 
|  | futures.add(client | 
|  | .openUrl("GET", Uri.parse('${baseHttpUrl}')) | 
|  | .then((request) => request.close()) | 
|  | .then((response) { | 
|  | response.listen((_) {}); | 
|  | Expect.equals(HttpStatus.ok, response.statusCode); | 
|  | })); | 
|  | } | 
|  |  | 
|  | Future.wait(futures).then((_) { | 
|  | server.close(); | 
|  | client.close(); | 
|  | asyncEnd(); | 
|  | }); | 
|  | }); | 
|  | } | 
|  |  | 
|  | testFromUpgradedSocket() { | 
|  | asyncStart(); | 
|  | createServer().then((server) { | 
|  | server.listen((request) { | 
|  | Expect.equals( | 
|  | 'Upgrade', request.headers.value(HttpHeaders.connectionHeader)); | 
|  | Expect.equals( | 
|  | 'websocket', request.headers.value(HttpHeaders.upgradeHeader)); | 
|  |  | 
|  | var key = request.headers.value('Sec-WebSocket-Key'); | 
|  | var digest = sha1.convert("$key$WEB_SOCKET_GUID".codeUnits); | 
|  | var accept = base64.encode(digest.bytes); | 
|  | request.response | 
|  | ..statusCode = HttpStatus.switchingProtocols | 
|  | ..headers.add(HttpHeaders.connectionHeader, "Upgrade") | 
|  | ..headers.add(HttpHeaders.upgradeHeader, "websocket") | 
|  | ..headers.add("Sec-WebSocket-Accept", accept); | 
|  | request.response.contentLength = 0; | 
|  | request.response.detachSocket().then((socket) { | 
|  | return new WebSocket.fromUpgradedSocket(socket, serverSide: true); | 
|  | }).then((websocket) { | 
|  | websocket.add("Hello"); | 
|  | websocket.close(); | 
|  | asyncEnd(); | 
|  | }); | 
|  | }); | 
|  |  | 
|  | createClient(server.port).then((websocket) { | 
|  | return websocket.listen((message) { | 
|  | Expect.equals("Hello", message); | 
|  | websocket.close(); | 
|  | }).asFuture(); | 
|  | }).then((_) => server.close()); | 
|  | }); | 
|  | } | 
|  |  | 
|  | void testAdditionalHeaders() { | 
|  | asyncStart(); | 
|  | createServer().then((server) { | 
|  | server.listen((request) { | 
|  | Expect.isTrue(WebSocketTransformer.isUpgradeRequest(request)); | 
|  | Expect.equals('my-value', request.headers['My-Header'][0]); | 
|  | var header = request.headers['My-Header-Multiple']; | 
|  | Expect.equals(1, header.length); | 
|  | Expect.equals('my-value-1, my-value-2', header[0]); | 
|  | WebSocketTransformer.upgrade(request).then((webSocket) { | 
|  | webSocket.listen((_) { | 
|  | webSocket.close(); | 
|  | }); | 
|  | webSocket.add("Hello"); | 
|  | }); | 
|  | }); | 
|  |  | 
|  | var headers = { | 
|  | 'My-Header': 'my-value', | 
|  | 'My-Header-Multiple': ['my-value-1', 'my-value-2'] | 
|  | }; | 
|  | createClient(server.port, headers: headers).then((websocket) { | 
|  | return websocket.listen((message) { | 
|  | Expect.equals("Hello", message); | 
|  | websocket.close(); | 
|  | }).asFuture(); | 
|  | }).then((_) { | 
|  | server.close(); | 
|  | asyncEnd(); | 
|  | }); | 
|  | }); | 
|  | } | 
|  |  | 
|  | void testBasicAuthentication() { | 
|  | var userInfo = 'user:password'; | 
|  |  | 
|  | asyncStart(); | 
|  | asyncStart(); | 
|  | createServer().then((server) { | 
|  | server.listen((request) { | 
|  | Expect.isTrue(WebSocketTransformer.isUpgradeRequest(request)); | 
|  | String auth = base64.encode(utf8.encode(userInfo)); | 
|  | Expect.equals('Basic $auth', request.headers['Authorization'][0]); | 
|  | Expect.equals(1, request.headers['Authorization'].length); | 
|  | WebSocketTransformer.upgrade(request).then((webSocket) { | 
|  | webSocket.listen((_) { | 
|  | throw 'Unexpected'; | 
|  | }, onDone: () { | 
|  | asyncEnd(); | 
|  | }); | 
|  | webSocket.add("Hello"); | 
|  | }); | 
|  | }); | 
|  |  | 
|  | createClient(server.port, user: userInfo).then((websocket) { | 
|  | return websocket.listen((message) { | 
|  | Expect.equals("Hello", message); | 
|  | websocket.close(); | 
|  | }).asFuture(); | 
|  | }).then((_) { | 
|  | return server.close(); | 
|  | }).whenComplete(() { | 
|  | asyncEnd(); | 
|  | }); | 
|  | }); | 
|  | } | 
|  |  | 
|  | void testShouldSetUserAgent() { | 
|  | asyncStart(); | 
|  | createServer().then((server) { | 
|  | server.transform(new WebSocketTransformer()).listen((webSocket) { | 
|  | Expect.equals('Custom User Agent', WebSocket.userAgent); | 
|  | server.close(); | 
|  | webSocket.close(); | 
|  | asyncEnd(); | 
|  | }); | 
|  |  | 
|  | WebSocket.userAgent = 'Custom User Agent'; | 
|  | createClient(server.port).then((webSocket) { | 
|  | webSocket.close(); | 
|  | }); | 
|  | }); | 
|  | } | 
|  |  | 
|  | void testStaticClientUserAgentStaysTheSame() { | 
|  | asyncStart(); | 
|  | createServer().then((server) { | 
|  | server.transform(new WebSocketTransformer()).listen((webSocket) { | 
|  | Expect.equals('Custom User Agent', WebSocket.userAgent); | 
|  | server.close(); | 
|  | webSocket.close(); | 
|  | asyncEnd(); | 
|  | }); | 
|  | // Next line should take no effect on custom user agent value provided | 
|  | WebSocket.userAgent = 'Custom User Agent'; | 
|  | createClient(server.port, customUserAgent: 'New User Agent') | 
|  | .then((webSocket) { | 
|  | webSocket.close(); | 
|  | }); | 
|  | }); | 
|  | } | 
|  |  | 
|  | void runTests() { | 
|  | testRequestResponseClientCloses(2, null, null, 1); | 
|  | testRequestResponseClientCloses(2, 3001, null, 2); | 
|  | testRequestResponseClientCloses(2, 3002, "Got tired", 3); | 
|  | testRequestResponseServerCloses(2, null, null); | 
|  | testRequestResponseServerCloses(2, 3001, null); | 
|  | testRequestResponseServerCloses(2, 3002, "Got tired"); | 
|  | testMessageLength(125); | 
|  | testMessageLength(126); | 
|  | testMessageLength(127); | 
|  | testMessageLength(65535); | 
|  | testMessageLength(65536); | 
|  | testCloseNoListen(); | 
|  | testCancelThenClose(); | 
|  | testCloseThenCancel(); | 
|  | testListenAfterClose(); | 
|  | testDoubleCloseClient(); | 
|  | testDoubleCloseServer(); | 
|  | testImmediateCloseServer(); | 
|  | testImmediateCloseClient(); | 
|  | testNoUpgrade(); | 
|  | testUsePOST(); | 
|  | testConnections(10, 3002, "Got tired"); | 
|  | testIndividualUpgrade(5); | 
|  | testFromUpgradedSocket(); | 
|  | testAdditionalHeaders(); | 
|  | testBasicAuthentication(); | 
|  | testShouldSetUserAgent(); | 
|  | testStaticClientUserAgentStaysTheSame(); | 
|  | } | 
|  | } | 
|  |  | 
|  | main() { | 
|  | new SecurityConfiguration(secure: false).runTests(); | 
|  | new SecurityConfiguration(secure: true).runTests(); | 
|  | } |