diff --git a/app/Http/Controllers/AttachmentController.php b/app/Http/Controllers/AttachmentController.php index 04e89ac5d..74eae641b 100644 --- a/app/Http/Controllers/AttachmentController.php +++ b/app/Http/Controllers/AttachmentController.php @@ -14,16 +14,14 @@ use Illuminate\Validation\ValidationException; class AttachmentController extends Controller { protected $attachmentService; - protected $attachment; protected $pageRepo; /** * AttachmentController constructor. */ - public function __construct(AttachmentService $attachmentService, Attachment $attachment, PageRepo $pageRepo) + public function __construct(AttachmentService $attachmentService, PageRepo $pageRepo) { $this->attachmentService = $attachmentService; - $this->attachment = $attachment; $this->pageRepo = $pageRepo; } @@ -67,7 +65,7 @@ class AttachmentController extends Controller 'file' => 'required|file' ]); - $attachment = $this->attachment->newQuery()->findOrFail($attachmentId); + $attachment = Attachment::query()->findOrFail($attachmentId); $this->checkOwnablePermission('view', $attachment->page); $this->checkOwnablePermission('page-update', $attachment->page); $this->checkOwnablePermission('attachment-create', $attachment); @@ -89,7 +87,7 @@ class AttachmentController extends Controller */ public function getUpdateForm(string $attachmentId) { - $attachment = $this->attachment->findOrFail($attachmentId); + $attachment = Attachment::query()->findOrFail($attachmentId); $this->checkOwnablePermission('page-update', $attachment->page); $this->checkOwnablePermission('attachment-create', $attachment); @@ -202,9 +200,10 @@ class AttachmentController extends Controller * @throws FileNotFoundException * @throws NotFoundException */ - public function get(string $attachmentId) + public function get(Request $request, string $attachmentId) { - $attachment = $this->attachment->findOrFail($attachmentId); + /** @var Attachment $attachment */ + $attachment = Attachment::query()->findOrFail($attachmentId); try { $page = $this->pageRepo->getById($attachment->uploaded_to); } catch (NotFoundException $exception) { @@ -217,8 +216,13 @@ class AttachmentController extends Controller return redirect($attachment->path); } + $fileName = $attachment->getFileName(); $attachmentContents = $this->attachmentService->getAttachmentFromStorage($attachment); - return $this->downloadResponse($attachmentContents, $attachment->getFileName()); + + if ($request->get('open') === 'true') { + return $this->inlineDownloadResponse($attachmentContents, $fileName); + } + return $this->downloadResponse($attachmentContents, $fileName); } /** @@ -227,7 +231,7 @@ class AttachmentController extends Controller */ public function delete(string $attachmentId) { - $attachment = $this->attachment->findOrFail($attachmentId); + $attachment = Attachment::query()->findOrFail($attachmentId); $this->checkOwnablePermission('attachment-delete', $attachment); $this->attachmentService->deleteFile($attachment); return response()->json(['message' => trans('entities.attachments_deleted')]); diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php index 034dfa524..47b03b28d 100644 --- a/app/Http/Controllers/Controller.php +++ b/app/Http/Controllers/Controller.php @@ -6,6 +6,7 @@ use BookStack\Facades\Activity; use BookStack\Interfaces\Loggable; use BookStack\HasCreatorAndUpdater; use BookStack\Model; +use finfo; use Illuminate\Foundation\Bus\DispatchesJobs; use Illuminate\Foundation\Validation\ValidatesRequests; use Illuminate\Http\Exceptions\HttpResponseException; @@ -121,6 +122,20 @@ abstract class Controller extends BaseController ]); } + /** + * 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. + */ + protected function inlineDownloadResponse(string $content, string $fileName): Response + { + $finfo = new finfo(FILEINFO_MIME_TYPE); + $mime = $finfo->buffer($content) ?: 'application/octet-stream'; + return response()->make($content, 200, [ + 'Content-Type' => $mime, + 'Content-Disposition' => 'inline; filename="' . $fileName . '"' + ]); + } + /** * Show a positive, successful notification to the user on next view load. */ diff --git a/app/Uploads/AttachmentService.php b/app/Uploads/AttachmentService.php index 4437897c7..37adb4f83 100644 --- a/app/Uploads/AttachmentService.php +++ b/app/Uploads/AttachmentService.php @@ -3,8 +3,10 @@ use BookStack\Exceptions\FileUploadException; use Exception; use Illuminate\Contracts\Filesystem\Factory as FileSystem; +use Illuminate\Contracts\Filesystem\FileNotFoundException; use Illuminate\Contracts\Filesystem\Filesystem as FileSystemInstance; use Illuminate\Support\Str; +use Log; use Symfony\Component\HttpFoundation\File\UploadedFile; class AttachmentService @@ -38,11 +40,9 @@ class AttachmentService /** * Get an attachment from storage. - * @param Attachment $attachment - * @return string - * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException + * @throws FileNotFoundException */ - public function getAttachmentFromStorage(Attachment $attachment) + public function getAttachmentFromStorage(Attachment $attachment): string { return $this->getStorage()->get($attachment->path); } @@ -202,7 +202,7 @@ class AttachmentService try { $storage->put($attachmentPath, $attachmentData); } catch (Exception $e) { - \Log::error('Error when attempting file upload:' . $e->getMessage()); + Log::error('Error when attempting file upload:' . $e->getMessage()); throw new FileUploadException(trans('errors.path_not_writable', ['filePath' => $attachmentPath])); } diff --git a/composer.json b/composer.json index 3e604b8fd..8450a2f92 100644 --- a/composer.json +++ b/composer.json @@ -8,6 +8,7 @@ "php": "^7.3|^8.0", "ext-curl": "*", "ext-dom": "*", + "ext-fileinfo": "*", "ext-gd": "*", "ext-json": "*", "ext-mbstring": "*",