Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/RequestHeaderParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
*
* @internal
*/
class RequestHeaderParser extends EventEmitter
class RequestHeaderParser extends EventEmitter implements RequestHeaderParserInterface
{
private $buffer = '';
private $maxSize = 4096;
Expand Down
80 changes: 80 additions & 0 deletions src/RequestHeaderParserFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?php

namespace React\Http;

use React\Socket\ConnectionInterface;

class RequestHeaderParserFactory implements RequestHeaderParserFactoryInterface
{

/**
* @param ConnectionInterface $conn
* @return RequestHeaderParserInterface
*/
public function create(ConnectionInterface $conn)
{
$uriLocal = $this->getUriLocal($conn);
$uriRemote = $this->getUriRemote($conn);

return new RequestHeaderParser($uriLocal, $uriRemote);
}

/**
* @param ConnectionInterface $conn
* @return string
*/
protected function getUriLocal(ConnectionInterface $conn)
{
$uriLocal = $conn->getLocalAddress();
if ($uriLocal !== null && strpos($uriLocal, '://') === false) {
// local URI known but does not contain a scheme. Should only happen for old Socket < 0.8
// try to detect transport encryption and assume default application scheme
$uriLocal = ($this->isConnectionEncrypted($conn) ? 'https://' : 'http://') . $uriLocal;
} elseif ($uriLocal !== null) {
// local URI known, so translate transport scheme to application scheme
$uriLocal = strtr($uriLocal, array('tcp://' => 'http://', 'tls://' => 'https://'));
}

return $uriLocal;
}

/**
* @param ConnectionInterface $conn
* @return string
*/
protected function getUriRemote(ConnectionInterface $conn)
{
$uriRemote = $conn->getRemoteAddress();
if ($uriRemote !== null && strpos($uriRemote, '://') === false) {
// local URI known but does not contain a scheme. Should only happen for old Socket < 0.8
// actual scheme is not evaluated but required for parsing URI
$uriRemote = 'unused://' . $uriRemote;
}

return $uriRemote;
}

/**
* @param ConnectionInterface $conn
* @return bool
* @codeCoverageIgnore
*/
private function isConnectionEncrypted(ConnectionInterface $conn)
{
// Legacy PHP < 7 does not offer any direct access to check crypto parameters
// We work around by accessing the context options and assume that only
// secure connections *SHOULD* set the "ssl" context options by default.
if (PHP_VERSION_ID < 70000) {
$context = isset($conn->stream) ? stream_context_get_options($conn->stream) : array();

return (isset($context['ssl']) && $context['ssl']);
}

// Modern PHP 7+ offers more reliable access to check crypto parameters
// by checking stream crypto meta data that is only then made available.
$meta = isset($conn->stream) ? stream_get_meta_data($conn->stream) : array();

return (isset($meta['crypto']) && $meta['crypto']);
}

}
15 changes: 15 additions & 0 deletions src/RequestHeaderParserFactoryInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace React\Http;

use React\Socket\ConnectionInterface;

interface RequestHeaderParserFactoryInterface
{

/**
* @param ConnectionInterface $conn
* @return RequestHeaderParserInterface
*/
public function create(ConnectionInterface $conn);
}
16 changes: 16 additions & 0 deletions src/RequestHeaderParserInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace React\Http;

use Evenement\EventEmitterInterface;

interface RequestHeaderParserInterface extends EventEmitterInterface
{

/**
* Feed the RequestHeaderParser with a data chunk from the connection
* @param string $data
* @return void
*/
public function feed($data);
}
51 changes: 9 additions & 42 deletions src/Server.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ class Server extends EventEmitter
{
private $callback;

/**
* @var RequestHeaderParserFactoryInterface
*/
private $factory;

/**
* Creates an HTTP server that invokes the given callback for each incoming HTTP request
*
Expand All @@ -86,15 +91,17 @@ class Server extends EventEmitter
* See also [listen()](#listen) for more details.
*
* @param callable $callback
* @param RequestHeaderParserFactoryInterface $factory
* @see self::listen()
*/
public function __construct($callback)
public function __construct($callback, RequestHeaderParserFactoryInterface $factory = null)
{
if (!is_callable($callback)) {
throw new \InvalidArgumentException();
}

$this->callback = $callback;
$this->factory = $factory ? $factory : new RequestHeaderParserFactory();
}

/**
Expand Down Expand Up @@ -147,25 +154,8 @@ public function listen(ServerInterface $socket)
/** @internal */
public function handleConnection(ConnectionInterface $conn)
{
$uriLocal = $conn->getLocalAddress();
if ($uriLocal !== null && strpos($uriLocal, '://') === false) {
// local URI known but does not contain a scheme. Should only happen for old Socket < 0.8
// try to detect transport encryption and assume default application scheme
$uriLocal = ($this->isConnectionEncrypted($conn) ? 'https://' : 'http://') . $uriLocal;
} elseif ($uriLocal !== null) {
// local URI known, so translate transport scheme to application scheme
$uriLocal = strtr($uriLocal, array('tcp://' => 'http://', 'tls://' => 'https://'));
}

$uriRemote = $conn->getRemoteAddress();
if ($uriRemote !== null && strpos($uriRemote, '://') === false) {
// local URI known but does not contain a scheme. Should only happen for old Socket < 0.8
// actual scheme is not evaluated but required for parsing URI
$uriRemote = 'unused://' . $uriRemote;
}

$that = $this;
$parser = new RequestHeaderParser($uriLocal, $uriRemote);
$parser = $this->factory->create($conn);

$listener = array($parser, 'feed');
$parser->on('headers', function (RequestInterface $request, $bodyBuffer) use ($conn, $listener, $that) {
Expand Down Expand Up @@ -422,27 +412,4 @@ private function handleResponseBody(ResponseInterface $response, ConnectionInter
$connection->end();
}
}

/**
* @param ConnectionInterface $conn
* @return bool
* @codeCoverageIgnore
*/
private function isConnectionEncrypted(ConnectionInterface $conn)
{
// Legacy PHP < 7 does not offer any direct access to check crypto parameters
// We work around by accessing the context options and assume that only
// secure connections *SHOULD* set the "ssl" context options by default.
if (PHP_VERSION_ID < 70000) {
$context = isset($conn->stream) ? stream_context_get_options($conn->stream) : array();

return (isset($context['ssl']) && $context['ssl']);
}

// Modern PHP 7+ offers more reliable access to check crypto parameters
// by checking stream crypto meta data that is only then made available.
$meta = isset($conn->stream) ? stream_get_meta_data($conn->stream) : array();

return (isset($meta['crypto']) && $meta['crypto']);
}
}
3 changes: 2 additions & 1 deletion tests/ServerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1258,6 +1258,7 @@ function ($data) use (&$buffer) {

public function testRequestOverflowWillEmitErrorAndSendErrorResponse()
{
$defaultMaxHeaderSize = 4096;
$error = null;
$server = new Server($this->expectCallableNever());
$server->on('error', function ($message) use (&$error) {
Expand All @@ -1281,7 +1282,7 @@ function ($data) use (&$buffer) {
$this->socket->emit('connection', array($this->connection));

$data = "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\nX-DATA: ";
$data .= str_repeat('A', 4097 - strlen($data)) . "\r\n\r\n";
$data .= str_repeat('A', $defaultMaxHeaderSize + 1 - strlen($data)) . "\r\n\r\n";
$this->connection->emit('data', array($data));

$this->assertInstanceOf('OverflowException', $error);
Expand Down