Merge remote-tracking branch 'origin/v0.10'
authorFedor Indutny <fedor.indutny@gmail.com>
Sun, 2 Mar 2014 19:54:19 +0000 (23:54 +0400)
committerFedor Indutny <fedor.indutny@gmail.com>
Sun, 2 Mar 2014 19:54:19 +0000 (23:54 +0400)
Conflicts:
configure
lib/_stream_readable.js
lib/http.js
src/node_dtrace.cc

1  2 
configure
lib/_http_client.js
lib/_stream_readable.js
lib/_tls_wrap.js
lib/assert.js
node.gyp
src/node_dtrace.cc
test/simple/test-assert.js
tools/install.py

diff --cc configure
+++ b/configure
@@@ -464,16 -486,18 +464,18 @@@ def configure_node(o)
    if not is_clang and cc_version < (4,0,0):
      o['variables']['visibility'] = ''
  
-   if flavor in ('solaris', 'mac', 'linux'):
 -  # By default, enable DTrace on SunOS systems. Don't allow it on other
 -  # systems, since it won't work.  (The MacOS build process is different than
 -  # SunOS, and we haven't implemented it.)
 -  if flavor in ('solaris', 'mac'):
 -    o['variables']['node_use_dtrace'] = b(not options.without_dtrace)
 -  elif flavor == 'freebsd':
 -    o['variables']['node_use_dtrace'] = b(options.with_dtrace)
 -  elif flavor == 'linux':
 -    o['variables']['node_use_dtrace'] = 'false'
 -    o['variables']['node_use_systemtap'] = b(options.with_dtrace)
 -    if options.systemtap_includes:
 -      o['include_dirs'] += [options.systemtap_includes]
++  if flavor in ('solaris', 'mac', 'linux', 'freebsd'):
 +    use_dtrace = not options.without_dtrace
-     # Don't enable by default on linux, it needs the sdt-devel package.
++    # Don't enable by default on linux and freebsd
++    if flavor in ('linux', 'freebsd'):
++      use_dtrace = options.with_dtrace
++
 +    if flavor == 'linux':
 +      if options.systemtap_includes:
 +        o['include_dirs'] += [options.systemtap_includes]
-       use_dtrace = options.with_dtrace
 +    o['variables']['node_use_dtrace'] = b(use_dtrace)
 +    o['variables']['uv_use_dtrace'] = b(use_dtrace)
 +    o['variables']['uv_parent_path'] = '/deps/uv/'
    elif options.with_dtrace:
      raise Exception(
         'DTrace is currently only supported on SunOS, MacOS or Linux systems.')
