| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1306,6 +1306,12 @@ When using [`fs.cp()`][], `src` or `dest` pointed to an invalid path. | |||
| 1306 | 1306 | ||
| 1307 | 1307 | <a id="ERR_FS_CP_FIFO_PIPE"></a> | |
| 1308 | 1308 | ||
| 1309 | + ### `ERR_HTTP_CONTENT_LENGTH_MISMATCH` | ||
| 1310 | + | ||
| 1311 | + Response body size doesn't match with the specified content-length header value. | ||
| 1312 | + | ||
| 1313 | + <a id="ERR_HTTP_CONTENT_LENGTH_MISMATCH"></a> | ||
| 1314 | + | ||
| 1309 | 1315 | ### `ERR_FS_CP_FIFO_PIPE` | |
| 1310 | 1316 | ||
| 1311 | 1317 | <!-- | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -419,8 +419,12 @@ the data is read it will consume memory that can eventually lead to a | |||
| 419 | 419 | For backward compatibility, `res` will only emit `'error'` if there is an | |
| 420 | 420 | `'error'` listener registered. | |
| 421 | 421 | ||
| 422 | - Node.js does not check whether Content-Length and the length of the | ||
| 423 | - body which has been transmitted are equal or not. | ||
| 422 | + Set `Content-Length` header to limit the response body size. Mismatching the | ||
| 423 | + `Content-Length` header value will result in an \[`Error`]\[] being thrown, | ||
| 424 | + identified by `code:` [`'ERR_HTTP_CONTENT_LENGTH_MISMATCH'`][]. | ||
| 425 | + | ||
| 426 | + `Content-Length` value should be in bytes, not characters. Use | ||
| 427 | + [`Buffer.byteLength()`][] to determine the length of the body in bytes. | ||
| 424 | 428 | ||
| 425 | 429 | ### Event: `'abort'` | |
| 426 | 430 | ||
@@ -2169,13 +2173,13 @@ const server = http.createServer((req, res) => { | |||
| 2169 | 2173 | }); | |
| 2170 | 2174 | ``` | |
| 2171 | 2175 | ||
| 2172 | - `Content-Length` is given in bytes, not characters. Use | ||
| 2176 | + `Content-Length` is read in bytes, not characters. Use | ||
| 2173 | 2177 | [`Buffer.byteLength()`][] to determine the length of the body in bytes. Node.js | |
| 2174 | - does not check whether `Content-Length` and the length of the body which has | ||
| 2178 | + will check whether `Content-Length` and the length of the body which has | ||
| 2175 | 2179 | been transmitted are equal or not. | |
| 2176 | 2180 | ||
| 2177 | 2181 | Attempting to set a header field name or value that contains invalid characters | |
| 2178 | - will result in a [`TypeError`][] being thrown. | ||
| 2182 | + will result in a \[`Error`]\[] being thrown. | ||
| 2179 | 2183 | ||
| 2180 | 2184 | ### `response.writeProcessing()` | |
| 2181 | 2185 | ||
@@ -3568,6 +3572,7 @@ added: REPLACEME | |||
| 3568 | 3572 | Set the maximum number of idle HTTP parsers. **Default:** `1000`. | |
| 3569 | 3573 | ||
| 3570 | 3574 | [RFC 8187]: https://www.rfc-editor.org/rfc/rfc8187.txt | |
| 3575 | + [`'ERR_HTTP_CONTENT_LENGTH_MISMATCH'`]: errors.md#err_http_content_length_mismatch | ||
| 3571 | 3576 | [`'checkContinue'`]: #event-checkcontinue | |
| 3572 | 3577 | [`'finish'`]: #event-finish | |
| 3573 | 3578 | [`'request'`]: #event-request | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -25,6 +25,7 @@ const { | |||
| 25 | 25 | Array, | |
| 26 | 26 | ArrayIsArray, | |
| 27 | 27 | ArrayPrototypeJoin, | |
| 28 | + MathAbs, | ||
| 28 | 29 | MathFloor, | |
| 29 | 30 | NumberPrototypeToString, | |
| 30 | 31 | ObjectCreate, | |
@@ -57,6 +58,7 @@ const { | |||
| 57 | 58 | } = require('internal/async_hooks'); | |
| 58 | 59 | const { | |
| 59 | 60 | codes: { | |
| 61 | + ERR_HTTP_CONTENT_LENGTH_MISMATCH, | ||
| 60 | 62 | ERR_HTTP_HEADERS_SENT, | |
| 61 | 63 | ERR_HTTP_INVALID_HEADER_VALUE, | |
| 62 | 64 | ERR_HTTP_TRAILER_INVALID, | |
@@ -84,6 +86,8 @@ const HIGH_WATER_MARK = getDefaultHighWaterMark(); | |||
| 84 | 86 | ||
| 85 | 87 | const kCorked = Symbol('corked'); | |
| 86 | 88 | const kUniqueHeaders = Symbol('kUniqueHeaders'); | |
| 89 | + const kBytesWritten = Symbol('kBytesWritten'); | ||
| 90 | + const kEndCalled = Symbol('kEndCalled'); | ||
| 87 | 91 | ||
| 88 | 92 | const nop = () => {}; | |
| 89 | 93 | ||
@@ -123,6 +127,9 @@ function OutgoingMessage() { | |||
| 123 | 127 | this._removedContLen = false; | |
| 124 | 128 | this._removedTE = false; | |
| 125 | 129 | ||
| 130 | + this.strictContentLength = false; | ||
| 131 | + this[kBytesWritten] = 0; | ||
| 132 | + this[kEndCalled] = false; | ||
| 126 | 133 | this._contentLength = null; | |
| 127 | 134 | this._hasBody = true; | |
| 128 | 135 | this._trailer = ''; | |
@@ -330,7 +337,9 @@ OutgoingMessage.prototype._send = function _send(data, encoding, callback) { | |||
| 330 | 337 | // This is a shameful hack to get the headers and first body chunk onto | |
| 331 | 338 | // the same packet. Future versions of Node are going to take care of | |
| 332 | 339 | // 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 | ||
| 334 | 343 | if (typeof data === 'string' && | |
| 335 | 344 | (encoding === 'utf8' || encoding === 'latin1' || !encoding)) { | |
| 336 | 345 | data = this._header + data; | |
@@ -349,6 +358,14 @@ OutgoingMessage.prototype._send = function _send(data, encoding, callback) { | |||
| 349 | 358 | return this._writeRaw(data, encoding, callback); | |
| 350 | 359 | }; | |
| 351 | 360 | ||
| 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 | + } | ||
| 352 | 369 | ||
| 353 | 370 | OutgoingMessage.prototype._writeRaw = _writeRaw; | |
| 354 | 371 | function _writeRaw(data, encoding, callback) { | |
@@ -364,6 +381,25 @@ function _writeRaw(data, encoding, callback) { | |||
| 364 | 381 | encoding = null; | |
| 365 | 382 | } | |
| 366 | 383 | ||
| 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 | + | ||
| 367 | 403 | if (conn && conn._httpMessage === this && conn.writable) { | |
| 368 | 404 | // There might be pending data in the this.output buffer. | |
| 369 | 405 | if (this.outputData.length) { | |
@@ -559,6 +595,7 @@ function matchHeader(self, state, field, value) { | |||
| 559 | 595 | break; | |
| 560 | 596 | case 'content-length': | |
| 561 | 597 | state.contLen = true; | |
| 598 | + self._contentLength = value; | ||
| 562 | 599 | self._removedContLen = false; | |
| 563 | 600 | break; | |
| 564 | 601 | case 'date': | |
@@ -923,6 +960,8 @@ OutgoingMessage.prototype.end = function end(chunk, encoding, callback) { | |||
| 923 | 960 | encoding = null; | |
| 924 | 961 | } | |
| 925 | 962 | ||
| 963 | + this[kEndCalled] = true; | ||
| 964 | + | ||
| 926 | 965 | if (chunk) { | |
| 927 | 966 | if (this.finished) { | |
| 928 | 967 | onError(this, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1134,6 +1134,8 @@ E('ERR_HTTP2_TRAILERS_NOT_READY', | |||
| 1134 | 1134 | 'Trailing headers cannot be sent until after the wantTrailers event is ' + | |
| 1135 | 1135 | 'emitted', Error); | |
| 1136 | 1136 | E('ERR_HTTP2_UNSUPPORTED_PROTOCOL', 'protocol "%s" is unsupported.', Error); | |
| 1137 | + E('ERR_HTTP_CONTENT_LENGTH_MISMATCH', | ||
| 1138 | + 'Response body\'s content-length of %s byte(s) does not match the content-length of %s byte(s) set in header', Error); | ||
| 1137 | 1139 | E('ERR_HTTP_HEADERS_SENT', | |
| 1138 | 1140 | 'Cannot %s headers after they are sent to the client', Error); | |
| 1139 | 1141 | E('ERR_HTTP_INVALID_HEADER_VALUE', | |
| Original file line number | Diff line number | Diff 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(); | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -49,7 +49,7 @@ const OutgoingMessage = http.OutgoingMessage; | |||
| 49 | 49 | msg._implicitHeader = function() {}; | |
| 50 | 50 | assert.strictEqual(msg.writableLength, 0); | |
| 51 | 51 | msg.write('asd'); | |
| 52 | - assert.strictEqual(msg.writableLength, 7); | ||
| 52 | + assert.strictEqual(msg.writableLength, 3); | ||
| 53 | 53 | } | |
| 54 | 54 | ||
| 55 | 55 | { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -24,7 +24,7 @@ function test(server) { | |||
| 24 | 24 | { | |
| 25 | 25 | const server = http.createServer((req, res) => { | |
| 26 | 26 | res.setHeader('content-length', [2, 1]); | |
| 27 | - res.end('ok'); | ||
| 27 | + res.end('k'); | ||
| 28 | 28 | }); | |
| 29 | 29 | ||
| 30 | 30 | test(server); | |
| Back | FazBrowse Home | New Git URL |
0 commit comments