mirror of
https://github.com/nodejs/node.git
synced 2024-11-21 10:59:27 +00:00
http: join authorization headers
PR-URL: https://github.com/nodejs/node/pull/45982 Fixes: https://github.com/nodejs/node/issues/45699 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: Paolo Insogna <paolo@cowtech.it>
This commit is contained in:
parent
e35e893d26
commit
4080bada1a
@ -2426,6 +2426,13 @@ as an argument to any listeners on the event.
|
|||||||
<!-- YAML
|
<!-- YAML
|
||||||
added: v0.1.5
|
added: v0.1.5
|
||||||
changes:
|
changes:
|
||||||
|
- version: REPLACEME
|
||||||
|
pr-url: https://github.com/nodejs/node/pull/45982
|
||||||
|
description: >-
|
||||||
|
The `joinDuplicateHeaders` option in the `http.request()`
|
||||||
|
and `http.createServer()` functions ensures that duplicate
|
||||||
|
headers are not discarded, but rather combined using a
|
||||||
|
comma separator, in accordance with RFC 9110 Section 5.3.
|
||||||
- version: v15.1.0
|
- version: v15.1.0
|
||||||
pr-url: https://github.com/nodejs/node/pull/35281
|
pr-url: https://github.com/nodejs/node/pull/35281
|
||||||
description: >-
|
description: >-
|
||||||
@ -2455,6 +2462,10 @@ header name:
|
|||||||
`etag`, `expires`, `from`, `host`, `if-modified-since`, `if-unmodified-since`,
|
`etag`, `expires`, `from`, `host`, `if-modified-since`, `if-unmodified-since`,
|
||||||
`last-modified`, `location`, `max-forwards`, `proxy-authorization`, `referer`,
|
`last-modified`, `location`, `max-forwards`, `proxy-authorization`, `referer`,
|
||||||
`retry-after`, `server`, or `user-agent` are discarded.
|
`retry-after`, `server`, or `user-agent` are discarded.
|
||||||
|
To allow duplicate values of the headers listed above to be joined,
|
||||||
|
use the option `joinDuplicateHeaders` in [`http.request()`][]
|
||||||
|
and [`http.createServer()`][]. See RFC 9110 Section 5.3 for more
|
||||||
|
information.
|
||||||
* `set-cookie` is always an array. Duplicates are added to the array.
|
* `set-cookie` is always an array. Duplicates are added to the array.
|
||||||
* For duplicate `cookie` headers, the values are joined together with `; `.
|
* For duplicate `cookie` headers, the values are joined together with `; `.
|
||||||
* For all other headers, the values are joined together with `, `.
|
* For all other headers, the values are joined together with `, `.
|
||||||
@ -3186,6 +3197,10 @@ changes:
|
|||||||
a 400 (Bad Request) status code to any HTTP/1.1 request message
|
a 400 (Bad Request) status code to any HTTP/1.1 request message
|
||||||
that lacks a Host header (as mandated by the specification).
|
that lacks a Host header (as mandated by the specification).
|
||||||
**Default:** `true`.
|
**Default:** `true`.
|
||||||
|
* `joinDuplicateHeaders` {boolean} It joins the field line values of multiple
|
||||||
|
headers in a request with `, ` instead of discarding the duplicates.
|
||||||
|
See [`message.headers`][] for more information.
|
||||||
|
**Default:** `false`.
|
||||||
* `ServerResponse` {http.ServerResponse} Specifies the `ServerResponse` class
|
* `ServerResponse` {http.ServerResponse} Specifies the `ServerResponse` class
|
||||||
to be used. Useful for extending the original `ServerResponse`. **Default:**
|
to be used. Useful for extending the original `ServerResponse`. **Default:**
|
||||||
`ServerResponse`.
|
`ServerResponse`.
|
||||||
@ -3441,6 +3456,10 @@ changes:
|
|||||||
* `uniqueHeaders` {Array} A list of request headers that should be sent
|
* `uniqueHeaders` {Array} A list of request headers that should be sent
|
||||||
only once. If the header's value is an array, the items will be joined
|
only once. If the header's value is an array, the items will be joined
|
||||||
using `; `.
|
using `; `.
|
||||||
|
* `joinDuplicateHeaders` {boolean} It joins the field line values of
|
||||||
|
multiple headers in a request with `, ` instead of discarding
|
||||||
|
the duplicates. See [`message.headers`][] for more information.
|
||||||
|
**Default:** `false`.
|
||||||
* `callback` {Function}
|
* `callback` {Function}
|
||||||
* Returns: {http.ClientRequest}
|
* Returns: {http.ClientRequest}
|
||||||
|
|
||||||
@ -3754,6 +3773,7 @@ Set the maximum number of idle HTTP parsers. **Default:** `1000`.
|
|||||||
[`http.IncomingMessage`]: #class-httpincomingmessage
|
[`http.IncomingMessage`]: #class-httpincomingmessage
|
||||||
[`http.ServerResponse`]: #class-httpserverresponse
|
[`http.ServerResponse`]: #class-httpserverresponse
|
||||||
[`http.Server`]: #class-httpserver
|
[`http.Server`]: #class-httpserver
|
||||||
|
[`http.createServer()`]: #httpcreateserveroptions-requestlistener
|
||||||
[`http.get()`]: #httpgetoptions-callback
|
[`http.get()`]: #httpgetoptions-callback
|
||||||
[`http.globalAgent`]: #httpglobalagent
|
[`http.globalAgent`]: #httpglobalagent
|
||||||
[`http.request()`]: #httprequestoptions-callback
|
[`http.request()`]: #httprequestoptions-callback
|
||||||
|
@ -82,6 +82,7 @@ const {
|
|||||||
} = codes;
|
} = codes;
|
||||||
const {
|
const {
|
||||||
validateInteger,
|
validateInteger,
|
||||||
|
validateBoolean,
|
||||||
} = require('internal/validators');
|
} = require('internal/validators');
|
||||||
const { getTimerDuration } = require('internal/timers');
|
const { getTimerDuration } = require('internal/timers');
|
||||||
const {
|
const {
|
||||||
@ -229,6 +230,12 @@ function ClientRequest(input, options, cb) {
|
|||||||
}
|
}
|
||||||
this.insecureHTTPParser = insecureHTTPParser;
|
this.insecureHTTPParser = insecureHTTPParser;
|
||||||
|
|
||||||
|
if (options.joinDuplicateHeaders !== undefined) {
|
||||||
|
validateBoolean(options.joinDuplicateHeaders, 'options.joinDuplicateHeaders');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.joinDuplicateHeaders = options.joinDuplicateHeaders;
|
||||||
|
|
||||||
this.path = options.path || '/';
|
this.path = options.path || '/';
|
||||||
if (cb) {
|
if (cb) {
|
||||||
this.once('response', cb);
|
this.once('response', cb);
|
||||||
@ -811,6 +818,8 @@ function tickOnSocket(req, socket) {
|
|||||||
parser.maxHeaderPairs = req.maxHeadersCount << 1;
|
parser.maxHeaderPairs = req.maxHeadersCount << 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
parser.joinDuplicateHeaders = req.joinDuplicateHeaders;
|
||||||
|
|
||||||
parser.onIncoming = parserOnIncomingClient;
|
parser.onIncoming = parserOnIncomingClient;
|
||||||
socket.on('error', socketErrorListener);
|
socket.on('error', socketErrorListener);
|
||||||
socket.on('data', socketOnData);
|
socket.on('data', socketOnData);
|
||||||
|
@ -94,6 +94,8 @@ function parserOnHeadersComplete(versionMajor, versionMinor, headers, method,
|
|||||||
incoming.httpVersionMajor = versionMajor;
|
incoming.httpVersionMajor = versionMajor;
|
||||||
incoming.httpVersionMinor = versionMinor;
|
incoming.httpVersionMinor = versionMinor;
|
||||||
incoming.httpVersion = `${versionMajor}.${versionMinor}`;
|
incoming.httpVersion = `${versionMajor}.${versionMinor}`;
|
||||||
|
incoming.joinDuplicateHeaders = socket?.server?.joinDuplicateHeaders ||
|
||||||
|
parser.joinDuplicateHeaders;
|
||||||
incoming.url = url;
|
incoming.url = url;
|
||||||
incoming.upgrade = upgrade;
|
incoming.upgrade = upgrade;
|
||||||
|
|
||||||
|
@ -75,7 +75,7 @@ function IncomingMessage(socket) {
|
|||||||
this[kTrailers] = null;
|
this[kTrailers] = null;
|
||||||
this[kTrailersCount] = 0;
|
this[kTrailersCount] = 0;
|
||||||
this.rawTrailers = [];
|
this.rawTrailers = [];
|
||||||
|
this.joinDuplicateHeaders = false;
|
||||||
this.aborted = false;
|
this.aborted = false;
|
||||||
|
|
||||||
this.upgrade = null;
|
this.upgrade = null;
|
||||||
@ -400,6 +400,16 @@ function _addHeaderLine(field, value, dest) {
|
|||||||
} else {
|
} else {
|
||||||
dest['set-cookie'] = [value];
|
dest['set-cookie'] = [value];
|
||||||
}
|
}
|
||||||
|
} else if (this.joinDuplicateHeaders) {
|
||||||
|
// RFC 9110 https://www.rfc-editor.org/rfc/rfc9110#section-5.2
|
||||||
|
// https://github.com/nodejs/node/issues/45699
|
||||||
|
// allow authorization multiple fields
|
||||||
|
// Make a delimited list
|
||||||
|
if (dest[field] === undefined) {
|
||||||
|
dest[field] = value;
|
||||||
|
} else {
|
||||||
|
dest[field] += ', ' + value;
|
||||||
|
}
|
||||||
} else if (dest[field] === undefined) {
|
} else if (dest[field] === undefined) {
|
||||||
// Drop duplicates
|
// Drop duplicates
|
||||||
dest[field] = value;
|
dest[field] = value;
|
||||||
|
@ -482,6 +482,12 @@ function storeHTTPOptions(options) {
|
|||||||
} else {
|
} else {
|
||||||
this.requireHostHeader = true;
|
this.requireHostHeader = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const joinDuplicateHeaders = options.joinDuplicateHeaders;
|
||||||
|
if (joinDuplicateHeaders !== undefined) {
|
||||||
|
validateBoolean(joinDuplicateHeaders, 'options.joinDuplicateHeaders');
|
||||||
|
}
|
||||||
|
this.joinDuplicateHeaders = joinDuplicateHeaders;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupConnectionsTracking(server) {
|
function setupConnectionsTracking(server) {
|
||||||
|
@ -52,7 +52,8 @@ let maxHeaderSize;
|
|||||||
* ServerResponse?: ServerResponse;
|
* ServerResponse?: ServerResponse;
|
||||||
* insecureHTTPParser?: boolean;
|
* insecureHTTPParser?: boolean;
|
||||||
* maxHeaderSize?: number;
|
* maxHeaderSize?: number;
|
||||||
* requireHostHeader?: boolean
|
* requireHostHeader?: boolean;
|
||||||
|
* joinDuplicateHeaders?: boolean;
|
||||||
* }} [opts]
|
* }} [opts]
|
||||||
* @param {Function} [requestListener]
|
* @param {Function} [requestListener]
|
||||||
* @returns {Server}
|
* @returns {Server}
|
||||||
|
@ -0,0 +1,83 @@
|
|||||||
|
'use strict';
|
||||||
|
const common = require('../common');
|
||||||
|
const assert = require('assert');
|
||||||
|
const http = require('http');
|
||||||
|
|
||||||
|
{
|
||||||
|
const server = http.createServer({
|
||||||
|
requireHostHeader: false,
|
||||||
|
joinDuplicateHeaders: true
|
||||||
|
}, common.mustCall((req, res) => {
|
||||||
|
assert.strictEqual(req.headers.authorization, '1, 2');
|
||||||
|
assert.strictEqual(req.headers.cookie, 'foo; bar');
|
||||||
|
res.writeHead(200, ['authorization', '3', 'authorization', '4', 'cookie', 'foo', 'cookie', 'bar']);
|
||||||
|
res.end();
|
||||||
|
}));
|
||||||
|
|
||||||
|
server.listen(0, common.mustCall(() => {
|
||||||
|
http.get({
|
||||||
|
port: server.address().port,
|
||||||
|
headers: ['authorization', '1', 'authorization', '2', 'cookie', 'foo', 'cookie', 'bar'],
|
||||||
|
joinDuplicateHeaders: true
|
||||||
|
}, (res) => {
|
||||||
|
assert.strictEqual(res.statusCode, 200);
|
||||||
|
assert.strictEqual(res.headers.authorization, '3, 4');
|
||||||
|
assert.strictEqual(res.headers.cookie, 'foo; bar');
|
||||||
|
res.resume().on('end', common.mustCall(() => {
|
||||||
|
server.close();
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Server joinDuplicateHeaders false
|
||||||
|
const server = http.createServer({
|
||||||
|
requireHostHeader: false,
|
||||||
|
joinDuplicateHeaders: false
|
||||||
|
}, common.mustCall((req, res) => {
|
||||||
|
assert.strictEqual(req.headers.authorization, '1'); // non joined value
|
||||||
|
res.writeHead(200, ['authorization', '3', 'authorization', '4']);
|
||||||
|
res.end();
|
||||||
|
}));
|
||||||
|
|
||||||
|
server.listen(0, common.mustCall(() => {
|
||||||
|
http.get({
|
||||||
|
port: server.address().port,
|
||||||
|
headers: ['authorization', '1', 'authorization', '2'],
|
||||||
|
joinDuplicateHeaders: true
|
||||||
|
}, (res) => {
|
||||||
|
assert.strictEqual(res.statusCode, 200);
|
||||||
|
assert.strictEqual(res.headers.authorization, '3, 4');
|
||||||
|
res.resume().on('end', common.mustCall(() => {
|
||||||
|
server.close();
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Client joinDuplicateHeaders false
|
||||||
|
const server = http.createServer({
|
||||||
|
requireHostHeader: false,
|
||||||
|
joinDuplicateHeaders: true
|
||||||
|
}, common.mustCall((req, res) => {
|
||||||
|
assert.strictEqual(req.headers.authorization, '1, 2');
|
||||||
|
res.writeHead(200, ['authorization', '3', 'authorization', '4']);
|
||||||
|
res.end();
|
||||||
|
}));
|
||||||
|
|
||||||
|
server.listen(0, common.mustCall(() => {
|
||||||
|
http.get({
|
||||||
|
port: server.address().port,
|
||||||
|
headers: ['authorization', '1', 'authorization', '2'],
|
||||||
|
joinDuplicateHeaders: false
|
||||||
|
}, (res) => {
|
||||||
|
assert.strictEqual(res.statusCode, 200);
|
||||||
|
assert.strictEqual(res.headers.authorization, '3'); // non joined value
|
||||||
|
res.resume().on('end', common.mustCall(() => {
|
||||||
|
server.close();
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user