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 069a30b

Browse filesBrowse files
marco-ippolitoRafaelGSS
authored andcommitted
http: join authorization headers
PR-URL: #45982 Backport-PR-URL: #46240 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 ed69a3a commit 069a30b
Copy full SHA for 069a30b

File tree

Expand file treeCollapse file tree

7 files changed

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

7 files changed

+129
-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
@@ -2426,6 +2426,13 @@ as an argument to any listeners on the event.
24262426
<!-- YAML
24272427
added: v0.1.5
24282428
changes:
2429+
- version: REPLACEME
2430+
pr-url: https://github.com/nodejs/node/pull/45982
2431+
description: >-
2432+
The `joinDuplicateHeaders` option in the `http.request()`
2433+
and `http.createServer()` functions ensures that duplicate
2434+
headers are not discarded, but rather combined using a
2435+
comma separator, in accordance with RFC 9110 Section 5.3.
24292436
- version: v15.1.0
24302437
pr-url: https://github.com/nodejs/node/pull/35281
24312438
description: >-
@@ -2455,6 +2462,10 @@ header name:
24552462
`etag`, `expires`, `from`, `host`, `if-modified-since`, `if-unmodified-since`,
24562463
`last-modified`, `location`, `max-forwards`, `proxy-authorization`, `referer`,
24572464
`retry-after`, `server`, or `user-agent` are discarded.
2465+
To allow duplicate values of the headers listed above to be joined,
2466+
use the option `joinDuplicateHeaders` in [`http.request()`][]
2467+
and [`http.createServer()`][]. See RFC 9110 Section 5.3 for more
2468+
information.
24582469
* `set-cookie` is always an array. Duplicates are added to the array.
24592470
* For duplicate `cookie` headers, the values are joined together with `; `.
24602471
* For all other headers, the values are joined together with `, `.
@@ -3182,6 +3193,10 @@ changes:
31823193
* `requestTimeout`: Sets the timeout value in milliseconds for receiving
31833194
the entire request from the client.
31843195
See [`server.requestTimeout`][] for more information.
3196+
* `joinDuplicateHeaders` {boolean} It joins the field line values of multiple
3197+
headers in a request with `, ` instead of discarding the duplicates.
3198+
See [`message.headers`][] for more information.
3199+
**Default:** `false`.
31853200
* `ServerResponse` {http.ServerResponse} Specifies the `ServerResponse` class
31863201
to be used. Useful for extending the original `ServerResponse`. **Default:**
31873202
`ServerResponse`.
@@ -3437,6 +3452,10 @@ changes:
34373452
* `uniqueHeaders` {Array} A list of request headers that should be sent
34383453
only once. If the header's value is an array, the items will be joined
34393454
using `; `.
3455+
* `joinDuplicateHeaders` {boolean} It joins the field line values of
3456+
multiple headers in a request with `, ` instead of discarding
3457+
the duplicates. See [`message.headers`][] for more information.
3458+
**Default:** `false`.
34403459
* `callback` {Function}
34413460
* Returns: {http.ClientRequest}
34423461

