Kiyoshi Nomo <tokyoincidents.g@gmail.com>
Veres Lajos <vlajos@gmail.com>
Yuan Chuan <yuanchuan23@gmail.com>
+Krzysztof Chrapka <chrapka.k@gmail.com>
+Linus Mårtensson <linus.martensson@sonymobile.com>
+ Peter Rust <peter@cornerstonenw.com>
-2013.07.09, Version 0.10.13 (Stable)
+2013.06.26, Version 0.11.3 (Unstable)
+
+* 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.07.09, Version 0.10.13 (Stable), e32660a984427d46af6a144983cf7b8045b7299c
+
+ * uv: Upgrade to v0.10.12
+
+ * npm: Upgrade to 1.3.2
+
+ * windows: get proper errno (Ben Noordhuis)
+
+ * tls: only wait for finish if we haven't seen it (Timothy J Fontaine)
+
+ * http: Dump response when request is aborted (isaacs)
+
+ * http: use an unref'd timer to fix delay in exit (Peter Rust)
+
+ * zlib: level can be negative (Brian White)
+
+ * zlib: allow zero values for level and strategy (Brian White)
+
+ * buffer: add comment explaining buffer alignment (Ben Noordhuis)
+
+ * string_bytes: properly detect 64bit (Timothy J Fontaine)
+
+ * src: fix memory leak in UsingDomains() (Ben Noordhuis)
+
+
-2013.06.18, Version 0.10.12 (Stable)
+2013.06.18, Version 0.10.12 (Stable), a088cf4f930d3928c97d239adf950ab43e7794aa
* npm: Upgrade to 1.2.32
--- /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 util = require('util');
+var net = require('net');
+var EventEmitter = require('events').EventEmitter;
+var HTTPParser = process.binding('http_parser').HTTPParser;
+var assert = require('assert').ok;
+
+var common = require('_http_common');
+
+var httpSocketSetup = common.httpSocketSetup;
+var parsers = common.parsers;
+var freeParser = common.freeParser;
+var debug = common.debug;
+
+var IncomingMessage = require('_http_incoming').IncomingMessage;
+var OutgoingMessage = require('_http_outgoing').OutgoingMessage;
+
+var agent = require('_http_agent');
+var Agent = agent.Agent;
+var globalAgent = agent.globalAgent;
+
+
+function ClientRequest(options, cb) {
+ var self = this;
+ OutgoingMessage.call(self);
+
+ self.agent = options.agent === undefined ? globalAgent : options.agent;
+
+ var defaultPort = options.defaultPort || 80;
+
+ var port = options.port || defaultPort;
+ var host = options.hostname || options.host || 'localhost';
+
+ if (options.setHost === undefined) {
+ var setHost = true;
+ }
+
+ self.socketPath = options.socketPath;
+
+ var method = self.method = (options.method || 'GET').toUpperCase();
+ self.path = options.path || '/';
+ if (cb) {
+ self.once('response', cb);
+ }
+
+ if (!Array.isArray(options.headers)) {
+ if (options.headers) {
+ var keys = Object.keys(options.headers);
+ for (var i = 0, l = keys.length; i < l; i++) {
+ var key = keys[i];
+ self.setHeader(key, options.headers[key]);
+ }
+ }
+ if (host && !this.getHeader('host') && setHost) {
+ var hostHeader = host;
+ if (port && +port !== defaultPort) {
+ hostHeader += ':' + port;
+ }
+ this.setHeader('Host', hostHeader);
+ }
+ }
+
+ if (options.auth && !this.getHeader('Authorization')) {
+ //basic auth
+ this.setHeader('Authorization', 'Basic ' +
+ new Buffer(options.auth).toString('base64'));
+ }
+
+ if (method === 'GET' || method === 'HEAD' || method === 'CONNECT') {
+ self.useChunkedEncodingByDefault = false;
+ } else {
+ self.useChunkedEncodingByDefault = true;
+ }
+
+ if (Array.isArray(options.headers)) {
+ self._storeHeader(self.method + ' ' + self.path + ' HTTP/1.1\r\n',
+ options.headers);
+ } else if (self.getHeader('expect')) {
+ self._storeHeader(self.method + ' ' + self.path + ' HTTP/1.1\r\n',
+ self._renderHeaders());
+ }
+
+ if (self.socketPath) {
+ self._last = true;
+ self.shouldKeepAlive = false;
+ var conn = self.agent.createConnection({ path: self.socketPath });
+ self.onSocket(conn);
+ } else if (self.agent) {
+ // If there is an agent we should default to Connection:keep-alive.
+ self._last = false;
+ self.shouldKeepAlive = true;
+ self.agent.addRequest(self, options);
+ } else {
+ // No agent, default to Connection:close.
+ self._last = true;
+ self.shouldKeepAlive = false;
+ if (options.createConnection) {
+ var conn = options.createConnection(options);
+ } else {
+ debug('CLIENT use net.createConnection', options);
+ var conn = net.createConnection(options);
+ }
+ self.onSocket(conn);
+ }
+
+ self._deferToConnect(null, null, function() {
+ self._flush();
+ self = null;
+ });
+}
+
+util.inherits(ClientRequest, OutgoingMessage);
+
+exports.ClientRequest = ClientRequest;
+
+ClientRequest.prototype._finish = function() {
+ DTRACE_HTTP_CLIENT_REQUEST(this, this.connection);
+ COUNTER_HTTP_CLIENT_REQUEST();
+ OutgoingMessage.prototype._finish.call(this);
+};
+
+ClientRequest.prototype._implicitHeader = function() {
+ this._storeHeader(this.method + ' ' + this.path + ' HTTP/1.1\r\n',
+ this._renderHeaders());
+};
+
+ClientRequest.prototype.abort = function() {
++// If we're aborting, we don't care about any more response data.
++ if (this.res)
++ this.res._dump();
++ else
++ this.once('response', function(res) {
++ res._dump();
++ });
++
+ if (this.socket) {
+ // in-progress
+ this.socket.destroy();
+ } else {
+ // haven't been assigned a socket yet.
+ // this could be more efficient, it could
+ // remove itself from the pending requests
+ this._deferToConnect('destroy', []);
+ }
+};
+
+
+function createHangUpError() {
+ var error = new Error('socket hang up');
+ error.code = 'ECONNRESET';
+ return error;
+}
+
+
+function socketCloseListener() {
+ var socket = this;
+ var parser = socket.parser;
+ var req = socket._httpMessage;
+ debug('HTTP socket close');
+ req.emit('close');
+ if (req.res && req.res.readable) {
+ // Socket closed before we emitted 'end' below.
+ req.res.emit('aborted');
+ var res = req.res;
+ res.on('end', function() {
+ res.emit('close');
+ });
+ res.push(null);
+ } else if (!req.res && !req.socket._hadError) {
+ // This socket error fired before we started to
+ // receive a response. The error needs to
+ // fire on the request.
+ req.emit('error', createHangUpError());
+ req.socket._hadError = true;
+ }
+
+ // Too bad. That output wasn't getting written.
+ // This is pretty terrible that it doesn't raise an error.
+ // Fixed better in v0.10
+ if (req.output)
+ req.output.length = 0;
+ if (req.outputEncodings)
+ req.outputEncodings.length = 0;
+
+ if (parser) {
+ parser.finish();
+ freeParser(parser, req);
+ }
+}
+
+function socketErrorListener(err) {
+ var socket = this;
+ var parser = socket.parser;
+ var req = socket._httpMessage;
+ debug('SOCKET ERROR:', err.message, err.stack);
+
+ if (req) {
+ req.emit('error', err);
+ // For Safety. Some additional errors might fire later on
+ // and we need to make sure we don't double-fire the error event.
+ req.socket._hadError = true;
+ }
+
+ if (parser) {
+ parser.finish();
+ freeParser(parser, req);
+ }
+ socket.destroy();
+}
+
+function socketOnEnd() {
+ var socket = this;
+ var req = this._httpMessage;
+ var parser = this.parser;
+
+ if (!req.res && !req.socket._hadError) {
+ // If we don't have a response then we know that the socket
+ // ended prematurely and we need to emit an error on the request.
+ req.emit('error', createHangUpError());
+ req.socket._hadError = true;
+ }
+ if (parser) {
+ parser.finish();
+ freeParser(parser, req);
+ }
+ socket.destroy();
+}
+
+function socketOnData(d) {
+ var socket = this;
+ var req = this._httpMessage;
+ var parser = this.parser;
+
+ var ret = parser.execute(d);
+ if (ret instanceof Error) {
+ debug('parse error');
+ freeParser(parser, req);
+ socket.destroy();
+ req.emit('error', ret);
+ req.socket._hadError = true;
+ } else if (parser.incoming && parser.incoming.upgrade) {
+ // Upgrade or CONNECT
+ var bytesParsed = ret;
+ var res = parser.incoming;
+ req.res = res;
+
+ socket.ondata = null;
+ socket.onend = null;
+ parser.finish();
+
+ var bodyHead = d.slice(bytesParsed, d.length);
+
+ var eventName = req.method === 'CONNECT' ? 'connect' : 'upgrade';
+ if (EventEmitter.listenerCount(req, eventName) > 0) {
+ req.upgradeOrConnect = true;
+
+ // detach the socket
+ socket.emit('agentRemove');
+ socket.removeListener('close', socketCloseListener);
+ socket.removeListener('error', socketErrorListener);
+
+ req.emit(eventName, res, socket, bodyHead);
+ req.emit('close');
+ } else {
+ // Got Upgrade header or CONNECT method, but have no handler.
+ socket.destroy();
+ }
+ freeParser(parser, req);
+ } else if (parser.incoming && parser.incoming.complete &&
+ // When the status code is 100 (Continue), the server will
+ // send a final response after this client sends a request
+ // body. So, we must not free the parser.
+ parser.incoming.statusCode !== 100) {
+ freeParser(parser, req);
+ }
+}
+
+
+// client
+function parserOnIncomingClient(res, shouldKeepAlive) {
+ var parser = this;
+ var socket = this.socket;
+ var req = socket._httpMessage;
+
+
+ // propogate "domain" setting...
+ if (req.domain && !res.domain) {
+ debug('setting "res.domain"');
+ res.domain = req.domain;
+ }
+
+ debug('AGENT incoming response!');
+
+ if (req.res) {
+ // We already have a response object, this means the server
+ // sent a double response.
+ socket.destroy();
+ return;
+ }
+ req.res = res;
+
+ // Responses to CONNECT request is handled as Upgrade.
+ if (req.method === 'CONNECT') {
+ res.upgrade = true;
+ return true; // skip body
+ }
+
+ // Responses to HEAD requests are crazy.
+ // HEAD responses aren't allowed to have an entity-body
+ // but *can* have a content-length which actually corresponds
+ // to the content-length of the entity-body had the request
+ // been a GET.
+ var isHeadResponse = req.method == 'HEAD';
+ debug('AGENT isHeadResponse', isHeadResponse);
+
+ if (res.statusCode == 100) {
+ // restart the parser, as this is a continue message.
+ delete req.res; // Clear res so that we don't hit double-responses.
+ req.emit('continue');
+ return true;
+ }
+
+ if (req.shouldKeepAlive && !shouldKeepAlive && !req.upgradeOrConnect) {
+ // Server MUST respond with Connection:keep-alive for us to enable it.
+ // If we've been upgraded (via WebSockets) we also shouldn't try to
+ // keep the connection open.
+ req.shouldKeepAlive = false;
+ }
+
+
+ DTRACE_HTTP_CLIENT_RESPONSE(socket, req);
+ COUNTER_HTTP_CLIENT_RESPONSE();
+ req.res = res;
+ res.req = req;
+
+ // add our listener first, so that we guarantee socket cleanup
+ res.on('end', responseOnEnd);
+ var handled = req.emit('response', res);
+
+ // If the user did not listen for the 'response' event, then they
+ // can't possibly read the data, so we ._dump() it into the void
+ // so that the socket doesn't hang there in a paused state.
+ if (!handled)
+ res._dump();
+
+ return isHeadResponse;
+}
+
+// client
+function responseOnEnd() {
+ var res = this;
+ var req = res.req;
+ var socket = req.socket;
+
+ if (!req.shouldKeepAlive) {
+ if (socket.writable) {
+ debug('AGENT socket.destroySoon()');
+ socket.destroySoon();
+ }
+ assert(!socket.writable);
+ } else {
+ debug('AGENT socket keep-alive');
+ if (req.timeoutCb) {
+ socket.setTimeout(0, req.timeoutCb);
+ req.timeoutCb = null;
+ }
+ socket.removeListener('close', socketCloseListener);
+ socket.removeListener('error', socketErrorListener);
+ // Mark this socket as available, AFTER user-added end
+ // handlers have a chance to run.
+ process.nextTick(function() {
+ socket.emit('free');
+ });
+ }
+}
+
+ClientRequest.prototype.onSocket = function(socket) {
+ var req = this;
+
+ process.nextTick(function() {
+ var parser = parsers.alloc();
+ req.socket = socket;
+ req.connection = socket;
+ parser.reinitialize(HTTPParser.RESPONSE);
+ parser.socket = socket;
+ parser.incoming = null;
+ req.parser = parser;
+
+ socket.parser = parser;
+ socket._httpMessage = req;
+
+ // Setup "drain" propogation.
+ httpSocketSetup(socket);
+
+ // Propagate headers limit from request object to parser
+ if (typeof req.maxHeadersCount === 'number') {
+ parser.maxHeaderPairs = req.maxHeadersCount << 1;
+ } else {
+ // Set default value because parser may be reused from FreeList
+ parser.maxHeaderPairs = 2000;
+ }
+
+ socket.on('error', socketErrorListener);
+ socket.ondata = socketOnData;
+ socket.onend = socketOnEnd;
+ socket.on('close', socketCloseListener);
+ parser.onIncoming = parserOnIncomingClient;
+ req.emit('socket', socket);
+ });
+
+};
+
+ClientRequest.prototype._deferToConnect = function(method, arguments_, cb) {
+ // This function is for calls that need to happen once the socket is
+ // connected and writable. It's an important promisy thing for all the socket
+ // calls that happen either now (when a socket is assigned) or
+ // in the future (when a socket gets assigned out of the pool and is
+ // eventually writable).
+ var self = this;
+ var onSocket = function() {
+ if (self.socket.writable) {
+ if (method) {
+ self.socket[method].apply(self.socket, arguments_);
+ }
+ if (cb) { cb(); }
+ } else {
+ self.socket.once('connect', function() {
+ if (method) {
+ self.socket[method].apply(self.socket, arguments_);
+ }
+ if (cb) { cb(); }
+ });
+ }
+ }
+ if (!self.socket) {
+ self.once('socket', onSocket);
+ } else {
+ onSocket();
+ }
+};
+
+ClientRequest.prototype.setTimeout = function(msecs, callback) {
+ if (callback) this.once('timeout', callback);
+
+ var self = this;
+ function emitTimeout() {
+ self.emit('timeout');
+ }
+
+ if (this.socket && this.socket.writable) {
+ if (this.timeoutCb)
+ this.socket.setTimeout(0, this.timeoutCb);
+ this.timeoutCb = emitTimeout;
+ this.socket.setTimeout(msecs, emitTimeout);
+ return;
+ }
+
+ // Set timeoutCb so that it'll get cleaned up on request end
+ this.timeoutCb = emitTimeout;
+ if (this.socket) {
+ var sock = this.socket;
+ this.socket.once('connect', function() {
+ sock.setTimeout(msecs, emitTimeout);
+ });
+ return;
+ }
+
+ this.once('socket', function(sock) {
+ sock.setTimeout(msecs, emitTimeout);
+ });
+};
+
+ClientRequest.prototype.setNoDelay = function() {
+ this._deferToConnect('setNoDelay', arguments);
+};
+ClientRequest.prototype.setSocketKeepAlive = function() {
+ this._deferToConnect('setKeepAlive', arguments);
+};
+
+ClientRequest.prototype.clearTimeout = function(cb) {
+ this.setTimeout(0, cb);
+};
--- /dev/null
- var waiting = 2;
+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');
+
+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 (size !== null) 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 (slabBuffer === null) 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');
+ }
+
+ if (this.onend) this.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(this._pending === null);
+
+ // 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(this._sslOutCb === null);
+ 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);
+ if (read > 0) {
+ bytesRead += read;
+ size -= 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();
+ } while (read > 0 && !this._buffer.isFull && bytesRead < size);
+
+ // 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 (this._pending !== null) this._writePending();
+ if (this._opposite._pending !== null) 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
+ 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));
+ }
+
+ // 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 (this._pending !== null) 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;
- this.once('finish', finish);
++ 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);
+
+ // 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();
+ }
+ });
+}
+
+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;
+};
int argc,
Handle<Value> argv[]) {
// TODO Hook for long stack traces to be made here.
+ Local<Object> process = PersistentToLocal(process_p);
+ if (using_domains)
+ return MakeDomainCallback(object, callback, argc, argv);
+
// lazy load no domain next tick callbacks
if (process_tickCallback.IsEmpty()) {
Local<Value> cb_v = process->Get(String::New("_tickCallback"));
#define NODE_VERSION_H
#define NODE_MAJOR_VERSION 0
-#define NODE_MINOR_VERSION 10
-#define NODE_PATCH_VERSION 14
+#define NODE_MINOR_VERSION 11
- #define NODE_PATCH_VERSION 3
++#define NODE_PATCH_VERSION 4
-#define NODE_VERSION_IS_RELEASE 0
+#define NODE_VERSION_IS_RELEASE 1
#ifndef NODE_TAG
# define NODE_TAG ""