Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit aaee9b1

Browse filesBrowse files
sidwebworksRafaelGSS
authored andcommitted
http: throw error on content-length mismatch
PR-URL: #44378 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Robert Nagy <ronagy@icloud.com>
1 parent 8e954fa commit aaee9b1
Copy full SHA for aaee9b1

File tree

Expand file treeCollapse file tree

7 files changed

+140
-8
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

7 files changed

+140
-8
lines changed
Open diff view settings
Collapse file

‎doc/api/errors.md‎

Copy file name to clipboardExpand all lines: doc/api/errors.md
+6Lines changed: 6 additions & 0 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -1325,6 +1325,12 @@ When using [`fs.cp()`][], `src` or `dest` pointed to an invalid path.
13251325

13261326
<a id="ERR_FS_CP_FIFO_PIPE"></a>
13271327

1328+
### `ERR_HTTP_CONTENT_LENGTH_MISMATCH`
1329+
1330+
Response body size doesn't match with the specified content-length header value.
1331+
1332+
<a id="ERR_HTTP_CONTENT_LENGTH_MISMATCH"></a>
1333+
13281334
### `ERR_FS_CP_FIFO_PIPE`
13291335

13301336
<!--
Collapse file

‎doc/api/http.md‎

Copy file name to clipboardExpand all lines: doc/api/http.md
+10-5Lines changed: 10 additions & 5 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -421,8 +421,12 @@ the data is read it will consume memory that can eventually lead to a
421421
For backward compatibility, `res` will only emit `'error'` if there is an
422422
`'error'` listener registered.
423423

424-
Node.js does not check whether Content-Length and the length of the
425-
body which has been transmitted are equal or not.
424+
Set `Content-Length` header to limit the response body size. Mismatching the
425+
`Content-Length` header value will result in an \[`Error`]\[] being thrown,
426+
identified by `code:` [`'ERR_HTTP_CONTENT_LENGTH_MISMATCH'`][].
427+
428+
`Content-Length` value should be in bytes, not characters. Use
429+
[`Buffer.byteLength()`][] to determine the length of the body in bytes.
426430

427431
### Event: `'abort'`
428432

