diff options
author | Shogun <ShogunPanda@users.noreply.github.com> | 2022-01-10 23:50:58 +0300 |
---|---|---|
committer | Bryan English <bryan@bryanenglish.com> | 2022-05-30 19:33:54 +0300 |
commit | 9539cfa35817ea3ad61eccd2ed0572cc5c449d03 (patch) | |
tree | 0033e0d0e832f843661bc9b75253b2430b9a4575 /lib | |
parent | b772c13a6226a4201bf7f1f9e7c653db6bc95321 (diff) |
http: add uniqueHeaders option to request and createServer
PR-URL: https://github.com/nodejs/node/pull/41397
Reviewed-By: Robert Nagy <ronagy@icloud.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Diffstat (limited to 'lib')
-rw-r--r-- | lib/_http_client.js | 8 | ||||
-rw-r--r-- | lib/_http_incoming.js | 50 | ||||
-rw-r--r-- | lib/_http_outgoing.js | 82 | ||||
-rw-r--r-- | lib/_http_server.js | 8 |
4 files changed, 140 insertions, 8 deletions
diff --git a/lib/_http_client.js b/lib/_http_client.js index a4f7a255a99..41f183aa524 100644 --- a/lib/_http_client.js +++ b/lib/_http_client.js @@ -52,7 +52,11 @@ const { isLenient, prepareError, } = require('_http_common'); -const { OutgoingMessage } = require('_http_outgoing'); +const { + kUniqueHeaders, + parseUniqueHeadersOption, + OutgoingMessage +} = require('_http_outgoing'); const Agent = require('_http_agent'); const { Buffer } = require('buffer'); const { defaultTriggerAsyncIdScope } = require('internal/async_hooks'); @@ -300,6 +304,8 @@ function ClientRequest(input, options, cb) { options.headers); } + this[kUniqueHeaders] = parseUniqueHeadersOption(options.uniqueHeaders); + let optsWithoutSignal = options; if (optsWithoutSignal.signal) { optsWithoutSignal = ObjectAssign({}, options); diff --git a/lib/_http_incoming.js b/lib/_http_incoming.js index 34de1c559c1..f1e6b7f54ff 100644 --- a/lib/_http_incoming.js +++ b/lib/_http_incoming.js @@ -33,8 +33,10 @@ const { const { Readable, finished } = require('stream'); const kHeaders = Symbol('kHeaders'); +const kHeadersDistinct = Symbol('kHeadersDistinct'); const kHeadersCount = Symbol('kHeadersCount'); const kTrailers = Symbol('kTrailers'); +const kTrailersDistinct = Symbol('kTrailersDistinct'); const kTrailersCount = Symbol('kTrailersCount'); function readStart(socket) { @@ -123,6 +125,25 @@ ObjectDefineProperty(IncomingMessage.prototype, 'headers', { } }); +ObjectDefineProperty(IncomingMessage.prototype, 'headersDistinct', { + get: function() { + if (!this[kHeadersDistinct]) { + this[kHeadersDistinct] = {}; + + const src = this.rawHeaders; + const dst = this[kHeadersDistinct]; + + for (let n = 0; n < this[kHeadersCount]; n += 2) { + this._addHeaderLineDistinct(src[n + 0], src[n + 1], dst); + } + } + return this[kHeadersDistinct]; + }, + set: function(val) { + this[kHeadersDistinct] = val; + } +}); + ObjectDefineProperty(IncomingMessage.prototype, 'trailers', { get: function() { if (!this[kTrailers]) { @@ -142,6 +163,25 @@ ObjectDefineProperty(IncomingMessage.prototype, 'trailers', { } }); +ObjectDefineProperty(IncomingMessage.prototype, 'trailersDistinct', { + get: function() { + if (!this[kTrailersDistinct]) { + this[kTrailersDistinct] = {}; + + const src = this.rawTrailers; + const dst = this[kTrailersDistinct]; + + for (let n = 0; n < this[kTrailersCount]; n += 2) { + this._addHeaderLineDistinct(src[n + 0], src[n + 1], dst); + } + } + return this[kTrailersDistinct]; + }, + set: function(val) { + this[kTrailersDistinct] = val; + } +}); + IncomingMessage.prototype.setTimeout = function setTimeout(msecs, callback) { if (callback) this.on('timeout', callback); @@ -361,6 +401,16 @@ function _addHeaderLine(field, value, dest) { } } +IncomingMessage.prototype._addHeaderLineDistinct = _addHeaderLineDistinct; +function _addHeaderLineDistinct(field, value, dest) { + field = StringPrototypeToLowerCase(field); + if (!dest[field]) { + dest[field] = [value]; + } else { + dest[field].push(value); + } +} + // Call this instead of resume() if we want to just // dump all the data to /dev/null diff --git a/lib/_http_outgoing.js b/lib/_http_outgoing.js index 71f6e3e7237..8b2d24bbf69 100644 --- a/lib/_http_outgoing.js +++ b/lib/_http_outgoing.js @@ -34,6 +34,7 @@ const { ObjectPrototypeHasOwnProperty, ObjectSetPrototypeOf, RegExpPrototypeTest, + SafeSet, StringPrototypeToLowerCase, Symbol, } = primordials; @@ -82,6 +83,7 @@ let debug = require('internal/util/debuglog').debuglog('http', (fn) => { const HIGH_WATER_MARK = getDefaultHighWaterMark(); const kCorked = Symbol('corked'); +const kUniqueHeaders = Symbol('kUniqueHeaders'); const nop = () => {}; @@ -502,7 +504,10 @@ function processHeader(self, state, key, value, validate) { if (validate) validateHeaderName(key); if (ArrayIsArray(value)) { - if (value.length < 2 || !isCookieField(key)) { + if ( + (value.length < 2 || !isCookieField(key)) && + (!self[kUniqueHeaders] || !self[kUniqueHeaders].has(StringPrototypeToLowerCase(key))) + ) { // Retain for(;;) loop for performance reasons // Refs: https://github.com/nodejs/node/pull/30958 for (let i = 0; i < value.length; i++) @@ -571,6 +576,20 @@ const validateHeaderValue = hideStackFrames((name, value) => { } }); +function parseUniqueHeadersOption(headers) { + if (!ArrayIsArray(headers)) { + return null; + } + + const unique = new SafeSet(); + const l = headers.length; + for (let i = 0; i < l; i++) { + unique.add(StringPrototypeToLowerCase(headers[i])); + } + + return unique; +} + OutgoingMessage.prototype.setHeader = function setHeader(name, value) { if (this._header) { throw new ERR_HTTP_HEADERS_SENT('set'); @@ -586,6 +605,36 @@ OutgoingMessage.prototype.setHeader = function setHeader(name, value) { return this; }; +OutgoingMessage.prototype.appendHeader = function appendHeader(name, value) { + if (this._header) { + throw new ERR_HTTP_HEADERS_SENT('append'); + } + validateHeaderName(name); + validateHeaderValue(name, value); + + const field = StringPrototypeToLowerCase(name); + const headers = this[kOutHeaders]; + if (headers === null || !headers[field]) { + return this.setHeader(name, value); + } + + // Prepare the field for appending, if required + if (!ArrayIsArray(headers[field][1])) { + headers[field][1] = [headers[field][1]]; + } + + const existingValues = headers[field][1]; + if (ArrayIsArray(value)) { + for (let i = 0, length = value.length; i < length; i++) { + existingValues.push(value[i]); + } + } else { + existingValues.push(value); + } + + return this; +}; + OutgoingMessage.prototype.getHeader = function getHeader(name) { validateString(name, 'name'); @@ -797,7 +846,6 @@ function connectionCorkNT(conn) { conn.uncork(); } - OutgoingMessage.prototype.addTrailers = function addTrailers(headers) { this._trailer = ''; const keys = ObjectKeys(headers); @@ -817,11 +865,31 @@ OutgoingMessage.prototype.addTrailers = function addTrailers(headers) { if (typeof field !== 'string' || !field || !checkIsHttpToken(field)) { throw new ERR_INVALID_HTTP_TOKEN('Trailer name', field); } - if (checkInvalidHeaderChar(value)) { - debug('Trailer "%s" contains invalid characters', field); - throw new ERR_INVALID_CHAR('trailer content', field); + + // Check if the field must be sent several times + const isArrayValue = ArrayIsArray(value); + if ( + isArrayValue && value.length > 1 && + (!this[kUniqueHeaders] || !this[kUniqueHeaders].has(StringPrototypeToLowerCase(field))) + ) { + for (let j = 0, l = value.length; j < l; j++) { + if (checkInvalidHeaderChar(value[j])) { + debug('Trailer "%s"[%d] contains invalid characters', field, j); + throw new ERR_INVALID_CHAR('trailer content', field); + } + this._trailer += field + ': ' + value[j] + '\r\n'; + } + } else { + if (isArrayValue) { + value = ArrayPrototypeJoin(value, '; '); + } + + if (checkInvalidHeaderChar(value)) { + debug('Trailer "%s" contains invalid characters', field); + throw new ERR_INVALID_CHAR('trailer content', field); + } + this._trailer += field + ': ' + value + '\r\n'; } - this._trailer += field + ': ' + value + '\r\n'; } }; @@ -997,6 +1065,8 @@ function(err, event) { }; module.exports = { + kUniqueHeaders, + parseUniqueHeadersOption, validateHeaderName, validateHeaderValue, OutgoingMessage diff --git a/lib/_http_server.js b/lib/_http_server.js index c8dc22929bf..615ee4c670c 100644 --- a/lib/_http_server.js +++ b/lib/_http_server.js @@ -47,7 +47,11 @@ const { prepareError, } = require('_http_common'); const { ConnectionsList } = internalBinding('http_parser'); -const { OutgoingMessage } = require('_http_outgoing'); +const { + kUniqueHeaders, + parseUniqueHeadersOption, + OutgoingMessage +} = require('_http_outgoing'); const { kOutHeaders, kNeedDrain, @@ -450,6 +454,7 @@ function Server(options, requestListener) { this.maxHeadersCount = null; this.maxRequestsPerSocket = 0; setupConnectionsTracking(this); + this[kUniqueHeaders] = parseUniqueHeadersOption(options.uniqueHeaders); } ObjectSetPrototypeOf(Server.prototype, net.Server.prototype); ObjectSetPrototypeOf(Server, net.Server); @@ -916,6 +921,7 @@ function parserOnIncoming(server, socket, state, req, keepAlive) { socket, state); res.shouldKeepAlive = keepAlive; + res[kUniqueHeaders] = server[kUniqueHeaders]; DTRACE_HTTP_SERVER_REQUEST(req, socket); if (onRequestStartChannel.hasSubscribers) { |