From 5ec0f39a7a565b5a82fe90ba9f095731a7b8b005 Mon Sep 17 00:00:00 2001 From: Gerrard Lindsay Date: Sat, 13 May 2023 13:09:26 -0400 Subject: http: prevent writing to the body when not allowed by HTTP spec PR-URL: https://github.com/nodejs/node/pull/47732 Reviewed-By: Robert Nagy Reviewed-By: Paolo Insogna Reviewed-By: Marco Ippolito Reviewed-By: Matteo Collina --- lib/_http_outgoing.js | 21 ++++++++++++++------- lib/_http_server.js | 14 +++++++++++++- lib/http.js | 1 + lib/internal/errors.js | 2 ++ 4 files changed, 30 insertions(+), 8 deletions(-) (limited to 'lib') diff --git a/lib/_http_outgoing.js b/lib/_http_outgoing.js index 55a2f8bef3..73bd2aad8f 100644 --- a/lib/_http_outgoing.js +++ b/lib/_http_outgoing.js @@ -60,6 +60,7 @@ const { ERR_HTTP_HEADERS_SENT, ERR_HTTP_INVALID_HEADER_VALUE, ERR_HTTP_TRAILER_INVALID, + ERR_HTTP_BODY_NOT_ALLOWED, ERR_INVALID_HTTP_TOKEN, ERR_INVALID_ARG_TYPE, ERR_INVALID_ARG_VALUE, @@ -85,6 +86,7 @@ const kUniqueHeaders = Symbol('kUniqueHeaders'); const kBytesWritten = Symbol('kBytesWritten'); const kErrored = Symbol('errored'); const kHighWaterMark = Symbol('kHighWaterMark'); +const kRejectNonStandardBodyWrites = Symbol('kRejectNonStandardBodyWrites'); const nop = () => {}; @@ -150,6 +152,7 @@ function OutgoingMessage(options) { this[kErrored] = null; this[kHighWaterMark] = options?.highWaterMark ?? getDefaultHighWaterMark(); + this[kRejectNonStandardBodyWrites] = options?.rejectNonStandardBodyWrites ?? false; } ObjectSetPrototypeOf(OutgoingMessage.prototype, Stream.prototype); ObjectSetPrototypeOf(OutgoingMessage, Stream); @@ -884,6 +887,17 @@ function write_(msg, chunk, encoding, callback, fromEnd) { err = new ERR_STREAM_DESTROYED('write'); } + if (!msg._hasBody) { + if (msg[kRejectNonStandardBodyWrites]) { + throw new ERR_HTTP_BODY_NOT_ALLOWED(); + } else { + debug('This type of response MUST NOT have a body. ' + + 'Ignoring write() calls.'); + process.nextTick(callback); + return true; + } + } + if (err) { if (!msg.destroyed) { onError(msg, err, callback); @@ -916,13 +930,6 @@ function write_(msg, chunk, encoding, callback, fromEnd) { msg._implicitHeader(); } - if (!msg._hasBody) { - debug('This type of response MUST NOT have a body. ' + - 'Ignoring write() calls.'); - process.nextTick(callback); - return true; - } - if (!fromEnd && msg.socket && !msg.socket.writableCorked) { msg.socket.cork(); process.nextTick(connectionCorkNT, msg.socket); diff --git a/lib/_http_server.js b/lib/_http_server.js index 774bdc368f..c6a0701155 100644 --- a/lib/_http_server.js +++ b/lib/_http_server.js @@ -487,6 +487,14 @@ function storeHTTPOptions(options) { validateBoolean(joinDuplicateHeaders, 'options.joinDuplicateHeaders'); } this.joinDuplicateHeaders = joinDuplicateHeaders; + + const rejectNonStandardBodyWrites = options.rejectNonStandardBodyWrites; + if (rejectNonStandardBodyWrites !== undefined) { + validateBoolean(rejectNonStandardBodyWrites, 'options.rejectNonStandardBodyWrites'); + this.rejectNonStandardBodyWrites = rejectNonStandardBodyWrites; + } else { + this.rejectNonStandardBodyWrites = false; + } } function setupConnectionsTracking(server) { @@ -1023,7 +1031,11 @@ function parserOnIncoming(server, socket, state, req, keepAlive) { } } - const res = new server[kServerResponse](req, { highWaterMark: socket.writableHighWaterMark }); + const res = new server[kServerResponse](req, + { + highWaterMark: socket.writableHighWaterMark, + rejectNonStandardBodyWrites: server.rejectNonStandardBodyWrites, + }); res._keepAliveTimeout = server.keepAliveTimeout; res._maxRequestsPerSocket = server.maxRequestsPerSocket; res._onPendingData = updateOutgoingData.bind(undefined, diff --git a/lib/http.js b/lib/http.js index a1f811c695..d86d36d12c 100644 --- a/lib/http.js +++ b/lib/http.js @@ -55,6 +55,7 @@ let maxHeaderSize; * requireHostHeader?: boolean; * joinDuplicateHeaders?: boolean; * highWaterMark?: number; + * rejectNonStandardBodyWrites?: boolean; * }} [opts] * @param {Function} [requestListener] * @returns {Server} diff --git a/lib/internal/errors.js b/lib/internal/errors.js index 18f4a2c42c..8a490ea3a4 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -1154,6 +1154,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_BODY_NOT_ALLOWED', + 'Adding content for this request method or response status is not allowed.', 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', -- cgit v1.2.1