diff --git a/lib/dispatcher/client-h2.js b/lib/dispatcher/client-h2.js index 6b48ab9904e..1fedae88715 100644 --- a/lib/dispatcher/client-h2.js +++ b/lib/dispatcher/client-h2.js @@ -55,14 +55,20 @@ const { } = http2 function parseH2Headers (headers) { - // set-cookie is always an array. Duplicates are added to the array. - // For duplicate cookie headers, the values are joined together with '; '. - headers = Object.entries(headers).flat(2) - const result = [] - for (const header of headers) { - result.push(Buffer.from(header)) + for (const [name, value] of Object.entries(headers)) { + // h2 may concat the header value by array + // e.g. Set-Cookie + if (Array.isArray(value)) { + for (const subvalue of value) { + // we need to provide each header value of header name + // because the headers handler expect name-value pair + result.push(Buffer.from(name), Buffer.from(subvalue)) + } + } else { + result.push(Buffer.from(name), Buffer.from(value)) + } } return result diff --git a/test/fetch/http2.js b/test/fetch/http2.js index 1cef1d00ce6..f64756de788 100644 --- a/test/fetch/http2.js +++ b/test/fetch/http2.js @@ -462,3 +462,48 @@ test('Issue #2386', async (t) => { controller.abort() ok(true) }) + +test('Issue #3046', async (t) => { + const server = createSecureServer(pem) + + const { strictEqual, deepStrictEqual } = tspl(t, { plan: 6 }) + + server.on('stream', async (stream, headers) => { + strictEqual(headers[':method'], 'GET') + strictEqual(headers[':path'], '/') + strictEqual(headers[':scheme'], 'https') + + stream.respond({ + 'set-cookie': ['hello=world', 'foo=bar'], + 'content-type': 'text/html; charset=utf-8', + ':status': 200 + }) + + stream.end('

Hello World

') + }) + + server.listen(0) + await once(server, 'listening') + + const client = new Client(`https://localhost:${server.address().port}`, { + connect: { + rejectUnauthorized: false + }, + allowH2: true + }) + + t.after(closeClientAndServerAsPromise(client, server)) + + const response = await fetch( + `https://localhost:${server.address().port}/`, + // Needs to be passed to disable the reject unauthorized + { + method: 'GET', + dispatcher: client + } + ) + + strictEqual(response.status, 200) + strictEqual(response.headers.get('content-type'), 'text/html; charset=utf-8') + deepStrictEqual(response.headers.getSetCookie(), ['hello=world', 'foo=bar']) +})