Skip to content

Commit 46e0a55

Browse files
committed
stream: add type and range check for highWaterMark
The (h|readableH|writableH)ighWaterMark options should only permit positive numbers and zero. PR-URL: #18098 Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Michaël Zasso <targos@protonmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
1 parent e0864e5 commit 46e0a55

File tree

6 files changed

+70
-31
lines changed

6 files changed

+70
-31
lines changed

lib/_stream_readable.js

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const util = require('util');
3131
const debug = util.debuglog('stream');
3232
const BufferList = require('internal/streams/BufferList');
3333
const destroyImpl = require('internal/streams/destroy');
34+
const { getHighWaterMark } = require('internal/streams/state');
3435
const errors = require('internal/errors');
3536
const ReadableAsyncIterator = require('internal/streams/async_iterator');
3637
const { emitExperimentalWarning } = require('internal/util');
@@ -77,19 +78,8 @@ function ReadableState(options, stream) {
7778

7879
// the point at which it stops calling _read() to fill the buffer
7980
// Note: 0 is a valid value, means "don't call _read preemptively ever"
80-
var hwm = options.highWaterMark;
81-
var readableHwm = options.readableHighWaterMark;
82-
var defaultHwm = this.objectMode ? 16 : 16 * 1024;
83-
84-
if (hwm || hwm === 0)
85-
this.highWaterMark = hwm;
86-
else if (isDuplex && (readableHwm || readableHwm === 0))
87-
this.highWaterMark = readableHwm;
88-
else
89-
this.highWaterMark = defaultHwm;
90-
91-
// cast to ints.
92-
this.highWaterMark = Math.floor(this.highWaterMark);
81+
this.highWaterMark = getHighWaterMark(this, options, 'readableHighWaterMark',
82+
isDuplex);
9383

9484
// A linked list is used to store data chunks instead of an array because the
9585
// linked list can remove elements from the beginning faster than

lib/_stream_writable.js

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ const internalUtil = require('internal/util');
3333
const Stream = require('stream');
3434
const { Buffer } = require('buffer');
3535
const destroyImpl = require('internal/streams/destroy');
36+
const { getHighWaterMark } = require('internal/streams/state');
3637
const errors = require('internal/errors');
3738

3839
util.inherits(Writable, Stream);
@@ -59,19 +60,8 @@ function WritableState(options, stream) {
5960
// the point at which write() starts returning false
6061
// Note: 0 is a valid value, means that we always return false if
6162
// the entire buffer is not flushed immediately on write()
62-
var hwm = options.highWaterMark;
63-
var writableHwm = options.writableHighWaterMark;
64-
var defaultHwm = this.objectMode ? 16 : 16 * 1024;
65-
66-
if (hwm || hwm === 0)
67-
this.highWaterMark = hwm;
68-
else if (isDuplex && (writableHwm || writableHwm === 0))
69-
this.highWaterMark = writableHwm;
70-
else
71-
this.highWaterMark = defaultHwm;
72-
73-
// cast to ints.
74-
this.highWaterMark = Math.floor(this.highWaterMark);
63+
this.highWaterMark = getHighWaterMark(this, options, 'writableHighWaterMark',
64+
isDuplex);
7565

7666
// if _final has been called
7767
this.finalCalled = false;

lib/internal/streams/state.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
'use strict';
2+
3+
const errors = require('internal/errors');
4+
5+
function getHighWaterMark(state, options, duplexKey, isDuplex) {
6+
let hwm = options.highWaterMark;
7+
if (hwm != null) {
8+
if (typeof hwm !== 'number' || !(hwm >= 0))
9+
throw new errors.TypeError('ERR_INVALID_OPT_VALUE', 'highWaterMark', hwm);
10+
return Math.floor(hwm);
11+
} else if (isDuplex) {
12+
hwm = options[duplexKey];
13+
if (hwm != null) {
14+
if (typeof hwm !== 'number' || !(hwm >= 0))
15+
throw new errors.TypeError('ERR_INVALID_OPT_VALUE', duplexKey, hwm);
16+
return Math.floor(hwm);
17+
}
18+
}
19+
20+
// Default value
21+
return state.objectMode ? 16 : 16 * 1024;
22+
}
23+
24+
module.exports = {
25+
getHighWaterMark
26+
};

node.gyp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@
144144
'lib/internal/streams/duplexpair.js',
145145
'lib/internal/streams/legacy.js',
146146
'lib/internal/streams/destroy.js',
147+
'lib/internal/streams/state.js',
147148
'lib/internal/wrap_js_stream.js',
148149
'deps/v8/tools/splaytree.js',
149150
'deps/v8/tools/codemap.js',

test/parallel/test-stream-transform-split-highwatermark.js

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
'use strict';
2-
require('../common');
2+
const common = require('../common');
33
const assert = require('assert');
44

55
const { Transform, Readable, Writable } = require('stream');
@@ -54,14 +54,33 @@ testTransform(0, 0, {
5454
writableHighWaterMark: 777,
5555
});
5656

57-
// test undefined, null, NaN
58-
[undefined, null, NaN].forEach((v) => {
57+
// test undefined, null
58+
[undefined, null].forEach((v) => {
5959
testTransform(DEFAULT, DEFAULT, { readableHighWaterMark: v });
6060
testTransform(DEFAULT, DEFAULT, { writableHighWaterMark: v });
6161
testTransform(666, DEFAULT, { highWaterMark: v, readableHighWaterMark: 666 });
6262
testTransform(DEFAULT, 777, { highWaterMark: v, writableHighWaterMark: 777 });
6363
});
6464

65+
// test NaN
66+
{
67+
common.expectsError(() => {
68+
new Transform({ readableHighWaterMark: NaN });
69+
}, {
70+
type: TypeError,
71+
code: 'ERR_INVALID_OPT_VALUE',
72+
message: 'The value "NaN" is invalid for option "readableHighWaterMark"'
73+
});
74+
75+
common.expectsError(() => {
76+
new Transform({ writableHighWaterMark: NaN });
77+
}, {
78+
type: TypeError,
79+
code: 'ERR_INVALID_OPT_VALUE',
80+
message: 'The value "NaN" is invalid for option "writableHighWaterMark"'
81+
});
82+
}
83+
6584
// test non Duplex streams ignore the options
6685
{
6786
const r = new Readable({ readableHighWaterMark: 666 });

test/parallel/test-streams-highwatermark.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
'use strict';
2-
require('../common');
2+
const common = require('../common');
33

44
// This test ensures that the stream implementation correctly handles values
5-
// for highWaterMark which exceed the range of signed 32 bit integers.
5+
// for highWaterMark which exceed the range of signed 32 bit integers and
6+
// rejects invalid values.
67

78
const assert = require('assert');
89
const stream = require('stream');
@@ -16,3 +17,15 @@ assert.strictEqual(readable._readableState.highWaterMark, ovfl);
1617

1718
const writable = stream.Writable({ highWaterMark: ovfl });
1819
assert.strictEqual(writable._writableState.highWaterMark, ovfl);
20+
21+
for (const invalidHwm of [true, false, '5', {}, -5, NaN]) {
22+
for (const type of [stream.Readable, stream.Writable]) {
23+
common.expectsError(() => {
24+
type({ highWaterMark: invalidHwm });
25+
}, {
26+
type: TypeError,
27+
code: 'ERR_INVALID_OPT_VALUE',
28+
message: `The value "${invalidHwm}" is invalid for option "highWaterMark"`
29+
});
30+
}
31+
}

0 commit comments

Comments
 (0)