Skip to content

Commit c8d50b4

Browse files
authored
fix: reject malformed content-length request headers (#5060)
1 parent a1d6766 commit c8d50b4

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
@@ -28,6 +28,21 @@ const { headerNameLowerCasedRecord } = require('./constants')
2828
// Verifies that a given path is valid does not contain control chars \x00 to \x20
2929
const invalidPathRegex = /[^\u0021-\u00ff]/
3030

31+
function isValidContentLengthHeaderValue (val) {
32+
if (typeof val !== 'string' || val.length === 0) {
33+
return false
34+
}
35+
36+
for (let i = 0; i < val.length; i++) {
37+
const charCode = val.charCodeAt(i)
38+
if (charCode < 48 || charCode > 57) {
39+
return false
40+
}
41+
}
42+
43+
return true
44+
}
45+
3146
const kHandler = Symbol('handler')
3247
const kController = Symbol('controller')
3348
const kResume = Symbol('resume')
@@ -484,10 +499,10 @@ function processHeader (request, key, val) {
484499
if (request.contentLength !== null) {
485500
throw new InvalidArgumentError('duplicate content-length header')
486501
}
487-
request.contentLength = parseInt(val, 10)
488-
if (!Number.isFinite(request.contentLength)) {
502+
if (!isValidContentLengthHeaderValue(val)) {
489503
throw new InvalidArgumentError('invalid content-length header')
490504
}
505+
request.contentLength = parseInt(val, 10)
491506
} else if (request.contentType === null && headerName === 'content-type') {
492507
request.contentType = val
493508
request.headers.push(key, val)

lib/web/fetch/index.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1428,7 +1428,10 @@ async function httpNetworkOrCacheFetch (
14281428
// 8. If contentLengthHeaderValue is non-null, then append
14291429
// `Content-Length`/contentLengthHeaderValue to httpRequest’s header
14301430
// list.
1431-
if (contentLengthHeaderValue != null) {
1431+
if (
1432+
contentLengthHeaderValue != null &&
1433+
!httpRequest.headersList.contains('content-length', true)
1434+
) {
14321435
httpRequest.headersList.append('content-length', contentLengthHeaderValue, true)
14331436
}
14341437

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)