| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2413,6 +2413,13 @@ as an argument to any listeners on the event. | |||
| 2413 | 2413 | <!-- YAML | |
| 2414 | 2414 | added: v0.1.5 | |
| 2415 | 2415 | 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. | ||
| 2416 | 2423 | - version: v15.1.0 | |
| 2417 | 2424 | pr-url: https://github.com/nodejs/node/pull/35281 | |
| 2418 | 2425 | description: >- | |
@@ -2442,6 +2449,10 @@ header name: | |||
| 2442 | 2449 | `etag`, `expires`, `from`, `host`, `if-modified-since`, `if-unmodified-since`, | |
| 2443 | 2450 | `last-modified`, `location`, `max-forwards`, `proxy-authorization`, `referer`, | |
| 2444 | 2451 | `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. | ||
| 2445 | 2456 | * `set-cookie` is always an array. Duplicates are added to the array. | |
| 2446 | 2457 | * For duplicate `cookie` headers, the values are joined together with `; `. | |
| 2447 | 2458 | * For all other headers, the values are joined together with `, `. | |
@@ -3149,6 +3160,10 @@ changes: | |||
| 3149 | 3160 | * `requestTimeout`: Sets the timeout value in milliseconds for receiving | |
| 3150 | 3161 | the entire request from the client. | |
| 3151 | 3162 | 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`. | ||
| 3152 | 3167 | * `ServerResponse` {http.ServerResponse} Specifies the `ServerResponse` class | |
| 3153 | 3168 | to be used. Useful for extending the original `ServerResponse`. **Default:** | |
| 3154 | 3169 | `ServerResponse`. | |
@@ -3399,6 +3414,10 @@ changes: | |||
| 3399 | 3414 | * `uniqueHeaders` {Array} A list of request headers that should be sent | |
| 3400 | 3415 | only once. If the header's value is an array, the items will be joined | |
| 3401 | 3416 | 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`. | ||
| 3402 | 3421 | * `callback` {Function} | |
| 3403 | 3422 | * Returns: {http.ClientRequest} | |
| 3404 | 3423 | ||
@@ -3710,6 +3729,7 @@ Set the maximum number of idle HTTP parsers. **Default:** `1000`. | |||
| 3710 | 3729 | [`http.IncomingMessage`]: #class-httpincomingmessage | |
| 3711 | 3730 | [`http.ServerResponse`]: #class-httpserverresponse | |
| 3712 | 3731 | [`http.Server`]: #class-httpserver | |
| 3732 | + [`http.createServer()`]: #httpcreateserveroptions-requestlistener | ||
| 3713 | 3733 | [`http.get()`]: #httpgetoptions-callback | |
| 3714 | 3734 | [`http.globalAgent`]: #httpglobalagent | |
| 3715 | 3735 | [`http.request()`]: #httprequestoptions-callback | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -82,6 +82,7 @@ const { | |||
| 82 | 82 | } = codes; | |
| 83 | 83 | const { | |
| 84 | 84 | validateInteger, | |
| 85 | + validateBoolean, | ||
| 85 | 86 | } = require('internal/validators'); | |
| 86 | 87 | const { getTimerDuration } = require('internal/timers'); | |
| 87 | 88 | const { | |
@@ -234,6 +235,12 @@ function ClientRequest(input, options, cb) { | |||
| 234 | 235 | } | |
| 235 | 236 | this.insecureHTTPParser = insecureHTTPParser; | |
| 236 | 237 | ||
| 238 | + if (options.joinDuplicateHeaders !== undefined) { | ||
| 239 | + validateBoolean(options.joinDuplicateHeaders, 'options.joinDuplicateHeaders'); | ||
| 240 | + } | ||
| 241 | + | ||
| 242 | + this.joinDuplicateHeaders = options.joinDuplicateHeaders; | ||
| 243 | + | ||
| 237 | 244 | this.path = options.path || '/'; | |
| 238 | 245 | if (cb) { | |
| 239 | 246 | this.once('response', cb); | |
@@ -818,6 +825,8 @@ function tickOnSocket(req, socket) { | |||
| 818 | 825 | parser.maxHeaderPairs = req.maxHeadersCount << 1; | |
| 819 | 826 | } | |
| 820 | 827 | ||
| 828 | + parser.joinDuplicateHeaders = req.joinDuplicateHeaders; | ||
| 829 | + | ||
| 821 | 830 | parser.onIncoming = parserOnIncomingClient; | |
| 822 | 831 | socket.on('error', socketErrorListener); | |
| 823 | 832 | socket.on('data', socketOnData); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -94,6 +94,8 @@ function parserOnHeadersComplete(versionMajor, versionMinor, headers, method, | |||
| 94 | 94 | incoming.httpVersionMajor = versionMajor; | |
| 95 | 95 | incoming.httpVersionMinor = versionMinor; | |
| 96 | 96 | incoming.httpVersion = `${versionMajor}.${versionMinor}`; | |
| 97 | + incoming.joinDuplicateHeaders = socket?.server?.joinDuplicateHeaders || | ||
| 98 | + parser.joinDuplicateHeaders; | ||
| 97 | 99 | incoming.url = url; | |
| 98 | 100 | incoming.upgrade = upgrade; | |
| 99 | 101 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -75,7 +75,7 @@ function IncomingMessage(socket) { | |||
| 75 | 75 | this[kTrailers] = null; | |
| 76 | 76 | this[kTrailersCount] = 0; | |
| 77 | 77 | this.rawTrailers = []; | |
| 78 | - | ||
| 78 | + this.joinDuplicateHeaders = false; | ||
| 79 | 79 | this.aborted = false; | |
| 80 | 80 | ||
| 81 | 81 | this.upgrade = null; | |
@@ -400,6 +400,16 @@ function _addHeaderLine(field, value, dest) { | |||
| 400 | 400 | } else { | |
| 401 | 401 | dest['set-cookie'] = [value]; | |
| 402 | 402 | } | |
| 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 | + } | ||
| 403 | 413 | } else if (dest[field] === undefined) { | |
| 404 | 414 | // Drop duplicates | |
| 405 | 415 | dest[field] = value; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -474,6 +474,12 @@ function storeHTTPOptions(options) { | |||
| 474 | 474 | } else { | |
| 475 | 475 | this.connectionsCheckingInterval = 30_000; // 30 seconds | |
| 476 | 476 | } | |
| 477 | + | ||
| 478 | + const joinDuplicateHeaders = options.joinDuplicateHeaders; | ||
| 479 | + if (joinDuplicateHeaders !== undefined) { | ||
| 480 | + validateBoolean(joinDuplicateHeaders, 'options.joinDuplicateHeaders'); | ||
| 481 | + } | ||
| 482 | + this.joinDuplicateHeaders = joinDuplicateHeaders; | ||
| 477 | 483 | } | |
| 478 | 484 | ||
| 479 | 485 | function setupConnectionsTracking(server) { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -52,6 +52,7 @@ let maxHeaderSize; | |||
| 52 | 52 | * ServerResponse?: ServerResponse; | |
| 53 | 53 | * insecureHTTPParser?: boolean; | |
| 54 | 54 | * maxHeaderSize?: number; | |
| 55 | + * joinDuplicateHeaders?: boolean; | ||
| 55 | 56 | * }} [opts] | |
| 56 | 57 | * @param {Function} [requestListener] | |
| 57 | 58 | * @returns {Server} | |
| Original file line number | Diff line number | Diff 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 | + } | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments