Skip to content

Commit d29f5ef

Browse files
committed
http: rejecting invalid body writes configurable with option rejectNonStandardBodyWrites
1 parent 68a2fe6 commit d29f5ef

File tree

5 files changed

+104
-25
lines changed

5 files changed

+104
-25
lines changed

doc/api/errors.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1346,7 +1346,8 @@ Response body size doesn't match with the specified content-length header value.
13461346

13471347
### `ERR_HTTP_CONTENT_LENGTH_MISMATCH`
13481348

1349-
An error is thrown if when writing to an HTTP response which does not allow contents.
1349+
An error is thrown if when writing to an HTTP response which does not allow
1350+
contents.
13501351

13511352
<a id="ERR_HTTP_CONTENT_LENGTH_MISMATCH"></a>
13521353

lib/_http_outgoing.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ const kUniqueHeaders = Symbol('kUniqueHeaders');
8686
const kBytesWritten = Symbol('kBytesWritten');
8787
const kErrored = Symbol('errored');
8888
const kHighWaterMark = Symbol('kHighWaterMark');
89+
const kRejectNonStandardBodyWrites = Symbol('kRejectNonStandardBodyWrites');
8990

9091
const nop = () => {};
9192

@@ -151,6 +152,7 @@ function OutgoingMessage(options) {
151152

152153
this[kErrored] = null;
153154
this[kHighWaterMark] = options?.highWaterMark ?? getDefaultHighWaterMark();
155+
this[kRejectNonStandardBodyWrites] = options?.rejectNonStandardBodyWrites ?? true;
154156
}
155157
ObjectSetPrototypeOf(OutgoingMessage.prototype, Stream.prototype);
156158
ObjectSetPrototypeOf(OutgoingMessage, Stream);
@@ -886,7 +888,14 @@ function write_(msg, chunk, encoding, callback, fromEnd) {
886888
}
887889

888890
if (!msg._hasBody) {
889-
throw ERR_HTTP_BODY_NOT_ALLOWED();
891+
if (msg[kRejectNonStandardBodyWrites]) {
892+
throw new ERR_HTTP_BODY_NOT_ALLOWED();
893+
} else {
894+
debug('This type of response MUST NOT have a body. ' +
895+
'Ignoring write() calls.');
896+
process.nextTick(callback);
897+
return true;
898+
}
890899
}
891900

892901
if (err) {

lib/_http_server.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,14 @@ function storeHTTPOptions(options) {
484484
validateBoolean(joinDuplicateHeaders, 'options.joinDuplicateHeaders');
485485
}
486486
this.joinDuplicateHeaders = joinDuplicateHeaders;
487+
488+
const rejectNonStandardBodyWrites = options.rejectNonStandardBodyWrites;
489+
if (rejectNonStandardBodyWrites !== undefined) {
490+
validateBoolean(rejectNonStandardBodyWrites, 'options.rejectNonStandardBodyWrites');
491+
this.rejectNonStandardBodyWrites = rejectNonStandardBodyWrites;
492+
} else {
493+
this.rejectNonStandardBodyWrites = true
494+
}
487495
}
488496

489497
function setupConnectionsTracking(server) {
@@ -1020,7 +1028,11 @@ function parserOnIncoming(server, socket, state, req, keepAlive) {
10201028
}
10211029
}
10221030

1023-
const res = new server[kServerResponse](req, { highWaterMark: socket.writableHighWaterMark });
1031+
const res = new server[kServerResponse](req,
1032+
{
1033+
highWaterMark: socket.writableHighWaterMark,
1034+
rejectNonStandardBodyWrites: server.rejectNonStandardBodyWrites
1035+
});
10241036
res._keepAliveTimeout = server.keepAliveTimeout;
10251037
res._maxRequestsPerSocket = server.maxRequestsPerSocket;
10261038
res._onPendingData = updateOutgoingData.bind(undefined,

lib/http.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ let maxHeaderSize;
5555
* requireHostHeader?: boolean;
5656
* joinDuplicateHeaders?: boolean;
5757
* highWaterMark?: number;
58+
* rejectNonStandardBodyWrites?: boolean;
5859
* }} [opts]
5960
* @param {Function} [requestListener]
6061
* @returns {Server}

test/parallel/test-http-head-throw-on-response-body-write.js

Lines changed: 78 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -24,30 +24,86 @@ const common = require('../common');
2424
const assert = require('assert');
2525
const http = require('http');
2626

27+
// should be using common.mustCall on both req res callbacks provided to createServer
28+
{
29+
const server = http.createServer((req, res) => {
30+
assert.throws(() => {
31+
res.write('this is content');
32+
}, {
33+
code: 'ERR_HTTP_BODY_NOT_ALLOWED',
34+
name: 'Error',
35+
message: 'Adding content for this request method or response status is not allowed.'
36+
});
37+
res.end();
38+
});
39+
server.listen(0);
40+
41+
server.on('listening', common.mustCall(function() {
42+
const req = http.request({
43+
port: this.address().port,
44+
method: 'HEAD',
45+
path: '/'
46+
}, common.mustCall((res) => {
47+
res.resume();
48+
res.on('end', common.mustCall(function() {
49+
server.close();
50+
}));
51+
}));
52+
req.end();
53+
}));
54+
}
55+
56+
{
57+
const server = http.createServer({
58+
rejectNonStandardBodyWrites: true,
59+
}, (req, res) => {
60+
res.writeHead(204);
61+
assert.throws(() => {
62+
res.write('this is content');
63+
}, {
64+
code: 'ERR_HTTP_BODY_NOT_ALLOWED',
65+
name: 'Error',
66+
message: 'Adding content for this request method or response status is not allowed.'
67+
});
68+
res.end();
69+
});
70+
server.listen(0);
71+
72+
server.on('listening', common.mustCall(function() {
73+
const req = http.request({
74+
port: this.address().port,
75+
method: 'GET',
76+
path: '/'
77+
}, common.mustCall((res) => {
78+
res.resume();
79+
res.on('end', common.mustCall(function() {
80+
server.close();
81+
}));
82+
}));
83+
req.end();
84+
}));
85+
}
2786

28-
const server = http.createServer((req, res) => {
29-
res.writeHead(204);
30-
assert.throws(() => {
31-
res.write('this is content');
32-
}, {
33-
code: 'ERR_HTTP_BODY_NOT_ALLOWED',
34-
name: 'Error',
35-
message: 'Adding content for this request method or response status is not allowed.'
87+
{
88+
const server = http.createServer({
89+
rejectNonStandardBodyWrites: false,
90+
}, (req, res) => {
91+
res.writeHead(200);
92+
res.end('this is content');
3693
});
37-
res.end();
38-
});
39-
server.listen(0);
94+
server.listen(0);
4095

41-
server.on('listening', common.mustCall(function() {
42-
const req = http.request({
43-
port: this.address().port,
44-
method: 'GET',
45-
path: '/'
46-
}, common.mustCall((res) => {
47-
res.resume();
48-
res.on('end', common.mustCall(function() {
49-
server.close();
96+
server.on('listening', common.mustCall(function() {
97+
const req = http.request({
98+
port: this.address().port,
99+
method: 'HEAD',
100+
path: '/'
101+
}, common.mustCall((res) => {
102+
res.resume();
103+
res.on('end', common.mustCall(function() {
104+
server.close();
105+
}));
50106
}));
107+
req.end();
51108
}));
52-
req.end();
53-
}));
109+
}

0 commit comments

Comments
 (0)