mirror of
https://github.com/zebrajr/node.git
synced 2026-01-15 12:15:26 +00:00
http: throw error on content-length mismatch
PR-URL: https://github.com/nodejs/node/pull/44378 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Robert Nagy <ronagy@icloud.com>
This commit is contained in:
committed by
Antoine du Hamel
parent
84000f18ba
commit
91020db933
@@ -1325,6 +1325,12 @@ When using [`fs.cp()`][], `src` or `dest` pointed to an invalid path.
|
||||
|
||||
<a id="ERR_FS_CP_FIFO_PIPE"></a>
|
||||
|
||||
### `ERR_HTTP_CONTENT_LENGTH_MISMATCH`
|
||||
|
||||
Response body size doesn't match with the specified content-length header value.
|
||||
|
||||
<a id="ERR_HTTP_CONTENT_LENGTH_MISMATCH"></a>
|
||||
|
||||
### `ERR_FS_CP_FIFO_PIPE`
|
||||
|
||||
<!--
|
||||
|
||||
@@ -423,8 +423,12 @@ the data is read it will consume memory that can eventually lead to a
|
||||
For backward compatibility, `res` will only emit `'error'` if there is an
|
||||
`'error'` listener registered.
|
||||
|
||||
Node.js does not check whether Content-Length and the length of the
|
||||
body which has been transmitted are equal or not.
|
||||
Set `Content-Length` header to limit the response body size. Mismatching the
|
||||
`Content-Length` header value will result in an \[`Error`]\[] being thrown,
|
||||
identified by `code:` [`'ERR_HTTP_CONTENT_LENGTH_MISMATCH'`][].
|
||||
|
||||
`Content-Length` value should be in bytes, not characters. Use
|
||||
[`Buffer.byteLength()`][] to determine the length of the body in bytes.
|
||||
|
||||
### Event: `'abort'`
|
||||
|
||||
@@ -2240,13 +2244,13 @@ const server = http.createServer((req, res) => {
|
||||
});
|
||||
```
|
||||
|
||||
`Content-Length` is given in bytes, not characters. Use
|
||||
`Content-Length` is read in bytes, not characters. Use
|
||||
[`Buffer.byteLength()`][] to determine the length of the body in bytes. Node.js
|
||||
does not check whether `Content-Length` and the length of the body which has
|
||||
will check whether `Content-Length` and the length of the body which has
|
||||
been transmitted are equal or not.
|
||||
|
||||
Attempting to set a header field name or value that contains invalid characters
|
||||
will result in a [`TypeError`][] being thrown.
|
||||
will result in a \[`Error`]\[] being thrown.
|
||||
|
||||
### `response.writeProcessing()`
|
||||
|
||||
@@ -3683,6 +3687,7 @@ added: v18.8.0
|
||||
Set the maximum number of idle HTTP parsers. **Default:** `1000`.
|
||||
|
||||
[RFC 8187]: https://www.rfc-editor.org/rfc/rfc8187.txt
|
||||
[`'ERR_HTTP_CONTENT_LENGTH_MISMATCH'`]: errors.md#err_http_content_length_mismatch
|
||||
[`'checkContinue'`]: #event-checkcontinue
|
||||
[`'finish'`]: #event-finish
|
||||
[`'request'`]: #event-request
|
||||
|
||||
@@ -25,6 +25,7 @@ const {
|
||||
Array,
|
||||
ArrayIsArray,
|
||||
ArrayPrototypeJoin,
|
||||
MathAbs,
|
||||
MathFloor,
|
||||
NumberPrototypeToString,
|
||||
ObjectCreate,
|
||||
@@ -57,6 +58,7 @@ const {
|
||||
} = require('internal/async_hooks');
|
||||
const {
|
||||
codes: {
|
||||
ERR_HTTP_CONTENT_LENGTH_MISMATCH,
|
||||
ERR_HTTP_HEADERS_SENT,
|
||||
ERR_HTTP_INVALID_HEADER_VALUE,
|
||||
ERR_HTTP_TRAILER_INVALID,
|
||||
@@ -84,6 +86,8 @@ const HIGH_WATER_MARK = getDefaultHighWaterMark();
|
||||
|
||||
const kCorked = Symbol('corked');
|
||||
const kUniqueHeaders = Symbol('kUniqueHeaders');
|
||||
const kBytesWritten = Symbol('kBytesWritten');
|
||||
const kEndCalled = Symbol('kEndCalled');
|
||||
|
||||
const nop = () => {};
|
||||
|
||||
@@ -123,6 +127,9 @@ function OutgoingMessage() {
|
||||
this._removedContLen = false;
|
||||
this._removedTE = false;
|
||||
|
||||
this.strictContentLength = false;
|
||||
this[kBytesWritten] = 0;
|
||||
this[kEndCalled] = false;
|
||||
this._contentLength = null;
|
||||
this._hasBody = true;
|
||||
this._trailer = '';
|
||||
@@ -330,7 +337,9 @@ OutgoingMessage.prototype._send = function _send(data, encoding, callback) {
|
||||
// This is a shameful hack to get the headers and first body chunk onto
|
||||
// the same packet. Future versions of Node are going to take care of
|
||||
// this at a lower level and in a more general way.
|
||||
if (!this._headerSent) {
|
||||
if (!this._headerSent && this._header !== null) {
|
||||
// `this._header` can be null if OutgoingMessage is used without a proper Socket
|
||||
// See: /test/parallel/test-http-outgoing-message-inheritance.js
|
||||
if (typeof data === 'string' &&
|
||||
(encoding === 'utf8' || encoding === 'latin1' || !encoding)) {
|
||||
data = this._header + data;
|
||||
@@ -349,6 +358,14 @@ OutgoingMessage.prototype._send = function _send(data, encoding, callback) {
|
||||
return this._writeRaw(data, encoding, callback);
|
||||
};
|
||||
|
||||
function _getMessageBodySize(chunk, headers, encoding) {
|
||||
if (Buffer.isBuffer(chunk)) return chunk.length;
|
||||
const chunkLength = chunk ? Buffer.byteLength(chunk, encoding) : 0;
|
||||
const headerLength = headers ? headers.length : 0;
|
||||
if (headerLength === chunkLength) return 0;
|
||||
if (headerLength < chunkLength) return MathAbs(chunkLength - headerLength);
|
||||
return chunkLength;
|
||||
}
|
||||
|
||||
OutgoingMessage.prototype._writeRaw = _writeRaw;
|
||||
function _writeRaw(data, encoding, callback) {
|
||||
@@ -364,6 +381,25 @@ function _writeRaw(data, encoding, callback) {
|
||||
encoding = null;
|
||||
}
|
||||
|
||||
// TODO(sidwebworks): flip the `strictContentLength` default to `true` in a future PR
|
||||
if (this.strictContentLength && conn && conn.writable && !this._removedContLen && this._hasBody) {
|
||||
const skip = conn._httpMessage.statusCode === 304 || (this.hasHeader('transfer-encoding') || this.chunkedEncoding);
|
||||
|
||||
if (typeof this._contentLength === 'number' && !skip) {
|
||||
const size = _getMessageBodySize(data, conn._httpMessage._header, encoding);
|
||||
|
||||
if ((size + this[kBytesWritten]) > this._contentLength) {
|
||||
throw new ERR_HTTP_CONTENT_LENGTH_MISMATCH(size + this[kBytesWritten], this._contentLength);
|
||||
}
|
||||
|
||||
if (this[kEndCalled] && (size + this[kBytesWritten]) !== this._contentLength) {
|
||||
throw new ERR_HTTP_CONTENT_LENGTH_MISMATCH(size + this[kBytesWritten], this._contentLength);
|
||||
}
|
||||
|
||||
this[kBytesWritten] += size;
|
||||
}
|
||||
}
|
||||
|
||||
if (conn && conn._httpMessage === this && conn.writable) {
|
||||
// There might be pending data in the this.output buffer.
|
||||
if (this.outputData.length) {
|
||||
@@ -559,6 +595,7 @@ function matchHeader(self, state, field, value) {
|
||||
break;
|
||||
case 'content-length':
|
||||
state.contLen = true;
|
||||
self._contentLength = value;
|
||||
self._removedContLen = false;
|
||||
break;
|
||||
case 'date':
|
||||
@@ -923,6 +960,8 @@ OutgoingMessage.prototype.end = function end(chunk, encoding, callback) {
|
||||
encoding = null;
|
||||
}
|
||||
|
||||
this[kEndCalled] = true;
|
||||
|
||||
if (chunk) {
|
||||
if (this.finished) {
|
||||
onError(this,
|
||||
|
||||
@@ -1142,6 +1142,8 @@ E('ERR_HTTP2_TRAILERS_NOT_READY',
|
||||
'Trailing headers cannot be sent until after the wantTrailers event is ' +
|
||||
'emitted', Error);
|
||||
E('ERR_HTTP2_UNSUPPORTED_PROTOCOL', 'protocol "%s" is unsupported.', Error);
|
||||
E('ERR_HTTP_CONTENT_LENGTH_MISMATCH',
|
||||
'Response body\'s content-length of %s byte(s) does not match the content-length of %s byte(s) set in header', Error);
|
||||
E('ERR_HTTP_HEADERS_SENT',
|
||||
'Cannot %s headers after they are sent to the client', Error);
|
||||
E('ERR_HTTP_INVALID_HEADER_VALUE',
|
||||
|
||||
80
test/parallel/test-http-content-length-mismatch.js
Normal file
80
test/parallel/test-http-content-length-mismatch.js
Normal file
@@ -0,0 +1,80 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const http = require('http');
|
||||
|
||||
function shouldThrowOnMoreBytes() {
|
||||
const server = http.createServer(common.mustCall((req, res) => {
|
||||
res.strictContentLength = true;
|
||||
res.setHeader('Content-Length', 5);
|
||||
res.write('hello');
|
||||
assert.throws(() => {
|
||||
res.write('a');
|
||||
}, {
|
||||
code: 'ERR_HTTP_CONTENT_LENGTH_MISMATCH'
|
||||
});
|
||||
res.statusCode = 200;
|
||||
res.end();
|
||||
}));
|
||||
|
||||
server.listen(0, () => {
|
||||
const req = http.get({
|
||||
port: server.address().port,
|
||||
}, common.mustCall((res) => {
|
||||
res.resume();
|
||||
assert.strictEqual(res.statusCode, 200);
|
||||
server.close();
|
||||
}));
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
function shouldNotThrow() {
|
||||
const server = http.createServer(common.mustCall((req, res) => {
|
||||
res.strictContentLength = true;
|
||||
res.write('helloaa');
|
||||
res.statusCode = 200;
|
||||
res.end('ending');
|
||||
}));
|
||||
|
||||
server.listen(0, () => {
|
||||
http.get({
|
||||
port: server.address().port,
|
||||
}, common.mustCall((res) => {
|
||||
res.resume();
|
||||
assert.strictEqual(res.statusCode, 200);
|
||||
server.close();
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function shouldThrowOnFewerBytes() {
|
||||
const server = http.createServer(common.mustCall((req, res) => {
|
||||
res.strictContentLength = true;
|
||||
res.setHeader('Content-Length', 5);
|
||||
res.write('a');
|
||||
res.statusCode = 200;
|
||||
assert.throws(() => {
|
||||
res.end();
|
||||
}, {
|
||||
code: 'ERR_HTTP_CONTENT_LENGTH_MISMATCH'
|
||||
});
|
||||
res.end('aaaa');
|
||||
}));
|
||||
|
||||
server.listen(0, () => {
|
||||
http.get({
|
||||
port: server.address().port,
|
||||
}, common.mustCall((res) => {
|
||||
res.resume();
|
||||
assert.strictEqual(res.statusCode, 200);
|
||||
server.close();
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
shouldThrowOnMoreBytes();
|
||||
shouldNotThrow();
|
||||
shouldThrowOnFewerBytes();
|
||||
@@ -49,7 +49,7 @@ const OutgoingMessage = http.OutgoingMessage;
|
||||
msg._implicitHeader = function() {};
|
||||
assert.strictEqual(msg.writableLength, 0);
|
||||
msg.write('asd');
|
||||
assert.strictEqual(msg.writableLength, 7);
|
||||
assert.strictEqual(msg.writableLength, 3);
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -24,7 +24,7 @@ function test(server) {
|
||||
{
|
||||
const server = http.createServer((req, res) => {
|
||||
res.setHeader('content-length', [2, 1]);
|
||||
res.end('ok');
|
||||
res.end('k');
|
||||
});
|
||||
|
||||
test(server);
|
||||
|
||||
Reference in New Issue
Block a user