From c0ba75f784765e81722d0c2b31c08d273e04b0f6 Mon Sep 17 00:00:00 2001 From: KaKa Date: Wed, 8 Feb 2023 11:29:31 +0800 Subject: [PATCH] fix: content-disposition header parsing (#1911) --- lib/core/util.js | 23 +++++++++++-- lib/mock/mock-utils.js | 6 +++- test/issue-1903.js | 77 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+), 4 deletions(-) create mode 100644 test/issue-1903.js diff --git a/lib/core/util.js b/lib/core/util.js index 6f38ffc8a4d..3b9b56cb64d 100644 --- a/lib/core/util.js +++ b/lib/core/util.js @@ -213,25 +213,42 @@ function parseHeaders (headers, obj = {}) { for (let i = 0; i < headers.length; i += 2) { const key = headers[i].toString().toLowerCase() let val = obj[key] + + const encoding = key.length === 19 && key === 'content-disposition' + ? 'latin1' + : 'utf8' + if (!val) { if (Array.isArray(headers[i + 1])) { obj[key] = headers[i + 1] } else { - obj[key] = headers[i + 1].toString() + obj[key] = headers[i + 1].toString(encoding) } } else { if (!Array.isArray(val)) { val = [val] obj[key] = val } - val.push(headers[i + 1].toString()) + val.push(headers[i + 1].toString(encoding)) } } return obj } function parseRawHeaders (headers) { - return headers.map(header => header.toString()) + const ret = [] + for (let n = 0; n < headers.length; n += 2) { + const key = headers[n + 0].toString() + + const encoding = key.length === 19 && key.toLowerCase() === 'content-disposition' + ? 'latin1' + : 'utf8' + + const val = headers[n + 1].toString(encoding) + + ret.push(key, val) + } + return ret } function isBuffer (buffer) { diff --git a/lib/mock/mock-utils.js b/lib/mock/mock-utils.js index 9dc414d0a35..42ea185cc0e 100644 --- a/lib/mock/mock-utils.js +++ b/lib/mock/mock-utils.js @@ -188,7 +188,11 @@ function buildKey (opts) { } function generateKeyValues (data) { - return Object.entries(data).reduce((keyValuePairs, [key, value]) => [...keyValuePairs, key, value], []) + return Object.entries(data).reduce((keyValuePairs, [key, value]) => [ + ...keyValuePairs, + Buffer.from(`${key}`), + Array.isArray(value) ? value.map(x => Buffer.from(`${x}`)) : Buffer.from(`${value}`) + ], []) } /** diff --git a/test/issue-1903.js b/test/issue-1903.js new file mode 100644 index 00000000000..8478b2d53dd --- /dev/null +++ b/test/issue-1903.js @@ -0,0 +1,77 @@ +'use strict' + +const { createServer } = require('http') +const { test } = require('tap') +const { request } = require('..') + +function createPromise () { + const result = {} + result.promise = new Promise((resolve) => { + result.resolve = resolve + }) + return result +} + +test('should parse content-disposition consistencely', async (t) => { + t.plan(5) + + // create promise to allow server spinup in parallel + const spinup1 = createPromise() + const spinup2 = createPromise() + const spinup3 = createPromise() + + // variables to store content-disposition header + const header = [] + + const server = createServer((req, res) => { + res.writeHead(200, { + 'content-length': 2, + 'content-disposition': "attachment; filename='år.pdf'" + }) + header.push("attachment; filename='år.pdf'") + res.end('OK', spinup1.resolve) + }) + t.teardown(server.close.bind(server)) + server.listen(0, spinup1.resolve) + + const proxy1 = createServer(async (req, res) => { + const { statusCode, headers, body } = await request(`http://localhost:${server.address().port}`, { + method: 'GET' + }) + header.push(headers['content-disposition']) + delete headers['transfer-encoding'] + res.writeHead(statusCode, headers) + body.pipe(res) + }) + t.teardown(proxy1.close.bind(proxy1)) + proxy1.listen(0, spinup2.resolve) + + const proxy2 = createServer(async (req, res) => { + const { statusCode, headers, body } = await request(`http://localhost:${proxy1.address().port}`, { + method: 'GET' + }) + header.push(headers['content-disposition']) + delete headers['transfer-encoding'] + res.writeHead(statusCode, headers) + body.pipe(res) + }) + t.teardown(proxy2.close.bind(proxy2)) + proxy2.listen(0, spinup3.resolve) + + // wait until all server spinup + await Promise.all([spinup1.promise, spinup2.promise, spinup3.promise]) + + const { statusCode, headers, body } = await request(`http://localhost:${proxy2.address().port}`, { + method: 'GET' + }) + header.push(headers['content-disposition']) + t.equal(statusCode, 200) + t.equal(await body.text(), 'OK') + + // we check header + // must not be the same in first proxy + t.notSame(header[0], header[1]) + // chaining always the same value + t.equal(header[1], header[2]) + t.equal(header[2], header[3]) +})