Skip to content

Commit

Permalink
Merge pull request #15 from Guite/master
Browse files Browse the repository at this point in the history
split actual service from event listener
  • Loading branch information
goetas authored Oct 11, 2021
2 parents 8018d02 + 011d58a commit 2050dd6
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 131 deletions.
2 changes: 1 addition & 1 deletion src/DependencyInjection/GoetasMultipartUploadExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public function load(array $configs, ContainerBuilder $container)
$loader = new Loader\XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
$loader->load('services.xml');

$container->getDefinition('goetas.multipart_upload.request_listener')
$container->getDefinition('goetas.multipart_upload.request_handler')
->setArguments([$config['first_part_as_default']]);
}
}
137 changes: 7 additions & 130 deletions src/EventListener/MultipartRequestListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,159 +2,36 @@

namespace Goetas\MultipartUploadBundle\EventListener;

use Riverline\MultiPartParser\Converters\HttpFoundation;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\Request;
use Goetas\MultipartUploadBundle\Service\MultipartRequestHandler;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\RequestEvent;

class MultipartRequestListener
{
/**
* @var bool
* @var MultipartRequestHandler
*/
private $injectFirstPart;
private $requestHandler;

public function __construct(bool $injectFirstPart = true)
public function __construct(MultipartRequestHandler $requestHandler)
{
$this->injectFirstPart = $injectFirstPart;
$this->requestHandler = $requestHandler;
}

public function onKernelRequest(RequestEvent $event)
{
try {
$this->processRequest($event->getRequest());
$this->requestHandler->processRequest($event->getRequest());
} catch (\LogicException $e) {
$message = 'Bad Request';

if ($e->getMessage()) {
$message .= ': ' . $e->getMessage();
}

$response = new Response($message, 400);
$response = new Response($message, Response::HTTP_BAD_REQUEST);

$event->setResponse($response);
}
}

private function processRequest(Request $request)
{
$contentType = $request->headers->get('Content-Type');
if (0 !== strpos($contentType, 'multipart/') || false !== strpos($contentType, 'multipart/form-data')) {
return;
}

$streamedPart = HttpFoundation::convert($request);

if ($this->injectFirstPart === true) {
$request->headers->remove('Content-Type');
$request->headers->remove('Content-Length');
}
$attachments = [];
$relatedParts = $streamedPart->getParts();

foreach ($relatedParts as $k => $part) {

if ($this->injectFirstPart === true && 0 === $k) {
$request->headers->add($part->getHeaders());
if ('application/x-www-form-urlencoded' === $part->getHeader('Content-Type')) {
$output = [];
parse_str($part->getBody(), $output);
$request->request->add($output);
} else {
$this->setRequestContent($request, $part->getBody());
}
continue;
}

$contentDisposition = $part->getHeader('Content-Disposition');

if ($contentDisposition === null) {
continue;
}

$fileName = $part->getFileName();

if ($fileName!== null) {
$fp = tmpfile();
fwrite($fp, $part->getBody());
rewind($fp);

$tmpPath = stream_get_meta_data($fp)['uri'];

$ref = new \ReflectionClass('Symfony\Component\HttpFoundation\File\UploadedFile');
$params = $ref->getConstructor()->getParameters();
if ('error' === $params[3]->getName()) { // symfony 4
$file = new UploadedFile($tmpPath, urldecode($fileName), $part->getMimeType(), null, true);
} else { // symfony < 4
$file = new UploadedFile($tmpPath, urldecode($fileName), $part->getMimeType(), filesize($tmpPath), null, true);
}
@$file->ref = $fp;
$attachments[] = $file;
}

if (($formName = $this->isDispositionFormData($contentDisposition)) !== null) {
$formPath = $this->parseKey($formName);

if ($fileName !== null) {
$files = $request->files->all();
$files = $this->mergeFormArray($files, $formPath, $file);
$request->files->replace($files);
} else {
$data = $request->request->all();
$data = $this->mergeFormArray($data, $formPath, $part->getBody());
$request->request->replace($data);
}
}
}

$request->attributes->set('attachments', $attachments);
$request->attributes->set('related-parts', $relatedParts);
}

protected function mergeFormArray($array, $path, $data)
{
if (count($path) > 0) {
$key = array_shift($path);

if (!is_array($array)) {
$array = [];
}

if (!empty($key)) {
$array[$key] = $this->mergeFormArray($array[$key] ?? [], $path, $data);
} else {
$array[] = $data;
}

return $array;
}

return $data;
}

private function parseKey($key)
{
return array_map(
function ($v) {
return trim($v, ']');
},
explode('[', $key)
);
}

private function isDispositionFormData($value)
{
if (preg_match('/(?:^|form-data;\s*)name="?([^";]+)("|;|$)/', $value, $matches)) {
return trim($matches[1]);
}
return null;
}

private function setRequestContent(Request $request, $content)
{
$p = new \ReflectionProperty(Request::class, 'content');
$p->setAccessible(true);
$p->setValue($request, $content);
}
}
3 changes: 3 additions & 0 deletions src/Resources/config/services.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="goetas.multipart_upload.request_handler" class="Goetas\MultipartUploadBundle\Service\MultipartRequestHandler">
</service>
<service id="goetas.multipart_upload.request_listener" class="Goetas\MultipartUploadBundle\EventListener\MultipartRequestListener">
<argument type="service" key="$requestHandler" id="goetas.multipart_upload.request_handler"/>
<tag name="kernel.event_listener" event="kernel.request" method="onKernelRequest" priority="200"/>
</service>
</services>
Expand Down
140 changes: 140 additions & 0 deletions src/Service/MultipartRequestHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
<?php

