[php-symfony] Fix problem with clients, that put charset in content type header. (#6078)

* Fix problem with clients, that put charset in content type header.

With this fix header "Content-Type: application/json; charset=utf-8" working same as "Content-Type: application/json" for parse input data

* Fix code style, add $consumes length check.

* Add isContentTypeAllowed static method and tests

* Fix old tests

Right now serializer doesn't support anything beside json and xml.
Call tests with application/json instead of form data.

Co-authored-by: Yuriy Belenko <yura-bely@mail.ru>
This commit is contained in:
Artem
2020-04-30 11:22:44 +02:00
committed by GitHub
parent 8e4452e383
commit 3e2c933dec
15 changed files with 344 additions and 24 deletions

View File

@@ -30,6 +30,7 @@
namespace OpenAPI\Server\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\HttpException;
use OpenAPI\Server\Service\SerializerInterface;
@@ -186,4 +187,40 @@ class Controller extends AbstractController
// If we reach this point, we don't have a common ground between server and client
return null;
}
/**
* Checks whether Content-Type request header presented in supported formats.
*
* @param Request $request Request instance.
* @param array $consumes Array of supported content types.
*
* @return bool Returns true if Content-Type supported otherwise false.
*/
public static function isContentTypeAllowed(Request $request, array $consumes = [])
{
if (!empty($consumes) && $consumes[0] !== '*/*') {
$currentFormat = $request->getContentType();
foreach ($consumes as $mimeType) {
// canonize mime type
if (is_string($mimeType) && false !== $pos = strpos($mimeType, ';')) {
$mimeType = trim(substr($mimeType, 0, $pos));
}
if (!$format = $request->getFormat($mimeType)) {
// add custom format to request
$format = $mimeType;
$request->setFormat($format, $format);
$currentFormat = $request->getContentType();
}
if ($format === $currentFormat) {
return true;
}
}
return false;
}
return true;
}
}

View File

