From 167edf1388e29b4a008231667aeec4ed0b996f18 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 3 Oct 2025 16:48:38 +0200 Subject: [PATCH] [HttpFoundation] Deprecate HTTP method override for methods GET, HEAD, CONNECT and TRACE --- UPGRADE-7.4.md | 1 + .../Controller/ProfilerControllerTest.php | 2 +- .../Component/HttpFoundation/CHANGELOG.md | 1 + .../Component/HttpFoundation/Request.php | 4 ++++ .../HttpFoundation/Tests/RequestTest.php | 12 ++++++++++++ .../HttpKernel/HttpCache/AbstractSurrogate.php | 2 +- .../RequestPayloadValueResolverTest.php | 2 +- .../AbstractLoginFormAuthenticatorTest.php | 12 ++++++------ ...EncodedBodyAccessTokenAuthenticatorTest.php | 18 +++++++++--------- 9 files changed, 36 insertions(+), 18 deletions(-) diff --git a/UPGRADE-7.4.md b/UPGRADE-7.4.md index 3e2f0d388fbb8..7480a818c1d87 100644 --- a/UPGRADE-7.4.md +++ b/UPGRADE-7.4.md @@ -82,6 +82,7 @@ HttpFoundation * Add argument `$subtypeFallback` to `Request::getFormat()` * Deprecate using `Request::sendHeaders()` after headers have already been sent; use a `StreamedResponse` instead * Deprecate method `Request::get()`, use properties `->attributes`, `query` or `request` directly instead + * Deprecate HTTP method override for methods GET, HEAD, CONNECT and TRACE; it will be ignored in Symfony 8.0 HttpKernel ---------- diff --git a/src/Symfony/Bundle/WebProfilerBundle/Tests/Controller/ProfilerControllerTest.php b/src/Symfony/Bundle/WebProfilerBundle/Tests/Controller/ProfilerControllerTest.php index aa04f60993b28..583d86dcd7c06 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Tests/Controller/ProfilerControllerTest.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Tests/Controller/ProfilerControllerTest.php @@ -182,7 +182,7 @@ public function testOpeningDisallowedPaths($path, $isAllowed) $controller = new ProfilerController($urlGenerator, $profiler, $twig, [], null, __DIR__.'/../..'); try { - $response = $controller->openAction(Request::create('/_wdt/open', Request::METHOD_GET, ['file' => $path])); + $response = $controller->openAction(Request::create('/_wdt/open', 'GET', ['file' => $path])); $this->assertEquals(200, $response->getStatusCode()); $this->assertTrue($isAllowed); } catch (NotFoundHttpException $e) { diff --git a/src/Symfony/Component/HttpFoundation/CHANGELOG.md b/src/Symfony/Component/HttpFoundation/CHANGELOG.md index 4bb2667b4f842..c0fb4cc4e8763 100644 --- a/src/Symfony/Component/HttpFoundation/CHANGELOG.md +++ b/src/Symfony/Component/HttpFoundation/CHANGELOG.md @@ -11,6 +11,7 @@ CHANGELOG * Deprecate using `Request::sendHeaders()` after headers have already been sent; use a `StreamedResponse` instead * Deprecate method `Request::get()`, use properties `->attributes`, `query` or `request` directly instead * Make `Request::createFromGlobals()` parse the body of PUT, DELETE, PATCH and QUERY requests + * Deprecate HTTP method override for methods GET, HEAD, CONNECT and TRACE; it will be ignored in Symfony 8.0 7.3 --- diff --git a/src/Symfony/Component/HttpFoundation/Request.php b/src/Symfony/Component/HttpFoundation/Request.php index 2f25afeac5e02..abe4b905ceb09 100644 --- a/src/Symfony/Component/HttpFoundation/Request.php +++ b/src/Symfony/Component/HttpFoundation/Request.php @@ -1244,6 +1244,10 @@ public function getMethod(): string $method = strtoupper($method); + if (\in_array($method, ['GET', 'HEAD', 'CONNECT', 'TRACE'], true)) { + trigger_deprecation('symfony/http-foundation', '7.4', 'HTTP method override is deprecated for methods GET, HEAD, CONNECT and TRACE; it will be ignored in Symfony 8.0.', $method); + } + if (self::$allowedHttpMethodOverride && !\in_array($method, self::$allowedHttpMethodOverride, true)) { return $this->method; } diff --git a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php index 699b5d51ea598..c5bb41ace9652 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php @@ -1079,6 +1079,18 @@ public function testGetSetMethod() $this->assertSame('POST', $request->getMethod(), '->getMethod() returns the request method if invalid type is defined in query'); } + #[IgnoreDeprecations] + #[Group('legacy')] + public function testUnsafeMethodOverride() + { + $request = new Request(); + $request->setMethod('POST'); + $request->headers->set('X-HTTP-METHOD-OVERRIDE', 'get'); + + $this->expectUserDeprecationMessage('Since symfony/http-foundation 7.4: HTTP method override is deprecated for methods GET, HEAD, CONNECT and TRACE; it will be ignored in Symfony 8.0.'); + $this->assertSame('GET', $request->getMethod()); + } + #[DataProvider('getClientIpsProvider')] public function testGetClientIp($expected, $remoteAddr, $httpForwardedFor, $trustedProxies) { diff --git a/src/Symfony/Component/HttpKernel/HttpCache/AbstractSurrogate.php b/src/Symfony/Component/HttpKernel/HttpCache/AbstractSurrogate.php index 07a125af538d3..81b775a51bc9f 100644 --- a/src/Symfony/Component/HttpKernel/HttpCache/AbstractSurrogate.php +++ b/src/Symfony/Component/HttpKernel/HttpCache/AbstractSurrogate.php @@ -70,7 +70,7 @@ public function needsParsing(Response $response): bool public function handle(HttpCache $cache, string $uri, string $alt, bool $ignoreErrors): string { - $subRequest = Request::create($uri, Request::METHOD_GET, [], $cache->getRequest()->cookies->all(), [], $cache->getRequest()->server->all()); + $subRequest = Request::create($uri, 'GET', [], $cache->getRequest()->cookies->all(), [], $cache->getRequest()->server->all()); try { $response = $cache->handle($subRequest, HttpKernelInterface::SUB_REQUEST, true); diff --git a/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/RequestPayloadValueResolverTest.php b/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/RequestPayloadValueResolverTest.php index cdd9d3faf6291..ea98cc0c828d5 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/RequestPayloadValueResolverTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/RequestPayloadValueResolverTest.php @@ -937,7 +937,7 @@ public function testConfigKeyForQueryString() $argument = new ArgumentMetadata('filtered', QueryPayload::class, false, false, null, false, [ MapQueryString::class => new MapQueryString(key: 'value'), ]); - $request = Request::create('/', Request::METHOD_GET, ['value' => ['page' => 1.0]]); + $request = Request::create('/', 'GET', ['value' => ['page' => 1.0]]); $kernel = $this->createMock(HttpKernelInterface::class); $arguments = $resolver->resolve($request, $argument); diff --git a/src/Symfony/Component/Security/Http/Tests/Authenticator/AbstractLoginFormAuthenticatorTest.php b/src/Symfony/Component/Security/Http/Tests/Authenticator/AbstractLoginFormAuthenticatorTest.php index ea017fff12496..aca27a1d977ec 100644 --- a/src/Symfony/Component/Security/Http/Tests/Authenticator/AbstractLoginFormAuthenticatorTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Authenticator/AbstractLoginFormAuthenticatorTest.php @@ -34,7 +34,7 @@ public static function provideSupportsData(): iterable { yield [ '/login', - Request::create('http://localhost/login', Request::METHOD_POST, [], [], [], [ + Request::create('http://localhost/login', 'POST', [], [], [], [ 'DOCUMENT_ROOT' => '/var/www/app/public', 'PHP_SELF' => '/index.php', 'SCRIPT_FILENAME' => '/var/www/app/public/index.php', @@ -44,7 +44,7 @@ public static function provideSupportsData(): iterable ]; yield [ '/login', - Request::create('http://localhost/somepath', Request::METHOD_POST, [], [], [], [ + Request::create('http://localhost/somepath', 'POST', [], [], [], [ 'DOCUMENT_ROOT' => '/var/www/app/public', 'PHP_SELF' => '/index.php', 'SCRIPT_FILENAME' => '/var/www/app/public/index.php', @@ -54,7 +54,7 @@ public static function provideSupportsData(): iterable ]; yield [ '/folder/login', - Request::create('http://localhost/folder/login', Request::METHOD_POST, [], [], [], [ + Request::create('http://localhost/folder/login', 'POST', [], [], [], [ 'DOCUMENT_ROOT' => '/var/www/app/public', 'PHP_SELF' => '/folder/index.php', 'SCRIPT_FILENAME' => '/var/www/app/public/index.php', @@ -64,7 +64,7 @@ public static function provideSupportsData(): iterable ]; yield [ '/folder/login', - Request::create('http://localhost/folder/somepath', Request::METHOD_POST, [], [], [], [ + Request::create('http://localhost/folder/somepath', 'POST', [], [], [], [ 'DOCUMENT_ROOT' => '/var/www/app/public', 'PHP_SELF' => '/folder/index.php', 'SCRIPT_FILENAME' => '/var/www/app/public/index.php', @@ -74,7 +74,7 @@ public static function provideSupportsData(): iterable ]; yield [ '/index.php/login', - Request::create('http://localhost/index.php/login', Request::METHOD_POST, [], [], [], [ + Request::create('http://localhost/index.php/login', 'POST', [], [], [], [ 'DOCUMENT_ROOT' => '/var/www/app/public', 'PHP_SELF' => '/index.php', 'SCRIPT_FILENAME' => '/var/www/app/public/index.php', @@ -84,7 +84,7 @@ public static function provideSupportsData(): iterable ]; yield [ '/index.php/login', - Request::create('http://localhost/index.php/somepath', Request::METHOD_POST, [], [], [], [ + Request::create('http://localhost/index.php/somepath', 'POST', [], [], [], [ 'DOCUMENT_ROOT' => '/var/www/app/public', 'PHP_SELF' => '/index.php', 'SCRIPT_FILENAME' => '/var/www/app/public/index.php', diff --git a/src/Symfony/Component/Security/Http/Tests/Authenticator/AccessToken/FormEncodedBodyAccessTokenAuthenticatorTest.php b/src/Symfony/Component/Security/Http/Tests/Authenticator/AccessToken/FormEncodedBodyAccessTokenAuthenticatorTest.php index c8083c4b3e4ca..2287eced41eee 100644 --- a/src/Symfony/Component/Security/Http/Tests/Authenticator/AccessToken/FormEncodedBodyAccessTokenAuthenticatorTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Authenticator/AccessToken/FormEncodedBodyAccessTokenAuthenticatorTest.php @@ -40,7 +40,7 @@ public function testSupport() $this->setUpAuthenticator(); $request = new Request([], [], [], [], [], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']); $request->request->set('access_token', 'INVALID_ACCESS_TOKEN'); - $request->setMethod(Request::METHOD_POST); + $request->setMethod('POST'); $this->assertNull($this->authenticator->supports($request)); } @@ -50,7 +50,7 @@ public function testSupportsWithCustomParameter() $this->setUpAuthenticator('protection-token'); $request = new Request([], [], [], [], [], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']); $request->request->set('protection-token', 'INVALID_ACCESS_TOKEN'); - $request->setMethod(Request::METHOD_POST); + $request->setMethod('POST'); $this->assertNull($this->authenticator->supports($request)); } @@ -61,7 +61,7 @@ public function testAuthenticate() $this->setUpAuthenticator(); $request = new Request([], [], [], [], [], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded'], 'access_token=VALID_ACCESS_TOKEN'); $request->request->set('access_token', 'VALID_ACCESS_TOKEN'); - $request->setMethod(Request::METHOD_POST); + $request->setMethod('POST'); $passport = $this->authenticator->authenticate($request); $this->assertInstanceOf(SelfValidatingPassport::class, $passport); @@ -73,7 +73,7 @@ public function testAuthenticateWithCustomParameter() $this->setUpAuthenticator('protection-token'); $request = new Request([], [], [], [], [], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']); $request->request->set('protection-token', 'VALID_ACCESS_TOKEN'); - $request->setMethod(Request::METHOD_POST); + $request->setMethod('POST'); $passport = $this->authenticator->authenticate($request); $this->assertInstanceOf(SelfValidatingPassport::class, $passport); @@ -93,24 +93,24 @@ public function testAuthenticateInvalid(Request $request, string $errorMessage, public static function provideInvalidAuthenticateData(): iterable { $request = new Request(); - $request->setMethod(Request::METHOD_GET); + $request->setMethod('GET'); yield [$request, 'Invalid credentials.', BadCredentialsException::class]; $request = new Request(); - $request->setMethod(Request::METHOD_POST); + $request->setMethod('POST'); yield [$request, 'Invalid credentials.', BadCredentialsException::class]; $request = new Request([], [], [], [], [], ['HTTP_AUTHORIZATION' => 'Bearer VALID_ACCESS_TOKEN']); - $request->setMethod(Request::METHOD_POST); + $request->setMethod('POST'); yield [$request, 'Invalid credentials.', BadCredentialsException::class]; $request = new Request(); - $request->setMethod(Request::METHOD_POST); + $request->setMethod('POST'); $request->request->set('foo', 'VALID_ACCESS_TOKEN'); yield [$request, 'Invalid credentials.', BadCredentialsException::class]; $request = new Request([], [], [], [], [], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']); - $request->setMethod(Request::METHOD_POST); + $request->setMethod('POST'); $request->request->set('access_token', 'INVALID_ACCESS_TOKEN'); yield [$request, 'Invalid access token or invalid user.', BadCredentialsException::class]; }