namespace Goetas\MultipartUploadBundle\Service;

use Riverline\MultiPartParser\Converters\HttpFoundation;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\Request;

class MultipartRequestHandler
{
/**
* @var bool
*/
private $injectFirstPart;

public function __construct(bool $injectFirstPart = true)
{
$this->injectFirstPart = $injectFirstPart;
}

public function processRequest(Request $request)
{
$contentType = $request->headers->get('Content-Type');
if (0 !== strpos($contentType, 'multipart/') || false !== strpos($contentType, 'multipart/form-data')) {
return;
}

$streamedPart = HttpFoundation::convert($request);

if ($this->injectFirstPart === true) {
$request->headers->remove('Content-Type');
$request->headers->remove('Content-Length');
}
$attachments = [];
$relatedParts = $streamedPart->getParts();

foreach ($relatedParts as $k => $part) {
if ($this->injectFirstPart === true && 0 === $k) {
$request->headers->add($part->getHeaders());
if ('application/x-www-form-urlencoded' === $part->getHeader('Content-Type')) {
$output = [];
parse_str($part->getBody(), $output);
$request->request->add($output);
} else {
$this->setRequestContent($request, $part->getBody());
}
continue;
}

$contentDisposition = $part->getHeader('Content-Disposition');

if ($contentDisposition === null) {
continue;
}

$fileName = $part->getFileName();

if ($fileName!== null) {
$fp = tmpfile();
fwrite($fp, $part->getBody());
rewind($fp);

$tmpPath = stream_get_meta_data($fp)['uri'];

$ref = new \ReflectionClass('Symfony\Component\HttpFoundation\File\UploadedFile');
$params = $ref->getConstructor()->getParameters();
if ('error' === $params[3]->getName()) { // symfony 4
$file = new UploadedFile($tmpPath, urldecode($fileName), $part->getMimeType(), null, true);
} else { // symfony < 4
$file = new UploadedFile($tmpPath, urldecode($fileName), $part->getMimeType(), filesize($tmpPath), null, true);
}
@$file->ref = $fp;
$attachments[] = $file;
}

if (($formName = $this->isDispositionFormData($contentDisposition)) !== null) {
$formPath = $this->parseKey($formName);

if ($fileName !== null) {
$files = $request->files->all();
$files = $this->mergeFormArray($files, $formPath, $file);
$request->files->replace($files);
} else {
$data = $request->request->all();
$data = $this->mergeFormArray($data, $formPath, $part->getBody());
$request->request->replace($data);
}
}
}

$request->attributes->set('attachments', $attachments);
$request->attributes->set('related-parts', $relatedParts);
}

private function mergeFormArray($array, $path, $data)
{
if (count($path) > 0) {
$key = array_shift($path);

if (!is_array($array)) {
$array = [];
}

if (!empty($key)) {
$array[$key] = $this->mergeFormArray($array[$key] ?? [], $path, $data);
} else {
$array[] = $data;
}

return $array;
}

return $data;
}

private function parseKey($key)
{
return array_map(
function ($v) {
return trim($v, ']');
},
explode('[', $key)
);
}

private function isDispositionFormData($value)
{
if (preg_match('/(?:^|form-data;\s*)name="?([^";]+)("|;|$)/', $value, $matches)) {
return trim($matches[1]);
}
return null;
}

private function setRequestContent(Request $request, $content)
{
$p = new \ReflectionProperty(Request::class, 'content');
$p->setAccessible(true);
$p->setValue($request, $content);
}
}

0 comments on commit 2050dd6

Please sign in to comment.