From d26ed81252ec997c266b41d3f3ce2c2c649f6fe0 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Thu, 9 Jul 2026 09:16:29 +0100 Subject: [PATCH] Tighten front-end form upload validation in `SubmitForm` action --- src/Forms/SubmitForm.php | 23 ++++++++++- tests/Tags/Form/FormUploadValidationTest.php | 40 +++++++++++++++++++- 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/src/Forms/SubmitForm.php b/src/Forms/SubmitForm.php index 34ba38595ac..eec2600e172 100644 --- a/src/Forms/SubmitForm.php +++ b/src/Forms/SubmitForm.php @@ -2,6 +2,7 @@ namespace Statamic\Forms; +use Facades\Statamic\Fields\Validator as FieldValidator; use Illuminate\Support\Traits\Localizable; use Illuminate\Validation\ValidationException; use Statamic\Contracts\Forms\Form; @@ -10,6 +11,7 @@ use Statamic\Exceptions\FormRestrictedException; use Statamic\Exceptions\SilentFormFailureException; use Statamic\Facades\Asset; +use Statamic\Facades\AssetContainer; use Statamic\Facades\Site; use Statamic\Forms\Logic\PageLogic; use Statamic\Rules\AllowedFile; @@ -214,7 +216,26 @@ private function extraRules($fields): array { return $fields->all() ->filter(fn ($field): bool => in_array($field->fieldtype()->handle(), ['assets', 'files'])) - ->mapWithKeys(fn ($field): array => [$field->handle().'.*' => ['file', new AllowedFile]]) + ->mapWithKeys(function ($field): array { + $rules = $field->fieldtype()->handle() === 'assets' + ? array_merge(['file', new AllowedFile], $this->assetContainerRules($field)) + : ['file', new AllowedFile($field->fieldtype()->config('allowed_extensions'))]; + + return [$field->handle().'.*' => $rules]; + }) + ->all(); + } + + private function assetContainerRules($field): array + { + $configured = $field->fieldtype()->config('container'); + + $container = $configured + ? AssetContainer::find($configured) + : (($containers = AssetContainer::all())->count() === 1 ? $containers->first() : null); + + return collect($container?->validationRules()) + ->map(fn ($rule) => FieldValidator::parse($rule)) ->all(); } diff --git a/tests/Tags/Form/FormUploadValidationTest.php b/tests/Tags/Form/FormUploadValidationTest.php index 681d8b6e989..a36f6d417bd 100644 --- a/tests/Tags/Form/FormUploadValidationTest.php +++ b/tests/Tags/Form/FormUploadValidationTest.php @@ -10,7 +10,7 @@ class FormUploadValidationTest extends FormTestCase { #[Test] - public function it_rejects_disallowed_extensions_uploaded_to_a_files_field() + public function it_enforces_default_allowed_extensions_on_a_files_field() { Storage::fake('local'); @@ -28,7 +28,7 @@ public function it_rejects_disallowed_extensions_uploaded_to_a_files_field() } #[Test] - public function it_allows_permitted_extensions_uploaded_to_a_files_field() + public function it_allows_uploads_permitted_by_the_default_allowed_extensions() { Storage::fake('local'); @@ -45,6 +45,42 @@ public function it_allows_permitted_extensions_uploaded_to_a_files_field() ->assertSessionHasNoErrors(); } + #[Test] + public function it_enforces_allowed_extensions_on_a_files_field() + { + Storage::fake('local'); + + $this->createForm([ + 'fields' => [ + ['handle' => 'document', 'field' => ['type' => 'files', 'allowed_extensions' => ['pdf']]], + ], + ], 'survey'); + + $this + ->post('/!/forms/survey', [ + 'document' => UploadedFile::fake()->create('notes.txt', 10), + ]) + ->assertSessionHasErrors('document.0', null, 'form.survey'); + } + + #[Test] + public function it_allows_uploads_permitted_by_the_allowed_extensions() + { + Storage::fake('local'); + + $this->createForm([ + 'fields' => [ + ['handle' => 'document', 'field' => ['type' => 'files', 'allowed_extensions' => ['pdf']]], + ], + ], 'survey'); + + $this + ->post('/!/forms/survey', [ + 'document' => UploadedFile::fake()->create('notes.pdf', 10), + ]) + ->assertSessionHasNoErrors(); + } + #[Test] public function it_enforces_asset_container_validation_rules_on_an_assets_field() {