Skip to content

Commit

Permalink
Validate Host header for HTTP/1.1 requests
Browse files Browse the repository at this point in the history
  • Loading branch information
clue committed Feb 19, 2017
1 parent 8dfc3a3 commit 16b425b
Show file tree
Hide file tree
Showing 2 changed files with 128 additions and 1 deletion.
18 changes: 18 additions & 0 deletions src/Server.php
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,24 @@ public function handleRequest(ConnectionInterface $conn, Request $request)
return $this->writeError($conn, 505);
}

// HTTP/1.1 requests MUST include a valid host header (host and optional port)
// https://tools.ietf.org/html/rfc7230#section-5.4
if ($request->getProtocolVersion() === '1.1') {
$parts = parse_url('http://' . $request->getHeaderLine('Host'));

// make sure value contains valid host component (IP or hostname)
if (!$parts || !isset($parts['scheme'], $parts['host'])) {
$parts = false;
}

// make sure value does not contain any other URI component
unset($parts['scheme'], $parts['host'], $parts['port']);
if ($parts === false || $parts) {
$this->emit('error', array(new \InvalidArgumentException('Invalid Host header for HTTP/1.1 request')));
return $this->writeError($conn, 400);
}
}

$response = new Response($conn, $request->getProtocolVersion());
$response->on('close', array($request, 'close'));

Expand Down
111 changes: 110 additions & 1 deletion tests/ServerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ public function testRequestEventWithSecondDataEventWillEmitBodyData()

$data = '';
$data .= "POST / HTTP/1.1\r\n";
$data .= "Host: localhost\r\n";
$data .= "Content-Length: 100\r\n";
$data .= "\r\n";
$data .= "incomplete";
Expand All @@ -178,6 +179,7 @@ public function testRequestEventWithPartialBodyWillEmitData()

$data = '';
$data .= "POST / HTTP/1.1\r\n";
$data .= "Host: localhost\r\n";
$data .= "Content-Length: 100\r\n";
$data .= "\r\n";
$this->connection->emit('data', array($data));
Expand Down Expand Up @@ -239,7 +241,7 @@ function ($data) use (&$buffer) {

$this->socket->emit('connection', array($this->connection));

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

$this->assertContains("HTTP/1.1 200 OK\r\n", $buffer);
Expand Down Expand Up @@ -526,7 +528,114 @@ public function testChunkedIsMixedUpperAndLowerCase()
$data .= "\r\n";
$data .= "5\r\nhello\r\n";
$data .= "0\r\n\r\n";
$this->connection->emit('data', array($data));
}

public function testRequestHttp11WithoutHostWillEmitErrorAndSendErrorResponse()
{
$error = null;
$server = new Server($this->socket);
$server->on('error', function ($message) use (&$error) {
$error = $message;
});

$buffer = '';

$this->connection
->expects($this->any())
->method('write')
->will(
$this->returnCallback(
function ($data) use (&$buffer) {
$buffer .= $data;
}
)
);

$this->socket->emit('connection', array($this->connection));

$data = "GET / HTTP/1.1\r\n\r\n";
$this->connection->emit('data', array($data));

$this->assertInstanceOf('InvalidArgumentException', $error);

$this->assertContains("HTTP/1.1 400 Bad Request\r\n", $buffer);
$this->assertContains("\r\n\r\nError 400: Bad Request", $buffer);
}

public function testRequestHttp11WithMalformedHostWillEmitErrorAndSendErrorResponse()
{
$error = null;
$server = new Server($this->socket);
$server->on('error', function ($message) use (&$error) {
$error = $message;
});

$buffer = '';

$this->connection
->expects($this->any())
->method('write')
->will(
$this->returnCallback(
function ($data) use (&$buffer) {
$buffer .= $data;
}
)
);

$this->socket->emit('connection', array($this->connection));

$data = "GET / HTTP/1.1\r\nHost: ///\r\n\r\n";
$this->connection->emit('data', array($data));

$this->assertInstanceOf('InvalidArgumentException', $error);

$this->assertContains("HTTP/1.1 400 Bad Request\r\n", $buffer);
$this->assertContains("\r\n\r\nError 400: Bad Request", $buffer);
}

public function testRequestHttp11WithInvalidHostUriComponentsWillEmitErrorAndSendErrorResponse()
{
$error = null;
$server = new Server($this->socket);
$server->on('error', function ($message) use (&$error) {
$error = $message;
});

$buffer = '';

$this->connection
->expects($this->any())
->method('write')
->will(
$this->returnCallback(
function ($data) use (&$buffer) {
$buffer .= $data;
}
)
);

$this->socket->emit('connection', array($this->connection));

$data = "GET / HTTP/1.1\r\nHost: localhost:80/test\r\n\r\n";
$this->connection->emit('data', array($data));

$this->assertInstanceOf('InvalidArgumentException', $error);

$this->assertContains("HTTP/1.1 400 Bad Request\r\n", $buffer);
$this->assertContains("\r\n\r\nError 400: Bad Request", $buffer);
}

public function testRequestHttp10WithoutHostEmitsRequestWithNoError()
{
$server = new Server($this->socket);
$server->on('request', $this->expectCallableOnce());
$server->on('error', $this->expectCallableNever());

$this->socket->emit('connection', array($this->connection));

$data = "GET / HTTP/1.0\r\n\r\n";
$this->connection->emit('data', array($data));
}

Expand Down

0 comments on commit 16b425b

Please sign in to comment.