Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 3 additions & 22 deletions lib/core/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand All @@ -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
}

Expand Down
48 changes: 48 additions & 0 deletions test/node-test/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
Expand Down
Loading