*/ private const ALLOWED_EXTENSIONS = [ 'jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'tiff', ]; public function __invoke(Request $request, int $id): BinaryFileResponse { $artwork = Artwork::query()->find($id); if (! $artwork) { abort(404); } $hash = strtolower((string) $artwork->hash); $ext = strtolower(ltrim((string) $artwork->file_ext, '.')); if (! $this->isValidHash($hash) || ! in_array($ext, self::ALLOWED_EXTENSIONS, true)) { abort(404); } $filePath = $this->resolveOriginalPath($hash, $ext); $this->recordDownload($request, $artwork->id); $this->incrementDownloadCountIfAvailable($artwork->id); if (! File::isFile($filePath)) { Log::warning('Artwork original file missing for download.', [ 'artwork_id' => $artwork->id, 'hash' => $hash, 'ext' => $ext, 'resolved_path' => $filePath, ]); abort(404); } $downloadName = $this->buildDownloadFilename((string) $artwork->file_name, $ext); return response()->download($filePath, $downloadName); } private function resolveOriginalPath(string $hash, string $ext): string { $firstDir = substr($hash, 0, 2); $secondDir = substr($hash, 2, 2); $root = rtrim((string) config('uploads.storage_root'), DIRECTORY_SEPARATOR); return $root . DIRECTORY_SEPARATOR . 'original' . DIRECTORY_SEPARATOR . $firstDir . DIRECTORY_SEPARATOR . $secondDir . DIRECTORY_SEPARATOR . $hash . '.' . $ext; } 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 isValidHash(string $hash): bool { return $hash !== '' && preg_match('/^[a-f0-9]+$/', $hash) === 1; } 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'; } if (strtolower((string) pathinfo($name, PATHINFO_EXTENSION)) !== $ext) { $name .= '.' . $ext; } return $name; } }