Skip to content

Commit ed3604c

Browse files
wwwzbwcomlpincamscdex
authored andcommitted
http: server check Host header, to meet RFC 7230 5.4 requirement
PR-URL: #45597 Fixes: #39033 Co-authored-by: Luigi Pinca <[email protected]> Co-authored-by: mscdex <[email protected]> Reviewed-By: Robert Nagy <[email protected]> Reviewed-By: Paolo Insogna <[email protected]> Reviewed-By: Yagiz Nizipli <[email protected]> Reviewed-By: Matteo Collina <[email protected]>
1 parent 71ff89f commit ed3604c

File tree

46 files changed

+156
-42
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+156
-42
lines changed

doc/api/http.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3185,6 +3185,10 @@ changes:
31853185
* `uniqueHeaders` {Array} A list of response headers that should be sent only
31863186
once. If the header's value is an array, the items will be joined
31873187
using `; `.
3188+
* `requireHostHeader` {boolean} It forces the server to respond with
3189+
a 400 (Bad Request) status code to any HTTP/1.1 request message
3190+
that lacks a Host header (as mandated by the specification).
3191+
**Default:** `true`.
31883192

31893193
* `requestListener` {Function}
31903194

lib/_http_server.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,14 @@ function storeHTTPOptions(options) {
473473
} else {
474474
this.connectionsCheckingInterval = 30_000; // 30 seconds
475475
}
476+
477+
const requireHostHeader = options.requireHostHeader;
478+
if (requireHostHeader !== undefined) {
479+
validateBoolean(requireHostHeader, 'options.requireHostHeader');
480+
this.requireHostHeader = requireHostHeader;
481+
} else {
482+
this.requireHostHeader = true;
483+
}
476484
}
477485

