Added an env configurable file upload size limit
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
This commit is contained in:
		
							parent
							
								
									f910738a80
								
							
						
					
					
						commit
						85154fff69
					
				| 
						 | 
					@ -293,6 +293,10 @@ REVISION_LIMIT=50
 | 
				
			||||||
# Set to -1 for unlimited recycle bin lifetime.
 | 
					# Set to -1 for unlimited recycle bin lifetime.
 | 
				
			||||||
RECYCLE_BIN_LIFETIME=30
 | 
					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
 | 
					# Allow <script> tags in page content
 | 
				
			||||||
# Note, if set to 'true' the page editor may still escape scripts.
 | 
					# Note, if set to 'true' the page editor may still escape scripts.
 | 
				
			||||||
ALLOW_CONTENT_SCRIPTS=false
 | 
					ALLOW_CONTENT_SCRIPTS=false
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -31,6 +31,9 @@ return [
 | 
				
			||||||
    // Set to -1 for unlimited recycle bin lifetime.
 | 
					    // Set to -1 for unlimited recycle bin lifetime.
 | 
				
			||||||
    'recycle_bin_lifetime' => env('RECYCLE_BIN_LIFETIME', 30),
 | 
					    '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.
 | 
					    // Allow <script> tags to entered within page content.
 | 
				
			||||||
    // <script> tags are escaped by default.
 | 
					    // <script> tags are escaped by default.
 | 
				
			||||||
    // Even when overridden the WYSIWYG editor may still escape script content.
 | 
					    // Even when overridden the WYSIWYG editor may still escape script content.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -135,6 +135,12 @@ class PageContent
 | 
				
			||||||
            return '';
 | 
					            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
 | 
					        // Save image from data with a random name
 | 
				
			||||||
        $imageName = 'embedded-image-' . Str::random(8) . '.' . $imageInfo['extension'];
 | 
					        $imageName = 'embedded-image-' . Str::random(8) . '.' . $imageInfo['extension'];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -24,9 +24,14 @@ abstract class ApiController extends Controller
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Get the validation rules for this controller.
 | 
					     * Get the validation rules for this controller.
 | 
				
			||||||
 | 
					     * Defaults to a $rules property but can be a rules() method.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function getValdationRules(): array
 | 
					    public function getValdationRules(): array
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 | 
					        if (method_exists($this, 'rules')) {
 | 
				
			||||||
 | 
					            return $this->rules();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return $this->rules;
 | 
					        return $this->rules;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,21 +15,6 @@ class AttachmentApiController extends ApiController
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    protected $attachmentService;
 | 
					    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)
 | 
					    public function __construct(AttachmentService $attachmentService)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $this->attachmentService = $attachmentService;
 | 
					        $this->attachmentService = $attachmentService;
 | 
				
			||||||
| 
						 | 
					@ -61,7 +46,7 @@ class AttachmentApiController extends ApiController
 | 
				
			||||||
    public function create(Request $request)
 | 
					    public function create(Request $request)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $this->checkPermission('attachment-create-all');
 | 
					        $this->checkPermission('attachment-create-all');
 | 
				
			||||||
        $requestData = $this->validate($request, $this->rules['create']);
 | 
					        $requestData = $this->validate($request, $this->rules()['create']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $pageId = $request->get('uploaded_to');
 | 
					        $pageId = $request->get('uploaded_to');
 | 
				
			||||||
        $page = Page::visible()->findOrFail($pageId);
 | 
					        $page = Page::visible()->findOrFail($pageId);
 | 
				
			||||||
| 
						 | 
					@ -122,7 +107,7 @@ class AttachmentApiController extends ApiController
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function update(Request $request, string $id)
 | 
					    public function update(Request $request, string $id)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $requestData = $this->validate($request, $this->rules['update']);
 | 
					        $requestData = $this->validate($request, $this->rules()['update']);
 | 
				
			||||||
        /** @var Attachment $attachment */
 | 
					        /** @var Attachment $attachment */
 | 
				
			||||||
        $attachment = Attachment::visible()->findOrFail($id);
 | 
					        $attachment = Attachment::visible()->findOrFail($id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -162,4 +147,22 @@ class AttachmentApiController extends ApiController
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return response('', 204);
 | 
					        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'],
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,6 +9,7 @@ use BookStack\Uploads\Attachment;
 | 
				
			||||||
use BookStack\Uploads\AttachmentService;
 | 
					use BookStack\Uploads\AttachmentService;
 | 
				
			||||||
use Exception;
 | 
					use Exception;
 | 
				
			||||||
use Illuminate\Contracts\Filesystem\FileNotFoundException;
 | 
					use Illuminate\Contracts\Filesystem\FileNotFoundException;
 | 
				
			||||||
 | 
					use Illuminate\Foundation\Http\Middleware\ValidatePostSize;
 | 
				
			||||||
use Illuminate\Http\Request;
 | 
					use Illuminate\Http\Request;
 | 
				
			||||||
use Illuminate\Support\MessageBag;
 | 
					use Illuminate\Support\MessageBag;
 | 
				
			||||||
use Illuminate\Validation\ValidationException;
 | 
					use Illuminate\Validation\ValidationException;
 | 
				
			||||||
| 
						 | 
					@ -37,7 +38,7 @@ class AttachmentController extends Controller
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $this->validate($request, [
 | 
					        $this->validate($request, [
 | 
				
			||||||
            'uploaded_to' => ['required', 'integer', 'exists:pages,id'],
 | 
					            'uploaded_to' => ['required', 'integer', 'exists:pages,id'],
 | 
				
			||||||
            'file'        => ['required', 'file'],
 | 
					            'file'        => array_merge(['required'], $this->attachmentService->getFileValidationRules()),
 | 
				
			||||||
        ]);
 | 
					        ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $pageId = $request->get('uploaded_to');
 | 
					        $pageId = $request->get('uploaded_to');
 | 
				
			||||||
| 
						 | 
					@ -65,7 +66,7 @@ class AttachmentController extends Controller
 | 
				
			||||||
    public function uploadUpdate(Request $request, $attachmentId)
 | 
					    public function uploadUpdate(Request $request, $attachmentId)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $this->validate($request, [
 | 
					        $this->validate($request, [
 | 
				
			||||||
            'file' => ['required', 'file'],
 | 
					            'file' => array_merge(['required'], $this->attachmentService->getFileValidationRules()),
 | 
				
			||||||
        ]);
 | 
					        ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /** @var Attachment $attachment */
 | 
					        /** @var Attachment $attachment */
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -165,7 +165,7 @@ abstract class Controller extends BaseController
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Log an activity in the system.
 | 
					     * Log an activity in the system.
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * @param $detail string|Loggable
 | 
					     * @param string|Loggable $detail
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    protected function logActivity(string $type, $detail = ''): void
 | 
					    protected function logActivity(string $type, $detail = ''): void
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
| 
						 | 
					@ -177,6 +177,6 @@ abstract class Controller extends BaseController
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    protected function getImageValidationRules(): array
 | 
					    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)];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -233,4 +233,12 @@ class AttachmentService
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return $attachmentPath;
 | 
					        return $attachmentPath;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Get the file validation rules for attachments.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function getFileValidationRules(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return ['file', 'max:' . (config('app.upload_limit') * 1000)];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,6 +11,7 @@ class Dropzone {
 | 
				
			||||||
        this.url = this.$opts.url;
 | 
					        this.url = this.$opts.url;
 | 
				
			||||||
        this.successMessage = this.$opts.successMessage;
 | 
					        this.successMessage = this.$opts.successMessage;
 | 
				
			||||||
        this.removeMessage = this.$opts.removeMessage;
 | 
					        this.removeMessage = this.$opts.removeMessage;
 | 
				
			||||||
 | 
					        this.uploadLimit = Number(this.$opts.uploadLimit);
 | 
				
			||||||
        this.uploadLimitMessage = this.$opts.uploadLimitMessage;
 | 
					        this.uploadLimitMessage = this.$opts.uploadLimitMessage;
 | 
				
			||||||
        this.timeoutMessage = this.$opts.timeoutMessage;
 | 
					        this.timeoutMessage = this.$opts.timeoutMessage;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -19,7 +20,7 @@ class Dropzone {
 | 
				
			||||||
            addRemoveLinks: true,
 | 
					            addRemoveLinks: true,
 | 
				
			||||||
            dictRemoveFile: this.removeMessage,
 | 
					            dictRemoveFile: this.removeMessage,
 | 
				
			||||||
            timeout: Number(window.uploadTimeout) || 60000,
 | 
					            timeout: Number(window.uploadTimeout) || 60000,
 | 
				
			||||||
            maxFilesize: Number(window.uploadLimit) || 256,
 | 
					            maxFilesize: this.uploadLimit,
 | 
				
			||||||
            url: this.url,
 | 
					            url: this.url,
 | 
				
			||||||
            withCredentials: true,
 | 
					            withCredentials: true,
 | 
				
			||||||
            init() {
 | 
					            init() {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,6 +7,7 @@
 | 
				
			||||||
     option:dropzone:url="{{ $url }}"
 | 
					     option:dropzone:url="{{ $url }}"
 | 
				
			||||||
     option:dropzone:success-message="{{ $successMessage ?? '' }}"
 | 
					     option:dropzone:success-message="{{ $successMessage ?? '' }}"
 | 
				
			||||||
     option:dropzone:remove-message="{{ trans('components.image_upload_remove') }}"
 | 
					     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:upload-limit-message="{{ trans('errors.server_upload_limit') }}"
 | 
				
			||||||
     option:dropzone:timeout-message="{{ trans('errors.file_upload_timeout') }}"
 | 
					     option:dropzone:timeout-message="{{ trans('errors.file_upload_timeout') }}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue