*/ private const ALLOWED_EXTENSIONS = [ 'jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'tiff', 'zip', 'rar', '7z', 'tar', 'gz', ]; public function __construct( private readonly ArtworkStatsService $stats, private readonly ArtworkOriginalFileLocator $originalFiles, ) {} public function __invoke(Request $request, int $id): BinaryFileResponse|Response { $artwork = Artwork::query()->find($id); if (! $artwork) { abort(404); } $filePath = $this->originalFiles->resolveLocalPath($artwork); $ext = strtolower(ltrim((string) pathinfo($filePath, PATHINFO_EXTENSION), '.')); if ($filePath === '' || ! in_array($ext, self::ALLOWED_EXTENSIONS, true)) { abort(404); } $this->recordDownload($request, $artwork->id); $this->incrementDownloadCountIfAvailable($artwork->id); try { $this->stats->incrementDownloads((int) $artwork->id, 1, defer: false); } catch (\Throwable $exception) { Log::warning('Failed to increment artwork_stats download counter.', [ 'artwork_id' => $artwork->id, 'error' => $exception->getMessage(), ]); } if (! File::isFile($filePath)) { Log::warning('Artwork original file missing for download.', [ 'artwork_id' => $artwork->id, 'ext' => $ext, 'resolved_path' => $filePath, ]); abort(404); } $downloadName = $this->buildDownloadFilename((string) $artwork->file_name, $ext); // X-Accel-Redirect is safe only when nginx is explicitly configured to // map the internal URI to the originals root. Otherwise fallback to the // normal Laravel download response. $accelUri = $this->resolveAccelUri($filePath); if ($accelUri !== null) { return response('', 200, [ 'X-Accel-Redirect' => $accelUri, 'Content-Type' => 'application/octet-stream', 'Content-Disposition' => 'attachment; filename="' . addslashes($downloadName) . '"', 'X-Content-Type-Options' => 'nosniff', ]); } return response()->download($filePath, $downloadName); } private function resolveAccelUri(string $filePath): ?string { if (! config('app.download_accel_enabled')) { return null; } $accelBase = rtrim((string) config('app.download_accel_path', ''), '/'); if ($accelBase === '') { return null; } $root = rtrim((string) config('uploads.local_originals_root'), DIRECTORY_SEPARATOR); if ($root === '') { return null; } $normalizedRoot = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $root); $normalizedFilePath = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $filePath); $rootPrefix = $normalizedRoot . DIRECTORY_SEPARATOR; if (! str_starts_with($normalizedFilePath, $rootPrefix)) { Log::warning('Artwork download accel path skipped because file is outside originals root.', [ 'resolved_path' => $filePath, 'originals_root' => $root, ]); return null; } $relativePath = substr($normalizedFilePath, strlen($normalizedRoot)); if ($relativePath === false || $relativePath === '') { return null; } return $accelBase . str_replace(DIRECTORY_SEPARATOR, '/', $relativePath); } private function recordDownload(Request $request, int $artworkId): void { try { $ipAddress = $request->ip(); $ipBinary = $ipAddress ? @inet_pton($ipAddress) : false; ArtworkDownload::query()->create([ 'artwork_id' => $artworkId, 'user_id' => $request->user()?->id, 'ip' => $ipBinary !== false ? $ipBinary : null, 'ip_address' => $ipAddress, 'user_agent' => mb_substr((string) $request->userAgent(), 0, 1024), 'referer' => mb_substr((string) $request->headers->get('referer'), 0, 65535), ]); } catch (\Throwable $exception) { Log::warning('Failed to record artwork download analytics.', [ 'artwork_id' => $artworkId, 'error' => $exception->getMessage(), ]); } } private function incrementDownloadCountIfAvailable(int $artworkId): void { if (! Schema::hasColumn('artworks', 'download_count')) { return; } Artwork::query()->whereKey($artworkId)->increment('download_count'); } private function buildDownloadFilename(string $fileName, string $ext): string { $name = trim($fileName); $name = str_replace(['/', '\\'], '-', $name); $name = preg_replace('/[\x00-\x1F\x7F]/', '', $name) ?? ''; $name = preg_replace('/\s+/', ' ', $name) ?? ''; $name = trim((string) $name, ". \t\n\r\0\x0B"); if ($name === '') { $name = 'artwork'; } $baseName = pathinfo($name, PATHINFO_FILENAME); $baseName = trim((string) $baseName, ". \t\n\r\0\x0B"); if ($baseName === '') { $baseName = 'artwork'; } $brandSuffix = $this->downloadBrandSuffix(); if ($brandSuffix !== '' && ! Str::contains(Str::lower($baseName), Str::lower($brandSuffix))) { $baseName .= ' (' . $brandSuffix . ')'; } return $baseName . '.' . $ext; } private function downloadBrandSuffix(): string { $host = (string) parse_url((string) config('app.url'), PHP_URL_HOST); $host = strtolower(trim($host)); $host = preg_replace('/^www\./', '', $host) ?? ''; if ($host === '' || in_array($host, ['localhost', '127.0.0.1'], true) || str_ends_with($host, '.test')) { return 'skinbase.org'; } return $host; } }