Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 4df1fcc

Browse filesBrowse files
marco-ippolitojuanarbol
authored andcommitted
http: join authorization headers
PR-URL: #45982 Fixes: #45699 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: Paolo Insogna <paolo@cowtech.it>
1 parent 41f5a29 commit 4df1fcc
Copy full SHA for 4df1fcc

File tree

Expand file treeCollapse file tree

7 files changed

+132
-1
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

7 files changed

+132
-1
lines changed
Open diff view settings
Collapse file

‎doc/api/http.md‎

Copy file name to clipboardExpand all lines: doc/api/http.md
+20Lines changed: 20 additions & 0 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -2413,6 +2413,13 @@ as an argument to any listeners on the event.
24132413
<!-- YAML
24142414
added: v0.1.5
24152415
changes:
2416+
- version: REPLACEME
2417+
pr-url: https://github.com/nodejs/node/pull/45982
2418+
description: >-
2419+
The `joinDuplicateHeaders` option in the `http.request()`
2420+
and `http.createServer()` functions ensures that duplicate
2421+
headers are not discarded, but rather combined using a
2422+
comma separator, in accordance with RFC 9110 Section 5.3.
24162423
- version: v15.1.0
24172424
pr-url: https://github.com/nodejs/node/pull/35281
24182425
description: >-
@@ -2442,6 +2449,10 @@ header name:
24422449
`etag`, `expires`, `from`, `host`, `if-modified-since`, `if-unmodified-since`,
24432450
`last-modified`, `location`, `max-forwards`, `proxy-authorization`, `referer`,
24442451
`retry-after`, `server`, or `user-agent` are discarded.
2452+
To allow duplicate values of the headers listed above to be joined,
2453+
use the option `joinDuplicateHeaders` in [`http.request()`][]
2454+
and [`http.createServer()`][]. See RFC 9110 Section 5.3 for more
2455+
information.
24452456
* `set-cookie` is always an array. Duplicates are added to the array.
24462457
* For duplicate `cookie` headers, the values are joined together with `; `.
24472458
* For all other headers, the values are joined together with `, `.
@@ -3149,6 +3160,10 @@ changes:
31493160
* `requestTimeout`: Sets the timeout value in milliseconds for receiving
31503161
the entire request from the client.
31513162
See [`server.requestTimeout`][] for more information.
3163+
* `joinDuplicateHeaders` {boolean} It joins the field line values of multiple
3164+
headers in a request with `, ` instead of discarding the duplicates.
3165+
See [`message.headers`][] for more information.
3166+
**Default:** `false`.
31523167
* `ServerResponse` {http.ServerResponse} Specifies the `ServerResponse` class
31533168
to be used. Useful for extending the original `ServerResponse`. **Default:**
31543169
`ServerResponse`.
@@ -3399,6 +3414,10 @@ changes:
33993414
* `uniqueHeaders` {Array} A list of request headers that should be sent
34003415
only once. If the header's value is an array, the items will be joined
34013416
using `; `.
3417+
* `joinDuplicateHeaders` {boolean} It joins the field line values of
3418+
multiple headers in a request with `, ` instead of discarding
3419+
the duplicates. See [`message.headers`][] for more information.
3420+
**Default:** `false`.
34023421
* `callback` {Function}
34033422
* Returns: {http.ClientRequest}
34043423

@@ -3710,6 +3729,7 @@ Set the maximum number of idle HTTP parsers. **Default:** `1000`.
37103729
[`http.IncomingMessage`]: #class-httpincomingmessage
37113730
[`http.ServerResponse`]: #class-httpserverresponse
37123731
[`http.Server`]: #class-httpserver
3732+
[`http.createServer()`]: #httpcreateserveroptions-requestlistener
37133733
[`http.get()`]: #httpgetoptions-callback
37143734
[`http.globalAgent`]: #httpglobalagent
37153735
[`http.request()`]: #httprequestoptions-callback
Collapse file

‎lib/_http_client.js‎

