Files
Helpddesk/Modules/Stylist/Http/Controllers/ThemeAssetController.php
T
2026-08-25 12:36:04 +01:00

65 lines
2.3 KiB
PHP

<?php
namespace Modules\Stylist\Http\Controllers;
use Illuminate\Http\Response;
use Illuminate\Routing\Controller;
use Modules\Stylist\Theme\Loader;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
final class ThemeAssetController extends Controller
{
private const ALLOWED_ROOTS = ['css', 'js', 'images', 'fonts', 'icons', 'assets'];
private const ALLOWED_EXTENSIONS = [
'css', 'js', 'mjs', 'map', 'svg', 'png', 'jpg', 'jpeg', 'gif', 'webp', 'avif',
'ico', 'woff', 'woff2', 'ttf', 'otf', 'eot', 'json', 'xml', 'txt', 'webmanifest',
];
public function __invoke(string $theme, string $path, Loader $loader): BinaryFileResponse|Response
{
abort_if(str_contains($path, "\0"), 404);
$normalised = str_replace('\\', '/', ltrim($path, '/'));
$root = explode('/', $normalised, 2)[0] ?? '';
$extension = strtolower(pathinfo($normalised, PATHINFO_EXTENSION));
abort_unless(in_array($root, self::ALLOWED_ROOTS, true), 404);
abort_unless(in_array($extension, self::ALLOWED_EXTENSIONS, true), 404);
$loadedTheme = $loader->find($theme);
$themeRoot = realpath($loadedTheme->path());
abort_unless($themeRoot !== false, 404);
$asset = realpath($themeRoot.DIRECTORY_SEPARATOR.$normalised);
abort_unless(
$asset !== false
&& str_starts_with($asset, $themeRoot.DIRECTORY_SEPARATOR)
&& is_file($asset),
404
);
$mime = match ($extension) {
'css' => 'text/css; charset=UTF-8',
'js', 'mjs' => 'application/javascript; charset=UTF-8',
'svg' => 'image/svg+xml',
'json', 'map', 'webmanifest' => 'application/json',
'woff' => 'font/woff',
'woff2' => 'font/woff2',
'ttf' => 'font/ttf',
'otf' => 'font/otf',
default => mime_content_type($asset) ?: 'application/octet-stream',
};
return response()->file($asset, [
'Content-Type' => $mime,
'Cache-Control' => app()->isProduction()
? 'public, max-age=31536000, immutable'
: 'no-cache, private',
'X-Content-Type-Options' => 'nosniff',
'Content-Security-Policy' => "default-src 'none'; style-src 'unsafe-inline'",
]);
}
}