From 97fea3d703d7540c6b87cfa3c056de1afc8b1832 Mon Sep 17 00:00:00 2001 From: Robert Nagy Date: Sat, 13 Jul 2019 14:04:37 +0200 Subject: [PATCH] http: end with data can cause write after end Calling end() with data while ending should trigger a write after end error. PR-URL: https://github.com/nodejs/node/pull/28666 Reviewed-By: Luigi Pinca Reviewed-By: Benjamin Gruenbaum Reviewed-By: James M Snell Reviewed-By: Trivikram Kamat Reviewed-By: Matteo Collina Reviewed-By: Rich Trott --- lib/_http_outgoing.js | 47 +++++++++++-------- .../test-http-server-write-end-after-end.js | 27 +++++++++++ 2 files changed, 54 insertions(+), 20 deletions(-) create mode 100644 test/parallel/test-http-server-write-end-after-end.js diff --git a/lib/_http_outgoing.js b/lib/_http_outgoing.js index c1f277da3fff57..b8ee0b67689003 100644 --- a/lib/_http_outgoing.js +++ b/lib/_http_outgoing.js @@ -641,17 +641,20 @@ OutgoingMessage.prototype.write = function write(chunk, encoding, callback) { return ret; }; +function writeAfterEnd(msg, callback) { + const err = new ERR_STREAM_WRITE_AFTER_END(); + const triggerAsyncId = msg.socket ? msg.socket[async_id_symbol] : undefined; + defaultTriggerAsyncIdScope(triggerAsyncId, + process.nextTick, + writeAfterEndNT, + msg, + err, + callback); +} + function write_(msg, chunk, encoding, callback, fromEnd) { if (msg.finished) { - const err = new ERR_STREAM_WRITE_AFTER_END(); - const triggerAsyncId = msg.socket ? msg.socket[async_id_symbol] : undefined; - defaultTriggerAsyncIdScope(triggerAsyncId, - process.nextTick, - writeAfterEndNT, - msg, - err, - callback); - + writeAfterEnd(msg, callback); return true; } @@ -748,17 +751,6 @@ OutgoingMessage.prototype.end = function end(chunk, encoding, callback) { encoding = null; } - if (this.finished) { - if (typeof callback === 'function') { - if (!this.writableFinished) { - this.on('finish', callback); - } else { - callback(new ERR_STREAM_ALREADY_FINISHED('end')); - } - } - return this; - } - if (this.socket) { this.socket.cork(); } @@ -767,6 +759,12 @@ OutgoingMessage.prototype.end = function end(chunk, encoding, callback) { if (typeof chunk !== 'string' && !(chunk instanceof Buffer)) { throw new ERR_INVALID_ARG_TYPE('chunk', ['string', 'Buffer'], chunk); } + + if (this.finished) { + writeAfterEnd(this, callback); + return this; + } + if (!this._header) { if (typeof chunk === 'string') this._contentLength = Buffer.byteLength(chunk, encoding); @@ -774,6 +772,15 @@ OutgoingMessage.prototype.end = function end(chunk, encoding, callback) { this._contentLength = chunk.length; } write_(this, chunk, encoding, null, true); + } else if (this.finished) { + if (typeof callback === 'function') { + if (!this.writableFinished) { + this.on('finish', callback); + } else { + callback(new ERR_STREAM_ALREADY_FINISHED('end')); + } + } + return this; } else if (!this._header) { this._contentLength = 0; this._implicitHeader(); diff --git a/test/parallel/test-http-server-write-end-after-end.js b/test/parallel/test-http-server-write-end-after-end.js new file mode 100644 index 00000000000000..37fbe062f12b69 --- /dev/null +++ b/test/parallel/test-http-server-write-end-after-end.js @@ -0,0 +1,27 @@ +'use strict'; + +const common = require('../common'); +const http = require('http'); + +const server = http.createServer(handle); + +function handle(req, res) { + res.on('error', common.mustCall((err) => { + common.expectsError({ + code: 'ERR_STREAM_WRITE_AFTER_END', + name: 'Error' + })(err); + server.close(); + })); + + res.write('hello'); + res.end(); + + setImmediate(common.mustCall(() => { + res.end('world'); + })); +} + +server.listen(0, common.mustCall(() => { + http.get(`http://localhost:${server.address().port}`); +}));