diff --git a/UPGRADE-2.2.md b/UPGRADE-2.2.md index 4fbe41726dfa..83e328fb8ce0 100644 --- a/UPGRADE-2.2.md +++ b/UPGRADE-2.2.md @@ -12,6 +12,9 @@ * The Stopwatch functionality was moved from HttpKernel\Debug to its own component + * The _method request parameter support has been disabled by default (call + Request::enableHttpMethodParameterOverride() to enable it). + #### Deprecations * The `Request::splitHttpAcceptHeader()` is deprecated and will be removed in 2.3. diff --git a/src/Symfony/Component/HttpFoundation/CHANGELOG.md b/src/Symfony/Component/HttpFoundation/CHANGELOG.md index 8cb0db89673b..4b7d0c64ace5 100644 --- a/src/Symfony/Component/HttpFoundation/CHANGELOG.md +++ b/src/Symfony/Component/HttpFoundation/CHANGELOG.md @@ -4,6 +4,8 @@ CHANGELOG 2.2.0 ----- + * added Request::getRealMethod() to get the "real" HTTP method (getMethod() returns the "intended" HTTP method) + * disabled _method request parameter support by default (call Request::enableHttpMethodParameterOverride() to enable it) * Request::splitHttpAcceptHeader() method is deprecated and will be removed in 2.3 2.1.0 diff --git a/src/Symfony/Component/HttpFoundation/Request.php b/src/Symfony/Component/HttpFoundation/Request.php index 79d34e842384..55bc4aa10d26 100644 --- a/src/Symfony/Component/HttpFoundation/Request.php +++ b/src/Symfony/Component/HttpFoundation/Request.php @@ -32,6 +32,8 @@ class Request { protected static $trustProxy = false; + protected static $httpMethodParameterOverride = false; + /** * @var \Symfony\Component\HttpFoundation\ParameterBag * @@ -503,6 +505,19 @@ public static function normalizeQueryString($qs) return implode('&', $parts); } + /** + * Enables support for the _method request parameter to determine the intended HTTP method. + * + * Be warned that enabling this feature might lead to CSRF issues in your code. + * Check that you are using CSRF tokens when required. + * + * The HTTP method can only be overriden when the real HTTP method is POST. + */ + public static function enableHttpMethodParameterOverride() + { + self::$httpMethodParameterOverride = true; + } + /** * Gets a "parameter" value. * @@ -915,26 +930,51 @@ public function setMethod($method) } /** - * Gets the request method. + * Gets the request "intended" method. + * + * If the X-HTTP-Method-Override header is set, and if the method is a POST, + * then it is used to determine the "real" intended HTTP method. + * + * The _method request parameter can also be used to determine the HTTP method, + * but only if enableHttpMethodParameterOverride() has been called. * * The method is always an uppercased string. * * @return string The request method * * @api + * + * @see getRealMethod */ public function getMethod() { if (null === $this->method) { $this->method = strtoupper($this->server->get('REQUEST_METHOD', 'GET')); + if ('POST' === $this->method) { - $this->method = strtoupper($this->headers->get('X-HTTP-METHOD-OVERRIDE', $this->request->get('_method', $this->query->get('_method', 'POST')))); + if ($method = $this->headers->get('X-HTTP-METHOD-OVERRIDE')) { + $this->method = strtoupper($method); + } elseif (self::$httpMethodParameterOverride) { + $this->method = strtoupper($this->request->get('_method', $this->query->get('_method', 'POST'))); + } } } return $this->method; } + /** + * Gets the "real" request method. + * + * @return string The request method + * + * @see getMethod + */ + public function getRealMethod() + { + return $this->server->get('REQUEST_METHOD', 'GET'); + } + /** * Gets the mime type associated with the format. * diff --git a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php index 488bf5eda9e2..6ccd725c646b 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/RequestTest.php @@ -675,13 +675,28 @@ public function testGetSetMethod() $request->setMethod('POST'); $request->request->set('_method', 'purge'); + $this->assertEquals('POST', $request->getMethod(), '->getMethod() does not return the method from _method if defined and POST but support not enabled'); + + $request = new Request(); + $request->setMethod('POST'); + $request->request->set('_method', 'purge'); + Request::enableHttpMethodParameterOverride(); $this->assertEquals('PURGE', $request->getMethod(), '->getMethod() returns the method from _method if defined and POST'); + $this->disableHttpMethodParameterOverride(); + $request = new Request(); + $request->setMethod('POST'); + $request->query->set('_method', 'purge'); + $this->assertEquals('POST', $request->getMethod(), '->getMethod() does not return the method from _method if defined and POST but support not enabled'); + + $request = new Request(); $request->setMethod('POST'); - $request->request->remove('_method'); $request->query->set('_method', 'purge'); + Request::enableHttpMethodParameterOverride(); $this->assertEquals('PURGE', $request->getMethod(), '->getMethod() returns the method from _method if defined and POST'); + $this->disableHttpMethodParameterOverride(); + $request = new Request(); $request->setMethod('POST'); $request->headers->set('X-HTTP-METHOD-OVERRIDE', 'delete'); $this->assertEquals('DELETE', $request->getMethod(), '->getMethod() returns the method from X-HTTP-Method-Override even though _method is set if defined and POST'); @@ -807,6 +822,8 @@ public function testCreateFromGlobals($method) unset($_SERVER['REQUEST_METHOD'], $_SERVER['CONTENT_TYPE']); + $request = Request::createFromGlobals(); + Request::enableHttpMethodParameterOverride(); $_POST['_method'] = $method; $_POST['foo6'] = 'bar6'; $_SERVER['REQUEST_METHOD'] = 'POST'; @@ -815,6 +832,7 @@ public function testCreateFromGlobals($method) $this->assertEquals('bar6', $request->request->get('foo6')); unset($_POST['_method'], $_POST['foo6'], $_SERVER['REQUEST_METHOD']); + $this->disableHttpMethodParameterOverride(); } public function testOverrideGlobals() @@ -1254,6 +1272,14 @@ private function stopTrustingProxyData() $property->setAccessible(true); $property->setValue(false); } + + private function disableHttpMethodParameterOverride() + { + $class = new \ReflectionClass('Symfony\\Component\\HttpFoundation\\Request'); + $property = $class->getProperty('httpMethodParameterOverride'); + $property->setAccessible(true); + $property->setValue(false); + } } class RequestContentProxy extends Request