65 lines
2.3 KiB
PHP
Executable File
65 lines
2.3 KiB
PHP
Executable File
<?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'",
|
|
]);
|
|
}
|
|
}
|