Newer
Older
<?php
/*
* Copyright (C) 2015 IRSTEA
* All rights reserved.
*/
namespace Irstea\FileUploadBundle\Controller;
use Irstea\FileUploadBundle\Entity\UploadedFile;
use Irstea\FileUploadBundle\Service\FileManagerInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Routing\RouterInterface;
/**
* @Route("/files", service="irstea_file_upload.upload_controller")
*/
class UploadController extends Controller
{
Guillaume Perréal
committed
const CSRF_INTENTION = "file_upload";
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/**
*
* @var FileManagerInterface
*/
protected $fileManager;
/**
*
* @var RouterInterface
*/
protected $router;
/**
* @var CsrfProviderInterface
*/
protected $csrfProvider;
/**
*
*/
public function __construct(FileManagerInterface $fileManager, RouterInterface $router, CsrfProviderInterface $csrfProvider)
{
$this->fileManager = $fileManager;
$this->router = $router;
$this->csrfProvider = $csrfProvider;
}
/**
* @Route("", name="file_upload_create")
* @Method("POST")
* @param Request $request
*/
public function createAction(Request $request)
{
$data = $request->request->get('file');
$file = $this->fileManager->create(
$data['name'],
$data['size'],
$data['type'],
isset($data['lastModified']) ? $data['lastModified'] : null
);
Guillaume Perréal
committed
$token = $this->csrfProvider->generateCsrfToken(self::CSRF_INTENTION);
$location = $this->router->generate(
'file_upload_put',
[
'id' => $file->getId(),
'token' => $token
],
RouterInterface::ABSOLUTE_URL
);
return $this->createResponse(
Response::HTTP_CREATED,
'New file created',
[ 'id' => $file->getId(), 'url' => $location ],
[ 'Location' => $location ]
);
}
/**
* @Route("/{id}", name="file_upload_put")
* @Method("PUT")
* @param Request $request
* @param UploadedFile $file
*/
public function putAction(Request $request, UploadedFile $file)
{
$this->validateToken($request);
$range = $request->headers->get('Content-Range');
$final = true;
if($range) {
$matches = [];
if(!preg_match('@^bytes (\d+)-(\d+)/(\d+)$@', $range, $matches)) {
throw new BadRequestHttpException("Invalid Content-Range");
}
$start = intval($matches[1]);
$end = intval($matches[2]);
$total = intval($matches[3]);
$stream = $file->open('ab');
$stream->seek($start);
$final = $end === ($total-1);
} else {
$stream = $file->open('wb');
}
Guillaume Perréal
committed
// Demande un filehandle plutôt que charger le contenu en mémoire
$input = $request->getContent(true);
if(false !== $fileHandler = $stream->cast(STREAM_CAST_AS_STREAM)) {
// Utilise stream_copy_to_stream si le Gaufrette\Stream peut nous retourner un filehandle
stream_copy_to_stream($input, $fileHandler);
} else {
// Sinon fait une copie par blocs (moins performant)
while(!feof($input)) {
$stream->write(fread($input, 8192));
}
}
fclose($input);
$stream->close();
if(!$final) {
return $this->createResponse(Response::HTTP_OK, 'Chunk received', ['range' => compact('start', 'end', 'total')]);
}
Guillaume Perréal
committed
$this->fileManager->completed($file);
$parameters = [
'id' => $file->getId(),
'token' => $request->query->get('token')
];
Guillaume Perréal
committed
return $this->createResponse(
Response::HTTP_OK,
'File uploaded',
[
'files' => [
[
'id' => $file->getId(),
'name' => $file->getOriginalFilename(),
'type' => $file->getMimeType(),
'size' => $file->getSize(),
'url' => $this->router->generate('file_upload_get', $parameters),
'delete_type' => 'DELETE',
'delete_url' => $this->router->generate('file_upload_delete', $parameters),
]
]
]
Guillaume Perréal
committed
);
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
/**
* @Route("/{id}", name="file_upload_get")
* @Method("GET")
* @param Request $request
* @param UploadedFile $file
*/
public function getAction(Request $request, UploadedFile $file)
{
$this->validateToken($request);
if(!$file->isValid()) {
throw new NotFoundHttpException();
}
$response = StreamedResponse::create(function() use($file) { echo $file->getContent(); })
->setPrivate()
->setLastModified($file->getLastModified())
->setEtag($file->getChecksum());
$response->headers->add(
[
'Content-Type' => $file->getMimeType(),
'Content-Length' => $file->getSize(),
'Content-Disposition' => $response->headers->makeDisposition(
ResponseHeaderBag::DISPOSITION_ATTACHMENT,
$file->getOriginalFilename()
),
]
);
$response->isNotModified($request);
return $response;
}
/**
* @Route("/{id}", name="file_upload_delete")
* @Method("DELETE")
* @param Request $request
* @param UploadedFile $file
*/
public function deleteAction(Request $request, UploadedFile $file)
{
$this->validateToken($request);
$this->fileManager->delete($file);
return $this->createResponse();
}
/**
*
* @param Request $request
* @throws HttpException
*/
protected function validateToken(Request $request)
{
Guillaume Perréal
committed
if(!$this->csrfProvider->isCsrfTokenValid(self::CSRF_INTENTION, $request->query->get('token', null))) {
throw new HttpException(Response::HTTP_FORBIDDEN, 'Invalid CSRF token');
}
}
/**
*
* @param int $status
* @param string $message
* @param array $data
* @param array $headers
* @return JsonResponse
*/
protected function createResponse($status = Response::HTTP_OK, $message = 'OK', array $data = [], array $headers = [])
{
$data['status'] = $status;
$data['message'] = $message;
return new JsonResponse($data, $status, $headers);
}
}