Skip to content

Commit e9b1d43

Browse files
[HttpFoundation] Add Request::$allowedHttpMethodOverride to list which HTTP methods can be overridden
1 parent 3d9e8f1 commit e9b1d43

File tree

3 files changed

+81
-4
lines changed

3 files changed

+81
-4
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ CHANGELOG
77
* Add `#[WithHttpStatus]` to define status codes: 404 for `SignedUriException` and 403 for `ExpiredSignedUriException`
88
* Add support for the `QUERY` HTTP method
99
* Add support for structured MIME suffix
10+
* Add `Request::set/getAllowedHttpMethodOverride()` to list which HTTP methods can be overridden
1011
* Deprecate using `Request::sendHeaders()` after headers have already been sent; use a `StreamedResponse` instead
1112
* Deprecate method `Request::get()`, use properties `->attributes`, `query` or `request` directly instead
1213

Request.php

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,13 @@ class Request
8181

8282
protected static bool $httpMethodParameterOverride = false;
8383

84+
/**
85+
* The HTTP methods that can be overridden.
86+
*
87+
* @var uppercase-string[]|null
88+
*/
89+
protected static ?array $allowedHttpMethodOverride = null;
90+
8491
/**
8592
* Custom parameters.
8693
*/
@@ -670,6 +677,30 @@ public static function getHttpMethodParameterOverride(): bool
670677
return self::$httpMethodParameterOverride;
671678
}
672679

680+
/**
681+
* Sets the list of HTTP methods that can be overridden.
682+
*
683+
* Set to null to allow all methods to be overridden (default). Set to an
684+
* empty array to disallow overrides entirely. Otherwise, provide the list
685+
* of uppercased method names that are allowed.
686+
*
687+
* @param uppercase-string[]|null $methods
688+
*/
689+
public static function setAllowedHttpMethodOverride(?array $methods): void
690+
{
691+
self::$allowedHttpMethodOverride = $methods;
692+
}
693+
694+
/**
695+
* Gets the list of HTTP methods that can be overridden.
696+
*
697+
* @return uppercase-string[]|null
698+
*/
699+
public static function getAllowedHttpMethodOverride(): ?array
700+
{
701+
return self::$allowedHttpMethodOverride;
702+
}
703+
673704
/**
674705
* Gets a "parameter" value from any bag.
675706
*
@@ -1187,7 +1218,7 @@ public function getMethod(): string
11871218

11881219
$this->method = strtoupper($this->server->get('REQUEST_METHOD', 'GET'));
11891220

1190-
if ('POST' !== $this->method) {
1221+
if ('POST' !== $this->method || !(self::$allowedHttpMethodOverride ?? true)) {
11911222
return $this->method;
11921223
}
11931224

@@ -1203,11 +1234,11 @@ public function getMethod(): string
12031234

12041235
$method = strtoupper($method);
12051236

1206-
if (\in_array($method, ['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'PATCH', 'PURGE', 'TRACE', 'QUERY'], true)) {
1207-
return $this->method = $method;
1237+
if (self::$allowedHttpMethodOverride && !\in_array($method, self::$allowedHttpMethodOverride, true)) {
1238+
return $this->method;
12081239
}
12091240

1210-
if (!preg_match('/^[A-Z]++$/D', $method)) {
1241+
if (\strlen($method) !== strspn($method, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')) {
12111242
throw new SuspiciousOperationException('Invalid HTTP method override.');
12121243
}
12131244

Tests/RequestTest.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ protected function tearDown(): void
3434
{
3535
Request::setTrustedProxies([], -1);
3636
Request::setTrustedHosts([]);
37+
Request::setAllowedHttpMethodOverride(null);
3738
}
3839

3940
public function testInitialize()
@@ -256,6 +257,50 @@ public function testCreate()
256257
$this->assertEquals('http://test.com/foo', $request->getUri());
257258
}
258259

260+
public function testHttpMethodOverrideRespectsAllowedListWithHeader()
261+
{
262+
$request = Request::create('http://example.com/', 'POST');
263+
$request->headers->set('X-HTTP-METHOD-OVERRIDE', 'PATCH');
264+
265+
Request::setAllowedHttpMethodOverride(['PUT', 'PATCH']);
266+
267+
$this->assertSame('PATCH', $request->getMethod());
268+
}
269+
270+
public function testHttpMethodOverrideDisallowedSkipsOverrideWithHeader()
271+
{
272+
$request = Request::create('http://example.com/', 'POST');
273+
$request->headers->set('X-HTTP-METHOD-OVERRIDE', 'DELETE');
274+
275+
Request::setAllowedHttpMethodOverride(['PUT', 'PATCH']);
276+
277+
$this->assertSame('POST', $request->getMethod());
278+
}
279+
280+
public function testHttpMethodOverrideDisabledWithEmptyAllowedList()
281+
{
282+
$request = Request::create('http://example.com/', 'POST');
283+
$request->headers->set('X-HTTP-METHOD-OVERRIDE', 'PUT');
284+
285+
Request::setAllowedHttpMethodOverride([]);
286+
287+
$this->assertSame('POST', $request->getMethod());
288+
}
289+
290+
public function testHttpMethodOverrideRespectsAllowedListWithParameter()
291+
{
292+
Request::enableHttpMethodParameterOverride();
293+
Request::setAllowedHttpMethodOverride(['PUT']);
294+
295+
try {
296+
$request = Request::create('http://example.com/', 'POST', ['_method' => 'PUT']);
297+
298+
$this->assertSame('PUT', $request->getMethod());
299+
} finally {
300+
(new \ReflectionProperty(Request::class, 'httpMethodParameterOverride'))->setValue(null, false);
301+
}
302+
}
303+
259304
public function testCreateWithRequestUri()
260305
{
261306
$request = Request::create('http://test.com:80/foo');

0 commit comments

Comments
 (0)