<?php
namespace App\EventSubscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
class MultipartRequestSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
KernelEvents::REQUEST => [
['onKernelRequest', 100],
],
];
}
public function onKernelRequest(RequestEvent $event): void
{
if (!$event->isMainRequest()) {
return;
}
$request = $event->getRequest();
if (!$this->shouldParse($request)) {
return;
}
$this->parseMultipartRequest($request);
}
private function shouldParse(Request $request): bool
{
if (!$request->isMethod('PUT') && !$request->isMethod('PATCH')) {
return false;
}
return str_starts_with(
strtolower($request->headers->get('Content-Type', '')),
'multipart/form-data'
);
}
private function parseMultipartRequest(Request $request): void
{
$contentType = $request->headers->get('Content-Type', '');
$content = $request->getContent();
if ($content === '') {
return;
}
$boundary = $this->getBoundary($contentType);
if ($boundary === null) {
return;
}
$parts = $this->splitMultipartBody($content, $boundary);
$fields = [];
$files = [];
foreach ($parts as $part) {
$parsedPart = $this->parsePart($part);
if ($parsedPart === null) {
continue;
}
$name = $parsedPart['name'];
if ($parsedPart['filename'] !== null) {
$file = $this->createUploadedFile($parsedPart);
if ($file !== null) {
$files[$name] = $file;
}
continue;
}
/*
* IMPORTANTE:
*
* Se utiliza EXACTAMENTE el nombre recibido.
*
* relojRef1 -> relojRef1
* reloj_ref1 -> reloj_ref1
* cualquier_nombre -> cualquier_nombre
*/
$fields[$name] = $parsedPart['content'];
}
if ($fields) {
$request->request->add($fields);
}
if ($files) {
$request->files->add($files);
}
}
private function getBoundary(string $contentType): ?string
{
if (!preg_match(
'/boundary="?([^";]+)"?/i',
$contentType,
$matches
)) {
return null;
}
return $matches[1];
}
private function splitMultipartBody(
string $content,
string $boundary
): array {
$delimiter = '--' . $boundary;
$parts = explode($delimiter, $content);
$result = [];
foreach ($parts as $part) {
$part = ltrim($part, "\r\n");
if ($part === '' || $part === '--') {
continue;
}
if (str_ends_with($part, '--')) {
$part = substr($part, 0, -2);
}
$part = rtrim($part, "\r\n");
if ($part !== '') {
$result[] = $part;
}
}
return $result;
}
private function parsePart(string $part): ?array
{
$separatorPosition = strpos($part, "\r\n\r\n");
if ($separatorPosition === false) {
$separatorPosition = strpos($part, "\n\n");
if ($separatorPosition === false) {
return null;
}
$separatorLength = 2;
} else {
$separatorLength = 4;
}
$headers = substr($part, 0, $separatorPosition);
$body = substr(
$part,
$separatorPosition + $separatorLength
);
if (!preg_match(
'/Content-Disposition:\s*form-data;\s*([^\r\n]+)/i',
$headers,
$dispositionMatch
)) {
return null;
}
$disposition = $dispositionMatch[1];
if (!preg_match(
'/name="([^"]*)"/i',
$disposition,
$nameMatch
)) {
return null;
}
$name = $nameMatch[1];
$filename = null;
if (preg_match(
'/filename="([^"]*)"/i',
$disposition,
$filenameMatch
)) {
$filename = $filenameMatch[1];
}
$contentType = null;
if (preg_match(
'/Content-Type:\s*([^\r\n]+)/i',
$headers,
$contentTypeMatch
)) {
$contentType = trim($contentTypeMatch[1]);
}
return [
'name' => $name,
'filename' => $filename,
'content_type' => $contentType,
'content' => $body,
];
}
private function createUploadedFile(array $part): ?UploadedFile
{
if ($part['filename'] === '') {
return null;
}
$tmpFile = tempnam(
sys_get_temp_dir(),
'multipart_'
);
if ($tmpFile === false) {
return null;
}
file_put_contents(
$tmpFile,
$part['content']
);
return new UploadedFile(
$tmpFile,
basename($part['filename']),
$part['content_type'],
UPLOAD_ERR_OK,
true
);
}
}