@@ -25,7 +25,6 @@ const { |
25 | 25 | Array, |
26 | 26 | ArrayIsArray, |
27 | 27 | ArrayPrototypeJoin, |
28 | | - MathAbs, |
29 | 28 | MathFloor, |
30 | 29 | NumberPrototypeToString, |
31 | 30 | ObjectCreate, |
@@ -87,7 +86,6 @@ const HIGH_WATER_MARK = getDefaultHighWaterMark(); |
87 | 86 | const kCorked = Symbol('corked'); |
88 | 87 | const kUniqueHeaders = Symbol('kUniqueHeaders'); |
89 | 88 | const kBytesWritten = Symbol('kBytesWritten'); |
90 | | -const kEndCalled = Symbol('kEndCalled'); |
91 | 89 | const kErrored = Symbol('errored'); |
92 | 90 |
|
93 | 91 | const nop = () => {}; |
@@ -134,7 +132,6 @@ function OutgoingMessage() { |
134 | 132 |
|
135 | 133 | this.strictContentLength = false; |
136 | 134 | this[kBytesWritten] = 0; |
137 | | - this[kEndCalled] = false; |
138 | 135 | this._contentLength = null; |
139 | 136 | this._hasBody = true; |
140 | 137 | this._trailer = ''; |
@@ -356,7 +353,7 @@ OutgoingMessage.prototype.destroy = function destroy(error) { |
356 | 353 |
|
357 | 354 |
|
358 | 355 | // This abstract either writing directly to the socket or buffering it. |
359 | | -OutgoingMessage.prototype._send = function _send(data, encoding, callback) { |
| 356 | +OutgoingMessage.prototype._send = function _send(data, encoding, callback, byteLength) { |
360 | 357 | // This is a shameful hack to get the headers and first body chunk onto |
361 | 358 | // the same packet. Future versions of Node are going to take care of |
362 | 359 | // this at a lower level and in a more general way. |
@@ -378,20 +375,11 @@ OutgoingMessage.prototype._send = function _send(data, encoding, callback) { |
378 | 375 | } |
379 | 376 | this._headerSent = true; |
380 | 377 | } |
381 | | - return this._writeRaw(data, encoding, callback); |
| 378 | + return this._writeRaw(data, encoding, callback, byteLength); |
382 | 379 | }; |
383 | 380 |
|
384 | | -function _getMessageBodySize(chunk, headers, encoding) { |
385 | | - if (Buffer.isBuffer(chunk)) return chunk.length; |
386 | | - const chunkLength = chunk ? Buffer.byteLength(chunk, encoding) : 0; |
387 | | - const headerLength = headers ? headers.length : 0; |
388 | | - if (headerLength === chunkLength) return 0; |
389 | | - if (headerLength < chunkLength) return MathAbs(chunkLength - headerLength); |
390 | | - return chunkLength; |
391 | | -} |
392 | | - |
393 | 381 | OutgoingMessage.prototype._writeRaw = _writeRaw; |
394 | | -function _writeRaw(data, encoding, callback) { |
| 382 | +function _writeRaw(data, encoding, callback, size) { |
395 | 383 | const conn = this.socket; |
396 | 384 | if (conn && conn.destroyed) { |
397 | 385 | // The socket was destroyed. If we're still trying to write to it, |
@@ -404,25 +392,6 @@ function _writeRaw(data, encoding, callback) { |
404 | 392 | encoding = null; |
405 | 393 | } |
406 | 394 |
|
407 | | - // TODO(sidwebworks): flip the `strictContentLength` default to `true` in a future PR |
408 | | - if (this.strictContentLength && conn && conn.writable && !this._removedContLen && this._hasBody) { |
409 | | - const skip = conn._httpMessage.statusCode === 304 || (this.hasHeader('transfer-encoding') || this.chunkedEncoding); |
410 | | - |
411 | | - if (typeof this._contentLength === 'number' && !skip) { |
412 | | - const size = _getMessageBodySize(data, conn._httpMessage._header, encoding); |
413 | | - |
414 | | - if ((size + this[kBytesWritten]) > this._contentLength) { |
415 | | - throw new ERR_HTTP_CONTENT_LENGTH_MISMATCH(size + this[kBytesWritten], this._contentLength); |
416 | | - } |
417 | | - |
418 | | - if (this[kEndCalled] && (size + this[kBytesWritten]) !== this._contentLength) { |
419 | | - throw new ERR_HTTP_CONTENT_LENGTH_MISMATCH(size + this[kBytesWritten], this._contentLength); |
420 | | - } |
421 | | - |
422 | | - this[kBytesWritten] += size; |
423 | | - } |
424 | | - } |
425 | | - |
426 | 395 | if (conn && conn._httpMessage === this && conn.writable) { |
427 | 396 | // There might be pending data in the this.output buffer. |
428 | 397 | if (this.outputData.length) { |
@@ -882,18 +851,24 @@ function emitErrorNt(msg, err, callback) { |
882 | 851 | } |
883 | 852 | } |
884 | 853 |
|
| 854 | +function strictContentLength(msg) { |
| 855 | + return ( |
| 856 | + msg.strictContentLength && |
| 857 | + msg._contentLength != null && |
| 858 | + msg._hasBody && |
| 859 | + !msg._removedContLen && |
| 860 | + !msg.chunkedEncoding && |
| 861 | + !msg.hasHeader('transfer-encoding') |
| 862 | + ); |
| 863 | +} |
| 864 | + |
885 | 865 | function write_(msg, chunk, encoding, callback, fromEnd) { |
886 | 866 | if (typeof callback !== 'function') |
887 | 867 | callback = nop; |
888 | 868 |
|
889 | | - let len; |
890 | 869 | if (chunk === null) { |
891 | 870 | throw new ERR_STREAM_NULL_VALUES(); |
892 | | - } else if (typeof chunk === 'string') { |
893 | | - len = Buffer.byteLength(chunk, encoding); |
894 | | - } else if (isUint8Array(chunk)) { |
895 | | - len = chunk.length; |
896 | | - } else { |
| 871 | + } else if (typeof chunk !== 'string' && !isUint8Array(chunk)) { |
897 | 872 | throw new ERR_INVALID_ARG_TYPE( |
898 | 873 | 'chunk', ['string', 'Buffer', 'Uint8Array'], chunk); |
899 | 874 | } |
@@ -914,8 +889,24 @@ function write_(msg, chunk, encoding, callback, fromEnd) { |
914 | 889 | return false; |
915 | 890 | } |
916 | 891 |
|
| 892 | + let len; |
| 893 | + |
| 894 | + if (msg.strictContentLength) { |
| 895 | + len ??= typeof chunk === 'string' ? Buffer.byteLength(chunk, encoding) : chunk.byteLength; |
| 896 | + |
| 897 | + if ( |
| 898 | + strictContentLength(msg) && |
| 899 | + (fromEnd ? msg[kBytesWritten] + len !== msg._contentLength : msg[kBytesWritten] + len > msg._contentLength) |
| 900 | + ) { |
| 901 | + throw new ERR_HTTP_CONTENT_LENGTH_MISMATCH(len + msg[kBytesWritten], msg._contentLength); |
| 902 | + } |
| 903 | + |
| 904 | + msg[kBytesWritten] += len; |
| 905 | + } |
| 906 | + |
917 | 907 | if (!msg._header) { |
918 | 908 | if (fromEnd) { |
| 909 | + len ??= typeof chunk === 'string' ? Buffer.byteLength(chunk, encoding) : chunk.byteLength; |
919 | 910 | msg._contentLength = len; |
920 | 911 | } |
921 | 912 | msg._implicitHeader(); |
@@ -935,12 +926,13 @@ function write_(msg, chunk, encoding, callback, fromEnd) { |
935 | 926 |
|
936 | 927 | let ret; |
937 | 928 | if (msg.chunkedEncoding && chunk.length !== 0) { |
| 929 | + len ??= typeof chunk === 'string' ? Buffer.byteLength(chunk, encoding) : chunk.byteLength; |
938 | 930 | msg._send(NumberPrototypeToString(len, 16), 'latin1', null); |
939 | 931 | msg._send(crlf_buf, null, null); |
940 | | - msg._send(chunk, encoding, null); |
| 932 | + msg._send(chunk, encoding, null, len); |
941 | 933 | ret = msg._send(crlf_buf, null, callback); |
942 | 934 | } else { |
943 | | - ret = msg._send(chunk, encoding, callback); |
| 935 | + ret = msg._send(chunk, encoding, callback, len); |
944 | 936 | } |
945 | 937 |
|
946 | 938 | debug('write ret = ' + ret); |
@@ -1012,8 +1004,6 @@ OutgoingMessage.prototype.end = function end(chunk, encoding, callback) { |
1012 | 1004 | encoding = null; |
1013 | 1005 | } |
1014 | 1006 |
|
1015 | | - this[kEndCalled] = true; |
1016 | | - |
1017 | 1007 | if (chunk) { |
1018 | 1008 | if (this.finished) { |
1019 | 1009 | onError(this, |
@@ -1048,6 +1038,10 @@ OutgoingMessage.prototype.end = function end(chunk, encoding, callback) { |
1048 | 1038 | if (typeof callback === 'function') |
1049 | 1039 | this.once('finish', callback); |
1050 | 1040 |
|
| 1041 | + if (strictContentLength(this) && this[kBytesWritten] !== this._contentLength) { |
| 1042 | + throw new ERR_HTTP_CONTENT_LENGTH_MISMATCH(this[kBytesWritten], this._contentLength); |
| 1043 | + } |
| 1044 | + |
1051 | 1045 | const finish = onFinish.bind(undefined, this); |
1052 | 1046 |
|
1053 | 1047 | if (this._hasBody && this.chunkedEncoding) { |
|