Skip to content

Commit d94c36d

Browse files
trivikrmcollina
authored andcommitted
fix: reject malformed content-length request headers (#5060)
(cherry picked from commit c8d50b4)
1 parent 1badc5d commit d94c36d

4 files changed

Lines changed: 72 additions & 4 deletions

File tree

lib/core/request.js

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,21 @@ const { headerNameLowerCasedRecord } = require('./constants')
2727
// Verifies that a given path is valid does not contain control chars \x00 to \x20
2828
const invalidPathRegex = /[^\u0021-\u00ff]/
2929

30+
function isValidContentLengthHeaderValue (val) {
31+
if (typeof val !== 'string' || val.length === 0) {
32+
return false
33+
}
34+
35+
for (let i = 0; i < val.length; i++) {
36+
const charCode = val.charCodeAt(i)
37+
if (charCode < 48 || charCode > 57) {
38+
return false
39+
}
40+
}
41+
42+
return true
43+
}
44+
3045
const kHandler = Symbol('handler')
3146

3247
class Request {
@@ -402,10 +417,10 @@ function processHeader (request, key, val) {
402417
if (request.contentLength !== null) {
403418
throw new InvalidArgumentError('duplicate content-length header')
404419
}
405-
request.contentLength = parseInt(val, 10)
406-
if (!Number.isFinite(request.contentLength)) {
420+
if (!isValidContentLengthHeaderValue(val)) {
407421
throw new InvalidArgumentError('invalid content-length header')
408422
}
423+
request.contentLength = parseInt(val, 10)
409424
} else if (request.contentType === null && headerName === 'content-type') {
410425
request.contentType = val
411426
request.headers.push(key, val)

lib/web/fetch/index.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1433,7 +1433,10 @@ async function httpNetworkOrCacheFetch (
14331433
// 8. If contentLengthHeaderValue is non-null, then append
14341434
// `Content-Length`/contentLengthHeaderValue to httpRequest’s header
14351435
// list.
1436-
if (contentLengthHeaderValue != null) {
1436+
if (
1437+
contentLengthHeaderValue != null &&
1438+
!httpRequest.headersList.contains('content-length', true)
1439+
) {
14371440
httpRequest.headersList.append('content-length', contentLengthHeaderValue, true)
14381441
}
14391442

test/fetch/content-length.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,23 @@ test('Content-Length is set when using a FormData body with fetch', async (t) =>
2727
body: fd
2828
})
2929
})
30+
31+
test('Content-Length is not duplicated when provided explicitly', async (t) => {
32+
const body = 'a+b+c'
33+
34+
const server = createServer({ joinDuplicateHeaders: true }, (req, res) => {
35+
t.assert.strictEqual(req.headers['content-length'], `${Buffer.byteLength(body)}`)
36+
res.end()
37+
}).listen(0)
38+
39+
await once(server, 'listening')
40+
t.after(closeServerAsPromise(server))
41+
42+
await fetch(`http://localhost:${server.address().port}`, {
43+
method: 'POST',
44+
body,
45+
headers: {
46+
'content-length': Buffer.byteLength(body)
47+
}
48+
})
49+
})

test/invalid-headers.js

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const { test, after } = require('node:test')
55
const { Client, errors } = require('..')
66

77
test('invalid headers', (t) => {
8-
t = tspl(t, { plan: 10 })
8+
t = tspl(t, { plan: 13 })
99

1010
const client = new Client('http://localhost:3000')
1111
after(() => client.close())
@@ -19,6 +19,36 @@ test('invalid headers', (t) => {
1919
t.ok(err instanceof errors.InvalidArgumentError)
2020
})
2121

22+
client.request({
23+
path: '/',
24+
method: 'GET',
25+
headers: {
26+
'content-length': '1.1'
27+
}
28+
}, (err, data) => {
29+
t.ok(err instanceof errors.InvalidArgumentError)
30+
})
31+
32+
client.request({
33+
path: '/',
34+
method: 'GET',
35+
headers: {
36+
'content-length': '10abc'
37+
}
38+
}, (err, data) => {
39+
t.ok(err instanceof errors.InvalidArgumentError)
40+
})
41+
42+
client.request({
43+
path: '/',
44+
method: 'GET',
45+
headers: {
46+
'content-length': '-1'
47+
}
48+
}, (err, data) => {
49+
t.ok(err instanceof errors.InvalidArgumentError)
50+
})
51+
2252
client.request({
2353
path: '/',
2454
method: 'GET',

0 commit comments

Comments
 (0)