Skip to content

Commit db706da

Browse files
ronagTrott
authored andcommitted
stream: disallow stream methods on finished stream
Invoke callback with ERR_STREAM_ALREADY_FINISHED error if `end()` is called on a finished stream. PR-URL: #28687 Refs: #28667 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Luigi Pinca <luigipinca@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com> Reviewed-By: Rich Trott <rtrott@gmail.com>
1 parent 85898e0 commit db706da

6 files changed

+72
-0
lines changed

doc/api/errors.md

+6
Original file line numberDiff line numberDiff line change
@@ -1703,6 +1703,12 @@ An attempt was made to call [`stream.pipe()`][] on a [`Writable`][] stream.
17031703
A stream method was called that cannot complete because the stream was
17041704
destroyed using `stream.destroy()`.
17051705

1706+
<a id="ERR_STREAM_ALREADY_FINISHED"></a>
1707+
### ERR_STREAM_ALREADY_FINISHED
1708+
1709+
A stream method was called that cannot complete because the stream was
1710+
finished.
1711+
17061712
<a id="ERR_STREAM_NULL_VALUES"></a>
17071713
### ERR_STREAM_NULL_VALUES
17081714

lib/_http_outgoing.js

+8
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ const {
4646
ERR_INVALID_CHAR,
4747
ERR_METHOD_NOT_IMPLEMENTED,
4848
ERR_STREAM_CANNOT_PIPE,
49+
ERR_STREAM_ALREADY_FINISHED,
4950
ERR_STREAM_WRITE_AFTER_END
5051
},
5152
hideStackFrames
@@ -704,6 +705,13 @@ OutgoingMessage.prototype.end = function end(chunk, encoding, callback) {
704705
}
705706

706707
if (this.finished) {
708+
if (typeof callback === 'function') {
709+
if (!this.writableFinished) {
710+
this.on('finish', callback);
711+
} else {
712+
callback(new ERR_STREAM_ALREADY_FINISHED('end'));
713+
}
714+
}
707715
return this;
708716
}
709717

lib/_stream_writable.js

+8
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ const {
4141
ERR_MULTIPLE_CALLBACK,
4242
ERR_STREAM_CANNOT_PIPE,
4343
ERR_STREAM_DESTROYED,
44+
ERR_STREAM_ALREADY_FINISHED,
4445
ERR_STREAM_NULL_VALUES,
4546
ERR_STREAM_WRITE_AFTER_END,
4647
ERR_UNKNOWN_ENCODING
@@ -591,6 +592,13 @@ Writable.prototype.end = function(chunk, encoding, cb) {
591592
// Ignore unnecessary end() calls.
592593
if (!state.ending)
593594
endWritable(this, state, cb);
595+
else if (typeof cb === 'function') {
596+
if (!state.finished) {
597+
this.once('finish', cb);
598+
} else {
599+
cb(new ERR_STREAM_ALREADY_FINISHED('end'));
600+
}
601+
}
594602

595603
return this;
596604
};

lib/internal/errors.js

+3
Original file line numberDiff line numberDiff line change
@@ -1128,6 +1128,9 @@ E('ERR_SOCKET_DGRAM_NOT_RUNNING', 'Not running', Error);
11281128
E('ERR_SRI_PARSE',
11291129
'Subresource Integrity string %s had an unexpected at %d',
11301130
SyntaxError);
1131+
E('ERR_STREAM_ALREADY_FINISHED',
1132+
'Cannot call %s after a stream was finished',
1133+
Error);
11311134
E('ERR_STREAM_CANNOT_PIPE', 'Cannot pipe, not readable', Error);
11321135
E('ERR_STREAM_DESTROYED', 'Cannot call %s after a stream was destroyed', Error);
11331136
E('ERR_STREAM_NULL_VALUES', 'May not write null values to stream', TypeError);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
'use strict';
2+
const common = require('../common');
3+
const assert = require('assert');
4+
const http = require('http');
5+
6+
const server = http.createServer(common.mustCall(function(req, res) {
7+
res.end('testing ended state', common.mustCall());
8+
res.end(common.mustCall());
9+
res.on('finish', common.mustCall(() => {
10+
res.end(common.mustCall((err) => {
11+
assert.strictEqual(err.code, 'ERR_STREAM_ALREADY_FINISHED');
12+
server.close();
13+
}));
14+
}));
15+
}));
16+
17+
server.listen(0);
18+
19+
server.on('listening', common.mustCall(function() {
20+
http
21+
.request({
22+
port: server.address().port,
23+
method: 'GET',
24+
path: '/'
25+
})
26+
.end();
27+
}));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
5+
const assert = require('assert');
6+
const stream = require('stream');
7+
8+
const writable = new stream.Writable();
9+
10+
writable._write = (chunk, encoding, cb) => {
11+
setTimeout(() => cb(), 10);
12+
};
13+
14+
writable.end('testing ended state', common.mustCall());
15+
writable.end(common.mustCall());
16+
writable.on('finish', common.mustCall(() => {
17+
writable.end(common.mustCall((err) => {
18+
assert.strictEqual(err.code, 'ERR_STREAM_ALREADY_FINISHED');
19+
}));
20+
}));

0 commit comments

Comments
 (0)