src/EventSubscriber/MultipartRequestSubscriber.php line 22

Open in your IDE?
  1. <?php
  2.     namespace App\EventSubscriber;
  3.     use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  4.     use Symfony\Component\HttpFoundation\File\UploadedFile;
  5.     use Symfony\Component\HttpFoundation\Request;
  6.     use Symfony\Component\HttpKernel\Event\RequestEvent;
  7.     use Symfony\Component\HttpKernel\KernelEvents;
  8.     class MultipartRequestSubscriber implements EventSubscriberInterface
  9.     {
  10.         public static function getSubscribedEvents(): array
  11.         {
  12.             return [
  13.                 KernelEvents::REQUEST => [
  14.                     ['onKernelRequest'100],
  15.                 ],
  16.             ];
  17.         }
  18.         public function onKernelRequest(RequestEvent $event): void
  19.         {
  20.             if (!$event->isMainRequest()) {
  21.                 return;
  22.             }
  23.             $request $event->getRequest();
  24.             if (!$this->shouldParse($request)) {
  25.                 return;
  26.             }
  27.             $this->parseMultipartRequest($request);
  28.         }
  29.         private function shouldParse(Request $request): bool
  30.         {
  31.             if (!$request->isMethod('PUT') && !$request->isMethod('PATCH')) {
  32.                 return false;
  33.             }
  34.             return str_starts_with(
  35.                 strtolower($request->headers->get('Content-Type''')),
  36.                 'multipart/form-data'
  37.             );
  38.         }
  39.         private function parseMultipartRequest(Request $request): void
  40.         {
  41.             $contentType $request->headers->get('Content-Type''');
  42.             $content $request->getContent();
  43.             if ($content === '') {
  44.                 return;
  45.             }
  46.             $boundary $this->getBoundary($contentType);
  47.             if ($boundary === null) {
  48.                 return;
  49.             }
  50.             $parts $this->splitMultipartBody($content$boundary);
  51.             $fields = [];
  52.             $files = [];
  53.             foreach ($parts as $part) {
  54.                 $parsedPart $this->parsePart($part);
  55.                 if ($parsedPart === null) {
  56.                     continue;
  57.                 }
  58.                 $name $parsedPart['name'];
  59.                 if ($parsedPart['filename'] !== null) {
  60.                     $file $this->createUploadedFile($parsedPart);
  61.                     if ($file !== null) {
  62.                         $files[$name] = $file;
  63.                     }
  64.                     continue;
  65.                 }
  66.                 /*
  67.                  * IMPORTANTE:
  68.                  *
  69.                  * Se utiliza EXACTAMENTE el nombre recibido.
  70.                  *
  71.                  * relojRef1 -> relojRef1
  72.                  * reloj_ref1 -> reloj_ref1
  73.                  * cualquier_nombre -> cualquier_nombre
  74.                  */
  75.                 $fields[$name] = $parsedPart['content'];
  76.             }
  77.             if ($fields) {
  78.                 $request->request->add($fields);
  79.             }
  80.             if ($files) {
  81.                 $request->files->add($files);
  82.             }
  83.         }
  84.         private function getBoundary(string $contentType): ?string
  85.         {
  86.             if (!preg_match(
  87.                 '/boundary="?([^";]+)"?/i',
  88.                 $contentType,
  89.                 $matches
  90.             )) {
  91.                 return null;
  92.             }
  93.             return $matches[1];
  94.         }
  95.         private function splitMultipartBody(
  96.             string $content,
  97.             string $boundary
  98.         ): array {
  99.             $delimiter '--' $boundary;
  100.             $parts explode($delimiter$content);
  101.             $result = [];
  102.             foreach ($parts as $part) {
  103.                 $part ltrim($part"\r\n");
  104.                 if ($part === '' || $part === '--') {
  105.                     continue;
  106.                 }
  107.                 if (str_ends_with($part'--')) {
  108.                     $part substr($part0, -2);
  109.                 }
  110.                 $part rtrim($part"\r\n");
  111.                 if ($part !== '') {
  112.                     $result[] = $part;
  113.                 }
  114.             }
  115.             return $result;
  116.         }
  117.         private function parsePart(string $part): ?array
  118.         {
  119.             $separatorPosition strpos($part"\r\n\r\n");
  120.             if ($separatorPosition === false) {
  121.                 $separatorPosition strpos($part"\n\n");
  122.                 if ($separatorPosition === false) {
  123.                     return null;
  124.                 }
  125.                 $separatorLength 2;
  126.             } else {
  127.                 $separatorLength 4;
  128.             }
  129.             $headers substr($part0$separatorPosition);
  130.             $body substr(
  131.                 $part,
  132.                 $separatorPosition $separatorLength
  133.             );
  134.             if (!preg_match(
  135.                 '/Content-Disposition:\s*form-data;\s*([^\r\n]+)/i',
  136.                 $headers,
  137.                 $dispositionMatch
  138.             )) {
  139.                 return null;
  140.             }
  141.             $disposition $dispositionMatch[1];
  142.             if (!preg_match(
  143.                 '/name="([^"]*)"/i',
  144.                 $disposition,
  145.                 $nameMatch
  146.             )) {
  147.                 return null;
  148.             }
  149.             $name $nameMatch[1];
  150.             $filename null;
  151.             if (preg_match(
  152.                 '/filename="([^"]*)"/i',
  153.                 $disposition,
  154.                 $filenameMatch
  155.             )) {
  156.                 $filename $filenameMatch[1];
  157.             }
  158.             $contentType null;
  159.             if (preg_match(
  160.                 '/Content-Type:\s*([^\r\n]+)/i',
  161.                 $headers,
  162.                 $contentTypeMatch
  163.             )) {
  164.                 $contentType trim($contentTypeMatch[1]);
  165.             }
  166.             return [
  167.                 'name' => $name,
  168.                 'filename' => $filename,
  169.                 'content_type' => $contentType,
  170.                 'content' => $body,
  171.             ];
  172.         }
  173.         private function createUploadedFile(array $part): ?UploadedFile
  174.         {
  175.             if ($part['filename'] === '') {
  176.                 return null;
  177.             }
  178.             $tmpFile tempnam(
  179.                 sys_get_temp_dir(),
  180.                 'multipart_'
  181.             );
  182.             if ($tmpFile === false) {
  183.                 return null;
  184.             }
  185.             file_put_contents(
  186.                 $tmpFile,
  187.                 $part['content']
  188.             );
  189.             return new UploadedFile(
  190.                 $tmpFile,
  191.                 basename($part['filename']),
  192.                 $part['content_type'],
  193.                 UPLOAD_ERR_OK,
  194.                 true
  195.             );
  196.         }
  197.     }