Copy file name to clipboardExpand all lines: lib/_http_client.js
+9Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ const {
8282
} = codes;
8383
const {
8484
validateInteger,
85+
validateBoolean,
8586
} = require('internal/validators');
8687
const { getTimerDuration } = require('internal/timers');
8788
const {
@@ -234,6 +235,12 @@ function ClientRequest(input, options, cb) {
234235
}
235236
this.insecureHTTPParser = insecureHTTPParser;
236237

238+
if (options.joinDuplicateHeaders !== undefined) {
239+
validateBoolean(options.joinDuplicateHeaders, 'options.joinDuplicateHeaders');
240+
}
241+
242+
this.joinDuplicateHeaders = options.joinDuplicateHeaders;
243+
237244
this.path = options.path || '/';
238245
if (cb) {
239246
this.once('response', cb);
@@ -818,6 +825,8 @@ function tickOnSocket(req, socket) {
818825
parser.maxHeaderPairs = req.maxHeadersCount << 1;
819826
}
820827

828+
parser.joinDuplicateHeaders = req.joinDuplicateHeaders;
829+
821830
parser.onIncoming = parserOnIncomingClient;
822831
socket.on('error', socketErrorListener);
823832
socket.on('data', socketOnData);
Collapse file

‎lib/_http_common.js‎

Copy file name to clipboardExpand all lines: lib/_http_common.js
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ function parserOnHeadersComplete(versionMajor, versionMinor, headers, method,
9494
incoming.httpVersionMajor = versionMajor;
9595
incoming.httpVersionMinor = versionMinor;
9696
incoming.httpVersion = `${versionMajor}.${versionMinor}`;
97+
incoming.joinDuplicateHeaders = socket?.server?.joinDuplicateHeaders ||
98+
parser.joinDuplicateHeaders;
9799
incoming.url = url;
98100
incoming.upgrade = upgrade;
99101

Collapse file

‎lib/_http_incoming.js‎

Copy file name to clipboardExpand all lines: lib/_http_incoming.js
+11-1Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ function IncomingMessage(socket) {
7575
this[kTrailers] = null;
7676
this[kTrailersCount] = 0;
7777
this.rawTrailers = [];
78-
78+
this.joinDuplicateHeaders = false;
7979
this.aborted = false;
8080

8181
this.upgrade = null;
@@ -400,6 +400,16 @@ function _addHeaderLine(field, value, dest) {
400400
} else {
401401
dest['set-cookie'] = [value];
402402
}
403+
} else if (this.joinDuplicateHeaders) {
404+
// RFC 9110 https://www.rfc-editor.org/rfc/rfc9110#section-5.2
405+
// https://github.com/nodejs/node/issues/45699
406+
// allow authorization multiple fields
407+
// Make a delimited list
408+
if (dest[field] === undefined) {
409+
dest[field] = value;
410+
} else {
411+
dest[field] += ', ' + value;
412+
}
403413
} else if (dest[field] === undefined) {
404414
// Drop duplicates
405415
dest[field] = value;
Collapse file

‎lib/_http_server.js‎

Copy file name to clipboardExpand all lines: lib/_http_server.js
+6Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,12 @@ function storeHTTPOptions(options) {
474474
} else {
475475
this.connectionsCheckingInterval = 30_000; // 30 seconds
476476
}
477+
478+
const joinDuplicateHeaders = options.joinDuplicateHeaders;
479+
if (joinDuplicateHeaders !== undefined) {
480+
validateBoolean(joinDuplicateHeaders, 'options.joinDuplicateHeaders');
481+
}
482+
this.joinDuplicateHeaders = joinDuplicateHeaders;
477483
}
478484

479485
function setupConnectionsTracking(server) {
Collapse file

‎lib/http.js‎

Copy file name to clipboardExpand all lines: lib/http.js
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ let maxHeaderSize;
5252
* ServerResponse?: ServerResponse;
5353
* insecureHTTPParser?: boolean;
5454
* maxHeaderSize?: number;
55+
* joinDuplicateHeaders?: boolean;
5556
* }} [opts]
5657
* @param {Function} [requestListener]
5758
* @returns {Server}
Collapse file
+83Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
'use strict';
2+
const common = require('../common');
3+
const assert = require('assert');
4+
const http = require('http');
5+
6+
{
7+
const server = http.createServer({
8+
requireHostHeader: false,
9+
joinDuplicateHeaders: true
10+
}, common.mustCall((req, res) => {
11+
assert.strictEqual(req.headers.authorization, '1, 2');
12+
assert.strictEqual(req.headers.cookie, 'foo; bar');
13+
res.writeHead(200, ['authorization', '3', 'authorization', '4', 'cookie', 'foo', 'cookie', 'bar']);
14+
res.end();
15+
}));
16+
17+
server.listen(0, common.mustCall(() => {
18+
http.get({
19+
port: server.address().port,
20+
headers: ['authorization', '1', 'authorization', '2', 'cookie', 'foo', 'cookie', 'bar'],
21+
joinDuplicateHeaders: true
22+
}, (res) => {
23+
assert.strictEqual(res.statusCode, 200);
24+
assert.strictEqual(res.headers.authorization, '3, 4');
25+
assert.strictEqual(res.headers.cookie, 'foo; bar');
26+
res.resume().on('end', common.mustCall(() => {
27+
server.close();
28+
}));
29+
});
30+
}));
31+
}
32+
33+
{
34+
// Server joinDuplicateHeaders false
35+
const server = http.createServer({
36+
requireHostHeader: false,
37+
joinDuplicateHeaders: false
38+
}, common.mustCall((req, res) => {
39+
assert.strictEqual(req.headers.authorization, '1'); // non joined value
40+
res.writeHead(200, ['authorization', '3', 'authorization', '4']);
41+
res.end();
42+
}));
43+
44+
server.listen(0, common.mustCall(() => {
45+
http.get({
46+
port: server.address().port,
47+
headers: ['authorization', '1', 'authorization', '2'],
48+
joinDuplicateHeaders: true
49+
}, (res) => {
50+
assert.strictEqual(res.statusCode, 200);
51+
assert.strictEqual(res.headers.authorization, '3, 4');
52+
res.resume().on('end', common.mustCall(() => {
53+
server.close();
54+
}));
55+
});
56+
}));
57+
}
58+
59+
{
60+
// Client joinDuplicateHeaders false
61+
const server = http.createServer({
62+
requireHostHeader: false,
63+
joinDuplicateHeaders: true
64+
}, common.mustCall((req, res) => {
65+
assert.strictEqual(req.headers.authorization, '1, 2');
66+
res.writeHead(200, ['authorization', '3', 'authorization', '4']);
67+
res.end();
68+
}));
69+
70+
server.listen(0, common.mustCall(() => {
71+
http.get({
72+
port: server.address().port,
73+
headers: ['authorization', '1', 'authorization', '2'],
74+
joinDuplicateHeaders: false
75+
}, (res) => {
76+
assert.strictEqual(res.statusCode, 200);
77+
assert.strictEqual(res.headers.authorization, '3'); // non joined value
78+
res.resume().on('end', common.mustCall(() => {
79+
server.close();
80+
}));
81+
});
82+
}));
83+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.