- 2013.10.30, Version 0.11.8 (Unstable)
-2013.11.12, Version 0.10.22 (Stable)
++2013.10.30, Version 0.11.8 (Unstable), f8d86e24f3463c36f7f3f4c3b3ec779e5b6201e1
+
+* uv: Upgrade to v0.11.14
+
+* v8: upgrade 3.21.18.3
+
+* assert: indicate if exception message is generated (Glen Mailer)
+
+* buffer: add buf.toArrayBuffer() API (Trevor Norris)
+
+* cluster: fix premature 'disconnect' event (Ben Noordhuis)
+
+* crypto: add SPKAC support (Jason Gerfen)
+
+* debugger: count space for line numbers correctly (Alex Kocharin)
+
+* debugger: make busy loops SIGUSR1-interruptible (Ben Noordhuis)
+
+* debugger: repeat last command (Alex Kocharin)
+
+* debugger: show current line, fix for #6150 (Alex Kocharin)
+
+* dgram: send() can accept strings (Trevor Norris)
+
+* dns: rename domain to hostname (Ben Noordhuis)
+
+* dns: set hostname property on error object (Ben Noordhuis)
+
+* dtrace, mdb_v8: support more string, frame types (Dave Pacheco)
+
+* http: add statusMessage (Patrik Stutz)
+
+* http: expose supported methods (Ben Noordhuis)
+
+* http: provide backpressure for pipeline flood (isaacs)
+
+* process: Add exitCode property (isaacs)
+
+* tls: socket.renegotiate(options, callback) (Fedor Indutny)
+
+* util: format as Error if instanceof Error (Rod Vagg)
+
+
+2013.08.21, Version 0.11.7 (Unstable), be52549bfa5311208b5fcdb3ba09210460fa9ceb
+
+* uv: upgrade to v0.11.13
+
+* v8: upgrade to 3.20.17
+
+* buffer: adhere to INSPECT_MAX_BYTES (Timothy J Fontaine)
+
+* buffer: fix regression for large buffer creation (Trevor Norris)
+
+* buffer: don't throw if slice length too long (Trevor Norris)
+
+* buffer: Buffer(buf) constructor copies into the proper buffer (Ben Noordhuis)
+
+* cli: remove --max-stack-size (Ben Noordhuis)
+
+* cli: unknown command line options are errors (Ben Noordhuis)
+
+* child_process: exec accept buffer as an encoding (Seth Fitzsimmons)
+
+* crypto: make randomBytes/pbkdf2 callbacks domain aware (Ben Noordhuis)
+
+* domain: deprecate domain.dispose(). (Forrest L Norvell)
+
+* fs: Expose birthtime on stat objects (isaacs)
+
+* http: Only send connection:keep-alive if necessary (isaacs)
+
+* repl: Catch syntax errors better (isaacs, Nathan Rajlich)
+
+* stream: change default highWaterMark for objectMode to 16 (Mathias Buus)
+
+* stream: make setEncoding/pause/resume chainable (Julian Gruber, isaacs)
+
+* util: pass opts to custom inspect functions (Timothy J Fontaine)
+
+* vm: rewritten to behave like Contextify (Domenic Denicola)
+
+
+2013.08.21, Version 0.11.6 (Unstable), 04018d4b3938fd30ba14822e79195e4af2be36f6
+
+* uv: Upgrade to v0.11.8
+
+* v8: upgrade v8 to 3.20.14.1
+
+* build: disable SSLv2 by default (Ben Noordhuis)
+
+* build: don't auto-destroy existing configuration (Ben Noordhuis)
+
+* crypto: add TLS 1.1 and 1.2 to secureProtocol list (Matthias Bartelmeß)
+
+* crypto: fix memory leak in randomBytes() error path (Ben Noordhuis)
+
+* dgram: don't call into js when send cb is omitted (Ben Noordhuis)
+
+* dgram: fix regression in string argument handling (Ben Noordhuis)
+
+* domains: performance improvements (Trevor Norris)
+
+* events: EventEmitter = require('events') (Jake Verbaten)
+
+* http: Add write()/end() callbacks (isaacs)
+
+* http: Consistent 'finish' event semantics (isaacs)
+
+* http: Prefer 'binary' over 'ascii' (isaacs)
+
+* http: Support legacy agent.addRequest API (isaacs)
+
+* http: Write hex/base64 chunks properly (isaacs)
+
+* http: add agent.maxFreeSockets option (isaacs)
+
+* http: provide access to raw headers/trailers (isaacs)
+
+* http: removed headers stay removed (James Halliday)
+
+* http,timers: improve callback performance (Ben Noordhuis)
+
+* net: family option in net.connect (Vsevolod Strukchinsky)
+
+* readline: pause stdin before turning off terminal raw mode (Daniel Chatfield)
+
+* smalloc: allow different external array types (Trevor Norris)
+
+* smalloc: expose ExternalArraySize (Trevor Norris)
+
+* stream: Short-circuit buffer pushes when flowing (isaacs)
+
+* tls: handle errors on socket before releasing it (Fedor Indutny)
+
+* util: fix isPrimitive check (Trevor Norris)
+
+* util: isObject should always return boolean (Trevor Norris)
+
+
+2013.08.06, Version 0.11.5 (Unstable), 6f92da2dd106b0c63fde563284f83e08e2a521b5
+
+* v8: upgrade to 3.20.11
+
+* uv: upgrade to v0.11.7
+
+* buffer: return offset for end of last write (Trevor Norris)
+
+* build: embed the mdb_v8.so into the binary (Timothy J Fontaine)
+
+* build: fix --without-ssl build (Ben Noordhuis)
+
+* child_process: add 'shell' option to .exec() (Ben Noordhuis)
+
+* dgram: report send errors to cb, don't pass bytes (Ben Noordhuis)
+
+* fs: write strings directly to disk (Trevor Norris)
+
+* https: fix default port (Koichi Kobayashi)
+
+* openssl: use asm for sha, md5, rmd (Fedor Indutny)
+
+* os: add mac address to networkInterfaces() output (Brian White)
+
+* smalloc: introduce smalloc module (Trevor Norris)
+
+* stream: Simplify flowing, passive data listening (streams3) (isaacs)
+
+* tls: asynchronous SNICallback (Fedor Indutny)
+
+* tls: share tls tickets key between cluster workers (Fedor Indutny)
+
+* util: don't throw on circular %j input to format() (Ben Noordhuis)
+
+
+2013.07.12, Version 0.11.4 (Unstable), b5b84197ed037918fd1a26e5cb87cce7c812ca55
+
+* npm: Upgrade to 1.3.4
+
+* v8: Upgrade to v3.20.2
+
+* c-ares: Upgrade to piscisaureus/cares@805d153
+
+* timers: setImmediate process full queue each turn (Ben Noordhuis)
+
+* http: Add agent.get/request methods (isaacs)
+
+* http: Proper KeepAlive behavior (isaacs)
+
+* configure: fix the --without-ssl option (Nathan Rajlich)
+
+* buffer: propagate originating parent (Trevor Norris)
+
+* tls_wrap: return Error not throw for missing cert (Timothy J Fontaine)
+
+* src: enable native v8 typed arrays (Ben Noordhuis)
+
+* stream: objectMode transform should allow falsey values (Jeff Barczewski)
+
+* slab_allocator: remove SlabAllocator (Trevor Norris)
+
+* crypto: fix memory leak in LoadPKCS12 (Fedor Indutny)
+
+* tls: export TLSSocket (Fedor Indutny)
+
+* zlib: allow changing of level and strategy (Brian White)
+
+* zlib: allow custom flush type for flush() (Brian White)
+
+
+2013.06.26, Version 0.11.3 (Unstable), 38c0c47bbe280ddc42054418091571e532d82a1e
+
+* uv: Upgrade to v0.11.5
+
+* c-ares: upgrade to 1.10.0
+
+* v8: upgrade to v3.19.13
+
+* punycode: update to v1.2.3 (Mathias Bynens)
+
+* debugger: break on uncaught exception (Miroslav Bajtos)
+
+* child_process: emit 'disconnect' asynchronously (Ben Noordhuis)
+
+* dtrace: enable uv's probes if enabled (Timothy J Fontaine)
+
+* dtrace: unify dtrace and systemtap interfaces (Timothy J Fontaine)
+
+* buffer: New API for backing data store (Trevor Norris)
+
+* buffer: return `this` in fill() for chainability (Brian White)
+
+* build: fix include order for building on windows (Timothy J Fontaine)
+
+* build: add android support (Linus Mårtensson)
+
+* readline: strip ctrl chars for prompt width calc (Krzysztof Chrapka)
+
+* tls: introduce TLSSocket based on tls_wrap binding (Fedor Indutny)
+
+* tls: add localAddress and localPort properties (Ben Noordhuis)
+
+* crypto: free excessive memory in NodeBIO (Fedor Indutny)
+
+* process: remove maxTickDepth (Trevor Norris)
+
+* timers: use uv_now instead of Date.now (Timothy J Fontaine)
+
+* util: Add debuglog, deprecate console lookalikes (isaacs)
+
+* module: use path.sep instead of a custom solution (Robert Kowalski)
+
+* http: don't escape request path, reject bad chars (Ben Noordhuis)
+
+* net: emit dns 'lookup' event before connect (Ben Noordhuis)
+
+* dns: add getServers and setServers (Timothy J Fontaine)
+
+
+2013.05.13, Version 0.11.2 (Unstable), 5d3dc0e4c3369dfb00b7b13e08936c2e652fa696
+
+* uv: Upgrade to 0.11.2
+
+* V8: Upgrade to 3.19.0
+
+* npm: Upgrade to 1.2.21
+
+* build: Makefile should respect configure --prefix (Timothy J Fontaine)
+
+* cluster: use round-robin load balancing (Ben Noordhuis)
+
+* debugger, cluster: each worker has new debug port (Miroslav Bajtoš)
+
+* debugger: `restart` with custom debug port (Miroslav Bajtoš)
+
+* debugger: breakpoints in scripts not loaded yet (Miroslav Bajtoš)
+
+* event: EventEmitter#setMaxListeners() returns this (Sam Roberts)
+
+* events: add EventEmitter.defaultMaxListeners (Ben Noordhuis)
+
+* install: Support $(PREFIX) install target directory prefix (Olof Johansson)
+
+* os: Include netmask in os.networkInterfaces() (Ben Kelly)
+
+* path: add path.isAbsolute(path) (Ryan Doenges)
+
+* stream: Guarantee ordering of 'finish' event (isaacs)
+
+* streams: introduce .cork/.uncork/._writev (Fedor Indutny)
+
+* vm: add support for timeout argument (Andrew Paprocki)
+
+
+2013.04.19, Version 0.11.1 (Unstable), 4babd2b46ebf9fbea2c9946af5cfae25a33b2b22
+
+* V8: upgrade to 3.18.0
+
+* uv: Upgrade to v0.11.1
+
+* http: split into multiple separate modules (Timothy J Fontaine)
+
+* http: escape unsafe characters in request path (Ben Noordhuis)
+
+* url: Escape all unwise characters (isaacs)
+
+* build: depend on v8 postmortem-metadata if enabled (Paddy Byers)
+
+* etw: update prototypes to match dtrace provider (Timothy J Fontaine)
+
+* buffer: change output of Buffer.prototype.toJSON() (David Braun)
+
+* dtrace: actually use the _handle.fd value (Timothy J Fontaine)
+
+* dtrace: pass more arguments to probes (Dave Pacheco)
+
+* build: allow building with dtrace on osx (Dave Pacheco)
+
+* zlib: allow passing options to convenience methods (Kyle Robinson Young)
+
+
+2013.03.28, Version 0.11.0 (Unstable), bce38b3d74e64fcb7d04a2dd551151da6168cdc5
+
+* V8: update to 3.17.13
+
+* os: use %SystemRoot% or %windir% in os.tmpdir() (Suwon Chae)
+
+* util: fix util.inspect() line width calculation (Marcin Kostrzewa)
+
+* buffer: remove _charsWritten (Trevor Norris)
+
+* fs: uv_[fl]stat now reports subsecond resolution (Timothy J Fontaine)
+
+* fs: Throw if error raised and missing callback (bnoordhuis)
+
+* tls: expose SSL_CTX_set_timeout via tls.createServer (Manav Rathi)
+
+* tls: remove harmful unnecessary bounds checking (Marcel Laverdet)
+
+* buffer: write ascii strings using WriteOneByte (Trevor Norris)
+
+* dtrace: fix generation of v8 constants on freebsd (Fedor Indutny)
+
+* dtrace: x64 ustack helper (Fedor Indutny)
+
+* readline: handle wide characters properly (Nao Iizuka)
+
+* repl: Use a domain to catch async errors safely (isaacs)
+
+* repl: emit 'reset' event when context is reset (Sami Samhuri)
+
+* util: custom `inspect()` method may return an Object (Nathan Rajlich)
+
+* console: `console.dir()` bypasses inspect() methods (Nathan Rajlich)
+
+
++2013.11.12, Version 0.10.22 (Stable), cbff8f091c22fb1df6b238c7a1b9145db950fa65
+
+ * npm: Upgrade to 1.3.14
+
+ * uv: Upgrade to v0.10.19
+
+ * child_process: don't assert on stale file descriptor events (Fedor Indutny)
+
+ * darwin: Fix "Not Responding" in Mavericks activity monitor (Fedor Indutny)
+
+ * debugger: Fix bug in sb() with unnamed script (Maxim Bogushevich)
+
+ * repl: do not insert duplicates into completions (Maciej Małecki)
+
+ * src: Fix memory leak on closed handles (Timothy J Fontaine)
+
+ * tls: prevent stalls by using read(0) (Fedor Indutny)
+
+ * v8: use correct timezone information on Solaris (Maciej Małecki)
+
+
2013.10.18, Version 0.10.21 (Stable), e2da042844a830fafb8031f6c477eb4f96195210
* uv: Upgrade to v0.10.18
--- /dev/null
+// 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.
+
+var assert = require('assert');
+var crypto = require('crypto');
+var events = require('events');
+var stream = require('stream');
+var tls = require('tls');
+var util = require('util');
+
+var Timer = process.binding('timer_wrap').Timer;
+var Connection = null;
+try {
+ Connection = process.binding('crypto').Connection;
+} catch (e) {
+ throw new Error('node.js not compiled with openssl crypto support.');
+}
+
+var debug = util.debuglog('tls-legacy');
+
+function SlabBuffer() {
+ this.create();
+}
+
+
+SlabBuffer.prototype.create = function create() {
+ this.isFull = false;
+ this.pool = new Buffer(tls.SLAB_BUFFER_SIZE);
+ this.offset = 0;
+ this.remaining = this.pool.length;
+};
+
+
+SlabBuffer.prototype.use = function use(context, fn, size) {
+ if (this.remaining === 0) {
+ this.isFull = true;
+ return 0;
+ }
+
+ var actualSize = this.remaining;
+
+ if (!util.isNull(size)) actualSize = Math.min(size, actualSize);
+
+ var bytes = fn.call(context, this.pool, this.offset, actualSize);
+ if (bytes > 0) {
+ this.offset += bytes;
+ this.remaining -= bytes;
+ }
+
+ assert(this.remaining >= 0);
+
+ return bytes;
+};
+
+
+var slabBuffer = null;
+
+
+// Base class of both CleartextStream and EncryptedStream
+function CryptoStream(pair, options) {
+ stream.Duplex.call(this, options);
+
+ this.pair = pair;
+ this._pending = null;
+ this._pendingEncoding = '';
+ this._pendingCallback = null;
+ this._doneFlag = false;
+ this._retryAfterPartial = false;
+ this._halfRead = false;
+ this._sslOutCb = null;
+ this._resumingSession = false;
+ this._reading = true;
+ this._destroyed = false;
+ this._ended = false;
+ this._finished = false;
+ this._opposite = null;
+
+ if (util.isNull(slabBuffer)) slabBuffer = new SlabBuffer();
+ this._buffer = slabBuffer;
+
+ this.once('finish', onCryptoStreamFinish);
+
+ // net.Socket calls .onend too
+ this.once('end', onCryptoStreamEnd);
+}
+util.inherits(CryptoStream, stream.Duplex);
+
+
+function onCryptoStreamFinish() {
+ this._finished = true;
+
+ if (this === this.pair.cleartext) {
+ debug('cleartext.onfinish');
+ if (this.pair.ssl) {
+ // Generate close notify
+ // 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');
+ }
+
+ // Try to read just to get sure that we won't miss EOF
+ if (this._opposite.readable) this._opposite.read(0);
+
+ if (this._opposite._ended) {
+ this._done();
+
+ // No half-close, sorry
+ if (this === this.pair.cleartext) this._opposite._done();
+ }
+}
+
+
+function onCryptoStreamEnd() {
+ this._ended = true;
+ if (this === this.pair.cleartext) {
+ debug('cleartext.onend');
+ } else {
+ debug('encrypted.onend');
+ }
+}
+
+
+// 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(util.isNull(this._pending));
+
+ // Black-hole data
+ if (!this.pair.ssl) return cb(null);
+
+ // When resuming session don't accept any new data.
+ // And do not put too much data into openssl, before writing it from encrypted
+ // side.
+ //
+ // TODO(indutny): Remove magic number, use watermark based limits
+ if (!this._resumingSession &&
+ this._opposite._internallyPendingBytes() < 128 * 1024) {
+ // Write current buffer now
+ var written;
+ if (this === this.pair.cleartext) {
+ debug('cleartext.write called with %d bytes', data.length);
+ written = this.pair.ssl.clearIn(data, 0, data.length);
+ } else {
+ debug('encrypted.write called with %d bytes', data.length);
+ written = this.pair.ssl.encIn(data, 0, data.length);
+ }
+
+ // Handle and report errors
+ if (this.pair.ssl && this.pair.ssl.error) {
+ return cb(this.pair.error(true));
+ }
+
+ // Force SSL_read call to cycle some states/data inside OpenSSL
+ this.pair.cleartext.read(0);
+
+ // Cycle encrypted data
+ 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 ' + 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(util.isNull(this._sslOutCb));
+ this._sslOutCb = cb;
+ } else {
+ cb(null);
+ }
+ return;
+ } else if (written !== 0 && written !== -1) {
+ assert(!this._retryAfterPartial);
+ this._retryAfterPartial = true;
+ this._write(data.slice(written), encoding, cb);
+ this._retryAfterPartial = false;
+ return;
+ }
+ } else {
+ debug('cleartext.write queue is full');
+
+ // Force SSL_read call to cycle some states/data inside OpenSSL
+ this.pair.cleartext.read(0);
+ }
+
+ // No write has happened
+ this._pending = data;
+ this._pendingEncoding = encoding;
+ this._pendingCallback = cb;
+
+ if (this === this.pair.cleartext) {
+ debug('cleartext.write queued with %d bytes', data.length);
+ } else {
+ debug('encrypted.write queued with %d bytes', data.length);
+ }
+};
+
+
+CryptoStream.prototype._writePending = function writePending() {
+ var data = this._pending,
+ encoding = this._pendingEncoding,
+ cb = this._pendingCallback;
+
+ this._pending = null;
+ this._pendingEncoding = '';
+ this._pendingCallback = null;
+ this._write(data, encoding, cb);
+};
+
+
+CryptoStream.prototype._read = function read(size) {
+ // XXX: EOF?!
+ if (!this.pair.ssl) return this.push(null);
+
+ // Wait for session to be resumed
+ // Mark that we're done reading, but don't provide data or EOF
+ if (this._resumingSession || !this._reading) return this.push('');
+
+ var out;
+ if (this === this.pair.cleartext) {
+ debug('cleartext.read called with %d bytes', size);
+ out = this.pair.ssl.clearOut;
+ } else {
+ debug('encrypted.read called with %d bytes', size);
+ out = this.pair.ssl.encOut;
+ }
+
+ var bytesRead = 0,
+ start = this._buffer.offset;
+ do {
+ var read = this._buffer.use(this.pair.ssl, out, size - bytesRead);
+ if (read > 0) {
+ bytesRead += read;
+ }
+
+ // Handle and report errors
+ if (this.pair.ssl && this.pair.ssl.error) {
+ this.pair.error();
+ break;
+ }
+
+ // Get NPN and Server name when ready
+ this.pair.maybeInitFinished();
+
+ // `maybeInitFinished()` can emit the 'secure' event which
+ // in turn destroys the connection in case of authentication
+ // failure and sets `this.pair.ssl` to `null`.
+ } while (read > 0 &&
+ !this._buffer.isFull &&
+ bytesRead < size &&
+ this.pair.ssl !== null);
+
+ // Create new buffer if previous was filled up
+ var pool = this._buffer.pool;
+ if (this._buffer.isFull) this._buffer.create();
+
+ assert(bytesRead >= 0);
+
+ if (this === this.pair.cleartext) {
+ debug('cleartext.read succeed with %d bytes', bytesRead);
+ } else {
+ debug('encrypted.read succeed with %d bytes', bytesRead);
+ }
+
+ // Try writing pending data
+ if (!util.isNull(this._pending)) this._writePending();
+ if (!util.isNull(this._opposite._pending)) this._opposite._writePending();
+
+ if (bytesRead === 0) {
+ // EOF when cleartext has finished and we have nothing to read
+ if (this._opposite._finished && this._internallyPendingBytes() === 0) {
+ // Perform graceful shutdown
+ this._done();
+
+ // No half-open, sorry!
+ if (this === this.pair.cleartext)
+ this._opposite._done();
+
+ // EOF
+ this.push(null);
+ } else {
+ // Bail out
+ this.push('');
+ }
+ } else {
+ // Give them requested data
+ this.push(pool.slice(start, start + bytesRead));
+ }
+
+ // Let users know that we've some internal data to read
+ var halfRead = this._internallyPendingBytes() !== 0;
+
+ // 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 (!halfRead) {
+ if (this === this.pair.cleartext) {
+ debug('cleartext.sslOutEnd');
+ } else {
+ debug('encrypted.sslOutEnd');
+ }
+
+ this.emit('sslOutEnd');
+ }
+ }
+};
+
+
+CryptoStream.prototype.setTimeout = function(timeout, callback) {
+ if (this.socket) this.socket.setTimeout(timeout, callback);
+};
+
+
+CryptoStream.prototype.setNoDelay = function(noDelay) {
+ if (this.socket) this.socket.setNoDelay(noDelay);
+};
+
+
+CryptoStream.prototype.setKeepAlive = function(enable, initialDelay) {
+ if (this.socket) this.socket.setKeepAlive(enable, initialDelay);
+};
+
+CryptoStream.prototype.__defineGetter__('bytesWritten', function() {
+ return this.socket ? this.socket.bytesWritten : 0;
+});
+
+CryptoStream.prototype.getPeerCertificate = function() {
+ if (this.pair.ssl) {
+ var c = this.pair.ssl.getPeerCertificate();
+
+ if (c) {
+ if (c.issuer) c.issuer = tls.parseCertString(c.issuer);
+ if (c.subject) c.subject = tls.parseCertString(c.subject);
+ return c;
+ }
+ }
+
+ return null;
+};
+
+CryptoStream.prototype.getSession = function() {
+ if (this.pair.ssl) {
+ return this.pair.ssl.getSession();
+ }
+
+ return null;
+};
+
+CryptoStream.prototype.isSessionReused = function() {
+ if (this.pair.ssl) {
+ return this.pair.ssl.isSessionReused();
+ }
+
+ return null;
+};
+
+CryptoStream.prototype.getCipher = function(err) {
+ if (this.pair.ssl) {
+ return this.pair.ssl.getCurrentCipher();
+ } else {
+ return null;
+ }
+};
+
+
+CryptoStream.prototype.end = function(chunk, encoding) {
+ if (this === this.pair.cleartext) {
+ debug('cleartext.end');
+ } else {
+ debug('encrypted.end');
+ }
+
+ // Write pending data first
+ if (!util.isNull(this._pending)) this._writePending();
+
+ this.writable = false;
+
+ stream.Duplex.prototype.end.call(this, chunk, encoding);
+};
+
+
+CryptoStream.prototype.destroySoon = function(err) {
+ if (this === this.pair.cleartext) {
+ debug('cleartext.destroySoon');
+ } else {
+ debug('encrypted.destroySoon');
+ }
+
+ if (this.writable)
+ this.end();
+
+ if (this._writableState.finished && this._opposite._ended) {
+ this.destroy();
+ } else {
+ // Wait for both `finish` and `end` events to ensure that all data that
+ // was written on this side was read from the other side.
+ var self = this;
+ var waiting = 1;
+ function finish() {
+ if (--waiting === 0) self.destroy();
+ }
+ this._opposite.once('end', finish);
+ if (!this._finished) {
+ this.once('finish', finish);
+ ++waiting;
+ }
+ }
+};
+
+
+CryptoStream.prototype.destroy = function(err) {
+ if (this._destroyed) return;
+ this._destroyed = true;
+ this.readable = this.writable = false;
+
+ // Destroy both ends
+ if (this === this.pair.cleartext) {
+ debug('cleartext.destroy');
+ } else {
+ debug('encrypted.destroy');
+ }
+ this._opposite.destroy();
+
+ var self = this;
+ process.nextTick(function() {
+ // Force EOF
+ self.push(null);
+
+ // Emit 'close' event
+ self.emit('close', err ? true : false);
+ });
+};
+
+
+CryptoStream.prototype._done = function() {
+ this._doneFlag = true;
+
+ if (this === this.pair.encrypted && !this.pair._secureEstablished)
+ return this.pair.error();
+
+ if (this.pair.cleartext._doneFlag &&
+ this.pair.encrypted._doneFlag &&
+ !this.pair._doneFlag) {
+ // If both streams are done:
+ this.pair.destroy();
+ }
+};
+
+
+// readyState is deprecated. Don't use it.
+Object.defineProperty(CryptoStream.prototype, 'readyState', {
+ get: function() {
+ if (this._connecting) {
+ return 'opening';
+ } else if (this.readable && this.writable) {
+ return 'open';
+ } else if (this.readable && !this.writable) {
+ return 'readOnly';
+ } else if (!this.readable && this.writable) {
+ return 'writeOnly';
+ } else {
+ return 'closed';
+ }
+ }
+});
+
+
+function CleartextStream(pair, options) {
+ CryptoStream.call(this, pair, options);
+
+ // This is a fake kludge to support how the http impl sits
+ // on top of net Sockets
+ var self = this;
+ this._handle = {
+ readStop: function() {
+ self._reading = false;
+ },
+ readStart: function() {
+ if (self._reading && self._readableState.length > 0) return;
+ self._reading = true;
+ self.read(0);
+ if (self._opposite.readable) self._opposite.read(0);
+ }
+ };
+}
+util.inherits(CleartextStream, CryptoStream);
+
+
+CleartextStream.prototype._internallyPendingBytes = function() {
+ if (this.pair.ssl) {
+ return this.pair.ssl.clearPending();
+ } else {
+ return 0;
+ }
+};
+
+
+CleartextStream.prototype.address = function() {
+ return this.socket && this.socket.address();
+};
+
+
+CleartextStream.prototype.__defineGetter__('remoteAddress', function() {
+ return this.socket && this.socket.remoteAddress;
+});
+
+
+CleartextStream.prototype.__defineGetter__('remotePort', function() {
+ return this.socket && this.socket.remotePort;
+});
+
+
+CleartextStream.prototype.__defineGetter__('localAddress', function() {
+ return this.socket && this.socket.localAddress;
+});
+
+
+CleartextStream.prototype.__defineGetter__('localPort', function() {
+ return this.socket && this.socket.localPort;
+});
+
+
+function EncryptedStream(pair, options) {
+ CryptoStream.call(this, pair, options);
+}
+util.inherits(EncryptedStream, CryptoStream);
+
+
+EncryptedStream.prototype._internallyPendingBytes = function() {
+ if (this.pair.ssl) {
+ return this.pair.ssl.encPending();
+ } else {
+ return 0;
+ }
+};
+
+
+function onhandshakestart() {
+ debug('onhandshakestart');
+
+ var self = this;
+ var ssl = self.ssl;
+ var now = Timer.now();
+
+ assert(now >= ssl.lastHandshakeTime);
+
+ if ((now - ssl.lastHandshakeTime) >= tls.CLIENT_RENEG_WINDOW * 1000) {
+ ssl.handshakes = 0;
+ }
+
+ var first = (ssl.lastHandshakeTime === 0);
+ ssl.lastHandshakeTime = now;
+ if (first) return;
+
+ if (++ssl.handshakes > tls.CLIENT_RENEG_LIMIT) {
+ // Defer the error event to the next tick. We're being called from OpenSSL's
+ // state machine and OpenSSL is not re-entrant. We cannot allow the user's
+ // callback to destroy the connection right now, it would crash and burn.
+ setImmediate(function() {
+ var err = new Error('TLS session renegotiation attack detected.');
+ if (self.cleartext) self.cleartext.emit('error', err);
+ });
+ }
+}
+
+
+function onhandshakedone() {
+ // for future use
+ debug('onhandshakedone');
+}
+
+
+function onclienthello(hello) {
+ var self = this,
+ once = false;
+
+ this._resumingSession = true;
+ function callback(err, session) {
+ if (once) return;
+ once = true;
+
+ if (err) return self.socket.destroy(err);
+
+ self.ssl.loadSession(session);
+ self.ssl.endParser();
+
+ // Cycle data
+ self._resumingSession = false;
+ self.cleartext.read(0);
+ self.encrypted.read(0);
+ }
+
+ if (hello.sessionId.length <= 0 ||
+ !this.server ||
+ !this.server.emit('resumeSession', hello.sessionId, callback)) {
+ callback(null, null);
+ }
+}
+
+
+function onnewsession(key, session) {
+ if (!this.server) return;
+ this.server.emit('newSession', key, session);
+}
+
+
+/**
+ * Provides a pair of streams to do encrypted communication.
+ */
+
+function SecurePair(credentials, isServer, requestCert, rejectUnauthorized,
+ options) {
+ if (!(this instanceof SecurePair)) {
+ return new SecurePair(credentials,
+ isServer,
+ requestCert,
+ rejectUnauthorized,
+ options);
+ }
+
+ var self = this;
+
+ options || (options = {});
+
+ events.EventEmitter.call(this);
+
+ this.server = options.server;
+ this._secureEstablished = false;
+ this._isServer = isServer ? true : false;
+ this._encWriteState = true;
+ this._clearWriteState = true;
+ this._doneFlag = false;
+ this._destroying = false;
+
+ if (!credentials) {
+ this.credentials = crypto.createCredentials();
+ } else {
+ this.credentials = credentials;
+ }
+
+ if (!this._isServer) {
+ // For clients, we will always have either a given ca list or be using
+ // default one
+ requestCert = true;
+ }
+
+ this._rejectUnauthorized = rejectUnauthorized ? true : false;
+ this._requestCert = requestCert ? true : false;
+
+ this.ssl = new Connection(this.credentials.context,
+ this._isServer ? true : false,
+ this._isServer ? this._requestCert :
+ options.servername,
+ this._rejectUnauthorized);
+
+ if (this._isServer) {
+ this.ssl.onhandshakestart = onhandshakestart.bind(this);
+ this.ssl.onhandshakedone = onhandshakedone.bind(this);
+ this.ssl.onclienthello = onclienthello.bind(this);
+ this.ssl.onnewsession = onnewsession.bind(this);
+ this.ssl.lastHandshakeTime = 0;
+ this.ssl.handshakes = 0;
+ }
+
+ if (process.features.tls_sni) {
+ if (this._isServer && options.SNICallback) {
+ this.ssl.setSNICallback(options.SNICallback);
+ }
+ this.servername = null;
+ }
+
+ if (process.features.tls_npn && options.NPNProtocols) {
+ this.ssl.setNPNProtocols(options.NPNProtocols);
+ this.npnProtocol = null;
+ }
+
+ /* Acts as a r/w stream to the cleartext side of the stream. */
+ this.cleartext = new CleartextStream(this, options.cleartext);
+
+ /* Acts as a r/w stream to the encrypted side of the stream. */
+ this.encrypted = new EncryptedStream(this, options.encrypted);
+
+ /* 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 */
+ if (self.ssl) {
+ self.ssl.start();
++
++ /* In case of cipher suite failures - SSL_accept/SSL_connect may fail */
++ if (self.ssl && self.ssl.error)
++ self.error();
+ }
+ });
+}
+
+util.inherits(SecurePair, events.EventEmitter);
+
+
+exports.createSecurePair = function(credentials,
+ isServer,
+ requestCert,
+ rejectUnauthorized) {
+ var pair = new SecurePair(credentials,
+ isServer,
+ requestCert,
+ rejectUnauthorized);
+ return pair;
+};
+
+
+SecurePair.prototype.maybeInitFinished = function() {
+ if (this.ssl && !this._secureEstablished && this.ssl.isInitFinished()) {
+ if (process.features.tls_npn) {
+ this.npnProtocol = this.ssl.getNegotiatedProtocol();
+ }
+
+ if (process.features.tls_sni) {
+ this.servername = this.ssl.getServername();
+ }
+
+ this._secureEstablished = true;
+ debug('secure established');
+ this.emit('secure');
+ }
+};
+
+
+SecurePair.prototype.destroy = function() {
+ if (this._destroying) return;
+
+ if (!this._doneFlag) {
+ debug('SecurePair.destroy');
+ this._destroying = true;
+
+ // SecurePair should be destroyed only after it's streams
+ this.cleartext.destroy();
+ this.encrypted.destroy();
+
+ this._doneFlag = true;
+ this.ssl.error = null;
+ this.ssl.close();
+ this.ssl = null;
+ }
+};
+
+
+SecurePair.prototype.error = function(returnOnly) {
+ var err = this.ssl.error;
+ this.ssl.error = null;
+
+ if (!this._secureEstablished) {
+ // Emit ECONNRESET instead of zero return
+ if (!err || err.message === 'ZERO_RETURN') {
+ var connReset = new Error('socket hang up');
+ connReset.code = 'ECONNRESET';
+ connReset.sslError = err && err.message;
+
+ err = connReset;
+ }
+ this.destroy();
+ if (!returnOnly) this.emit('error', err);
+ } else if (this._isServer &&
+ this._rejectUnauthorized &&
+ /peer did not return a certificate/.test(err.message)) {
+ // Not really an error.
+ this.destroy();
+ } else {
+ if (!returnOnly) this.cleartext.emit('error', err);
+ }
+ return err;
+};
+
+
+exports.pipe = function pipe(pair, socket) {
+ pair.encrypted.pipe(socket);
+ socket.pipe(pair.encrypted);
+
+ pair.encrypted.on('close', function() {
+ process.nextTick(function() {
+ // Encrypted should be unpiped from socket to prevent possible
+ // write after destroy.
+ pair.encrypted.unpipe(socket);
+ socket.destroySoon();
+ });
+ });
+
+ pair.fd = socket.fd;
+ var cleartext = pair.cleartext;
+ cleartext.socket = socket;
+ cleartext.encrypted = pair.encrypted;
+ cleartext.authorized = false;
+
+ // cycle the data whenever the socket drains, so that
+ // we can pull some more into it. normally this would
+ // be handled by the fact that pipe() triggers read() calls
+ // on writable.drain, but CryptoStreams are a bit more
+ // complicated. Since the encrypted side actually gets
+ // its data from the cleartext side, we have to give it a
+ // light kick to get in motion again.
+ socket.on('drain', function() {
+ if (pair.encrypted._pending)
+ pair.encrypted._writePending();
+ if (pair.cleartext._pending)
+ pair.cleartext._writePending();
+ pair.encrypted.read(0);
+ pair.cleartext.read(0);
+ });
+
+ function onerror(e) {
+ if (cleartext._controlReleased) {
+ cleartext.emit('error', e);
+ }
+ }
+
+ function onclose() {
+ socket.removeListener('error', onerror);
+ socket.removeListener('timeout', ontimeout);
+ }
+
+ function ontimeout() {
+ cleartext.emit('timeout');
+ }
+
+ socket.on('error', onerror);
+ socket.on('close', onclose);
+ socket.on('timeout', ontimeout);
+
+ return cleartext;
+};