Skip to content

Commit feef62b

Browse files
authored
fix: support Connection header with connection-specific header names per RFC 7230 (#4775)
1 parent a613d9a commit feef62b

File tree

3 files changed

+110
-5
lines changed

3 files changed

+110
-5
lines changed

lib/core/request.js

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -412,13 +412,21 @@ function processHeader (request, key, val) {
412412
} else if (headerName === 'transfer-encoding' || headerName === 'keep-alive' || headerName === 'upgrade') {
413413
throw new InvalidArgumentError(`invalid ${headerName} header`)
414414
} else if (headerName === 'connection') {
415-
const value = typeof val === 'string' ? val.toLowerCase() : null
416-
if (value !== 'close' && value !== 'keep-alive') {
415+
// Per RFC 7230 Section 6.1, Connection header can contain
416+
// a comma-separated list of connection option tokens (header names)
417+
const value = typeof val === 'string' ? val : null
418+
if (value === null) {
417419
throw new InvalidArgumentError('invalid connection header')
418420
}
419421

420-
if (value === 'close') {
421-
request.reset = true
422+
for (const token of value.toLowerCase().split(',')) {
423+
const trimmed = token.trim()
424+
if (!isValidHTTPToken(trimmed)) {
425+
throw new InvalidArgumentError('invalid connection header')
426+
}
427+
if (trimmed === 'close') {
428+
request.reset = true
429+
}
422430
}
423431
} else if (headerName === 'expect') {
424432
throw new NotSupportedError('expect header not supported')

test/invalid-headers.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ test('invalid headers', (t) => {
5151
path: '/',
5252
method: 'GET',
5353
headers: {
54-
connection: 'asd'
54+
connection: 'invalid header with spaces'
5555
}
5656
}, (err, data) => {
5757
t.ok(err instanceof errors.InvalidArgumentError)

test/request.js

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -539,3 +539,100 @@ test('request should include statusText in response', async t => {
539539
await body.dump()
540540
t.ok('request completed')
541541
})
542+
543+
describe('connection header per RFC 7230', () => {
544+
test('should allow close', async (t) => {
545+
t = tspl(t, { plan: 1 })
546+
547+
const server = createServer((req, res) => {
548+
res.end('ok')
549+
})
550+
551+
after(() => server.close())
552+
await new Promise((resolve) => server.listen(0, resolve))
553+
554+
const { statusCode, body } = await request({
555+
method: 'GET',
556+
origin: `http://localhost:${server.address().port}`,
557+
headers: { connection: 'close' }
558+
})
559+
await body.dump()
560+
t.strictEqual(statusCode, 200)
561+
})
562+
563+
test('should allow keep-alive', async (t) => {
564+
t = tspl(t, { plan: 1 })
565+
566+
const server = createServer((req, res) => {
567+
res.end('ok')
568+
})
569+
570+
after(() => server.close())
571+
await new Promise((resolve) => server.listen(0, resolve))
572+
573+
const { statusCode, body } = await request({
574+
method: 'GET',
575+
origin: `http://localhost:${server.address().port}`,
576+
headers: { connection: 'keep-alive' }
577+
})
578+
await body.dump()
579+
t.strictEqual(statusCode, 200)
580+
})
581+
582+
test('should allow custom header name as connection option', async (t) => {
583+
t = tspl(t, { plan: 1 })
584+
585+
const server = createServer((req, res) => {
586+
res.end('ok')
587+
})
588+
589+
after(() => server.close())
590+
await new Promise((resolve) => server.listen(0, resolve))
591+
592+
const { statusCode, body } = await request({
593+
method: 'GET',
594+
origin: `http://localhost:${server.address().port}`,
595+
headers: {
596+
'x-custom-header': 'value',
597+
connection: 'x-custom-header'
598+
}
599+
})
600+
await body.dump()
601+
t.strictEqual(statusCode, 200)
602+
})
603+
604+
test('should allow comma-separated list of connection options', async (t) => {
605+
t = tspl(t, { plan: 1 })
606+
607+
const server = createServer((req, res) => {
608+
res.end('ok')
609+
})
610+
611+
after(() => server.close())
612+
await new Promise((resolve) => server.listen(0, resolve))
613+
614+
const { statusCode, body } = await request({
615+
method: 'GET',
616+
origin: `http://localhost:${server.address().port}`,
617+
headers: {
618+
'x-custom-header': 'value',
619+
connection: 'close, x-custom-header'
620+
}
621+
})
622+
await body.dump()
623+
t.strictEqual(statusCode, 200)
624+
})
625+
626+
test('should reject invalid tokens in connection header', async (t) => {
627+
t = tspl(t, { plan: 2 })
628+
629+
await request({
630+
method: 'GET',
631+
origin: 'http://localhost:1234',
632+
headers: { connection: 'invalid header with spaces' }
633+
}).catch((err) => {
634+
t.ok(err instanceof errors.InvalidArgumentError)
635+
t.strictEqual(err.message, 'invalid connection header')
636+
})
637+
})
638+
})

0 commit comments

Comments
 (0)