From 4b76ccea958f969adf68833a64ce0883eed9dd30 Mon Sep 17 00:00:00 2001 From: Tim Perry <1526883+pimterry@users.noreply.github.com> Date: Thu, 14 Dec 2023 15:26:30 +0100 Subject: [PATCH] http: preserve raw header duplicates in writeHead after setHeader calls writeHead accepts a raw header array, which is intended to allow directly specifying raw header details, such as ordering, duplicates and header key casing. When used by itself this works correctly. However, if setHeader was called first, it effectively changed the behaviour of subsequent writeHead calls, so that even if a raw header array was provided, duplicates were collapsed, losing raw header data. This change preserves the raw headers passed to writeHead, while still maintaining the 'writeHead overwrites setHeader' behaviour. PR-URL: https://github.com/nodejs/node/pull/50394 Reviewed-By: Matteo Collina Reviewed-By: James M Snell --- lib/_http_server.js | 11 ++++- .../test-http-write-head-after-set-header.js | 47 +++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 test/parallel/test-http-write-head-after-set-header.js diff --git a/lib/_http_server.js b/lib/_http_server.js index 3e19f1ba78e7cc..775090b373f696 100644 --- a/lib/_http_server.js +++ b/lib/_http_server.js @@ -373,9 +373,18 @@ function writeHead(statusCode, reason, obj) { throw new ERR_INVALID_ARG_VALUE('headers', obj); } + // Headers in obj should override previous headers but still + // allow explicit duplicates. To do so, we first remove any + // existing conflicts, then use appendHeader. + + for (let n = 0; n < obj.length; n += 2) { + k = obj[n + 0]; + this.removeHeader(k); + } + for (let n = 0; n < obj.length; n += 2) { k = obj[n + 0]; - if (k) this.setHeader(k, obj[n + 1]); + if (k) this.appendHeader(k, obj[n + 1]); } } else if (obj) { const keys = ObjectKeys(obj); diff --git a/test/parallel/test-http-write-head-after-set-header.js b/test/parallel/test-http-write-head-after-set-header.js new file mode 100644 index 00000000000000..019a0aa50a2540 --- /dev/null +++ b/test/parallel/test-http-write-head-after-set-header.js @@ -0,0 +1,47 @@ +'use strict'; + +const common = require('../common'); +const Countdown = require('../common/countdown'); +const assert = require('assert'); +const { createServer, request } = require('http'); + +const server = createServer(common.mustCall((req, res) => { + if (req.url.includes('setHeader')) { + res.setHeader('set-val', 'abc'); + } + + res.writeHead(200, [ + 'array-val', '1', + 'array-val', '2', + ]); + + res.end(); +}, 2)); + +const countdown = new Countdown(2, () => server.close()); + +server.listen(0, common.mustCall(() => { + request({ + port: server.address().port + }, common.mustCall((res) => { + assert.deepStrictEqual(res.rawHeaders.slice(0, 4), [ + 'array-val', '1', + 'array-val', '2', + ]); + + countdown.dec(); + })).end(); + + request({ + port: server.address().port, + path: '/?setHeader' + }, common.mustCall((res) => { + assert.deepStrictEqual(res.rawHeaders.slice(0, 6), [ + 'set-val', 'abc', + 'array-val', '1', + 'array-val', '2', + ]); + + countdown.dec(); + })).end(); +}));