@@ -2196,13 +2200,13 @@ const server = http.createServer((req, res) => {
21962200
});
21972201
```
21982202

2199-
`Content-Length` is given in bytes, not characters. Use
2203+
`Content-Length` is read in bytes, not characters. Use
22002204
[`Buffer.byteLength()`][] to determine the length of the body in bytes. Node.js
2201-
does not check whether `Content-Length` and the length of the body which has
2205+
will check whether `Content-Length` and the length of the body which has
22022206
been transmitted are equal or not.
22032207

22042208
Attempting to set a header field name or value that contains invalid characters
2205-
will result in a [`TypeError`][] being thrown.
2209+
will result in a \[`Error`]\[] being thrown.
22062210

22072211
### `response.writeProcessing()`
22082212

@@ -3626,6 +3630,7 @@ added: v18.8.0
36263630
Set the maximum number of idle HTTP parsers. **Default:** `1000`.
36273631

36283632
[RFC 8187]: https://www.rfc-editor.org/rfc/rfc8187.txt
3633+
[`'ERR_HTTP_CONTENT_LENGTH_MISMATCH'`]: errors.md#err_http_content_length_mismatch
36293634
[`'checkContinue'`]: #event-checkcontinue
36303635
[`'finish'`]: #event-finish
36313636
[`'request'`]: #event-request
Collapse file

‎lib/_http_outgoing.js‎

Copy file name to clipboardExpand all lines: lib/_http_outgoing.js
+40-1Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const {
2525
Array,
2626
ArrayIsArray,
2727
ArrayPrototypeJoin,
28+
MathAbs,
2829
MathFloor,
2930
NumberPrototypeToString,
3031
ObjectCreate,
@@ -57,6 +58,7 @@ const {
5758
} = require('internal/async_hooks');
5859
const {
5960
codes: {
61+
ERR_HTTP_CONTENT_LENGTH_MISMATCH,
6062
ERR_HTTP_HEADERS_SENT,
6163
ERR_HTTP_INVALID_HEADER_VALUE,
6264
ERR_HTTP_TRAILER_INVALID,
@@ -84,6 +86,8 @@ const HIGH_WATER_MARK = getDefaultHighWaterMark();
8486

8587
const kCorked = Symbol('corked');
8688
const kUniqueHeaders = Symbol('kUniqueHeaders');
89+
const kBytesWritten = Symbol('kBytesWritten');
90+
const kEndCalled = Symbol('kEndCalled');
8791

8892
const nop = () => {};
8993

@@ -123,6 +127,9 @@ function OutgoingMessage() {
123127
this._removedContLen = false;
124128
this._removedTE = false;
125129

130+
this.strictContentLength = false;
131+
this[kBytesWritten] = 0;
132+
this[kEndCalled] = false;
126133
this._contentLength = null;
127134
this._hasBody = true;
128135
this._trailer = '';
@@ -330,7 +337,9 @@ OutgoingMessage.prototype._send = function _send(data, encoding, callback) {
330337
// This is a shameful hack to get the headers and first body chunk onto
331338
// the same packet. Future versions of Node are going to take care of
332339
// this at a lower level and in a more general way.
333-
if (!this._headerSent) {
340+
if (!this._headerSent && this._header !== null) {
341+
// `this._header` can be null if OutgoingMessage is used without a proper Socket
342+
// See: /test/parallel/test-http-outgoing-message-inheritance.js
334343
if (typeof data === 'string' &&
335344
(encoding === 'utf8' || encoding === 'latin1' || !encoding)) {
336345
data = this._header + data;
@@ -349,6 +358,14 @@ OutgoingMessage.prototype._send = function _send(data, encoding, callback) {
349358
return this._writeRaw(data, encoding, callback);
350359
};
351360

361+
function _getMessageBodySize(chunk, headers, encoding) {
362+
if (Buffer.isBuffer(chunk)) return chunk.length;
363+
const chunkLength = chunk ? Buffer.byteLength(chunk, encoding) : 0;
364+
const headerLength = headers ? headers.length : 0;
365+
if (headerLength === chunkLength) return 0;
366+
if (headerLength < chunkLength) return MathAbs(chunkLength - headerLength);
367+
return chunkLength;
368+
}
352369

353370
OutgoingMessage.prototype._writeRaw = _writeRaw;
354371
function _writeRaw(data, encoding, callback) {
@@ -364,6 +381,25 @@ function _writeRaw(data, encoding, callback) {
364381
encoding = null;
365382
}
366383

384+
// TODO(sidwebworks): flip the `strictContentLength` default to `true` in a future PR
385+
if (this.strictContentLength && conn && conn.writable && !this._removedContLen && this._hasBody) {
386+
const skip = conn._httpMessage.statusCode === 304 || (this.hasHeader('transfer-encoding') || this.chunkedEncoding);
387+
388+
if (typeof this._contentLength === 'number' && !skip) {
389+
const size = _getMessageBodySize(data, conn._httpMessage._header, encoding);
390+
391+
if ((size + this[kBytesWritten]) > this._contentLength) {
392+
throw new ERR_HTTP_CONTENT_LENGTH_MISMATCH(size + this[kBytesWritten], this._contentLength);
393+
}
394+
395+
if (this[kEndCalled] && (size + this[kBytesWritten]) !== this._contentLength) {
396+
throw new ERR_HTTP_CONTENT_LENGTH_MISMATCH(size + this[kBytesWritten], this._contentLength);
397+
}
398+
399+
this[kBytesWritten] += size;
400+
}
401+
}
402+
367403
if (conn && conn._httpMessage === this && conn.writable) {
368404
// There might be pending data in the this.output buffer.
369405
if (this.outputData.length) {
@@ -559,6 +595,7 @@ function matchHeader(self, state, field, value) {
559595
break;
560596
case 'content-length':
561597
state.contLen = true;
598+
self._contentLength = value;
562599
self._removedContLen = false;
563600
break;
564601
case 'date':
@@ -923,6 +960,8 @@ OutgoingMessage.prototype.end = function end(chunk, encoding, callback) {
923960
encoding = null;
924961
}
925962

963+
this[kEndCalled] = true;
964+
926965
if (chunk) {
927966
if (this.finished) {
928967
onError(this,
Collapse file

‎lib/internal/errors.js‎

Copy file name to clipboardExpand all lines: lib/internal/errors.js
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1142,6 +1142,8 @@ E('ERR_HTTP2_TRAILERS_NOT_READY',
11421142
'Trailing headers cannot be sent until after the wantTrailers event is ' +
11431143
'emitted', Error);
11441144
E('ERR_HTTP2_UNSUPPORTED_PROTOCOL', 'protocol "%s" is unsupported.', Error);
1145+
E('ERR_HTTP_CONTENT_LENGTH_MISMATCH',
1146+
'Response body\'s content-length of %s byte(s) does not match the content-length of %s byte(s) set in header', Error);
11451147
E('ERR_HTTP_HEADERS_SENT',
11461148
'Cannot %s headers after they are sent to the client', Error);
11471149
E('ERR_HTTP_INVALID_HEADER_VALUE',
Collapse file
+80Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
const http = require('http');
6+
7+
function shouldThrowOnMoreBytes() {
8+
const server = http.createServer(common.mustCall((req, res) => {
9+
res.strictContentLength = true;
10+
res.setHeader('Content-Length', 5);
11+
res.write('hello');
12+
assert.throws(() => {
13+
res.write('a');
14+
}, {
15+
code: 'ERR_HTTP_CONTENT_LENGTH_MISMATCH'
16+
});
17+
res.statusCode = 200;
18+
res.end();
19+
}));
20+
21+
server.listen(0, () => {
22+
const req = http.get({
23+
port: server.address().port,
24+
}, common.mustCall((res) => {
25+
res.resume();
26+
assert.strictEqual(res.statusCode, 200);
27+
server.close();
28+
}));
29+
req.end();
30+
});
31+
}
32+
33+
function shouldNotThrow() {
34+
const server = http.createServer(common.mustCall((req, res) => {
35+
res.strictContentLength = true;
36+
res.write('helloaa');
37+
res.statusCode = 200;
38+
res.end('ending');
39+
}));
40+
41+
server.listen(0, () => {
42+
http.get({
43+
port: server.address().port,
44+
}, common.mustCall((res) => {
45+
res.resume();
46+
assert.strictEqual(res.statusCode, 200);
47+
server.close();
48+
}));
49+
});
50+
}
51+
52+
53+
function shouldThrowOnFewerBytes() {
54+
const server = http.createServer(common.mustCall((req, res) => {
55+
res.strictContentLength = true;
56+
res.setHeader('Content-Length', 5);
57+
res.write('a');
58+
res.statusCode = 200;
59+
assert.throws(() => {
60+
res.end();
61+
}, {
62+
code: 'ERR_HTTP_CONTENT_LENGTH_MISMATCH'
63+
});
64+
res.end('aaaa');
65+
}));
66+
67+
server.listen(0, () => {
68+
http.get({
69+
port: server.address().port,
70+
}, common.mustCall((res) => {
71+
res.resume();
72+
assert.strictEqual(res.statusCode, 200);
73+
server.close();
74+
}));
75+
});
76+
}
77+
78+
shouldThrowOnMoreBytes();
79+
shouldNotThrow();
80+
shouldThrowOnFewerBytes();
Collapse file

‎test/parallel/test-http-outgoing-properties.js‎

Copy file name to clipboardExpand all lines: test/parallel/test-http-outgoing-properties.js
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ const OutgoingMessage = http.OutgoingMessage;
4949
msg._implicitHeader = function() {};
5050
assert.strictEqual(msg.writableLength, 0);
5151
msg.write('asd');
52-
assert.strictEqual(msg.writableLength, 7);
52+
assert.strictEqual(msg.writableLength, 3);
5353
}
5454

5555
{
Collapse file

‎test/parallel/test-http-response-multi-content-length.js‎

Copy file name to clipboardExpand all lines: test/parallel/test-http-response-multi-content-length.js
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ function test(server) {
2424
{
2525
const server = http.createServer((req, res) => {
2626
res.setHeader('content-length', [2, 1]);
27-
res.end('ok');
27+
res.end('k');
2828
});
2929

3030
test(server);

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.