diff --git a/.gitignore b/.gitignore
index 7be817acce2349..ebeedeac6b8fe0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -127,3 +127,4 @@ deps/v8/src/Debug/
deps/v8/src/Release/
deps/v8/src/inspector/Debug/
deps/v8/src/inspector/Release/
+deps/v8/third_party/eu-strip/
diff --git a/Makefile b/Makefile
index 6736d601685dce..b3e65cec6030a9 100644
--- a/Makefile
+++ b/Makefile
@@ -652,7 +652,7 @@ tools/doc/node_modules/js-yaml/package.json:
gen-json = tools/doc/generate.js --format=json $< > $@
gen-html = tools/doc/generate.js --node-version=$(FULLVERSION) --format=html \
- --template=doc/template.html --analytics=$(DOCS_ANALYTICS) $< > $@
+ --analytics=$(DOCS_ANALYTICS) $< > $@
out/doc/api/%.json: doc/api/%.md
$(call available-node, $(gen-json))
diff --git a/README.md b/README.md
index d39c7639e20d1a..ef733c277619b5 100644
--- a/README.md
+++ b/README.md
@@ -55,6 +55,8 @@ search these unofficial resources:
* [Questions tagged 'node.js' on StackOverflow][]
* [#node.js channel on chat.freenode.net][]. See for more
information.
+* [Node.js Slack Community](https://node-js.slack.com/): Visit
+ [nodeslackers.com](http://www.nodeslackers.com/) to register.
GitHub issues are meant for tracking enhancements and bugs, not general support.
diff --git a/benchmark/http/headers.js b/benchmark/http/headers.js
new file mode 100644
index 00000000000000..748865afbf3a04
--- /dev/null
+++ b/benchmark/http/headers.js
@@ -0,0 +1,36 @@
+'use strict';
+
+const common = require('../common.js');
+const http = require('http');
+
+const bench = common.createBenchmark(main, {
+ duplicates: [1, 100],
+ n: [10, 1000],
+});
+
+function main({ duplicates, n }) {
+ const headers = {
+ 'Connection': 'keep-alive',
+ 'Transfer-Encoding': 'chunked',
+ };
+
+ for (var i = 0; i < n / duplicates; i++) {
+ headers[`foo${i}`] = [];
+ for (var j = 0; j < duplicates; j++) {
+ headers[`foo${i}`].push(`some header value ${i}`);
+ }
+ }
+
+ const server = http.createServer(function(req, res) {
+ res.writeHead(200, headers);
+ res.end();
+ });
+ server.listen(common.PORT, function() {
+ bench.http({
+ path: '/',
+ connections: 10
+ }, function() {
+ server.close();
+ });
+ });
+}
diff --git a/benchmark/zlib/pipe.js b/benchmark/zlib/pipe.js
new file mode 100644
index 00000000000000..9b05749bbb824d
--- /dev/null
+++ b/benchmark/zlib/pipe.js
@@ -0,0 +1,39 @@
+'use strict';
+const common = require('../common.js');
+const fs = require('fs');
+const zlib = require('zlib');
+
+const bench = common.createBenchmark(main, {
+ inputLen: [1024],
+ duration: [5],
+ type: ['string', 'buffer']
+});
+
+function main({ inputLen, duration, type }) {
+ const buffer = Buffer.alloc(inputLen, fs.readFileSync(__filename));
+ const chunk = type === 'buffer' ? buffer : buffer.toString('utf8');
+
+ const input = zlib.createGzip();
+ const output = zlib.createGunzip();
+
+ let readFromOutput = 0;
+ input.pipe(output);
+ if (type === 'string')
+ output.setEncoding('utf8');
+ output.on('data', (chunk) => readFromOutput += chunk.length);
+
+ function write() {
+ input.write(chunk, write);
+ }
+
+ bench.start();
+ write();
+
+ setTimeout(() => {
+ // Give result in GBit/s, like the net benchmarks do
+ bench.end(readFromOutput * 8 / (1024 ** 3));
+
+ // Cut off writing the easy way.
+ input.write = () => {};
+ }, duration * 1000);
+}
diff --git a/configure b/configure
index c1170f9a132a16..17e13a48d47e5a 100755
--- a/configure
+++ b/configure
@@ -501,11 +501,6 @@ parser.add_option('--without-node-options',
dest='without_node_options',
help='build without NODE_OPTIONS support')
-parser.add_option('--xcode',
- action='store_true',
- dest='use_xcode',
- help='generate build files for use with xcode')
-
parser.add_option('--ninja',
action='store_true',
dest='use_ninja',
@@ -1005,9 +1000,6 @@ def configure_node(o):
o['variables']['asan'] = int(options.enable_asan or 0)
- if options.use_xcode and options.use_ninja:
- raise Exception('--xcode and --ninja cannot be used together.')
-
if options.coverage:
o['variables']['coverage'] = 'true'
else:
@@ -1530,7 +1522,6 @@ write('config.gypi', do_not_edit +
config = {
'BUILDTYPE': 'Debug' if options.debug else 'Release',
- 'USE_XCODE': str(int(options.use_xcode or 0)),
'PYTHON': sys.executable,
'NODE_TARGET_TYPE': variables['node_target_type'],
}
@@ -1549,9 +1540,7 @@ write('config.mk', do_not_edit + config)
gyp_args = ['--no-parallel']
-if options.use_xcode:
- gyp_args += ['-f', 'xcode']
-elif options.use_ninja:
+if options.use_ninja:
gyp_args += ['-f', 'ninja']
elif flavor == 'win' and sys.platform != 'msys':
gyp_args += ['-f', 'msvs', '-G', 'msvs_version=auto']
diff --git a/deps/v8/third_party/eu-strip/README.v8 b/deps/v8/third_party/eu-strip/README.v8
deleted file mode 100644
index e84974d92b9cec..00000000000000
--- a/deps/v8/third_party/eu-strip/README.v8
+++ /dev/null
@@ -1,24 +0,0 @@
-Name: eu-strip
-URL: https://sourceware.org/elfutils/
-Version: 0.158
-Security Critical: no
-License: LGPL 3
-License File: NOT_SHIPPED
-
-Description:
-
-Patched eu-strip from elfutils.
-
-Build instructions (on Trusty; note that this will build the
-Ubuntu-patched version of elfutils):
-$ mkdir elfutils
-$ cd elfutils
-$ apt-get source elfutils
-$ cd elfutils-0.158
-[ Edit libelf/elf_end.c and remove the free() on line 164. ]
-$ ./configure
-$ make
-$ gcc -std=gnu99 -Wall -Wshadow -Wunused -Wextra -fgnu89-inline
- -Wformat=2 -Werror -g -O2 -Wl,-rpath-link,libelf:libdw -o eu-strip
- src/strip.o libebl/libebl.a libelf/libelf.a lib/libeu.a -ldl
-$ eu-strip ./eu-strip # Keep the binary small, please.
diff --git a/deps/v8/third_party/eu-strip/bin/eu-strip b/deps/v8/third_party/eu-strip/bin/eu-strip
deleted file mode 100755
index 994e2263b9185f..00000000000000
Binary files a/deps/v8/third_party/eu-strip/bin/eu-strip and /dev/null differ
diff --git a/doc/api/addons.md b/doc/api/addons.md
index 3c4c7b39bedfeb..5828574893a89d 100644
--- a/doc/api/addons.md
+++ b/doc/api/addons.md
@@ -217,7 +217,6 @@ Addon developers are recommended to use to keep compatibility between past and
future releases of V8 and Node.js. See the `nan` [examples][] for an
illustration of how it can be used.
-
## N-API
> Stability: 1 - Experimental
@@ -307,7 +306,6 @@ built using `node-gyp`:
$ node-gyp configure build
```
-
### Function arguments
Addons will typically expose objects and functions that can be accessed from
@@ -381,7 +379,6 @@ const addon = require('./build/Release/addon');
console.log('This should be eight:', addon.add(3, 5));
```
-
### Callbacks
It is common practice within Addons to pass JavaScript functions to a C++
@@ -488,7 +485,6 @@ console.log(obj1.msg, obj2.msg);
// Prints: 'hello world'
```
-
### Function factory
Another common scenario is creating JavaScript functions that wrap C++
@@ -546,7 +542,6 @@ console.log(fn());
// Prints: 'hello world'
```
-
### Wrapping C++ objects
It is also possible to wrap C++ objects/classes in a way that allows new
@@ -713,6 +708,13 @@ console.log(obj.plusOne());
// Prints: 13
```
+The garbage collector can execute forcefully using V8 command line flags
+` --gc_global ` and ` --gc_interval `, where ` --gc_global ` forces V8 to
+perform a full garbage collection and ` --gc_interval ` forces V8 to
+perform garbage collection after a given amount of allocations. Although,
+it is recommended to limit V8 command line flags for testing purposes
+only, since these are primarily debug flags.
+
### Factory of wrapped objects
Alternatively, it is possible to use a factory pattern to avoid explicitly
@@ -916,7 +918,6 @@ console.log(obj2.plusOne());
// Prints: 23
```
-
### Passing wrapped objects around
In addition to wrapping and returning C++ objects, it is possible to pass
diff --git a/doc/api/assert.md b/doc/api/assert.md
index ab4aa9c7cd2499..468293b208a90d 100644
--- a/doc/api/assert.md
+++ b/doc/api/assert.md
@@ -102,31 +102,25 @@ It can be accessed using:
const assert = require('assert').strict;
```
-Example error diff (the `expected`, `actual`, and `Lines skipped` will be on a
-single row):
+Example error diff:
```js
const assert = require('assert').strict;
assert.deepEqual([[[1, 2, 3]], 4, 5], [[[1, 2, '3']], 4, 5]);
-```
-
-```diff
-AssertionError [ERR_ASSERTION]: Input A expected to deepStrictEqual input B:
-+ expected
-- actual
-... Lines skipped
-
- [
- [
-...
- 2,
-- 3
-+ '3'
- ],
-...
- 5
- ]
+// AssertionError: Input A expected to strictly deep-equal input B:
+// + expected - actual ... Lines skipped
+//
+// [
+// [
+// ...
+// 2,
+// - 3
+// + '3'
+// ],
+// ...
+// 5
+// ]
```
To deactivate the colors, use the `NODE_DISABLE_COLORS` environment variable.
@@ -319,9 +313,14 @@ are recursively evaluated also by the following rules.
```js
const assert = require('assert').strict;
+// This fails because 1 !== '1'.
assert.deepStrictEqual({ a: 1 }, { a: '1' });
-// AssertionError: { a: 1 } deepStrictEqual { a: '1' }
-// because 1 !== '1' using SameValue comparison
+// AssertionError: Input A expected to strictly deep-equal input B:
+// + expected - actual
+// {
+// - a: 1
+// + a: '1'
+// }
// The following objects don't have own properties
const date = new Date();
@@ -329,33 +328,52 @@ const object = {};
const fakeDate = {};
Object.setPrototypeOf(fakeDate, Date.prototype);
+// Different [[Prototype]]:
assert.deepStrictEqual(object, fakeDate);
-// AssertionError: {} deepStrictEqual Date {}
-// Different [[Prototype]]
+// AssertionError: Input A expected to strictly deep-equal input B:
+// + expected - actual
+// - {}
+// + Date {}
+// Different type tags:
assert.deepStrictEqual(date, fakeDate);
-// AssertionError: 2017-03-11T14:25:31.849Z deepStrictEqual Date {}
-// Different type tags
+// AssertionError: Input A expected to strictly deep-equal input B:
+// + expected - actual
+// - 2018-04-26T00:49:08.604Z
+// + Date {}
assert.deepStrictEqual(NaN, NaN);
// OK, because of the SameValue comparison
+// Different unwrapped numbers:
assert.deepStrictEqual(new Number(1), new Number(2));
-// Fails because the wrapped number is unwrapped and compared as well.
+// AssertionError: Input A expected to strictly deep-equal input B:
+// + expected - actual
+// - [Number: 1]
+// + [Number: 2]
+
assert.deepStrictEqual(new String('foo'), Object('foo'));
// OK because the object and the string are identical when unwrapped.
assert.deepStrictEqual(-0, -0);
// OK
+
+// Different zeros using the SameValue Comparison:
assert.deepStrictEqual(0, -0);
-// AssertionError: 0 deepStrictEqual -0
+// AssertionError: Input A expected to strictly deep-equal input B:
+// + expected - actual
+// - 0
+// + -0
const symbol1 = Symbol();
const symbol2 = Symbol();
assert.deepStrictEqual({ [symbol1]: 1 }, { [symbol1]: 1 });
// OK, because it is the same symbol on both objects.
assert.deepStrictEqual({ [symbol1]: 1 }, { [symbol2]: 1 });
-// Fails because symbol1 !== symbol2!
+// AssertionError [ERR_ASSERTION]: Input objects not identical:
+// {
+// [Symbol()]: 1
+// }
const weakMap1 = new WeakMap();
const weakMap2 = new WeakMap([[{}, {}]]);
@@ -364,8 +382,16 @@ weakMap3.unequal = true;
assert.deepStrictEqual(weakMap1, weakMap2);
// OK, because it is impossible to compare the entries
+
+// Fails because weakMap3 has a property that weakMap1 does not contain:
assert.deepStrictEqual(weakMap1, weakMap3);
-// Fails because weakMap3 has a property that weakMap1 does not contain!
+// AssertionError: Input A expected to strictly deep-equal input B:
+// + expected - actual
+// WeakMap {
+// - [items unknown]
+// + [items unknown],
+// + unequal: true
+// }
```
If the values are not equal, an `AssertionError` is thrown with a `message`
@@ -639,7 +665,9 @@ changes:
* `value` {any}
Throws `value` if `value` is not `undefined` or `null`. This is useful when
-testing the `error` argument in callbacks.
+testing the `error` argument in callbacks. The stack trace contains all frames
+from the error passed to `ifError()` including the potential new frames for
+`ifError()` itself. See below for an example.
```js
const assert = require('assert').strict;
@@ -652,6 +680,19 @@ assert.ifError('error');
// AssertionError [ERR_ASSERTION]: ifError got unwanted exception: 'error'
assert.ifError(new Error());
// AssertionError [ERR_ASSERTION]: ifError got unwanted exception: Error
+
+// Create some random error frames.
+let err;
+(function errorFrame() {
+ err = new Error('test error');
+})();
+
+(function ifErrorFrame() {
+ assert.ifError(err);
+})();
+// AssertionError [ERR_ASSERTION]: ifError got unwanted exception: test error
+// at ifErrorFrame
+// at errorFrame
```
## assert.notDeepEqual(actual, expected[, message])
@@ -834,7 +875,7 @@ assert.notStrictEqual(1, 2);
// OK
assert.notStrictEqual(1, 1);
-// AssertionError: 1 notStrictEqual 1
+// AssertionError [ERR_ASSERTION]: Identical input passed to notStrictEqual: 1
assert.notStrictEqual(1, '1');
// OK
@@ -880,40 +921,34 @@ assert.ok(1);
// OK
assert.ok();
-// throws:
-// "AssertionError: No value argument passed to `assert.ok`.
+// AssertionError: No value argument passed to `assert.ok()`
assert.ok(false, 'it\'s false');
-// throws "AssertionError: it's false"
+// AssertionError: it's false
// In the repl:
assert.ok(typeof 123 === 'string');
-// throws:
-// "AssertionError: false == true
+// AssertionError: false == true
// In a file (e.g. test.js):
assert.ok(typeof 123 === 'string');
-// throws:
-// "AssertionError: The expression evaluated to a falsy value:
+// AssertionError: The expression evaluated to a falsy value:
//
// assert.ok(typeof 123 === 'string')
assert.ok(false);
-// throws:
-// "AssertionError: The expression evaluated to a falsy value:
+// AssertionError: The expression evaluated to a falsy value:
//
// assert.ok(false)
assert.ok(0);
-// throws:
-// "AssertionError: The expression evaluated to a falsy value:
+// AssertionError: The expression evaluated to a falsy value:
//
// assert.ok(0)
// Using `assert()` works the same:
assert(0);
-// throws:
-// "AssertionError: The expression evaluated to a falsy value:
+// AssertionError: The expression evaluated to a falsy value:
//
// assert(0)
```
@@ -995,13 +1030,19 @@ determined by the [SameValue Comparison][].
const assert = require('assert').strict;
assert.strictEqual(1, 2);
-// AssertionError: 1 strictEqual 2
+// AssertionError [ERR_ASSERTION]: Input A expected to strictly equal input B:
+// + expected - actual
+// - 1
+// + 2
assert.strictEqual(1, 1);
// OK
assert.strictEqual(1, '1');
-// AssertionError: 1 strictEqual '1'
+// AssertionError [ERR_ASSERTION]: Input A expected to strictly equal input B:
+// + expected - actual
+// - 1
+// + '1'
```
If the values are not strictly equal, an `AssertionError` is thrown with a
@@ -1035,6 +1076,34 @@ each property will be tested for including the non-enumerable `message` and
If specified, `message` will be the message provided by the `AssertionError` if
the block fails to throw.
+Custom error object / error instance:
+
+```js
+const err = new TypeError('Wrong value');
+err.code = 404;
+
+assert.throws(
+ () => {
+ throw err;
+ },
+ {
+ name: 'TypeError',
+ message: 'Wrong value'
+ // Note that only properties on the error object will be tested!
+ }
+);
+
+// Fails due to the different `message` and `name` properties:
+assert.throws(
+ () => {
+ const otherErr = new Error('Not found');
+ otherErr.code = 404;
+ throw otherErr;
+ },
+ err // This tests for `message`, `name` and `code`.
+);
+```
+
Validate instanceof using constructor:
```js
@@ -1076,39 +1145,12 @@ assert.throws(
);
```
-Custom error object / error instance:
-
-```js
-const err = new TypeError('Wrong value');
-err.code = 404;
-
-assert.throws(
- () => {
- throw err;
- },
- {
- name: 'TypeError',
- message: 'Wrong value'
- // Note that only properties on the error object will be tested!
- }
-);
-
-// Fails due to the different `message` and `name` properties:
-assert.throws(
- () => {
- const otherErr = new Error('Not found');
- otherErr.code = 404;
- throw otherErr;
- },
- err // This tests for `message`, `name` and `code`.
-);
-```
-
Note that `error` cannot be a string. If a string is provided as the second
argument, then `error` is assumed to be omitted and the string will be used for
-`message` instead. This can lead to easy-to-miss mistakes. Please read the
-example below carefully if using a string as the second argument gets
-considered:
+`message` instead. This can lead to easy-to-miss mistakes. Using the same
+message as the thrown error message is going to result in an
+`ERR_AMBIGUOUS_ARGUMENT` error. Please read the example below carefully if using
+a string as the second argument gets considered:
```js
@@ -1121,10 +1163,15 @@ function throwingSecond() {
function notThrowing() {}
// The second argument is a string and the input function threw an Error.
-// In that case both cases do not throw as neither is going to try to
-// match for the error message thrown by the input function!
+// The first case will not throw as it does not match for the error message
+// thrown by the input function!
assert.throws(throwingFirst, 'Second');
+// In the next example the message has no benefit over the message from the
+// error and since it is not clear if the user intended to actually match
+// against the error message, Node.js thrown an `ERR_AMBIGUOUS_ARGUMENT` error.
assert.throws(throwingSecond, 'Second');
+// Throws an error:
+// TypeError [ERR_AMBIGUOUS_ARGUMENT]
// The string is only used (as message) in case the function does not throw:
assert.throws(notThrowing, 'Second');
@@ -1134,7 +1181,7 @@ assert.throws(notThrowing, 'Second');
assert.throws(throwingSecond, /Second$/);
// Does not throw because the error messages match.
assert.throws(throwingFirst, /Second$/);
-// Throws a error:
+// Throws an error:
// Error: First
// at throwingFirst (repl:2:9)
```
diff --git a/doc/api/async_hooks.md b/doc/api/async_hooks.md
index 2628cc290a5660..601dad93e75a57 100644
--- a/doc/api/async_hooks.md
+++ b/doc/api/async_hooks.md
@@ -141,7 +141,6 @@ future. This is subject to change in the future if a comprehensive analysis is
performed to ensure an exception can follow the normal control flow without
unintentional side effects.
-
##### Printing in AsyncHooks callbacks
Because printing to the console is an asynchronous operation, `console.log()`
@@ -257,7 +256,6 @@ the new resource to initialize and that caused `init` to call. This is different
from `async_hooks.executionAsyncId()` that only shows *when* a resource was
created, while `triggerAsyncId` shows *why* a resource was created.
-
The following is a simple demonstration of `triggerAsyncId`:
```js
@@ -395,7 +393,6 @@ API the user's callback is placed in a `process.nextTick()`.
The graph only shows *when* a resource was created, not *why*, so to track
the *why* use `triggerAsyncId`.
-
##### before(asyncId)
* `asyncId` {number}
@@ -413,7 +410,6 @@ asynchronous resources like a TCP server will typically call the `before`
callback multiple times, while other operations like `fs.open()` will call
it only once.
-
##### after(asyncId)
* `asyncId` {number}
@@ -424,7 +420,6 @@ If an uncaught exception occurs during execution of the callback, then `after`
will run *after* the `'uncaughtException'` event is emitted or a `domain`'s
handler runs.
-
##### destroy(asyncId)
* `asyncId` {number}
diff --git a/doc/api/child_process.md b/doc/api/child_process.md
index 6e26cd8be8d064..62b2355ee38803 100644
--- a/doc/api/child_process.md
+++ b/doc/api/child_process.md
@@ -456,7 +456,6 @@ ls.on('close', (code) => {
});
```
-
Example: A very elaborate way to run `ps ax | grep ssh`
```js
@@ -494,7 +493,6 @@ grep.on('close', (code) => {
});
```
-
Example of checking for failed `spawn`:
```js
diff --git a/doc/api/cli.md b/doc/api/cli.md
index 164e370fff5e15..817f49142cb4c6 100644
--- a/doc/api/cli.md
+++ b/doc/api/cli.md
@@ -8,7 +8,6 @@ debugging, multiple ways to execute scripts, and other helpful runtime options.
To view this documentation as a manual page in a terminal, run `man node`.
-
## Synopsis
`node [options] [V8 options] [script.js | -e "script" | -] [--] [arguments]`
@@ -21,7 +20,6 @@ Execute without arguments to start the [REPL][].
_For more info about `node debug`, please see the [debugger][] documentation._
-
## Options
### `-`
@@ -33,7 +31,6 @@ Alias for stdin, analogous to the use of - in other command line utilities,
meaning that the script will be read from stdin, and the rest of the options
are passed to that script.
-
### `--`
-Enable loading native modules compiled with the ABI-stable Node.js API (N-API)
-(experimental).
-
+This option is a no-op. It is kept for compatibility.
### `--no-deprecation`
-
The `Console` class can be used to create a simple logger with configurable
@@ -354,7 +353,7 @@ added: v10.0.0
* `properties` {string[]} Alternate properties for constructing the table.
Try to construct a table with the columns of the properties of `tabularData`
-(or use `properties`) and rows of `tabularData` and logit. Falls back to just
+(or use `properties`) and rows of `tabularData` and log it. Falls back to just
logging the argument if it can’t be parsed as tabular.
```js
@@ -364,9 +363,7 @@ console.table(Symbol());
console.table(undefined);
// undefined
-```
-```js
console.table([{ a: 1, b: 'Y' }, { a: 'Z', b: 2 }]);
// ┌─────────┬─────┬─────┐
// │ (index) │ a │ b │
@@ -374,9 +371,7 @@ console.table([{ a: 1, b: 'Y' }, { a: 'Z', b: 2 }]);
// │ 0 │ 1 │ 'Y' │
// │ 1 │ 'Z' │ 2 │
// └─────────┴─────┴─────┘
-```
-```js
console.table([{ a: 1, b: 'Y' }, { a: 'Z', b: 2 }], ['a']);
// ┌─────────┬─────┐
// │ (index) │ a │
@@ -495,17 +490,6 @@ current JavaScript CPU profiling session if one has been started and prints
the report to the **Profiles** panel of the inspector. See
[`console.profile()`][] for an example.
-### console.table(array[, columns])
-
-* `array` {Array|Object}
-* `columns` {string[]} Display only selected properties of objects in the
- `array`.
-
-This method does not display anything unless used in the inspector. Prints to
-`stdout` the array `array` formatted as a table.
-
### console.timeStamp([label])
> Stability: 0 - Deprecated: Use [`crypto.createCipheriv()`][] instead.
@@ -1336,7 +1341,9 @@ Creates and returns a `Cipher` object that uses the given `algorithm` and
The `options` argument controls stream behavior and is optional except when a
cipher in CCM mode is used (e.g. `'aes-128-ccm'`). In that case, the
`authTagLength` option is required and specifies the length of the
-authentication tag in bytes, see [CCM mode][].
+authentication tag in bytes, see [CCM mode][]. In GCM mode, the `authTagLength`
+option is not required but can be used to set the length of the authentication
+tag that will be returned by `getAuthTag()` and defaults to 16 bytes.
The `algorithm` is dependent on OpenSSL, examples are `'aes192'`, etc. On
recent OpenSSL releases, `openssl list-cipher-algorithms` will display the
@@ -1366,6 +1373,10 @@ Adversaries][] for details.
+
+* `eventName` {string|symbol}
+* `listener` {Function}
+* Returns: {EventEmitter}
+
+Alias for [`emitter.removeListener()`][].
+
### emitter.on(eventName, listener)
-Emitted when the request has been aborted and the network socket has closed.
+Emitted when the request has been aborted.
### Event: 'close'
+
+* {boolean}
+
+The `message.aborted` property will be `true` if the request has
+been aborted.
+
### message.destroy([error])
+* `type` {integer} The frame type.
+* `code` {integer} The error code.
+* `id` {integer} The stream id (or `0` if the frame isn't associated with a
+ stream).
+
The `'frameError'` event is emitted when an error occurs while attempting to
send a frame on the session. If the frame that could not be sent is associated
with a specific `Http2Stream`, an attempt to emit `'frameError'` event on the
`Http2Stream` is made.
-When invoked, the handler function will receive three arguments:
-
-* An integer identifying the frame type.
-* An integer identifying the error code.
-* An integer identifying the stream (or 0 if the frame is not associated with
- a stream).
-
If the `'frameError'` event is associated with a stream, the stream will be
closed and destroyed immediately following the `'frameError'` event. If the
event is not associated with a stream, the `Http2Session` will be shut down
@@ -181,15 +179,14 @@ immediately following the `'frameError'` event.
added: v8.4.0
-->
-The `'goaway'` event is emitted when a `GOAWAY` frame is received. When invoked,
-the handler function will receive three arguments:
-
* `errorCode` {number} The HTTP/2 error code specified in the `GOAWAY` frame.
* `lastStreamID` {number} The ID of the last stream the remote peer successfully
processed (or `0` if no ID is specified).
* `opaqueData` {Buffer} If additional opaque data was included in the `GOAWAY`
frame, a `Buffer` instance will be passed containing that data.
+The `'goaway'` event is emitted when a `GOAWAY` frame is received.
+
The `Http2Session` instance will be shut down automatically when the `'goaway'`
event is emitted.
@@ -2419,6 +2416,16 @@ added: v8.4.0
Indicates that the underlying [`Http2Stream`][] was closed.
Just like `'end'`, this event occurs only once per response.
+#### request.aborted
+
+
+* {boolean}
+
+The `request.aborted` property will be `true` if the request has
+been aborted.
+
#### request.destroy([error])
* Returns: {integer}
The `os.uptime()` method returns the system uptime in number of seconds.
-On Windows the returned value includes fractions of a second. Use `Math.floor()`
-to get whole seconds.
-
## os.userInfo([options])
+* `code` {integer}
+
The `'exit'` event is emitted when the Node.js process is about to exit as a
result of either:
@@ -56,7 +58,7 @@ all `'exit'` listeners have finished running the Node.js process will terminate.
The listener callback function is invoked with the exit code specified either
by the [`process.exitCode`][] property, or the `exitCode` argument passed to the
-[`process.exit()`] method, as the only argument.
+[`process.exit()`] method.
```js
process.on('exit', (code) => {
@@ -82,16 +84,15 @@ process.on('exit', (code) => {
added: v0.5.10
-->
+* `message` {Object} a parsed JSON object or primitive value.
+* `sendHandle` {net.Server|net.Socket} a [`net.Server`][] or [`net.Socket`][]
+ object, or undefined.
+
If the Node.js process is spawned with an IPC channel (see the [Child Process][]
and [Cluster][] documentation), the `'message'` event is emitted whenever a
message sent by a parent process using [`childprocess.send()`][] is received by
the child process.
-The listener callback is invoked with the following arguments:
-* `message` {Object} a parsed JSON object or primitive value.
-* `sendHandle` {net.Server|net.Socket} a [`net.Server`][] or [`net.Socket`][]
- object, or undefined.
-
The message goes through serialization and parsing. The resulting message might
not be the same as what is originally sent.
@@ -100,13 +101,12 @@ not be the same as what is originally sent.
added: v1.4.1
-->
+* `promise` {Promise} The late handled promise.
+
The `'rejectionHandled'` event is emitted whenever a `Promise` has been rejected
and an error handler was attached to it (using [`promise.catch()`][], for
example) later than one turn of the Node.js event loop.
-The listener callback is invoked with a reference to the rejected `Promise` as
-the only argument.
-
The `Promise` object would have previously been emitted in an
`'unhandledRejection'` event, but during the course of processing gained a
rejection handler.
@@ -129,11 +129,11 @@ when the list of unhandled rejections shrinks.
```js
const unhandledRejections = new Map();
-process.on('unhandledRejection', (reason, p) => {
- unhandledRejections.set(p, reason);
+process.on('unhandledRejection', (reason, promise) => {
+ unhandledRejections.set(promise, reason);
});
-process.on('rejectionHandled', (p) => {
- unhandledRejections.delete(p);
+process.on('rejectionHandled', (promise) => {
+ unhandledRejections.delete(promise);
});
```
@@ -261,6 +261,12 @@ being emitted. Alternatively, the [`'rejectionHandled'`][] event may be used.
added: v6.0.0
-->
+* `warning` {Error} Key properties of the warning are:
+ * `name` {string} The name of the warning. **Default:** `'Warning'`.
+ * `message` {string} A system-provided description of the warning.
+ * `stack` {string} A stack trace to the location in the code where the warning
+ was issued.
+
The `'warning'` event is emitted whenever Node.js emits a process warning.
A process warning is similar to an error in that it describes exceptional
@@ -269,14 +275,6 @@ are not part of the normal Node.js and JavaScript error handling flow.
Node.js can emit warnings whenever it detects bad coding practices that could
lead to sub-optimal application performance, bugs, or security vulnerabilities.
-The listener function is called with a single `warning` argument whose value is
-an `Error` object. There are three key properties that describe the warning:
-
-* `name` {string} The name of the warning (currently `'Warning'` by default).
-* `message` {string} A system-provided description of the warning.
-* `stack` {string} A stack trace to the location in the code where the warning
- was issued.
-
```js
process.on('warning', (warning) => {
console.warn(warning.name); // Print the warning name
@@ -966,7 +964,6 @@ that started the Node.js process.
'/usr/local/bin/node'
```
-
## process.exit([code])
* `str` {string}
-
The `querystring.unescape()` method performs decoding of URL percent-encoded
characters on the given `str`.
diff --git a/doc/api/readline.md b/doc/api/readline.md
index c1e50ef7eee350..d3afe5d9bf5ba2 100644
--- a/doc/api/readline.md
+++ b/doc/api/readline.md
@@ -320,7 +320,6 @@ added: v0.7.7
The `readline.clearLine()` method clears current line of given [TTY][] stream
in a specified direction identified by `dir`.
-
## readline.clearScreenDown(stream)
-* Returns: {Immediate}
+* Returns: {Immediate} a reference to `immediate`
When called, requests that the Node.js event loop *not* exit so long as the
`Immediate` is active. Calling `immediate.ref()` multiple times will have no
@@ -37,22 +37,18 @@ effect.
By default, all `Immediate` objects are "ref'ed", making it normally unnecessary
to call `immediate.ref()` unless `immediate.unref()` had been called previously.
-Returns a reference to the `Immediate`.
-
### immediate.unref()
-* Returns: {Immediate}
+* Returns: {Immediate} a reference to `immediate`
When called, the active `Immediate` object will not require the Node.js event
loop to remain active. If there is no other activity keeping the event loop
running, the process may exit before the `Immediate` object's callback is
invoked. Calling `immediate.unref()` multiple times will have no effect.
-Returns a reference to the `Immediate`.
-
## Class: Timeout
This object is created internally and is returned from [`setTimeout()`][] and
@@ -70,7 +66,7 @@ control this default behavior.
added: v0.9.1
-->
-* Returns: {Timeout}
+* Returns: {Timeout} a reference to `timeout`
When called, requests that the Node.js event loop *not* exit so long as the
`Timeout` is active. Calling `timeout.ref()` multiple times will have no effect.
@@ -78,14 +74,12 @@ When called, requests that the Node.js event loop *not* exit so long as the
By default, all `Timeout` objects are "ref'ed", making it normally unnecessary
to call `timeout.ref()` unless `timeout.unref()` had been called previously.
-Returns a reference to the `Timeout`.
-
### timeout.unref()
-* Returns: {Timeout}
+* Returns: {Timeout} a reference to `timeout`
When called, the active `Timeout` object will not require the Node.js event loop
to remain active. If there is no other activity keeping the event loop running,
@@ -96,8 +90,6 @@ Calling `timeout.unref()` creates an internal timer that will wake the Node.js
event loop. Creating too many of these can adversely impact performance
of the Node.js application.
-Returns a reference to the `Timeout`.
-
## Scheduling Timers
A timer in Node.js is an internal construct that calls a given function after
@@ -113,9 +105,10 @@ added: v0.9.1
* `callback` {Function} The function to call at the end of this turn of
[the Node.js Event Loop]
* `...args` {any} Optional arguments to pass when the `callback` is called.
+* Returns: {Immediate} for use with [`clearImmediate()`][]
Schedules the "immediate" execution of the `callback` after I/O events'
-callbacks. Returns an `Immediate` for use with [`clearImmediate()`][].
+callbacks.
When multiple calls to `setImmediate()` are made, the `callback` functions are
queued for execution in the order in which they are created. The entire callback
@@ -155,10 +148,9 @@ added: v0.0.1
* `delay` {number} The number of milliseconds to wait before calling the
`callback`.
* `...args` {any} Optional arguments to pass when the `callback` is called.
-* Returns: {Timeout}
+* Returns: {Timeout} for use with [`clearInterval()`][]
Schedules repeated execution of `callback` every `delay` milliseconds.
-Returns a `Timeout` for use with [`clearInterval()`][].
When `delay` is larger than `2147483647` or less than `1`, the `delay` will be
set to `1`.
@@ -174,10 +166,9 @@ added: v0.0.1
* `delay` {number} The number of milliseconds to wait before calling the
`callback`.
* `...args` {any} Optional arguments to pass when the `callback` is called.
-* Returns: {Timeout}
+* Returns: {Timeout} for use with [`clearTimeout()`][]
Schedules execution of a one-time `callback` after `delay` milliseconds.
-Returns a `Timeout` for use with [`clearTimeout()`][].
The `callback` will likely not be invoked in precisely `delay` milliseconds.
Node.js makes no guarantees about the exact timing of when callbacks will fire,
@@ -239,7 +230,6 @@ added: v0.0.1
Cancels a `Timeout` object created by [`setTimeout()`][].
-
[`TypeError`]: errors.html#errors_class_typeerror
[`clearImmediate()`]: timers.html#timers_clearimmediate_immediate
[`clearInterval()`]: timers.html#timers_clearinterval_timeout
diff --git a/doc/api/tls.md b/doc/api/tls.md
index 0f9e46f2474d57..beb2ec679e9f11 100644
--- a/doc/api/tls.md
+++ b/doc/api/tls.md
@@ -428,7 +428,6 @@ more information on how it is used.
Changes to the ticket keys are effective only for future server connections.
Existing or currently pending server connections will use the previous keys.
-
## Class: tls.TLSSocket
-The `tty.WriteStream` class is a subclass of `net.Socket` that represents the
-writable side of a TTY. In normal circumstances, [`process.stdout`][] and
+The `tty.WriteStream` class is a subclass of [`net.Socket`][] that represents
+the writable side of a TTY. In normal circumstances, [`process.stdout`][] and
[`process.stderr`][] will be the only `tty.WriteStream` instances created for a
Node.js process and there should be no reason to create additional instances.
diff --git a/doc/api/url.md b/doc/api/url.md
index 35a72da8ee681f..f8a22fefac3ddf 100644
--- a/doc/api/url.md
+++ b/doc/api/url.md
@@ -132,8 +132,6 @@ and a `base` is provided, it is advised to validate that the `origin` of
the `URL` object is what is expected.
```js
-const { URL } = require('url');
-
let myURL = new URL('http://anotherExample.org/', 'https://example.org/');
// http://anotherexample.org/
@@ -1063,7 +1061,6 @@ The formatting process operates as follows:
string, an [`Error`][] is thrown.
* `result` is returned.
-
### url.parse(urlString[, parseQueryString[, slashesDenoteHost]])
```
Please try to do your best at filling out the details, but feel free to skip
diff --git a/doc/node.1 b/doc/node.1
index 24d8260b8765ac..c3572acee7bf6b 100644
--- a/doc/node.1
+++ b/doc/node.1
@@ -122,8 +122,7 @@ V8 Inspector integration allows attaching Chrome DevTools and IDEs to Node.js in
It uses the Chrome DevTools Protocol.
.
.It Fl -napi-modules
-Enable loading native modules compiled with the ABI-stable Node.js API (N-API)
-(experimental).
+This option is a no-op. It is kept for compatibility.
.
.It Fl -no-deprecation
Silence deprecation warnings.
diff --git a/doc/onboarding-extras.md b/doc/onboarding-extras.md
index 080918049f66c0..ffc316d7a670b6 100644
--- a/doc/onboarding-extras.md
+++ b/doc/onboarding-extras.md
@@ -91,7 +91,7 @@ need to be attached anymore, as only important bugfixes will be included.
to update from nodejs/node:
* `git checkout master`
-* `git remote update -p` OR `git fetch --all` (I prefer the former)
+* `git remote update -p` OR `git fetch --all`
* `git merge --ff-only upstream/master` (or `REMOTENAME/BRANCH`)
## Best practices
diff --git a/lib/_http_client.js b/lib/_http_client.js
index 22ae85c92782ec..1985c617bec537 100644
--- a/lib/_http_client.js
+++ b/lib/_http_client.js
@@ -37,12 +37,11 @@ const { OutgoingMessage } = require('_http_outgoing');
const Agent = require('_http_agent');
const { Buffer } = require('buffer');
const { defaultTriggerAsyncIdScope } = require('internal/async_hooks');
-const { urlToOptions, searchParamsSymbol } = require('internal/url');
+const { URL, urlToOptions, searchParamsSymbol } = require('internal/url');
const { outHeadersKey, ondrain } = require('internal/http');
const {
ERR_HTTP_HEADERS_SENT,
ERR_INVALID_ARG_TYPE,
- ERR_INVALID_DOMAIN_NAME,
ERR_INVALID_HTTP_TOKEN,
ERR_INVALID_PROTOCOL,
ERR_UNESCAPED_CHARACTERS
@@ -60,13 +59,26 @@ function validateHost(host, name) {
return host;
}
+let urlWarningEmitted = false;
function ClientRequest(options, cb) {
OutgoingMessage.call(this);
if (typeof options === 'string') {
- options = url.parse(options);
- if (!options.hostname) {
- throw new ERR_INVALID_DOMAIN_NAME();
+ const urlStr = options;
+ try {
+ options = urlToOptions(new URL(urlStr));
+ } catch (err) {
+ options = url.parse(urlStr);
+ if (!options.hostname) {
+ throw err;
+ }
+ if (!urlWarningEmitted && !process.noDeprecation) {
+ urlWarningEmitted = true;
+ process.emitWarning(
+ `The provided URL ${urlStr} is not a valid URL, and is supported ` +
+ 'in the http module solely for compatibility.',
+ 'DeprecationWarning', 'DEP0109');
+ }
}
} else if (options && options[searchParamsSymbol] &&
options[searchParamsSymbol][searchParamsSymbol]) {
@@ -279,6 +291,7 @@ ClientRequest.prototype.abort = function abort() {
if (!this.aborted) {
process.nextTick(emitAbortNT.bind(this));
}
+
// Mark as aborting so we can avoid sending queued request data
// This is used as a truthy flag elsewhere. The use of Date.now is for
// debugging purposes only.
@@ -330,7 +343,10 @@ function socketCloseListener() {
var parser = socket.parser;
if (req.res && req.res.readable) {
// Socket closed before we emitted 'end' below.
- if (!req.res.complete) req.res.emit('aborted');
+ if (!req.res.complete) {
+ req.res.aborted = true;
+ req.res.emit('aborted');
+ }
var res = req.res;
res.on('end', function() {
res.emit('close');
diff --git a/lib/_http_incoming.js b/lib/_http_incoming.js
index 55c196399c5e6b..23ac4d54be1ec5 100644
--- a/lib/_http_incoming.js
+++ b/lib/_http_incoming.js
@@ -54,6 +54,8 @@ function IncomingMessage(socket) {
this.readable = true;
+ this.aborted = false;
+
this.upgrade = null;
// request (server) only
diff --git a/lib/_http_outgoing.js b/lib/_http_outgoing.js
index 770e555d1ead8a..f075e0ecad7a2b 100644
--- a/lib/_http_outgoing.js
+++ b/lib/_http_outgoing.js
@@ -52,6 +52,8 @@ const { utcDate } = internalHttp;
const kIsCorked = Symbol('isCorked');
+const hasOwnProperty = Function.call.bind(Object.prototype.hasOwnProperty);
+
var RE_CONN_CLOSE = /(?:^|\W)close(?:$|\W)/i;
var RE_TE_CHUNKED = common.chunkExpression;
@@ -116,7 +118,7 @@ Object.defineProperty(OutgoingMessage.prototype, '_headers', {
if (val == null) {
this[outHeadersKey] = null;
} else if (typeof val === 'object') {
- const headers = this[outHeadersKey] = {};
+ const headers = this[outHeadersKey] = Object.create(null);
const keys = Object.keys(val);
for (var i = 0; i < keys.length; ++i) {
const name = keys[i];
@@ -129,7 +131,7 @@ Object.defineProperty(OutgoingMessage.prototype, '_headers', {
Object.defineProperty(OutgoingMessage.prototype, '_headerNames', {
get: function() {
const headers = this[outHeadersKey];
- if (headers) {
+ if (headers !== null) {
const out = Object.create(null);
const keys = Object.keys(headers);
for (var i = 0; i < keys.length; ++i) {
@@ -138,9 +140,8 @@ Object.defineProperty(OutgoingMessage.prototype, '_headerNames', {
out[key] = val;
}
return out;
- } else {
- return headers;
}
+ return null;
},
set: function(val) {
if (typeof val === 'object' && val !== null) {
@@ -164,14 +165,14 @@ OutgoingMessage.prototype._renderHeaders = function _renderHeaders() {
}
var headersMap = this[outHeadersKey];
- if (!headersMap) return {};
-
- var headers = {};
- var keys = Object.keys(headersMap);
+ const headers = {};
- for (var i = 0, l = keys.length; i < l; i++) {
- var key = keys[i];
- headers[headersMap[key][0]] = headersMap[key][1];
+ if (headersMap !== null) {
+ const keys = Object.keys(headersMap);
+ for (var i = 0, l = keys.length; i < l; i++) {
+ const key = keys[i];
+ headers[headersMap[key][0]] = headersMap[key][1];
+ }
}
return headers;
};
@@ -285,72 +286,40 @@ OutgoingMessage.prototype._storeHeader = _storeHeader;
function _storeHeader(firstLine, headers) {
// firstLine in the case of request is: 'GET /index.html HTTP/1.1\r\n'
// in the case of response it is: 'HTTP/1.1 200 OK\r\n'
- var state = {
+ const state = {
connection: false,
contLen: false,
te: false,
date: false,
expect: false,
trailer: false,
- upgrade: false,
header: firstLine
};
- var field;
var key;
- var value;
- var i;
- var j;
if (headers === this[outHeadersKey]) {
for (key in headers) {
- var entry = headers[key];
- field = entry[0];
- value = entry[1];
-
- if (value instanceof Array) {
- if (value.length < 2 || !isCookieField(field)) {
- for (j = 0; j < value.length; j++)
- storeHeader(this, state, field, value[j], false);
- continue;
- }
- value = value.join('; ');
- }
- storeHeader(this, state, field, value, false);
+ const entry = headers[key];
+ processHeader(this, state, entry[0], entry[1], false);
}
- } else if (headers instanceof Array) {
- for (i = 0; i < headers.length; i++) {
- field = headers[i][0];
- value = headers[i][1];
-
- if (value instanceof Array) {
- for (j = 0; j < value.length; j++) {
- storeHeader(this, state, field, value[j], true);
- }
- } else {
- storeHeader(this, state, field, value, true);
- }
+ } else if (Array.isArray(headers)) {
+ for (var i = 0; i < headers.length; i++) {
+ const entry = headers[i];
+ processHeader(this, state, entry[0], entry[1], true);
}
} else if (headers) {
- var keys = Object.keys(headers);
- for (i = 0; i < keys.length; i++) {
- field = keys[i];
- value = headers[field];
-
- if (value instanceof Array) {
- if (value.length < 2 || !isCookieField(field)) {
- for (j = 0; j < value.length; j++)
- storeHeader(this, state, field, value[j], true);
- continue;
- }
- value = value.join('; ');
+ for (key in headers) {
+ if (hasOwnProperty(headers, key)) {
+ processHeader(this, state, key, headers[key], true);
}
- storeHeader(this, state, field, value, true);
}
}
+ let { header } = state;
+
// Date header
if (this.sendDate && !state.date) {
- state.header += 'Date: ' + utcDate() + CRLF;
+ header += 'Date: ' + utcDate() + CRLF;
}
// Force the connection to close when the response is a 204 No Content or
@@ -364,9 +333,9 @@ function _storeHeader(firstLine, headers) {
// It was pointed out that this might confuse reverse proxies to the point
// of creating security liabilities, so suppress the zero chunk and force
// the connection to close.
- var statusCode = this.statusCode;
- if ((statusCode === 204 || statusCode === 304) && this.chunkedEncoding) {
- debug(statusCode + ' response should not use chunked encoding,' +
+ if (this.chunkedEncoding && (this.statusCode === 204 ||
+ this.statusCode === 304)) {
+ debug(this.statusCode + ' response should not use chunked encoding,' +
' closing connection.');
this.chunkedEncoding = false;
this.shouldKeepAlive = false;
@@ -377,13 +346,13 @@ function _storeHeader(firstLine, headers) {
this._last = true;
this.shouldKeepAlive = false;
} else if (!state.connection) {
- var shouldSendKeepAlive = this.shouldKeepAlive &&
+ const shouldSendKeepAlive = this.shouldKeepAlive &&
(state.contLen || this.useChunkedEncodingByDefault || this.agent);
if (shouldSendKeepAlive) {
- state.header += 'Connection: keep-alive\r\n';
+ header += 'Connection: keep-alive\r\n';
} else {
this._last = true;
- state.header += 'Connection: close\r\n';
+ header += 'Connection: close\r\n';
}
}
@@ -396,9 +365,9 @@ function _storeHeader(firstLine, headers) {
} else if (!state.trailer &&
!this._removedContLen &&
typeof this._contentLength === 'number') {
- state.header += 'Content-Length: ' + this._contentLength + CRLF;
+ header += 'Content-Length: ' + this._contentLength + CRLF;
} else if (!this._removedTE) {
- state.header += 'Transfer-Encoding: chunked\r\n';
+ header += 'Transfer-Encoding: chunked\r\n';
this.chunkedEncoding = true;
} else {
// We should only be able to get here if both Content-Length and
@@ -416,7 +385,7 @@ function _storeHeader(firstLine, headers) {
throw new ERR_HTTP_TRAILER_INVALID();
}
- this._header = state.header + CRLF;
+ this._header = header + CRLF;
this._headerSent = false;
// wait until the first body chunk, or close(), is sent to flush,
@@ -424,10 +393,23 @@ function _storeHeader(firstLine, headers) {
if (state.expect) this._send('');
}
-function storeHeader(self, state, key, value, validate) {
- if (validate) {
- validateHeader(key, value);
+function processHeader(self, state, key, value, validate) {
+ if (validate)
+ validateHeaderName(key);
+ if (Array.isArray(value)) {
+ if (value.length < 2 || !isCookieField(key)) {
+ for (var i = 0; i < value.length; i++)
+ storeHeader(self, state, key, value[i], validate);
+ return;
+ }
+ value = value.join('; ');
}
+ storeHeader(self, state, key, value, validate);
+}
+
+function storeHeader(self, state, key, value, validate) {
+ if (validate)
+ validateHeaderValue(key, value);
state.header += key + ': ' + escapeHeaderValue(value) + CRLF;
matchHeader(self, state, key, value);
}
@@ -439,6 +421,7 @@ function matchHeader(self, state, field, value) {
switch (field) {
case 'connection':
state.connection = true;
+ self._removedConnection = false;
if (RE_CONN_CLOSE.test(value))
self._last = true;
else
@@ -446,32 +429,39 @@ function matchHeader(self, state, field, value) {
break;
case 'transfer-encoding':
state.te = true;
+ self._removedTE = false;
if (RE_TE_CHUNKED.test(value)) self.chunkedEncoding = true;
break;
case 'content-length':
state.contLen = true;
+ self._removedContLen = false;
break;
case 'date':
case 'expect':
case 'trailer':
- case 'upgrade':
state[field] = true;
break;
}
}
-function validateHeader(name, value) {
- let err;
+function validateHeaderName(name) {
if (typeof name !== 'string' || !name || !checkIsHttpToken(name)) {
- err = new ERR_INVALID_HTTP_TOKEN('Header name', name);
- } else if (value === undefined) {
+ const err = new ERR_INVALID_HTTP_TOKEN('Header name', name);
+ Error.captureStackTrace(err, validateHeaderName);
+ throw err;
+ }
+}
+
+function validateHeaderValue(name, value) {
+ let err;
+ if (value === undefined) {
err = new ERR_HTTP_INVALID_HEADER_VALUE(value, name);
} else if (checkInvalidHeaderChar(value)) {
debug('Header "%s" contains invalid characters', name);
err = new ERR_INVALID_CHAR('header content', name);
}
if (err !== undefined) {
- Error.captureStackTrace(err, validateHeader);
+ Error.captureStackTrace(err, validateHeaderValue);
throw err;
}
}
@@ -480,25 +470,14 @@ OutgoingMessage.prototype.setHeader = function setHeader(name, value) {
if (this._header) {
throw new ERR_HTTP_HEADERS_SENT('set');
}
- validateHeader(name, value);
+ validateHeaderName(name);
+ validateHeaderValue(name, value);
- if (!this[outHeadersKey])
- this[outHeadersKey] = {};
+ let headers = this[outHeadersKey];
+ if (headers === null)
+ this[outHeadersKey] = headers = Object.create(null);
- const key = name.toLowerCase();
- this[outHeadersKey][key] = [name, value];
-
- switch (key) {
- case 'connection':
- this._removedConnection = false;
- break;
- case 'content-length':
- this._removedContLen = false;
- break;
- case 'transfer-encoding':
- this._removedTE = false;
- break;
- }
+ headers[name.toLowerCase()] = [name, value];
};
@@ -507,18 +486,18 @@ OutgoingMessage.prototype.getHeader = function getHeader(name) {
throw new ERR_INVALID_ARG_TYPE('name', 'string', name);
}
- if (!this[outHeadersKey]) return;
-
- var entry = this[outHeadersKey][name.toLowerCase()];
- if (!entry)
+ const headers = this[outHeadersKey];
+ if (headers === null)
return;
- return entry[1];
+
+ const entry = headers[name.toLowerCase()];
+ return entry && entry[1];
};
// Returns an array of the names of the current outgoing headers.
OutgoingMessage.prototype.getHeaderNames = function getHeaderNames() {
- return (this[outHeadersKey] ? Object.keys(this[outHeadersKey]) : []);
+ return this[outHeadersKey] !== null ? Object.keys(this[outHeadersKey]) : [];
};
@@ -543,7 +522,8 @@ OutgoingMessage.prototype.hasHeader = function hasHeader(name) {
throw new ERR_INVALID_ARG_TYPE('name', 'string', name);
}
- return !!(this[outHeadersKey] && this[outHeadersKey][name.toLowerCase()]);
+ return this[outHeadersKey] !== null &&
+ !!this[outHeadersKey][name.toLowerCase()];
};
@@ -573,7 +553,7 @@ OutgoingMessage.prototype.removeHeader = function removeHeader(name) {
break;
}
- if (this[outHeadersKey]) {
+ if (this[outHeadersKey] !== null) {
delete this[outHeadersKey][key];
}
};
diff --git a/lib/_http_server.js b/lib/_http_server.js
index bf228de643422e..9c8b5cb8fbfe8e 100644
--- a/lib/_http_server.js
+++ b/lib/_http_server.js
@@ -440,6 +440,7 @@ function socketOnClose(socket, state) {
function abortIncoming(incoming) {
while (incoming.length) {
var req = incoming.shift();
+ req.aborted = true;
req.emit('aborted');
req.emit('close');
}
diff --git a/lib/_stream_duplex.js b/lib/_stream_duplex.js
index b123cdcb4d776f..7059757dbd44b1 100644
--- a/lib/_stream_duplex.js
+++ b/lib/_stream_duplex.js
@@ -50,17 +50,19 @@ function Duplex(options) {
Readable.call(this, options);
Writable.call(this, options);
+ this.allowHalfOpen = true;
- if (options && options.readable === false)
- this.readable = false;
+ if (options) {
+ if (options.readable === false)
+ this.readable = false;
- if (options && options.writable === false)
- this.writable = false;
+ if (options.writable === false)
+ this.writable = false;
- this.allowHalfOpen = true;
- if (options && options.allowHalfOpen === false) {
- this.allowHalfOpen = false;
- this.once('end', onend);
+ if (options.allowHalfOpen === false) {
+ this.allowHalfOpen = false;
+ this.once('end', onend);
+ }
}
}
diff --git a/lib/_tls_common.js b/lib/_tls_common.js
index a9fe0d8f06aa0d..d8f6afed0bd8fb 100644
--- a/lib/_tls_common.js
+++ b/lib/_tls_common.js
@@ -56,11 +56,10 @@ function SecureContext(secureProtocol, secureOptions, context) {
if (secureOptions) this.context.setOptions(secureOptions);
}
-function validateKeyCert(value, type) {
+function validateKeyCert(name, value) {
if (typeof value !== 'string' && !isArrayBufferView(value)) {
throw new ERR_INVALID_ARG_TYPE(
- // TODO(BridgeAR): Change this to `options.${type}`
- type,
+ `options.${name}`,
['string', 'Buffer', 'TypedArray', 'DataView'],
value
);
@@ -100,11 +99,11 @@ exports.createSecureContext = function createSecureContext(options, context) {
if (Array.isArray(ca)) {
for (i = 0; i < ca.length; ++i) {
val = ca[i];
- validateKeyCert(val, 'ca');
+ validateKeyCert('ca', val);
c.context.addCACert(val);
}
} else {
- validateKeyCert(ca, 'ca');
+ validateKeyCert('ca', ca);
c.context.addCACert(ca);
}
} else {
@@ -116,11 +115,11 @@ exports.createSecureContext = function createSecureContext(options, context) {
if (Array.isArray(cert)) {
for (i = 0; i < cert.length; ++i) {
val = cert[i];
- validateKeyCert(val, 'cert');
+ validateKeyCert('cert', val);
c.context.setCert(val);
}
} else {
- validateKeyCert(cert, 'cert');
+ validateKeyCert('cert', cert);
c.context.setCert(cert);
}
}
@@ -137,11 +136,11 @@ exports.createSecureContext = function createSecureContext(options, context) {
val = key[i];
// eslint-disable-next-line eqeqeq
const pem = (val != undefined && val.pem !== undefined ? val.pem : val);
- validateKeyCert(pem, 'key');
+ validateKeyCert('key', pem);
c.context.setKey(pem, val.passphrase || passphrase);
}
} else {
- validateKeyCert(key, 'key');
+ validateKeyCert('key', key);
c.context.setKey(key, passphrase);
}
}
diff --git a/lib/console.js b/lib/console.js
index 39bcf701bf83eb..a0158ec6643782 100644
--- a/lib/console.js
+++ b/lib/console.js
@@ -363,9 +363,7 @@ Console.prototype.table = function(tabularData, properties) {
tabularData = previewSetIterator(tabularData);
const setlike = setIter || isSet(tabularData);
- if (setlike ||
- (properties === undefined &&
- (isArray(tabularData) || isTypedArray(tabularData)))) {
+ if (setlike) {
const values = [];
let length = 0;
for (const v of tabularData) {
diff --git a/lib/fs.js b/lib/fs.js
index d71e31565fbad6..50913866f9fdfd 100644
--- a/lib/fs.js
+++ b/lib/fs.js
@@ -51,7 +51,6 @@ const internalUtil = require('internal/util');
const {
copyObject,
getOptions,
- isUint32,
modeNum,
nullCheck,
preprocessSymlinkDestination,
@@ -61,16 +60,19 @@ const {
stringToSymlinkType,
toUnixTimestamp,
validateBuffer,
- validateLen,
validateOffsetLengthRead,
validateOffsetLengthWrite,
- validatePath,
- validateUint32
+ validatePath
} = internalFS;
const {
CHAR_FORWARD_SLASH,
CHAR_BACKWARD_SLASH,
} = require('internal/constants');
+const {
+ isUint32,
+ validateInt32,
+ validateUint32
+} = require('internal/validators');
Object.defineProperty(exports, 'constants', {
configurable: false,
@@ -151,9 +153,7 @@ function makeStatsCallback(cb) {
};
}
-function isFd(path) {
- return (path >>> 0) === path;
-}
+const isFd = isUint32;
fs.Stats = Stats;
@@ -755,8 +755,8 @@ fs.ftruncate = function(fd, len = 0, callback) {
// TODO(BridgeAR): This does not seem right.
// There does not seem to be any validation before and if there is any, it
// should work similar to validateUint32 or not have a upper cap at all.
- // This applies to all usage of `validateLen`.
- validateLen(len);
+ // This applies to all usage of `validateInt32(len, 'len')`.
+ validateInt32(len, 'len');
len = Math.max(0, len);
const req = new FSReqWrap();
req.oncomplete = makeCallback(callback);
@@ -765,7 +765,7 @@ fs.ftruncate = function(fd, len = 0, callback) {
fs.ftruncateSync = function(fd, len = 0) {
validateUint32(fd, 'fd');
- validateLen(len);
+ validateInt32(len, 'len');
len = Math.max(0, len);
const ctx = {};
binding.ftruncate(fd, len, undefined, ctx);
diff --git a/lib/fs/promises.js b/lib/fs/promises.js
index ba6c2b7aa64855..6ccff6933bf106 100644
--- a/lib/fs/promises.js
+++ b/lib/fs/promises.js
@@ -24,7 +24,6 @@ const {
copyObject,
getOptions,
getStatsFromBinding,
- isUint32,
modeNum,
nullCheck,
preprocessSymlinkDestination,
@@ -32,12 +31,15 @@ const {
stringToSymlinkType,
toUnixTimestamp,
validateBuffer,
- validateLen,
validateOffsetLengthRead,
validateOffsetLengthWrite,
- validatePath,
- validateUint32
+ validatePath
} = require('internal/fs');
+const {
+ isUint32,
+ validateInt32,
+ validateUint32
+} = require('internal/validators');
const pathModule = require('path');
const kHandle = Symbol('handle');
@@ -275,7 +277,7 @@ async function truncate(path, len = 0) {
async function ftruncate(handle, len = 0) {
validateFileHandle(handle);
- validateLen(len);
+ validateInt32(len, 'len');
len = Math.max(0, len);
return binding.ftruncate(handle.fd, len, kUsePromises);
}
diff --git a/lib/https.js b/lib/https.js
index 4cca2fb9eeea88..ead7a2bb927dad 100644
--- a/lib/https.js
+++ b/lib/https.js
@@ -34,8 +34,7 @@ const {
const { ClientRequest } = require('_http_client');
const { inherits } = util;
const debug = util.debuglog('https');
-const { urlToOptions, searchParamsSymbol } = require('internal/url');
-const { ERR_INVALID_DOMAIN_NAME } = require('internal/errors').codes;
+const { URL, urlToOptions, searchParamsSymbol } = require('internal/url');
const { IncomingMessage, ServerResponse } = require('http');
const { kIncomingMessage } = require('_http_common');
const { kServerResponse } = require('_http_server');
@@ -254,11 +253,24 @@ Agent.prototype._evictSession = function _evictSession(key) {
const globalAgent = new Agent();
+let urlWarningEmitted = false;
function request(options, cb) {
if (typeof options === 'string') {
- options = url.parse(options);
- if (!options.hostname) {
- throw new ERR_INVALID_DOMAIN_NAME();
+ const urlStr = options;
+ try {
+ options = urlToOptions(new URL(urlStr));
+ } catch (err) {
+ options = url.parse(urlStr);
+ if (!options.hostname) {
+ throw err;
+ }
+ if (!urlWarningEmitted && !process.noDeprecation) {
+ urlWarningEmitted = true;
+ process.emitWarning(
+ `The provided URL ${urlStr} is not a valid URL, and is supported ` +
+ 'in the https module solely for compatibility.',
+ 'DeprecationWarning', 'DEP0109');
+ }
}
} else if (options && options[searchParamsSymbol] &&
options[searchParamsSymbol][searchParamsSymbol]) {
diff --git a/lib/internal/bootstrap/node.js b/lib/internal/bootstrap/node.js
index f9bed03d269fb2..5993cc71f7d404 100644
--- a/lib/internal/bootstrap/node.js
+++ b/lib/internal/bootstrap/node.js
@@ -41,6 +41,7 @@
NativeModule.require('internal/process/warning').setup();
NativeModule.require('internal/process/next_tick').setup();
NativeModule.require('internal/process/stdio').setup();
+ NativeModule.require('internal/process/methods').setup();
const perf = process.binding('performance');
const {
diff --git a/lib/internal/buffer.js b/lib/internal/buffer.js
index 085a82265a95de..54e13ff30bcb4e 100644
--- a/lib/internal/buffer.js
+++ b/lib/internal/buffer.js
@@ -59,6 +59,8 @@ function boundsError(value, length, type) {
// Read integers.
function readUIntLE(offset, byteLength) {
+ if (offset === undefined)
+ throw new ERR_INVALID_ARG_TYPE('offset', 'number', offset);
if (byteLength === 6)
return readUInt48LE(this, offset);
if (byteLength === 5)
@@ -69,7 +71,7 @@ function readUIntLE(offset, byteLength) {
return this.readUInt32LE(offset);
if (byteLength === 2)
return this.readUInt16LE(offset);
- if (byteLength === 1 || byteLength === undefined)
+ if (byteLength === 1)
return this.readUInt8(offset);
boundsError(byteLength, 6, 'byteLength');
@@ -146,6 +148,8 @@ function readUInt8(offset = 0) {
}
function readUIntBE(offset, byteLength) {
+ if (offset === undefined)
+ throw new ERR_INVALID_ARG_TYPE('offset', 'number', offset);
if (byteLength === 6)
return readUInt48BE(this, offset);
if (byteLength === 5)
@@ -156,7 +160,7 @@ function readUIntBE(offset, byteLength) {
return this.readUInt32BE(offset);
if (byteLength === 2)
return this.readUInt16BE(offset);
- if (byteLength === 1 || byteLength === undefined)
+ if (byteLength === 1)
return this.readUInt8(offset);
boundsError(byteLength, 6, 'byteLength');
@@ -224,6 +228,8 @@ function readUInt16BE(offset = 0) {
}
function readIntLE(offset, byteLength) {
+ if (offset === undefined)
+ throw new ERR_INVALID_ARG_TYPE('offset', 'number', offset);
if (byteLength === 6)
return readInt48LE(this, offset);
if (byteLength === 5)
@@ -234,7 +240,7 @@ function readIntLE(offset, byteLength) {
return this.readInt32LE(offset);
if (byteLength === 2)
return this.readInt16LE(offset);
- if (byteLength === 1 || byteLength === undefined)
+ if (byteLength === 1)
return this.readInt8(offset);
boundsError(byteLength, 6, 'byteLength');
@@ -314,6 +320,8 @@ function readInt8(offset = 0) {
}
function readIntBE(offset, byteLength) {
+ if (offset === undefined)
+ throw new ERR_INVALID_ARG_TYPE('offset', 'number', offset);
if (byteLength === 6)
return readInt48BE(this, offset);
if (byteLength === 5)
@@ -324,7 +332,7 @@ function readIntBE(offset, byteLength) {
return this.readInt32BE(offset);
if (byteLength === 2)
return this.readInt16BE(offset);
- if (byteLength === 1 || byteLength === undefined)
+ if (byteLength === 1)
return this.readInt8(offset);
boundsError(byteLength, 6, 'byteLength');
@@ -460,7 +468,7 @@ function readDoubleForwards(offset = 0) {
}
// Write integers.
-function writeUIntLE(value, offset = 0, byteLength) {
+function writeUIntLE(value, offset, byteLength) {
if (byteLength === 6)
return writeU_Int48LE(this, value, offset, 0, 0xffffffffffff);
if (byteLength === 5)
@@ -471,7 +479,7 @@ function writeUIntLE(value, offset = 0, byteLength) {
return writeU_Int32LE(this, value, offset, 0, 0xffffffff);
if (byteLength === 2)
return writeU_Int16LE(this, value, offset, 0, 0xffff);
- if (byteLength === 1 || byteLength === undefined)
+ if (byteLength === 1)
return writeU_Int8(this, value, offset, 0, 0xff);
boundsError(byteLength, 6, 'byteLength');
@@ -571,7 +579,7 @@ function writeUInt8(value, offset = 0) {
return writeU_Int8(this, value, offset, 0, 0xff);
}
-function writeUIntBE(value, offset = 0, byteLength) {
+function writeUIntBE(value, offset, byteLength) {
if (byteLength === 6)
return writeU_Int48BE(this, value, offset, 0, 0xffffffffffffff);
if (byteLength === 5)
@@ -582,7 +590,7 @@ function writeUIntBE(value, offset = 0, byteLength) {
return writeU_Int32BE(this, value, offset, 0, 0xffffffff);
if (byteLength === 2)
return writeU_Int16BE(this, value, offset, 0, 0xffff);
- if (byteLength === 1 || byteLength === undefined)
+ if (byteLength === 1)
return writeU_Int8(this, value, offset, 0, 0xff);
boundsError(byteLength, 6, 'byteLength');
@@ -663,7 +671,7 @@ function writeUInt16BE(value, offset = 0) {
return writeU_Int16BE(this, value, offset, 0, 0xffffffff);
}
-function writeIntLE(value, offset = 0, byteLength) {
+function writeIntLE(value, offset, byteLength) {
if (byteLength === 6)
return writeU_Int48LE(this, value, offset, -0x800000000000, 0x7fffffffffff);
if (byteLength === 5)
@@ -674,7 +682,7 @@ function writeIntLE(value, offset = 0, byteLength) {
return writeU_Int32LE(this, value, offset, -0x80000000, 0x7fffffff);
if (byteLength === 2)
return writeU_Int16LE(this, value, offset, -0x8000, 0x7fff);
- if (byteLength === 1 || byteLength === undefined)
+ if (byteLength === 1)
return writeU_Int8(this, value, offset, -0x80, 0x7f);
boundsError(byteLength, 6, 'byteLength');
@@ -692,7 +700,7 @@ function writeInt8(value, offset = 0) {
return writeU_Int8(this, value, offset, -0x80, 0x7f);
}
-function writeIntBE(value, offset = 0, byteLength) {
+function writeIntBE(value, offset, byteLength) {
if (byteLength === 6)
return writeU_Int48BE(this, value, offset, -0x800000000000, 0x7fffffffffff);
if (byteLength === 5)
@@ -703,7 +711,7 @@ function writeIntBE(value, offset = 0, byteLength) {
return writeU_Int32BE(this, value, offset, -0x80000000, 0x7fffffff);
if (byteLength === 2)
return writeU_Int16BE(this, value, offset, -0x8000, 0x7fff);
- if (byteLength === 1 || byteLength === undefined)
+ if (byteLength === 1)
return writeU_Int8(this, value, offset, -0x80, 0x7f);
boundsError(byteLength, 6, 'byteLength');
diff --git a/lib/internal/child_process.js b/lib/internal/child_process.js
index 8cca1c2f6b0443..a630cff717bcad 100644
--- a/lib/internal/child_process.js
+++ b/lib/internal/child_process.js
@@ -31,6 +31,8 @@ const SocketList = require('internal/socket_list');
const { convertToValidSignal } = require('internal/util');
const { isUint8Array } = require('internal/util/types');
const spawn_sync = process.binding('spawn_sync');
+const { HTTPParser } = process.binding('http_parser');
+const { freeParser } = require('_http_common');
const {
UV_EACCES,
@@ -107,6 +109,14 @@ const handleConversion = {
if (!options.keepOpen) {
handle.onread = nop;
socket._handle = null;
+ socket.setTimeout(0);
+ // In case of an HTTP connection socket, release the associated
+ // resources
+ if (socket.parser && socket.parser instanceof HTTPParser) {
+ freeParser(socket.parser, null, socket);
+ if (socket._httpMessage)
+ socket._httpMessage.detachSocket(socket);
+ }
}
return handle;
diff --git a/lib/internal/crypto/diffiehellman.js b/lib/internal/crypto/diffiehellman.js
index 329add6d4d7cd5..dad7a903b26a5e 100644
--- a/lib/internal/crypto/diffiehellman.js
+++ b/lib/internal/crypto/diffiehellman.js
@@ -219,21 +219,15 @@ function encode(buffer, encoding) {
}
function getFormat(format) {
- let f;
if (format) {
if (format === 'compressed')
- f = POINT_CONVERSION_COMPRESSED;
- else if (format === 'hybrid')
- f = POINT_CONVERSION_HYBRID;
- // Default
- else if (format === 'uncompressed')
- f = POINT_CONVERSION_UNCOMPRESSED;
- else
+ return POINT_CONVERSION_COMPRESSED;
+ if (format === 'hybrid')
+ return POINT_CONVERSION_HYBRID;
+ if (format !== 'uncompressed')
throw new ERR_CRYPTO_ECDH_INVALID_FORMAT(format);
- } else {
- f = POINT_CONVERSION_UNCOMPRESSED;
}
- return f;
+ return POINT_CONVERSION_UNCOMPRESSED;
}
module.exports = {
diff --git a/lib/internal/crypto/pbkdf2.js b/lib/internal/crypto/pbkdf2.js
index 4a7f26b509b521..82ea9feb852649 100644
--- a/lib/internal/crypto/pbkdf2.js
+++ b/lib/internal/crypto/pbkdf2.js
@@ -7,10 +7,10 @@ const {
ERR_OUT_OF_RANGE
} = require('internal/errors').codes;
const {
+ checkIsArrayBufferView,
getDefaultEncoding,
toBuf
} = require('internal/crypto/util');
-const { isArrayBufferView } = require('internal/util/types');
const {
PBKDF2
} = process.binding('crypto');
@@ -39,19 +39,8 @@ function _pbkdf2(password, salt, iterations, keylen, digest, callback) {
if (digest !== null && typeof digest !== 'string')
throw new ERR_INVALID_ARG_TYPE('digest', ['string', 'null'], digest);
- password = toBuf(password);
- salt = toBuf(salt);
-
- if (!isArrayBufferView(password)) {
- throw new ERR_INVALID_ARG_TYPE('password',
- ['string', 'Buffer', 'TypedArray'],
- password);
- }
-
- if (!isArrayBufferView(salt)) {
- throw new ERR_INVALID_ARG_TYPE('salt',
- ['string', 'Buffer', 'TypedArray'], salt);
- }
+ password = checkIsArrayBufferView('password', toBuf(password));
+ salt = checkIsArrayBufferView('salt', toBuf(salt));
if (typeof iterations !== 'number')
throw new ERR_INVALID_ARG_TYPE('iterations', 'number', iterations);
diff --git a/lib/internal/crypto/sig.js b/lib/internal/crypto/sig.js
index aed679e99b9b8f..1073b83d720098 100644
--- a/lib/internal/crypto/sig.js
+++ b/lib/internal/crypto/sig.js
@@ -14,10 +14,10 @@ const {
RSA_PKCS1_PADDING
} = process.binding('constants').crypto;
const {
+ checkIsArrayBufferView,
getDefaultEncoding,
toBuf
} = require('internal/crypto/util');
-const { isArrayBufferView } = require('internal/util/types');
const { Writable } = require('stream');
const { inherits } = require('util');
@@ -41,18 +41,30 @@ Sign.prototype._write = function _write(chunk, encoding, callback) {
Sign.prototype.update = function update(data, encoding) {
encoding = encoding || getDefaultEncoding();
- data = toBuf(data, encoding);
- if (!isArrayBufferView(data)) {
- throw new ERR_INVALID_ARG_TYPE(
- 'data',
- ['string', 'Buffer', 'TypedArray', 'DataView'],
- data
- );
- }
+ data = checkIsArrayBufferView('data', toBuf(data, encoding));
this._handle.update(data);
return this;
};
+function getPadding(options) {
+ return getIntOption('padding', RSA_PKCS1_PADDING, options);
+}
+
+function getSaltLength(options) {
+ return getIntOption('saltLength', RSA_PSS_SALTLEN_AUTO, options);
+}
+
+function getIntOption(name, defaultValue, options) {
+ if (options.hasOwnProperty(name)) {
+ if (options[name] === options[name] >> 0) {
+ return options[name];
+ } else {
+ throw new ERR_INVALID_OPT_VALUE(name, options[name]);
+ }
+ }
+ return defaultValue;
+}
+
Sign.prototype.sign = function sign(options, encoding) {
if (!options)
throw new ERR_CRYPTO_SIGN_KEY_REQUIRED();
@@ -61,32 +73,11 @@ Sign.prototype.sign = function sign(options, encoding) {
var passphrase = options.passphrase || null;
// Options specific to RSA
- var rsaPadding = RSA_PKCS1_PADDING;
- if (options.hasOwnProperty('padding')) {
- if (options.padding === options.padding >> 0) {
- rsaPadding = options.padding;
- } else {
- throw new ERR_INVALID_OPT_VALUE('padding', options.padding);
- }
- }
+ var rsaPadding = getPadding(options);
- var pssSaltLength = RSA_PSS_SALTLEN_AUTO;
- if (options.hasOwnProperty('saltLength')) {
- if (options.saltLength === options.saltLength >> 0) {
- pssSaltLength = options.saltLength;
- } else {
- throw new ERR_INVALID_OPT_VALUE('saltLength', options.saltLength);
- }
- }
+ var pssSaltLength = getSaltLength(options);
- key = toBuf(key);
- if (!isArrayBufferView(key)) {
- throw new ERR_INVALID_ARG_TYPE(
- 'key',
- ['string', 'Buffer', 'TypedArray', 'DataView'],
- key
- );
- }
+ key = checkIsArrayBufferView('key', toBuf(key));
var ret = this._handle.sign(key, passphrase, rsaPadding, pssSaltLength);
@@ -119,41 +110,14 @@ Verify.prototype.verify = function verify(options, signature, sigEncoding) {
sigEncoding = sigEncoding || getDefaultEncoding();
// Options specific to RSA
- var rsaPadding = RSA_PKCS1_PADDING;
- if (options.hasOwnProperty('padding')) {
- if (options.padding === options.padding >> 0) {
- rsaPadding = options.padding;
- } else {
- throw new ERR_INVALID_OPT_VALUE('padding', options.padding);
- }
- }
+ var rsaPadding = getPadding(options);
- var pssSaltLength = RSA_PSS_SALTLEN_AUTO;
- if (options.hasOwnProperty('saltLength')) {
- if (options.saltLength === options.saltLength >> 0) {
- pssSaltLength = options.saltLength;
- } else {
- throw new ERR_INVALID_OPT_VALUE('saltLength', options.saltLength);
- }
- }
+ var pssSaltLength = getSaltLength(options);
- key = toBuf(key);
- if (!isArrayBufferView(key)) {
- throw new ERR_INVALID_ARG_TYPE(
- 'key',
- ['string', 'Buffer', 'TypedArray', 'DataView'],
- key
- );
- }
+ key = checkIsArrayBufferView('key', toBuf(key));
- signature = toBuf(signature, sigEncoding);
- if (!isArrayBufferView(signature)) {
- throw new ERR_INVALID_ARG_TYPE(
- 'signature',
- ['string', 'Buffer', 'TypedArray', 'DataView'],
- signature
- );
- }
+ signature = checkIsArrayBufferView('signature',
+ toBuf(signature, sigEncoding));
return this._handle.verify(key, signature, rsaPadding, pssSaltLength);
};
diff --git a/lib/internal/crypto/util.js b/lib/internal/crypto/util.js
index 095ca0478bd99f..59a5d57a1e4f39 100644
--- a/lib/internal/crypto/util.js
+++ b/lib/internal/crypto/util.js
@@ -83,7 +83,19 @@ function timingSafeEqual(buf1, buf2) {
return _timingSafeEqual(buf1, buf2);
}
+function checkIsArrayBufferView(name, buffer) {
+ if (!isArrayBufferView(buffer)) {
+ throw new ERR_INVALID_ARG_TYPE(
+ name,
+ ['string', 'Buffer', 'TypedArray', 'DataView'],
+ buffer
+ );
+ }
+ return buffer;
+}
+
module.exports = {
+ checkIsArrayBufferView,
getCiphers,
getCurves,
getDefaultEncoding,
diff --git a/lib/internal/errors.js b/lib/internal/errors.js
index 09f58506c44ea0..5299b66c831061 100644
--- a/lib/internal/errors.js
+++ b/lib/internal/errors.js
@@ -15,6 +15,7 @@ const kInfo = Symbol('info');
const messages = new Map();
const codes = {};
+let blue = '';
let green = '';
let red = '';
let white = '';
@@ -259,7 +260,7 @@ function createErrDiff(actual, expected, operator) {
const expectedLines = inspectValue(expected);
const msg = READABLE_OPERATOR[operator] +
`:\n${green}+ expected${white} ${red}- actual${white}`;
- const skippedMsg = ' ... Lines skipped';
+ const skippedMsg = ` ${blue}...${white} Lines skipped`;
// Remove all ending lines that match (this optimizes the output for
// readability by reducing the number of total changed lines).
@@ -280,7 +281,7 @@ function createErrDiff(actual, expected, operator) {
b = expectedLines[expectedLines.length - 1];
}
if (i > 3) {
- end = `\n...${end}`;
+ end = `\n${blue}...${white}${end}`;
skipped = true;
}
if (other !== '') {
@@ -297,7 +298,7 @@ function createErrDiff(actual, expected, operator) {
if (actualLines.length < i + 1) {
if (cur > 1 && i > 2) {
if (cur > 4) {
- res += '\n...';
+ res += `\n${blue}...${white}`;
skipped = true;
} else if (cur > 3) {
res += `\n ${expectedLines[i - 2]}`;
@@ -313,7 +314,7 @@ function createErrDiff(actual, expected, operator) {
} else if (expectedLines.length < i + 1) {
if (cur > 1 && i > 2) {
if (cur > 4) {
- res += '\n...';
+ res += `\n${blue}...${white}`;
skipped = true;
} else if (cur > 3) {
res += `\n ${actualLines[i - 2]}`;
@@ -329,7 +330,7 @@ function createErrDiff(actual, expected, operator) {
} else if (actualLines[i] !== expectedLines[i]) {
if (cur > 1 && i > 2) {
if (cur > 4) {
- res += '\n...';
+ res += `\n${blue}...${white}`;
skipped = true;
} else if (cur > 3) {
res += `\n ${actualLines[i - 2]}`;
@@ -354,19 +355,17 @@ function createErrDiff(actual, expected, operator) {
}
// Inspected object to big (Show ~20 rows max)
if (printedLines > 20 && i < maxLines - 2) {
- return `${msg}${skippedMsg}\n${res}\n...${other}\n...`;
+ return `${msg}${skippedMsg}\n${res}\n${blue}...${white}${other}\n` +
+ `${blue}...${white}`;
}
}
// Strict equal with identical objects that are not identical by reference.
if (identical === maxLines) {
- let base = 'Input object identical but not reference equal:';
-
- if (operator !== 'strictEqual') {
- // This code path should not be possible to reach.
- // The output is identical but it is not clear why.
- base = 'Input objects not identical:';
- }
+ // E.g., assert.deepStrictEqual(Symbol(), Symbol())
+ const base = operator === 'strictEqual' ?
+ 'Input objects identical but not reference equal:' :
+ 'Input objects not identical:';
// We have to get the result again. The lines were all removed before.
const actualLines = inspectValue(actual);
@@ -374,13 +373,13 @@ function createErrDiff(actual, expected, operator) {
// Only remove lines in case it makes sense to collapse those.
// TODO: Accept env to always show the full error.
if (actualLines.length > 30) {
- actualLines[26] = '...';
+ actualLines[26] = `${blue}...${white}`;
while (actualLines.length > 27) {
actualLines.pop();
}
}
- return `${base}\n\n ${actualLines.join('\n ')}\n`;
+ return `${base}\n\n${actualLines.join('\n')}\n`;
}
return `${msg}${skipped ? skippedMsg : ''}\n${res}${other}${end}`;
}
@@ -405,10 +404,12 @@ class AssertionError extends Error {
// Reset on each call to make sure we handle dynamically set environment
// variables correct.
if (process.stdout.getColorDepth() !== 1) {
+ blue = '\u001b[34m';
green = '\u001b[32m';
white = '\u001b[39m';
red = '\u001b[31m';
} else {
+ blue = '';
green = '';
white = '';
red = '';
@@ -438,7 +439,7 @@ class AssertionError extends Error {
// Only remove lines in case it makes sense to collapse those.
// TODO: Accept env to always show the full error.
if (res.length > 30) {
- res[26] = '...';
+ res[26] = `${blue}...${white}`;
while (res.length > 27) {
res.pop();
}
@@ -448,7 +449,7 @@ class AssertionError extends Error {
if (res.length === 1) {
super(`${base} ${res[0]}`);
} else {
- super(`${base}\n\n ${res.join('\n ')}\n`);
+ super(`${base}\n\n${res.join('\n')}\n`);
}
} else {
let res = util.inspect(actual);
@@ -881,7 +882,6 @@ E('ERR_INVALID_CALLBACK', 'Callback must be a function', TypeError);
E('ERR_INVALID_CHAR', invalidChar, TypeError);
E('ERR_INVALID_CURSOR_POS',
'Cannot set cursor row without setting its column', TypeError);
-E('ERR_INVALID_DOMAIN_NAME', 'Unable to determine the domain name', TypeError);
E('ERR_INVALID_FD',
'"fd" must be a positive integer: %s', RangeError);
E('ERR_INVALID_FD_TYPE', 'Unsupported fd type: %s', TypeError);
@@ -952,7 +952,7 @@ E('ERR_NO_LONGER_SUPPORTED', '%s is no longer supported', Error);
E('ERR_OUT_OF_RANGE', outOfRange, RangeError);
E('ERR_REQUIRE_ESM', 'Must use import to load ES Module: %s', Error);
E('ERR_SCRIPT_EXECUTION_INTERRUPTED',
- 'Script execution was interrupted by `SIGINT`.', Error);
+ 'Script execution was interrupted by `SIGINT`', Error);
E('ERR_SERVER_ALREADY_LISTEN',
'Listen method has been called more than once without closing.', Error);
E('ERR_SERVER_NOT_RUNNING', 'Server is not running.', Error);
@@ -1017,6 +1017,7 @@ E('ERR_UNHANDLED_ERROR',
if (err === undefined) return msg;
return `${msg} (${err})`;
}, Error);
+E('ERR_UNKNOWN_CREDENTIAL', '%s identifier does not exist: %s', Error);
E('ERR_UNKNOWN_ENCODING', 'Unknown encoding: %s', TypeError);
// This should probably be a `TypeError`.
diff --git a/lib/internal/fs.js b/lib/internal/fs.js
index 0cfb89a9d3d084..2f7a8d8ced176e 100644
--- a/lib/internal/fs.js
+++ b/lib/internal/fs.js
@@ -70,9 +70,6 @@ function getOptions(options, defaultOptions) {
return options;
}
-function isInt32(n) { return n === (n | 0); }
-function isUint32(n) { return n === (n >>> 0); }
-
function modeNum(m, def) {
if (typeof m === 'number')
return m;
@@ -341,26 +338,6 @@ function validateBuffer(buffer) {
}
}
-function validateLen(len) {
- let err;
-
- if (!isInt32(len)) {
- if (typeof len !== 'number') {
- err = new ERR_INVALID_ARG_TYPE('len', 'number', len);
- } else if (!Number.isInteger(len)) {
- err = new ERR_OUT_OF_RANGE('len', 'an integer', len);
- } else {
- // 2 ** 31 === 2147483648
- err = new ERR_OUT_OF_RANGE('len', '> -2147483649 && < 2147483648', len);
- }
- }
-
- if (err !== undefined) {
- Error.captureStackTrace(err, validateLen);
- throw err;
- }
-}
-
function validateOffsetLengthRead(offset, length, bufferLength) {
let err;
@@ -410,28 +387,10 @@ function validatePath(path, propName = 'path') {
}
}
-function validateUint32(value, propName) {
- if (!isUint32(value)) {
- let err;
- if (typeof value !== 'number') {
- err = new ERR_INVALID_ARG_TYPE(propName, 'number', value);
- } else if (!Number.isInteger(value)) {
- err = new ERR_OUT_OF_RANGE(propName, 'an integer', value);
- } else {
- // 2 ** 32 === 4294967296
- err = new ERR_OUT_OF_RANGE(propName, '>= 0 && < 4294967296', value);
- }
- Error.captureStackTrace(err, validateUint32);
- throw err;
- }
-}
-
module.exports = {
assertEncoding,
copyObject,
getOptions,
- isInt32,
- isUint32,
modeNum,
nullCheck,
preprocessSymlinkDestination,
@@ -443,9 +402,7 @@ module.exports = {
SyncWriteStream,
toUnixTimestamp,
validateBuffer,
- validateLen,
validateOffsetLengthRead,
validateOffsetLengthWrite,
- validatePath,
- validateUint32
+ validatePath
};
diff --git a/lib/internal/http2/compat.js b/lib/internal/http2/compat.js
index 0bdbe7b69fdf10..d2aaa8838e2cbc 100644
--- a/lib/internal/http2/compat.js
+++ b/lib/internal/http2/compat.js
@@ -31,6 +31,7 @@ const kTrailers = Symbol('trailers');
const kRawTrailers = Symbol('rawTrailers');
const kProxySocket = Symbol('proxySocket');
const kSetHeader = Symbol('setHeader');
+const kAborted = Symbol('aborted');
const {
HTTP2_HEADER_AUTHORITY,
@@ -137,6 +138,7 @@ function onStreamDrain() {
function onStreamAbortedRequest() {
const request = this[kRequest];
if (request !== undefined && request[kState].closed === false) {
+ request[kAborted] = true;
request.emit('aborted');
}
}
@@ -233,6 +235,7 @@ class Http2ServerRequest extends Readable {
this[kTrailers] = {};
this[kRawTrailers] = [];
this[kStream] = stream;
+ this[kAborted] = false;
stream[kProxySocket] = null;
stream[kRequest] = this;
@@ -248,6 +251,10 @@ class Http2ServerRequest extends Readable {
this.on('resume', onRequestResume);
}
+ get aborted() {
+ return this[kAborted];
+ }
+
get complete() {
return this._readableState.ended ||
this[kState].closed ||
diff --git a/lib/internal/http2/core.js b/lib/internal/http2/core.js
index 495fa60ba39ce3..a9fdcb659544e5 100644
--- a/lib/internal/http2/core.js
+++ b/lib/internal/http2/core.js
@@ -206,6 +206,7 @@ const STREAM_FLAGS_CLOSED = 0x2;
const STREAM_FLAGS_HEADERS_SENT = 0x4;
const STREAM_FLAGS_HEAD_REQUEST = 0x8;
const STREAM_FLAGS_ABORTED = 0x10;
+const STREAM_FLAGS_HAS_TRAILERS = 0x20;
const SESSION_FLAGS_PENDING = 0x0;
const SESSION_FLAGS_READY = 0x1;
@@ -330,26 +331,13 @@ function onStreamClose(code) {
if (stream.destroyed)
return;
- const state = stream[kState];
-
debug(`Http2Stream ${stream[kID]} [Http2Session ` +
`${sessionName(stream[kSession][kType])}]: closed with code ${code}`);
- if (!stream.closed) {
- // Clear timeout and remove timeout listeners
- stream.setTimeout(0);
- stream.removeAllListeners('timeout');
+ if (!stream.closed)
+ closeStream(stream, code, false);
- // Set the state flags
- state.flags |= STREAM_FLAGS_CLOSED;
- state.rstCode = code;
-
- // Close the writable side of the stream
- abort(stream);
- stream.end();
- }
-
- state.fd = -1;
+ stream[kState].fd = -1;
// Defer destroy we actually emit end.
if (stream._readableState.endEmitted || code !== NGHTTP2_NO_ERROR) {
// If errored or ended, we can destroy immediately.
@@ -504,7 +492,7 @@ function requestOnConnect(headers, options) {
// At this point, the stream should have already been destroyed during
// the session.destroy() method. Do nothing else.
- if (session.destroyed)
+ if (session === undefined || session.destroyed)
return;
// If the session was closed while waiting for the connect, destroy
@@ -716,8 +704,11 @@ const proxySocketHandler = {
// data received on the PING acknowlegement.
function pingCallback(cb) {
return function pingCallback(ack, duration, payload) {
- const err = ack ? null : new ERR_HTTP2_PING_CANCEL();
- cb(err, duration, payload);
+ if (ack) {
+ cb(null, duration, payload);
+ } else {
+ cb(new ERR_HTTP2_PING_CANCEL());
+ }
};
}
@@ -1412,6 +1403,9 @@ class ClientHttp2Session extends Http2Session {
if (options.endStream)
stream.end();
+ if (options.waitForTrailers)
+ stream[kState].flags |= STREAM_FLAGS_HAS_TRAILERS;
+
const onConnect = requestOnConnect.bind(stream, headersList, options);
if (this.connecting) {
this.on('connect', onConnect);
@@ -1445,8 +1439,11 @@ function afterDoStreamWrite(status, handle) {
}
function streamOnResume() {
- if (!this.destroyed && !this.pending)
+ if (!this.destroyed && !this.pending) {
+ if (!this[kState].didRead)
+ this[kState].didRead = true;
this[kHandle].readStart();
+ }
}
function streamOnPause() {
@@ -1454,16 +1451,6 @@ function streamOnPause() {
this[kHandle].readStop();
}
-// If the writable side of the Http2Stream is still open, emit the
-// 'aborted' event and set the aborted flag.
-function abort(stream) {
- if (!stream.aborted &&
- !(stream._writableState.ended || stream._writableState.ending)) {
- stream[kState].flags |= STREAM_FLAGS_ABORTED;
- stream.emit('aborted');
- }
-}
-
function afterShutdown() {
this.callback();
const stream = this.handle[kOwner];
@@ -1471,6 +1458,51 @@ function afterShutdown() {
stream[kMaybeDestroy]();
}
+function closeStream(stream, code, shouldSubmitRstStream = true) {
+ const state = stream[kState];
+ state.flags |= STREAM_FLAGS_CLOSED;
+ state.rstCode = code;
+
+ // Clear timeout and remove timeout listeners
+ stream.setTimeout(0);
+ stream.removeAllListeners('timeout');
+
+ const { ending, finished } = stream._writableState;
+
+ if (!ending) {
+ // If the writable side of the Http2Stream is still open, emit the
+ // 'aborted' event and set the aborted flag.
+ if (!stream.aborted) {
+ state.flags |= STREAM_FLAGS_ABORTED;
+ stream.emit('aborted');
+ }
+
+ // Close the writable side.
+ stream.end();
+ }
+
+ if (shouldSubmitRstStream) {
+ const finishFn = finishCloseStream.bind(stream, code);
+ if (!ending || finished || code !== NGHTTP2_NO_ERROR)
+ finishFn();
+ else
+ stream.once('finish', finishFn);
+ }
+}
+
+function finishCloseStream(code) {
+ const rstStreamFn = submitRstStream.bind(this, code);
+ // If the handle has not yet been assigned, queue up the request to
+ // ensure that the RST_STREAM frame is sent after the stream ID has
+ // been determined.
+ if (this.pending) {
+ this.push(null);
+ this.once('ready', rstStreamFn);
+ return;
+ }
+ rstStreamFn();
+}
+
// An Http2Stream is a Duplex stream that is backed by a
// node::http2::Http2Stream handle implementing StreamBase.
class Http2Stream extends Duplex {
@@ -1490,6 +1522,7 @@ class Http2Stream extends Duplex {
this[kTimeout] = null;
this[kState] = {
+ didRead: false,
flags: STREAM_FLAGS_PENDING,
rstCode: NGHTTP2_NO_ERROR,
writeQueueSize: 0,
@@ -1756,6 +1789,8 @@ class Http2Stream extends Duplex {
throw headersList;
this[kSentTrailers] = headers;
+ this[kState].flags &= ~STREAM_FLAGS_HAS_TRAILERS;
+
const ret = this[kHandle].trailers(headersList);
if (ret < 0)
this.destroy(new NghttpError(ret));
@@ -1786,38 +1821,13 @@ class Http2Stream extends Duplex {
if (callback !== undefined && typeof callback !== 'function')
throw new ERR_INVALID_CALLBACK();
- // Clear timeout and remove timeout listeners
- this.setTimeout(0);
- this.removeAllListeners('timeout');
-
- // Close the writable
- abort(this);
- this.end();
-
if (this.closed)
return;
- const state = this[kState];
- state.flags |= STREAM_FLAGS_CLOSED;
- state.rstCode = code;
-
- if (callback !== undefined) {
+ if (callback !== undefined)
this.once('close', callback);
- }
-
- if (this[kHandle] === undefined)
- return;
- const rstStreamFn = submitRstStream.bind(this, code);
- // If the handle has not yet been assigned, queue up the request to
- // ensure that the RST_STREAM frame is sent after the stream ID has
- // been determined.
- if (this.pending) {
- this.push(null);
- this.once('ready', rstStreamFn);
- return;
- }
- rstStreamFn();
+ closeStream(this, code);
}
// Called by this.destroy().
@@ -1832,26 +1842,19 @@ class Http2Stream extends Duplex {
debug(`Http2Stream ${this[kID] || ''} [Http2Session ` +
`${sessionName(session[kType])}]: destroying stream`);
const state = this[kState];
- const code = state.rstCode =
- err != null ?
- NGHTTP2_INTERNAL_ERROR :
- state.rstCode || NGHTTP2_NO_ERROR;
- if (handle !== undefined) {
- // If the handle exists, we need to close, then destroy the handle
- this.close(code);
- if (!this._readableState.ended && !this._readableState.ending)
- this.push(null);
+ const code = err != null ?
+ NGHTTP2_INTERNAL_ERROR : (state.rstCode || NGHTTP2_NO_ERROR);
+
+ const hasHandle = handle !== undefined;
+
+ if (!this.closed)
+ closeStream(this, code, hasHandle);
+ this.push(null);
+
+ if (hasHandle) {
handle.destroy();
session[kState].streams.delete(id);
} else {
- // Clear timeout and remove timeout listeners
- this.setTimeout(0);
- this.removeAllListeners('timeout');
-
- state.flags |= STREAM_FLAGS_CLOSED;
- abort(this);
- this.end();
- this.push(null);
session[kState].pendingStreams.delete(this);
}
@@ -1884,13 +1887,23 @@ class Http2Stream extends Duplex {
}
// TODO(mcollina): remove usage of _*State properties
- if (this._readableState.ended &&
- this._writableState.ended &&
- this._writableState.pendingcb === 0 &&
- this.closed) {
- this.destroy();
- // This should return, but eslint complains.
- // return
+ if (this._writableState.ended && this._writableState.pendingcb === 0) {
+ if (this._readableState.ended && this.closed) {
+ this.destroy();
+ return;
+ }
+
+ // We've submitted a response from our server session, have not attempted
+ // to process any incoming data, and have no trailers. This means we can
+ // attempt to gracefully close the session.
+ const state = this[kState];
+ if (this.headersSent &&
+ this[kSession][kType] === NGHTTP2_SESSION_SERVER &&
+ !(state.flags & STREAM_FLAGS_HAS_TRAILERS) &&
+ !state.didRead &&
+ !this._readableState.resumeScheduled) {
+ this.close();
+ }
}
}
}
@@ -2095,7 +2108,6 @@ function afterOpen(session, options, headers, streamOptions, err, fd) {
}
if (this.destroyed || this.closed) {
tryClose(fd);
- abort(this);
return;
}
state.fd = fd;
@@ -2224,8 +2236,10 @@ class ServerHttp2Stream extends Http2Stream {
if (options.endStream)
streamOptions |= STREAM_OPTION_EMPTY_PAYLOAD;
- if (options.waitForTrailers)
+ if (options.waitForTrailers) {
streamOptions |= STREAM_OPTION_GET_TRAILERS;
+ state.flags |= STREAM_FLAGS_HAS_TRAILERS;
+ }
headers = processHeaders(headers);
const statusCode = headers[HTTP2_HEADER_STATUS] |= 0;
@@ -2285,8 +2299,10 @@ class ServerHttp2Stream extends Http2Stream {
}
let streamOptions = 0;
- if (options.waitForTrailers)
+ if (options.waitForTrailers) {
streamOptions |= STREAM_OPTION_GET_TRAILERS;
+ this[kState].flags |= STREAM_FLAGS_HAS_TRAILERS;
+ }
if (typeof fd !== 'number')
throw new ERR_INVALID_ARG_TYPE('fd', 'number', fd);
@@ -2346,8 +2362,10 @@ class ServerHttp2Stream extends Http2Stream {
}
let streamOptions = 0;
- if (options.waitForTrailers)
+ if (options.waitForTrailers) {
streamOptions |= STREAM_OPTION_GET_TRAILERS;
+ this[kState].flags |= STREAM_FLAGS_HAS_TRAILERS;
+ }
const session = this[kSession];
debug(`Http2Stream ${this[kID]} [Http2Session ` +
diff --git a/lib/internal/process/methods.js b/lib/internal/process/methods.js
new file mode 100644
index 00000000000000..503fd317f60395
--- /dev/null
+++ b/lib/internal/process/methods.js
@@ -0,0 +1,137 @@
+'use strict';
+
+const {
+ ERR_INVALID_ARG_TYPE,
+ ERR_INVALID_ARG_VALUE,
+ ERR_UNKNOWN_CREDENTIAL
+} = require('internal/errors').codes;
+const {
+ validateUint32
+} = require('internal/validators');
+
+function setupProcessMethods() {
+ // Non-POSIX platforms like Windows don't have certain methods.
+ if (process.setgid !== undefined) {
+ setupPosixMethods();
+ }
+
+ const {
+ chdir: _chdir,
+ umask: _umask,
+ } = process;
+
+ process.chdir = chdir;
+ process.umask = umask;
+
+ function chdir(directory) {
+ if (typeof directory !== 'string') {
+ throw new ERR_INVALID_ARG_TYPE('directory', 'string', directory);
+ }
+ return _chdir(directory);
+ }
+
+ const octalReg = /^[0-7]+$/;
+ function umask(mask) {
+ if (typeof mask === 'undefined') {
+ return _umask(mask);
+ }
+
+ if (typeof mask === 'number') {
+ validateUint32(mask, 'mask');
+ return _umask(mask);
+ }
+
+ if (typeof mask === 'string') {
+ if (!octalReg.test(mask)) {
+ throw new ERR_INVALID_ARG_VALUE('mask', mask,
+ 'must be an octal string');
+ }
+ const octal = Number.parseInt(mask, 8);
+ validateUint32(octal, 'mask');
+ return _umask(octal);
+ }
+
+ throw new ERR_INVALID_ARG_TYPE('mask', ['number', 'string', 'undefined'],
+ mask);
+ }
+}
+
+function setupPosixMethods() {
+ const {
+ initgroups: _initgroups,
+ setegid: _setegid,
+ seteuid: _seteuid,
+ setgid: _setgid,
+ setuid: _setuid,
+ setgroups: _setgroups
+ } = process;
+
+ process.initgroups = initgroups;
+ process.setegid = setegid;
+ process.seteuid = seteuid;
+ process.setgid = setgid;
+ process.setuid = setuid;
+ process.setgroups = setgroups;
+
+ function initgroups(user, extraGroup) {
+ validateId(user, 'user');
+ validateId(extraGroup, 'extraGroup');
+ // Result is 0 on success, 1 if user is unknown, 2 if group is unknown.
+ const result = _initgroups(user, extraGroup);
+ if (result === 1) {
+ throw new ERR_UNKNOWN_CREDENTIAL('User', user);
+ } else if (result === 2) {
+ throw new ERR_UNKNOWN_CREDENTIAL('Group', extraGroup);
+ }
+ }
+
+ function setegid(id) {
+ return execId(id, 'Group', _setegid);
+ }
+
+ function seteuid(id) {
+ return execId(id, 'User', _seteuid);
+ }
+
+ function setgid(id) {
+ return execId(id, 'Group', _setgid);
+ }
+
+ function setuid(id) {
+ return execId(id, 'User', _setuid);
+ }
+
+ function setgroups(groups) {
+ if (!Array.isArray(groups)) {
+ throw new ERR_INVALID_ARG_TYPE('groups', 'Array', groups);
+ }
+ for (var i = 0; i < groups.length; i++) {
+ validateId(groups[i], `groups[${i}]`);
+ }
+ // Result is 0 on success. A positive integer indicates that the
+ // corresponding group was not found.
+ const result = _setgroups(groups);
+ if (result > 0) {
+ throw new ERR_UNKNOWN_CREDENTIAL('Group', groups[result - 1]);
+ }
+ }
+
+ function execId(id, type, method) {
+ validateId(id, 'id');
+ // Result is 0 on success, 1 if credential is unknown.
+ const result = method(id);
+ if (result === 1) {
+ throw new ERR_UNKNOWN_CREDENTIAL(type, id);
+ }
+ }
+
+ function validateId(id, name) {
+ if (typeof id === 'number') {
+ validateUint32(id, name);
+ } else if (typeof id !== 'string') {
+ throw new ERR_INVALID_ARG_TYPE(name, ['number', 'string'], id);
+ }
+ }
+}
+
+exports.setup = setupProcessMethods;
diff --git a/lib/internal/streams/async_iterator.js b/lib/internal/streams/async_iterator.js
index 9ca8e5ebe23b15..0e34573d877aee 100644
--- a/lib/internal/streams/async_iterator.js
+++ b/lib/internal/streams/async_iterator.js
@@ -58,7 +58,7 @@ function onError(iter, err) {
iter[kLastReject] = null;
reject(err);
}
- iter.error = err;
+ iter[kError] = err;
}
function wrapForNext(lastPromise, iter) {
diff --git a/lib/internal/util.js b/lib/internal/util.js
index ce25317b778a3f..071563a737815b 100644
--- a/lib/internal/util.js
+++ b/lib/internal/util.js
@@ -356,16 +356,17 @@ function isInsideNodeModules() {
// Iterate over all stack frames and look for the first one not coming
// from inside Node.js itself:
- for (const frame of stack) {
- const filename = frame.getFileName();
- // If a filename does not start with / or contain \,
- // it's likely from Node.js core.
- if (!/^\/|\\/.test(filename))
- continue;
- return kNodeModulesRE.test(filename);
+ if (Array.isArray(stack)) {
+ for (const frame of stack) {
+ const filename = frame.getFileName();
+ // If a filename does not start with / or contain \,
+ // it's likely from Node.js core.
+ if (!/^\/|\\/.test(filename))
+ continue;
+ return kNodeModulesRE.test(filename);
+ }
}
-
- return false; // This should be unreachable.
+ return false;
}
diff --git a/lib/internal/validators.js b/lib/internal/validators.js
new file mode 100644
index 00000000000000..556bfb2dc08f5f
--- /dev/null
+++ b/lib/internal/validators.js
@@ -0,0 +1,58 @@
+'use strict';
+
+const {
+ ERR_INVALID_ARG_TYPE,
+ ERR_OUT_OF_RANGE
+} = require('internal/errors').codes;
+
+function isInt32(value) {
+ return value === (value | 0);
+}
+
+function isUint32(value) {
+ return value === (value >>> 0);
+}
+
+function validateInt32(value, name) {
+ if (!isInt32(value)) {
+ let err;
+ if (typeof value !== 'number') {
+ err = new ERR_INVALID_ARG_TYPE(name, 'number', value);
+ } else if (!Number.isInteger(value)) {
+ err = new ERR_OUT_OF_RANGE(name, 'an integer', value);
+ } else {
+ // 2 ** 31 === 2147483648
+ err = new ERR_OUT_OF_RANGE(name, '> -2147483649 && < 2147483648', value);
+ }
+ Error.captureStackTrace(err, validateInt32);
+ throw err;
+ }
+}
+
+function validateUint32(value, name, positive) {
+ if (!isUint32(value)) {
+ let err;
+ if (typeof value !== 'number') {
+ err = new ERR_INVALID_ARG_TYPE(name, 'number', value);
+ } else if (!Number.isInteger(value)) {
+ err = new ERR_OUT_OF_RANGE(name, 'an integer', value);
+ } else {
+ const min = positive ? 1 : 0;
+ // 2 ** 32 === 4294967296
+ err = new ERR_OUT_OF_RANGE(name, `>= ${min} && < 4294967296`, value);
+ }
+ Error.captureStackTrace(err, validateUint32);
+ throw err;
+ } else if (positive && value === 0) {
+ const err = new ERR_OUT_OF_RANGE(name, '>= 1 && < 4294967296', value);
+ Error.captureStackTrace(err, validateUint32);
+ throw err;
+ }
+}
+
+module.exports = {
+ isInt32,
+ isUint32,
+ validateInt32,
+ validateUint32
+};
diff --git a/lib/internal/vm/module.js b/lib/internal/vm/module.js
index 7284c8bd619901..9e945c45c3de8a 100644
--- a/lib/internal/vm/module.js
+++ b/lib/internal/vm/module.js
@@ -6,7 +6,6 @@ const { URL } = require('internal/url');
const { isContext } = process.binding('contextify');
const {
ERR_INVALID_ARG_TYPE,
- ERR_OUT_OF_RANGE,
ERR_VM_MODULE_ALREADY_LINKED,
ERR_VM_MODULE_DIFFERENT_CONTEXT,
ERR_VM_MODULE_LINKING_ERRORED,
@@ -19,6 +18,7 @@ const {
customInspectSymbol,
} = require('internal/util');
const { SafePromise } = require('internal/safe_globals');
+const { validateInt32, validateUint32 } = require('internal/validators');
const {
ModuleWrap,
@@ -92,8 +92,8 @@ class Module {
perContextModuleId.set(context, 1);
}
- validateInteger(lineOffset, 'options.lineOffset');
- validateInteger(columnOffset, 'options.columnOffset');
+ validateInt32(lineOffset, 'options.lineOffset');
+ validateInt32(columnOffset, 'options.columnOffset');
if (initializeImportMeta !== undefined) {
if (typeof initializeImportMeta === 'function') {
@@ -203,9 +203,8 @@ class Module {
let timeout = options.timeout;
if (timeout === undefined) {
timeout = -1;
- } else if (!Number.isInteger(timeout) || timeout <= 0) {
- throw new ERR_INVALID_ARG_TYPE('options.timeout', 'a positive integer',
- timeout);
+ } else {
+ validateUint32(timeout, 'options.timeout', true);
}
const { breakOnSigint = false } = options;
@@ -243,15 +242,6 @@ class Module {
}
}
-function validateInteger(prop, propName) {
- if (!Number.isInteger(prop)) {
- throw new ERR_INVALID_ARG_TYPE(propName, 'integer', prop);
- }
- if ((prop >> 0) !== prop) {
- throw new ERR_OUT_OF_RANGE(propName, '32-bit integer', prop);
- }
-}
-
module.exports = {
Module,
initImportMetaMap,
diff --git a/lib/timers.js b/lib/timers.js
index 15700f5a1212ab..30bffb432ac26b 100644
--- a/lib/timers.js
+++ b/lib/timers.js
@@ -285,15 +285,18 @@ function tryOnTimeout(timer, start) {
var threw = true;
if (timerAsyncId !== null)
emitBefore(timerAsyncId, timer[trigger_async_id_symbol]);
+ if (start === undefined && timer._repeat)
+ start = TimerWrap.now();
try {
- ontimeout(timer, start);
+ ontimeout(timer);
threw = false;
} finally {
if (timerAsyncId !== null) {
if (!threw)
emitAfter(timerAsyncId);
- if ((threw || !timer._repeat) && destroyHooksExist() &&
- !timer._destroyed) {
+ if (timer._repeat) {
+ rearm(timer, start);
+ } else if (destroyHooksExist() && !timer._destroyed) {
emitDestroy(timerAsyncId);
timer._destroyed = true;
}
@@ -417,18 +420,14 @@ setTimeout[internalUtil.promisify.custom] = function(after, value) {
exports.setTimeout = setTimeout;
-function ontimeout(timer, start) {
+function ontimeout(timer) {
const args = timer._timerArgs;
if (typeof timer._onTimeout !== 'function')
return promiseResolve(timer._onTimeout, args[0]);
- if (start === undefined && timer._repeat)
- start = TimerWrap.now();
if (!args)
timer._onTimeout();
else
Reflect.apply(timer._onTimeout, timer, args);
- if (timer._repeat)
- rearm(timer, start);
}
function rearm(timer, start = TimerWrap.now()) {
diff --git a/lib/tls.js b/lib/tls.js
index 1e444d5d8898c2..28cd302674b04c 100644
--- a/lib/tls.js
+++ b/lib/tls.js
@@ -32,6 +32,7 @@ const url = require('url');
const binding = process.binding('crypto');
const { Buffer } = require('buffer');
const EventEmitter = require('events');
+const { URL } = require('internal/url');
const DuplexPair = require('internal/streams/duplexpair');
const { canonicalizeIP } = process.binding('cares_wrap');
const _tls_common = require('_tls_common');
@@ -169,6 +170,7 @@ function check(hostParts, pattern, wildcards) {
return true;
}
+let urlWarningEmitted = false;
exports.checkServerIdentity = function checkServerIdentity(host, cert) {
const subject = cert.subject;
const altNames = cert.subjectaltname;
@@ -183,7 +185,21 @@ exports.checkServerIdentity = function checkServerIdentity(host, cert) {
if (name.startsWith('DNS:')) {
dnsNames.push(name.slice(4));
} else if (name.startsWith('URI:')) {
- const uri = url.parse(name.slice(4));
+ let uri;
+ try {
+ uri = new URL(name.slice(4));
+ } catch (err) {
+ uri = url.parse(name.slice(4));
+ if (!urlWarningEmitted && !process.noDeprecation) {
+ urlWarningEmitted = true;
+ process.emitWarning(
+ `The URI ${name.slice(4)} found in cert.subjectaltname ` +
+ 'is not a valid URI, and is supported in the tls module ' +
+ 'solely for compatibility.',
+ 'DeprecationWarning', 'DEP0109');
+ }
+ }
+
uriNames.push(uri.hostname); // TODO(bnoordhuis) Also use scheme.
} else if (name.startsWith('IP Address:')) {
ips.push(canonicalizeIP(name.slice(11)));
diff --git a/lib/url.js b/lib/url.js
index ac9879a650fce6..e4326e80b5d948 100644
--- a/lib/url.js
+++ b/lib/url.js
@@ -281,9 +281,6 @@ Url.prototype.parse = function parse(url, parseQueryString, slashesDenoteHost) {
// http://a@b@c/ => user:a@b host:c
// http://a@b?@c => user:a host:b path:/?@c
- // v0.12 TODO(isaacs): This is not quite how Chrome does things.
- // Review our test case against browsers more comprehensively.
-
var hostEnd = -1;
var atSign = -1;
var nonHost = -1;
diff --git a/lib/vm.js b/lib/vm.js
index 5a5130d7c9c328..3ab50c81580365 100644
--- a/lib/vm.js
+++ b/lib/vm.js
@@ -28,11 +28,9 @@ const {
isContext: _isContext,
} = process.binding('contextify');
-const {
- ERR_INVALID_ARG_TYPE,
- ERR_OUT_OF_RANGE
-} = require('internal/errors').codes;
+const { ERR_INVALID_ARG_TYPE } = require('internal/errors').codes;
const { isUint8Array } = require('internal/util/types');
+const { validateInt32, validateUint32 } = require('internal/validators');
class Script extends ContextifyScript {
constructor(code, options = {}) {
@@ -56,8 +54,8 @@ class Script extends ContextifyScript {
if (typeof filename !== 'string') {
throw new ERR_INVALID_ARG_TYPE('options.filename', 'string', filename);
}
- validateInteger(lineOffset, 'options.lineOffset');
- validateInteger(columnOffset, 'options.columnOffset');
+ validateInt32(lineOffset, 'options.lineOffset');
+ validateInt32(columnOffset, 'options.columnOffset');
if (cachedData !== undefined && !isUint8Array(cachedData)) {
throw new ERR_INVALID_ARG_TYPE('options.cachedData',
['Buffer', 'Uint8Array'], cachedData);
@@ -119,15 +117,6 @@ function validateContext(sandbox) {
}
}
-function validateInteger(prop, propName) {
- if (!Number.isInteger(prop)) {
- throw new ERR_INVALID_ARG_TYPE(propName, 'integer', prop);
- }
- if ((prop >> 0) !== prop) {
- throw new ERR_OUT_OF_RANGE(propName, '32-bit integer', prop);
- }
-}
-
function validateString(prop, propName) {
if (prop !== undefined && typeof prop !== 'string')
throw new ERR_INVALID_ARG_TYPE(propName, 'string', prop);
@@ -151,9 +140,8 @@ function getRunInContextArgs(options = {}) {
let timeout = options.timeout;
if (timeout === undefined) {
timeout = -1;
- } else if (!Number.isInteger(timeout) || timeout <= 0) {
- throw new ERR_INVALID_ARG_TYPE('options.timeout', 'a positive integer',
- timeout);
+ } else {
+ validateUint32(timeout, 'options.timeout', true);
}
const {
diff --git a/node.gyp b/node.gyp
index 8347beb18245ca..78a15bd7b838a7 100644
--- a/node.gyp
+++ b/node.gyp
@@ -118,6 +118,7 @@
'lib/internal/net.js',
'lib/internal/os.js',
'lib/internal/process/esm_loader.js',
+ 'lib/internal/process/methods.js',
'lib/internal/process/next_tick.js',
'lib/internal/process/promises.js',
'lib/internal/process/stdio.js',
@@ -146,6 +147,7 @@
'lib/internal/v8.js',
'lib/internal/v8_prof_polyfill.js',
'lib/internal/v8_prof_processor.js',
+ 'lib/internal/validators.js',
'lib/internal/stream_base_commons.js',
'lib/internal/vm/module.js',
'lib/internal/streams/lazy_transform.js',
@@ -680,13 +682,13 @@
'toolsets': ['host'],
'conditions': [
[ 'v8_enable_inspector==1', {
- 'actions': [
+ 'copies': [
{
- 'action_name': 'v8_inspector_copy_protocol_to_intermediate_folder',
- 'inputs': [ 'deps/v8/src/inspector/js_protocol.pdl' ],
- 'outputs': [ '<(SHARED_INTERMEDIATE_DIR)/js_protocol.pdl' ],
- 'action': [ 'cp', '<@(_inputs)', '<(SHARED_INTERMEDIATE_DIR)' ],
- },
+ 'destination': '<(SHARED_INTERMEDIATE_DIR)',
+ 'files': ['deps/v8/src/inspector/js_protocol.pdl']
+ }
+ ],
+ 'actions': [
{
'action_name': 'v8_inspector_convert_protocol_to_json',
'inputs': [
@@ -961,6 +963,7 @@
'test/cctest/test_base64.cc',
'test/cctest/test_node_postmortem_metadata.cc',
'test/cctest/test_environment.cc',
+ 'test/cctest/test_platform.cc',
'test/cctest/test_util.cc',
'test/cctest/test_url.cc'
],
diff --git a/src/env.cc b/src/env.cc
index 1f47ea21af21b8..08d719a51011d1 100644
--- a/src/env.cc
+++ b/src/env.cc
@@ -28,44 +28,46 @@ IsolateData::IsolateData(Isolate* isolate,
uv_loop_t* event_loop,
MultiIsolatePlatform* platform,
uint32_t* zero_fill_field) :
-
-// Create string and private symbol properties as internalized one byte strings.
-//
-// Internalized because it makes property lookups a little faster and because
-// the string is created in the old space straight away. It's going to end up
-// in the old space sooner or later anyway but now it doesn't go through
-// v8::Eternal's new space handling first.
-//
-// One byte because our strings are ASCII and we can safely skip V8's UTF-8
-// decoding step. It's a one-time cost, but why pay it when you don't have to?
-#define V(PropertyName, StringValue) \
- PropertyName ## _( \
- isolate, \
- Private::New( \
- isolate, \
- String::NewFromOneByte( \
- isolate, \
- reinterpret_cast(StringValue), \
- v8::NewStringType::kInternalized, \
- sizeof(StringValue) - 1).ToLocalChecked())),
- PER_ISOLATE_PRIVATE_SYMBOL_PROPERTIES(V)
-#undef V
-#define V(PropertyName, StringValue) \
- PropertyName ## _( \
- isolate, \
- String::NewFromOneByte( \
- isolate, \
- reinterpret_cast(StringValue), \
- v8::NewStringType::kInternalized, \
- sizeof(StringValue) - 1).ToLocalChecked()),
- PER_ISOLATE_STRING_PROPERTIES(V)
-#undef V
isolate_(isolate),
event_loop_(event_loop),
zero_fill_field_(zero_fill_field),
platform_(platform) {
if (platform_ != nullptr)
platform_->RegisterIsolate(this, event_loop);
+
+ // Create string and private symbol properties as internalized one byte
+ // strings after the platform is properly initialized.
+ //
+ // Internalized because it makes property lookups a little faster and
+ // because the string is created in the old space straight away. It's going
+ // to end up in the old space sooner or later anyway but now it doesn't go
+ // through v8::Eternal's new space handling first.
+ //
+ // One byte because our strings are ASCII and we can safely skip V8's UTF-8
+ // decoding step.
+
+#define V(PropertyName, StringValue) \
+ PropertyName ## _.Set( \
+ isolate, \
+ Private::New( \
+ isolate, \
+ String::NewFromOneByte( \
+ isolate, \
+ reinterpret_cast(StringValue), \
+ v8::NewStringType::kInternalized, \
+ sizeof(StringValue) - 1).ToLocalChecked()));
+ PER_ISOLATE_PRIVATE_SYMBOL_PROPERTIES(V)
+#undef V
+#define V(PropertyName, StringValue) \
+ PropertyName ## _.Set( \
+ isolate, \
+ String::NewFromOneByte( \
+ isolate, \
+ reinterpret_cast(StringValue), \
+ v8::NewStringType::kInternalized, \
+ sizeof(StringValue) - 1).ToLocalChecked());
+ PER_ISOLATE_STRING_PROPERTIES(V)
+#undef V
}
IsolateData::~IsolateData() {
diff --git a/src/inspector_agent.cc b/src/inspector_agent.cc
index e143d316d2e6fa..4e0c04a7b95527 100644
--- a/src/inspector_agent.cc
+++ b/src/inspector_agent.cc
@@ -189,9 +189,9 @@ const int CONTEXT_GROUP_ID = 1;
class ChannelImpl final : public v8_inspector::V8Inspector::Channel {
public:
- explicit ChannelImpl(V8Inspector* inspector,
- InspectorSessionDelegate* delegate)
- : delegate_(delegate) {
+ explicit ChannelImpl(const std::unique_ptr& inspector,
+ std::unique_ptr delegate)
+ : delegate_(std::move(delegate)) {
session_ = inspector->connect(1, this, StringView());
}
@@ -201,19 +201,11 @@ class ChannelImpl final : public v8_inspector::V8Inspector::Channel {
session_->dispatchProtocolMessage(message);
}
- bool waitForFrontendMessage() {
- return delegate_->WaitForFrontendMessageWhilePaused();
- }
-
void schedulePauseOnNextStatement(const std::string& reason) {
std::unique_ptr buffer = Utf8ToStringView(reason);
session_->schedulePauseOnNextStatement(buffer->string(), buffer->string());
}
- InspectorSessionDelegate* delegate() {
- return delegate_;
- }
-
private:
void sendResponse(
int callId,
@@ -232,7 +224,7 @@ class ChannelImpl final : public v8_inspector::V8Inspector::Channel {
delegate_->SendMessageToFrontend(message);
}
- InspectorSessionDelegate* const delegate_;
+ std::unique_ptr delegate_;
std::unique_ptr session_;
};
@@ -300,8 +292,7 @@ class InspectorTimerHandle {
class NodeInspectorClient : public V8InspectorClient {
public:
NodeInspectorClient(node::Environment* env, node::NodePlatform* platform)
- : env_(env), platform_(platform), terminated_(false),
- running_nested_loop_(false) {
+ : env_(env), platform_(platform) {
client_ = V8Inspector::create(env->isolate(), this);
// TODO(bnoordhuis) Make name configurable from src/node.cc.
ContextInfo info(GetHumanReadableProcessName());
@@ -310,18 +301,28 @@ class NodeInspectorClient : public V8InspectorClient {
}
void runMessageLoopOnPause(int context_group_id) override {
- CHECK_NE(channel_, nullptr);
+ runMessageLoop(false);
+ }
+
+ void runMessageLoop(bool ignore_terminated) {
if (running_nested_loop_)
return;
terminated_ = false;
running_nested_loop_ = true;
- while (!terminated_ && channel_->waitForFrontendMessage()) {
- platform_->FlushForegroundTasks(env_->isolate());
+ while ((ignore_terminated || !terminated_) && waitForFrontendEvent()) {
+ while (platform_->FlushForegroundTasks(env_->isolate())) {}
}
terminated_ = false;
running_nested_loop_ = false;
}
+ bool waitForFrontendEvent() {
+ InspectorIo* io = env_->inspector_agent()->io();
+ if (io == nullptr)
+ return false;
+ return io->WaitForFrontendEvent();
+ }
+
double currentTimeMS() override {
return uv_hrtime() * 1.0 / NANOS_PER_MSEC;
}
@@ -363,20 +364,23 @@ class NodeInspectorClient : public V8InspectorClient {
terminated_ = true;
}
- void connectFrontend(InspectorSessionDelegate* delegate) {
- CHECK_EQ(channel_, nullptr);
- channel_ = std::unique_ptr(
- new ChannelImpl(client_.get(), delegate));
+ int connectFrontend(std::unique_ptr delegate) {
+ events_dispatched_ = true;
+ int session_id = next_session_id_++;
+ // TODO(addaleax): Revert back to using make_unique once we get issues
+ // with CI resolved (i.e. revert the patch that added this comment).
+ channels_[session_id].reset(new ChannelImpl(client_, std::move(delegate)));
+ return session_id;
}
- void disconnectFrontend() {
- quitMessageLoopOnPause();
- channel_.reset();
+ void disconnectFrontend(int session_id) {
+ events_dispatched_ = true;
+ channels_.erase(session_id);
}
- void dispatchMessageFromFrontend(const StringView& message) {
- CHECK_NE(channel_, nullptr);
- channel_->dispatchProtocolMessage(message);
+ void dispatchMessageFromFrontend(int session_id, const StringView& message) {
+ events_dispatched_ = true;
+ channels_[session_id]->dispatchProtocolMessage(message);
}
Local ensureDefaultContextInGroup(int contextGroupId) override {
@@ -426,10 +430,6 @@ class NodeInspectorClient : public V8InspectorClient {
script_id);
}
- ChannelImpl* channel() {
- return channel_.get();
- }
-
void startRepeatingTimer(double interval_s,
TimerCallback callback,
void* data) override {
@@ -464,20 +464,31 @@ class NodeInspectorClient : public V8InspectorClient {
client_->allAsyncTasksCanceled();
}
+ void schedulePauseOnNextStatement(const std::string& reason) {
+ for (const auto& id_channel : channels_) {
+ id_channel.second->schedulePauseOnNextStatement(reason);
+ }
+ }
+
+ bool hasConnectedSessions() {
+ return !channels_.empty();
+ }
+
private:
node::Environment* env_;
node::NodePlatform* platform_;
- bool terminated_;
- bool running_nested_loop_;
+ bool terminated_ = false;
+ bool running_nested_loop_ = false;
std::unique_ptr client_;
- std::unique_ptr channel_;
+ std::unordered_map> channels_;
std::unordered_map timers_;
+ int next_session_id_ = 1;
+ bool events_dispatched_ = false;
};
Agent::Agent(Environment* env) : parent_env_(env),
client_(nullptr),
platform_(nullptr),
- enabled_(false),
pending_enable_async_hook_(false),
pending_disable_async_hook_(false) {}
@@ -491,7 +502,7 @@ bool Agent::Start(node::NodePlatform* platform, const char* path,
path_ = path == nullptr ? "" : path;
debug_options_ = options;
client_ =
- std::unique_ptr(
+ std::shared_ptr(
new NodeInspectorClient(parent_env_, platform));
platform_ = platform;
CHECK_EQ(0, uv_async_init(uv_default_loop(),
@@ -515,7 +526,6 @@ bool Agent::StartIoThread(bool wait_for_connect) {
CHECK_NE(client_, nullptr);
- enabled_ = true;
io_ = std::unique_ptr(
new InspectorIo(parent_env_, platform_, path_, debug_options_,
wait_for_connect));
@@ -554,13 +564,14 @@ void Agent::Stop() {
if (io_ != nullptr) {
io_->Stop();
io_.reset();
- enabled_ = false;
}
}
-void Agent::Connect(InspectorSessionDelegate* delegate) {
- enabled_ = true;
- client_->connectFrontend(delegate);
+std::unique_ptr Agent::Connect(
+ std::unique_ptr delegate) {
+ int session_id = client_->connectFrontend(std::move(delegate));
+ return std::unique_ptr(
+ new InspectorSession(session_id, client_));
}
void Agent::WaitForDisconnect() {
@@ -568,6 +579,11 @@ void Agent::WaitForDisconnect() {
client_->contextDestroyed(parent_env_->context());
if (io_ != nullptr) {
io_->WaitForDisconnect();
+ // There is a bug in V8 Inspector (https://crbug.com/834056) that
+ // calls V8InspectorClient::quitMessageLoopOnPause when a session
+ // disconnects. We are using this flag to ignore those calls so the message
+ // loop is spinning as long as there's a reason to expect inspector messages
+ client_->runMessageLoop(true);
}
}
@@ -578,33 +594,8 @@ void Agent::FatalException(Local error, Local message) {
WaitForDisconnect();
}
-void Agent::Dispatch(const StringView& message) {
- CHECK_NE(client_, nullptr);
- client_->dispatchMessageFromFrontend(message);
-}
-
-void Agent::Disconnect() {
- CHECK_NE(client_, nullptr);
- client_->disconnectFrontend();
-}
-
-void Agent::RunMessageLoop() {
- CHECK_NE(client_, nullptr);
- client_->runMessageLoopOnPause(CONTEXT_GROUP_ID);
-}
-
-InspectorSessionDelegate* Agent::delegate() {
- CHECK_NE(client_, nullptr);
- ChannelImpl* channel = client_->channel();
- if (channel == nullptr)
- return nullptr;
- return channel->delegate();
-}
-
void Agent::PauseOnNextJavascriptStatement(const std::string& reason) {
- ChannelImpl* channel = client_->channel();
- if (channel != nullptr)
- channel->schedulePauseOnNextStatement(reason);
+ client_->schedulePauseOnNextStatement(reason);
}
void Agent::RegisterAsyncHook(Isolate* isolate,
@@ -699,5 +690,20 @@ bool Agent::IsWaitingForConnect() {
return debug_options_.wait_for_connect();
}
+bool Agent::HasConnectedSessions() {
+ return client_->hasConnectedSessions();
+}
+
+InspectorSession::InspectorSession(int session_id,
+ std::shared_ptr client)
+ : session_id_(session_id), client_(client) {}
+
+InspectorSession::~InspectorSession() {
+ client_->disconnectFrontend(session_id_);
+}
+
+void InspectorSession::Dispatch(const StringView& message) {
+ client_->dispatchMessageFromFrontend(session_id_, message);
+}
} // namespace inspector
} // namespace node
diff --git a/src/inspector_agent.h b/src/inspector_agent.h
index 56fb407930fac5..64e4202ee88e13 100644
--- a/src/inspector_agent.h
+++ b/src/inspector_agent.h
@@ -23,18 +23,26 @@ class Environment;
struct ContextInfo;
namespace inspector {
+class InspectorIo;
+class NodeInspectorClient;
+
+class InspectorSession {
+ public:
+ InspectorSession(int session_id, std::shared_ptr client);
+ ~InspectorSession();
+ void Dispatch(const v8_inspector::StringView& message);
+ private:
+ int session_id_;
+ std::shared_ptr client_;
+};
class InspectorSessionDelegate {
public:
virtual ~InspectorSessionDelegate() = default;
- virtual bool WaitForFrontendMessageWhilePaused() = 0;
virtual void SendMessageToFrontend(const v8_inspector::StringView& message)
= 0;
};
-class InspectorIo;
-class NodeInspectorClient;
-
class Agent {
public:
explicit Agent(node::Environment* env);
@@ -66,19 +74,19 @@ class Agent {
void RegisterAsyncHook(v8::Isolate* isolate,
v8::Local enable_function,
v8::Local disable_function);
+ void EnableAsyncHook();
+ void DisableAsyncHook();
- // These methods are called by the WS protocol and JS binding to create
- // inspector sessions. The inspector responds by using the delegate to send
- // messages back.
- void Connect(InspectorSessionDelegate* delegate);
- void Disconnect();
- void Dispatch(const v8_inspector::StringView& message);
- InspectorSessionDelegate* delegate();
+ // Called by the WS protocol and JS binding to create inspector sessions.
+ // The inspector responds by using the delegate to send messages back.
+ std::unique_ptr Connect(
+ std::unique_ptr delegate);
- void RunMessageLoop();
- bool enabled() { return enabled_; }
void PauseOnNextJavascriptStatement(const std::string& reason);
+ // Returns true as long as there is at least one connected session.
+ bool HasConnectedSessions();
+
InspectorIo* io() {
return io_.get();
}
@@ -92,18 +100,14 @@ class Agent {
DebugOptions& options() { return debug_options_; }
void ContextCreated(v8::Local context, const ContextInfo& info);
- void EnableAsyncHook();
- void DisableAsyncHook();
-
private:
void ToggleAsyncHook(v8::Isolate* isolate,
const Persistent& fn);
node::Environment* parent_env_;
- std::unique_ptr client_;
+ std::shared_ptr client_;
std::unique_ptr io_;
v8::Platform* platform_;
- bool enabled_;
std::string path_;
DebugOptions debug_options_;
diff --git a/src/inspector_io.cc b/src/inspector_io.cc
index 01ddc296b08693..38d88d7ab890c9 100644
--- a/src/inspector_io.cc
+++ b/src/inspector_io.cc
@@ -122,11 +122,11 @@ std::unique_ptr Utf8ToStringView(const std::string& message) {
class IoSessionDelegate : public InspectorSessionDelegate {
public:
- explicit IoSessionDelegate(InspectorIo* io) : io_(io) { }
- bool WaitForFrontendMessageWhilePaused() override;
+ explicit IoSessionDelegate(InspectorIo* io, int id) : io_(io), id_(id) { }
void SendMessageToFrontend(const v8_inspector::StringView& message) override;
private:
InspectorIo* io_;
+ int id_;
};
// Passed to InspectorSocketServer to handle WS inspector protocol events,
@@ -190,8 +190,7 @@ InspectorIo::InspectorIo(Environment* env, v8::Platform* platform,
: options_(options), thread_(), delegate_(nullptr),
state_(State::kNew), parent_env_(env),
thread_req_(), platform_(platform),
- dispatching_messages_(false), session_id_(0),
- script_name_(path),
+ dispatching_messages_(false), script_name_(path),
wait_for_connect_(wait_for_connect), port_(-1) {
main_thread_req_ = new AsyncAndAgent({uv_async_t(), env->inspector_agent()});
CHECK_EQ(0, uv_async_init(env->event_loop(), &main_thread_req_->first,
@@ -222,7 +221,7 @@ bool InspectorIo::Start() {
}
void InspectorIo::Stop() {
- CHECK(state_ == State::kAccepting || state_ == State::kConnected);
+ CHECK(state_ == State::kAccepting || !sessions_.empty());
Write(TransportAction::kKill, 0, StringView());
int err = uv_thread_join(&thread_);
CHECK_EQ(err, 0);
@@ -237,12 +236,11 @@ bool InspectorIo::IsStarted() {
void InspectorIo::WaitForDisconnect() {
if (state_ == State::kAccepting)
state_ = State::kDone;
- if (state_ == State::kConnected) {
+ if (!sessions_.empty()) {
state_ = State::kShutDown;
Write(TransportAction::kStop, 0, StringView());
fprintf(stderr, "Waiting for the debugger to disconnect...\n");
fflush(stderr);
- parent_env_->inspector_agent()->RunMessageLoop();
}
}
@@ -348,45 +346,23 @@ void InspectorIo::PostIncomingMessage(InspectorAction action, int session_id,
isolate->RequestInterrupt(InterruptCallback, agent);
CHECK_EQ(0, uv_async_send(&main_thread_req_->first));
}
- NotifyMessageReceived();
+ Mutex::ScopedLock scoped_lock(state_lock_);
+ incoming_message_cond_.Broadcast(scoped_lock);
}
std::vector InspectorIo::GetTargetIds() const {
return delegate_ ? delegate_->GetTargetIds() : std::vector();
}
-void InspectorIo::WaitForFrontendMessageWhilePaused() {
- dispatching_messages_ = false;
- Mutex::ScopedLock scoped_lock(state_lock_);
- if (incoming_message_queue_.empty())
- incoming_message_cond_.Wait(scoped_lock);
-}
-
-void InspectorIo::NotifyMessageReceived() {
- Mutex::ScopedLock scoped_lock(state_lock_);
- incoming_message_cond_.Broadcast(scoped_lock);
-}
-
TransportAction InspectorIo::Attach(int session_id) {
Agent* agent = parent_env_->inspector_agent();
- if (agent->delegate() != nullptr)
- return TransportAction::kDeclineSession;
-
- CHECK_EQ(session_delegate_, nullptr);
- session_id_ = session_id;
- state_ = State::kConnected;
fprintf(stderr, "Debugger attached.\n");
- session_delegate_ = std::unique_ptr(
- new IoSessionDelegate(this));
- agent->Connect(session_delegate_.get());
+ sessions_[session_id] = agent->Connect(std::unique_ptr(
+ new IoSessionDelegate(this, session_id)));
return TransportAction::kAcceptSession;
}
void InspectorIo::DispatchMessages() {
- // This function can be reentered if there was an incoming message while
- // V8 was processing another inspector request (e.g. if the user is
- // evaluating a long-running JS code snippet). This can happen only at
- // specific points (e.g. the lines that call inspector_ methods)
if (dispatching_messages_)
return;
dispatching_messages_ = true;
@@ -409,17 +385,20 @@ void InspectorIo::DispatchMessages() {
Attach(id);
break;
case InspectorAction::kEndSession:
- CHECK_NE(session_delegate_, nullptr);
+ sessions_.erase(id);
+ if (!sessions_.empty())
+ continue;
if (state_ == State::kShutDown) {
state_ = State::kDone;
} else {
state_ = State::kAccepting;
}
- parent_env_->inspector_agent()->Disconnect();
- session_delegate_.reset();
break;
case InspectorAction::kSendMessage:
- parent_env_->inspector_agent()->Dispatch(message);
+ auto session = sessions_.find(id);
+ if (session != sessions_.end() && session->second) {
+ session->second->Dispatch(message);
+ }
break;
}
}
@@ -445,6 +424,20 @@ void InspectorIo::Write(TransportAction action, int session_id,
CHECK_EQ(0, err);
}
+bool InspectorIo::WaitForFrontendEvent() {
+ // We allow DispatchMessages reentry as we enter the pause. This is important
+ // to support debugging the code invoked by an inspector call, such
+ // as Runtime.evaluate
+ dispatching_messages_ = false;
+ Mutex::ScopedLock scoped_lock(state_lock_);
+ if (sessions_.empty())
+ return false;
+ if (dispatching_message_queue_.empty() && incoming_message_queue_.empty()) {
+ incoming_message_cond_.Wait(scoped_lock);
+ }
+ return true;
+}
+
InspectorIoDelegate::InspectorIoDelegate(InspectorIo* io,
const std::string& script_path,
const std::string& script_name,
@@ -502,14 +495,9 @@ std::string InspectorIoDelegate::GetTargetUrl(const std::string& id) {
return "file://" + script_path_;
}
-bool IoSessionDelegate::WaitForFrontendMessageWhilePaused() {
- io_->WaitForFrontendMessageWhilePaused();
- return true;
-}
-
void IoSessionDelegate::SendMessageToFrontend(
const v8_inspector::StringView& message) {
- io_->Write(TransportAction::kSendMessage, io_->session_id_, message);
+ io_->Write(TransportAction::kSendMessage, id_, message);
}
} // namespace inspector
diff --git a/src/inspector_io.h b/src/inspector_io.h
index 79ccc6095ffec3..276c78056cb0a1 100644
--- a/src/inspector_io.h
+++ b/src/inspector_io.h
@@ -7,6 +7,7 @@
#include "uv.h"
#include
+#include