vendor/friendsofsymfony/rest-bundle/EventListener/BodyListener.php line 57

Open in your IDE?
  1. <?php
  2. /*
  3. * This file is part of the FOSRestBundle package.
  4. *
  5. * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace FOS\RestBundle\EventListener;
  11. use FOS\RestBundle\Decoder\DecoderProviderInterface;
  12. use FOS\RestBundle\FOSRestBundle;
  13. use FOS\RestBundle\Normalizer\ArrayNormalizerInterface;
  14. use FOS\RestBundle\Normalizer\Exception\NormalizationException;
  15. use Symfony\Component\HttpFoundation\InputBag;
  16. use Symfony\Component\HttpFoundation\ParameterBag;
  17. use Symfony\Component\HttpFoundation\Request;
  18. use Symfony\Component\HttpKernel\Event\RequestEvent;
  19. use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
  20. use Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException;
  21. /**
  22. * This listener handles Request body decoding.
  23. *
  24. * @author Lukas Kahwe Smith <smith@pooteeweet.org>
  25. *
  26. * @internal
  27. */
  28. class BodyListener
  29. {
  30. private $decoderProvider;
  31. private $throwExceptionOnUnsupportedContentType;
  32. private $defaultFormat;
  33. private $arrayNormalizer;
  34. private $normalizeForms;
  35. public function __construct(
  36. DecoderProviderInterface $decoderProvider,
  37. bool $throwExceptionOnUnsupportedContentType = false,
  38. ?ArrayNormalizerInterface $arrayNormalizer = null,
  39. bool $normalizeForms = false
  40. ) {
  41. $this->decoderProvider = $decoderProvider;
  42. $this->throwExceptionOnUnsupportedContentType = $throwExceptionOnUnsupportedContentType;
  43. $this->arrayNormalizer = $arrayNormalizer;
  44. $this->normalizeForms = $normalizeForms;
  45. }
  46. public function setDefaultFormat(?string $defaultFormat): void
  47. {
  48. $this->defaultFormat = $defaultFormat;
  49. }
  50. public function onKernelRequest(RequestEvent $event): void
  51. {
  52. $request = $event->getRequest();
  53. if (!$request->attributes->get(FOSRestBundle::ZONE_ATTRIBUTE, true)) {
  54. return;
  55. }
  56. $method = $request->getMethod();
  57. $contentType = $request->headers->get('Content-Type');
  58. $normalizeRequest = $this->normalizeForms && $this->isFormRequest($request);
  59. if ($this->isDecodeable($request)) {
  60. $format = null === $contentType
  61. ? $request->getRequestFormat()
  62. : $request->getFormat($contentType);
  63. $format = $format ?: $this->defaultFormat;
  64. $content = $request->getContent();
  65. if (null === $format || !$this->decoderProvider->supports($format)) {
  66. if ($this->throwExceptionOnUnsupportedContentType
  67. && $this->isNotAnEmptyDeleteRequestWithNoSetContentType($method, $content, $contentType)
  68. ) {
  69. throw new UnsupportedMediaTypeHttpException("Request body format '$format' not supported");
  70. }
  71. return;
  72. }
  73. if (!empty($content)) {
  74. $decoder = $this->decoderProvider->getDecoder($format);
  75. $data = $decoder->decode($content);
  76. if (is_array($data)) {
  77. if (class_exists(InputBag::class)) {
  78. $request->request = new InputBag($data);
  79. } else {
  80. $request->request = new ParameterBag($data);
  81. }
  82. $normalizeRequest = true;
  83. } else {
  84. throw new BadRequestHttpException('Invalid '.$format.' message received');
  85. }
  86. }
  87. }
  88. if (null !== $this->arrayNormalizer && $normalizeRequest) {
  89. $data = $request->request->all();
  90. try {
  91. $data = $this->arrayNormalizer->normalize($data);
  92. } catch (NormalizationException $e) {
  93. throw new BadRequestHttpException($e->getMessage());
  94. }
  95. if (class_exists(InputBag::class)) {
  96. $request->request = new InputBag($data);
  97. } else {
  98. $request->request = new ParameterBag($data);
  99. }
  100. }
  101. }
  102. private function isNotAnEmptyDeleteRequestWithNoSetContentType(string $method, $content, ?string $contentType): bool
  103. {
  104. return false === ('DELETE' === $method && empty($content) && empty($contentType));
  105. }
  106. private function isDecodeable(Request $request): bool
  107. {
  108. if (!in_array($request->getMethod(), ['POST', 'PUT', 'PATCH', 'DELETE'])) {
  109. return false;
  110. }
  111. return !$this->isFormRequest($request);
  112. }
  113. private function isFormRequest(Request $request): bool
  114. {
  115. $contentTypeParts = explode(';', $request->headers->get('Content-Type', ''));
  116. if (isset($contentTypeParts[0])) {
  117. return in_array($contentTypeParts[0], ['multipart/form-data', 'application/x-www-form-urlencoded']);
  118. }
  119. return false;
  120. }
  121. }