index 4e17549,0000000..fe9d634
mode 100644,000000..100644
--- /dev/null
@@@ -1,555 -1,0 +1,555 @@@
-   } else if (util.isNullOrUndefined(agent)) {
 +// 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 url = require('url');
 +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');
 +
 +
 +function ClientRequest(options, cb) {
 +  var self = this;
 +  OutgoingMessage.call(self);
 +
 +  if (util.isString(options)) {
 +    options = url.parse(options);
 +  } else {
 +    options = util._extend({}, options);
 +  }
 +
 +  var agent = options.agent;
 +  var defaultAgent = options._defaultAgent || Agent.globalAgent;
 +  if (agent === false) {
 +    agent = new defaultAgent.constructor();
-   var defaultPort = options.defaultPort || self.agent.defaultPort;
++  } else if (util.isNullOrUndefined(agent) && !options.createConnection) {
 +    agent = defaultAgent;
 +  }
 +  self.agent = agent;
 +
 +  if (options.path && / /.test(options.path)) {
 +    // The actual regex is more like /[^A-Za-z0-9\-._~!$&'()*+,;=/:@]/
 +    // with an additional rule for ignoring percentage-escaped characters
 +    // but that's a) hard to capture in a regular expression that performs
 +    // well, and b) possibly too restrictive for real-world usage. That's
 +    // why it only scans for spaces because those are guaranteed to create
 +    // an invalid request.
 +    throw new TypeError('Request path contains unescaped characters.');
 +  } else if (options.protocol && options.protocol !== self.agent.protocol) {
 +    throw new Error('Protocol:' + options.protocol + ' not supported.');
 +  }
 +
-   var port = options.port = options.port || defaultPort;
++  var defaultPort = options.defaultPort || self.agent && self.agent.defaultPort;
 +
++  var port = options.port = options.port || defaultPort || 80;
 +  var host = options.host = options.hostname || options.host || 'localhost';
 +
 +  if (util.isUndefined(options.setHost)) {
 +    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 (!util.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 === 'DELETE' ||
 +      method === 'CONNECT') {
 +    self.useChunkedEncodingByDefault = false;
 +  } else {
 +    self.useChunkedEncodingByDefault = true;
 +  }
 +
 +  if (util.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,
 +    // but only if the Agent will actually reuse the connection!
 +    // If it's not a keepAlive agent, and the maxSockets==Infinity, then
 +    // there's never a case where this socket will actually be reused
 +    if (!self.agent.keepAlive && !Number.isFinite(self.agent.maxSockets)) {
 +      self._last = true;
 +      self.shouldKeepAlive = false;
 +    } else {
 +      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 req = socket._httpMessage;
 +  debug('HTTP socket close');
 +
 +  // Pull through final chunk, if anything is buffered.
 +  // the ondata function will handle it properly, and this
 +  // is a no-op if no final chunk remains.
 +  socket.read();
 +
 +  // NOTE: Its important to get parser here, because it could be freed by
 +  // the `socketOnData`.
 +  var parser = socket.parser;
 +  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;
 +
 +  assert(parser && parser.socket === socket);
 +
 +  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.removeListener('data', socketOnData);
 +    socket.removeListener('end', socketOnEnd);
 +    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);
 +
 +      // TODO(isaacs): Need a way to reset a stream to fresh state
 +      // IE, not flowing, and not explicitly paused.
 +      socket._readableState.flowing = null;
 +
 +      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) {
 +    socket.removeListener('data', socketOnData);
 +    socket.removeListener('end', socketOnEnd);
 +    freeParser(parser, req);
 +  }
 +}
 +
 +
 +// client
 +function parserOnIncomingClient(res, shouldKeepAlive) {
 +  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');
 +    });
 +  }
 +}
 +
 +function tickOnSocket(req, socket) {
 +  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 (util.isNumber(req.maxHeadersCount)) {
 +    parser.maxHeaderPairs = req.maxHeadersCount << 1;
 +  } else {
 +    // Set default value because parser may be reused from FreeList
 +    parser.maxHeaderPairs = 2000;
 +  }
 +
 +  parser.onIncoming = parserOnIncomingClient;
 +  socket.on('error', socketErrorListener);
 +  socket.on('data', socketOnData);
 +  socket.on('end', socketOnEnd);
 +  socket.on('close', socketCloseListener);
 +  req.emit('socket', socket);
 +}
 +
 +ClientRequest.prototype.onSocket = function(socket) {
 +  var req = this;
 +
 +  process.nextTick(function() {
 +    tickOnSocket(req, 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);
 +};
index e1636c8,1d486cf..3534bf9
mode 100644,100755..100644
@@@ -370,11 -356,11 +370,10 @@@ Readable.prototype.read = function(n) 
  
  function chunkInvalid(state, chunk) {
    var er = null;
 -  if (!Buffer.isBuffer(chunk) &&
 -      'string' !== typeof chunk &&
 -      chunk !== null &&
 -      chunk !== undefined &&
 +  if (!util.isBuffer(chunk) &&
 +      !util.isString(chunk) &&
 +      !util.isNullOrUndefined(chunk) &&
-       !state.objectMode &&
-       !er) {
+       !state.objectMode) {
      er = new TypeError('Invalid non-string/buffer chunk');
    }
    return er;
index c0af83a,0000000..f3061c4
mode 100644,000000..100644
--- /dev/null
@@@ -1,883 -1,0 +1,886 @@@
 +// Copyright Joyent, Inc. and other Node contributors.
 +//
++// // Emit `beforeExit` if the loop became alive either after emitting
++// event, or after running some callbacks.
++//
 +// 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 constants = require('constants');
 +var crypto = require('crypto');
 +var net = require('net');
 +var tls = require('tls');
 +var util = require('util');
 +
 +var Timer = process.binding('timer_wrap').Timer;
 +var tls_wrap = process.binding('tls_wrap');
 +
 +// Lazy load
 +var tls_legacy;
 +
 +var debug = util.debuglog('tls');
 +
 +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.');
 +      self._tlsError(err);
 +    });
 +  }
 +}
 +
 +
 +function onhandshakedone() {
 +  // for future use
 +  debug('onhandshakedone');
 +  this._finishInit();
 +}
 +
 +
 +function onclienthello(hello) {
 +  var self = this,
 +      onceSession = false,
 +      onceSNI = false;
 +
 +  function callback(err, session) {
 +    if (onceSession)
 +      return self.destroy(new Error('TLS session callback was called 2 times'));
 +    onceSession = true;
 +
 +    if (err)
 +      return self.destroy(err);
 +
 +    // NOTE: That we have disabled OpenSSL's internal session storage in
 +    // `node_crypto.cc` and hence its safe to rely on getting servername only
 +    // from clienthello or this place.
 +    var ret = self.ssl.loadSession(session);
 +
 +    // Servername came from SSL session
 +    // NOTE: TLS Session ticket doesn't include servername information
 +    //
 +    // Another note, From RFC3546:
 +    //
 +    //   If, on the other hand, the older
 +    //   session is resumed, then the server MUST ignore extensions appearing
 +    //   in the client hello, and send a server hello containing no
 +    //   extensions; in this case the extension functionality negotiated
 +    //   during the original session initiation is applied to the resumed
 +    //   session.
 +    //
 +    // Therefore we should account session loading when dealing with servername
 +    if (ret && ret.servername) {
 +      self._SNICallback(ret.servername, onSNIResult);
 +    } else if (hello.servername && self._SNICallback) {
 +      self._SNICallback(hello.servername, onSNIResult);
 +    } else {
 +      self.ssl.endParser();
 +    }
 +  }
 +
 +  function onSNIResult(err, context) {
 +    if (onceSNI)
 +      return self.destroy(new Error('TLS SNI callback was called 2 times'));
 +    onceSNI = true;
 +
 +    if (err)
 +      return self.destroy(err);
 +
 +    if (context)
 +      self.ssl.sni_context = context;
 +
 +    self.ssl.endParser();
 +  }
 +
 +  if (hello.sessionId.length <= 0 ||
 +      hello.tlsTicket ||
 +      this.server &&
 +      !this.server.emit('resumeSession', hello.sessionId, callback)) {
 +    // Invoke SNI callback, since we've no session to resume
 +    if (hello.servername && this._SNICallback)
 +      this._SNICallback(hello.servername, onSNIResult);
 +    else
 +      this.ssl.endParser();
 +  }
 +}
 +
 +
 +function onnewsession(key, session) {
 +  if (!this.server)
 +    return;
 +
 +  var self = this;
 +  var once = false;
 +
 +  this._newSessionPending = true;
 +  this.server.emit('newSession', key, session, function() {
 +    if (once)
 +      return;
 +    once = true;
 +
 +    self.ssl.newSessionDone();
 +
 +    self._newSessionPending = false;
 +    if (self._securePending)
 +      self._finishInit();
 +    self._securePending = false;
 +  });
 +}
 +
 +
 +/**
 + * Provides a wrap of socket stream to do encrypted communication.
 + */
 +
 +function TLSSocket(socket, options) {
 +  // Disallow wrapping TLSSocket in TLSSocket
 +  assert(!(socket instanceof TLSSocket));
 +
 +  net.Socket.call(this, socket && {
 +    handle: socket._handle,
 +    allowHalfOpen: socket.allowHalfOpen,
 +    readable: socket.readable,
 +    writable: socket.writable
 +  });
 +
 +  // To prevent assertion in afterConnect()
 +  if (socket)
 +    this._connecting = socket._connecting;
 +
 +  this._tlsOptions = options;
 +  this._secureEstablished = false;
 +  this._securePending = false;
 +  this._newSessionPending = false;
 +  this._controlReleased = false;
 +  this._SNICallback = null;
 +  this.ssl = null;
 +  this.servername = null;
 +  this.npnProtocol = null;
 +  this.authorized = false;
 +  this.authorizationError = null;
 +
 +  // Just a documented property to make secure sockets
 +  // distinguishable from regular ones.
 +  this.encrypted = true;
 +
 +  this.on('error', this._tlsError);
 +
 +  if (!this._handle) {
 +    this.once('connect', function() {
 +      this._init(null);
 +    });
 +  } else {
 +    this._init(socket);
 +  }
 +}
 +util.inherits(TLSSocket, net.Socket);
 +exports.TLSSocket = TLSSocket;
 +
 +TLSSocket.prototype._init = function(socket) {
 +  assert(this._handle);
 +
 +  // lib/net.js expect this value to be non-zero if write hasn't been flushed
 +  // immediately
 +  // TODO(indutny): rewise this solution, it might be 1 before handshake and
 +  // represent real writeQueueSize during regular writes.
 +  this._handle.writeQueueSize = 1;
 +
 +  var self = this;
 +  var options = this._tlsOptions;
 +
 +  // Wrap socket's handle
 +  var credentials = options.credentials || crypto.createCredentials();
 +  this.ssl = tls_wrap.wrap(this._handle, credentials.context, options.isServer);
 +  this.server = options.server || null;
 +
 +  // For clients, we will always have either a given ca list or be using
 +  // default one
 +  var requestCert = !!options.requestCert || !options.isServer,
 +      rejectUnauthorized = !!options.rejectUnauthorized;
 +
 +  this._requestCert = requestCert;
 +  this._rejectUnauthorized = rejectUnauthorized;
 +  if (requestCert || rejectUnauthorized)
 +    this.ssl.setVerifyMode(requestCert, rejectUnauthorized);
 +
 +  if (options.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 (this.server &&
 +        (this.server.listeners('resumeSession').length > 0 ||
 +         this.server.listeners('newSession').length > 0)) {
 +      this.ssl.enableSessionCallbacks();
 +    }
 +  } else {
 +    this.ssl.onhandshakestart = function() {};
 +    this.ssl.onhandshakedone = this._finishInit.bind(this);
 +
 +    if (options.session)
 +      this.ssl.setSession(options.session);
 +  }
 +
 +  this.ssl.onerror = function(err) {
 +    if (self._writableState.errorEmitted)
 +      return;
 +    self._writableState.errorEmitted = true;
 +
 +    // Destroy socket if error happened before handshake's finish
 +    if (!this._secureEstablished) {
 +      self._tlsError(err);
 +      self.destroy();
 +    } else if (options.isServer &&
 +               rejectUnauthorized &&
 +               /peer did not return a certificate/.test(err.message)) {
 +      // Ignore server's authorization errors
 +      self.destroy();
 +    } else {
 +      // Throw error
 +      self._tlsError(err);
 +    }
 +  };
 +
 +  // If custom SNICallback was given, or if
 +  // there're SNI contexts to perform match against -
 +  // set `.onsniselect` callback.
 +  if (process.features.tls_sni &&
 +      options.isServer &&
 +      options.server &&
 +      (options.SNICallback !== SNICallback ||
 +       options.server._contexts.length)) {
 +    assert(typeof options.SNICallback === 'function');
 +    this._SNICallback = options.SNICallback;
 +    this.ssl.enableHelloParser();
 +  }
 +
 +  if (process.features.tls_npn && options.NPNProtocols)
 +    this.ssl.setNPNProtocols(options.NPNProtocols);
 +
 +  if (options.handshakeTimeout > 0)
 +    this.setTimeout(options.handshakeTimeout, this._handleTimeout);
 +
 +  // Socket already has some buffered data - emulate receiving it
 +  if (socket && socket._readableState.length) {
 +    var buf;
 +    while ((buf = socket.read()) !== null)
 +      this.ssl.receive(buf);
 +  }
 +};
 +
 +TLSSocket.prototype.renegotiate = function(options, callback) {
 +  var requestCert = this._requestCert,
 +      rejectUnauthorized = this._rejectUnauthorized;
 +
 +  if (typeof options.requestCert !== 'undefined')
 +    requestCert = !!options.requestCert;
 +  if (typeof options.rejectUnauthorized !== 'undefined')
 +    rejectUnauthorized = !!options.rejectUnauthorized;
 +
 +  if (requestCert !== this._requestCert ||
 +      rejectUnauthorized !== this._rejectUnauthorized) {
 +    this.ssl.setVerifyMode(requestCert, rejectUnauthorized);
 +    this._requestCert = requestCert;
 +    this._rejectUnauthorized = rejectUnauthorized;
 +  }
 +  if (!this.ssl.renegotiate()) {
 +    if (callback) {
 +      process.nextTick(function() {
 +        callback(new Error('Failed to renegotiate'));
 +      });
 +    }
 +    return false;
 +  }
 +
 +  // Ensure that we'll cycle through internal openssl's state
 +  this.write('');
 +
 +  if (callback) {
 +    this.once('secure', function() {
 +      callback(null);
 +    });
 +  }
 +
 +  return true;
 +};
 +
 +TLSSocket.prototype.setMaxSendFragment = function setMaxSendFragment(size) {
 +  return this.ssl.setMaxSendFragment(size) == 1;
 +};
 +
 +TLSSocket.prototype.getTLSTicket = function getTLSTicket() {
 +  return this.ssl.getTLSTicket();
 +};
 +
 +TLSSocket.prototype._handleTimeout = function() {
 +  this._tlsError(new Error('TLS handshake timeout'));
 +};
 +
 +TLSSocket.prototype._tlsError = function(err) {
 +  this.emit('_tlsError', err);
 +  if (this._controlReleased)
 +    this.emit('error', err);
 +};
 +
 +TLSSocket.prototype._releaseControl = function() {
 +  if (this._controlReleased)
 +    return false;
 +  this._controlReleased = true;
 +  this.removeListener('error', this._tlsError);
 +  return true;
 +};
 +
 +TLSSocket.prototype._finishInit = function() {
 +  // `newSession` callback wasn't called yet
 +  if (this._newSessionPending) {
 +    this._securePending = true;
 +    return;
 +  }
 +
 +  if (process.features.tls_npn) {
 +    this.npnProtocol = this.ssl.getNegotiatedProtocol();
 +  }
 +
 +  if (process.features.tls_sni && this._tlsOptions.isServer) {
 +    this.servername = this.ssl.getServername();
 +  }
 +
 +  debug('secure established');
 +  this._secureEstablished = true;
 +  if (this._tlsOptions.handshakeTimeout > 0)
 +    this.setTimeout(0, this._handleTimeout);
 +  this.emit('secure');
 +};
 +
 +TLSSocket.prototype._start = function() {
 +  this.ssl.start();
 +};
 +
 +TLSSocket.prototype.setServername = function(name) {
 +  this.ssl.setServername(name);
 +};
 +
 +TLSSocket.prototype.setSession = function(session) {
 +  if (util.isString(session))
 +    session = new Buffer(session, 'binary');
 +  this.ssl.setSession(session);
 +};
 +
 +TLSSocket.prototype.getPeerCertificate = function() {
 +  if (this.ssl) {
 +    var c = this.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;
 +};
 +
 +TLSSocket.prototype.getSession = function() {
 +  if (this.ssl) {
 +    return this.ssl.getSession();
 +  }
 +
 +  return null;
 +};
 +
 +TLSSocket.prototype.isSessionReused = function() {
 +  if (this.ssl) {
 +    return this.ssl.isSessionReused();
 +  }
 +
 +  return null;
 +};
 +
 +TLSSocket.prototype.getCipher = function(err) {
 +  if (this.ssl) {
 +    return this.ssl.getCurrentCipher();
 +  } else {
 +    return null;
 +  }
 +};
 +
 +// TODO: support anonymous (nocert) and PSK
 +
 +
 +// AUTHENTICATION MODES
 +//
 +// There are several levels of authentication that TLS/SSL supports.
 +// Read more about this in "man SSL_set_verify".
 +//
 +// 1. The server sends a certificate to the client but does not request a
 +// cert from the client. This is common for most HTTPS servers. The browser
 +// can verify the identity of the server, but the server does not know who
 +// the client is. Authenticating the client is usually done over HTTP using
 +// login boxes and cookies and stuff.
 +//
 +// 2. The server sends a cert to the client and requests that the client
 +// also send it a cert. The client knows who the server is and the server is
 +// requesting the client also identify themselves. There are several
 +// outcomes:
 +//
 +//   A) verifyError returns null meaning the client's certificate is signed
 +//   by one of the server's CAs. The server know's the client idenity now
 +//   and the client is authorized.
 +//
 +//   B) For some reason the client's certificate is not acceptable -
 +//   verifyError returns a string indicating the problem. The server can
 +//   either (i) reject the client or (ii) allow the client to connect as an
 +//   unauthorized connection.
 +//
 +// The mode is controlled by two boolean variables.
 +//
 +// requestCert
 +//   If true the server requests a certificate from client connections. For
 +//   the common HTTPS case, users will want this to be false, which is what
 +//   it defaults to.
 +//
 +// rejectUnauthorized
 +//   If true clients whose certificates are invalid for any reason will not
 +//   be allowed to make connections. If false, they will simply be marked as
 +//   unauthorized but secure communication will continue. By default this is
 +//   true.
 +//
 +//
 +//
 +// Options:
 +// - requestCert. Send verify request. Default to false.
 +// - rejectUnauthorized. Boolean, default to true.
 +// - key. string.
 +// - cert: string.
 +// - ca: string or array of strings.
 +// - sessionTimeout: integer.
 +//
 +// emit 'secureConnection'
 +//   function (tlsSocket) { }
 +//
 +//   "UNABLE_TO_GET_ISSUER_CERT", "UNABLE_TO_GET_CRL",
 +//   "UNABLE_TO_DECRYPT_CERT_SIGNATURE", "UNABLE_TO_DECRYPT_CRL_SIGNATURE",
 +//   "UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY", "CERT_SIGNATURE_FAILURE",
 +//   "CRL_SIGNATURE_FAILURE", "CERT_NOT_YET_VALID" "CERT_HAS_EXPIRED",
 +//   "CRL_NOT_YET_VALID", "CRL_HAS_EXPIRED" "ERROR_IN_CERT_NOT_BEFORE_FIELD",
 +//   "ERROR_IN_CERT_NOT_AFTER_FIELD", "ERROR_IN_CRL_LAST_UPDATE_FIELD",
 +//   "ERROR_IN_CRL_NEXT_UPDATE_FIELD", "OUT_OF_MEM",
 +//   "DEPTH_ZERO_SELF_SIGNED_CERT", "SELF_SIGNED_CERT_IN_CHAIN",
 +//   "UNABLE_TO_GET_ISSUER_CERT_LOCALLY", "UNABLE_TO_VERIFY_LEAF_SIGNATURE",
 +//   "CERT_CHAIN_TOO_LONG", "CERT_REVOKED" "INVALID_CA",
 +//   "PATH_LENGTH_EXCEEDED", "INVALID_PURPOSE" "CERT_UNTRUSTED",
 +//   "CERT_REJECTED"
 +//
 +function Server(/* [options], listener */) {
 +  var options, listener;
 +  if (util.isObject(arguments[0])) {
 +    options = arguments[0];
 +    listener = arguments[1];
 +  } else if (util.isFunction(arguments[0])) {
 +    options = {};
 +    listener = arguments[0];
 +  }
 +
 +  if (!(this instanceof Server)) return new Server(options, listener);
 +
 +  this._contexts = [];
 +
 +  var self = this;
 +
 +  // Handle option defaults:
 +  this.setOptions(options);
 +
 +  var sharedCreds = crypto.createCredentials({
 +    pfx: self.pfx,
 +    key: self.key,
 +    passphrase: self.passphrase,
 +    cert: self.cert,
 +    ca: self.ca,
 +    ciphers: self.ciphers || tls.DEFAULT_CIPHERS,
 +    ecdhCurve: util.isUndefined(self.ecdhCurve) ?
 +        tls.DEFAULT_ECDH_CURVE : self.ecdhCurve,
 +    secureProtocol: self.secureProtocol,
 +    secureOptions: self.secureOptions,
 +    crl: self.crl,
 +    sessionIdContext: self.sessionIdContext
 +  });
 +  this._sharedCreds = sharedCreds;
 +
 +  var timeout = options.handshakeTimeout || (120 * 1000);
 +
 +  if (!util.isNumber(timeout)) {
 +    throw new TypeError('handshakeTimeout must be a number');
 +  }
 +
 +  if (self.sessionTimeout) {
 +    sharedCreds.context.setSessionTimeout(self.sessionTimeout);
 +  }
 +
 +  if (self.ticketKeys) {
 +    sharedCreds.context.setTicketKeys(self.ticketKeys);
 +  }
 +
 +  // constructor call
 +  net.Server.call(this, function(raw_socket) {
 +    var socket = new TLSSocket(raw_socket, {
 +      credentials: sharedCreds,
 +      isServer: true,
 +      server: self,
 +      requestCert: self.requestCert,
 +      rejectUnauthorized: self.rejectUnauthorized,
 +      handshakeTimeout: timeout,
 +      NPNProtocols: self.NPNProtocols,
 +      SNICallback: options.SNICallback || SNICallback
 +    });
 +
 +    socket.on('secure', function() {
 +      if (socket._requestCert) {
 +        var verifyError = socket.ssl.verifyError();
 +        if (verifyError) {
 +          socket.authorizationError = verifyError.code;
 +
 +          if (socket._rejectUnauthorized)
 +            socket.destroy();
 +        } else {
 +          socket.authorized = true;
 +        }
 +      }
 +
 +      if (!socket.destroyed && socket._releaseControl())
 +        self.emit('secureConnection', socket);
 +    });
 +
 +    var errorEmitted = false;
 +    socket.on('close', function() {
 +      // Emit ECONNRESET
 +      if (!socket._controlReleased && !errorEmitted) {
 +        errorEmitted = true;
 +        var connReset = new Error('socket hang up');
 +        connReset.code = 'ECONNRESET';
 +        self.emit('clientError', connReset, socket);
 +      }
 +    });
 +
 +    socket.on('_tlsError', function(err) {
 +      if (!socket._controlReleased && !errorEmitted) {
 +        errorEmitted = true;
 +        self.emit('clientError', err, socket);
 +      }
 +    });
 +  });
 +
 +  if (listener) {
 +    this.on('secureConnection', listener);
 +  }
 +}
 +
 +util.inherits(Server, net.Server);
 +exports.Server = Server;
 +exports.createServer = function(options, listener) {
 +  return new Server(options, listener);
 +};
 +
 +
 +Server.prototype._getServerData = function() {
 +  return {
 +    ticketKeys: this._sharedCreds.context.getTicketKeys().toString('hex')
 +  };
 +};
 +
 +
 +Server.prototype._setServerData = function(data) {
 +  this._sharedCreds.context.setTicketKeys(new Buffer(data.ticketKeys, 'hex'));
 +};
 +
 +
 +Server.prototype.setOptions = function(options) {
 +  if (util.isBoolean(options.requestCert)) {
 +    this.requestCert = options.requestCert;
 +  } else {
 +    this.requestCert = false;
 +  }
 +
 +  if (util.isBoolean(options.rejectUnauthorized)) {
 +    this.rejectUnauthorized = options.rejectUnauthorized;
 +  } else {
 +    this.rejectUnauthorized = false;
 +  }
 +
 +  if (options.pfx) this.pfx = options.pfx;
 +  if (options.key) this.key = options.key;
 +  if (options.passphrase) this.passphrase = options.passphrase;
 +  if (options.cert) this.cert = options.cert;
 +  if (options.ca) this.ca = options.ca;
 +  if (options.secureProtocol) this.secureProtocol = options.secureProtocol;
 +  if (options.crl) this.crl = options.crl;
 +  if (options.ciphers) this.ciphers = options.ciphers;
 +  if (!util.isUndefined(options.ecdhCurve))
 +    this.ecdhCurve = options.ecdhCurve;
 +  if (options.sessionTimeout) this.sessionTimeout = options.sessionTimeout;
 +  if (options.ticketKeys) this.ticketKeys = options.ticketKeys;
 +  var secureOptions = options.secureOptions || 0;
 +  if (options.honorCipherOrder) {
 +    secureOptions |= constants.SSL_OP_CIPHER_SERVER_PREFERENCE;
 +  }
 +  if (secureOptions) this.secureOptions = secureOptions;
 +  if (options.NPNProtocols) tls.convertNPNProtocols(options.NPNProtocols, this);
 +  if (options.sessionIdContext) {
 +    this.sessionIdContext = options.sessionIdContext;
 +  } else {
 +    this.sessionIdContext = crypto.createHash('md5')
 +                                  .update(process.argv.join(' '))
 +                                  .digest('hex');
 +  }
 +};
 +
 +// SNI Contexts High-Level API
 +Server.prototype.addContext = function(servername, credentials) {
 +  if (!servername) {
 +    throw 'Servername is required parameter for Server.addContext';
 +  }
 +
 +  var re = new RegExp('^' +
 +                      servername.replace(/([\.^$+?\-\\[\]{}])/g, '\\$1')
 +                                .replace(/\*/g, '[^\.]*') +
 +                      '$');
 +  this._contexts.push([re, crypto.createCredentials(credentials).context]);
 +};
 +
 +function SNICallback(servername, callback) {
 +  var ctx;
 +
 +  this.server._contexts.some(function(elem) {
 +    if (!util.isNull(servername.match(elem[0]))) {
 +      ctx = elem[1];
 +      return true;
 +    }
 +  });
 +
 +  callback(null, ctx);
 +}
 +
 +
 +// Target API:
 +//
 +//  var s = tls.connect({port: 8000, host: "google.com"}, function() {
 +//    if (!s.authorized) {
 +//      s.destroy();
 +//      return;
 +//    }
 +//
 +//    // s.socket;
 +//
 +//    s.end("hello world\n");
 +//  });
 +//
 +//
 +function normalizeConnectArgs(listArgs) {
 +  var args = net._normalizeConnectArgs(listArgs);
 +  var options = args[0];
 +  var cb = args[1];
 +
 +  if (util.isObject(listArgs[1])) {
 +    options = util._extend(options, listArgs[1]);
 +  } else if (util.isObject(listArgs[2])) {
 +    options = util._extend(options, listArgs[2]);
 +  }
 +
 +  return (cb) ? [options, cb] : [options];
 +}
 +
 +function legacyConnect(hostname, options, NPN, credentials) {
 +  assert(options.socket);
 +  if (!tls_legacy)
 +    tls_legacy = require('_tls_legacy');
 +
 +  var pair = tls_legacy.createSecurePair(credentials,
 +                                         false,
 +                                         true,
 +                                         !!options.rejectUnauthorized,
 +                                         {
 +                                           NPNProtocols: NPN.NPNProtocols,
 +                                           servername: hostname
 +                                         });
 +  tls_legacy.pipe(pair, options.socket);
 +  pair.cleartext._controlReleased = true;
 +  pair.on('error', function(err) {
 +    pair.cleartext.emit('error', err);
 +  });
 +
 +  return pair;
 +}
 +
 +exports.connect = function(/* [port, host], options, cb */) {
 +  var args = normalizeConnectArgs(arguments);
 +  var options = args[0];
 +  var cb = args[1];
 +
 +  var defaults = {
 +    rejectUnauthorized: '0' !== process.env.NODE_TLS_REJECT_UNAUTHORIZED,
 +    ciphers: tls.DEFAULT_CIPHERS
 +  };
 +  options = util._extend(defaults, options || {});
 +
 +  var hostname = options.servername ||
 +                 options.host ||
 +                 options.socket && options.socket._host,
 +      NPN = {},
 +      credentials = crypto.createCredentials(options);
 +  tls.convertNPNProtocols(options.NPNProtocols, NPN);
 +
 +  // Wrapping TLS socket inside another TLS socket was requested -
 +  // create legacy secure pair
 +  var socket;
 +  var legacy;
 +  var result;
 +  if (options.socket instanceof TLSSocket) {
 +    debug('legacy connect');
 +    legacy = true;
 +    socket = legacyConnect(hostname, options, NPN, credentials);
 +    result = socket.cleartext;
 +  } else {
 +    legacy = false;
 +    socket = new TLSSocket(options.socket, {
 +      credentials: credentials,
 +      isServer: false,
 +      requestCert: true,
 +      rejectUnauthorized: options.rejectUnauthorized,
 +      session: options.session,
 +      NPNProtocols: NPN.NPNProtocols
 +    });
 +    result = socket;
 +  }
 +
 +  if (socket._handle && !socket._connecting) {
 +    onHandle();
 +  } else {
 +    // Not even started connecting yet (or probably resolving dns address),
 +    // catch socket errors and assign handle.
 +    if (!legacy && options.socket) {
 +      options.socket.once('connect', function() {
 +        assert(options.socket._handle);
 +        socket._handle = options.socket._handle;
 +        socket._handle.owner = socket;
 +        socket.emit('connect');
 +      });
 +    }
 +    socket.once('connect', onHandle);
 +  }
 +
 +  if (cb)
 +    result.once('secureConnect', cb);
 +
 +  if (!options.socket) {
 +    assert(!legacy);
 +    var connect_opt;
 +    if (options.path && !options.port) {
 +      connect_opt = { path: options.path };
 +    } else {
 +      connect_opt = {
 +        port: options.port,
 +        host: options.host,
 +        localAddress: options.localAddress
 +      };
 +    }
 +    socket.connect(connect_opt);
 +  }
 +
 +  return result;
 +
 +  function onHandle() {
 +    if (!legacy)
 +      socket._releaseControl();
 +
 +    if (options.session)
 +      socket.setSession(options.session);
 +
 +    if (!legacy) {
 +      if (options.servername)
 +        socket.setServername(options.servername);
 +
 +      socket._start();
 +    }
 +    socket.on('secure', function() {
 +      var verifyError = socket.ssl.verifyError();
 +
 +      // Verify that server's identity matches it's certificate's names
 +      if (!verifyError) {
 +        var cert = result.getPeerCertificate();
 +        var validCert = tls.checkServerIdentity(hostname, cert);
 +        if (!validCert) {
 +          verifyError = new Error('Hostname/IP doesn\'t match certificate\'s ' +
 +                                  'altnames');
 +        }
 +      }
 +
 +      if (verifyError) {
 +        result.authorized = false;
 +        result.authorizationError = verifyError.code || verifyError.message;
 +
 +        if (options.rejectUnauthorized) {
 +          result.emit('error', verifyError);
 +          result.destroy();
 +          return;
 +        } else {
 +          result.emit('secureConnect');
 +        }
 +      } else {
 +        result.authorized = true;
 +        result.emit('secureConnect');
 +      }
 +
 +      // Uncork incoming data
 +      result.removeListener('end', onHangUp);
 +    });
 +
 +    function onHangUp() {
 +      // NOTE: This logic is shared with _http_client.js
 +      if (!socket._hadError) {
 +        socket._hadError = true;
 +        var error = new Error('socket hang up');
 +        error.code = 'ECONNRESET';
 +        socket.destroy();
 +        socket.emit('error', error);
 +      }
 +    }
 +    result.once('end', onHangUp);
 +  }
 +};
diff --cc lib/assert.js
Simple merge
diff --cc node.gyp
+++ b/node.gyp
          }],
          [ 'node_use_dtrace=="true"', {
            'defines': [ 'HAVE_DTRACE=1' ],
-           'dependencies': [ 'node_dtrace_header' ],
+           'dependencies': [
+             'node_dtrace_header',
+             'specialize_node_d',
+           ],
            'include_dirs': [ '<(SHARED_INTERMEDIATE_DIR)' ],
            #
 -          # DTrace is supported on solaris, mac, and bsd.  There are three
 -          # object files associated with DTrace support, but they're not all
 -          # used all the time:
 +          # DTrace is supported on linux, solaris, mac, and bsd.  There are
 +          # three object files associated with DTrace support, but they're
 +          # not all used all the time:
            #
            #   node_dtrace.o           all configurations
 -          #   node_dtrace_ustack.o    not supported on OS X
 +          #   node_dtrace_ustack.o    not supported on mac and linux
            #   node_dtrace_provider.o  All except OS X.  "dtrace -G" is not
            #                           used on OS X.
            #
@@@ -272,23 -312,41 +272,23 @@@ void DTRACE_HTTP_CLIENT_REQUEST(const F
    *header = '\0';
  
    SLURP_CONNECTION_HTTP_CLIENT(args[1], conn);
 -#ifdef HAVE_SYSTEMTAP
 -  NODE_HTTP_CLIENT_REQUEST(&req, conn.fd, conn.remote, conn.port, \
 -                           conn.buffered);
 -#else
    NODE_HTTP_CLIENT_REQUEST(&req, &conn, conn.remote, conn.port, req.method, \
                             req.url, conn.fd);
 -#endif
 -  return Undefined();
  }
  
 -Handle<Value> DTRACE_HTTP_CLIENT_RESPONSE(const Arguments& args) {
 -#ifndef HAVE_SYSTEMTAP
 -  if (!NODE_HTTP_CLIENT_RESPONSE_ENABLED())
 -    return Undefined();
 -#endif
 -  HandleScope scope;
  
 +void DTRACE_HTTP_CLIENT_RESPONSE(const FunctionCallbackInfo<Value>& args) {
 +  if (!NODE_HTTP_CLIENT_RESPONSE_ENABLED())
 +    return;
 +  Environment* env = Environment::GetCurrent(args.GetIsolate());
 +  HandleScope scope(env->isolate());
    SLURP_CONNECTION_HTTP_CLIENT_RESPONSE(args[0], args[1], conn);
 -#ifdef HAVE_SYSTEMTAP
 -  NODE_HTTP_CLIENT_RESPONSE(conn.fd, conn.remote, conn.port, conn.buffered);
 -#else
    NODE_HTTP_CLIENT_RESPONSE(&conn, conn.remote, conn.port, conn.fd);
 -#endif
 -
 -  return Undefined();
  }
  
 -#define NODE_PROBE(name) #name, name, Persistent<FunctionTemplate>()
  
static int dtrace_gc_start(GCType type, GCCallbackFlags flags) {
+ int dtrace_gc_start(GCType type, GCCallbackFlags flags) {
 -#ifdef HAVE_SYSTEMTAP
 -  NODE_GC_START();
 -#else
    NODE_GC_START(type, flags);
 -#endif
    /*
     * We avoid the tail-call elimination of the USDT probe (which screws up
     * args) by forcing a return of 0.
    return 0;
  }
  
- static int dtrace_gc_done(GCType type, GCCallbackFlags flags) {
 +
 -#ifdef HAVE_SYSTEMTAP
 -  NODE_GC_DONE();
 -#else
+ int dtrace_gc_done(GCType type, GCCallbackFlags flags) {
    NODE_GC_DONE(type, flags);
 -#endif
    return 0;
  }
  
Simple merge
@@@ -129,14 -129,9 +129,12 @@@ def subdir_files(path, dest, action)
  def files(action):
    action(['out/Release/node'], 'bin/node')
  
-   # install unconditionally, checking if the platform supports dtrace doesn't
-   # work when cross-compiling and besides, there's at least one linux flavor
-   # with dtrace support now (oracle's "unbreakable" linux)
-   action(['src/node.d'], 'lib/dtrace/')
+   if 'true' == variables.get('node_use_dtrace'):
+     action(['out/Release/node.d'], 'lib/dtrace/node.d')
  
 +  # behave similarly for systemtap
 +  action(['src/node.stp'], 'share/systemtap/tapset/')
 +
    if 'freebsd' in sys.platform or 'openbsd' in sys.platform:
      action(['doc/node.1'], 'man/man1/')
    else: