diff --git a/src/RequestHeaderParser.php b/src/RequestHeaderParser.php index a6126da9..87475e2a 100644 --- a/src/RequestHeaderParser.php +++ b/src/RequestHeaderParser.php @@ -12,7 +12,7 @@ * * @internal */ -class RequestHeaderParser extends EventEmitter +class RequestHeaderParser extends EventEmitter implements RequestHeaderParserInterface { private $buffer = ''; private $maxSize = 4096; diff --git a/src/RequestHeaderParserFactory.php b/src/RequestHeaderParserFactory.php new file mode 100644 index 00000000..78b504ff --- /dev/null +++ b/src/RequestHeaderParserFactory.php @@ -0,0 +1,80 @@ +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']); + } + +} diff --git a/src/RequestHeaderParserFactoryInterface.php b/src/RequestHeaderParserFactoryInterface.php new file mode 100644 index 00000000..c2a98371 --- /dev/null +++ b/src/RequestHeaderParserFactoryInterface.php @@ -0,0 +1,15 @@ +callback = $callback; + $this->factory = $factory ? $factory : new RequestHeaderParserFactory(); } /** @@ -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) { @@ -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']); - } } diff --git a/tests/ServerTest.php b/tests/ServerTest.php index bdeb759d..a87b522a 100644 --- a/tests/ServerTest.php +++ b/tests/ServerTest.php @@ -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) { @@ -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);