]> BookStack Code Mirror - bookstack/commitdiff
Added an env configurable file upload size limit
authorDan Brown <redacted>
Sun, 14 Nov 2021 22:03:22 +0000 (22:03 +0000)
committerDan Brown <redacted>
Sun, 14 Nov 2021 22:03:22 +0000 (22:03 +0000)
Replaces the old suggestion of setting JS head 'window.uploadLimit'
variable. This new env option will be used by back-end validation and
front-end libs/logic too.

Limits already likely exist within prod environments at a PHP and
webserver level but this allows an app-level limit and centralises the
option on the BookStack side into the .env

Closes #3033

.env.example.complete
app/Config/app.php
app/Entities/Tools/PageContent.php
app/Http/Controllers/Api/ApiController.php
app/Http/Controllers/Api/AttachmentApiController.php
app/Http/Controllers/AttachmentController.php
app/Http/Controllers/Controller.php
app/Uploads/AttachmentService.php
resources/js/components/dropzone.js
resources/views/form/dropzone.blade.php

index 683db703c6b59e78a8ad941fc7078dad9adf9f23..9a46b23a51e5244ca221b9541649c62819bdb26a 100644 (file)
@@ -293,6 +293,10 @@ REVISION_LIMIT=50
 # Set to -1 for unlimited recycle bin lifetime.
 RECYCLE_BIN_LIFETIME=30
 
+# File Upload Limit
+# Maximum file size, in megabytes, that can be uploaded to the system.
+FILE_UPLOAD_SIZE_LIMIT=50
+
 # Allow <script> tags in page content
 # Note, if set to 'true' the page editor may still escape scripts.
 ALLOW_CONTENT_SCRIPTS=false
index 44e382cdcb59061fd12013caefab501dae883564..39bfa7134f9e441e1b0686b750e72a05c1bdafdf 100644 (file)
@@ -31,6 +31,9 @@ return [
     // Set to -1 for unlimited recycle bin lifetime.
     'recycle_bin_lifetime' => env('RECYCLE_BIN_LIFETIME', 30),
 
+    // The limit for all uploaded files, including images and attachments in MB.
+    'upload_limit' => env('FILE_UPLOAD_SIZE_LIMIT', 50),
+
     // Allow <script> tags to entered within page content.
     // <script> tags are escaped by default.
     // Even when overridden the WYSIWYG editor may still escape script content.
index b1323bc68ac0f02b6f3f799a59a965c0f7953fb9..52a10ae872ebbcf55827acf345296b20a4a327da 100644 (file)
@@ -135,6 +135,12 @@ class PageContent
             return '';
         }
 
+        // Validate that the content is not over our upload limit
+        $uploadLimitBytes = (config('app.upload_limit') * 1000000);
+        if (strlen($imageInfo['data']) > $uploadLimitBytes) {
+            return '';
+        }
+
         // Save image from data with a random name
         $imageName = 'embedded-image-' . Str::random(8) . '.' . $imageInfo['extension'];
 
index fc9788b06e340d405eb73b9d56fd5c1fb4290c5f..3f049a08c8afaba8523448137624b80dc078a0d1 100644 (file)
@@ -24,9 +24,14 @@ abstract class ApiController extends Controller
 
     /**
      * Get the validation rules for this controller.
+     * Defaults to a $rules property but can be a rules() method.
      */
     public function getValdationRules(): array
     {
+        if (method_exists($this, 'rules')) {
+            return $this->rules();
+        }
+
         return $this->rules;
     }
 }
