| Index: sdk/lib/io/secure_socket.dart
|
| diff --git a/sdk/lib/io/secure_socket.dart b/sdk/lib/io/secure_socket.dart
|
| index 6dc1d6dcb9a1b45d0cabd2e5ef73085a3f57da87..68dd8df4b526922526d99a040af510938721e24b 100644
|
| --- a/sdk/lib/io/secure_socket.dart
|
| +++ b/sdk/lib/io/secure_socket.dart
|
| @@ -1,54 +1,63 @@
|
| -// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
| +// 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.
|
|
|
| part of dart.io;
|
|
|
| /**
|
| - * SecureSocket provides a secure (SSL or TLS) client connection to a server.
|
| - * The certificate provided by the server is checked
|
| - * using the certificate database (optionally) provided in initialize().
|
| + * A high-level class for communicating securely over a TCP socket, using
|
| + * TLS and SSL. The [SecureSocket] exposes both a [Stream] and an
|
| + * [IOSink] interface, making it ideal for using together with
|
| + * other [Stream]s.
|
| */
|
| abstract class SecureSocket implements Socket {
|
| + external factory SecureSocket._(RawSecureSocket rawSocket);
|
| +
|
| /**
|
| * Constructs a new secure client socket and connect it to the given
|
| - * host on the given port. The returned socket is not yet connected
|
| - * but ready for registration of callbacks. If sendClientCertificate is
|
| - * set to true, the socket will send a client certificate if one is
|
| - * requested by the server. If clientCertificate is the nickname of
|
| - * a certificate in the certificate database, that certificate will be sent.
|
| - * If clientCertificate is null, which is the usual use case, an
|
| + * [host] on port [port]. The returned Future will complete with a
|
| + * [SecureSocket] that is connected and ready for subscription.
|
| + *
|
| + * If [sendClientCertificate] is set to true, the socket will send a client
|
| + * certificate if one is requested by the server.
|
| + *
|
| + * If [certificateName] is the nickname of a certificate in the certificate
|
| + * database, that certificate will be sent.
|
| + *
|
| + * If [certificateName] is null, which is the usual use case, an
|
| * appropriate certificate will be searched for in the database and
|
| * sent automatically, based on what the server says it will accept.
|
| + *
|
| + * [onBadCertificate] is an optional handler for unverifiable certificates.
|
| + * The handler receives the [X509Certificate], and can inspect it and
|
| + * decide (or let the user decide) whether to accept
|
| + * the connection or not. The handler should return true
|
| + * to continue the [SecureSocket] connection.
|
| */
|
| - factory SecureSocket(String host,
|
| - int port,
|
| - {bool sendClientCertificate: false,
|
| - String certificateName}) {
|
| - return new _SecureSocket(host,
|
| - port,
|
| - certificateName,
|
| - is_server: false,
|
| - sendClientCertificate: sendClientCertificate);
|
| + static Future<SecureSocket> connect(
|
| + String host,
|
| + int port,
|
| + {bool sendClientCertificate: false,
|
| + String certificateName,
|
| + bool onBadCertificate(X509Certificate certificate)}) {
|
| + return RawSecureSocket.connect(host,
|
| + port,
|
| + sendClientCertificate: sendClientCertificate,
|
| + certificateName: certificateName,
|
| + onBadCertificate: onBadCertificate)
|
| + .then((rawSocket) => new SecureSocket._(rawSocket));
|
| }
|
|
|
| /**
|
| - * Install a handler for unverifiable certificates. The handler can inspect
|
| - * the certificate, and decide (or let the user decide) whether to accept
|
| - * the connection or not. The callback should return true
|
| - * to continue the SecureSocket connection.
|
| - */
|
| - void set onBadCertificate(bool callback(X509Certificate certificate));
|
| -
|
| - /**
|
| - * Get the peerCertificate for a connected secure socket. For a server
|
| - * socket, this will return the client certificate, or null, if no
|
| - * client certificate was received. For a client socket, this
|
| - * will return the server's certificate.
|
| + * Get the peer certificate for a connected SecureSocket. If this
|
| + * SecureSocket is the server end of a secure socket connection,
|
| + * [peerCertificate] will return the client certificate, or null, if no
|
| + * client certificate was received. If it is the client end,
|
| + * [peerCertificate] will return the server's certificate.
|
| */
|
| X509Certificate get peerCertificate;
|
|
|
| - /**
|
| + /**
|
| * Initializes the NSS library. If [initialize] is not called, the library
|
| * is automatically initialized as if [initialize] were called with no
|
| * arguments.
|
| @@ -88,6 +97,64 @@ abstract class SecureSocket implements Socket {
|
|
|
|
|
| /**
|
| + * RawSecureSocket provides a secure (SSL or TLS) network connection.
|
| + * Client connections to a server are provided by calling
|
| + * RawSecureSocket.connect. A secure server, created with
|
| + * RawSecureServerSocket, also returns RawSecureSocket objects representing
|
| + * the server end of a secure connection.
|
| + * The certificate provided by the server is checked
|
| + * using the certificate database provided in SecureSocket.initialize, and/or
|
| + * the default built-in root certificates.
|
| + */
|
| +abstract class RawSecureSocket implements RawSocket {
|
| + /**
|
| + * Constructs a new secure client socket and connect it to the given
|
| + * host on the given port. The returned Future is completed with the
|
| + * RawSecureSocket when it is connected and ready for subscription.
|
| + *
|
| + * The certificate provided by the server is checked using the certificate
|
| + * database provided in [SecureSocket.initialize], and/or the default built-in
|
| + * root certificates. If [sendClientCertificate] is
|
| + * set to true, the socket will send a client certificate if one is
|
| + * requested by the server. If [certificateName] is the nickname of
|
| + * a certificate in the certificate database, that certificate will be sent.
|
| + * If [certificateName] is null, which is the usual use case, an
|
| + * appropriate certificate will be searched for in the database and
|
| + * sent automatically, based on what the server says it will accept.
|
| + *
|
| + * [onBadCertificate] is an optional handler for unverifiable certificates.
|
| + * The handler receives the [X509Certificate], and can inspect it and
|
| + * decide (or let the user decide) whether to accept
|
| + * the connection or not. The handler should return true
|
| + * to continue the [RawSecureSocket] connection.
|
| + */
|
| + static Future<RawSecureSocket> connect(
|
| + String host,
|
| + int port,
|
| + {bool sendClientCertificate: false,
|
| + String certificateName,
|
| + bool onBadCertificate(X509Certificate certificate)}) {
|
| + return _RawSecureSocket.connect(
|
| + host,
|
| + port,
|
| + certificateName,
|
| + is_server: false,
|
| + sendClientCertificate: sendClientCertificate,
|
| + onBadCertificate: onBadCertificate);
|
| + }
|
| +
|
| + /**
|
| + * Get the peer certificate for a connected RawSecureSocket. If this
|
| + * RawSecureSocket is the server end of a secure socket connection,
|
| + * [peerCertificate] will return the client certificate, or null, if no
|
| + * client certificate was received. If it is the client end,
|
| + * [peerCertificate] will return the server's certificate.
|
| + */
|
| + X509Certificate get peerCertificate;
|
| +}
|
| +
|
| +
|
| +/**
|
| * X509Certificate represents an SSL certificate, with accessors to
|
| * get the fields of the certificate.
|
| */
|
| @@ -103,7 +170,8 @@ class X509Certificate {
|
| }
|
|
|
|
|
| -class _SecureSocket implements SecureSocket {
|
| +class _RawSecureSocket extends Stream<RawSocketEvent>
|
| + implements RawSecureSocket {
|
| // Status states
|
| static final int NOT_CONNECTED = 200;
|
| static final int HANDSHAKE = 201;
|
| @@ -118,178 +186,225 @@ class _SecureSocket implements SecureSocket {
|
| static final int WRITE_ENCRYPTED = 3;
|
| static final int NUM_BUFFERS = 4;
|
|
|
| - _SecureSocket(String this.host,
|
| - int requestedPort,
|
| - String this.certificateName,
|
| - {bool this.is_server,
|
| - Socket this.socket,
|
| - bool this.requestClientCertificate: false,
|
| - bool this.requireClientCertificate: false,
|
| - bool this.sendClientCertificate: false})
|
| - : secureFilter = new _SecureFilter() {
|
| - // Throw an ArgumentError if any field is invalid.
|
| + RawSocket _socket;
|
| + final Completer<_RawSecureSocket> _handshakeComplete =
|
| + new Completer<_RawSecureSocket>();
|
| + StreamController<RawSocketEvent> _controller;
|
| + Stream<RawSocketEvent> _stream;
|
| + StreamSubscription<RawSocketEvent> _socketSubscription;
|
| + final String host;
|
| + final bool is_server;
|
| + final String certificateName;
|
| + final bool requestClientCertificate;
|
| + final bool requireClientCertificate;
|
| + final bool sendClientCertificate;
|
| + final Function onBadCertificate;
|
| +
|
| + var _status = NOT_CONNECTED;
|
| + bool _writeEventsEnabled = true;
|
| + bool _readEventsEnabled = true;
|
| + bool _socketClosedRead = false; // The network socket is closed for reading.
|
| + bool _socketClosedWrite = false; // The network socket is closed for writing.
|
| + bool _closedRead = false; // The secure socket has fired an onClosed event.
|
| + bool _closedWrite = false; // The secure socket has been closed for writing.
|
| + bool _filterReadEmpty = true; // There is no buffered data to read.
|
| + bool _filterWriteEmpty = true; // There is no buffered data to be written.
|
| + bool _connectPending = false;
|
| + _SecureFilter _secureFilter = new _SecureFilter();
|
| +
|
| + static Future<_RawSecureSocket> connect(
|
| + String host,
|
| + int requestedPort,
|
| + String certificateName,
|
| + {bool is_server,
|
| + RawSocket socket,
|
| + bool requestClientCertificate: false,
|
| + bool requireClientCertificate: false,
|
| + bool sendClientCertificate: false,
|
| + bool onBadCertificate(X509Certificate certificate)}){
|
| + return new _RawSecureSocket(host,
|
| + requestedPort,
|
| + certificateName,
|
| + is_server,
|
| + socket,
|
| + requestClientCertificate,
|
| + requireClientCertificate,
|
| + sendClientCertificate,
|
| + onBadCertificate)
|
| + ._handshakeComplete.future;
|
| + }
|
| +
|
| + _RawSecureSocket(
|
| + String this.host,
|
| + int requestedPort,
|
| + String this.certificateName,
|
| + bool this.is_server,
|
| + RawSocket socket,
|
| + bool this.requestClientCertificate,
|
| + bool this.requireClientCertificate,
|
| + bool this.sendClientCertificate,
|
| + bool this.onBadCertificate(X509Certificate certificate)) {
|
| + _controller = new StreamController<RawSocketEvent>(
|
| + onPauseStateChange: _onPauseStateChange,
|
| + onSubscriptionStateChange: _onSubscriptionStateChange);
|
| + _stream = _controller.stream;
|
| + // Throw an ArgumentError if any field is invalid. After this, all
|
| + // errors will be reported through the future or the stream.
|
| _verifyFields();
|
| - if (socket == null) {
|
| - socket = new Socket(host, requestedPort);
|
| - }
|
| - socket.onConnect = _secureConnectHandler;
|
| - socket.onData = _secureDataHandler;
|
| - socket.onClosed = _secureCloseHandler;
|
| - socket.onError = _secureErrorHandler;
|
| - secureFilter.init();
|
| - secureFilter.registerHandshakeCompleteCallback(
|
| + _secureFilter.init();
|
| + _secureFilter.registerHandshakeCompleteCallback(
|
| _secureHandshakeCompleteHandler);
|
| + if (onBadCertificate != null) {
|
| + _secureFilter.registerBadCertificateCallback(onBadCertificate);
|
| + }
|
| + var futureSocket;
|
| + if (socket == null) {
|
| + futureSocket = RawSocket.connect(host, requestedPort);
|
| + } else {
|
| + futureSocket = new Future.immediate(socket);
|
| + }
|
| + futureSocket.then((rawSocket) {
|
| + rawSocket.writeEventsEnabled = false;
|
| + _socket = rawSocket;
|
| + _socketSubscription = _socket.listen(_eventDispatcher,
|
| + onError: _errorHandler,
|
| + onDone: _doneHandler);
|
| + _connectPending = true;
|
| + _secureFilter.connect(host,
|
| + port,
|
| + is_server,
|
| + certificateName,
|
| + requestClientCertificate ||
|
| + requireClientCertificate,
|
| + requireClientCertificate,
|
| + sendClientCertificate);
|
| + _status = HANDSHAKE;
|
| + _secureHandshake();
|
| + })
|
| + .catchError((error) {
|
| + _handshakeComplete.completeError(error);
|
| + close();
|
| + });
|
| + }
|
| +
|
| + StreamSubscription listen(void onData(RawSocketEvent data),
|
| + {void onError(AsyncError error),
|
| + void onDone(),
|
| + bool unsubscribeOnError}) {
|
| + if (_writeEventsEnabled) {
|
| + _writeEventsEnabled = false;
|
| + _controller.add(RawSocketEvent.WRITE);
|
| + }
|
| + return _stream.listen(onData,
|
| + onError: onError,
|
| + onDone: onDone,
|
| + unsubscribeOnError: unsubscribeOnError);
|
| }
|
|
|
| void _verifyFields() {
|
| - if (host is! String) throw new ArgumentError(
|
| - "SecureSocket constructor: host is not a String");
|
| assert(is_server is bool);
|
| - assert(socket == null || socket is Socket);
|
| - if (certificateName != null && certificateName is! String) {
|
| + assert(_socket == null || _socket is RawSocket);
|
| + if (host is! String) {
|
| throw new ArgumentError(
|
| - "SecureSocket constructor: certificateName is not null or a String");
|
| + "RawSecureSocket constructor: host is not a String");
|
| + }
|
| + if (certificateName != null && certificateName is! String) {
|
| + throw new ArgumentError("certificateName is not null or a String");
|
| }
|
| if (certificateName == null && is_server) {
|
| - throw new ArgumentError(
|
| - "SecureSocket constructor: certificateName is null on a server");
|
| + throw new ArgumentError("certificateName is null on a server");
|
| }
|
| if (requestClientCertificate is! bool) {
|
| - throw new ArgumentError(
|
| - "SecureSocket constructor: requestClientCertificate is not a bool");
|
| + throw new ArgumentError("requestClientCertificate is not a bool");
|
| }
|
| if (requireClientCertificate is! bool) {
|
| - throw new ArgumentError(
|
| - "SecureSocket constructor: requireClientCertificate is not a bool");
|
| + throw new ArgumentError("requireClientCertificate is not a bool");
|
| }
|
| if (sendClientCertificate is! bool) {
|
| - throw new ArgumentError(
|
| - "SecureSocket constructor: sendClientCertificate is not a bool");
|
| + throw new ArgumentError("sendClientCertificate is not a bool");
|
| }
|
| - }
|
| -
|
| - int get port => socket.port;
|
| -
|
| - String get remoteHost => socket.remoteHost;
|
| -
|
| - int get remotePort => socket.remotePort;
|
| -
|
| - void set onClosed(void callback()) {
|
| - if (_inputStream != null && callback != null) {
|
| - throw new StreamException(
|
| - "Cannot set close handler when input stream is used");
|
| + if (onBadCertificate != null && onBadCertificate is! Function) {
|
| + throw new ArgumentError("onBadCertificate is not null or a Function");
|
| }
|
| - _onClosed = callback;
|
| - }
|
| + }
|
|
|
| - void set _onClosed(void callback()) {
|
| - _socketCloseHandler = callback;
|
| - }
|
| + int get port => _socket.port;
|
|
|
| - void set onConnect(void callback()) {
|
| - if (_status == CONNECTED || _status == CLOSED) {
|
| - throw new StreamException(
|
| - "Cannot set connect handler when already connected");
|
| - }
|
| - _onConnect = callback;
|
| - }
|
| + String get remoteHost => _socket.remoteHost;
|
|
|
| - void set _onConnect(void callback()) {
|
| - _socketConnectHandler = callback;
|
| - }
|
| + int get remotePort => _socket.remotePort;
|
|
|
| - void set onData(void callback()) {
|
| - if (_inputStream != null && callback != null) {
|
| - throw new StreamException(
|
| - "Cannot set data handler when input stream is used");
|
| - }
|
| - _onData = callback;
|
| - }
|
| -
|
| - void set _onData(void callback()) {
|
| - _socketDataHandler = callback;
|
| - }
|
| -
|
| - void set onError(void callback(e)) {
|
| - _socketErrorHandler = callback;
|
| - }
|
| -
|
| - void set onWrite(void callback()) {
|
| - if (_outputStream != null && callback != null) {
|
| - throw new StreamException(
|
| - "Cannot set write handler when output stream is used");
|
| - }
|
| - _onWrite = callback;
|
| - }
|
| -
|
| - void set _onWrite(void callback()) {
|
| - _socketWriteHandler = callback;
|
| - // Reset the one-shot onWrite handler.
|
| - socket.onWrite = _secureWriteHandler;
|
| + int available() {
|
| + if (_status != CONNECTED) return 0;
|
| + _readEncryptedData();
|
| + return _secureFilter.buffers[READ_PLAINTEXT].length;
|
| }
|
|
|
| - void set onBadCertificate(bool callback(X509Certificate certificate)) {
|
| - if (callback is! Function && callback != null) {
|
| - throw new SocketIOException(
|
| - "Callback provided to onBadCertificate is not a function or null");
|
| + void close() {
|
| + _closedWrite = true;
|
| + _closedRead = true;
|
| + if (_socket != null) {
|
| + _socket.close();
|
| }
|
| - secureFilter.registerBadCertificateCallback(callback);
|
| - }
|
| -
|
| - InputStream get inputStream {
|
| - if (_inputStream == null) {
|
| - if (_socketDataHandler != null || _socketCloseHandler != null) {
|
| - throw new StreamException(
|
| - "Cannot get input stream when socket handlers are used");
|
| - }
|
| - _inputStream = new _SocketInputStream(this);
|
| + _socketClosedWrite = true;
|
| + _socketClosedRead = true;
|
| + if (_secureFilter != null) {
|
| + _secureFilter.destroy();
|
| + _secureFilter = null;
|
| }
|
| - return _inputStream;
|
| - }
|
| -
|
| - OutputStream get outputStream {
|
| - if (_outputStream == null) {
|
| - if (_socketWriteHandler != null) {
|
| - throw new StreamException(
|
| - "Cannot get output stream when socket write handler is used");
|
| - }
|
| - _outputStream = new _SocketOutputStream(this);
|
| + if (_socketSubscription != null) {
|
| + _socketSubscription.cancel();
|
| }
|
| - return _outputStream;
|
| + _controller.close();
|
| + _status = CLOSED;
|
| }
|
|
|
| - int available() {
|
| - throw new UnimplementedError("SecureSocket.available not implemented yet");
|
| - }
|
| -
|
| - void close([bool halfClose = false]) {
|
| - if (_status == CLOSED) return;
|
| - if (halfClose) {
|
| + void shutdown(SocketDirection direction) {
|
| + if (direction == SocketDirection.BOTH) {
|
| + close();
|
| + } else if (direction == SocketDirection.SEND) {
|
| _closedWrite = true;
|
| _writeEncryptedData();
|
| if (_filterWriteEmpty) {
|
| - socket.close(true);
|
| + _socket.shutdown(SocketDirection.SEND);
|
| _socketClosedWrite = true;
|
| if (_closedRead) {
|
| - close(false);
|
| + close();
|
| }
|
| }
|
| - } else {
|
| - _closedWrite = true;
|
| + } else if (direction == SocketDirection.RECEIVE) {
|
| _closedRead = true;
|
| - socket.close(false);
|
| - _socketClosedWrite = true;
|
| _socketClosedRead = true;
|
| - secureFilter.destroy();
|
| - secureFilter = null;
|
| - if (scheduledDataEvent != null) {
|
| - scheduledDataEvent.cancel();
|
| + _socket.shutdown(SocketDirection.RECEIVE);
|
| + if (_socketClosedWrite) {
|
| + close();
|
| }
|
| - _status = CLOSED;
|
| }
|
| }
|
|
|
| - void _closeWrite() => close(true);
|
| + bool get writeEventsEnabled => _writeEventsEnabled;
|
| +
|
| + void set writeEventsEnabled(bool value) {
|
| + if (value &&
|
| + _secureFilter != null &&
|
| + _secureFilter.buffers[WRITE_PLAINTEXT].free > 0) {
|
| + new Timer(0, (_) => _controller.add(RawSocketEvent.WRITE));
|
| + } else {
|
| + _writeEventsEnabled = value;
|
| + }
|
| + }
|
| +
|
| + bool get readEventsEnabled => _readEventsEnabled;
|
| +
|
| + void set readEventsEnabled(bool value) {
|
| + _readEventsEnabled = value;
|
| + if (_socketClosedRead) {
|
| + if (value) {
|
| + // We have no underlying socket to set off read events.
|
| + new Timer(0, (_) => _readHandler());
|
| + }
|
| + }
|
| + }
|
|
|
| List<int> read([int len]) {
|
| if (_closedRead) {
|
| @@ -298,7 +413,7 @@ class _SecureSocket implements SecureSocket {
|
| if (_status != CONNECTED) {
|
| return new List<int>(0);
|
| }
|
| - var buffer = secureFilter.buffers[READ_PLAINTEXT];
|
| + var buffer = _secureFilter.buffers[READ_PLAINTEXT];
|
| _readEncryptedData();
|
| int toRead = buffer.length;
|
| if (len != null) {
|
| @@ -313,50 +428,41 @@ class _SecureSocket implements SecureSocket {
|
| List<int> result = (toRead == 0) ? null :
|
| buffer.data.getRange(buffer.start, toRead);
|
| buffer.advanceStart(toRead);
|
| - _setHandlersAfterRead();
|
| - return result;
|
| - }
|
|
|
| - int readList(List<int> data, int offset, int bytes) {
|
| - if (_closedRead) {
|
| - throw new SocketIOException("Reading from a closed socket");
|
| - }
|
| - if (offset < 0 || bytes < 0 || offset + bytes > data.length) {
|
| - throw new ArgumentError(
|
| - "Invalid offset or bytes in SecureSocket.readList");
|
| - }
|
| - if (_status != CONNECTED && _status != CLOSED) {
|
| - return 0;
|
| + // Set up a read event if the filter still has data.
|
| + if (!_filterReadEmpty) {
|
| + new Timer(0, (_) => _readHandler());
|
| }
|
|
|
| - int bytesRead = 0;
|
| - var buffer = secureFilter.buffers[READ_PLAINTEXT];
|
| - // TODO(whesse): Currently this fails if the if is turned into a while loop.
|
| - // Fix it so that it can loop and read more than one buffer's worth of data.
|
| - if (bytes > bytesRead) {
|
| - _readEncryptedData();
|
| - if (buffer.length > 0) {
|
| - int toRead = min(bytes - bytesRead, buffer.length);
|
| - data.setRange(offset, toRead, buffer.data, buffer.start);
|
| - buffer.advanceStart(toRead);
|
| - bytesRead += toRead;
|
| - offset += toRead;
|
| + if (_socketClosedRead) { // An onClose event is pending.
|
| + // _closedRead is false, since we are in a read call.
|
| + if (!_filterReadEmpty) {
|
| + // _filterReadEmpty may be out of date since read empties
|
| + // the plaintext buffer after calling _readEncryptedData.
|
| + // TODO(whesse): Fix this as part of fixing read.
|
| + _readEncryptedData();
|
| + }
|
| + if (_filterReadEmpty) {
|
| + // This can't be an else clause: the value of _filterReadEmpty changes.
|
| + // This must be asynchronous, because we are in a read call.
|
| + new Timer(0, (_) => _closeHandler());
|
| }
|
| }
|
|
|
| - _setHandlersAfterRead();
|
| - return bytesRead;
|
| + return result;
|
| }
|
|
|
| // Write the data to the socket, and flush it as much as possible
|
| // until it would block. If the write would block, _writeEncryptedData sets
|
| // up handlers to flush the pipeline when possible.
|
| - int writeList(List<int> data, int offset, int bytes) {
|
| + int write(List<int> data, [int offset, int bytes]) {
|
| if (_closedWrite) {
|
| - throw new SocketIOException("Writing to a closed socket");
|
| + _controller.signalError(new AsyncError(new SocketIOException(
|
| + "Writing to a closed socket")));
|
| + return 0;
|
| }
|
| if (_status != CONNECTED) return 0;
|
| - var buffer = secureFilter.buffers[WRITE_PLAINTEXT];
|
| + var buffer = _secureFilter.buffers[WRITE_PLAINTEXT];
|
| if (bytes > buffer.free) {
|
| bytes = buffer.free;
|
| }
|
| @@ -368,162 +474,180 @@ class _SecureSocket implements SecureSocket {
|
| return bytes;
|
| }
|
|
|
| - X509Certificate get peerCertificate => secureFilter.peerCertificate;
|
| -
|
| - void _secureConnectHandler() {
|
| - _connectPending = true;
|
| - secureFilter.connect(host,
|
| - port,
|
| - is_server,
|
| - certificateName,
|
| - requestClientCertificate || requireClientCertificate,
|
| - requireClientCertificate,
|
| - sendClientCertificate);
|
| - _status = HANDSHAKE;
|
| - _secureHandshake();
|
| - }
|
| + X509Certificate get peerCertificate => _secureFilter.peerCertificate;
|
|
|
| - void _secureWriteHandler() {
|
| + void _writeHandler() {
|
| + if (_status == CLOSED) return;
|
| _writeEncryptedData();
|
| if (_filterWriteEmpty && _closedWrite && !_socketClosedWrite) {
|
| - close(true);
|
| + // Close _socket for write, by calling shutdown(), to avoid cloning the
|
| + // socket closing code in shutdown().
|
| + shutdown(SocketDirection.SEND);
|
| }
|
| if (_status == HANDSHAKE) {
|
| - _secureHandshake();
|
| + try {
|
| + _secureHandshake();
|
| + } catch (e) { _reportError(e, "RawSecureSocket error"); }
|
| } else if (_status == CONNECTED &&
|
| - _socketWriteHandler != null &&
|
| - secureFilter.buffers[WRITE_PLAINTEXT].free > 0) {
|
| - // We must be able to set onWrite from the onWrite callback.
|
| - var handler = _socketWriteHandler;
|
| + _writeEventsEnabled &&
|
| + _secureFilter.buffers[WRITE_PLAINTEXT].free > 0) {
|
| // Reset the one-shot handler.
|
| - _socketWriteHandler = null;
|
| - handler();
|
| + _writeEventsEnabled = false;
|
| + _controller.add(RawSocketEvent.WRITE);
|
| }
|
| }
|
|
|
| - void _secureDataHandler() {
|
| - if (_status == HANDSHAKE) {
|
| + void _eventDispatcher(RawSocketEvent event) {
|
| + if (event == RawSocketEvent.READ) {
|
| + _readHandler();
|
| + } else if (event == RawSocketEvent.WRITE) {
|
| + _writeHandler();
|
| + } else if (event == RawSocketEvent.READ_CLOSED) {
|
| + _closeHandler();
|
| + }
|
| + }
|
| +
|
| + void _readHandler() {
|
| + if (_status == CLOSED) {
|
| + return;
|
| + } else if (_status == HANDSHAKE) {
|
| try {
|
| _secureHandshake();
|
| - } catch (e) { _reportError(e, "SecureSocket error"); }
|
| + if (_status != HANDSHAKE) _readHandler();
|
| + } catch (e) { _reportError(e, "RawSecureSocket error"); }
|
| } else {
|
| + if (_status != CONNECTED) {
|
| + // Cannot happen.
|
| + throw new SocketIOException("Internal SocketIO Error");
|
| + }
|
| try {
|
| - _writeEncryptedData(); // TODO(whesse): Removing this causes a failure.
|
| _readEncryptedData();
|
| - } catch (e) { _reportError(e, "SecureSocket error"); }
|
| + } catch (e) { _reportError(e, "RawSecureSocket error"); }
|
| if (!_filterReadEmpty) {
|
| - // Call the onData event.
|
| - if (scheduledDataEvent != null) {
|
| - scheduledDataEvent.cancel();
|
| - scheduledDataEvent = null;
|
| - }
|
| - if (_socketDataHandler != null) {
|
| - _socketDataHandler();
|
| + if (_readEventsEnabled) {
|
| + _controller.add(RawSocketEvent.READ);
|
| + if (_socketClosedRead) {
|
| + // Keep firing read events until we are paused or buffer is empty.
|
| + new Timer(0, (_) => _readHandler());
|
| + }
|
| }
|
| } else if (_socketClosedRead) {
|
| - _secureCloseHandler();
|
| + _closeHandler();
|
| }
|
| }
|
| }
|
|
|
| - void _secureErrorHandler(e) {
|
| - _reportError(e, 'Error on underlying Socket');
|
| + void _doneHandler() {
|
| + if (_filterReadEmpty) {
|
| + close();
|
| + }
|
| + }
|
| +
|
| + void _errorHandler(e) {
|
| + _reportError(e, 'Error on underlying RawSocket');
|
| }
|
|
|
| void _reportError(error, String message) {
|
| // TODO(whesse): Call _reportError from all internal functions that throw.
|
| var e;
|
| - if (error is SocketIOException) {
|
| + if (error is AsyncError) {
|
| + e = error;
|
| + } else if (error is SocketIOException) {
|
| e = new SocketIOException('$message (${error.message})', error.osError);
|
| } else if (error is OSError) {
|
| e = new SocketIOException(message, error);
|
| } else {
|
| e = new SocketIOException('$message (${error.toString()})', null);
|
| }
|
| - close(false);
|
| - bool reported = false;
|
| - if (_socketErrorHandler != null) {
|
| - reported = true;
|
| - _socketErrorHandler(e);
|
| - }
|
| - if (_inputStream != null) {
|
| - reported = reported || _inputStream._onSocketError(e);
|
| - }
|
| - if (_outputStream != null) {
|
| - reported = reported || _outputStream._onSocketError(e);
|
| + if (_connectPending) {
|
| + _handshakeComplete.completeError(e);
|
| + } else {
|
| + _controller.signalError(e);
|
| }
|
| - if (!reported) throw e;
|
| + close();
|
| }
|
|
|
| - void _secureCloseHandler() {
|
| - if (_closedRead) return;
|
| - _socketClosedRead = true;
|
| - if (_filterReadEmpty) {
|
| - _closedRead = true;
|
| - if (scheduledDataEvent != null) {
|
| - scheduledDataEvent.cancel();
|
| - }
|
| - if (_socketCloseHandler != null) {
|
| - _socketCloseHandler();
|
| - }
|
| - if (_socketClosedWrite) {
|
| - close(false);
|
| + void _closeHandler() {
|
| + if (_status == CONNECTED) {
|
| + if (_closedRead) return;
|
| + _socketClosedRead = true;
|
| + if (_filterReadEmpty) {
|
| + _closedRead = true;
|
| + _controller.add(RawSocketEvent.READ_CLOSED);
|
| + if (_socketClosedWrite) {
|
| + close();
|
| + }
|
| }
|
| + } else if (_status == HANDSHAKE) {
|
| + _reportError(
|
| + new SocketIOException('Connection terminated during handshake'),
|
| + 'handshake error');
|
| }
|
| }
|
|
|
| void _secureHandshake() {
|
| _readEncryptedData();
|
| - secureFilter.handshake();
|
| + _secureFilter.handshake();
|
| _writeEncryptedData();
|
| - if (secureFilter.buffers[WRITE_ENCRYPTED].length > 0) {
|
| - socket.onWrite = _secureWriteHandler;
|
| - }
|
| }
|
|
|
| void _secureHandshakeCompleteHandler() {
|
| _status = CONNECTED;
|
| - if (_connectPending && _socketConnectHandler != null) {
|
| + if (_connectPending) {
|
| _connectPending = false;
|
| - _socketConnectHandler();
|
| + // If we complete the future synchronously, user code will run here,
|
| + // and modify the state of the RawSecureSocket. For example, it
|
| + // could close the socket, and set _filter to null.
|
| + new Timer(0, (_) => _handshakeComplete.complete(this));
|
| }
|
| - if (_socketWriteHandler != null) {
|
| - socket.onWrite = _secureWriteHandler;
|
| + }
|
| +
|
| + void _onPauseStateChange() {
|
| + if (!_socketClosedRead || !_socketClosedWrite) {
|
| + if (_controller.isPaused) {
|
| + _socketSubscription.pause();
|
| + } else {
|
| + _socketSubscription.resume();
|
| + }
|
| }
|
| }
|
|
|
| - // True if the underlying socket is closed, the filter has been emptied of
|
| - // all data, and the close event has been fired.
|
| - get _closed => _socketClosed;
|
| + void _onSubscriptionStateChange() {
|
| + if (_controller.hasSubscribers) {
|
| + // TODO(ajohnsen): Do something here?
|
| + }
|
| + }
|
|
|
| void _readEncryptedData() {
|
| // Read from the socket, and push it through the filter as far as
|
| // possible.
|
| - var encrypted = secureFilter.buffers[READ_ENCRYPTED];
|
| - var plaintext = secureFilter.buffers[READ_PLAINTEXT];
|
| + var encrypted = _secureFilter.buffers[READ_ENCRYPTED];
|
| + var plaintext = _secureFilter.buffers[READ_PLAINTEXT];
|
| bool progress = true;
|
| while (progress) {
|
| progress = false;
|
| // Do not try to read plaintext from the filter while handshaking.
|
| if ((_status == CONNECTED) && plaintext.free > 0) {
|
| - int bytes = secureFilter.processBuffer(READ_PLAINTEXT);
|
| + int bytes = _secureFilter.processBuffer(READ_PLAINTEXT);
|
| if (bytes > 0) {
|
| plaintext.length += bytes;
|
| progress = true;
|
| }
|
| }
|
| if (encrypted.length > 0) {
|
| - int bytes = secureFilter.processBuffer(READ_ENCRYPTED);
|
| + int bytes = _secureFilter.processBuffer(READ_ENCRYPTED);
|
| if (bytes > 0) {
|
| encrypted.advanceStart(bytes);
|
| progress = true;
|
| }
|
| }
|
| - if (!_socketClosedRead) {
|
| - int bytes = socket.readList(encrypted.data,
|
| - encrypted.start + encrypted.length,
|
| - encrypted.free);
|
| - if (bytes > 0) {
|
| + if (!_socketClosedRead && encrypted.free > 0) {
|
| + List<int> data = _socket.read(encrypted.free);
|
| + if (data != null) {
|
| + int bytes = data.length;
|
| + encrypted.data.setRange(encrypted.start + encrypted.length,
|
| + bytes,
|
| + data);
|
| encrypted.length += bytes;
|
| progress = true;
|
| }
|
| @@ -538,29 +662,29 @@ class _SecureSocket implements SecureSocket {
|
|
|
| void _writeEncryptedData() {
|
| if (_socketClosedWrite) return;
|
| - var encrypted = secureFilter.buffers[WRITE_ENCRYPTED];
|
| - var plaintext = secureFilter.buffers[WRITE_PLAINTEXT];
|
| + var encrypted = _secureFilter.buffers[WRITE_ENCRYPTED];
|
| + var plaintext = _secureFilter.buffers[WRITE_PLAINTEXT];
|
| while (true) {
|
| if (encrypted.length > 0) {
|
| // Write from the filter to the socket.
|
| - int bytes = socket.writeList(encrypted.data,
|
| - encrypted.start,
|
| - encrypted.length);
|
| - if (bytes == 0) {
|
| + int bytes = _socket.write(encrypted.data,
|
| + encrypted.start,
|
| + encrypted.length);
|
| + encrypted.advanceStart(bytes);
|
| + if (encrypted.start < encrypted.length) {
|
| // The socket has blocked while we have data to write.
|
| // We must be notified when it becomes unblocked.
|
| - socket.onWrite = _secureWriteHandler;
|
| + _socket.writeEventsEnabled = true;
|
| _filterWriteEmpty = false;
|
| break;
|
| }
|
| - encrypted.advanceStart(bytes);
|
| } else {
|
| - var plaintext = secureFilter.buffers[WRITE_PLAINTEXT];
|
| + var plaintext = _secureFilter.buffers[WRITE_PLAINTEXT];
|
| if (plaintext.length > 0) {
|
| - int plaintext_bytes = secureFilter.processBuffer(WRITE_PLAINTEXT);
|
| + int plaintext_bytes = _secureFilter.processBuffer(WRITE_PLAINTEXT);
|
| plaintext.advanceStart(plaintext_bytes);
|
| }
|
| - int bytes = secureFilter.processBuffer(WRITE_ENCRYPTED);
|
| + int bytes = _secureFilter.processBuffer(WRITE_ENCRYPTED);
|
| if (bytes <= 0) {
|
| // We know the WRITE_ENCRYPTED buffer is empty, and the
|
| // filter wrote zero bytes to it, so the filter must be empty.
|
| @@ -574,69 +698,6 @@ class _SecureSocket implements SecureSocket {
|
| }
|
| }
|
| }
|
| -
|
| - /* After a read, the onData handler is enabled to fire again.
|
| - * We may also have a close event waiting for the SecureFilter to empty.
|
| - */
|
| - void _setHandlersAfterRead() {
|
| - // If the filter is empty, then we are guaranteed an event when it
|
| - // becomes unblocked. Cancel any _secureDataHandler call.
|
| - // Otherwise, schedule a _secureDataHandler call since there may data
|
| - // available, and this read call enables the data event.
|
| - if (_filterReadEmpty) {
|
| - if (scheduledDataEvent != null) {
|
| - scheduledDataEvent.cancel();
|
| - scheduledDataEvent = null;
|
| - }
|
| - } else if (scheduledDataEvent == null) {
|
| - scheduledDataEvent = Timer.run(_secureDataHandler);
|
| - }
|
| -
|
| - if (_socketClosedRead) { // An onClose event is pending.
|
| - // _closedRead is false, since we are in a read or readList call.
|
| - if (!_filterReadEmpty) {
|
| - // _filterReadEmpty may be out of date since read and readList empty
|
| - // the plaintext buffer after calling _readEncryptedData.
|
| - // TODO(whesse): Fix this as part of fixing read and readList.
|
| - _readEncryptedData();
|
| - }
|
| - if (_filterReadEmpty) {
|
| - // This can't be an else clause: the value of _filterReadEmpty changes.
|
| - // This must be asynchronous, because we are in a read or readList call.
|
| - Timer.run(_secureCloseHandler);
|
| - }
|
| - }
|
| - }
|
| -
|
| - bool get _socketClosed => _closedRead;
|
| -
|
| - // _SecureSocket cannot extend _Socket and use _Socket's factory constructor.
|
| - Socket socket;
|
| - final String host;
|
| - final bool is_server;
|
| - final String certificateName;
|
| - final bool requestClientCertificate;
|
| - final bool requireClientCertificate;
|
| - final bool sendClientCertificate;
|
| -
|
| - var _status = NOT_CONNECTED;
|
| - bool _socketClosedRead = false; // The network socket is closed for reading.
|
| - bool _socketClosedWrite = false; // The network socket is closed for writing.
|
| - bool _closedRead = false; // The secure socket has fired an onClosed event.
|
| - bool _closedWrite = false; // The secure socket has been closed for writing.
|
| - bool _filterReadEmpty = true; // There is no buffered data to read.
|
| - bool _filterWriteEmpty = true; // There is no buffered data to be written.
|
| - _SocketInputStream _inputStream;
|
| - _SocketOutputStream _outputStream;
|
| - bool _connectPending = false;
|
| - Function _socketConnectHandler;
|
| - Function _socketWriteHandler;
|
| - Function _socketDataHandler;
|
| - Function _socketErrorHandler;
|
| - Function _socketCloseHandler;
|
| - Timer scheduledDataEvent;
|
| -
|
| - _SecureFilter secureFilter;
|
| }
|
|
|
|
|
|
|