Skip to content

Commit 6bcbe54

Browse files
committed
Require Host request header for HTTP/1.1 requests
All HTTP/1.1 requests require a Host request header as per RFC 7230. The proxy CONNECT method is an exception to this rule because we do not want to break compatibility with common HTTP proxy clients that do not strictly follow the RFCs. This does not affect valid HTTP/1.1 requests and has no effect on HTTP/1.0 requests. Additionally, make sure we do not include a default Host request header in the parsed request object if the incoming request does not make use of the Host request header.
1 parent 2a9fc79 commit 6bcbe54

File tree

4 files changed

+96
-66
lines changed

4 files changed

+96
-66
lines changed

src/Io/RequestHeaderParser.php

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ public function parseRequest($headers, $remoteSocketUri, $localSocketUri)
172172
}
173173

174174
// default host if unset comes from local socket address or defaults to localhost
175+
$hasHost = $host !== null;
175176
if ($host === null) {
176177
$host = isset($localParts['host'], $localParts['port']) ? $localParts['host'] . ':' . $localParts['port'] : '127.0.0.1';
177178
}
@@ -234,8 +235,8 @@ public function parseRequest($headers, $remoteSocketUri, $localSocketUri)
234235
$request = $request->withRequestTarget($start['target']);
235236
}
236237

237-
// Optional Host header value MUST be valid (host and optional port)
238-
if ($request->hasHeader('Host')) {
238+
if ($hasHost) {
239+
// Optional Host request header value MUST be valid (host and optional port)
239240
$parts = \parse_url('http://' . $request->getHeaderLine('Host'));
240241

241242
// make sure value contains valid host component (IP or hostname)
@@ -248,6 +249,12 @@ public function parseRequest($headers, $remoteSocketUri, $localSocketUri)
248249
if ($parts === false || $parts) {
249250
throw new \InvalidArgumentException('Invalid Host header value');
250251
}
252+
} elseif (!$hasHost && $start['version'] === '1.1' && $start['method'] !== 'CONNECT') {
253+
// require Host request header for HTTP/1.1 (except for CONNECT method)
254+
throw new \InvalidArgumentException('Missing required Host request header');
255+
} elseif (!$hasHost) {
256+
// remove default Host request header for HTTP/1.0 when not explicitly given
257+
$request = $request->withoutHeader('Host');
251258
}
252259

253260
// ensure message boundaries are valid according to Content-Length and Transfer-Encoding request headers
@@ -270,9 +277,6 @@ public function parseRequest($headers, $remoteSocketUri, $localSocketUri)
270277
}
271278
}
272279

273-
// always sanitize Host header because it contains critical routing information
274-
$request = $request->withUri($request->getUri()->withUserInfo('u')->withUserInfo(''));
275-
276280
return $request;
277281
}
278282
}

tests/Io/RequestHeaderParserTest.php

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ public function testHeaderEventWithShouldApplyDefaultAddressFromLocalConnectionA
257257
$connection->emit('data', array("GET /foo HTTP/1.0\r\n\r\n"));
258258

259259
$this->assertEquals('http://127.1.1.1:8000/foo', $request->getUri());
260-
$this->assertEquals('127.1.1.1:8000', $request->getHeaderLine('Host'));
260+
$this->assertFalse($request->hasHeader('Host'));
261261
}
262262

263263
public function testHeaderEventViaHttpsShouldApplyHttpsSchemeFromLocalTlsConnectionAddress()
@@ -550,7 +550,7 @@ public function testInvalidContentLengthRequestHeaderWillEmitError()
550550
$connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(null)->getMock();
551551
$parser->handle($connection);
552552

553-
$connection->emit('data', array("GET / HTTP/1.1\r\nContent-Length: foo\r\n\r\n"));
553+
$connection->emit('data', array("GET / HTTP/1.1\r\nHost: localhost\r\nContent-Length: foo\r\n\r\n"));
554554

555555
$this->assertInstanceOf('InvalidArgumentException', $error);
556556
$this->assertSame(400, $error->getCode());
@@ -570,7 +570,7 @@ public function testInvalidRequestWithMultipleContentLengthRequestHeadersWillEmi
570570
$connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(null)->getMock();
571571
$parser->handle($connection);
572572

573-
$connection->emit('data', array("GET / HTTP/1.1\r\nContent-Length: 4\r\nContent-Length: 5\r\n\r\n"));
573+
$connection->emit('data', array("GET / HTTP/1.1\r\nHost: localhost\r\nContent-Length: 4\r\nContent-Length: 5\r\n\r\n"));
574574

575575
$this->assertInstanceOf('InvalidArgumentException', $error);
576576
$this->assertSame(400, $error->getCode());
@@ -590,7 +590,7 @@ public function testInvalidTransferEncodingRequestHeaderWillEmitError()
590590
$connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(null)->getMock();
591591
$parser->handle($connection);
592592

593-
$connection->emit('data', array("GET / HTTP/1.1\r\nTransfer-Encoding: foo\r\n\r\n"));
593+
$connection->emit('data', array("GET / HTTP/1.1\r\nHost: localhost\r\nTransfer-Encoding: foo\r\n\r\n"));
594594

595595
$this->assertInstanceOf('InvalidArgumentException', $error);
596596
$this->assertSame(501, $error->getCode());
@@ -610,7 +610,7 @@ public function testInvalidRequestWithBothTransferEncodingAndContentLengthWillEm
610610
$connection = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(null)->getMock();
611611
$parser->handle($connection);
612612

613-
$connection->emit('data', array("GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\nContent-Length: 0\r\n\r\n"));
613+
$connection->emit('data', array("GET / HTTP/1.1\r\nHost: localhost\r\nTransfer-Encoding: chunked\r\nContent-Length: 0\r\n\r\n"));
614614

615615
$this->assertInstanceOf('InvalidArgumentException', $error);
616616
$this->assertSame(400, $error->getCode());
@@ -762,7 +762,7 @@ public function testQueryParmetersWillBeSet()
762762
private function createGetRequest()
763763
{
764764
$data = "GET / HTTP/1.1\r\n";
765-
$data .= "Host: example.com:80\r\n";
765+
$data .= "Host: example.com\r\n";
766766
$data .= "Connection: close\r\n";
767767
$data .= "\r\n";
768768

@@ -772,7 +772,7 @@ private function createGetRequest()
772772
private function createAdvancedPostRequest()
773773
{
774774
$data = "POST /foo?bar=baz HTTP/1.1\r\n";
775-
$data .= "Host: example.com:80\r\n";
775+
$data .= "Host: example.com\r\n";
776776
$data .= "User-Agent: react/alpha\r\n";
777777
$data .= "Connection: close\r\n";
778778
$data .= "\r\n";

0 commit comments

Comments
 (0)