119 lines
4.3 KiB
PHP
119 lines
4.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Controller;
|
|
|
|
use App\Repository\DocumentRepository;
|
|
use App\Service\DocumentStorageService;
|
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
|
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
|
use Symfony\Component\HttpFoundation\HeaderUtils;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
|
|
use Symfony\Component\Routing\Attribute\Route;
|
|
|
|
use function strlen;
|
|
|
|
#[Route('/api/documents')]
|
|
class DocumentServeController extends AbstractController
|
|
{
|
|
public function __construct(
|
|
private readonly DocumentRepository $documentRepository,
|
|
private readonly DocumentStorageService $storageService,
|
|
) {}
|
|
|
|
#[Route('/{id}/file', name: 'document_serve_file', methods: ['GET'])]
|
|
public function serve(string $id): Response
|
|
{
|
|
$this->denyAccessUnlessGranted('ROLE_VIEWER');
|
|
|
|
$document = $this->documentRepository->find($id);
|
|
if (!$document) {
|
|
return $this->json(['error' => 'Document not found.'], 404);
|
|
}
|
|
|
|
$path = $document->getPath();
|
|
|
|
// Backward compatibility: serve Base64 data URIs from DB
|
|
if ($this->storageService->isBase64DataUri($path)) {
|
|
$parts = explode(',', $path, 2);
|
|
$content = base64_decode($parts[1] ?? '', true);
|
|
if (false === $content) {
|
|
return $this->json(['error' => 'Invalid document data.'], 500);
|
|
}
|
|
|
|
$disposition = HeaderUtils::makeDisposition(ResponseHeaderBag::DISPOSITION_INLINE, $document->getFilename());
|
|
|
|
return new Response($content, 200, [
|
|
'Content-Type' => $document->getMimeType(),
|
|
'Content-Disposition' => $disposition,
|
|
'Content-Length' => (string) strlen($content),
|
|
'Cache-Control' => 'private, max-age=3600',
|
|
'X-Content-Type-Options' => 'nosniff',
|
|
'Content-Security-Policy' => 'sandbox',
|
|
]);
|
|
}
|
|
|
|
// File-based path: serve from disk
|
|
$absolutePath = $this->storageService->getAbsolutePath($path);
|
|
if (!file_exists($absolutePath)) {
|
|
return $this->json(['error' => 'File not found on disk.'], 404);
|
|
}
|
|
|
|
$response = new BinaryFileResponse($absolutePath);
|
|
$response->headers->set('Content-Type', $document->getMimeType());
|
|
$response->setContentDisposition(
|
|
ResponseHeaderBag::DISPOSITION_INLINE,
|
|
$document->getFilename()
|
|
);
|
|
$response->headers->set('Cache-Control', 'private, max-age=3600');
|
|
$response->headers->set('X-Content-Type-Options', 'nosniff');
|
|
$response->headers->set('Content-Security-Policy', 'sandbox');
|
|
|
|
return $response;
|
|
}
|
|
|
|
#[Route('/{id}/download', name: 'document_download_file', methods: ['GET'])]
|
|
public function download(string $id): Response
|
|
{
|
|
$this->denyAccessUnlessGranted('ROLE_VIEWER');
|
|
|
|
$document = $this->documentRepository->find($id);
|
|
if (!$document) {
|
|
return $this->json(['error' => 'Document not found.'], 404);
|
|
}
|
|
|
|
$path = $document->getPath();
|
|
|
|
if ($this->storageService->isBase64DataUri($path)) {
|
|
$parts = explode(',', $path, 2);
|
|
$content = base64_decode($parts[1] ?? '', true);
|
|
if (false === $content) {
|
|
return $this->json(['error' => 'Invalid document data.'], 500);
|
|
}
|
|
|
|
$disposition = HeaderUtils::makeDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, $document->getFilename());
|
|
|
|
return new Response($content, 200, [
|
|
'Content-Type' => 'application/octet-stream',
|
|
'Content-Disposition' => $disposition,
|
|
'Content-Length' => (string) strlen($content),
|
|
]);
|
|
}
|
|
|
|
$absolutePath = $this->storageService->getAbsolutePath($path);
|
|
if (!file_exists($absolutePath)) {
|
|
return $this->json(['error' => 'File not found on disk.'], 404);
|
|
}
|
|
|
|
$response = new BinaryFileResponse($absolutePath);
|
|
$response->setContentDisposition(
|
|
ResponseHeaderBag::DISPOSITION_ATTACHMENT,
|
|
$document->getFilename()
|
|
);
|
|
|
|
return $response;
|
|
}
|
|
}
|