From 9b91467aac670809676cc80b101d732ee7c5cc50 Mon Sep 17 00:00:00 2001 From: Robert Nagy Date: Tue, 21 Jul 2020 20:53:07 +0200 Subject: [PATCH] http: don't write error to socket The state of the connection is unknown at this point and writing to it can corrupt client state before it is aware of an error. PR-URL: https://github.com/nodejs/node/pull/34465 Reviewed-By: Luigi Pinca Reviewed-By: Matteo Collina Reviewed-By: James M Snell --- doc/api/http.md | 4 +-- lib/_http_server.js | 2 +- test/parallel/test-http-header-badrequest.js | 31 ++++++++++++++++++++ 3 files changed, 34 insertions(+), 3 deletions(-) create mode 100644 test/parallel/test-http-header-badrequest.js diff --git a/doc/api/http.md b/doc/api/http.md index d94f791e462833..46067b777484ea 100644 --- a/doc/api/http.md +++ b/doc/api/http.md @@ -1071,8 +1071,8 @@ type other than {net.Socket}. Default behavior is to try close the socket with a HTTP '400 Bad Request', or a HTTP '431 Request Header Fields Too Large' in the case of a -[`HPE_HEADER_OVERFLOW`][] error. If the socket is not writable it is -immediately destroyed. +[`HPE_HEADER_OVERFLOW`][] error. If the socket is not writable or has already +written data it is immediately destroyed. `socket` is the [`net.Socket`][] object that the error originated from. diff --git a/lib/_http_server.js b/lib/_http_server.js index bef14bc880165c..c34f8092ecb6ce 100644 --- a/lib/_http_server.js +++ b/lib/_http_server.js @@ -599,7 +599,7 @@ function socketOnError(e) { this.on('error', noop); if (!this.server.emit('clientError', e, this)) { - if (this.writable) { + if (this.writable && this.bytesWritten === 0) { const response = e.code === 'HPE_HEADER_OVERFLOW' ? requestHeaderFieldsTooLargeResponse : badRequestResponse; this.write(response); diff --git a/test/parallel/test-http-header-badrequest.js b/test/parallel/test-http-header-badrequest.js new file mode 100644 index 00000000000000..8294ca9fe208ec --- /dev/null +++ b/test/parallel/test-http-header-badrequest.js @@ -0,0 +1,31 @@ +'use strict'; +const { mustCall } = require('../common'); +const assert = require('assert'); +const { createServer } = require('http'); +const { createConnection } = require('net'); + +const server = createServer(); + +server.on('request', mustCall((req, res) => { + res.write('asd', () => { + res.socket.emit('error', new Error('kaboom')); + }); +})); + +server.listen(0, mustCall(() => { + const c = createConnection(server.address().port); + let received = ''; + + c.on('connect', mustCall(() => { + c.write('GET /blah HTTP/1.1\r\n\r\n'); + })); + c.on('data', mustCall((data) => { + received += data.toString(); + })); + c.on('end', mustCall(() => { + // Should not include anything else after asd. + assert.strictEqual(received.indexOf('asd\r\n'), received.length - 5); + c.end(); + })); + c.on('close', mustCall(() => server.close())); +}));