more fixes
This commit is contained in:
133
app/Http/Controllers/ArtworkDownloadController.php
Normal file
133
app/Http/Controllers/ArtworkDownloadController.php
Normal file
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Artwork;
|
||||
use App\Models\ArtworkDownload;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
|
||||
final class ArtworkDownloadController extends Controller
|
||||
{
|
||||
/**
|
||||
* Allowed original file extensions for secure server-side download.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
$this->recordDownload($request, $artwork->id);
|
||||
$this->incrementDownloadCountIfAvailable($artwork->id);
|
||||
|
||||
$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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user