UploadController.php 9.02 KiB
<?php
/*
 * Copyright (C) 2015 IRSTEA
 * All rights reserved.
 */
namespace Irstea\FileUploadBundle\Controller;
use Irstea\FileUploadBundle\Entity\UploadedFile;
use Irstea\FileUploadBundle\Exception\RejectedFileException;
use Irstea\FileUploadBundle\Http\UploadedFileResponse;
use Irstea\FileUploadBundle\Model\FileManagerInterface;
use Irstea\FileUploadBundle\Model\UploadedFileInterface;
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\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Templating\EngineInterface;
/**
 * @Route("/api/files", service="irstea_file_upload.upload_controller")
class UploadController extends Controller
    const CSRF_INTENTION = "uploaded_file";
    /**
     * @var FileManagerInterface
    protected $fileManager;
    /**
     * @var UrlGeneratorInterface
    protected $urlGenerator;
    /**
     * @var CsrfProviderInterface
    protected $csrfProvider;
    /**
     * @var TokenStorageInterface
    protected $tokenStorage;
    /**
     * @var EngineInterface
    protected $templating;
    /**
    public function __construct(
        FileManagerInterface $fileManager,
        UrlGeneratorInterface $urlGenerator,
        CsrfProviderInterface $csrfProvider,
7172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
TokenStorageInterface $tokenStorage, EngineInterface $templating ) { $this->fileManager = $fileManager; $this->urlGenerator = $urlGenerator; $this->csrfProvider = $csrfProvider; $this->tokenStorage = $tokenStorage; $this->templating = $templating; } /** * @Route("", name="file_upload_create") * @Method("POST") * @param Request $request */ public function createAction(Request $request) { try { $data = $request->request->get('file'); $token = $this->tokenStorage->getToken(); $file = $this->fileManager->create( $data['name'], $data['size'], $data['type'], isset($data['lastModified']) ? $data['lastModified'] : null, null !== $token ? $token->getUsername() : null, $request->getClientIp() ); $parameters = ['id' => $file->getId()]; $deleteUrl = $this->urlGenerator->generate('file_upload_delete', $parameters); return $this->createResponse( Response::HTTP_CREATED, 'New file created', array_merge( $file->toArray(), [ 'put_url' => $this->urlGenerator->generate('file_upload_put_content', $parameters), 'delete_type' => 'DELETE', 'delete_url' => $deleteUrl ] ), // On a pas de get pour l'instant, le DELETE et ce qui y ressemble le plus [ 'Location' => $deleteUrl ] ); } catch(\Exception $ex) { return $this->createExceptionResponse($ex); } } /** * @Route("/{id}/content", name="file_upload_put_content") * @Method("PUT") * @param Request $request * @param UploadedFile $file */ public function putContentAction(Request $request, UploadedFile $file) { try { $this->validateCsrfToken($request); list($offset, $maxlen, $complete) = $this->handleRangeHeader($request); // Demande un filehandle plutôt que charger le contenu en mémoire $input = $request->getContent(true); $file->copyFrom($input, $maxlen, $offset);
141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
fclose($input); if($complete) { return $this->completeUpload($file); } return $this->createResponse(Response::HTTP_OK, 'Chunk received'); } catch(\Exception $ex) { return $this->createExceptionResponse($ex); } } /** * * @param Request $request * @return array */ protected function handleRangeHeader(Request $request) { if(null === $range = $request->headers->get('Content-Range', null)) { return [0, PHP_INT_MAX, true]; } $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]); if($start < 0 || $start >= $end || $end >= $total) { throw new HttpException(Response::HTTP_REQUESTED_RANGE_NOT_SATISFIABLE); } return [$start, 1 + ($end - $start), $end === ($total-1)]; } /** * * @param UploadedFileInterface $file * @return Response */ protected function completeUpload(UploadedFileInterface $file) { try { $this->fileManager->completed($file); } catch(RejectedFileException $ex) { return $this->createResponse(Response::HTTP_FORBIDDEN, 'File rejected: '.$ex->getMessage()); } $parameters = ['id' => $file->getId()]; $fileData = array_merge( $file->toArray(), [ 'url' => $this->urlGenerator->generate('file_upload_get_content', $parameters), 'delete_type' => 'DELETE', 'delete_url' => $this->urlGenerator->generate('file_upload_delete', $parameters), 'repr' => $this->templating->render( 'IrsteaFileUploadBundle:Extension:uploaded_file.html.twig', ['file' => $file->toArray()] ) ] ); return $this->createResponse(Response::HTTP_OK, 'File uploaded.', ['files' => [$fileData]]); }