| OLD | NEW |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2012, 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 part of dart.io; | 5 part of dart.io; |
| 6 | 6 |
| 7 const String _webSocketGUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; | 7 const String _webSocketGUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; |
| 8 | 8 |
| 9 class _WebSocketMessageType { | 9 class _WebSocketMessageType { |
| 10 static const int NONE = 0; | 10 static const int NONE = 0; |
| 11 static const int BINARY = 1; | 11 static const int BINARY = 1; |
| (...skipping 23 matching lines...) Expand all Loading... |
| 35 /** | 35 /** |
| 36 * The web socket protocol processor handles the protocol byte stream | 36 * The web socket protocol processor handles the protocol byte stream |
| 37 * which is supplied through the [:update:] and [:closed:] | 37 * which is supplied through the [:update:] and [:closed:] |
| 38 * methods. As the protocol is processed the following callbacks are | 38 * methods. As the protocol is processed the following callbacks are |
| 39 * called: | 39 * called: |
| 40 * | 40 * |
| 41 * [:onMessageStart:] | 41 * [:onMessageStart:] |
| 42 * [:onMessageData:] | 42 * [:onMessageData:] |
| 43 * [:onMessageEnd:] | 43 * [:onMessageEnd:] |
| 44 * [:onClosed:] | 44 * [:onClosed:] |
| 45 * [:onError:] | |
| 46 * | 45 * |
| 47 */ | 46 */ |
| 48 class _WebSocketProtocolProcessor { | 47 class _WebSocketProtocolProcessor { |
| 49 static const int START = 0; | 48 static const int START = 0; |
| 50 static const int LEN_FIRST = 1; | 49 static const int LEN_FIRST = 1; |
| 51 static const int LEN_REST = 2; | 50 static const int LEN_REST = 2; |
| 52 static const int MASK = 3; | 51 static const int MASK = 3; |
| 53 static const int PAYLOAD = 4; | 52 static const int PAYLOAD = 4; |
| 54 static const int CLOSED = 5; | 53 static const int CLOSED = 5; |
| 55 static const int FAILURE = 6; | 54 static const int FAILURE = 6; |
| 56 | 55 |
| 57 _WebSocketProtocolProcessor() { | 56 _WebSocketProtocolProcessor() { |
| 58 _prepareForNextFrame(); | 57 _prepareForNextFrame(); |
| 59 _currentMessageType = _WebSocketMessageType.NONE; | 58 _currentMessageType = _WebSocketMessageType.NONE; |
| 60 } | 59 } |
| 61 | 60 |
| 62 /** | 61 /** |
| 63 * Process data received from the underlying communication channel. | 62 * Process data received from the underlying communication channel. |
| 64 */ | 63 */ |
| 65 void update(List<int> buffer) { | 64 void update(List<int> buffer, int offset, int count) { |
| 66 int index = 0; | 65 int index = offset; |
| 67 int lastIndex = buffer.length; | 66 int lastIndex = offset + count; |
| 68 try { | 67 try { |
| 69 if (_state == CLOSED) { | 68 if (_state == CLOSED) { |
| 70 throw new WebSocketException("Data on closed connection"); | 69 throw new WebSocketException("Data on closed connection"); |
| 71 } | 70 } |
| 72 if (_state == FAILURE) { | 71 if (_state == FAILURE) { |
| 73 throw new WebSocketException("Data on failed connection"); | 72 throw new WebSocketException("Data on failed connection"); |
| 74 } | 73 } |
| 75 while ((index < lastIndex) && _state != CLOSED && _state != FAILURE) { | 74 while ((index < lastIndex) && _state != CLOSED && _state != FAILURE) { |
| 76 int byte = buffer[index]; | 75 int byte = buffer[index]; |
| 77 switch (_state) { | 76 switch (_state) { |
| (...skipping 211 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 289 String reason = ""; | 288 String reason = ""; |
| 290 if (_controlPayload.length > 0) { | 289 if (_controlPayload.length > 0) { |
| 291 if (_controlPayload.length == 1) { | 290 if (_controlPayload.length == 1) { |
| 292 throw new WebSocketException("Protocol error"); | 291 throw new WebSocketException("Protocol error"); |
| 293 } | 292 } |
| 294 status = _controlPayload[0] << 8 | _controlPayload[1]; | 293 status = _controlPayload[0] << 8 | _controlPayload[1]; |
| 295 if (status == WebSocketStatus.NO_STATUS_RECEIVED) { | 294 if (status == WebSocketStatus.NO_STATUS_RECEIVED) { |
| 296 throw new WebSocketException("Protocol error"); | 295 throw new WebSocketException("Protocol error"); |
| 297 } | 296 } |
| 298 if (_controlPayload.length > 2) { | 297 if (_controlPayload.length > 2) { |
| 299 var decoder = _StringDecoders.decoder(Encoding.UTF_8); | 298 reason = _decodeString( |
| 300 decoder.write( | |
| 301 _controlPayload.getRange(2, _controlPayload.length - 2)); | 299 _controlPayload.getRange(2, _controlPayload.length - 2)); |
| 302 reason = decoder.decoded(); | |
| 303 } | 300 } |
| 304 } | 301 } |
| 305 if (onClosed != null) onClosed(status, reason); | 302 if (onClosed != null) onClosed(status, reason); |
| 306 _state = CLOSED; | 303 _state = CLOSED; |
| 307 break; | 304 break; |
| 308 | 305 |
| 309 case _WebSocketOpcode.PING: | 306 case _WebSocketOpcode.PING: |
| 310 if (onPing != null) onPing(_controlPayload); | 307 if (onPing != null) onPing(_controlPayload); |
| 311 break; | 308 break; |
| 312 | 309 |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 353 | 350 |
| 354 Function onMessageStart; | 351 Function onMessageStart; |
| 355 Function onMessageData; | 352 Function onMessageData; |
| 356 Function onMessageEnd; | 353 Function onMessageEnd; |
| 357 Function onPing; | 354 Function onPing; |
| 358 Function onPong; | 355 Function onPong; |
| 359 Function onClosed; | 356 Function onClosed; |
| 360 } | 357 } |
| 361 | 358 |
| 362 | 359 |
| 363 class _WebSocketConnectionBase { | 360 class _WebSocketTransformerImpl implements WebSocketTransformer { |
| 364 void _socketConnected(Socket socket) { | 361 final StreamController<WebSocket> _controller = |
| 365 _socket = socket; | 362 new StreamController<WebSocket>(); |
| 366 _socket.onError = (e) => _socket.close(); | 363 |
| 367 } | 364 Stream<WebSocket> bind(Stream<HttpRequest> stream) { |
| 368 | 365 stream.listen((request) { |
| 369 void _startProcessing(List<int> unparsedData) { | 366 var response = request.response; |
| 370 _WebSocketProtocolProcessor processor = new _WebSocketProtocolProcessor(); | 367 if (!_isWebSocketUpgrade(request)) { |
| 371 processor.onMessageStart = _onWebSocketMessageStart; | 368 _controller.signalError( |
| 372 processor.onMessageData = _onWebSocketMessageData; | 369 new AsyncError( |
| 373 processor.onMessageEnd = _onWebSocketMessageEnd; | 370 new WebSocketException("Invalid WebSocket upgrade request"))); |
| 374 processor.onPing = _onWebSocketPing; | 371 request.listen((_) {}, onDone: () { |
| 375 processor.onPong = _onWebSocketPong; | 372 response.statusCode = HttpStatus.BAD_REQUEST; |
| 376 processor.onClosed = _onWebSocketClosed; | 373 response.contentLength = 0; |
| 377 if (unparsedData != null) { | 374 response.close(); |
| 378 processor.update(unparsedData); | 375 }); |
| 379 } | 376 return; |
| 380 _socket.onData = () { | 377 } |
| 381 processor.update(_socket.read()); | 378 // Send the upgrade response. |
| 382 }; | 379 response.statusCode = HttpStatus.SWITCHING_PROTOCOLS; |
| 383 _socket.onClosed = () { | 380 response.headers.add(HttpHeaders.CONNECTION, "Upgrade"); |
| 384 processor.closed(); | 381 response.headers.add(HttpHeaders.UPGRADE, "websocket"); |
| 385 if (_closeSent) { | 382 String key = request.headers.value("Sec-WebSocket-Key"); |
| 386 // Got socket close in response to close frame. Don't treat | 383 SHA1 sha1 = new SHA1(); |
| 387 // that as an error. | 384 sha1.add("$key$_webSocketGUID".charCodes); |
| 388 if (_closeTimer != null) _closeTimer.cancel(); | 385 String accept = _Base64._encode(sha1.close()); |
| 389 } else { | 386 response.headers.add("Sec-WebSocket-Accept", accept); |
| 390 if (_onClosed != null) _onClosed(WebSocketStatus.ABNORMAL_CLOSURE, | 387 response.headers.contentLength = 0; |
| 391 "Unexpected close"); | 388 response.detachSocket() |
| 392 } | 389 .then((socket) { |
| 390 _controller.add(new _WebSocketImpl._fromSocket(socket)); |
| 391 }, onError: (error) { |
| 392 _controller.signalError(error); |
| 393 }); |
| 394 }); |
| 395 |
| 396 return _controller.stream; |
| 397 } |
| 398 |
| 399 bool _isWebSocketUpgrade(HttpRequest request) { |
| 400 if (request.method != "GET") { |
| 401 return false; |
| 402 } |
| 403 if (request.headers[HttpHeaders.CONNECTION] == null) { |
| 404 return false; |
| 405 } |
| 406 bool isUpgrade = false; |
| 407 request.headers[HttpHeaders.CONNECTION].forEach((String value) { |
| 408 if (value.toLowerCase() == "upgrade") isUpgrade = true; |
| 409 }); |
| 410 if (!isUpgrade) return false; |
| 411 String upgrade = request.headers.value(HttpHeaders.UPGRADE); |
| 412 if (upgrade == null || upgrade.toLowerCase() != "websocket") { |
| 413 return false; |
| 414 } |
| 415 String version = request.headers.value("Sec-WebSocket-Version"); |
| 416 if (version == null || version != "13") { |
| 417 return false; |
| 418 } |
| 419 String key = request.headers.value("Sec-WebSocket-Key"); |
| 420 if (key == null) { |
| 421 return false; |
| 422 } |
| 423 return true; |
| 424 } |
| 425 } |
| 426 |
| 427 |
| 428 class _WebSocketImpl extends Stream<Event> implements WebSocket { |
| 429 final StreamController<Event> _controller = new StreamController<Event>(); |
| 430 |
| 431 final _WebSocketProtocolProcessor _processor = |
| 432 new _WebSocketProtocolProcessor(); |
| 433 |
| 434 final Socket _socket; |
| 435 int _readyState = WebSocket.CONNECTING; |
| 436 |
| 437 static final HttpClient _httpClient = new HttpClient(); |
| 438 |
| 439 static Future<WebSocket> connect(String url, [protocols]) { |
| 440 Uri uri = Uri.parse(url); |
| 441 if (uri.scheme != "ws" && uri.scheme != "wss") { |
| 442 throw new WebSocketException("Unsupported URL scheme '${uri.scheme}'"); |
| 443 } |
| 444 if (uri.userInfo != "") { |
| 445 throw new WebSocketException("Unsupported user info '${uri.userInfo}'"); |
| 446 } |
| 447 |
| 448 Random random = new Random(); |
| 449 // Generate 16 random bytes. |
| 450 List<int> nonceData = new List<int>.fixedLength(16); |
| 451 for (int i = 0; i < 16; i++) { |
| 452 nonceData[i] = random.nextInt(256); |
| 453 } |
| 454 String nonce = _Base64._encode(nonceData); |
| 455 |
| 456 uri = new Uri.fromComponents(scheme: uri.scheme == "wss" ? "https" : "http", |
| 457 userInfo: uri.userInfo, |
| 458 domain: uri.domain, |
| 459 port: uri.port, |
| 460 path: uri.path, |
| 461 query: uri.query, |
| 462 fragment: uri.fragment); |
| 463 return _httpClient.openUrl("GET", uri) |
| 464 .then((request) { |
| 465 // Setup the initial handshake. |
| 466 request.headers.add(HttpHeaders.CONNECTION, "upgrade"); |
| 467 request.headers.set(HttpHeaders.UPGRADE, "websocket"); |
| 468 request.headers.set("Sec-WebSocket-Key", nonce); |
| 469 request.headers.set("Sec-WebSocket-Version", "13"); |
| 470 return request.close(); |
| 471 }) |
| 472 .then((response) { |
| 473 void error(String message) { |
| 474 // Flush data. |
| 475 response.detachSocket().then((socket) { |
| 476 socket.destroy(); |
| 477 }); |
| 478 throw new WebSocketException(message); |
| 479 } |
| 480 if (response.statusCode != HttpStatus.SWITCHING_PROTOCOLS || |
| 481 response.headers[HttpHeaders.CONNECTION] == null || |
| 482 !response.headers[HttpHeaders.CONNECTION].any( |
| 483 (value) => value.toLowerCase() == "upgrade") || |
| 484 response.headers.value(HttpHeaders.UPGRADE).toLowerCase() != |
| 485 "websocket") { |
| 486 error("Connection to '$uri' was not upgraded to websocket"); |
| 487 } |
| 488 String accept = response.headers.value("Sec-WebSocket-Accept"); |
| 489 if (accept == null) { |
| 490 error("Response did not contain a 'Sec-WebSocket-Accept' header"); |
| 491 } |
| 492 SHA1 sha1 = new SHA1(); |
| 493 sha1.add("$nonce$_webSocketGUID".charCodes); |
| 494 List<int> expectedAccept = sha1.close(); |
| 495 List<int> receivedAccept = _Base64._decode(accept); |
| 496 if (expectedAccept.length != receivedAccept.length) { |
| 497 error("Reasponse header 'Sec-WebSocket-Accept' is the wrong length"); |
| 498 } |
| 499 for (int i = 0; i < expectedAccept.length; i++) { |
| 500 if (expectedAccept[i] != receivedAccept[i]) { |
| 501 error("Bad response 'Sec-WebSocket-Accept' header"); |
| 502 } |
| 503 } |
| 504 return response.detachSocket() |
| 505 .then((socket) => new _WebSocketImpl._fromSocket(socket)); |
| 506 }); |
| 507 } |
| 508 |
| 509 _WebSocketImpl._fromSocket(Socket this._socket) { |
| 510 _readyState = WebSocket.OPEN; |
| 511 |
| 512 int type; |
| 513 var data; |
| 514 _processor.onMessageStart = (int t) { |
| 515 type = t; |
| 516 if (type == _WebSocketMessageType.TEXT) { |
| 517 data = new StringBuffer(); |
| 518 } else { |
| 519 data = []; |
| 520 } |
| 521 }; |
| 522 _processor.onMessageData = (buffer, offset, count) { |
| 523 if (type == _WebSocketMessageType.TEXT) { |
| 524 data.add(_decodeString(buffer.getRange(offset, count))); |
| 525 } else { |
| 526 data.addAll(buffer.getRange(offset, count)); |
| 527 } |
| 528 }; |
| 529 _processor.onMessageEnd = () { |
| 530 if (type == _WebSocketMessageType.TEXT) { |
| 531 _controller.add(new _WebSocketMessageEvent(data.toString())); |
| 532 } else { |
| 533 _controller.add(new _WebSocketMessageEvent(data)); |
| 534 } |
| 535 }; |
| 536 _processor.onClosed = (code, reason) { |
| 537 bool clean = true; |
| 538 if (_readyState == WebSocket.OPEN) { |
| 539 _readyState = WebSocket.CLOSING; |
| 540 if (code != WebSocketStatus.NO_STATUS_RECEIVED) { |
| 541 _close(code); |
| 542 } else { |
| 543 _close(); |
| 544 clean = false; |
| 545 } |
| 546 _readyState = WebSocket.CLOSED; |
| 547 } |
| 548 _controller.add(new _WebSocketCloseEvent(clean, code, reason)); |
| 549 _controller.close(); |
| 550 }; |
| 551 |
| 552 _socket.listen( |
| 553 (data) => _processor.update(data, 0, data.length), |
| 554 onDone: () => _processor.closed(), |
| 555 onError: (error) => _controller.signalError(error)); |
| 556 } |
| 557 |
| 558 StreamSubscription<Event> listen(void onData(Event event), |
| 559 {void onError(AsyncError error), |
| 560 void onDone(), |
| 561 bool unsubscribeOnError}) { |
| 562 return _controller.stream.listen(onData, |
| 563 onError: onError, |
| 564 onDone: onDone, |
| 565 unsubscribeOnError: unsubscribeOnError); |
| 566 } |
| 567 |
| 568 int get readyState => _readyState; |
| 569 int get bufferedAmount => 0; |
| 570 |
| 571 String get extensions => null; |
| 572 String get protocol => null; |
| 573 |
| 574 void close([int code, String reason]) { |
| 575 if (_readyState < WebSocket.CLOSING) _readyState = WebSocket.CLOSING; |
| 576 if (code == WebSocketStatus.RESERVED_1004 || |
| 577 code == WebSocketStatus.NO_STATUS_RECEIVED || |
| 578 code == WebSocketStatus.RESERVED_1015) { |
| 579 throw new WebSocketException("Reserved status code $code"); |
| 580 } |
| 581 _close(code, reason); |
| 582 } |
| 583 |
| 584 void _close([int code, String reason]) { |
| 585 List<int> data; |
| 586 if (code != null) { |
| 587 data = new List<int>(); |
| 588 data.add((code >> 8) & 0xFF); |
| 589 data.add(code & 0xFF); |
| 590 if (reason != null) { |
| 591 data.addAll(_encodeString(reason)); |
| 592 } |
| 593 } |
| 594 _sendFrame(_WebSocketOpcode.CLOSE, data); |
| 595 |
| 596 if (_readyState == WebSocket.CLOSED) { |
| 597 // Close the socket when the close frame has been sent - if it |
| 598 // does not take too long. |
| 599 // TODO(ajohnsen): Honor comment. |
| 600 _socket.destroy(); |
| 601 } else { |
| 602 // Half close the socket and expect a close frame in response |
| 603 // before closing the socket. If a close frame does not arrive |
| 604 // within a reasonable amount of time just close the socket. |
| 605 // TODO(ajohnsen): Honor comment. |
| 393 _socket.close(); | 606 _socket.close(); |
| 394 }; | 607 } |
| 395 } | 608 } |
| 396 | 609 |
| 397 void set onMessage(void callback(Object message)) { | 610 void send(message) { |
| 398 _onMessage = callback; | 611 if (readyState != WebSocket.OPEN) { |
| 399 } | 612 throw new StateError("Connection not open"); |
| 400 | |
| 401 void set onClosed(void callback(int status, String reason)) { | |
| 402 _onClosed = callback; | |
| 403 } | |
| 404 | |
| 405 send(message) { | |
| 406 if (_closeSent) { | |
| 407 throw new WebSocketException("Connection closed"); | |
| 408 } | 613 } |
| 409 List<int> data; | 614 List<int> data; |
| 410 int opcode; | 615 int opcode; |
| 411 if (message != null) { | 616 if (message != null) { |
| 412 if (message is String) { | 617 if (message is String) { |
| 413 opcode = _WebSocketOpcode.TEXT; | 618 opcode = _WebSocketOpcode.TEXT; |
| 414 data = _StringEncoders.encoder(Encoding.UTF_8).encodeString(message); | 619 data = _encodeString(message); |
| 415 } else { | 620 } else { |
| 416 if (message is !List<int>) { | 621 if (message is !List<int>) { |
| 417 throw new ArgumentError(message); | 622 throw new ArgumentError(message); |
| 418 } | 623 } |
| 419 opcode = _WebSocketOpcode.BINARY; | 624 opcode = _WebSocketOpcode.BINARY; |
| 420 data = message; | 625 data = message; |
| 421 } | 626 } |
| 422 } else { | 627 } else { |
| 423 opcode = _WebSocketOpcode.TEXT; | 628 opcode = _WebSocketOpcode.TEXT; |
| 424 } | 629 } |
| 425 _sendFrame(opcode, data); | 630 _sendFrame(opcode, data); |
| 426 } | 631 } |
| 427 | 632 |
| 428 close([int status, String reason]) { | 633 void _sendFrame(int opcode, [List<int> data]) { |
| 429 if (status == WebSocketStatus.RESERVED_1004 || | |
| 430 status == WebSocketStatus.NO_STATUS_RECEIVED || | |
| 431 status == WebSocketStatus.RESERVED_1015) { | |
| 432 throw new WebSocketException("Reserved status code $status"); | |
| 433 } | |
| 434 | |
| 435 if (_closeSent) return; | |
| 436 List<int> data; | |
| 437 if (status != null) { | |
| 438 data = new List<int>(); | |
| 439 data.add((status >> 8) & 0xFF); | |
| 440 data.add(status & 0xFF); | |
| 441 if (reason != null) { | |
| 442 data.addAll( | |
| 443 _StringEncoders.encoder(Encoding.UTF_8).encodeString(reason)); | |
| 444 } | |
| 445 } | |
| 446 _sendFrame(_WebSocketOpcode.CLOSE, data); | |
| 447 | |
| 448 if (_closeReceived) { | |
| 449 // Close the socket when the close frame has been sent - if it | |
| 450 // does not take too long. | |
| 451 _socket.outputStream.close(); | |
| 452 _socket.outputStream.onClosed = () { | |
| 453 if (_closeTimer != null) _closeTimer.cancel(); | |
| 454 _socket.close(); | |
| 455 }; | |
| 456 _closeTimer = new Timer(const Duration(seconds: 5), _socket.close); | |
| 457 } else { | |
| 458 // Half close the socket and expect a close frame in response | |
| 459 // before closing the socket. If a close frame does not arrive | |
| 460 // within a reasonable amount of time just close the socket. | |
| 461 _socket.outputStream.close(); | |
| 462 _closeTimer = new Timer(const Duration(seconds: 5), _socket.close); | |
| 463 } | |
| 464 _closeSent = true; | |
| 465 } | |
| 466 | |
| 467 int get hashCode => _hash; | |
| 468 | |
| 469 _onWebSocketMessageStart(int type) { | |
| 470 _currentMessageType = type; | |
| 471 if (_currentMessageType == _WebSocketMessageType.TEXT) { | |
| 472 _decoder = _StringDecoders.decoder(Encoding.UTF_8); | |
| 473 } else { | |
| 474 _outputStream = new ListOutputStream(); | |
| 475 } | |
| 476 } | |
| 477 | |
| 478 _onWebSocketMessageData(List<int> buffer, int offset, int count) { | |
| 479 if (_currentMessageType == _WebSocketMessageType.TEXT) { | |
| 480 _decoder.write(buffer.getRange(offset, count)); | |
| 481 } else { | |
| 482 _outputStream.write(buffer.getRange(offset, count)); | |
| 483 } | |
| 484 } | |
| 485 | |
| 486 _onWebSocketMessageEnd() { | |
| 487 if (_onMessage != null) { | |
| 488 if (_currentMessageType == _WebSocketMessageType.TEXT) { | |
| 489 _onMessage(_decoder.decoded()); | |
| 490 } else { | |
| 491 _onMessage(_outputStream.read()); | |
| 492 } | |
| 493 } | |
| 494 _decoder = null; | |
| 495 _outputStream = null; | |
| 496 } | |
| 497 | |
| 498 _onWebSocketPing(List<int> payload) { | |
| 499 _sendFrame(_WebSocketOpcode.PONG, payload); | |
| 500 } | |
| 501 | |
| 502 _onWebSocketPong(List<int> payload) { | |
| 503 // Currently pong messages are ignored. | |
| 504 } | |
| 505 | |
| 506 _onWebSocketClosed(int status, String reason) { | |
| 507 _closeReceived = true; | |
| 508 if (_onClosed != null) _onClosed(status, reason); | |
| 509 if (_closeSent) { | |
| 510 // Got close frame in response to close frame. Now close the socket. | |
| 511 if (_closeTimer != null) _closeTimer.cancel(); | |
| 512 _socket.close(); | |
| 513 } else { | |
| 514 if (status != WebSocketStatus.NO_STATUS_RECEIVED) { | |
| 515 close(status); | |
| 516 } else { | |
| 517 close(); | |
| 518 } | |
| 519 } | |
| 520 } | |
| 521 | |
| 522 _sendFrame(int opcode, [List<int> data]) { | |
| 523 bool mask = false; // Masking not implemented for server. | 634 bool mask = false; // Masking not implemented for server. |
| 524 int dataLength = data == null ? 0 : data.length; | 635 int dataLength = data == null ? 0 : data.length; |
| 525 // Determine the header size. | 636 // Determine the header size. |
| 526 int headerSize = (mask) ? 6 : 2; | 637 int headerSize = (mask) ? 6 : 2; |
| 527 if (dataLength > 65535) { | 638 if (dataLength > 65535) { |
| 528 headerSize += 8; | 639 headerSize += 8; |
| 529 } else if (dataLength > 125) { | 640 } else if (dataLength > 125) { |
| 530 headerSize += 2; | 641 headerSize += 2; |
| 531 } | 642 } |
| 532 List<int> header = new List<int>.fixedLength(headerSize); | 643 List<int> header = new List<int>.fixedLength(headerSize); |
| 533 int index = 0; | 644 int index = 0; |
| 534 // Set FIN and opcode. | 645 // Set FIN and opcode. |
| 535 header[index++] = 0x80 | opcode; | 646 header[index++] = 0x80 | opcode; |
| 536 // Determine size and position of length field. | 647 // Determine size and position of length field. |
| 537 int lengthBytes = 1; | 648 int lengthBytes = 1; |
| 538 int firstLengthByte = 1; | 649 int firstLengthByte = 1; |
| 539 if (dataLength > 65535) { | 650 if (dataLength > 65535) { |
| 540 header[index++] = 127; | 651 header[index++] = 127; |
| 541 lengthBytes = 8; | 652 lengthBytes = 8; |
| 542 } else if (dataLength > 125) { | 653 } else if (dataLength > 125) { |
| 543 header[index++] = 126; | 654 header[index++] = 126; |
| 544 lengthBytes = 2; | 655 lengthBytes = 2; |
| 545 } | 656 } |
| 546 // Write the length in network byte order into the header. | 657 // Write the length in network byte order into the header. |
| 547 for (int i = 0; i < lengthBytes; i++) { | 658 for (int i = 0; i < lengthBytes; i++) { |
| 548 header[index++] = dataLength >> (((lengthBytes - 1) - i) * 8) & 0xFF; | 659 header[index++] = dataLength >> (((lengthBytes - 1) - i) * 8) & 0xFF; |
| 549 } | 660 } |
| 550 assert(index == headerSize); | 661 assert(index == headerSize); |
| 551 _socket.outputStream.write(header); | 662 _socket.add(header); |
| 552 if (data != null) { | 663 if (data != null) { |
| 553 _socket.outputStream.write(data); | 664 _socket.add(data); |
| 554 } | 665 } |
| 555 } | 666 } |
| 556 | |
| 557 Socket _socket; | |
| 558 Timer _closeTimer; | |
| 559 int _hash; | |
| 560 | |
| 561 Function _onMessage; | |
| 562 Function _onClosed; | |
| 563 | |
| 564 int _currentMessageType = _WebSocketMessageType.NONE; | |
| 565 _StringDecoder _decoder; | |
| 566 ListOutputStream _outputStream; | |
| 567 bool _closeReceived = false; | |
| 568 bool _closeSent = false; | |
| 569 } | |
| 570 | |
| 571 | |
| 572 class _WebSocketConnection | |
| 573 extends _WebSocketConnectionBase implements WebSocketConnection { | |
| 574 _WebSocketConnection(DetachedSocket detached) { | |
| 575 _hash = detached.socket.hashCode; | |
| 576 _socketConnected(detached.socket); | |
| 577 _startProcessing(detached.unparsedData); | |
| 578 } | |
| 579 } | 667 } |
| 580 | 668 |
| 581 | 669 |
| 582 class _WebSocketHandler implements WebSocketHandler { | |
| 583 void onRequest(HttpRequest request, HttpResponse response) { | |
| 584 // Check that this is a web socket upgrade. | |
| 585 if (!_isWebSocketUpgrade(request)) { | |
| 586 response.statusCode = HttpStatus.BAD_REQUEST; | |
| 587 response.outputStream.close(); | |
| 588 return; | |
| 589 } | |
| 590 | |
| 591 // Send the upgrade response. | |
| 592 response.statusCode = HttpStatus.SWITCHING_PROTOCOLS; | |
| 593 response.headers.add(HttpHeaders.CONNECTION, "Upgrade"); | |
| 594 response.headers.add(HttpHeaders.UPGRADE, "websocket"); | |
| 595 String key = request.headers.value("Sec-WebSocket-Key"); | |
| 596 SHA1 sha1 = new SHA1(); | |
| 597 sha1.add("$key$_webSocketGUID".charCodes); | |
| 598 String accept = _Base64._encode(sha1.close()); | |
| 599 response.headers.add("Sec-WebSocket-Accept", accept); | |
| 600 response.contentLength = 0; | |
| 601 | |
| 602 // Upgrade the connection and get the underlying socket. | |
| 603 WebSocketConnection conn = | |
| 604 new _WebSocketConnection(response.detachSocket()); | |
| 605 if (_onOpen != null) _onOpen(conn); | |
| 606 } | |
| 607 | |
| 608 void set onOpen(callback(WebSocketConnection connection)) { | |
| 609 _onOpen = callback; | |
| 610 } | |
| 611 | |
| 612 bool _isWebSocketUpgrade(HttpRequest request) { | |
| 613 if (request.method != "GET") { | |
| 614 return false; | |
| 615 } | |
| 616 if (request.headers[HttpHeaders.CONNECTION] == null) { | |
| 617 return false; | |
| 618 } | |
| 619 bool isUpgrade = false; | |
| 620 request.headers[HttpHeaders.CONNECTION].forEach((String value) { | |
| 621 if (value.toLowerCase() == "upgrade") isUpgrade = true; | |
| 622 }); | |
| 623 if (!isUpgrade) return false; | |
| 624 String upgrade = request.headers.value(HttpHeaders.UPGRADE); | |
| 625 if (upgrade == null || upgrade.toLowerCase() != "websocket") { | |
| 626 return false; | |
| 627 } | |
| 628 String version = request.headers.value("Sec-WebSocket-Version"); | |
| 629 if (version == null || version != "13") { | |
| 630 return false; | |
| 631 } | |
| 632 String key = request.headers.value("Sec-WebSocket-Key"); | |
| 633 if (key == null) { | |
| 634 return false; | |
| 635 } | |
| 636 return true; | |
| 637 } | |
| 638 | |
| 639 Function _onOpen; | |
| 640 } | |
| 641 | |
| 642 | |
| 643 class _WebSocketClientConnection | |
| 644 extends _WebSocketConnectionBase implements WebSocketClientConnection { | |
| 645 _WebSocketClientConnection(HttpClientConnection this._conn, | |
| 646 [List<String> protocols]) { | |
| 647 _conn.onRequest = _onHttpClientRequest; | |
| 648 _conn.onResponse = _onHttpClientResponse; | |
| 649 _conn.onError = (e) { | |
| 650 if (_onClosed != null) { | |
| 651 _onClosed(WebSocketStatus.ABNORMAL_CLOSURE, "$e"); | |
| 652 } | |
| 653 }; | |
| 654 | |
| 655 // Generate the nonce now as it is also used to set the hash code. | |
| 656 _generateNonceAndHash(); | |
| 657 } | |
| 658 | |
| 659 void set onRequest(void callback(HttpClientRequest request)) { | |
| 660 _onRequest = callback; | |
| 661 } | |
| 662 | |
| 663 void set onOpen(void callback()) { | |
| 664 _onOpen = callback; | |
| 665 } | |
| 666 | |
| 667 void set onNoUpgrade(void callback(HttpClientResponse request)) { | |
| 668 _onNoUpgrade = callback; | |
| 669 } | |
| 670 | |
| 671 void _onHttpClientRequest(HttpClientRequest request) { | |
| 672 if (_onRequest != null) { | |
| 673 _onRequest(request); | |
| 674 } | |
| 675 // Setup the initial handshake. | |
| 676 request.headers.add(HttpHeaders.CONNECTION, "upgrade"); | |
| 677 request.headers.set(HttpHeaders.UPGRADE, "websocket"); | |
| 678 request.headers.set("Sec-WebSocket-Key", _nonce); | |
| 679 request.headers.set("Sec-WebSocket-Version", "13"); | |
| 680 request.contentLength = 0; | |
| 681 request.outputStream.close(); | |
| 682 } | |
| 683 | |
| 684 void _onHttpClientResponse(HttpClientResponse response) { | |
| 685 if (response.statusCode != HttpStatus.SWITCHING_PROTOCOLS) { | |
| 686 if (_onNoUpgrade != null) { | |
| 687 _onNoUpgrade(response); | |
| 688 } else { | |
| 689 _conn.detachSocket().socket.close(); | |
| 690 throw new WebSocketException("Protocol upgrade refused"); | |
| 691 } | |
| 692 return; | |
| 693 } | |
| 694 | |
| 695 if (!_isWebSocketUpgrade(response)) { | |
| 696 _conn.detachSocket().socket.close(); | |
| 697 throw new WebSocketException("Protocol upgrade failed"); | |
| 698 } | |
| 699 | |
| 700 // Connection upgrade successful. | |
| 701 DetachedSocket detached = _conn.detachSocket(); | |
| 702 _socketConnected(detached.socket); | |
| 703 if (_onOpen != null) _onOpen(); | |
| 704 _startProcessing(detached.unparsedData); | |
| 705 } | |
| 706 | |
| 707 void _generateNonceAndHash() { | |
| 708 Random random = new Random(); | |
| 709 assert(_nonce == null); | |
| 710 void intToBigEndianBytes(int value, List<int> bytes, int offset) { | |
| 711 bytes[offset] = (value >> 24) & 0xFF; | |
| 712 bytes[offset + 1] = (value >> 16) & 0xFF; | |
| 713 bytes[offset + 2] = (value >> 8) & 0xFF; | |
| 714 bytes[offset + 3] = value & 0xFF; | |
| 715 } | |
| 716 | |
| 717 // Generate 16 random bytes. Use the last four bytes for the hash code. | |
| 718 List<int> nonce = new List<int>.fixedLength(16); | |
| 719 for (int i = 0; i < 4; i++) { | |
| 720 int r = random.nextInt(0x100000000); | |
| 721 intToBigEndianBytes(r, nonce, i * 4); | |
| 722 } | |
| 723 _nonce = _Base64._encode(nonce); | |
| 724 _hash = random.nextInt(0x100000000); | |
| 725 } | |
| 726 | |
| 727 bool _isWebSocketUpgrade(HttpClientResponse response) { | |
| 728 if (response.headers[HttpHeaders.CONNECTION] == null) { | |
| 729 return false; | |
| 730 } | |
| 731 bool isUpgrade = false; | |
| 732 response.headers[HttpHeaders.CONNECTION].forEach((String value) { | |
| 733 if (value.toLowerCase() == "upgrade") isUpgrade = true; | |
| 734 }); | |
| 735 if (!isUpgrade) return false; | |
| 736 String upgrade = response.headers.value(HttpHeaders.UPGRADE); | |
| 737 if (upgrade == null || upgrade.toLowerCase() != "websocket") { | |
| 738 return false; | |
| 739 } | |
| 740 String accept = response.headers.value("Sec-WebSocket-Accept"); | |
| 741 if (accept == null) { | |
| 742 return false; | |
| 743 } | |
| 744 SHA1 sha1 = new SHA1(); | |
| 745 sha1.add("$_nonce$_webSocketGUID".charCodes); | |
| 746 List<int> expectedAccept = sha1.close(); | |
| 747 List<int> receivedAccept = _Base64._decode(accept); | |
| 748 if (expectedAccept.length != receivedAccept.length) return false; | |
| 749 for (int i = 0; i < expectedAccept.length; i++) { | |
| 750 if (expectedAccept[i] != receivedAccept[i]) return false; | |
| 751 } | |
| 752 return true; | |
| 753 } | |
| 754 | |
| 755 Function _onRequest; | |
| 756 Function _onOpen; | |
| 757 Function _onNoUpgrade; | |
| 758 HttpClientConnection _conn; | |
| 759 String _nonce; | |
| 760 } | |
| 761 | |
| 762 | |
| 763 class _WebSocket implements WebSocket { | |
| 764 _WebSocket(String url, [protocols]) { | |
| 765 Uri uri = Uri.parse(url); | |
| 766 if (uri.scheme != "ws" && uri.scheme != "wss") { | |
| 767 throw new WebSocketException("Unsupported URL scheme ${uri.scheme}"); | |
| 768 } | |
| 769 if (uri.userInfo != "") { | |
| 770 throw new WebSocketException("Unsupported user info ${uri.userInfo}"); | |
| 771 } | |
| 772 int port = uri.port == 0 ? HttpClient.DEFAULT_HTTP_PORT : uri.port; | |
| 773 String path = uri.path; | |
| 774 if (path.length == 0) path = "/"; | |
| 775 if (uri.query != "") { | |
| 776 if (uri.fragment != "") { | |
| 777 path = "${path}?${uri.query}#${uri.fragment}"; | |
| 778 } else { | |
| 779 path = "${path}?${uri.query}"; | |
| 780 } | |
| 781 } | |
| 782 | |
| 783 HttpClient client = new HttpClient(); | |
| 784 bool secure = (uri.scheme == 'wss'); | |
| 785 HttpClientConnection conn = client.openUrl("GET", | |
| 786 new Uri.fromComponents(scheme: secure ? "https" : "http", | |
| 787 domain: uri.domain, | |
| 788 port: port, | |
| 789 path: path)); | |
| 790 if (protocols is String) protocols = [protocols]; | |
| 791 _wsconn = new WebSocketClientConnection(conn, protocols); | |
| 792 _wsconn.onOpen = () { | |
| 793 // HTTP client not needed after socket have been detached. | |
| 794 client.shutdown(); | |
| 795 client = null; | |
| 796 _readyState = WebSocket.OPEN; | |
| 797 if (_onopen != null) _onopen(); | |
| 798 }; | |
| 799 _wsconn.onMessage = (message) { | |
| 800 if (_onmessage != null) { | |
| 801 _onmessage(new _WebSocketMessageEvent(message)); | |
| 802 } | |
| 803 }; | |
| 804 _wsconn.onClosed = (status, reason) { | |
| 805 _readyState = WebSocket.CLOSED; | |
| 806 if (_onclose != null) { | |
| 807 _onclose(new _WebSocketCloseEvent(true, status, reason)); | |
| 808 } | |
| 809 }; | |
| 810 _wsconn.onNoUpgrade = (response) { | |
| 811 if (_onclose != null) { | |
| 812 _onclose( | |
| 813 new _WebSocketCloseEvent(true, | |
| 814 WebSocketStatus.ABNORMAL_CLOSURE, | |
| 815 "Connection not upgraded")); | |
| 816 } | |
| 817 }; | |
| 818 } | |
| 819 | |
| 820 int get readyState => _readyState; | |
| 821 int get bufferedAmount => 0; | |
| 822 | |
| 823 void set onopen(Function callback) { | |
| 824 _onopen = callback; | |
| 825 } | |
| 826 | |
| 827 void set onerror(Function callback) {} | |
| 828 | |
| 829 void set onclose(Function callback) { | |
| 830 _onclose = callback; | |
| 831 } | |
| 832 | |
| 833 String get extensions => null; | |
| 834 String get protocol => null; | |
| 835 | |
| 836 void close(int code, String reason) { | |
| 837 if (_readyState < WebSocket.CLOSING) _readyState = WebSocket.CLOSING; | |
| 838 _wsconn.close(code, reason); | |
| 839 } | |
| 840 | |
| 841 void set onmessage(Function callback) { | |
| 842 _onmessage = callback; | |
| 843 } | |
| 844 | |
| 845 void send(data) { | |
| 846 _wsconn.send(data); | |
| 847 } | |
| 848 | |
| 849 WebSocketClientConnection _wsconn; | |
| 850 int _readyState = WebSocket.CONNECTING; | |
| 851 Function _onopen; | |
| 852 Function _onclose; | |
| 853 Function _onmessage; | |
| 854 } | |
| 855 | |
| 856 | |
| 857 class _WebSocketMessageEvent implements MessageEvent { | 670 class _WebSocketMessageEvent implements MessageEvent { |
| 858 _WebSocketMessageEvent(this._data); | 671 _WebSocketMessageEvent(this._data); |
| 859 get data => _data; | 672 get data => _data; |
| 860 var _data; | 673 var _data; |
| 861 } | 674 } |
| 862 | 675 |
| 863 | 676 |
| 864 class _WebSocketCloseEvent implements CloseEvent { | 677 class _WebSocketCloseEvent implements CloseEvent { |
| 865 _WebSocketCloseEvent(this._wasClean, this._code, this._reason); | 678 _WebSocketCloseEvent(this._wasClean, this._code, this._reason); |
| 866 bool get wasClean => _wasClean; | 679 bool get wasClean => _wasClean; |
| 867 int get code => _code; | 680 int get code => _code; |
| 868 String get reason => _reason; | 681 String get reason => _reason; |
| 869 bool _wasClean; | 682 bool _wasClean; |
| 870 int _code; | 683 int _code; |
| 871 String _reason; | 684 String _reason; |
| 872 } | 685 } |
| OLD | NEW |