From be834dba54222c136af31bba9d9a9b05366671f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Sun, 13 Mar 2022 17:20:27 +0100 Subject: [PATCH] Reject non-HTTP schemes in StreamHandler (#2989) * Handle non-HTTP schemes gracefully in StreamHandler If an URI that does not use the HTTP stream wrapper is passed to the StreamHandler then the magic `$http_response_header` variable will not be filled, thus remaining `null`. This ultimately results in a `TypeError`, because `null` is passed to HeaderProcessor::parseHeaders(), which expects an `array`. * Reject non-HTTP schemes in StreamHandler Non-HTTP schemes are effectively not supported, because the HTTP response headers will only be filled for the `http` and `https` stream wrappers. Also Guzzle is an HTTP client after all. Reject non-HTTP schemes early on to improve error messages and to prevent possible exploits using odd stream wrappers in case an non-fully-trusted URL is passed to Guzzle. --- src/Handler/StreamHandler.php | 6 +++++- tests/Handler/StreamHandlerTest.php | 15 +++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/Handler/StreamHandler.php b/src/Handler/StreamHandler.php index 70c646be5..641c58f77 100644 --- a/src/Handler/StreamHandler.php +++ b/src/Handler/StreamHandler.php @@ -266,6 +266,10 @@ private function createStream(RequestInterface $request, array $options) $methods = \array_flip(\get_class_methods(__CLASS__)); } + if (!\in_array($request->getUri()->getScheme(), ['http', 'https'])) { + throw new RequestException(\sprintf("The scheme '%s' is not supported.", $request->getUri()->getScheme()), $request); + } + // HTTP/1.1 streams using the PHP stream wrapper require a // Connection: close header if ($request->getProtocolVersion() == '1.1' @@ -318,7 +322,7 @@ static function () use ($context, $params) { return $this->createResource( function () use ($uri, &$http_response_header, $contextResource, $context, $options, $request) { $resource = @\fopen((string) $uri, 'r', false, $contextResource); - $this->lastHeaders = $http_response_header; + $this->lastHeaders = $http_response_header ?? []; if (false === $resource) { throw new ConnectException(sprintf('Connection refused for URI %s', $uri), $request, null, $context); diff --git a/tests/Handler/StreamHandlerTest.php b/tests/Handler/StreamHandlerTest.php index c0c6c9f63..41f293ed1 100644 --- a/tests/Handler/StreamHandlerTest.php +++ b/tests/Handler/StreamHandlerTest.php @@ -738,4 +738,19 @@ public function testHandlesInvalidStatusCodeGracefully() ] )->wait(); } + + public function testRejectsNonHttpSchemes() + { + $handler = new StreamHandler(); + + $this->expectException(RequestException::class); + $this->expectExceptionMessage("The scheme 'file' is not supported."); + + $handler( + new Request('GET', 'file:///etc/passwd'), + [ + RequestOptions::STREAM => true, + ] + )->wait(); + } }