diff --git a/doc/api/child_process.markdown b/doc/api/child_process.markdown
index 85c9c8b8d4..4dfa7d8c6c 100644
--- a/doc/api/child_process.markdown
+++ b/doc/api/child_process.markdown
@@ -225,7 +225,7 @@ that some connections will be handled by the parent and some by the child.
For `dgram` servers the workflow is exactly the same. Here you listen on
a `message` event instead of `connection` and use `server.bind` instead of
-`server.listen`.
+`server.listen`. (Currently only supported on UNIX platforms.)
#### Example: sending socket object
diff --git a/doc/api/https.markdown b/doc/api/https.markdown
index 47db0c23c6..9371db0a88 100644
--- a/doc/api/https.markdown
+++ b/doc/api/https.markdown
@@ -134,6 +134,9 @@ The following options from [tls.connect()][] can also be specified. However, a
the list of supplied CAs. An `'error'` event is emitted if verification
fails. Verification happens at the connection level, *before* the HTTP
request is sent. Default `true`.
+- `secureProtocol`: The SSL method to use, e.g. `SSLv3_method` to force
+ SSL version 3. The possible values depend on your installation of
+ OpenSSL and are defined in the constant [SSL_METHODS][].
In order to specify these options, use a custom `Agent`.
@@ -217,3 +220,4 @@ Global instance of [https.Agent][] for all HTTPS client requests.
[https.request()]: #https_https_request_options_callback
[tls.connect()]: tls.html#tls_tls_connect_options_callback
[tls.createServer()]: tls.html#tls_tls_createserver_options_secureconnectionlistener
+[SSL_METHODS]: http://www.openssl.org/docs/ssl/ssl.html#DEALING_WITH_PROTOCOL_METHODS
diff --git a/doc/api/tls.markdown b/doc/api/tls.markdown
index 00cacad28a..536343d9ed 100644
--- a/doc/api/tls.markdown
+++ b/doc/api/tls.markdown
@@ -278,6 +278,10 @@ Creates a new client connection to the given `port` and `host` (old API) or
- `servername`: Servername for SNI (Server Name Indication) TLS extension.
+ - `secureProtocol`: The SSL method to use, e.g. `SSLv3_method` to force
+ SSL version 3. The possible values depend on your installation of
+ OpenSSL and are defined in the constant [SSL_METHODS][].
+
The `callback` parameter will be added as a listener for the
['secureConnect'][] event.
@@ -572,5 +576,6 @@ The numeric representation of the local port.
['secureConnect']: #tls_event_secureconnect
[secureConnection]: #tls_event_secureconnection
[Stream]: stream.html#stream_stream
+[SSL_METHODS]: http://www.openssl.org/docs/ssl/ssl.html#DEALING_WITH_PROTOCOL_METHODS
[tls.Server]: #tls_class_tls_server
[SSL_CTX_set_timeout]: http://www.openssl.org/docs/ssl/SSL_CTX_set_timeout.html
diff --git a/doc/community/index.html b/doc/community/index.html
index e47182d59e..5451b86141 100644
--- a/doc/community/index.html
+++ b/doc/community/index.html
@@ -54,14 +54,6 @@
Official API docs detail the
node API.
-
- Node Manual offers
- stylized API docs, as well as tutorials and live code samples
-
-
- Node Bits provides sample
- Node.js projects
-
docs.nodejitsu.com
answers many of the common problems people come across.
@@ -187,9 +179,11 @@
OctoberSkyJs Korea Node.js
community
FR . Node.js
- Google+ Community of Node.js French users
+ Google+ Community of Node.js French users
Node.js Türkiye
- Node.js in Turkish
+ Node.js in Turkish
+ NodeBR.com
+ Brazilian community of Node.js
diff --git a/lib/tls.js b/lib/tls.js
index 4f170324b9..92ec579fb9 100644
--- a/lib/tls.js
+++ b/lib/tls.js
@@ -250,6 +250,8 @@ function CryptoStream(pair, options) {
this._pendingCallback = null;
this._doneFlag = false;
this._retryAfterPartial = false;
+ this._halfRead = false;
+ this._sslOutCb = null;
this._resumingSession = false;
this._reading = true;
this._destroyed = false;
@@ -278,8 +280,14 @@ function onCryptoStreamFinish() {
// NOTE: first call checks if client has sent us shutdown,
// second call enqueues shutdown into the BIO.
if (this.pair.ssl.shutdown() !== 1) {
+ if (this.pair.ssl && this.pair.ssl.error)
+ return this.pair.error();
+
this.pair.ssl.shutdown();
}
+
+ if (this.pair.ssl && this.pair.ssl.error)
+ return this.pair.error();
}
} else {
debug('encrypted.onfinish');
@@ -309,6 +317,19 @@ function onCryptoStreamEnd() {
}
+// NOTE: Called once `this._opposite` is set.
+CryptoStream.prototype.init = function init() {
+ var self = this;
+ this._opposite.on('sslOutEnd', function() {
+ if (self._sslOutCb) {
+ var cb = self._sslOutCb;
+ self._sslOutCb = null;
+ cb(null);
+ }
+ });
+};
+
+
CryptoStream.prototype._write = function write(data, encoding, cb) {
assert(this._pending === null);
@@ -337,28 +358,36 @@ CryptoStream.prototype._write = function write(data, encoding, cb) {
return cb(this.pair.error(true));
}
+ // Whole buffer was written
+ if (written === data.length) {
+ if (this === this.pair.cleartext) {
+ debug('cleartext.write succeed with ' + written + ' bytes');
+ } else {
+ debug('encrypted.write succeed with ' + written + ' bytes');
+ }
+
+ // Invoke callback only when all data read from opposite stream
+ if (this._opposite._halfRead) {
+ assert(this._sslOutCb === null);
+ this._sslOutCb = cb;
+ } else {
+ cb(null);
+ }
+ }
+
// Force SSL_read call to cycle some states/data inside OpenSSL
this.pair.cleartext.read(0);
// Cycle encrypted data
- if (this.pair.encrypted._internallyPendingBytes()) {
+ if (this.pair.encrypted._internallyPendingBytes())
this.pair.encrypted.read(0);
- }
// Get NPN and Server name when ready
this.pair.maybeInitFinished();
- // Whole buffer was written
if (written === data.length) {
- if (this === this.pair.cleartext) {
- debug('cleartext.write succeed with %d bytes', data.length);
- } else {
- debug('encrypted.write succeed with %d bytes', data.length);
- }
-
- return cb(null);
- }
- if (written !== 0 && written !== -1) {
+ return;
+ } else if (written !== 0 && written !== -1) {
assert(!this._retryAfterPartial);
this._retryAfterPartial = true;
this._write(data.slice(written), encoding, cb);
@@ -460,25 +489,42 @@ CryptoStream.prototype._read = function read(size) {
this._opposite._done();
// EOF
- return this.push(null);
+ this.push(null);
+ } else {
+ // Bail out
+ this.push('');
}
-
- // Bail out
- return this.push('');
+ } else {
+ // Give them requested data
+ if (this.ondata) {
+ var self = this;
+ this.ondata(pool, start, start + bytesRead);
+
+ // Consume data automatically
+ // simple/test-https-drain fails without it
+ process.nextTick(function() {
+ self.read(bytesRead);
+ });
+ }
+ this.push(pool.slice(start, start + bytesRead));
}
- // Give them requested data
- if (this.ondata) {
- var self = this;
- this.ondata(pool, start, start + bytesRead);
+ // Let users know that we've some internal data to read
+ var halfRead = this._internallyPendingBytes() !== 0;
- // Consume data automatically
- // simple/test-https-drain fails without it
- process.nextTick(function() {
- self.read(bytesRead);
- });
+ // Smart check to avoid invoking 'sslOutEnd' in the most of the cases
+ if (this._halfRead !== halfRead) {
+ this._halfRead = halfRead;
+
+ // Notify listeners about internal data end
+ if (this === this.pair.cleartext) {
+ debug('cleartext.sslOutEnd');
+ } else {
+ debug('encrypted.sslOutEnd');
+ }
+
+ this.emit('sslOutEnd');
}
- return this.push(pool.slice(start, start + bytesRead));
};
@@ -590,7 +636,7 @@ CryptoStream.prototype.destroySoon = function(err) {
if (this.writable)
this.end();
- if (this._writableState.finished)
+ if (this._writableState.finished && this._opposite._ended)
this.destroy();
else
this.once('finish', this.destroy);
@@ -871,6 +917,8 @@ function SecurePair(credentials, isServer, requestCert, rejectUnauthorized,
/* Let streams know about each other */
this.cleartext._opposite = this.encrypted;
this.encrypted._opposite = this.cleartext;
+ this.cleartext.init();
+ this.encrypted.init();
process.nextTick(function() {
/* The Connection may be destroyed by an abort call */
diff --git a/src/node_crypto.cc b/src/node_crypto.cc
index 2decdef2b5..2cc684490b 100644
--- a/src/node_crypto.cc
+++ b/src/node_crypto.cc
@@ -934,7 +934,10 @@ int Connection::HandleBIOError(BIO *bio, const char* func, int rv) {
}
-int Connection::HandleSSLError(const char* func, int rv, ZeroStatus zs) {
+int Connection::HandleSSLError(const char* func,
+ int rv,
+ ZeroStatus zs,
+ SyscallStatus ss) {
ClearErrorOnReturn clear_error_on_return;
(void) &clear_error_on_return; // Silence unused variable warning.
@@ -959,6 +962,9 @@ int Connection::HandleSSLError(const char* func, int rv, ZeroStatus zs) {
Exception::Error(String::New("ZERO_RETURN")));
return rv;
+ } else if ((err == SSL_ERROR_SYSCALL) && (ss == kIgnoreSyscall)) {
+ return 0;
+
} else {
HandleScope scope(node_isolate);
BUF_MEM* mem;
@@ -1396,17 +1402,26 @@ Handle Connection::ClearOut(const Arguments& args) {
if (ss->is_server_) {
rv = SSL_accept(ss->ssl_);
- ss->HandleSSLError("SSL_accept:ClearOut", rv, kZeroIsAnError);
+ ss->HandleSSLError("SSL_accept:ClearOut",
+ rv,
+ kZeroIsAnError,
+ kSyscallError);
} else {
rv = SSL_connect(ss->ssl_);
- ss->HandleSSLError("SSL_connect:ClearOut", rv, kZeroIsAnError);
+ ss->HandleSSLError("SSL_connect:ClearOut",
+ rv,
+ kZeroIsAnError,
+ kSyscallError);
}
if (rv < 0) return scope.Close(Integer::New(rv, node_isolate));
}
int bytes_read = SSL_read(ss->ssl_, buffer_data + off, len);
- ss->HandleSSLError("SSL_read:ClearOut", bytes_read, kZeroIsNotAnError);
+ ss->HandleSSLError("SSL_read:ClearOut",
+ bytes_read,
+ kZeroIsNotAnError,
+ kSyscallError);
ss->SetShutdownFlags();
return scope.Close(Integer::New(bytes_read, node_isolate));
@@ -1496,10 +1511,16 @@ Handle Connection::ClearIn(const Arguments& args) {
int rv;
if (ss->is_server_) {
rv = SSL_accept(ss->ssl_);
- ss->HandleSSLError("SSL_accept:ClearIn", rv, kZeroIsAnError);
+ ss->HandleSSLError("SSL_accept:ClearIn",
+ rv,
+ kZeroIsAnError,
+ kSyscallError);
} else {
rv = SSL_connect(ss->ssl_);
- ss->HandleSSLError("SSL_connect:ClearIn", rv, kZeroIsAnError);
+ ss->HandleSSLError("SSL_connect:ClearIn",
+ rv,
+ kZeroIsAnError,
+ kSyscallError);
}
if (rv < 0) return scope.Close(Integer::New(rv, node_isolate));
@@ -1509,7 +1530,8 @@ Handle Connection::ClearIn(const Arguments& args) {
ss->HandleSSLError("SSL_write:ClearIn",
bytes_written,
- len == 0 ? kZeroIsNotAnError : kZeroIsAnError);
+ len == 0 ? kZeroIsNotAnError : kZeroIsAnError,
+ kSyscallError);
ss->SetShutdownFlags();
return scope.Close(Integer::New(bytes_written, node_isolate));
@@ -1749,10 +1771,13 @@ Handle Connection::Start(const Arguments& args) {
int rv;
if (ss->is_server_) {
rv = SSL_accept(ss->ssl_);
- ss->HandleSSLError("SSL_accept:Start", rv, kZeroIsAnError);
+ ss->HandleSSLError("SSL_accept:Start", rv, kZeroIsAnError, kSyscallError);
} else {
rv = SSL_connect(ss->ssl_);
- ss->HandleSSLError("SSL_connect:Start", rv, kZeroIsAnError);
+ ss->HandleSSLError("SSL_connect:Start",
+ rv,
+ kZeroIsAnError,
+ kSyscallError);
}
return scope.Close(Integer::New(rv, node_isolate));
@@ -1769,7 +1794,7 @@ Handle Connection::Shutdown(const Arguments& args) {
if (ss->ssl_ == NULL) return False(node_isolate);
int rv = SSL_shutdown(ss->ssl_);
- ss->HandleSSLError("SSL_shutdown", rv, kZeroIsNotAnError);
+ ss->HandleSSLError("SSL_shutdown", rv, kZeroIsNotAnError, kIgnoreSyscall);
ss->SetShutdownFlags();
return scope.Close(Integer::New(rv, node_isolate));
diff --git a/src/node_crypto.h b/src/node_crypto.h
index b15600dac3..2d5500a4e1 100644
--- a/src/node_crypto.h
+++ b/src/node_crypto.h
@@ -219,7 +219,12 @@ class Connection : ObjectWrap {
kZeroIsAnError
};
- int HandleSSLError(const char* func, int rv, ZeroStatus zs);
+ enum SyscallStatus {
+ kIgnoreSyscall,
+ kSyscallError
+ };
+
+ int HandleSSLError(const char* func, int rv, ZeroStatus zs, SyscallStatus ss);
void ClearError();
void SetShutdownFlags();
diff --git a/test/simple/test-tls-client-destroy-soon.js b/test/simple/test-tls-client-destroy-soon.js
new file mode 100644
index 0000000000..529b84a734
--- /dev/null
+++ b/test/simple/test-tls-client-destroy-soon.js
@@ -0,0 +1,75 @@
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// Create an ssl server. First connection, validate that not resume.
+// Cache session and close connection. Use session on second connection.
+// ASSERT resumption.
+
+if (!process.versions.openssl) {
+ console.error('Skipping because node compiled without OpenSSL.');
+ process.exit(0);
+}
+
+var common = require('../common');
+var assert = require('assert');
+var tls = require('tls');
+var fs = require('fs');
+
+var options = {
+ key: fs.readFileSync(common.fixturesDir + '/keys/agent2-key.pem'),
+ cert: fs.readFileSync(common.fixturesDir + '/keys/agent2-cert.pem')
+};
+
+var big = new Buffer(2 * 1024 * 1024);
+var connections = 0;
+var bytesRead = 0;
+
+big.fill('Y');
+
+// create server
+var server = tls.createServer(options, function(socket) {
+ socket.end(big);
+ socket.destroySoon();
+ connections++;
+});
+
+// start listening
+server.listen(common.PORT, function() {
+ var client = tls.connect({
+ port: common.PORT,
+ rejectUnauthorized: false
+ }, function() {
+ client.on('readable', function() {
+ var d = client.read();
+ if (d)
+ bytesRead += d.length;
+ });
+
+ client.on('end', function() {
+ server.close();
+ });
+ });
+});
+
+process.on('exit', function() {
+ assert.equal(1, connections);
+ assert.equal(big.length, bytesRead);
+});