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.
 | 
			
		||||
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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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'];
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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'],
 | 
			
		||||
            ],
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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() {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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') }}"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue