guzzle / psr7

PSR-7 HTTP message library

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Error when $version parameter is null in Message:parseRequest

markyou-dev opened this issue · comments

PHP version: 7.4

Description
Problem while implementing websocket server with ratchetphp/Ratchet

When sending HTTP request headers to Telnet,
If '/' is missing from GET HTTP/1.1
An error has occurred.

An error occurs when creating GuzzleHttp\Psr7\Request instance in Message:parseRequest if the version parameter is null

PHP Fatal error:  Uncaught TypeError: GuzzleHttp\Psr7\Request::__construct(): Argument #5 ($version) must be of type string, null given, ...
/guzzlehttp/psr7/src/Request.php:35

How to reproduce


/**
     * Parses a request message string into a request object.
     *
     * @param string $message Request message string.
     */
    public static function parseRequest(string $message): RequestInterface
    {
        $data = self::parseMessage($message);
        $matches = [];
        if (!preg_match('/^[\S]+\s+([a-zA-Z]+:\/\/|\/).*/', $data['start-line'], $matches)) {
            throw new \InvalidArgumentException('Invalid request string');
        }
        $parts = explode(' ', $data['start-line'], 3);
        $version = isset($parts[2]) ? explode('/', $parts[2])[1] : '1.1';

        $request = new Request(
            $parts[0],
            $matches[1] === '/' ? self::parseRequestUri($parts[1], $data['headers']) : $parts[1],
            $data['headers'],
            $data['body'],
            $version
        );

        return $matches[1] === '/' ? $request : $request->withRequestTarget($parts[1]);
    }

Possible Solution
In Message::parseRequest, the $version parameter is
If null, set to '1.1'.

$version ? $version : '1.1'

Additional context

This has affected me as well. Twice in the last couple of weeks I have noticed my websocket server was offline and checking the logs it looks like someone send an invalid request consisting of HEAD / \r\n\r\n

PHP Notice:  Undefined offset: 1 in /var/www/vhosts/ws_server/vendor/guzzlehttp/psr7/src/Message.php on line 209
PHP Fatal error:  Uncaught TypeError: Argument 5 passed to GuzzleHttp\Psr7\Request::__construct() must be of the type string, null given, called in /var/www/vhosts/ws_server/vendor/guzzlehttp/psr7/src/Message.php on line 212 and defined in /var/www/vhosts/ws_server/vendor/guzzlehttp/psr7/src/Request.php:35
Stack trace:
#0 /var/www/vhosts/ws_server/vendor/guzzlehttp/psr7/src/Message.php(212): GuzzleHttp\Psr7\Request->__construct('HEAD', '/', Array, '', NULL)
#1 /var/www/vhosts/ws_server/vendor/cboden/ratchet/src/Ratchet/Http/HttpRequestParser.php(62): GuzzleHttp\Psr7\Message::parseRequest('HEAD / \r\n\r\n')
#2 /var/www/vhosts/ws_server/vendor/cboden/ratchet/src/Ratchet/Http/HttpRequestParser.php(40): Ratchet\Http\HttpRequestParser->parse('HEAD / \r\n\r\n')
#3 /var/www/vhosts/ws_server/vendor/cboden/ratchet/src/Ratchet/Http/HttpServer.php(42): Ratchet\Http\HttpRequestParser->onMessage(Object(Ratchet\Server\IoConnection), 
'HEAD / in /var/www/vhosts/ws_server/vendor/guzzlehttp/psr7/src/Request.php on line 35

From my local testing, it appears most invalid requests are trapped by the preg_match on line 205, but as long as your request first line is in the form of Method [space] Path [space] and anything following this does not contain a / then Guzzle will fail to get a value for $version and crash.

What's the exact version of guzzlehttp/psr7 do you have installed, please?

I don't think we should silently change the version to 1.1, but we can probably make sure that we throw the most appropriate exception.

I don't think we should silently change the version to 1.1, but we can probably make sure that we throw the most appropriate exception.

Line 209 is defaulting to 1.1 if not supplied, which is exactly this situation.
$version = isset($parts[2]) ? explode('/', $parts[2])[1] : '1.1';

Another option would be to extend the isset() test to check that $parts[2] includes a / and that it isn't the last non-space character. That would also resolve the Undefined Offset notice that is thrown because [1] doesn't exist.

What's the exact version of guzzlehttp/psr7 do you have installed, please?

According to my composer.lock file, I am on 2.4.3

            "name": "guzzlehttp/psr7",
            "version": "2.4.3",

What's the exact version of guzzlehttp/psr7 do you have installed, please?

The version of guzzlehttp/psr7 I use is the latest version of 2.4.3.

composer show guzzlehttp/psr7 | grep 'versions'

versions : * 2.4.3

I don't think we should silently change the version to 1.1, but we can probably make sure that we throw the most appropriate exception.

I just need to solve the situation where the process exits.

If it can be solved with proper exception handling,
I don't think there will be any problem using it.

The problem you are facing needs to be fixed at the other end, really. Not having an HTTP version is an invalid response. All I am discussing here is if we should be crashing in a different way. ;)

The problem you are facing needs to be fixed at the other end, really. Not having an HTTP version is an invalid response. All I am discussing here is if we should be crashing in a different way. ;)

Thank you for your kind reply.
I agree with the answer.

However, the HTTP header is manipulated to transmit
I don't think we can respond enough to malicious attacks.

$version = isset($parts[2]) ? explode('/', $parts[2])[1] : '1.1';

In Logic after 209 Line,

$version != null ? $version : '1.1'


If you can do a null check of $version,
I think we can solve these errors.

Or Ian-hubbard-alkolizer's suggestion

...
Another option would be to extend the isset() test to check that $parts[2] includes a / and that it isn't the last non-space character. That would also resolve the Undefined Offset notice that is thrown because [1] doesn't exist.

I think that's a good idea.
209 Line Example

if ((isset($parts[2]) && $parts[2]) && strpos($parts[2],'/') !== false)  {
  $version = explode('/', $parts[2])[1];
} else {
  $version = '1.1';
}
commented

This issue has been automatically marked as stale because it has not had recent activity. It will be closed after 2 weeks if no further activity occurs. Thank you for your contributions.