diff --git a/lib/core/util.js b/lib/core/util.js index abfa156f153..be2c1a7320d 100644 --- a/lib/core/util.js +++ b/lib/core/util.js @@ -431,22 +431,17 @@ function parseHeaders (headers, obj) { val = [val] obj[key] = val } - val.push(headers[i + 1].toString('utf8')) + val.push(headers[i + 1].toString('latin1')) } else { const headersValue = headers[i + 1] if (typeof headersValue === 'string') { obj[key] = headersValue } else { - obj[key] = Array.isArray(headersValue) ? headersValue.map(x => x.toString('utf8')) : headersValue.toString('utf8') + obj[key] = Array.isArray(headersValue) ? headersValue.map(x => x.toString('latin1')) : headersValue.toString('latin1') } } } - // See https://github.com/nodejs/node/pull/46528 - if ('content-length' in obj && 'content-disposition' in obj) { - obj['content-disposition'] = Buffer.from(obj['content-disposition']).toString('latin1') - } - return obj } @@ -461,34 +456,20 @@ function parseRawHeaders (headers) { */ const ret = new Array(headersLength) - let hasContentLength = false - let contentDispositionIdx = -1 let key let val - let kLen = 0 for (let n = 0; n < headersLength; n += 2) { key = headers[n] val = headers[n + 1] typeof key !== 'string' && (key = key.toString()) - typeof val !== 'string' && (val = val.toString('utf8')) + typeof val !== 'string' && (val = val.toString('latin1')) - kLen = key.length - if (kLen === 14 && key[7] === '-' && (key === 'content-length' || key.toLowerCase() === 'content-length')) { - hasContentLength = true - } else if (kLen === 19 && key[7] === '-' && (key === 'content-disposition' || key.toLowerCase() === 'content-disposition')) { - contentDispositionIdx = n + 1 - } ret[n] = key ret[n + 1] = val } - // See https://github.com/nodejs/node/pull/46528 - if (hasContentLength && contentDispositionIdx !== -1) { - ret[contentDispositionIdx] = Buffer.from(ret[contentDispositionIdx]).toString('latin1') - } - return ret } diff --git a/test/node-test/util.js b/test/node-test/util.js index f488f866c45..c3f363ff40a 100644 --- a/test/node-test/util.js +++ b/test/node-test/util.js @@ -87,11 +87,59 @@ test('parseHeaders', () => { assert.deepEqual(util.parseHeaders([Buffer.from('key'), [Buffer.from('value1'), Buffer.from('value2'), Buffer.from('value3')]]), { key: ['value1', 'value2', 'value3'] }) }) +test('parseHeaders decodes values as latin1, not utf8', () => { + // These bytes (0xE2, 0x80, 0xA6) are the UTF-8 encoding of U+2026 (ellipsis) + // When decoded as latin1, they should be 3 separate characters: â, €, ¦ + // When incorrectly decoded as UTF-8, they would be a single character: … + const latin1Bytes = Buffer.from([0xe2, 0x80, 0xa6]) + const result = util.parseHeaders([Buffer.from('x-test'), latin1Bytes]) + + assert.strictEqual(result['x-test'].length, 3) + assert.strictEqual(result['x-test'].charCodeAt(0), 0xe2) + assert.strictEqual(result['x-test'].charCodeAt(1), 0x80) + assert.strictEqual(result['x-test'].charCodeAt(2), 0xa6) +}) + +test('parseHeaders decodes duplicate header values as latin1', () => { + const latin1Bytes = Buffer.from([0xe2, 0x80, 0xa6]) + const result = util.parseHeaders([ + Buffer.from('x-test'), Buffer.from('first'), + Buffer.from('x-test'), latin1Bytes + ]) + + assert.deepEqual(result['x-test'][0], 'first') + assert.strictEqual(result['x-test'][1].length, 3) + assert.strictEqual(result['x-test'][1].charCodeAt(0), 0xe2) +}) + +test('parseHeaders decodes array header values as latin1', () => { + const latin1Bytes = Buffer.from([0xe2, 0x80, 0xa6]) + const result = util.parseHeaders([Buffer.from('x-test'), [latin1Bytes, latin1Bytes]]) + + assert.strictEqual(result['x-test'].length, 2) + assert.strictEqual(result['x-test'][0].length, 3) + assert.strictEqual(result['x-test'][0].charCodeAt(0), 0xe2) +}) + test('parseRawHeaders', () => { assert.deepEqual(util.parseRawHeaders(['key', 'value', Buffer.from('key'), Buffer.from('value')]), ['key', 'value', 'key', 'value']) assert.deepEqual(util.parseRawHeaders(['content-length', 'value', 'content-disposition', 'form-data; name="fieldName"']), ['content-length', 'value', 'content-disposition', 'form-data; name="fieldName"']) }) +test('parseRawHeaders decodes values as latin1, not utf8', () => { + // These bytes (0xE2, 0x80, 0xA6) are the UTF-8 encoding of U+2026 (ellipsis) + // When decoded as latin1, they should be 3 separate characters + // When incorrectly decoded as UTF-8, they would be a single character + const latin1Bytes = Buffer.from([0xe2, 0x80, 0xa6]) + const result = util.parseRawHeaders([Buffer.from('x-test'), latin1Bytes]) + + assert.strictEqual(result[0], 'x-test') + assert.strictEqual(result[1].length, 3) + assert.strictEqual(result[1].charCodeAt(0), 0xe2) + assert.strictEqual(result[1].charCodeAt(1), 0x80) + assert.strictEqual(result[1].charCodeAt(2), 0xa6) +}) + test('serializePathWithQuery', () => { const tests = [ [{ id: BigInt(123456) }, 'id=123456'],