Skip to content

Commit

Permalink
Merge pull request #404 from clue-labs/require-host
Browse files Browse the repository at this point in the history
Require Host request header for HTTP/1.1 requests
  • Loading branch information
WyriHaximus authored Apr 10, 2021
2 parents c919cb7 + 6bcbe54 commit c22fde6
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 66 deletions.
14 changes: 9 additions & 5 deletions src/Io/RequestHeaderParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ public function parseRequest($headers, $remoteSocketUri, $localSocketUri)
}

// default host if unset comes from local socket address or defaults to localhost
$hasHost = $host !== null;
if ($host === null) {
$host = isset($localParts['host'], $localParts['port']) ? $localParts['host'] . ':' . $localParts['port'] : '127.0.0.1';
}
Expand Down Expand Up @@ -230,8 +231,8 @@ public function parseRequest($headers, $remoteSocketUri, $localSocketUri)
$request = $request->withRequestTarget($start['target']);
}

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

// make sure value contains valid host component (IP or hostname)
Expand All @@ -244,6 +245,12 @@ public function parseRequest($headers, $remoteSocketUri, $localSocketUri)
if ($parts === false || $parts) {
throw new \InvalidArgumentException('Invalid Host header value');
}
} elseif (!$hasHost && $start['version'] === '1.1' && $start['method'] !== 'CONNECT') {
// require Host request header for HTTP/1.1 (except for CONNECT method)
throw new \InvalidArgumentException('Missing required Host request header');
} elseif (!$hasHost) {
// remove default Host request header for HTTP/1.0 when not explicitly given
$request = $request->withoutHeader('Host');
}

// ensure message boundaries are valid according to Content-Length and Transfer-Encoding request headers
Expand All @@ -266,9 +273,6 @@ public function parseRequest($headers, $remoteSocketUri, $localSocketUri)
}
}

// always sanitize Host header because it contains critical routing information
$request = $request->withUri($request->getUri()->withUserInfo('u')->withUserInfo(''));

return $request;
}
}
14 changes: 7 additions & 7 deletions tests/Io/RequestHeaderParserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ public function testHeaderEventWithShouldApplyDefaultAddressFromLocalConnectionA
$connection->emit('data', array("GET /foo HTTP/1.0\r\n\r\n"));

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

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

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

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

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

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

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

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

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

$this->assertInstanceOf('InvalidArgumentException', $error);
$this->assertSame(400, $error->getCode());
Expand Down Expand Up @@ -762,7 +762,7 @@ public function testQueryParmetersWillBeSet()
private function createGetRequest()
{
$data = "GET / HTTP/1.1\r\n";
$data .= "Host: example.com:80\r\n";
$data .= "Host: example.com\r\n";
$data .= "Connection: close\r\n";
$data .= "\r\n";

Expand All @@ -772,7 +772,7 @@ private function createGetRequest()
private function createAdvancedPostRequest()
{
$data = "POST /foo?bar=baz HTTP/1.1\r\n";
$data .= "Host: example.com:80\r\n";
$data .= "Host: example.com\r\n";
$data .= "User-Agent: react/alpha\r\n";
$data .= "Connection: close\r\n";
$data .= "\r\n";
Expand Down
Loading

0 comments on commit c22fde6

Please sign in to comment.