Newer
Older
<?php
/*
* Copyright (C) 2015 IRSTEA
* All rights reserved.
*/
namespace Irstea\FileUploadBundle\Controller;
use Irstea\FileUploadBundle\Entity\UploadedFile;
Guillaume Perréal
committed
use Irstea\FileUploadBundle\Http\UploadedFileResponse;
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\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
{
const CSRF_READ_INTENTION = "uploaded_file_read";
const CSRF_WRITE_INTENTION = "uploaded_file_write_%s";
Guillaume Perréal
committed
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
69
/**
*
* @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
);
$token = $this->generateCsrfToken(self::CSRF_WRITE_INTENTION, $file);
Guillaume Perréal
committed
$parameters = [
'id' => $file->getId(),
'token' => $token
];
$deleteUrl = $this->router->generate('file_upload_delete', $parameters);
return $this->createResponse(
Response::HTTP_CREATED,
'New file created',
Guillaume Perréal
committed
[
'id' => $file->getId(),
'put_url' => $this->router->generate('file_upload_put_content', $parameters),
'delete_type' => 'DELETE',
Guillaume Perréal
committed
],
// On a pas de get pour l'instant, le DELETE et ce qui y ressemble le plus
);
}
/**
Guillaume Perréal
committed
* @Route("/{id}/content", name="file_upload_put_content")
* @Method("PUT")
* @param Request $request
* @param UploadedFile $file
*/
Guillaume Perréal
committed
public function putContentAction(Request $request, UploadedFile $file)
$this->validateCsrfToken($request, self::CSRF_WRITE_INTENTION, $file);
Guillaume Perréal
committed
if(null !== $range = $request->headers->get('Content-Range', null)) {
$matches = [];
if(!preg_match('@^bytes (\d+)-(\d+)/(\d+)$@', $range, $matches)) {
throw new BadRequestHttpException("Invalid Content-Range");
}
Guillaume Perréal
committed
$start = intval($matches[1]);
$end = intval($matches[2]);
$total = intval($matches[3]);
Guillaume Perréal
committed
if($start < 0 || $start >= $end || $end >= $total) {
throw new HttpException(Response::HTTP_REQUESTED_RANGE_NOT_SATISFIABLE);
}
Guillaume Perréal
committed
$offset = $start;
$maxlen = 1 + ($end - $start);
$complete = $end === ($total-1);
} else {
Guillaume Perréal
committed
$offset = 0;
$maxlen = PHP_INT_MAX;
Guillaume Perréal
committed
$complete = true;
}
Guillaume Perréal
committed
// Demande un filehandle plutôt que charger le contenu en mémoire
$input = $request->getContent(true);
$file->copyFrom($input, $maxlen, $offset);
Guillaume Perréal
committed
fclose($input);
Guillaume Perréal
committed
if(!$complete) {
return $this->createResponse(Response::HTTP_OK, 'Chunk received');
}
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->getDisplayName(),
'type' => $file->getMimeType(),
'size' => $file->getSize(),
'icon' => MimeTypeIcon::getMimeTypeIcon($file->getMimeType()),
Guillaume Perréal
committed
'url' => $this->router->generate('file_upload_get_content', $parameters),
'delete_type' => 'DELETE',
'delete_url' => $this->router->generate('file_upload_delete', $parameters),
]
]
]
Guillaume Perréal
committed
);
Guillaume Perréal
committed
* @Route("/{id}/content", name="file_upload_get_content")
* @Method("GET")
* @param Request $request
* @param UploadedFile $file
*/
Guillaume Perréal
committed
public function getContentAction(Request $request, UploadedFile $file)
$this->validateCsrfToken($request, self::CSRF_READ_INTENTION);
if(!$file->isValid()) {
throw new NotFoundHttpException();
}
Guillaume Perréal
committed
$response = UploadedFileResponse::create($file, 200, [], false, ResponseHeaderBag::DISPOSITION_ATTACHMENT);
$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->validateCsrfToken($request, self::CSRF_WRITE_INTENTION, $file);
$this->fileManager->delete($file);
return $this->createResponse();
}
/**
*
* @param type $intention
* @param UploadedFile $file
* @return string
*/
protected function generateCsrfToken($intention, UploadedFile $file = null)
{
if(null !== $file) {
$intention = sprintf($intention, $file->getId());
}
return $this->csrfProvider->generateCsrfToken($intention);
}
/**
*
* @param Request $request
* @param type $intention
* @param UploadedFile $file
* @throws HttpException
protected function validateCsrfToken(Request $request, $intention, UploadedFile $file = null)
if(null !== $file) {
$intention = sprintf($intention, $file->getId());
}
if(!$this->csrfProvider->isCsrfTokenValid($intention, $request->query->get('token', null))) {
Guillaume Perréal
committed
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);
}
}