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

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,14 @@ function writeRequest(socket, withBody) {
2424
socket.write('POST / HTTP/1.1\r\n');
2525
socket.write('Connection: keep-alive\r\n');
2626
socket.write('Content-Type: text/plain\r\n');
27+
socket.write('Host: localhost\r\n');
2728
socket.write(`Content-Length: ${bodySent.length}\r\n\r\n`);
2829
socket.write(`${bodySent}\r\n`);
2930
socket.write('\r\n\r\n');
3031
} else {
3132
socket.write('GET / HTTP/1.1\r\n');
3233
socket.write('Connection: keep-alive\r\n');
34+
socket.write('Host: localhost\r\n');
3335
socket.write('\r\n\r\n');
3436
}
3537
}

test/parallel/test-http-keep-alive-pipeline-max-requests.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ function assertResponse(headers, body, expectClosed) {
2121

2222
function writeRequest(socket) {
2323
socket.write('POST / HTTP/1.1\r\n');
24+
socket.write('Host: localhost\r\n');
2425
socket.write('Connection: keep-alive\r\n');
2526
socket.write('Content-Type: text/plain\r\n');
2627
socket.write(`Content-Length: ${bodySent.length}\r\n\r\n`);

test/parallel/test-http-malformed-request.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ server.listen(0);
4141
server.on('listening', function() {
4242
const c = net.createConnection(this.address().port);
4343
c.on('connect', function() {
44-
c.write('GET /hello?foo=%99bar HTTP/1.1\r\n\r\n');
44+
c.write('GET /hello?foo=%99bar HTTP/1.1\r\nHost: example.com\r\n\r\n');
4545
c.end();
4646
});
4747
});

test/parallel/test-http-max-header-size-per-stream.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ const MakeDuplexPair = require('../common/duplexpair');
6262
server.emit('connection', serverSide);
6363

6464
clientSide.write('GET / HTTP/1.1\r\n' +
65+
'Host: example.com\r\n' +
6566
'Hello: ' + 'A'.repeat(http.maxHeaderSize * 3) + '\r\n' +
6667
'\r\n\r\n');
6768
}

test/parallel/test-http-max-headers-count.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ const http = require('http');
2727
let requests = 0;
2828
let responses = 0;
2929

30-
const headers = {};
30+
const headers = {
31+
host: 'example.com'
32+
};
3133
const N = 100;
3234
for (let i = 0; i < N; ++i) {
3335
headers[`key${i}`] = i;
@@ -56,8 +58,8 @@ server.maxHeadersCount = max;
5658
server.listen(0, function() {
5759
const maxAndExpected = [ // for client
5860
[20, 20],
59-
[1200, 103],
60-
[0, N + 3], // Connection, Date and Transfer-Encoding
61+
[1200, 104],
62+
[0, N + 4], // Host and Connection
6163
];
6264
doRequest();
6365

test/parallel/test-http-pipeline-assertionerror-finish.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ const server = http
2727
.listen(0, function() {
2828
const s = net.connect(this.address().port);
2929

30-
const big = 'GET / HTTP/1.1\r\n\r\n'.repeat(COUNT);
30+
const big = 'GET / HTTP/1.1\r\nHost: example.com\r\n\r\n'.repeat(COUNT);
3131

3232
s.write(big);
3333
s.resume();

test/parallel/test-http-pipeline-requests-connection-leak.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ const server = http
2626
});
2727
})
2828
.listen(0, function() {
29-
const req = 'GET / HTTP/1.1\r\n\r\n'.repeat(COUNT);
29+
const req = 'GET / HTTP/1.1\r\nHost: example.com\r\n\r\n'.repeat(COUNT);
3030
client = net.connect(this.address().port, function() {
3131
client.write(req);
3232
});

test/parallel/test-http-pipeline-socket-parser-typeerror.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ const server = http
5050
.listen(0, () => {
5151
const s = net.connect(server.address().port);
5252
more = () => {
53-
s.write('GET / HTTP/1.1\r\n\r\n');
53+
s.write('GET / HTTP/1.1\r\nHost: example.com\r\n\r\n');
5454
};
5555
done = () => {
5656
s.write(

test/parallel/test-http-req-close-robust-from-tampering.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ server.listen(0, common.mustCall(() => {
1717

1818
const req = [
1919
'POST / HTTP/1.1',
20+
'Host: example.com',
2021
'Content-Length: 11',
2122
'',
2223
'hello world',
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
'use strict';
2+
const common = require('../common');
3+
const assert = require('assert');
4+
const http = require('http');
5+
6+
{
7+
const server = http.createServer(common.mustNotCall((req, res) => {
8+
res.writeHead(200);
9+
res.end();
10+
}));
11+
12+
// From RFC 7230 5.4 https://datatracker.ietf.org/doc/html/rfc7230#section-5.4
13+
// A server MUST respond with a 400 (Bad Request) status code to any
14+
// HTTP/1.1 request message that lacks a Host header field
15+
server.listen(0, common.mustCall(() => {
16+
http.get({ port: server.address().port, headers: [] }, (res) => {
17+
assert.strictEqual(res.statusCode, 400);
18+
assert.strictEqual(res.headers.connection, 'close');
19+
res.resume().on('end', common.mustCall(() => {
20+
server.close();
21+
}));
22+
});
23+
}));
24+
}
25+
26+
{
27+
const server = http.createServer({ requireHostHeader: false }, common.mustCall((req, res) => {
28+
res.writeHead(200, ['test', '1']);
29+
res.end();
30+
}));
31+
32+
server.listen(0, common.mustCall(() => {
33+
http.get({ port: server.address().port, headers: [] }, (res) => {
34+
assert.strictEqual(res.statusCode, 200);
35+
assert.strictEqual(res.headers.test, '1');
36+
res.resume().on('end', common.mustCall(() => {
37+
server.close();
38+
}));
39+
});
40+
}));
41+
}

test/parallel/test-http-response-splitting.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ const server = http.createServer((req, res) => {
5353
res.end('ok');
5454
});
5555
server.listen(0, () => {
56-
const end = 'HTTP/1.1\r\n\r\n';
56+
const end = 'HTTP/1.1\r\nHost: example.com\r\n\r\n';
5757
const client = net.connect({ port: server.address().port }, () => {
5858
client.write(`GET ${str} ${end}`);
5959
client.write(`GET / ${end}`);

test/parallel/test-http-server-close-all.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ server.listen(0, function() {
5050

5151
client2.on('close', common.mustCall());
5252

53-
client2.write('GET / HTTP/1.1\r\n\r\n');
53+
client2.write('GET / HTTP/1.1\r\nHost: example.com\r\n\r\n');
5454
}));
5555

5656
client1.on('close', common.mustCall());

test/parallel/test-http-server-close-idle.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ server.listen(0, function() {
6060
client2Closed = true;
6161
}));
6262

63-
client2.write('GET / HTTP/1.1\r\n\r\n');
63+
client2.write('GET / HTTP/1.1\r\nHost: example.com\r\n\r\n');
6464
}));
6565

6666
client1.on('close', common.mustCall(() => {

test/parallel/test-http-server-headers-timeout-keepalive.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const { connect } = require('net');
1212

1313
function performRequestWithDelay(client, firstDelay, secondDelay, closeAfter) {
1414
client.resume();
15-
client.write('GET / HTTP/1.1\r\n');
15+
client.write('GET / HTTP/1.1\r\nHost: example.com\r\n');
1616

1717
setTimeout(() => {
1818
client.write('Connection: ');

test/parallel/test-http-server-headers-timeout-pipelining.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ server.listen(0, common.mustCall(() => {
6666

6767
// Send two requests using pipelining. Delay before finishing the second one
6868
client.resume();
69-
client.write('GET / HTTP/1.1\r\nConnection: keep-alive\r\n\r\nGET / HTTP/1.1\r\nConnection: ');
69+
client.write('GET / HTTP/1.1\r\nHost: localhost\r\nConnection: keep-alive\r\n\r\n' +
70+
'GET / HTTP/1.1\r\nHost: localhost\r\nConnection: ');
7071

7172
// Complete the request
7273
setTimeout(() => {

0 commit comments

Comments
 (0)