Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nodejs/node.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorShogun <ShogunPanda@users.noreply.github.com>2022-01-10 23:50:58 +0300
committerBryan English <bryan@bryanenglish.com>2022-05-30 19:33:54 +0300
commit9539cfa35817ea3ad61eccd2ed0572cc5c449d03 (patch)
tree0033e0d0e832f843661bc9b75253b2430b9a4575 /lib
parentb772c13a6226a4201bf7f1f9e7c653db6bc95321 (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.js8
-rw-r--r--lib/_http_incoming.js50
-rw-r--r--lib/_http_outgoing.js82
-rw-r--r--lib/_http_server.js8
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) {