Skip to content

Commit 4de2f0d

Browse files
committed
stream: validate options.signal in iter Writer methods
Signed-off-by: Sai Asish Y <say.apm35@gmail.com>
1 parent ab41cf0 commit 4de2f0d

4 files changed

Lines changed: 55 additions & 7 deletions

File tree

lib/internal/streams/iter/broadcast.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,7 @@ class BroadcastWriter {
532532
}
533533

534534
write(chunk, options) {
535+
validateAbortSignal(options?.signal, 'options.signal');
535536
// Fast path: no signal, writer open, buffer has space
536537
if (this.#canUseWriteFastPath(options)) {
537538
const converted = toUint8Array(chunk);
@@ -546,6 +547,7 @@ class BroadcastWriter {
546547
if (!ArrayIsArray(chunks)) {
547548
throw new ERR_INVALID_ARG_TYPE('chunks', 'Array', chunks);
548549
}
550+
validateAbortSignal(options?.signal, 'options.signal');
549551
// Fast path: no signal, writer open, buffer has space
550552
if (this.#canUseWriteFastPath(options)) {
551553
const converted = convertChunks(chunks);
@@ -623,6 +625,7 @@ class BroadcastWriter {
623625

624626
// end() is synchronous internally - signal accepted for interface compliance.
625627
end(options) {
628+
validateAbortSignal(options?.signal, 'options.signal');
626629
if (this.#isClosed()) return this.#closed;
627630
this.#closed = PromiseResolve(this.#totalBytes);
628631
this.#broadcast[kEnd]();

lib/internal/streams/iter/classic.js

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ const {
4242
} = require('internal/errors');
4343

4444
const {
45+
validateAbortSignal,
4546
validateInteger,
4647
validateObject,
4748
} = require('internal/validators');
@@ -572,10 +573,11 @@ function fromWritable(writable, options = kNullPrototype) {
572573
// as 'error' events caught by our generic error handler, rejecting
573574
// the next pending operation rather than the already-resolved one.
574575
//
575-
// The options.signal parameter from the Writer interface is ignored.
576-
// Classic stream.Writable has no per-write abort signal support;
577-
// cancellation should be handled at the pipeline level instead.
578-
write(chunk) {
576+
// The options.signal parameter from the Writer interface is validated
577+
// but otherwise ignored. Classic stream.Writable has no per-write abort
578+
// signal support; cancellation should be handled at the pipeline level.
579+
write(chunk, options) {
580+
validateAbortSignal(options?.signal, 'options.signal');
579581
if (!isWritable()) {
580582
return PromiseReject(new ERR_STREAM_WRITE_AFTER_END());
581583
}
@@ -617,10 +619,11 @@ function fromWritable(writable, options = kNullPrototype) {
617619
return PromiseResolve();
618620
},
619621

620-
writev(chunks) {
622+
writev(chunks, options) {
621623
if (!ArrayIsArray(chunks)) {
622624
throw new ERR_INVALID_ARG_TYPE('chunks', 'Array', chunks);
623625
}
626+
validateAbortSignal(options?.signal, 'options.signal');
624627
if (!isWritable()) {
625628
return PromiseReject(new ERR_STREAM_WRITE_AFTER_END());
626629
}
@@ -666,8 +669,9 @@ function fromWritable(writable, options = kNullPrototype) {
666669
return -1;
667670
},
668671

669-
// options.signal is ignored for the same reason as write().
670-
end() {
672+
// options.signal is validated but otherwise ignored, as in write().
673+
end(options) {
674+
validateAbortSignal(options?.signal, 'options.signal');
671675
if ((writable.writableFinished ?? false) ||
672676
(writable.destroyed ?? false)) {
673677
cleanup();

lib/internal/streams/iter/push.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,7 @@ class PushWriter {
565565
}
566566

567567
write(chunk, options) {
568+
validateAbortSignal(options?.signal, 'options.signal');
568569
if (!options?.signal && this.#queue.canWriteSync()) {
569570
const bytes = toUint8Array(chunk);
570571
this.#queue.writeSync([bytes]);
@@ -578,6 +579,7 @@ class PushWriter {
578579
if (!ArrayIsArray(chunks)) {
579580
throw new ERR_INVALID_ARG_TYPE('chunks', 'Array', chunks);
580581
}
582+
validateAbortSignal(options?.signal, 'options.signal');
581583
if (!options?.signal && this.#queue.canWriteSync()) {
582584
const bytes = convertChunks(chunks);
583585
this.#queue.writeSync(bytes);
@@ -601,6 +603,7 @@ class PushWriter {
601603
}
602604

603605
end(options) {
606+
validateAbortSignal(options?.signal, 'options.signal');
604607
const result = this.#queue.end();
605608
if (result === -2) {
606609
// Errored: reject with stored error

test/parallel/test-stream-iter-validation.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,4 +353,42 @@ async function testAsyncValidation() {
353353
assert.strictEqual(typeof transform.transform, 'function');
354354
}
355355

356+
// Writer methods validate options.signal on every surface
357+
{
358+
const { writer } = push();
359+
assert.throws(() => writer.write('a', { signal: 'bad' }),
360+
{ code: 'ERR_INVALID_ARG_TYPE' });
361+
assert.throws(() => writer.writev(['a'], { signal: {} }),
362+
{ code: 'ERR_INVALID_ARG_TYPE' });
363+
assert.throws(() => writer.end({ signal: 'bad' }),
364+
{ code: 'ERR_INVALID_ARG_TYPE' });
365+
// A valid signal and an absent signal are both accepted.
366+
writer.write('a', { signal: new AbortController().signal });
367+
writer.write('b');
368+
writer.endSync();
369+
}
370+
371+
{
372+
const { writer } = broadcast();
373+
assert.throws(() => writer.write('a', { signal: 'bad' }),
374+
{ code: 'ERR_INVALID_ARG_TYPE' });
375+
assert.throws(() => writer.writev(['a'], { signal: {} }),
376+
{ code: 'ERR_INVALID_ARG_TYPE' });
377+
assert.throws(() => writer.end({ signal: 'bad' }),
378+
{ code: 'ERR_INVALID_ARG_TYPE' });
379+
writer.endSync();
380+
}
381+
382+
{
383+
const { Writable } = require('stream');
384+
const { fromWritable } = require('stream/iter');
385+
const writer = fromWritable(new Writable({ write(chunk, enc, cb) { cb(); } }));
386+
assert.throws(() => writer.write('a', { signal: 'bad' }),
387+
{ code: 'ERR_INVALID_ARG_TYPE' });
388+
assert.throws(() => writer.writev(['a'], { signal: {} }),
389+
{ code: 'ERR_INVALID_ARG_TYPE' });
390+
assert.throws(() => writer.end({ signal: 'bad' }),
391+
{ code: 'ERR_INVALID_ARG_TYPE' });
392+
}
393+
356394
testAsyncValidation().then(common.mustCall());

0 commit comments

Comments
 (0)