From 35c3ff2fbd16fbf5660550c19cdd62beb8e5096f Mon Sep 17 00:00:00 2001 From: Pavel Ivanov Date: Fri, 30 Mar 2018 17:16:52 +0200 Subject: [PATCH] Fixes #16010: Fixed `yii\filters\ContentNegotiator` behavior when GET parameters contain an array --- framework/CHANGELOG.md | 1 + framework/filters/ContentNegotiator.php | 12 +++- tests/framework/filters/ContentNegotiatorTest.php | 79 +++++++++++++++++++++++ 3 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 tests/framework/filters/ContentNegotiatorTest.php diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 796c963..b255abc 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -4,6 +4,7 @@ Yii Framework 2 Change Log 2.0.16 under development ------------------------ +- Bug #16010: Fixed `yii\filters\ContentNegotiator` behavior when GET parameters contain an array (rugabarbo) - Bug #14660: Fixed `yii\caching\DbCache` concurrency issue when set values with the same key (rugabarbo) - Bug #15988: Fixed bash completion (alekciy) diff --git a/framework/filters/ContentNegotiator.php b/framework/filters/ContentNegotiator.php index 9aea7c5..a84b728 100644 --- a/framework/filters/ContentNegotiator.php +++ b/framework/filters/ContentNegotiator.php @@ -10,7 +10,7 @@ namespace yii\filters; use Yii; use yii\base\ActionFilter; use yii\base\BootstrapInterface; -use yii\base\InvalidConfigException; +use yii\web\BadRequestHttpException; use yii\web\Request; use yii\web\Response; use yii\web\UnsupportedMediaTypeHttpException; @@ -165,12 +165,16 @@ class ContentNegotiator extends ActionFilter implements BootstrapInterface * Negotiates the response format. * @param Request $request * @param Response $response - * @throws InvalidConfigException if [[formats]] is empty + * @throws BadRequestHttpException if an array received for GET parameter [[formatParam]]. * @throws UnsupportedMediaTypeHttpException if none of the requested content types is accepted. */ protected function negotiateContentType($request, $response) { if (!empty($this->formatParam) && ($format = $request->get($this->formatParam)) !== null) { + if (is_array($format)) { + throw new BadRequestHttpException("Invalid data received for GET parameter '{$this->formatParam}'."); + } + if (in_array($format, $this->formats)) { $response->format = $format; $response->acceptMimeType = null; @@ -217,6 +221,10 @@ class ContentNegotiator extends ActionFilter implements BootstrapInterface protected function negotiateLanguage($request) { if (!empty($this->languageParam) && ($language = $request->get($this->languageParam)) !== null) { + if (is_array($language)) { + // If an array received, then skip it and use the first of supported languages + return reset($this->languages); + } if (isset($this->languages[$language])) { return $this->languages[$language]; } diff --git a/tests/framework/filters/ContentNegotiatorTest.php b/tests/framework/filters/ContentNegotiatorTest.php new file mode 100644 index 0000000..fae753b --- /dev/null +++ b/tests/framework/filters/ContentNegotiatorTest.php @@ -0,0 +1,79 @@ +mockWebApplication(); + } + + protected function mockActionAndFilter() + { + $action = new Action('test', new Controller('id', Yii::$app)); + $filter = new ContentNegotiator([ + 'request' => new Request(), + 'response' => new Response(), + ]); + + return [$action, $filter]; + } + + public function testWhenLanguageGETParamIsArray() + { + list($action, $filter) = $this->mockActionAndFilter(); + + $_SERVER['REQUEST_METHOD'] = 'GET'; + $_GET[$filter->languageParam] = [ + 'foo', + 'string-index' => 'bar', + ]; + + $targetLanguage = 'de'; + $filter->languages = [$targetLanguage, 'ru', 'en']; + + $filter->beforeAction($action); + $this->assertEquals($targetLanguage, Yii::$app->language); + } + + /** + * @expectedException yii\web\BadRequestHttpException + * @expectedExceptionMessageRegExp |Invalid data received for GET parameter '.+'| + */ + public function testWhenFormatGETParamIsArray() + { + list($action, $filter) = $this->mockActionAndFilter(); + + $_SERVER['REQUEST_METHOD'] = 'GET'; + $_GET[$filter->formatParam] = [ + 'format-A', + 'string-index' => 'format-B', + ]; + + $filter->formats = [ + 'application/json' => Response::FORMAT_JSON, + 'application/xml' => Response::FORMAT_XML, + ]; + + $filter->beforeAction($action); + } +}