diff --git a/Build/phpstan/phpstan-baseline.neon b/Build/phpstan/phpstan-baseline.neon index 567c1e1597203cba477afccf01b916af47ac09f9..f0152cd56694df4f8fde95d7f3282886b3d0ecb6 100644 --- a/Build/phpstan/phpstan-baseline.neon +++ b/Build/phpstan/phpstan-baseline.neon @@ -675,16 +675,6 @@ parameters: count: 1 path: ../../typo3/sysext/core/Classes/Http/RedirectResponse.php - - - message: "#^Result of && is always false\\.$#" - count: 2 - path: ../../typo3/sysext/core/Classes/Http/Request.php - - - - message: "#^Strict comparison using \\!\\=\\= between null and null will always evaluate to false\\.$#" - count: 1 - path: ../../typo3/sysext/core/Classes/Http/Request.php - - message: "#^Call to function is_resource\\(\\) with Psr\\\\Http\\\\Message\\\\StreamInterface will always evaluate to false\\.$#" count: 1 @@ -1670,16 +1660,6 @@ parameters: count: 1 path: ../../typo3/sysext/core/Tests/Unit/Html/Parser/ParserTest.php - - - message: "#^Call to static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertNull\\(\\) with Psr\\\\Http\\\\Message\\\\UriInterface will always evaluate to false\\.$#" - count: 1 - path: ../../typo3/sysext/core/Tests/Unit/Http/RequestTest.php - - - - message: "#^Parameter \\#1 \\$uri of class TYPO3\\\\CMS\\\\Core\\\\Http\\\\Request constructor expects Psr\\\\Http\\\\Message\\\\UriInterface\\|string\\|null, array\\<int, string\\> given\\.$#" - count: 1 - path: ../../typo3/sysext/core/Tests/Unit/Http/RequestTest.php - - message: "#^Parameter \\#1 \\$body of class TYPO3\\\\CMS\\\\Core\\\\Http\\\\Response constructor expects Psr\\\\Http\\\\Message\\\\StreamInterface\\|string\\|null, array\\<int, string\\> given\\.$#" count: 1 diff --git a/typo3/sysext/core/Classes/Http/Request.php b/typo3/sysext/core/Classes/Http/Request.php index f4344f65071c27537cb5207aab1e68d38482a021..3e3705868ab29caf183da7ebae794c1b55d6047a 100644 --- a/typo3/sysext/core/Classes/Http/Request.php +++ b/typo3/sysext/core/Classes/Http/Request.php @@ -33,23 +33,20 @@ class Request extends Message implements RequestInterface { /** * The request-target, if it has been provided or calculated. - * @var string|null */ - protected $requestTarget; + protected ?string $requestTarget = null; /** - * The HTTP method, defaults to GET - * - * @var string|null + * The HTTP method. */ - protected $method; + protected string $method = 'GET'; /** * Supported HTTP methods * - * @var array + * @var non-empty-string[] */ - protected $supportedMethods = [ + protected array $supportedMethods = [ 'CONNECT', 'DELETE', 'GET', @@ -75,44 +72,42 @@ class Request extends Message implements RequestInterface /** * An instance of the Uri object - * @var UriInterface|null + * + * @todo It is a PSR-7 spec violation for this to be null. This should be corrected. */ - protected $uri; + protected ?UriInterface $uri; /** * Constructor, the only place to set all parameters of this Request * * @param string|UriInterface|null $uri URI for the request, if any. - * @param string|null $method HTTP method for the request, if any. + * @param string $method HTTP method for the request, if any. * @param string|resource|StreamInterface|null $body Message body, if any. * @param array $headers Headers for the message, if any. * @throws \InvalidArgumentException for any invalid value. */ - public function __construct($uri = null, $method = null, $body = 'php://input', array $headers = []) + public function __construct(UriInterface|string|null $uri = null, string $method = 'GET', $body = 'php://input', array $headers = []) { - - // Build a streamable object for the body - if ($body !== null && !is_string($body) && !is_resource($body) && !$body instanceof StreamInterface) { - throw new \InvalidArgumentException('Body must be a string stream resource identifier, a stream resource, or a StreamInterface instance', 1436717271); - } - - if ($body !== null && !$body instanceof StreamInterface) { - $body = new Stream($body); + // Upcast the body to a streamable object, or error + // if it's an invalid type. + if ($body instanceof StreamInterface) { + $this->body = $body; + } else { + $this->body = match (get_debug_type($body)) { + 'string', 'resource' => new Stream($body), + 'null' => null, + default => throw new \InvalidArgumentException('Body must be a string stream resource identifier, a stream resource, or a StreamInterface instance', 1436717271), + }; } if (is_string($uri)) { $uri = new Uri($uri); } - if (!$uri instanceof UriInterface && $uri !== null) { - throw new \InvalidArgumentException('Invalid URI provided; must be null, a string, or a UriInterface instance', 1436717272); - } - $this->validateMethod($method); $this->method = $method; $this->uri = $uri; - $this->body = $body; [$this->lowercasedHeaderNames, $headers] = $this->filterHeaders($headers); $this->assertHeaders($headers); $this->headers = $headers; @@ -143,10 +138,10 @@ class Request extends Message implements RequestInterface * key MUST be a header name, and each value MUST be an array of strings * for that header. */ - public function getHeaders() + public function getHeaders(): array { $headers = parent::getHeaders(); - if (!$this->hasHeader('host') && ($this->uri && $this->uri->getHost())) { + if (!$this->hasHeader('host') && ($this->uri?->getHost())) { $headers['host'] = [$this->getHostFromUri()]; } return $headers; @@ -166,9 +161,9 @@ class Request extends Message implements RequestInterface * header. If the header does not appear in the message, this method MUST * return an empty array. */ - public function getHeader($header) + public function getHeader($header): array { - if (!$this->hasHeader($header) && strtolower($header) === 'host' && ($this->uri && $this->uri->getHost())) { + if (!$this->hasHeader($header) && strtolower($header) === 'host' && ($this->uri?->getHost())) { return [$this->getHostFromUri()]; } return parent::getHeader($header); @@ -176,10 +171,8 @@ class Request extends Message implements RequestInterface /** * Retrieve the host from the URI instance - * - * @return string */ - protected function getHostFromUri() + protected function getHostFromUri(): string { $host = $this->uri->getHost(); $host .= $this->uri->getPort() ? ':' . $this->uri->getPort() : ''; @@ -199,17 +192,17 @@ class Request extends Message implements RequestInterface * * If no URI is available, and no request-target has been specifically * provided, this method MUST return the string "/". - * - * @return string */ - public function getRequestTarget() + public function getRequestTarget(): string { if ($this->requestTarget !== null) { return $this->requestTarget; } + if (!$this->uri) { return '/'; } + $target = $this->uri->getPath(); if ($this->uri->getQuery()) { @@ -236,11 +229,8 @@ class Request extends Message implements RequestInterface * * @link https://tools.ietf.org/html/rfc7230#section-2.7 (for the various * request-target forms allowed in request messages) - * - * @param mixed $requestTarget - * @return static */ - public function withRequestTarget($requestTarget) + public function withRequestTarget(mixed $requestTarget): static { if (preg_match('#\s#', $requestTarget)) { throw new \InvalidArgumentException('Invalid request target provided which contains whitespaces.', 1436717273); @@ -252,12 +242,10 @@ class Request extends Message implements RequestInterface /** * Retrieves the HTTP method of the request, defaults to GET - * - * @return string Returns the request method. */ - public function getMethod() + public function getMethod(): string { - return !empty($this->method) ? $this->method : 'GET'; + return $this->method; } /** @@ -275,7 +263,7 @@ class Request extends Message implements RequestInterface * @return static * @throws \InvalidArgumentException for invalid HTTP methods. */ - public function withMethod($method) + public function withMethod($method): static { $clonedObject = clone $this; $clonedObject->method = $method; @@ -287,11 +275,14 @@ class Request extends Message implements RequestInterface * * This method MUST return a UriInterface instance. * + * @todo This method currently may return null instead. This is + * a PSR-7 spec violation that should be corrected. + * * @link https://tools.ietf.org/html/rfc3986#section-4.3 - * @return \Psr\Http\Message\UriInterface Returns a UriInterface instance + * @return \Psr\Http\Message\UriInterface|null Returns a UriInterface instance * representing the URI of the request. */ - public function getUri() + public function getUri(): ?UriInterface { return $this->uri; } @@ -327,7 +318,7 @@ class Request extends Message implements RequestInterface * @param bool $preserveHost Preserve the original state of the Host header. * @return static */ - public function withUri(UriInterface $uri, $preserveHost = false) + public function withUri(UriInterface $uri, $preserveHost = false): static { $clonedObject = clone $this; $clonedObject->uri = $uri; @@ -354,20 +345,17 @@ class Request extends Message implements RequestInterface /** * Validate the HTTP method, helper function. * - * @param string|null $method * @throws \InvalidArgumentException on invalid HTTP method. */ - protected function validateMethod($method) + protected function validateMethod(?string $method): void { - if ($method !== null) { - if (!is_string($method)) { - $methodAsString = get_debug_type($method); - throw new \InvalidArgumentException('Unsupported HTTP method "' . $methodAsString . '".', 1436717274); - } - $method = strtoupper($method); - if (!in_array($method, $this->supportedMethods, true)) { - throw new \InvalidArgumentException('Unsupported HTTP method "' . $method . '".', 1436717275); - } + if (is_null($method)) { + return; + } + + $method = strtoupper($method); + if (!in_array($method, $this->supportedMethods, true)) { + throw new \InvalidArgumentException('Unsupported HTTP method "' . $method . '".', 1436717275); } } } diff --git a/typo3/sysext/core/Classes/Http/ServerRequest.php b/typo3/sysext/core/Classes/Http/ServerRequest.php index 242e3b99f8d591ebdd145bcbf13806b3fd3094d0..cc7f1bd6b59ac3dd45846445baff4201429dfe46 100644 --- a/typo3/sysext/core/Classes/Http/ServerRequest.php +++ b/typo3/sysext/core/Classes/Http/ServerRequest.php @@ -82,7 +82,7 @@ class ServerRequest extends Request implements ServerRequestInterface $this->validateUploadedFiles($uploadedFiles); } - parent::__construct($uri, $method, $body, $headers); + parent::__construct($uri, $method ?? 'GET', $body, $headers); $this->serverParams = $serverParams; $this->uploadedFiles = $uploadedFiles ?? []; diff --git a/typo3/sysext/core/Tests/Unit/Http/RequestFactoryTest.php b/typo3/sysext/core/Tests/Unit/Http/RequestFactoryTest.php index 533216cc6000cf3b77c61dfb93728bc35d2c6602..53cdf46eedbebbc283b5725f33cd533f6e324890 100644 --- a/typo3/sysext/core/Tests/Unit/Http/RequestFactoryTest.php +++ b/typo3/sysext/core/Tests/Unit/Http/RequestFactoryTest.php @@ -67,33 +67,6 @@ class RequestFactoryTest extends UnitTestCase self::assertSame('Foo', $body->__toString()); } - /** - * @return array - */ - public function invalidRequestUriDataProvider(): array - { - return [ - 'true' => [true], - 'false' => [false], - 'int' => [1], - 'float' => [1.1], - 'array' => [['http://example.com']], - 'stdClass' => [(object)['href' => 'http://example.com']], - ]; - } - - /** - * @dataProvider invalidRequestUriDataProvider - * @test - */ - public function constructorRaisesExceptionForInvalidUri($uri): void - { - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionCode(1436717272); - $factory = new RequestFactory(new GuzzleClientFactory()); - $factory->createRequest('GET', $uri); - } - /** * @test */ diff --git a/typo3/sysext/core/Tests/Unit/Http/RequestTest.php b/typo3/sysext/core/Tests/Unit/Http/RequestTest.php index 5c56dfe63615c3b8bfbe77040e5d68f9af7a573c..32ffe2498db818af81b20acbca69b61d95943112 100644 --- a/typo3/sysext/core/Tests/Unit/Http/RequestTest.php +++ b/typo3/sysext/core/Tests/Unit/Http/RequestTest.php @@ -63,15 +63,6 @@ class RequestTest extends UnitTestCase self::assertNull($this->request->getUri()); } - /** - * @test - */ - public function constructorRaisesExceptionForInvalidStream(): void - { - $this->expectException(\InvalidArgumentException::class); - new Request(['TOTALLY INVALID']); - } - /** * @test */ @@ -112,58 +103,6 @@ class RequestTest extends UnitTestCase } } - /** - * @return array - */ - public function invalidRequestUriDataProvider(): array - { - return [ - 'true' => [true], - 'false' => [false], - 'int' => [1], - 'float' => [1.1], - 'array' => [['http://example.com']], - 'stdClass' => [(object)['href' => 'http://example.com']], - ]; - } - - /** - * @dataProvider invalidRequestUriDataProvider - * @test - */ - public function constructorRaisesExceptionForInvalidUri($uri): void - { - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionCode(1436717272); - new Request($uri); - } - - /** - * @return array - */ - public function invalidRequestMethodDataProvider(): array - { - return [ - 'true' => [true], - 'false' => [false], - 'int' => [1], - 'float' => [1.1], - 'array' => [['POST']], - 'stdClass' => [(object)['method' => 'POST']], - ]; - } - - /** - * @dataProvider invalidRequestMethodDataProvider - * @test - */ - public function constructorRaisesExceptionForInvalidMethodByType($method): void - { - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionCode(1436717274); - new Request(null, $method); - } - /** * @test */ @@ -197,7 +136,7 @@ class RequestTest extends UnitTestCase { $this->expectException(\InvalidArgumentException::class); $this->expectExceptionCode(1436717271); - new Request(null, null, $body); + new Request(null, 'GET', $body); } /** @@ -219,7 +158,7 @@ class RequestTest extends UnitTestCase 'x-valid-string' => ['VALID'], 'x-valid-array' => ['VALID'], ]; - $request = new Request(null, null, 'php://memory', $headers); + $request = new Request(null, 'GET', 'php://memory', $headers); self::assertEquals($expected, $request->getHeaders()); } @@ -492,7 +431,7 @@ class RequestTest extends UnitTestCase */ public function headerCanBeRetrieved($header, $value, $expected): void { - $request = new Request(null, null, 'php://memory', [$header => $value]); + $request = new Request(null, 'GET', 'php://memory', [$header => $value]); self::assertEquals([$expected], $request->getHeader(strtolower($header))); self::assertEquals([$expected], $request->getHeader(strtoupper($header))); } @@ -525,7 +464,7 @@ class RequestTest extends UnitTestCase public function constructorRaisesExceptionForHeadersWithCRLFVectors($name, $value): void { $this->expectException(\InvalidArgumentException::class); - new Request(null, null, 'php://memory', [$name => $value]); + new Request(null, 'GET', 'php://memory', [$name => $value]); } /** diff --git a/typo3/sysext/core/Tests/Unit/Http/ServerRequestFactoryTest.php b/typo3/sysext/core/Tests/Unit/Http/ServerRequestFactoryTest.php index 123fd1ef59c7298e44c427506eca47cbaf730c4a..139bf1e8d7001354ab4889d86856afc938d11d31 100644 --- a/typo3/sysext/core/Tests/Unit/Http/ServerRequestFactoryTest.php +++ b/typo3/sysext/core/Tests/Unit/Http/ServerRequestFactoryTest.php @@ -68,33 +68,6 @@ class ServerRequestFactoryTest extends UnitTestCase self::assertSame('Foo', $body->__toString()); } - /** - * @return array - */ - public function invalidRequestUriDataProvider(): array - { - return [ - 'true' => [true], - 'false' => [false], - 'int' => [1], - 'float' => [1.1], - 'array' => [['http://example.com']], - 'stdClass' => [(object)['href' => 'http://example.com']], - ]; - } - - /** - * @dataProvider invalidRequestUriDataProvider - * @test - */ - public function constructorRaisesExceptionForInvalidUri($uri): void - { - $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionCode(1436717272); - $factory = new ServerRequestFactory(); - $factory->createServerRequest('GET', $uri); - } - /** * @test */