index 67815bc47bf4d47230a27787c7b50039cea06de9..fc5008e3c6d94baf77ad70167a567c54e1451bcf 100644 (file)
@@ -15,21 +15,6 @@ class AttachmentApiController extends ApiController
 {
     protected $attachmentService;
 
-    protected $rules = [
-        'create' => [
-            'name'        => ['required', 'min:1', 'max:255', 'string'],
-            'uploaded_to' => ['required', 'integer', 'exists:pages,id'],
-            'file'        => ['required_without:link', 'file'],
-            'link'        => ['required_without:file', 'min:1', 'max:255', 'safe_url'],
-        ],
-        'update' => [
-            'name'        => ['min:1', 'max:255', 'string'],
-            'uploaded_to' => ['integer', 'exists:pages,id'],
-            'file'        => ['file'],
-            'link'        => ['min:1', 'max:255', 'safe_url'],
-        ],
-    ];
-
     public function __construct(AttachmentService $attachmentService)
     {
         $this->attachmentService = $attachmentService;
@@ -61,7 +46,7 @@ class AttachmentApiController extends ApiController
     public function create(Request $request)
     {
         $this->checkPermission('attachment-create-all');
-        $requestData = $this->validate($request, $this->rules['create']);
+        $requestData = $this->validate($request, $this->rules()['create']);
 
         $pageId = $request->get('uploaded_to');
         $page = Page::visible()->findOrFail($pageId);
@@ -122,7 +107,7 @@ class AttachmentApiController extends ApiController
      */
     public function update(Request $request, string $id)
     {
-        $requestData = $this->validate($request, $this->rules['update']);
+        $requestData = $this->validate($request, $this->rules()['update']);
         /** @var Attachment $attachment */
         $attachment = Attachment::visible()->findOrFail($id);
 
@@ -162,4 +147,22 @@ class AttachmentApiController extends ApiController
 
         return response('', 204);
     }
+
+    protected function rules(): array
+    {
+        return [
+            'create' => [
+                'name'        => ['required', 'min:1', 'max:255', 'string'],
+                'uploaded_to' => ['required', 'integer', 'exists:pages,id'],
+                'file'        => array_merge(['required_without:link'], $this->attachmentService->getFileValidationRules()),
+                'link'        => ['required_without:file', 'min:1', 'max:255', 'safe_url'],
+            ],
+            'update' => [
+                'name'        => ['min:1', 'max:255', 'string'],
+                'uploaded_to' => ['integer', 'exists:pages,id'],
+                'file'        => $this->attachmentService->getFileValidationRules(),
+                'link'        => ['min:1', 'max:255', 'safe_url'],
+            ],
+        ];
+    }
 }
index ec9872bcf9108f513b2f72614bb595dfd897bca8..445611fcbfe907108c25a758c1f645f6b1b368d5 100644 (file)
@@ -9,6 +9,7 @@ use BookStack\Uploads\Attachment;
 use BookStack\Uploads\AttachmentService;
 use Exception;
 use Illuminate\Contracts\Filesystem\FileNotFoundException;
+use Illuminate\Foundation\Http\Middleware\ValidatePostSize;
 use Illuminate\Http\Request;
 use Illuminate\Support\MessageBag;
 use Illuminate\Validation\ValidationException;
@@ -37,7 +38,7 @@ class AttachmentController extends Controller
     {
         $this->validate($request, [
             'uploaded_to' => ['required', 'integer', 'exists:pages,id'],
-            'file'        => ['required', 'file'],
+            'file'        => array_merge(['required'], $this->attachmentService->getFileValidationRules()),
         ]);
 
         $pageId = $request->get('uploaded_to');
@@ -65,7 +66,7 @@ class AttachmentController extends Controller
     public function uploadUpdate(Request $request, $attachmentId)
     {
         $this->validate($request, [
-            'file' => ['required', 'file'],
+            'file' => array_merge(['required'], $this->attachmentService->getFileValidationRules()),
         ]);
 
         /** @var Attachment $attachment */
index 047e0bed944ba0509ab88c841686acfd2d990809..f836f18edad2db02453717f3f2c4f4b43a099a77 100644 (file)
@@ -165,7 +165,7 @@ abstract class Controller extends BaseController
     /**
      * Log an activity in the system.
      *
-     * @param $detail string|Loggable
+     * @param string|Loggable $detail
      */
     protected function logActivity(string $type, $detail = ''): void
     {
@@ -177,6 +177,6 @@ abstract class Controller extends BaseController
      */
     protected function getImageValidationRules(): array
     {
-        return ['image_extension', 'mimes:jpeg,png,gif,webp'];
+        return ['image_extension', 'mimes:jpeg,png,gif,webp', 'max:' . (config('app.upload_limit') * 1000)];
     }
 }
index e62a18c8200fed7665b3f263989b0afb4b47511b..7974d7ae926b1472f61f567811e527acc254e688 100644 (file)
@@ -233,4 +233,12 @@ class AttachmentService
 
         return $attachmentPath;
     }
+
+    /**
+     * Get the file validation rules for attachments.
+     */
+    public function getFileValidationRules(): array
+    {
+        return ['file', 'max:' . (config('app.upload_limit') * 1000)];
+    }
 }
index e7273df62b2a13eb173b54df39cec2c77d0443e5..44fdf2d0d3fba800e31bf68364088751186428a0 100644 (file)
@@ -11,6 +11,7 @@ class Dropzone {
         this.url = this.$opts.url;
         this.successMessage = this.$opts.successMessage;
         this.removeMessage = this.$opts.removeMessage;
+        this.uploadLimit = Number(this.$opts.uploadLimit);
         this.uploadLimitMessage = this.$opts.uploadLimitMessage;
         this.timeoutMessage = this.$opts.timeoutMessage;
 
@@ -19,7 +20,7 @@ class Dropzone {
             addRemoveLinks: true,
             dictRemoveFile: this.removeMessage,
             timeout: Number(window.uploadTimeout) || 60000,
-            maxFilesize: Number(window.uploadLimit) || 256,
+            maxFilesize: this.uploadLimit,
             url: this.url,
             withCredentials: true,
             init() {
index 6c5ac49298fde46c7558a9cb4c413b082334615c..118761d4c1181b4cd38e9eebb89472a76c8344b8 100644 (file)
@@ -7,6 +7,7 @@
      option:dropzone:url="{{ $url }}"
      option:dropzone:success-message="{{ $successMessage ?? '' }}"
      option:dropzone:remove-message="{{ trans('components.image_upload_remove') }}"
+     option:dropzone:upload-limit="{{ config('app.upload_limit') }}"
      option:dropzone:upload-limit-message="{{ trans('errors.server_upload_limit') }}"
      option:dropzone:timeout-message="{{ trans('errors.file_upload_timeout') }}"