use Exception;
use Illuminate\Contracts\Filesystem\FileNotFoundException;
use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Storage;
use Illuminate\Support\MessageBag;
use Illuminate\Validation\ValidationException;
class AttachmentController extends Controller
{
- protected $attachmentService;
- protected $pageRepo;
+ protected AttachmentService $attachmentService;
+ protected PageRepo $pageRepo;
/**
* AttachmentController constructor.
}
$fileName = $attachment->getFileName();
- $attachmentContents = $this->attachmentService->getAttachmentFromStorage($attachment);
+ $attachmentStream = $this->attachmentService->streamAttachmentFromStorage($attachment);
if ($request->get('open') === 'true') {
- return $this->inlineDownloadResponse($attachmentContents, $fileName);
+ return $this->streamedInlineDownloadResponse($attachmentStream, $fileName);
}
- return $this->downloadResponse($attachmentContents, $fileName);
+ return $this->streamedDownloadResponse($attachmentStream, $fileName);
}
/**
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Response;
use Illuminate\Routing\Controller as BaseController;
+use Symfony\Component\HttpFoundation\StreamedResponse;
abstract class Controller extends BaseController
{
]);
}
+ /**
+ * Create a response that forces a download, from a given stream of content.
+ */
+ protected function streamedDownloadResponse($stream, string $fileName): StreamedResponse
+ {
+ return response()->stream(function() use ($stream) {
+ fpassthru($stream);
+ fclose($stream);
+ }, 200, [
+ 'Content-Type' => 'application/octet-stream',
+ 'Content-Disposition' => 'attachment; filename="' . $fileName . '"',
+ 'X-Content-Type-Options' => 'nosniff',
+ ]);
+ }
+
/**
* Create a file download response that provides the file with a content-type
* correct for the file, in a way so the browser can show the content in browser.
]);
}
+ /**
+ * Create a file download response that provides the file with a content-type
+ * correct for the file, in a way so the browser can show the content in browser,
+ * for a given content stream.
+ */
+ protected function streamedInlineDownloadResponse($stream, string $fileName): StreamedResponse
+ {
+ $sniffContent = fread($stream, 1000);
+ $mime = (new WebSafeMimeSniffer())->sniff($sniffContent);
+
+ return response()->stream(function() use ($sniffContent, $stream) {
+ echo $sniffContent;
+ fpassthru($stream);
+ fclose($stream);
+ }, 200, [
+ 'Content-Type' => $mime,
+ 'Content-Disposition' => 'inline; filename="' . $fileName . '"',
+ 'X-Content-Type-Options' => 'nosniff',
+ ]);
+ }
+
/**
* Show a positive, successful notification to the user on next view load.
*/
class AttachmentService
{
- protected $fileSystem;
+ protected FilesystemManager $fileSystem;
/**
* AttachmentService constructor.
return $this->getStorageDisk()->get($this->adjustPathForStorageDisk($attachment->path));
}
+ /**
+ * Stream an attachment from storage.
+ *
+ * @return resource|null
+ * @throws FileNotFoundException
+ */
+ public function streamAttachmentFromStorage(Attachment $attachment)
+ {
+
+ return $this->getStorageDisk()->readStream($this->adjustPathForStorageDisk($attachment->path));
+ }
+
/**
* Store a new attachment upon user upload.
*