@@ -62,8 +62,7 @@ class PetController extends Controller
{
// Make sure that the client is providing something that we can consume
$consumes = ['application/json', 'application/xml'];
$inputFormat = $request->headers->has('Content-Type')?$request->headers->get('Content-Type'):$consumes[0];
if (!in_array($inputFormat, $consumes)) {
if (!static::isContentTypeAllowed($request, $consumes)) {
// We can't consume the content that the client is sending us
return new Response('', 415);
}
@@ -80,6 +79,7 @@ class PetController extends Controller
// Deserialize the input values that needs it
try {
$inputFormat = $request->getMimeType($request->getContentType());
$body = $this->deserialize($body, 'OpenAPI\Server\Model\Pet', $inputFormat);
} catch (SerializerRuntimeException $exception) {
return $this->createBadRequestResponse($exception->getMessage());
@@ -491,8 +491,7 @@ class PetController extends Controller
{
// Make sure that the client is providing something that we can consume
$consumes = ['application/json', 'application/xml'];
$inputFormat = $request->headers->has('Content-Type')?$request->headers->get('Content-Type'):$consumes[0];
if (!in_array($inputFormat, $consumes)) {
if (!static::isContentTypeAllowed($request, $consumes)) {
// We can't consume the content that the client is sending us
return new Response('', 415);
}
@@ -509,6 +508,7 @@ class PetController extends Controller
// Deserialize the input values that needs it
try {
$inputFormat = $request->getMimeType($request->getContentType());
$body = $this->deserialize($body, 'OpenAPI\Server\Model\Pet', $inputFormat);
} catch (SerializerRuntimeException $exception) {
return $this->createBadRequestResponse($exception->getMessage());

View File

@@ -284,8 +284,7 @@ class StoreController extends Controller
{
// Make sure that the client is providing something that we can consume
$consumes = [];
$inputFormat = $request->headers->has('Content-Type')?$request->headers->get('Content-Type'):$consumes[0];
if (!in_array($inputFormat, $consumes)) {
if (!static::isContentTypeAllowed($request, $consumes)) {
// We can't consume the content that the client is sending us
return new Response('', 415);
}
@@ -308,6 +307,7 @@ class StoreController extends Controller
// Deserialize the input values that needs it
try {
$inputFormat = $request->getMimeType($request->getContentType());
$body = $this->deserialize($body, 'OpenAPI\Server\Model\Order', $inputFormat);
} catch (SerializerRuntimeException $exception) {
return $this->createBadRequestResponse($exception->getMessage());

View File

@@ -61,8 +61,7 @@ class UserController extends Controller
{
// Make sure that the client is providing something that we can consume
$consumes = [];
$inputFormat = $request->headers->has('Content-Type')?$request->headers->get('Content-Type'):$consumes[0];
if (!in_array($inputFormat, $consumes)) {
if (!static::isContentTypeAllowed($request, $consumes)) {
// We can't consume the content that the client is sending us
return new Response('', 415);
}
@@ -76,6 +75,7 @@ class UserController extends Controller
// Deserialize the input values that needs it
try {
$inputFormat = $request->getMimeType($request->getContentType());
$body = $this->deserialize($body, 'OpenAPI\Server\Model\User', $inputFormat);
} catch (SerializerRuntimeException $exception) {
return $this->createBadRequestResponse($exception->getMessage());
@@ -138,8 +138,7 @@ class UserController extends Controller
{
// Make sure that the client is providing something that we can consume
$consumes = [];
$inputFormat = $request->headers->has('Content-Type')?$request->headers->get('Content-Type'):$consumes[0];
if (!in_array($inputFormat, $consumes)) {
if (!static::isContentTypeAllowed($request, $consumes)) {
// We can't consume the content that the client is sending us
return new Response('', 415);
}
@@ -153,6 +152,7 @@ class UserController extends Controller
// Deserialize the input values that needs it
try {
$inputFormat = $request->getMimeType($request->getContentType());
$body = $this->deserialize($body, 'array<OpenAPI\Server\Model\User>', $inputFormat);
} catch (SerializerRuntimeException $exception) {
return $this->createBadRequestResponse($exception->getMessage());
@@ -217,8 +217,7 @@ class UserController extends Controller
{
// Make sure that the client is providing something that we can consume
$consumes = [];
$inputFormat = $request->headers->has('Content-Type')?$request->headers->get('Content-Type'):$consumes[0];
if (!in_array($inputFormat, $consumes)) {
if (!static::isContentTypeAllowed($request, $consumes)) {
// We can't consume the content that the client is sending us
return new Response('', 415);
}
@@ -232,6 +231,7 @@ class UserController extends Controller
// Deserialize the input values that needs it
try {
$inputFormat = $request->getMimeType($request->getContentType());
$body = $this->deserialize($body, 'array<OpenAPI\Server\Model\User>', $inputFormat);
} catch (SerializerRuntimeException $exception) {
return $this->createBadRequestResponse($exception->getMessage());
@@ -592,8 +592,7 @@ class UserController extends Controller
{
// Make sure that the client is providing something that we can consume
$consumes = [];
$inputFormat = $request->headers->has('Content-Type')?$request->headers->get('Content-Type'):$consumes[0];
if (!in_array($inputFormat, $consumes)) {
if (!static::isContentTypeAllowed($request, $consumes)) {
// We can't consume the content that the client is sending us
return new Response('', 415);
}
@@ -608,6 +607,7 @@ class UserController extends Controller
// Deserialize the input values that needs it
try {
$username = $this->deserialize($username, 'string', 'string');
$inputFormat = $request->getMimeType($request->getContentType());
$body = $this->deserialize($body, 'OpenAPI\Server\Model\User', $inputFormat);
} catch (SerializerRuntimeException $exception) {
return $this->createBadRequestResponse($exception->getMessage());

View File

@@ -85,7 +85,7 @@ class PetApiInterfaceTest extends WebTestCase
$path = '/pet';
$crawler = $client->request('POST', $path);
$crawler = $client->request('POST', $path, [], [], ['CONTENT_TYPE' => 'application/json']);
}
/**
@@ -166,7 +166,7 @@ class PetApiInterfaceTest extends WebTestCase
$path = '/pet';
$crawler = $client->request('PUT', $path);
$crawler = $client->request('PUT', $path, [], [], ['CONTENT_TYPE' => 'application/json']);
}
/**

View File

@@ -136,7 +136,7 @@ class StoreApiInterfaceTest extends WebTestCase
$path = '/store/order';
$crawler = $client->request('POST', $path);
$crawler = $client->request('POST', $path, [], [], ['CONTENT_TYPE' => 'application/json']);
}
protected function genTestData($regexp)

View File

@@ -85,7 +85,7 @@ class UserApiInterfaceTest extends WebTestCase
$path = '/user';
$crawler = $client->request('POST', $path);
$crawler = $client->request('POST', $path, [], [], ['CONTENT_TYPE' => 'application/json']);
}
/**
@@ -100,7 +100,7 @@ class UserApiInterfaceTest extends WebTestCase
$path = '/user/createWithArray';
$crawler = $client->request('POST', $path);
$crawler = $client->request('POST', $path, [], [], ['CONTENT_TYPE' => 'application/json']);
}
/**
@@ -115,7 +115,7 @@ class UserApiInterfaceTest extends WebTestCase
$path = '/user/createWithList';
$crawler = $client->request('POST', $path);
$crawler = $client->request('POST', $path, [], [], ['CONTENT_TYPE' => 'application/json']);
}
/**
@@ -199,7 +199,7 @@ class UserApiInterfaceTest extends WebTestCase
$data = $this->genTestData('[a-z0-9]+');
$path = str_replace($pattern, $data, $path);
$crawler = $client->request('PUT', $path);
$crawler = $client->request('PUT', $path, [], [], ['CONTENT_TYPE' => 'application/json']);
}
protected function genTestData($regexp)

View File

@@ -0,0 +1,123 @@
<?php
/**
* ControllerTest
* PHP version 5
*
* @category Class
* @package OpenAPI\Server\Tests\Controller
* @author openapi-generator contributors
* @link https://github.com/openapitools/openapi-generator
*/
/**
* OpenAPI Petstore
*
* This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters.
*
* The version of the OpenAPI document: 1.0.0
*
* Generated by: https://github.com/openapitools/openapi-generator.git
*
*/
/**
* NOTE: This class is auto generated by the openapi generator program.
* https://github.com/openapitools/openapi-generator
* Please update the test case below to test the endpoint.
*/
namespace OpenAPI\Server\Tests\Controller;
use OpenAPI\Server\Controller\Controller;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpFoundation\Request;
/**
* ControllerTest Class Doc Comment
*
* @category Class
* @package OpenAPI\Server\Tests\Controller
* @author openapi-generator contributors
* @link https://github.com/openapitools/openapi-generator
* @coversDefaultClass \OpenAPI\Server\Controller\Controller
*/
class ControllerTest extends TestCase
{
/**
* Tests isContentTypeAllowed static method.
*
* @param string $contentType
* @param array $consumes
* @param bool $expectedReturn
*
* @covers ::isContentTypeAllowed
* @dataProvider provideArgumentsForIsContentTypeAllowed
*/
public function testIsContentTypeAllowed($contentType, array $consumes, $expectedReturn)
{
$request = new Request();
$request->headers->set('CONTENT_TYPE', $contentType, true);// last one argument overrides header
$this->assertSame(
$expectedReturn,
Controller::isContentTypeAllowed($request, $consumes),
sprintf(
'Failed assertion that "Content-Type: %s" %s by [%s] consumes array.',
$contentType,
($expectedReturn) ? 'is allowed' : 'is forbidden',
implode(', ', $consumes)
)
);
}
public function provideArgumentsForIsContentTypeAllowed()
{
return [
'usual JSON content type' => [
'application/json',
['application/json'],
true,
],
'extended content type from PR #6078' => [
'application/json; charset=utf-8',
['application/json'],
true,
],
'more than one content types' => [
'application/json',
['application/xml', 'application/json; charset=utf-8'],
true,
],
'empty consumes array' => [
'application/json',
[],
true,
],
'empty consumes and content type' => [
null,
[],
true,
],
'consumes everything' => [
'application/json',
['*/*'],
true,
],
'fancy custom content type' => [
'foobar/foobaz',
['application/xml', 'foobar/foobaz; charset=utf-8'],
true,
],
'empty content type' => [
null,
['application/xml', 'application/json; charset=utf-8'],
false,
],
'content type out of consumes' => [
'text/html',
['application/xml', 'application/json; charset=utf-8'],
false,
],
];
}
}

View File

@@ -9,12 +9,14 @@
<testsuite>
<directory>./Tests/Api</directory>
<directory>./Tests/Model</directory>
<directory>./Tests/Controller</directory>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">././Api</directory>
<directory suffix=".php">././Model</directory>
<directory suffix=".php">././Controller</directory>
</whitelist>
</filter>
<php>