99 lines
3.2 KiB
PHP
Executable File
99 lines
3.2 KiB
PHP
Executable File
<?php
|
|
|
|
namespace App\Controllers;
|
|
|
|
use Kernel\View;
|
|
use Symfony\Component\HttpFoundation\Request;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
|
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
|
|
|
|
class StorageController extends BaseController
|
|
{
|
|
/**
|
|
* Serve a file from storage/uploads/{module}/{filename}
|
|
*
|
|
* @param Request $request
|
|
* @param string $module
|
|
* @param string $filename
|
|
* @return Response
|
|
*/
|
|
public function serve(Request $request, string $module, string $filename): Response
|
|
{
|
|
// Sanitize inputs to prevent directory traversal
|
|
$module = preg_replace('/[^a-zA-Z0-9_-]/', '', $module);
|
|
|
|
// Allow subfolders in filename (e.g. "123/image.jpg")
|
|
$filename = ltrim($filename, '/');
|
|
if (strpos($filename, '..') !== false) {
|
|
return new Response('Access denied.', 403);
|
|
}
|
|
|
|
// Build the physical path
|
|
$basePath = dirname(__DIR__, 2) . '/storage/uploads';
|
|
$filePath = $basePath . '/' . $module . '/' . $filename;
|
|
|
|
// Security check: Ensure the file is actually inside storage/uploads
|
|
$realPath = realpath($filePath);
|
|
if (!$realPath || strpos($realPath, realpath($basePath)) !== 0) {
|
|
return new Response('File not found or access denied.', 404);
|
|
}
|
|
|
|
if (!file_exists($realPath)) {
|
|
return new Response('File not found.', 404);
|
|
}
|
|
|
|
// Determine MIME type
|
|
$mimeType = $this->getMimeType($realPath);
|
|
|
|
// Create BinaryFileResponse for efficient file serving
|
|
$response = new BinaryFileResponse($realPath);
|
|
$response->headers->set('Content-Type', $mimeType);
|
|
$response->setContentDisposition(
|
|
ResponseHeaderBag::DISPOSITION_INLINE,
|
|
$filename
|
|
);
|
|
|
|
// Add cache headers (cache for 30 days)
|
|
$response->setPublic();
|
|
$response->setMaxAge(2592000);
|
|
$response->setSharedMaxAge(2592000);
|
|
|
|
return $response;
|
|
}
|
|
|
|
/**
|
|
* Get MIME type of a file
|
|
*/
|
|
private function getMimeType(string $path): string
|
|
{
|
|
$extension = strtolower(pathinfo($path, PATHINFO_EXTENSION));
|
|
|
|
$mimes = [
|
|
'jpg' => 'image/jpeg',
|
|
'jpeg' => 'image/jpeg',
|
|
'png' => 'image/png',
|
|
'gif' => 'image/gif',
|
|
'webp' => 'image/webp',
|
|
'svg' => 'image/svg+xml',
|
|
'pdf' => 'application/pdf',
|
|
'doc' => 'application/msword',
|
|
'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
'xls' => 'application/vnd.ms-excel',
|
|
'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
'txt' => 'text/plain',
|
|
'zip' => 'application/zip',
|
|
];
|
|
|
|
if (isset($mimes[$extension])) {
|
|
return $mimes[$extension];
|
|
}
|
|
|
|
if (function_exists('mime_content_type')) {
|
|
return mime_content_type($path);
|
|
}
|
|
|
|
return 'application/octet-stream';
|
|
}
|
|
}
|