From 547c6c8e75b545a7ff2c3153c6dfd938c0f658eb Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 5 Dec 2025 13:24:18 +0100 Subject: [PATCH] [HttpFoundation] Improve logic in Request::createFromGlobals() --- .../Component/HttpFoundation/Request.php | 42 +++++++------ .../HttpFoundation/Tests/RequestTest.php | 63 +++++++++++++++++-- 2 files changed, 80 insertions(+), 25 deletions(-) diff --git a/src/Symfony/Component/HttpFoundation/Request.php b/src/Symfony/Component/HttpFoundation/Request.php index b5ca911b0e76e..dc21ee16b0d2c 100644 --- a/src/Symfony/Component/HttpFoundation/Request.php +++ b/src/Symfony/Component/HttpFoundation/Request.php @@ -285,26 +285,30 @@ public function initialize(array $query = [], array $request = [], array $attrib */ public static function createFromGlobals(): static { - $request = self::createRequestFromFactory($_GET, $_POST, [], $_COOKIE, $_FILES, $_SERVER); - - if (!\in_array($request->server->get('REQUEST_METHOD', 'GET'), ['PUT', 'DELETE', 'PATCH', 'QUERY'], true)) { - return $request; + if (!\in_array($_SERVER['REQUEST_METHOD'] ?? null, ['PUT', 'DELETE', 'PATCH', 'QUERY'], true)) { + return self::createRequestFromFactory($_GET, $_POST, [], $_COOKIE, $_FILES, $_SERVER); } - if (\PHP_VERSION_ID >= 80400) { - try { - [$post, $files] = request_parse_body(); - - $request->request->add($post); - $request->files->add($files); - } catch (\RequestParseBodyException) { + if (\PHP_VERSION_ID < 80400) { + if (!isset($_SERVER['CONTENT_TYPE']) || str_starts_with($_SERVER['CONTENT_TYPE'], 'application/x-www-form-urlencoded')) { + $content = file_get_contents('php://input'); + parse_str($content, $post); + } else { + $content = null; + $post = $_POST; } - } elseif (str_starts_with($request->headers->get('CONTENT_TYPE', ''), 'application/x-www-form-urlencoded')) { - parse_str($request->getContent(), $data); - $request->request = new InputBag($data); + + return self::createRequestFromFactory($_GET, $post, [], $_COOKIE, $_FILES, $_SERVER, $content); } - return $request; + try { + [$post, $files] = request_parse_body(); + } catch (\RequestParseBodyException) { + $post = $_POST; + $files = $_FILES; + } + + return self::createRequestFromFactory($_GET, $post, [], $_COOKIE, $files, $_SERVER); } /** @@ -1538,10 +1542,8 @@ public function getProtocolVersion(): ?string */ public function getContent(bool $asResource = false) { - $currentContentIsResource = \is_resource($this->content); - - if (true === $asResource) { - if ($currentContentIsResource) { + if ($asResource) { + if (\is_resource($this->content)) { rewind($this->content); return $this->content; @@ -1561,7 +1563,7 @@ public function getContent(bool $asResource = false) return fopen('php://input', 'r'); } - if ($currentContentIsResource) { + if (\is_resource($this->content)) { rewind($this->content); return stream_get_contents($this->content); diff --git a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php index 707a2708b9a1e..cc3008b57144d 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php @@ -1431,9 +1431,17 @@ public function testFormUrlEncodedBodyParsing(string $method) $_SERVER['REQUEST_METHOD'] = $method; $_SERVER['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'; - $request = RequestContentProxy::createFromGlobals(); + MockPhpStreamWrapper::setInputContent('content=mycontent'); + stream_wrapper_unregister('php'); + stream_wrapper_register('php', MockPhpStreamWrapper::class); - $this->assertEquals('mycontent', $request->request->get('content')); + try { + $request = Request::createFromGlobals(); + + $this->assertSame('mycontent', $request->request->get('content')); + } finally { + stream_wrapper_restore('php'); + } } public function testOverrideGlobals() @@ -3050,11 +3058,56 @@ public static function providePreferredFormatRfc9110(): iterable } } -class RequestContentProxy extends Request +class MockPhpStreamWrapper { - public function getContent($asResource = false) + /** @var resource|null */ + public $context; + + private static string $inputContent = ''; + private string $content = ''; + private int $position = 0; + + public static function setInputContent(string $content): void + { + self::$inputContent = $content; + } + + public function stream_open(string $path, string $mode, int $options, ?string &$opened_path): bool + { + if ('php://input' === $path) { + $this->content = self::$inputContent; + $this->position = 0; + + return true; + } + + return false; + } + + public function stream_read(int $count): string + { + $result = substr($this->content, $this->position, $count); + $this->position += \strlen($result); + + return $result; + } + + public function stream_eof(): bool + { + return $this->position >= \strlen($this->content); + } + + public function stream_stat(): array { - return http_build_query(['content' => 'mycontent'], '', '&'); + return [ + 'size' => \strlen($this->content), + 'mode' => 0, + 'uid' => 0, + 'gid' => 0, + 'atime' => 0, + 'mtime' => 0, + 'ctime' => 0, + ]; } }