Skip to content

Commit ed3604c

Browse files
wwwzbwcomlpincamscdex
authored andcommitted
http: server check Host header, to meet RFC 7230 5.4 requirement
PR-URL: #45597Fixes: #39033 Co-authored-by: Luigi Pinca <luigipinca@gmail.com> Co-authored-by: mscdex <mscdex@users.noreply.github.com> Reviewed-By: Robert Nagy <ronagy@icloud.com> Reviewed-By: Paolo Insogna <paolo@cowtech.it> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
1 parent 71ff89f commit ed3604c

46 files changed

Lines changed: 156 additions & 42 deletions

File tree

Some content is hidden

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

‎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+
constrequireHostHeader=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
functionsetupConnectionsTracking(server){
@@ -1022,7 +1030,18 @@ function parserOnIncoming(server, socket, state, req, keepAlive) {
10221030

10231031
lethandled=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+
return0;
1043+
}
1044+
10261045
constisRequestsLimitSet=(
10271046
typeofserver.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
constconn=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
letresp='';
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
constexpectHeaders={
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+
deletereq.headers.host;
3132
assert.deepStrictEqual(req.headers,expectedHeadersMultipleWrites);
3233
res.write('hello');
3334
res.end('world');
3435
break;
3536
case'end-with-data':
37+
deletereq.headers.host;
3638
assert.deepStrictEqual(req.headers,expectedHeadersEndWithData);
3739
res.end('hello world');
3840
break;
3941
case'empty':
42+
deletereq.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
letreceived='';
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
functionrequest(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)