@@ -3750,6 +3769,7 @@ Set the maximum number of idle HTTP parsers. **Default:** `1000`.
37503769
[`http.IncomingMessage`]: #class-httpincomingmessage
37513770
[`http.ServerResponse`]: #class-httpserverresponse
37523771
[`http.Server`]: #class-httpserver
3772+
[`http.createServer()`]: #httpcreateserveroptions-requestlistener
37533773
[`http.get()`]: #httpgetoptions-callback
37543774
[`http.globalAgent`]: #httpglobalagent
37553775
[`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 {
@@ -229,6 +230,12 @@ function ClientRequest(input, options, cb) {
229230
}
230231
this.insecureHTTPParser = insecureHTTPParser;
231232

233+
if (options.joinDuplicateHeaders !== undefined) {
234+
validateBoolean(options.joinDuplicateHeaders, 'options.joinDuplicateHeaders');
235+
}
236+
237+
this.joinDuplicateHeaders = options.joinDuplicateHeaders;
238+
232239
this.path = options.path || '/';
233240
if (cb) {
234241
this.once('response', cb);
@@ -811,6 +818,8 @@ function tickOnSocket(req, socket) {
811818
parser.maxHeaderPairs = req.maxHeadersCount << 1;
812819
}
813820

821+
parser.joinDuplicateHeaders = req.joinDuplicateHeaders;
822+
814823
parser.onIncoming = parserOnIncomingClient;
815824
socket.on('error', socketErrorListener);
816825
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
@@ -469,6 +469,12 @@ function storeHTTPOptions(options) {
469469
} else {
470470
this.connectionsCheckingInterval = 30_000; // 30 seconds
471471
}
472+
473+
const joinDuplicateHeaders = options.joinDuplicateHeaders;
474+
if (joinDuplicateHeaders !== undefined) {
475+
validateBoolean(joinDuplicateHeaders, 'options.joinDuplicateHeaders');
476+
}
477+
this.joinDuplicateHeaders = joinDuplicateHeaders;
472478
}
473479

474480
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
+80Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
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+
joinDuplicateHeaders: true
9+
}, common.mustCall((req, res) => {
10+
assert.strictEqual(req.headers.authorization, '1, 2');
11+
assert.strictEqual(req.headers.cookie, 'foo; bar');
12+
res.writeHead(200, ['authorization', '3', 'authorization', '4', 'cookie', 'foo', 'cookie', 'bar']);
13+
res.end();
14+
}));
15+
16+
server.listen(0, common.mustCall(() => {
17+
http.get({
18+
port: server.address().port,
19+
headers: ['authorization', '1', 'authorization', '2', 'cookie', 'foo', 'cookie', 'bar'],
20+
joinDuplicateHeaders: true
21+
}, (res) => {
22+
assert.strictEqual(res.statusCode, 200);
23+
assert.strictEqual(res.headers.authorization, '3, 4');
24+
assert.strictEqual(res.headers.cookie, 'foo; bar');
25+
res.resume().on('end', common.mustCall(() => {
26+
server.close();
27+
}));
28+
});
29+
}));
30+
}
31+
32+
{
33+
// Server joinDuplicateHeaders false
34+
const server = http.createServer({
35+
joinDuplicateHeaders: false
36+
}, common.mustCall((req, res) => {
37+
assert.strictEqual(req.headers.authorization, '1'); // non joined value
38+
res.writeHead(200, ['authorization', '3', 'authorization', '4']);
39+
res.end();
40+
}));
41+
42+
server.listen(0, common.mustCall(() => {
43+
http.get({
44+
port: server.address().port,
45+
headers: ['authorization', '1', 'authorization', '2'],
46+
joinDuplicateHeaders: true
47+
}, (res) => {
48+
assert.strictEqual(res.statusCode, 200);
49+
assert.strictEqual(res.headers.authorization, '3, 4');
50+
res.resume().on('end', common.mustCall(() => {
51+
server.close();
52+
}));
53+
});
54+
}));
55+
}
56+
57+
{
58+
// Client joinDuplicateHeaders false
59+
const server = http.createServer({
60+
joinDuplicateHeaders: true
61+
}, common.mustCall((req, res) => {
62+
assert.strictEqual(req.headers.authorization, '1, 2');
63+
res.writeHead(200, ['authorization', '3', 'authorization', '4']);
64+
res.end();
65+
}));
66+
67+
server.listen(0, common.mustCall(() => {
68+
http.get({
69+
port: server.address().port,
70+
headers: ['authorization', '1', 'authorization', '2'],
71+
joinDuplicateHeaders: false
72+
}, (res) => {
73+
assert.strictEqual(res.statusCode, 200);
74+
assert.strictEqual(res.headers.authorization, '3'); // non joined value
75+
res.resume().on('end', common.mustCall(() => {
76+
server.close();
77+
}));
78+
});
79+
}));
80+
}

0 commit comments

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