Implement input and output streams for secure network sockets.

BUG=dart:6701

Review URL: https://codereview.chromium.org//11416108

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@15267 260f80e4-7a28-3924-810f-c04153c831b5
This commit is contained in:
whesse@google.com 2012-11-22 16:37:10 +00:00
parent 218c3ed3ff
commit 0807c2662b
4 changed files with 211 additions and 9 deletions

View file

@ -465,7 +465,7 @@ class _Socket extends _SocketBase implements Socket {
}
void set onConnect(void callback()) {
if (_seenFirstOutEvent || _outputStream != null) {
if (_seenFirstOutEvent) {
throw new StreamException(
"Cannot set connect handler when already connected");
}
@ -509,7 +509,7 @@ class _Socket extends _SocketBase implements Socket {
if (_outputStream == null) {
if (_handlerMap[_SocketBase._OUT_EVENT] != null) {
throw new StreamException(
"Cannot get input stream when socket handlers are used");
"Cannot get output stream when socket handlers are used");
}
_outputStream = new _SocketOutputStream(this);
}

View file

@ -82,11 +82,6 @@ class _TlsSocket implements TlsSocket {
_tlsFilter.registerHandshakeCompleteCallback(_tlsHandshakeCompleteHandler);
}
InputStream get inputStream {
// TODO(6701): Implement stream interfaces on TlsSocket.
throw new UnimplementedError("TlsSocket.inputStream not implemented yet");
}
int get port => _socket.port;
String get remoteHost => _socket.remoteHost;
@ -94,26 +89,79 @@ class _TlsSocket implements TlsSocket {
int get remotePort => _socket.remotePort;
void set onClosed(void callback()) {
if (_inputStream != null) {
throw new StreamException(
"Cannot set close handler when input stream is used");
}
_onClosed = callback;
}
void set _onClosed(void callback()) {
_socketCloseHandler = callback;
}
void set onConnect(void callback()) {
if (_outputStream != null) {
throw new StreamException(
"Cannot set connect handler when output stream is used");
}
if (_status == CONNECTED || _status == CLOSED) {
throw new StreamException(
"Cannot set connect handler when already connected");
}
_onConnect = callback;
}
void set _onConnect(void callback()) {
_socketConnectHandler = callback;
}
void set onData(void callback()) {
if (_outputStream != null) {
throw new StreamException(
"Cannot set data handler when input stream is used");
}
_onData = callback;
}
void set _onData(void callback()) {
_socketDataHandler = callback;
}
void set onWrite(void callback()) {
if (_outputStream != 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 = _tlsWriteHandler;
}
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);
}
return _inputStream;
}
OutputStream get outputStream {
// TODO(6701): Implement stream interfaces on TlsSocket.
throw new UnimplementedError("TlsSocket.inputStream not implemented yet");
if (_outputStream == null) {
if (_socketConnectHandler != null || _socketWriteHandler != null) {
throw new StreamException(
"Cannot get output stream when socket handlers are used");
}
_outputStream = new _SocketOutputStream(this);
}
return _outputStream;
}
int available() {
@ -143,10 +191,15 @@ class _TlsSocket implements TlsSocket {
}
}
void _closeWrite() => close(true);
List<int> read([int len]) {
if (_closedRead) {
throw new SocketException("Reading from a closed socket");
}
if (_status != CONNECTED) {
return new List<int>(0);
}
var buffer = _tlsFilter.buffers[READ_PLAINTEXT];
_readEncryptedData();
int toRead = buffer.length;
@ -173,6 +226,9 @@ class _TlsSocket implements TlsSocket {
throw new ArgumentError(
"Invalid offset or bytes in TlsSocket.readList");
}
if (_status != CONNECTED && _status != CLOSED) {
return 0;
}
int bytesRead = 0;
var buffer = _tlsFilter.buffers[READ_PLAINTEXT];
@ -200,6 +256,7 @@ class _TlsSocket implements TlsSocket {
if (_closedWrite) {
throw new SocketException("Writing to a closed socket");
}
if (_status != CONNECTED) return 0;
var buffer = _tlsFilter.buffers[WRITE_PLAINTEXT];
if (bytes > buffer.free) {
bytes = buffer.free;
@ -285,8 +342,15 @@ class _TlsSocket implements TlsSocket {
_connectPending = false;
_socketConnectHandler();
}
if (_socketWriteHandler != null) {
_socket.onWrite = _tlsWriteHandler;
}
}
// 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 && !_fireCloseEventPending;
void _fireCloseEvent() {
if (scheduledDataEvent != null) {
scheduledDataEvent.cancel();
@ -408,6 +472,8 @@ class _TlsSocket implements TlsSocket {
}
}
bool get _socketClosed => _closedRead;
// _TlsSocket cannot extend _Socket and use _Socket's factory constructor.
Socket _socket;
String _host;
@ -422,6 +488,8 @@ class _TlsSocket implements TlsSocket {
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;

View file

@ -0,0 +1,98 @@
// Copyright (c) 2012, 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.
import "dart:io";
const SERVER_ADDRESS = "127.0.0.1";
const HOST_NAME = "localhost";
class TlsTestServer {
void onConnection(Socket connection) {
numConnections++;
var input = connection.inputStream;
String received = "";
input.onData = () {
received = received.concat(new String.fromCharCodes(input.read()));
};
input.onClosed = () {
Expect.isTrue(received.contains("Hello from client "));
String name = received.substring(received.indexOf("client ") + 7);
connection.outputStream.write("Welcome, client $name".charCodes);
connection.outputStream.close();
};
}
void errorHandlerServer(Exception e) {
Expect.fail("Server socket error $e");
}
int start() {
server = new TlsServerSocket(SERVER_ADDRESS, 0, 10, "CN=$HOST_NAME");
Expect.isNotNull(server);
server.onConnection = onConnection;
server.onError = errorHandlerServer;
return server.port;
}
void stop() {
server.close();
}
int numConnections = 0;
TlsServerSocket server;
}
class TlsTestClient {
TlsTestClient(int this.port, String this.name) {
socket = new TlsSocket(HOST_NAME, port);
numRequests++;
socket.outputStream.write("Hello from client $name".charCodes);
socket.outputStream.close();
socket.inputStream.onData = () {
reply = reply.concat(new String.fromCharCodes(socket.inputStream.read()));
};
socket.inputStream.onClosed = this.done;
reply = "";
}
void done() {
Expect.equals("Welcome, client $name", reply);
numReplies++;
if (numReplies == CLIENT_NAMES.length) {
Expect.equals(numRequests, numReplies);
EndTest();
}
}
static int numRequests = 0;
static int numReplies = 0;
int port;
String name;
TlsSocket socket;
String reply;
}
Function EndTest;
const CLIENT_NAMES = const ['able', 'baker', 'camera', 'donut', 'echo'];
void main() {
Path scriptDir = new Path.fromNative(new Options().script).directoryPath;
Path certificateDatabase = scriptDir.append('pkcert');
TlsSocket.setCertificateDatabase(certificateDatabase.toNativePath(),
'dartdart');
var server = new TlsTestServer();
int port = server.start();
EndTest = () {
Expect.equals(CLIENT_NAMES.length, server.numConnections);
server.stop();
};
for (var x in CLIENT_NAMES) {
new TlsTestClient(port, x);
}
}

View file

@ -0,0 +1,36 @@
// Copyright (c) 2012, 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
// The --short_socket_write option does not work with external server
// www.google.dk. Add this to the test when we have secure server sockets.
// See TODO below.
#import("dart:isolate");
#import("dart:io");
void main() {
var testPkcertDatabase =
new Path.fromNative(new Options().script).directoryPath.append('pkcert/');
TlsSocket.setCertificateDatabase(testPkcertDatabase.toNativePath());
// TODO(3593): Use a Dart HTTPS server for this test using TLS server sockets.
// When we use a Dart HTTPS server, allow --short_socket_write. The flag
// causes fragmentation of the client hello message, which doesn't seem to
// work with www.google.dk.
var tls = new TlsSocket("www.google.dk", 443);
List<String> chunks = <String>[];
var input = tls.inputStream;
var output = tls.outputStream;
output.write("GET / HTTP/1.0\r\nHost: www.google.dk\r\n\r\n".charCodes);
output.close();
input.onData = () {
chunks.add(new String.fromCharCodes(input.read()));
};
input.onClosed = () {
String fullPage = Strings.concatAll(chunks);
Expect.isTrue(fullPage.contains('</body></html>'));
};
}