From e7943971d25c3021a47deef2fa377ec2d80f8169 Mon Sep 17 00:00:00 2001 From: Klimov Paul Date: Tue, 12 Sep 2017 14:42:12 +0300 Subject: [PATCH] `yii\web\Request::getBodyParams()` now generates 415 'Unsupported Media Type' error on invalid or missing 'Content-Type' header --- framework/CHANGELOG.md | 1 + framework/web/Request.php | 17 +++++++++++------ tests/framework/rest/UrlRuleTest.php | 4 ++-- tests/framework/web/RequestTest.php | 28 ++++++++++++++++++++++++++++ tests/framework/web/UrlRuleTest.php | 4 ++-- 5 files changed, 44 insertions(+), 10 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 7dd79f4..1ddbf9a 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -8,6 +8,7 @@ Yii Framework 2 Change Log - Bug #14458: Fixed `yii\filters\VerbFilter` uses case-insensitive comparison for the HTTP method name (klimov-paul) - Enh #879: Caching implementation refactored according to PSR-16 'Simple Cache' specification (klimov-paul) - Enh #11328: Added support for PSR-7 'HTTP Message' (klimov-paul) +- Enh #14522: `yii\web\Request::getBodyParams()` now generates 415 'Unsupported Media Type' error on invalid or missing 'Content-Type' header (klimov-paul) - Enh #13799: CAPTCHA rendering logic extracted into `yii\captcha\DriverInterface`, which instance is available via `yii\captcha\CaptchaAction::$driver` field (vladis84, klimov-paul) - Enh #9260: Mail view rendering encapsulated into `yii\mail\Template` class allowing rendering in isolation and access to `yii\mail\MessageInterface` instance via `$this->context->message` inside the view (klimov-paul) - Enh #11058: Add `$checkAjax` parameter to method `yii\web\Controller::redirect()` which controls redirection in AJAX and PJAX requests (ivanovyordan) diff --git a/framework/web/Request.php b/framework/web/Request.php index a2ea991..045261a 100644 --- a/framework/web/Request.php +++ b/framework/web/Request.php @@ -509,7 +509,8 @@ class Request extends \yii\base\Request implements RequestInterface * If no parsers are configured for the current [[contentType]] it uses the PHP function `mb_parse_str()` * to parse the [[rawBody|request body]]. * @return array the request parameters given in the request body. - * @throws \yii\base\InvalidConfigException if a registered parser does not implement the [[RequestParserInterface]]. + * @throws InvalidConfigException if a registered parser does not implement the [[RequestParserInterface]]. + * @throws UnsupportedMediaTypeHttpException if unable to parse raw body. * @see getMethod() * @see getBodyParam() * @see setBodyParams() @@ -523,12 +524,10 @@ class Request extends \yii\base\Request implements RequestInterface return $this->_bodyParams; } - $rawContentType = $this->getContentType(); - if (($pos = strpos($rawContentType, ';')) !== false) { + $contentType = $this->getContentType(); + if (($pos = strpos($contentType, ';')) !== false) { // e.g. text/html; charset=UTF-8 - $contentType = substr($rawContentType, 0, $pos); - } else { - $contentType = $rawContentType; + $contentType = trim(substr($contentType, 0, $pos)); } if (isset($this->parsers[$contentType])) { @@ -544,9 +543,15 @@ class Request extends \yii\base\Request implements RequestInterface } $this->_bodyParams = $parser->parse($this); } elseif ($this->getMethod() === 'POST') { + if ($contentType !== 'application/x-www-form-urlencoded' && $contentType !== 'multipart/form-data') { + throw new UnsupportedMediaTypeHttpException(); + } // PHP has already parsed the body so we have all params in $_POST $this->_bodyParams = $_POST; } else { + if ($contentType !== 'application/x-www-form-urlencoded') { + throw new UnsupportedMediaTypeHttpException(); + } $this->_bodyParams = []; mb_parse_str($this->getRawBody(), $this->_bodyParams); } diff --git a/tests/framework/rest/UrlRuleTest.php b/tests/framework/rest/UrlRuleTest.php index a80cd97..7f99b88 100644 --- a/tests/framework/rest/UrlRuleTest.php +++ b/tests/framework/rest/UrlRuleTest.php @@ -363,7 +363,7 @@ class UrlRuleTest extends TestCase } /** - * @dataProvider testGetCreateUrlStatusProvider + * @dataProvider dataProviderGetCreateUrlStatus * @param array $config * @param array $tests */ @@ -400,7 +400,7 @@ class UrlRuleTest extends TestCase * - second element is the expected URL * - third element is the expected result of getCreateUrlStatus() method */ - public function testGetCreateUrlStatusProvider() + public function dataProviderGetCreateUrlStatus() { return [ 'single controller' => [ diff --git a/tests/framework/web/RequestTest.php b/tests/framework/web/RequestTest.php index 3a6f22f..f3ff43a 100644 --- a/tests/framework/web/RequestTest.php +++ b/tests/framework/web/RequestTest.php @@ -7,7 +7,9 @@ namespace yiiunit\framework\web; +use yii\http\MemoryStream; use yii\web\Request; +use yii\web\UnsupportedMediaTypeHttpException; use yiiunit\TestCase; /** @@ -84,6 +86,7 @@ class RequestTest extends TestCase $this->mockWebApplication(); $request = new Request(); + $request->setHeader('Content-Type', 'application/x-www-form-urlencoded'); $request->enableCsrfCookie = false; $token = $request->getCsrfToken(); @@ -302,4 +305,29 @@ class RequestTest extends TestCase $request = new Request(); $this->assertEquals(null, $request->getOrigin()); } + + public function testGetBodyParams() + { + $body = new MemoryStream(); + $body->write('name=value'); + + $request = new Request(); + $request->setMethod('PUT'); + $request->setBody($body); + $_POST = ['name' => 'post']; + + $this->assertSame(['name' => 'value'], $request->withHeader('Content-Type', 'application/x-www-form-urlencoded')->getBodyParams()); + $this->assertSame(['name' => 'post'], $request->withHeader('Content-Type', 'application/x-www-form-urlencoded')->withMethod('POST')->getBodyParams()); + $this->assertSame(['name' => 'post'], $request->withHeader('Content-Type', 'multipart/form-data')->withMethod('POST')->getBodyParams()); + + try { + $request->getBodyParams(); + } catch (UnsupportedMediaTypeHttpException $noContentTypeException) {} + $this->assertTrue(isset($noContentTypeException)); + + try { + $request->withMethod('POST')->getBodyParams(); + } catch (UnsupportedMediaTypeHttpException $postWithoutContentTypeException) {} + $this->assertTrue(isset($postWithoutContentTypeException)); + } } diff --git a/tests/framework/web/UrlRuleTest.php b/tests/framework/web/UrlRuleTest.php index 6c078b1..dc5ba6b 100644 --- a/tests/framework/web/UrlRuleTest.php +++ b/tests/framework/web/UrlRuleTest.php @@ -1279,7 +1279,7 @@ class UrlRuleTest extends TestCase } /** - * @dataProvider testGetCreateUrlStatusProvider + * @dataProvider dataProviderGetCreateUrlStatus * @param array $config * @param array $tests */ @@ -1316,7 +1316,7 @@ class UrlRuleTest extends TestCase * - third element is the expected URL * - fourth element is the expected result of getCreateUrlStatus() method */ - public function testGetCreateUrlStatusProvider() + public function dataProviderGetCreateUrlStatus() { return [ 'route' => [