diff --git a/configure b/configure index ab2383c8141b15..f01aea6f927f69 100755 --- a/configure +++ b/configure @@ -335,6 +335,9 @@ parser.add_option('--enable-static', (options, args) = parser.parse_args() +# Expand ~ in the install prefix now, it gets written to multiple files. +options.prefix = os.path.expanduser(options.prefix or '') + # set up auto-download list auto_downloads = nodedownload.parse(options.download_list) @@ -611,7 +614,7 @@ def configure_mips(o): def configure_node(o): if options.dest_os == 'android': o['variables']['OS'] = 'android' - o['variables']['node_prefix'] = os.path.expanduser(options.prefix or '') + o['variables']['node_prefix'] = options.prefix o['variables']['node_install_npm'] = b(not options.without_npm) o['default_configuration'] = 'Debug' if options.debug else 'Release' diff --git a/doc/api/buffer.markdown b/doc/api/buffer.markdown index 92b7f2ba93b288..94f3e3d8e164f5 100644 --- a/doc/api/buffer.markdown +++ b/doc/api/buffer.markdown @@ -43,7 +43,7 @@ Creating a typed array from a `Buffer` works with the following caveats: 2. The buffer's memory is interpreted as an array, not a byte array. That is, `new Uint32Array(new Buffer([1,2,3,4]))` creates a 4-element `Uint32Array` - with elements `[1,2,3,4]`, not an `Uint32Array` with a single element + with elements `[1,2,3,4]`, not a `Uint32Array` with a single element `[0x1020304]` or `[0x4030201]`. NOTE: Node.js v0.8 simply retained a reference to the buffer in `array.buffer` @@ -67,6 +67,10 @@ Allocates a new buffer of `size` bytes. `size` must be less than 2,147,483,648 bytes (2 GB) on 64 bits architectures, otherwise a `RangeError` is thrown. +Unlike `ArrayBuffers`, the underlying memory for buffers is not initialized. So +the contents of a newly created `Buffer` is unknown. Use `buf.fill(0)`to +initialize a buffer to zeroes. + ### new Buffer(array) * `array` Array diff --git a/doc/api/child_process.markdown b/doc/api/child_process.markdown index 3d6e5f5fe2f048..4acd61bfa6a97c 100644 --- a/doc/api/child_process.markdown +++ b/doc/api/child_process.markdown @@ -279,7 +279,7 @@ Here is an example of sending a server: child.send('server', server); }); -And the child would the receive the server object as: +And the child would then receive the server object as: process.on('message', function(m, server) { if (m === 'server') { diff --git a/doc/api/cluster.markdown b/doc/api/cluster.markdown index b7e76fcb8ffa0e..abf05bf9f40c82 100644 --- a/doc/api/cluster.markdown +++ b/doc/api/cluster.markdown @@ -121,7 +121,7 @@ values are `"rr"` and `"none"`. ## cluster.settings * {Object} - * `execArgv` {Array} list of string arguments passed to the io.js executable. + * `execArgv` {Array} list of string arguments passed to the io.js executable. (Default=`process.execArgv`) * `exec` {String} file path to worker file. (Default=`process.argv[1]`) * `args` {Array} string arguments passed to worker. @@ -613,7 +613,7 @@ It is not emitted in the worker. ### Event: 'disconnect' -Similar to the `cluster.on('disconnect')` event, but specfic to this worker. +Similar to the `cluster.on('disconnect')` event, but specific to this worker. cluster.fork().on('disconnect', function() { // Worker has disconnected diff --git a/doc/api/dns.markdown b/doc/api/dns.markdown index d8ed53e3fa0f79..7c9f419ce08ae7 100644 --- a/doc/api/dns.markdown +++ b/doc/api/dns.markdown @@ -85,7 +85,7 @@ All properties are optional. An example usage of options is shown below. ``` The callback has arguments `(err, address, family)`. `address` is a string -representation of a IP v4 or v6 address. `family` is either the integer 4 or 6 +representation of an IP v4 or v6 address. `family` is either the integer 4 or 6 and denotes the family of `address` (not necessarily the value initially passed to `lookup`). @@ -163,7 +163,7 @@ attribute (e.g. `[{'priority': 10, 'exchange': 'mx.example.com'},...]`). ## dns.resolveTxt(hostname, callback) The same as `dns.resolve()`, but only for text queries (`TXT` records). -`addresses` is an 2-d array of the text records available for `hostname` (e.g., +`addresses` is a 2-d array of the text records available for `hostname` (e.g., `[ ['v=spf1 ip4:0.0.0.0 ', '~all' ] ]`). Each sub-array contains TXT chunks of one record. Depending on the use case, the could be either joined together or treated separately. diff --git a/doc/api/events.markdown b/doc/api/events.markdown index fbc04a96239b0f..a51905ab4da110 100644 --- a/doc/api/events.markdown +++ b/doc/api/events.markdown @@ -122,7 +122,7 @@ Note that `emitter.setMaxListeners(n)` still has precedence over ### emitter.listeners(event) -Returns an array of listeners for the specified event. +Returns a copy of the array of listeners for the specified event. server.on('connection', function (stream) { console.log('someone connected!'); diff --git a/doc/api/fs.markdown b/doc/api/fs.markdown index 5f96b10763fe45..ebc2046d76ac1e 100644 --- a/doc/api/fs.markdown +++ b/doc/api/fs.markdown @@ -801,6 +801,10 @@ on Unix systems, it never was. Returns a new ReadStream object (See `Readable Stream`). +Be aware that, unlike the default value set for `highWaterMark` on a +readable stream (16 kb), the stream returned by this method has a +default value of 64 kb for the same parameter. + `options` is an object or string with the following defaults: { flags: 'r', @@ -823,6 +827,9 @@ there's no file descriptor leak. If `autoClose` is set to true (default behavior), on `error` or `end` the file descriptor will be closed automatically. +`mode` sets the file mode (permission and sticky bits), but only if the +file was created. + An example to read the last 10 bytes of a file which is 100 bytes long: fs.createReadStream('sample.txt', {start: 90, end: 99}); @@ -847,14 +854,14 @@ Returns a new WriteStream object (See `Writable Stream`). `options` is an object or string with the following defaults: { flags: 'w', - encoding: null, + defaultEncoding: 'utf8', fd: null, mode: 0o666 } `options` may also include a `start` option to allow writing data at some position past the beginning of the file. Modifying a file rather than replacing it may require a `flags` mode of `r+` rather than the -default mode `w`. The `encoding` can be `'utf8'`, `'ascii'`, `binary`, +default mode `w`. The `defaultEncoding` can be `'utf8'`, `'ascii'`, `binary`, or `'base64'`. Like `ReadStream` above, if `fd` is specified, `WriteStream` will ignore the diff --git a/doc/api/http.markdown b/doc/api/http.markdown index 5cf3b07a9cd778..966b304b56d282 100644 --- a/doc/api/http.markdown +++ b/doc/api/http.markdown @@ -462,6 +462,7 @@ automatically parsed with [url.parse()][]. Options: +- `protocol`: Protocol to use. Defaults to `'http'`. - `host`: A domain name or IP address of the server to issue the request to. Defaults to `'localhost'`. - `hostname`: Alias for `host`. To support `url.parse()` `hostname` is @@ -911,7 +912,8 @@ is finished. ### request.abort() -Aborts a request. (New since v0.3.8.) +Marks the request as aborting. Calling this will cause remaining data +in the response to be dropped and the socket to be destroyed. ### request.setTimeout(timeout[, callback]) diff --git a/doc/api/stream.markdown b/doc/api/stream.markdown index a7a78f229edb57..ffad1717f75096 100644 --- a/doc/api/stream.markdown +++ b/doc/api/stream.markdown @@ -164,6 +164,34 @@ readable.on('readable', function() { Once the internal buffer is drained, a `readable` event will fire again when more data is available. +The `readable` event is not emitted in the "flowing" mode with the +sole exception of the last one, on end-of-stream. + +The 'readable' event indicates that the stream has new information: +either new data is available or the end of the stream has been reached. +In the former case, `.read()` will return that data. In the latter case, +`.read()` will return null. For instance, in the following example, `foo.txt` +is an empty file: + +```javascript +var fs = require('fs'); +var rr = fs.createReadStream('foo.txt'); +rr.on('readable', function() { + console.log('readable:', rr.read()); +}); +rr.on('end', function() { + console.log('end'); +}); +``` + +The output of running this script is: + +``` +bash-3.2$ node test.js +readable: null +end +``` + #### Event: 'data' * `chunk` {Buffer | String} The chunk of data. @@ -221,7 +249,9 @@ returns it. If there is no data available, then it will return `null`. If you pass in a `size` argument, then it will return that many -bytes. If `size` bytes are not available, then it will return `null`. +bytes. If `size` bytes are not available, then it will return `null`, +unless we've ended, in which case it will return the data remaining +in the buffer. If you do not specify a `size` argument, then it will return all the data in the internal buffer. @@ -243,6 +273,9 @@ readable.on('readable', function() { If this method returns a data chunk, then it will also trigger the emission of a [`'data'` event][]. +Note that calling `readable.read([size])` after the `end` event has been +triggered will return `null`. No runtime error will be raised. + #### readable.setEncoding(encoding) * `encoding` {String} The encoding to use. @@ -414,6 +447,9 @@ parser, which needs to "un-consume" some data that it has optimistically pulled out of the source, so that the stream can be passed on to some other party. +Note that `stream.unshift(chunk)` cannot be called after the `end` event +has been triggered; a runtime error will be raised. + If you find that you must often call `stream.unshift(chunk)` in your programs, consider implementing a [Transform][] stream instead. (See API for Stream Implementors, below.) @@ -452,6 +488,13 @@ function parseHeader(stream, callback) { } } ``` +Note that, unlike `stream.push(chunk)`, `stream.unshift(chunk)` will not +end the reading process by resetting the internal reading state of the +stream. This can cause unexpected results if `unshift` is called during a +read (i.e. from within a `_read` implementation on a custom stream). Following +the call to `unshift` with an immediate `stream.push('')` will reset the +reading state appropriately, however it is best to simply avoid calling +`unshift` while in the process of performing a read. #### readable.wrap(stream) @@ -883,6 +926,10 @@ SimpleProtocol.prototype._read = function(n) { // back into the read queue so that our consumer will see it. var b = chunk.slice(split); this.unshift(b); + // calling unshift by itself does not reset the reading state + // of the stream; since we're inside _read, doing an additional + // push('') will reset the state appropriately. + this.push(''); // and let them know that we are done parsing the header. this.emit('header', this.header); @@ -922,24 +969,22 @@ initialized. * `size` {Number} Number of bytes to read asynchronously -Note: **Implement this function, but do NOT call it directly.** +Note: **Implement this method, but do NOT call it directly.** -This function should NOT be called directly. It should be implemented -by child classes, and only called by the internal Readable class -methods. +This method is prefixed with an underscore because it is internal to the +class that defines it and should only be called by the internal Readable +class methods. All Readable stream implementations must provide a _read +method to fetch data from the underlying resource. -All Readable stream implementations must provide a `_read` method to -fetch data from the underlying resource. - -This method is prefixed with an underscore because it is internal to -the class that defines it, and should not be called directly by user -programs. However, you **are** expected to override this method in -your own extension classes. +When _read is called, if data is available from the resource, `_read` should +start pushing that data into the read queue by calling `this.push(dataChunk)`. +`_read` should continue reading from the resource and pushing data until push +returns false, at which point it should stop reading from the resource. Only +when _read is called again after it has stopped should it start reading +more data from the resource and pushing that data onto the queue. -When data is available, put it into the read queue by calling -`readable.push(chunk)`. If `push` returns false, then you should stop -reading. When `_read` is called again, you should start pushing more -data. +Note: once the `_read()` method is called, it will not be called again until +the `push` method is called. The `size` argument is advisory. Implementations where a "read" is a single call that returns data can use this to know how much data to @@ -955,19 +1000,16 @@ becomes available. There is no need, for example to "wait" until Buffer encoding, such as `'utf8'` or `'ascii'` * return {Boolean} Whether or not more pushes should be performed -Note: **This function should be called by Readable implementors, NOT +Note: **This method should be called by Readable implementors, NOT by consumers of Readable streams.** -The `_read()` function will not be called again until at least one -`push(chunk)` call is made. - -The `Readable` class works by putting data into a read queue to be -pulled out later by calling the `read()` method when the `'readable'` -event fires. +If a value other than null is passed, The `push()` method adds a chunk of data +into the queue for subsequent stream processors to consume. If `null` is +passed, it signals the end of the stream (EOF), after which no more data +can be written. -The `push()` method will explicitly insert some data into the read -queue. If it is called with `null` then it will signal the end of the -data (EOF). +The data added with `push` can be pulled out by calling the `read()` method +when the `'readable'`event fires. This API is designed to be as flexible as possible. For example, you may be wrapping a lower-level source which has some sort of @@ -1315,7 +1357,7 @@ for examples and testing, but there are occasionally use cases where it can come in handy as a building block for novel sorts of streams. -## Simplified Constructor API +## Simplified Constructor API diff --git a/lib/_http_client.js b/lib/_http_client.js index a7d714f7e0b0b2..50d1052b44e15c 100644 --- a/lib/_http_client.js +++ b/lib/_http_client.js @@ -359,7 +359,7 @@ function parserOnIncomingClient(res, shouldKeepAlive) { var req = socket._httpMessage; - // propogate "domain" setting... + // propagate "domain" setting... if (req.domain && !res.domain) { debug('setting "res.domain"'); res.domain = req.domain; @@ -465,7 +465,7 @@ function tickOnSocket(req, socket) { socket.parser = parser; socket._httpMessage = req; - // Setup "drain" propogation. + // Setup "drain" propagation. httpSocketSetup(socket); // Propagate headers limit from request object to parser diff --git a/lib/_tls_wrap.js b/lib/_tls_wrap.js index 182346904c9bf4..b5d1899480bf46 100644 --- a/lib/_tls_wrap.js +++ b/lib/_tls_wrap.js @@ -584,17 +584,6 @@ TLSSocket.prototype._start = function() { this._handle.start(); }; -TLSSocket.prototype._isSessionResumed = function _isSessionResumed(session) { - if (!session) - return false; - - var next = this.getSession(); - if (!next) - return false; - - return next.equals(session); -}; - TLSSocket.prototype.setServername = function(name) { this._handle.setServername(name); }; @@ -1011,7 +1000,7 @@ exports.connect = function(/* [port, host], options, cb */) { // Verify that server's identity matches it's certificate's names // Unless server has resumed our existing session - if (!verifyError && !socket._isSessionResumed(options.session)) { + if (!verifyError && !socket.isSessionReused()) { var cert = socket.getPeerCertificate(); verifyError = options.checkServerIdentity(hostname, cert); } diff --git a/lib/net.js b/lib/net.js index 6146029d3c4743..10e789678440fd 100644 --- a/lib/net.js +++ b/lib/net.js @@ -93,6 +93,7 @@ function initSocketHandle(self) { self.destroyed = false; self.bytesRead = 0; self._bytesDispatched = 0; + self._sockname = null; // Handle creation may be deferred to bind() or connect() time. if (self._handle) { @@ -469,6 +470,7 @@ Socket.prototype._destroy = function(exception, cb) { }); this._handle.onread = noop; this._handle = null; + this._sockname = null; } // we set destroyed to true before firing error callbacks in order @@ -871,6 +873,7 @@ Socket.prototype.connect = function(options, cb) { this.destroyed = false; this._handle = null; this._peername = null; + this._sockname = null; } var self = this; @@ -1032,6 +1035,7 @@ function afterConnect(status, handle, req, readable, writable) { assert.ok(self._connecting); self._connecting = false; + self._sockname = null; if (status == 0) { self.readable = readable; diff --git a/lib/path.js b/lib/path.js index e0c5bcaa1c1050..78c61579ec0fc8 100644 --- a/lib/path.js +++ b/lib/path.js @@ -76,7 +76,7 @@ function win32SplitPath(filename) { // Separate device+slash from tail var result = splitDeviceRe.exec(filename), device = (result[1] || '') + (result[2] || ''), - tail = result[3] || ''; + tail = result[3]; // Split the tail into dir, basename and extension var result2 = splitTailRe.exec(tail), dir = result2[1], @@ -386,9 +386,6 @@ win32.parse = function(pathString) { assertPath(pathString); var allParts = win32SplitPath(pathString); - if (!allParts || allParts.length !== 4) { - throw new TypeError("Invalid path '" + pathString + "'"); - } return { root: allParts[0], dir: allParts[0] + allParts[1].slice(0, -1), @@ -590,13 +587,6 @@ posix.parse = function(pathString) { assertPath(pathString); var allParts = posixSplitPath(pathString); - if (!allParts || allParts.length !== 4) { - throw new TypeError("Invalid path '" + pathString + "'"); - } - allParts[1] = allParts[1] || ''; - allParts[2] = allParts[2] || ''; - allParts[3] = allParts[3] || ''; - return { root: allParts[0], dir: allParts[0] + allParts[1].slice(0, -1), diff --git a/lib/url.js b/lib/url.js index 55c5248e4751dd..45155fee936bbf 100644 --- a/lib/url.js +++ b/lib/url.js @@ -587,7 +587,7 @@ Url.prototype.resolveObject = function(relative) { if (psychotic) { result.hostname = result.host = srcPath.shift(); //occationaly the auth can get stuck only in host - //this especialy happens in cases like + //this especially happens in cases like //url.resolveObject('mailto:local1@domain1', 'local2@domain2') var authInHost = result.host && result.host.indexOf('@') > 0 ? result.host.split('@') : false; @@ -669,7 +669,7 @@ Url.prototype.resolveObject = function(relative) { result.hostname = result.host = isAbsolute ? '' : srcPath.length ? srcPath.shift() : ''; //occationaly the auth can get stuck only in host - //this especialy happens in cases like + //this especially happens in cases like //url.resolveObject('mailto:local1@domain1', 'local2@domain2') var authInHost = result.host && result.host.indexOf('@') > 0 ? result.host.split('@') : false; diff --git a/lib/util.js b/lib/util.js index 136a66a6c1c0e8..c5d7bea7db352d 100644 --- a/lib/util.js +++ b/lib/util.js @@ -167,6 +167,22 @@ function arrayToHash(array) { } +function getConstructorOf(obj) { + while (obj) { + var descriptor = Object.getOwnPropertyDescriptor(obj, 'constructor'); + if (descriptor !== undefined && + typeof descriptor.value === 'function' && + descriptor.value.name !== '') { + return descriptor.value; + } + + obj = Object.getPrototypeOf(obj); + } + + return null; +} + + function inspectPromise(p) { Debug = Debug || require('vm').runInDebugContext('Debug'); var mirror = Debug.MakeMirror(p, true); @@ -260,14 +276,17 @@ function formatValue(ctx, value, recurseTimes) { } } + var constructor = getConstructorOf(value); var base = '', empty = false, braces, formatter; if (Array.isArray(value)) { + if (constructor === Array) + constructor = null; braces = ['[', ']']; empty = value.length === 0; formatter = formatArray; } else if (value instanceof Set) { - braces = ['Set {', '}']; + braces = ['{', '}']; // With `showHidden`, `length` will display as a hidden property for // arrays. For consistency's sake, do the same for `size`, even though this // property isn't selected by Object.getOwnPropertyNames(). @@ -276,7 +295,7 @@ function formatValue(ctx, value, recurseTimes) { empty = value.size === 0; formatter = formatSet; } else if (value instanceof Map) { - braces = ['Map {', '}']; + braces = ['{', '}']; // Ditto. if (ctx.showHidden) keys.unshift('size'); @@ -286,9 +305,11 @@ function formatValue(ctx, value, recurseTimes) { // Only create a mirror if the object superficially looks like a Promise. var promiseInternals = value instanceof Promise && inspectPromise(value); if (promiseInternals) { - braces = ['Promise {', '}']; + braces = ['{', '}']; formatter = formatPromise; } else { + if (constructor === Object) + constructor = null; braces = ['{', '}']; empty = true; // No other data than keys. formatter = formatObject; @@ -336,6 +357,10 @@ function formatValue(ctx, value, recurseTimes) { base = ' ' + '[Boolean: ' + formatted + ']'; } + // Add constructor name if available + if (base === '' && constructor) + braces[0] = constructor.name + ' ' + braces[0]; + if (empty === true) { return braces[0] + base + braces[1]; } diff --git a/src/env.h b/src/env.h index 501c151122198e..bf80881907e65a 100644 --- a/src/env.h +++ b/src/env.h @@ -198,6 +198,7 @@ namespace node { V(syscall_string, "syscall") \ V(tick_callback_string, "_tickCallback") \ V(tick_domain_cb_string, "_tickDomainCallback") \ + V(ticketkeycallback_string, "onticketkeycallback") \ V(timeout_string, "timeout") \ V(times_string, "times") \ V(timestamp_string, "timestamp") \ diff --git a/src/node.cc b/src/node.cc index 7c0a80ec31462b..c272139e5518cd 100644 --- a/src/node.cc +++ b/src/node.cc @@ -2184,7 +2184,7 @@ static void OnFatalError(const char* location, const char* message) { NO_RETURN void FatalError(const char* location, const char* message) { OnFatalError(location, message); - // to supress compiler warning + // to suppress compiler warning abort(); } diff --git a/src/node_crypto.cc b/src/node_crypto.cc index c14f2b600c677e..e33b249242e3e5 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -300,9 +300,23 @@ void SecureContext::Initialize(Environment* env, Handle target) { env->SetProtoMethod(t, "getTicketKeys", SecureContext::GetTicketKeys); env->SetProtoMethod(t, "setTicketKeys", SecureContext::SetTicketKeys); env->SetProtoMethod(t, "setFreeListLength", SecureContext::SetFreeListLength); + env->SetProtoMethod(t, + "enableTicketKeyCallback", + SecureContext::EnableTicketKeyCallback); env->SetProtoMethod(t, "getCertificate", SecureContext::GetCertificate); env->SetProtoMethod(t, "getIssuer", SecureContext::GetCertificate); + t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "kTicketKeyReturnIndex"), + Integer::NewFromUnsigned(env->isolate(), kTicketKeyReturnIndex)); + t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "kTicketKeyHMACIndex"), + Integer::NewFromUnsigned(env->isolate(), kTicketKeyHMACIndex)); + t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "kTicketKeyAESIndex"), + Integer::NewFromUnsigned(env->isolate(), kTicketKeyAESIndex)); + t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "kTicketKeyNameIndex"), + Integer::NewFromUnsigned(env->isolate(), kTicketKeyNameIndex)); + t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "kTicketKeyIVIndex"), + Integer::NewFromUnsigned(env->isolate(), kTicketKeyIVIndex)); + t->PrototypeTemplate()->SetAccessor( FIXED_ONE_BYTE_STRING(env->isolate(), "_external"), CtxGetter, @@ -378,6 +392,7 @@ void SecureContext::Init(const FunctionCallbackInfo& args) { } sc->ctx_ = SSL_CTX_new(method); + SSL_CTX_set_app_data(sc->ctx_, sc); // Disable SSLv2 in the case when method == SSLv23_method() and the // cipher list contains SSLv2 ciphers (not the default, should be rare.) @@ -982,6 +997,95 @@ void SecureContext::SetFreeListLength(const FunctionCallbackInfo& args) { } +void SecureContext::EnableTicketKeyCallback( + const FunctionCallbackInfo& args) { + SecureContext* wrap = Unwrap(args.Holder()); + + SSL_CTX_set_tlsext_ticket_key_cb(wrap->ctx_, TicketKeyCallback); +} + + +int SecureContext::TicketKeyCallback(SSL* ssl, + unsigned char* name, + unsigned char* iv, + EVP_CIPHER_CTX* ectx, + HMAC_CTX* hctx, + int enc) { + static const int kTicketPartSize = 16; + + SecureContext* sc = static_cast( + SSL_CTX_get_app_data(ssl->ctx)); + + Environment* env = sc->env(); + HandleScope handle_scope(env->isolate()); + Context::Scope context_scope(env->context()); + + Local argv[] = { + Buffer::New(env, + reinterpret_cast(name), + kTicketPartSize).ToLocalChecked(), + Buffer::New(env, + reinterpret_cast(iv), + kTicketPartSize).ToLocalChecked(), + Boolean::New(env->isolate(), enc != 0) + }; + Local ret = node::MakeCallback(env, + sc->object(), + env->ticketkeycallback_string(), + ARRAY_SIZE(argv), + argv); + Local arr = ret.As(); + + int r = arr->Get(kTicketKeyReturnIndex)->Int32Value(); + if (r < 0) + return r; + + Local hmac = arr->Get(kTicketKeyHMACIndex); + Local aes = arr->Get(kTicketKeyAESIndex); + if (Buffer::Length(aes) != kTicketPartSize) + return -1; + + if (enc) { + Local name_val = arr->Get(kTicketKeyNameIndex); + Local iv_val = arr->Get(kTicketKeyIVIndex); + + if (Buffer::Length(name_val) != kTicketPartSize || + Buffer::Length(iv_val) != kTicketPartSize) { + return -1; + } + + memcpy(name, Buffer::Data(name_val), kTicketPartSize); + memcpy(iv, Buffer::Data(iv_val), kTicketPartSize); + } + + HMAC_Init_ex(hctx, + Buffer::Data(hmac), + Buffer::Length(hmac), + EVP_sha256(), + nullptr); + + const unsigned char* aes_key = + reinterpret_cast(Buffer::Data(aes)); + if (enc) { + EVP_EncryptInit_ex(ectx, + EVP_aes_128_cbc(), + nullptr, + aes_key, + iv); + } else { + EVP_DecryptInit_ex(ectx, + EVP_aes_128_cbc(), + nullptr, + aes_key, + iv); + } + + return r; +} + + + + void SecureContext::CtxGetter(Local property, const PropertyCallbackInfo& info) { HandleScope scope(info.GetIsolate()); diff --git a/src/node_crypto.h b/src/node_crypto.h index 3a00b519323d52..edacaa1b0095b9 100644 --- a/src/node_crypto.h +++ b/src/node_crypto.h @@ -68,6 +68,13 @@ class SecureContext : public BaseObject { static const int kMaxSessionSize = 10 * 1024; + // See TicketKeyCallback + static const int kTicketKeyReturnIndex = 0; + static const int kTicketKeyHMACIndex = 1; + static const int kTicketKeyAESIndex = 2; + static const int kTicketKeyNameIndex = 3; + static const int kTicketKeyIVIndex = 4; + protected: static const int64_t kExternalSize = sizeof(SSL_CTX); @@ -92,12 +99,21 @@ class SecureContext : public BaseObject { static void SetTicketKeys(const v8::FunctionCallbackInfo& args); static void SetFreeListLength( const v8::FunctionCallbackInfo& args); + static void EnableTicketKeyCallback( + const v8::FunctionCallbackInfo& args); static void CtxGetter(v8::Local property, const v8::PropertyCallbackInfo& info); template static void GetCertificate(const v8::FunctionCallbackInfo& args); + static int TicketKeyCallback(SSL* ssl, + unsigned char* name, + unsigned char* iv, + EVP_CIPHER_CTX* ectx, + HMAC_CTX* hctx, + int enc); + SecureContext(Environment* env, v8::Local wrap) : BaseObject(env, wrap), ca_store_(nullptr), diff --git a/src/node_object_wrap.h b/src/node_object_wrap.h index d00e1484b7c10c..f0226622272a5c 100644 --- a/src/node_object_wrap.h +++ b/src/node_object_wrap.h @@ -80,7 +80,7 @@ class ObjectWrap { * attached to detached state it will be freed. Be careful not to access * the object after making this call as it might be gone! * (A "weak reference" means an object that only has a - * persistant handle.) + * persistent handle.) * * DO NOT CALL THIS FROM DESTRUCTOR */ diff --git a/src/res/node.exe.extra.manifest b/src/res/node.exe.extra.manifest index c4cc80a141d9dd..e2e9f175473305 100644 --- a/src/res/node.exe.extra.manifest +++ b/src/res/node.exe.extra.manifest @@ -2,6 +2,8 @@ + + diff --git a/test/internet/test-dgram-multicast-multi-process.js b/test/internet/test-dgram-multicast-multi-process.js index 05acb844e54490..34db38331dc7b6 100644 --- a/test/internet/test-dgram-multicast-multi-process.js +++ b/test/internet/test-dgram-multicast-multi-process.js @@ -3,7 +3,6 @@ var common = require('../common'), assert = require('assert'), dgram = require('dgram'), util = require('util'), - assert = require('assert'), Buffer = require('buffer').Buffer, fork = require('child_process').fork, LOCAL_BROADCAST_HOST = '224.0.0.114', @@ -183,10 +182,9 @@ if (process.argv[2] === 'child') { process.send({ message: buf.toString() }); if (receivedMessages.length == messages.length) { + // .dropMembership() not strictly needed but here as a sanity check listenSocket.dropMembership(LOCAL_BROADCAST_HOST); - - process.nextTick(function() { // TODO should be changed to below. - // listenSocket.dropMembership(LOCAL_BROADCAST_HOST, function() { + process.nextTick(function() { listenSocket.close(); }); } diff --git a/test/parallel/test-http-default-encoding.js b/test/parallel/test-http-default-encoding.js index a40c8841eceab9..612a75bce46647 100644 --- a/test/parallel/test-http-default-encoding.js +++ b/test/parallel/test-http-default-encoding.js @@ -11,16 +11,11 @@ var server = http.Server(function(req, res) { req.on('data', function(chunk) { result += chunk; }).on('end', function() { - clearTimeout(timeout); server.close(); + res.writeHead(200); + res.end('hello world\n'); }); - var timeout = setTimeout(function() { - process.exit(1); - }, 100); - - res.writeHead(200); - res.end('hello world\n'); }); server.listen(common.PORT, function() { diff --git a/test/parallel/test-http-request-end.js b/test/parallel/test-http-request-end.js index 43d023028a4fd0..6ecfc0672e35ff 100644 --- a/test/parallel/test-http-request-end.js +++ b/test/parallel/test-http-request-end.js @@ -14,10 +14,10 @@ var server = http.Server(function(req, res) { req.on('end', function() { server.close(); + res.writeHead(200); + res.end('hello world\n'); }); - res.writeHead(200); - res.end('hello world\n'); }); server.listen(common.PORT, function() { diff --git a/test/parallel/test-https-resume-after-renew.js b/test/parallel/test-https-resume-after-renew.js new file mode 100644 index 00000000000000..23626ccb40cb51 --- /dev/null +++ b/test/parallel/test-https-resume-after-renew.js @@ -0,0 +1,56 @@ +'use strict'; +var common = require('../common'); +var fs = require('fs'); +var https = require('https'); +var crypto = require('crypto'); + +var options = { + key: fs.readFileSync(common.fixturesDir + '/keys/agent1-key.pem'), + cert: fs.readFileSync(common.fixturesDir + '/keys/agent1-cert.pem'), + ca: fs.readFileSync(common.fixturesDir + '/keys/ca1-cert.pem') +}; + +var server = https.createServer(options, function(req, res) { + res.end('hello'); +}); + +var aes = new Buffer(16); +aes.fill('S'); +var hmac = new Buffer(16); +hmac.fill('H'); + +server._sharedCreds.context.enableTicketKeyCallback(); +server._sharedCreds.context.onticketkeycallback = function(name, iv, enc) { + if (enc) { + var newName = new Buffer(16); + var newIV = crypto.randomBytes(16); + newName.fill('A'); + } else { + // Renew + return [ 2, hmac, aes ]; + } + + return [ 1, hmac, aes, newName, newIV ]; +}; + +server.listen(common.PORT, function() { + var addr = this.address(); + + function doReq(callback) { + https.request({ + method: 'GET', + port: addr.port, + servername: 'agent1', + ca: options.ca + }, function(res) { + res.resume(); + res.once('end', callback); + }).end(); + } + + doReq(function() { + doReq(function() { + server.close(); + }); + }); +}); diff --git a/test/parallel/test-listen-fd-cluster.js b/test/parallel/test-listen-fd-cluster.js index e895a2944789bd..f6d00c72a0f78c 100644 --- a/test/parallel/test-listen-fd-cluster.js +++ b/test/parallel/test-listen-fd-cluster.js @@ -7,63 +7,62 @@ var PORT = common.PORT; var spawn = require('child_process').spawn; var cluster = require('cluster'); -console.error('Cluster listen fd test', process.argv.slice(2)); +console.error('Cluster listen fd test', process.argv[2] || 'runner'); if (common.isWindows) { console.log('1..0 # Skipped: This test is disabled on windows.'); return; } +// Process relationship is: +// +// parent: the test main script +// -> master: the cluster master +// -> worker: the cluster worker switch (process.argv[2]) { case 'master': return master(); case 'worker': return worker(); case 'parent': return parent(); - default: return test(); } +var ok; + +process.on('exit', function() { + assert.ok(ok); +}); + // spawn the parent, and listen for it to tell us the pid of the cluster. // WARNING: This is an example of listening on some arbitrary FD number // that has already been bound elsewhere in advance. However, binding // server handles to stdio fd's is NOT a good or reliable way to do // concurrency in HTTP servers! Use the cluster module, or if you want // a more low-level approach, use child process IPC manually. -function test() { - var parent = spawn(process.execPath, [__filename, 'parent'], { - stdio: [ 0, 'pipe', 2 ] - }); - var json = ''; - parent.stdout.on('data', function(c) { - json += c.toString(); - if (json.indexOf('\n') !== -1) next(); - }); - function next() { - console.error('output from parent = %s', json); - var cluster = JSON.parse(json); - // now make sure that we can request to the worker, then kill it. - http.get({ - server: 'localhost', - port: PORT, - path: '/', - }).on('response', function(res) { - var s = ''; - res.on('data', function(c) { - s += c.toString(); - }); - res.on('end', function() { - // kill the worker before we start doing asserts. - // it's really annoying when tests leave orphans! - parent.kill(); - process.kill(cluster.master, 'SIGKILL'); - +test(function(parent) { + // now make sure that we can request to the worker, then kill it. + http.get({ + server: 'localhost', + port: PORT, + path: '/', + }).on('response', function(res) { + var s = ''; + res.on('data', function(c) { + s += c.toString(); + }); + res.on('end', function() { + // kill the worker before we start doing asserts. + // it's really annoying when tests leave orphans! + parent.kill(); + parent.on('exit', function() { assert.equal(s, 'hello from worker\n'); assert.equal(res.statusCode, 200); console.log('ok'); + ok = true; }); }); - } -} + }); +}); -function parent() { +function test(cb) { console.error('about to listen in parent'); var server = net.createServer(function(conn) { console.error('connection on parent'); @@ -73,7 +72,7 @@ function parent() { var spawn = require('child_process').spawn; var master = spawn(process.execPath, [__filename, 'master'], { - stdio: [ 0, 1, 2, server._handle ], + stdio: [ 0, 'pipe', 2, server._handle, 'ipc' ], detached: true }); @@ -90,6 +89,11 @@ function parent() { console.error('master closed'); }); console.error('master spawned'); + master.on('message', function(msg) { + if (msg === 'started worker') { + cb(master); + } + }); }); } @@ -99,7 +103,17 @@ function master() { args: [ 'worker' ] }); var worker = cluster.fork(); - console.log('%j\n', { master: process.pid, worker: worker.pid }); + worker.on('message', function(msg) { + if (msg === 'worker ready') { + process.send('started worker'); + } + }); + // Prevent outliving our parent process in case it is abnormally killed - + // under normal conditions our parent kills this process before exiting. + process.on('disconnect', function() { + console.error('master exit on disconnect'); + process.exit(0); + }); } @@ -112,5 +126,6 @@ function worker() { res.end('hello from worker\n'); }).listen({ fd: 3 }, function() { console.error('worker listening on fd=3'); + process.send('worker ready'); }); } diff --git a/test/parallel/test-listen-fd-server.js b/test/parallel/test-listen-fd-server.js index d4d11e5a8d9693..d51d51ee8eb4de 100644 --- a/test/parallel/test-listen-fd-server.js +++ b/test/parallel/test-listen-fd-server.js @@ -13,54 +13,50 @@ if (common.isWindows) { switch (process.argv[2]) { case 'child': return child(); - case 'parent': return parent(); - default: return test(); } -// spawn the parent, and listen for it to tell us the pid of the child. +var ok; + +process.on('exit', function() { + assert.ok(ok); +}); + // WARNING: This is an example of listening on some arbitrary FD number // that has already been bound elsewhere in advance. However, binding // server handles to stdio fd's is NOT a good or reliable way to do // concurrency in HTTP servers! Use the cluster module, or if you want // a more low-level approach, use child process IPC manually. -function test() { - var parent = spawn(process.execPath, [__filename, 'parent'], { - stdio: [ 0, 'pipe', 2 ] - }); - var json = ''; - parent.stdout.on('data', function(c) { - json += c.toString(); - if (json.indexOf('\n') !== -1) next(); - }); - function next() { - console.error('output from parent = %s', json); - var child = JSON.parse(json); - // now make sure that we can request to the child, then kill it. - http.get({ - server: 'localhost', - port: PORT, - path: '/', - }).on('response', function(res) { - var s = ''; - res.on('data', function(c) { - s += c.toString(); - }); - res.on('end', function() { - // kill the child before we start doing asserts. - // it's really annoying when tests leave orphans! - process.kill(child.pid, 'SIGKILL'); - try { - parent.kill(); - } catch (e) {} - +test(function(child) { + // now make sure that we can request to the child, then kill it. + http.get({ + server: 'localhost', + port: PORT, + path: '/', + }).on('response', function(res) { + var s = ''; + res.on('data', function(c) { + s += c.toString(); + }); + res.on('end', function() { + child.kill(); + child.on('exit', function() { assert.equal(s, 'hello from child\n'); assert.equal(res.statusCode, 200); + console.log('ok'); + ok = true; }); }); - } -} + }); +}); function child() { + // Prevent outliving the parent process in case it is terminated before + // killing this child process. + process.on('disconnect', function() { + console.error('exit on disconnect'); + process.exit(0); + }); + // start a server on fd=3 http.createServer(function(req, res) { console.error('request on child'); @@ -68,10 +64,11 @@ function child() { res.end('hello from child\n'); }).listen({ fd: 3 }, function() { console.error('child listening on fd=3'); + process.send('listening'); }); } -function parent() { +function test(cb) { var server = net.createServer(function(conn) { console.error('connection on parent'); conn.end('hello from parent\n'); @@ -80,7 +77,7 @@ function parent() { var spawn = require('child_process').spawn; var child = spawn(process.execPath, [__filename, 'child'], { - stdio: [ 0, 1, 2, server._handle ] + stdio: [ 0, 1, 2, server._handle, 'ipc' ] }); console.log('%j\n', { pid: child.pid }); @@ -90,13 +87,10 @@ function parent() { // be accepted, because the child has the fd open. server.close(); - child.on('exit', function(code) { - console.error('child exited', code); - }); - - child.on('close', function() { - console.error('child closed'); + child.on('message', function(msg) { + if (msg === 'listening') { + cb(child); + } }); - console.error('child spawned'); }); } diff --git a/test/parallel/test-net-socket-local-address.js b/test/parallel/test-net-socket-local-address.js new file mode 100644 index 00000000000000..4c0e31d08c4771 --- /dev/null +++ b/test/parallel/test-net-socket-local-address.js @@ -0,0 +1,34 @@ +'use strict'; +var common = require('../common'); +var assert = require('assert'); +var net = require('net'); + +var conns = 0; +var clientLocalPorts = []; +var serverRemotePorts = []; + +var server = net.createServer(function(socket) { + serverRemotePorts.push(socket.remotePort); + conns++; +}); + +var client = new net.Socket(); + +server.on('close', function() { + assert.deepEqual(clientLocalPorts, serverRemotePorts, + 'client and server should agree on the ports used'); + assert.equal(2, conns); +}); + +server.listen(common.PORT, common.localhostIPv4, testConnect); + +function testConnect() { + if (conns == 2) { + return server.close(); + } + client.connect(common.PORT, common.localhostIPv4, function() { + clientLocalPorts.push(this.localPort); + this.once('close', testConnect); + this.destroy(); + }); +} diff --git a/test/parallel/test-path-parse-format.js b/test/parallel/test-path-parse-format.js index 677bf3241f0bae..e90fe217de93ec 100644 --- a/test/parallel/test-path-parse-format.js +++ b/test/parallel/test-path-parse-format.js @@ -9,6 +9,7 @@ var winPaths = [ '\\foo\\C:', 'file', '.\\file', + '', // unc '\\\\server\\share\\file_path', @@ -32,7 +33,8 @@ var unixPaths = [ 'file', '.\\file', './file', - 'C:\\foo' + 'C:\\foo', + '' ]; var unixSpecialCaseFormatTests = [ @@ -52,8 +54,6 @@ var errors = [ message: /Path must be a string. Received 1/}, {method: 'parse', input: [], message: /Path must be a string. Received undefined/}, - // {method: 'parse', input: [''], - // message: /Invalid path/}, // omitted because it's hard to trigger! {method: 'format', input: [null], message: /Parameter 'pathObject' must be an object, not/}, {method: 'format', input: [''], @@ -93,8 +93,13 @@ function checkErrors(path) { } function checkParseFormat(path, paths) { - paths.forEach(function(element, index, array) { + paths.forEach(function(element) { var output = path.parse(element); + assert.strictEqual(typeof output.root, 'string'); + assert.strictEqual(typeof output.dir, 'string'); + assert.strictEqual(typeof output.base, 'string'); + assert.strictEqual(typeof output.ext, 'string'); + assert.strictEqual(typeof output.name, 'string'); assert.strictEqual(path.format(output), element); assert.strictEqual(output.dir, output.dir ? path.dirname(element) : ''); assert.strictEqual(output.base, path.basename(element)); diff --git a/test/parallel/test-sys.js b/test/parallel/test-sys.js index bbc8c092002af6..9367e55c687f2f 100644 --- a/test/parallel/test-sys.js +++ b/test/parallel/test-sys.js @@ -17,7 +17,7 @@ assert.equal(new Date('2010-02-14T12:48:40+01:00').toString(), assert.equal("'\\n\\u0001'", common.inspect('\n\u0001')); assert.equal('[]', common.inspect([])); -assert.equal('{}', common.inspect(Object.create([]))); +assert.equal('Array {}', common.inspect(Object.create([]))); assert.equal('[ 1, 2 ]', common.inspect([1, 2])); assert.equal('[ 1, [ 2, 3 ] ]', common.inspect([1, [2, 3]])); diff --git a/test/parallel/test-util-inspect.js b/test/parallel/test-util-inspect.js index f583005ce96f75..fcde1c74c3984e 100644 --- a/test/parallel/test-util-inspect.js +++ b/test/parallel/test-util-inspect.js @@ -61,7 +61,7 @@ assert.ok(ex.indexOf('[message]') != -1); // GH-1941 // should not throw: -assert.equal(util.inspect(Object.create(Date.prototype)), '{}'); +assert.equal(util.inspect(Object.create(Date.prototype)), 'Date {}'); // GH-1944 assert.doesNotThrow(function() { @@ -306,3 +306,44 @@ checkAlignment(function() { }()); checkAlignment(new Set(big_array)); checkAlignment(new Map(big_array.map(function(y) { return [y, null]; }))); + + +// Test display of constructors + +class ObjectSubclass {} +class ArraySubclass extends Array {} +class SetSubclass extends Set {} +class MapSubclass extends Map {} +class PromiseSubclass extends Promise {} + +var x = new ObjectSubclass(); +x.foo = 42; +assert.equal(util.inspect(x), + 'ObjectSubclass { foo: 42 }'); +assert.equal(util.inspect(new ArraySubclass(1, 2, 3)), + 'ArraySubclass [ 1, 2, 3 ]'); +assert.equal(util.inspect(new SetSubclass([1, 2, 3])), + 'SetSubclass { 1, 2, 3 }'); +assert.equal(util.inspect(new MapSubclass([['foo', 42]])), + 'MapSubclass { \'foo\' => 42 }'); +assert.equal(util.inspect(new PromiseSubclass(function() {})), + 'PromiseSubclass { }'); + +// Corner cases. +var x = { constructor: 42 }; +assert.equal(util.inspect(x), '{ constructor: 42 }'); + +var x = {}; +Object.defineProperty(x, 'constructor', { + get: function() { + throw new Error('should not access constructor'); + }, + enumerable: true +}); +assert.equal(util.inspect(x), '{ constructor: [Getter] }'); + +var x = new (function() {}); +assert.equal(util.inspect(x), '{}'); + +var x = Object.create(null); +assert.equal(util.inspect(x), '{}');