From 44b3f75b48a2992a2558ad44d0cda31b7e50e683 Mon Sep 17 00:00:00 2001 From: dyzulk <66510723+dyzulk@users.noreply.github.com> Date: Sun, 4 Jan 2026 23:08:55 +0700 Subject: [PATCH] fix: manual streaming response for attachments --- .../Controllers/Api/AttachmentController.php | 55 ++++++++++++------- 1 file changed, 36 insertions(+), 19 deletions(-) diff --git a/app/Http/Controllers/Api/AttachmentController.php b/app/Http/Controllers/Api/AttachmentController.php index 6b3c2af..d0f5128 100644 --- a/app/Http/Controllers/Api/AttachmentController.php +++ b/app/Http/Controllers/Api/AttachmentController.php @@ -13,46 +13,63 @@ class AttachmentController extends Controller * Download a private attachment. */ public function download(Request $request, TicketAttachment $attachment) + { + public function download(Request $request, TicketAttachment $attachment) { try { + // Paranoid Auth Check $user = $request->user(); + if (!$user) { + return response()->json(['error' => 'Unauthenticated'], 401); + } + + if (!$attachment->reply) { + return response()->json(['error' => 'Orphaned Attachment (No Reply)'], 404); + } + if (!$attachment->reply->ticket) { + return response()->json(['error' => 'Orphaned Attachment (No Ticket)'], 404); + } - // 1. Authorization Logic - $attachment->load(['reply.ticket']); $ticket = $attachment->reply->ticket; if ($ticket->user_id !== $user->id && !$user->isAdminOrOwner()) { - abort(403, 'Unauthorized access to this attachment.'); + return response()->json(['error' => 'Unauthorized'], 403); } - // 2. Fetch File $path = $attachment->file_path; + // Legacy URL handling if (filter_var($path, FILTER_VALIDATE_URL)) { return redirect($path); } $disk = 'r2-private'; - + + // Use manual file retrieval to avoid header issues with Storage::download if (!Storage::disk($disk)->exists($path)) { - \Log::error("Attachment 404: Path [$path] not found on disk [$disk]"); - abort(404, 'File not found on secure storage.'); + return response()->json(['error' => 'File not found on storage'], 404); } - return Storage::disk($disk)->download($path, $attachment->file_name); + $mimeType = Storage::disk($disk)->mimeType($path) ?? 'application/octet-stream'; + $size = Storage::disk($disk)->size($path); + $fileName = $attachment->file_name ?? basename($path); + + return response()->stream(function() use ($disk, $path) { + $stream = Storage::disk($disk)->readStream($path); + fpassthru($stream); + if (is_resource($stream)) { + fclose($stream); + } + }, 200, [ + 'Content-Type' => $mimeType, + 'Content-Length' => $size, + 'Content-Disposition' => 'attachment; filename="' . $fileName . '"', + ]); } catch (\Exception $e) { - \Log::error("Attachment Download Error: " . $e->getMessage(), [ - 'attachment_id' => $attachment->id, - 'path' => $attachment->file_path ?? 'unknown', - 'trace' => $e->getTraceAsString() - ]); - - return response()->json([ - 'error' => 'Server Error', - 'message' => $e->getMessage(), - 'file_path' => $attachment->file_path ?? 'unknown' - ], 500); + \Log::error("Stream Download Error: " . $e->getMessage(), ['trace' => $e->getTraceAsString()]); + return response()->json(['error' => 'Server Error', 'message' => $e->getMessage()], 500); } } + } }