diff --git a/lib/util/cache.js b/lib/util/cache.js index a8f112168e6..c88db6292b3 100644 --- a/lib/util/cache.js +++ b/lib/util/cache.js @@ -42,15 +42,57 @@ function normalizeHeaders (opts) { headers = {} if (hasSafeIterator(opts.headers)) { - for (const x of opts.headers) { - if (!Array.isArray(x)) { - throw new Error('opts.headers is not a valid header map') + if (Array.isArray(opts.headers)) { + // Array format: could be flat alternating [k, v, k, v, ...] + // or array-of-pairs [[k, v], ...] + const first = opts.headers[0] + if (Array.isArray(first)) { + for (const x of opts.headers) { + if (!Array.isArray(x)) { + throw new Error('opts.headers is not a valid header map') + } + const [key, val] = x + if (typeof key !== 'string' || typeof val !== 'string') { + throw new Error('opts.headers is not a valid header map') + } + headers[key.toLowerCase()] = val + } + } else { + // Flat alternating array [k, v, k, v, ...] + const len = opts.headers.length + if (len % 2 !== 0) { + throw new Error('opts.headers is not a valid header map') + } + for (let i = 0; i < len; i += 2) { + const key = opts.headers[i] + const val = opts.headers[i + 1] + if (typeof key !== 'string' || (typeof val !== 'string' && !Array.isArray(val))) { + throw new Error('opts.headers is not a valid header map') + } + if (typeof val === 'string') { + headers[key.toLowerCase()] = val + } else { + const mapped = [] + for (let j = 0; j < val.length; j++) { + const v = val[j] + mapped.push(typeof v === 'string' ? v : v.toString('latin1')) + } + headers[key.toLowerCase()] = mapped + } + } } - const [key, val] = x - if (typeof key !== 'string' || typeof val !== 'string') { - throw new Error('opts.headers is not a valid header map') + } else { + // Non-array iterable (e.g. Map) — use original iteration logic + for (const x of opts.headers) { + if (!Array.isArray(x)) { + throw new Error('opts.headers is not a valid header map') + } + const [key, val] = x + if (typeof key !== 'string' || typeof val !== 'string') { + throw new Error('opts.headers is not a valid header map') + } + headers[key.toLowerCase()] = val } - headers[key.toLowerCase()] = val } } else { for (const key of Object.keys(opts.headers)) { diff --git a/test/cache-interceptor/cache-utils.js b/test/cache-interceptor/cache-utils.js index 7b66e66d3aa..70f15958de4 100644 --- a/test/cache-interceptor/cache-utils.js +++ b/test/cache-interceptor/cache-utils.js @@ -42,3 +42,67 @@ test('normalizeHeaders handles headers from Map', (t) => { strictEqual(headers['x-test'], 'ok') }) + +test('normalizeHeaders handles empty array', (t) => { + const { deepEqual } = tspl(t, { plan: 1 }) + + const headers = normalizeHeaders({ headers: [] }) + + deepEqual(headers, {}) +}) + +test('normalizeHeaders handles flat alternating array (single header)', (t) => { + const { strictEqual } = tspl(t, { plan: 1 }) + + const headers = normalizeHeaders({ + headers: ['host', 'localhost'] + }) + + strictEqual(headers.host, 'localhost') +}) + +test('normalizeHeaders handles flat alternating array (multiple headers)', (t) => { + const { deepEqual } = tspl(t, { plan: 1 }) + + const headers = normalizeHeaders({ + headers: ['host', 'localhost', 'content-type', 'application/json'] + }) + + deepEqual(headers, { host: 'localhost', 'content-type': 'application/json' }) +}) + +test('normalizeHeaders handles flat alternating array with array values', (t) => { + const { deepEqual } = tspl(t, { plan: 1 }) + + const headers = normalizeHeaders({ + headers: ['accept', ['application/json', 'text/plain']] + }) + + deepEqual(headers, { accept: ['application/json', 'text/plain'] }) +}) + +test('normalizeHeaders handles array-of-pairs (existing behavior)', (t) => { + const { strictEqual } = tspl(t, { plan: 1 }) + + const headers = normalizeHeaders({ + headers: [['host', 'localhost']] + }) + + strictEqual(headers.host, 'localhost') +}) + +test('normalizeHeaders throws on odd-length flat array', (t) => { + const { throws } = require('node:assert') + + throws(() => normalizeHeaders({ headers: ['host'] }), { + message: 'opts.headers is not a valid header map' + }) +}) + +test('normalizeHeaders throws on non-string key in flat array', (t) => { + const { throws } = require('node:assert') + + throws(() => normalizeHeaders({ headers: [42, 'value'] }), { + message: 'opts.headers is not a valid header map' + }) +})