478486
function setupConnectionsTracking(server) {
@@ -1022,7 +1030,18 @@ function parserOnIncoming(server, socket, state, req, keepAlive) {
10221030

10231031
let handled = false;
10241032

1033+
10251034
if (req.httpVersionMajor === 1 && req.httpVersionMinor === 1) {
1035+
1036+
// From RFC 7230 5.4 https://datatracker.ietf.org/doc/html/rfc7230#section-5.4
1037+
// A server MUST respond with a 400 (Bad Request) status code to any
1038+
// HTTP/1.1 request message that lacks a Host header field
1039+
if (server.requireHostHeader && req.headers.host === undefined) {
1040+
res.writeHead(400, ['Connection', 'close']);
1041+
res.end();
1042+
return 0;
1043+
}
1044+
10261045
const isRequestsLimitSet = (
10271046
typeof server.maxRequestsPerSocket === 'number' &&
10281047
server.maxRequestsPerSocket > 0
@@ -1045,7 +1064,6 @@ function parserOnIncoming(server, socket, state, req, keepAlive) {
10451064

10461065
if (RegExpPrototypeExec(continueExpression, req.headers.expect) !== null) {
10471066
res._expect_continue = true;
1048-
10491067
if (server.listenerCount('checkContinue') > 0) {
10501068
server.emit('checkContinue', req, res);
10511069
} else {

lib/http.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ let maxHeaderSize;
5252
* ServerResponse?: ServerResponse;
5353
* insecureHTTPParser?: boolean;
5454
* maxHeaderSize?: number;
55+
* requireHostHeader?: boolean
5556
* }} [opts]
5657
* @param {Function} [requestListener]
5758
* @returns {Server}

test/parallel/test-http-chunked-304.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ function test(statusCode) {
4747
const conn = net.createConnection(
4848
server.address().port,
4949
common.mustCall(() => {
50-
conn.write('GET / HTTP/1.1\r\n\r\n');
50+
conn.write('GET / HTTP/1.1\r\nHost: example.com\r\n\r\n');
5151

5252
let resp = '';
5353
conn.setEncoding('utf8');

test/parallel/test-http-client-headers-array.js

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ function execute(options) {
1010
const expectHeaders = {
1111
'x-foo': 'boom',
1212
'cookie': 'a=1; b=2; c=3',
13-
'connection': 'keep-alive'
13+
'connection': 'keep-alive',
14+
'host': 'example.com',
1415
};
1516

1617
// no Host header when you set headers an array
@@ -43,13 +44,20 @@ function execute(options) {
4344
// Should be the same except for implicit Host header on the first two
4445
execute({ headers: { 'x-foo': 'boom', 'cookie': 'a=1; b=2; c=3' } });
4546
execute({ headers: { 'x-foo': 'boom', 'cookie': [ 'a=1', 'b=2', 'c=3' ] } });
46-
execute({ headers: [[ 'x-foo', 'boom' ], [ 'cookie', 'a=1; b=2; c=3' ]] });
4747
execute({ headers: [
48-
[ 'x-foo', 'boom' ], [ 'cookie', [ 'a=1', 'b=2', 'c=3' ]],
48+
[ 'x-foo', 'boom' ],
49+
[ 'cookie', 'a=1; b=2; c=3' ],
50+
[ 'Host', 'example.com' ],
51+
] });
52+
execute({ headers: [
53+
[ 'x-foo', 'boom' ],
54+
[ 'cookie', [ 'a=1', 'b=2', 'c=3' ]],
55+
[ 'Host', 'example.com' ],
4956
] });
5057
execute({ headers: [
5158
[ 'x-foo', 'boom' ], [ 'cookie', 'a=1' ],
52-
[ 'cookie', 'b=2' ], [ 'cookie', 'c=3'],
59+
[ 'cookie', 'b=2' ], [ 'cookie', 'c=3' ],
60+
[ 'Host', 'example.com'],
5361
] });
5462

5563
// Authorization and Host header both missing from the second
@@ -58,4 +66,5 @@ execute({ auth: 'foo:bar', headers:
5866
execute({ auth: 'foo:bar', headers: [
5967
[ 'x-foo', 'boom' ], [ 'cookie', 'a=1' ],
6068
[ 'cookie', 'b=2' ], [ 'cookie', 'c=3'],
69+
[ 'Host', 'example.com'],
6170
] });

test/parallel/test-http-content-length.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,18 @@ const server = http.createServer(function(req, res) {
2828

2929
switch (req.url.substr(1)) {
3030
case 'multiple-writes':
31+
delete req.headers.host;
3132
assert.deepStrictEqual(req.headers, expectedHeadersMultipleWrites);
3233
res.write('hello');
3334
res.end('world');
3435
break;
3536
case 'end-with-data':
37+
delete req.headers.host;
3638
assert.deepStrictEqual(req.headers, expectedHeadersEndWithData);
3739
res.end('hello world');
3840
break;
3941
case 'empty':
42+
delete req.headers.host;
4043
assert.deepStrictEqual(req.headers, expectedHeadersEndNoData);
4144
res.end();
4245
break;
@@ -56,7 +59,6 @@ server.listen(0, function() {
5659
path: '/multiple-writes'
5760
});
5861
req.removeHeader('Date');
59-
req.removeHeader('Host');
6062
req.write('hello ');
6163
req.end('world');
6264
req.on('response', function(res) {
@@ -70,7 +72,6 @@ server.listen(0, function() {
7072
path: '/end-with-data'
7173
});
7274
req.removeHeader('Date');
73-
req.removeHeader('Host');
7475
req.end('hello world');
7576
req.on('response', function(res) {
7677
assert.deepStrictEqual(res.headers, { ...expectedHeadersEndWithData, 'keep-alive': 'timeout=1' });
@@ -83,7 +84,6 @@ server.listen(0, function() {
8384
path: '/empty'
8485
});
8586
req.removeHeader('Date');
86-
req.removeHeader('Host');
8787
req.end();
8888
req.on('response', function(res) {
8989
assert.deepStrictEqual(res.headers, { ...expectedHeadersEndNoData, 'keep-alive': 'timeout=1' });

test/parallel/test-http-header-badrequest.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ server.listen(0, mustCall(() => {
1717
let received = '';
1818

1919
c.on('connect', mustCall(() => {
20-
c.write('GET /blah HTTP/1.1\r\n\r\n');
20+
c.write('GET /blah HTTP/1.1\r\nHost: example.com\r\n\r\n');
2121
}));
2222
c.on('data', mustCall((data) => {
2323
received += data.toString();

test/parallel/test-http-insecure-parser-per-stream.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const MakeDuplexPair = require('../common/duplexpair');
2222

2323
serverSide.resume(); // Dump the request
2424
serverSide.end('HTTP/1.1 200 OK\r\n' +
25+
'Host: example.com\r\n' +
2526
'Hello: foo\x08foo\r\n' +
2627
'Content-Length: 0\r\n' +
2728
'\r\n\r\n');
@@ -39,6 +40,7 @@ const MakeDuplexPair = require('../common/duplexpair');
3940

4041
serverSide.resume(); // Dump the request
4142
serverSide.end('HTTP/1.1 200 OK\r\n' +
43+
'Host: example.com\r\n' +
4244
'Hello: foo\x08foo\r\n' +
4345
'Content-Length: 0\r\n' +
4446
'\r\n\r\n');
@@ -62,6 +64,7 @@ const MakeDuplexPair = require('../common/duplexpair');
6264
server.emit('connection', serverSide);
6365

6466
clientSide.write('GET / HTTP/1.1\r\n' +
67+
'Host: example.com\r\n' +
6568
'Hello: foo\x08foo\r\n' +
6669
'\r\n\r\n');
6770
}
@@ -77,6 +80,7 @@ const MakeDuplexPair = require('../common/duplexpair');
7780
server.emit('connection', serverSide);
7881

7982
clientSide.write('GET / HTTP/1.1\r\n' +
83+
'Host: example.com\r\n' +
8084
'Hello: foo\x08foo\r\n' +
8185
'\r\n\r\n');
8286
}

test/parallel/test-http-insecure-parser.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ server.listen(0, common.mustCall(function() {
1919
client.write(
2020
'GET / HTTP/1.1\r\n' +
2121
'Content-Type: text/te\x08t\r\n' +
22+
'Host: example.com' +
2223
'Connection: close\r\n\r\n');
2324
}
2425
);

test/parallel/test-http-keep-alive-drop-requests.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const assert = require('assert');
88
function request(socket) {
99
socket.write('GET / HTTP/1.1\r\n');
1010
socket.write('Connection: keep-alive\r\n');
11+
socket.write('Host: localhost\r\n');
1112
socket.write('\r\n\r\n');
1213
}
1314

0 commit comments

Comments
 (0)