]> BookStack Code Mirror - bookstack/blob - app/Http/Controllers/Controller.php
Added safe mime sniffing to prevent serving HTML
[bookstack] / app / Http / Controllers / Controller.php
1 <?php
2
3 namespace BookStack\Http\Controllers;
4
5 use BookStack\Facades\Activity;
6 use BookStack\Interfaces\Loggable;
7 use BookStack\Model;
8 use BookStack\Util\WebSafeMimeSniffer;
9 use finfo;
10 use Illuminate\Foundation\Bus\DispatchesJobs;
11 use Illuminate\Foundation\Validation\ValidatesRequests;
12 use Illuminate\Http\Exceptions\HttpResponseException;
13 use Illuminate\Http\JsonResponse;
14 use Illuminate\Http\Response;
15 use Illuminate\Routing\Controller as BaseController;
16
17 abstract class Controller extends BaseController
18 {
19     use DispatchesJobs;
20     use ValidatesRequests;
21
22     /**
23      * Check if the current user is signed in.
24      */
25     protected function isSignedIn(): bool
26     {
27         return auth()->check();
28     }
29
30     /**
31      * Stops the application and shows a permission error if
32      * the application is in demo mode.
33      */
34     protected function preventAccessInDemoMode()
35     {
36         if (config('app.env') === 'demo') {
37             $this->showPermissionError();
38         }
39     }
40
41     /**
42      * Adds the page title into the view.
43      */
44     public function setPageTitle(string $title)
45     {
46         view()->share('pageTitle', $title);
47     }
48
49     /**
50      * On a permission error redirect to home and display.
51      * the error as a notification.
52      */
53     protected function showPermissionError()
54     {
55         if (request()->wantsJson()) {
56             $response = response()->json(['error' => trans('errors.permissionJson')], 403);
57         } else {
58             $response = redirect('/');
59             $this->showErrorNotification(trans('errors.permission'));
60         }
61
62         throw new HttpResponseException($response);
63     }
64
65     /**
66      * Checks that the current user has the given permission otherwise throw an exception.
67      */
68     protected function checkPermission(string $permission): void
69     {
70         if (!user() || !user()->can($permission)) {
71             $this->showPermissionError();
72         }
73     }
74
75     /**
76      * Check the current user's permissions against an ownable item otherwise throw an exception.
77      */
78     protected function checkOwnablePermission(string $permission, Model $ownable): void
79     {
80         if (!userCan($permission, $ownable)) {
81             $this->showPermissionError();
82         }
83     }
84
85     /**
86      * Check if a user has a permission or bypass the permission
87      * check if the given callback resolves true.
88      */
89     protected function checkPermissionOr(string $permission, callable $callback): void
90     {
91         if ($callback() !== true) {
92             $this->checkPermission($permission);
93         }
94     }
95
96     /**
97      * Check if the current user has a permission or bypass if the provided user
98      * id matches the current user.
99      */
100     protected function checkPermissionOrCurrentUser(string $permission, int $userId): void
101     {
102         $this->checkPermissionOr($permission, function () use ($userId) {
103             return $userId === user()->id;
104         });
105     }
106
107     /**
108      * Send back a json error message.
109      */
110     protected function jsonError(string $messageText = '', int $statusCode = 500): JsonResponse
111     {
112         return response()->json(['message' => $messageText, 'status' => 'error'], $statusCode);
113     }
114
115     /**
116      * Create a response that forces a download in the browser.
117      */
118     protected function downloadResponse(string $content, string $fileName): Response
119     {
120         return response()->make($content, 200, [
121             'Content-Type'           => 'application/octet-stream',
122             'Content-Disposition'    => 'attachment; filename="' . $fileName . '"',
123             'X-Content-Type-Options' => 'nosniff',
124         ]);
125     }
126
127     /**
128      * Create a file download response that provides the file with a content-type
129      * correct for the file, in a way so the browser can show the content in browser.
130      */
131     protected function inlineDownloadResponse(string $content, string $fileName): Response
132     {
133
134         $mime = (new WebSafeMimeSniffer)->sniff($content);
135
136         return response()->make($content, 200, [
137             'Content-Type'           => $mime,
138             'Content-Disposition'    => 'inline; filename="' . $fileName . '"',
139             'X-Content-Type-Options' => 'nosniff',
140         ]);
141     }
142
143     /**
144      * Show a positive, successful notification to the user on next view load.
145      */
146     protected function showSuccessNotification(string $message): void
147     {
148         session()->flash('success', $message);
149     }
150
151     /**
152      * Show a warning notification to the user on next view load.
153      */
154     protected function showWarningNotification(string $message): void
155     {
156         session()->flash('warning', $message);
157     }
158
159     /**
160      * Show an error notification to the user on next view load.
161      */
162     protected function showErrorNotification(string $message): void
163     {
164         session()->flash('error', $message);
165     }
166
167     /**
168      * Log an activity in the system.
169      *
170      * @param string|Loggable
171      */
172     protected function logActivity(string $type, $detail = ''): void
173     {
174         Activity::add($type, $detail);
175     }
176
177     /**
178      * Get the validation rules for image files.
179      */
180     protected function getImageValidationRules(): string
181     {
182         return 'image_extension|mimes:jpeg,png,gif,webp';
183     }
184 }