From e4b6c4d5e490b4af6f7ebf63ef99ceddb35697c3 Mon Sep 17 00:00:00 2001 From: Alex Pott <alex.a.pott@googlemail.com> Date: Wed, 4 Nov 2015 13:35:04 +0000 Subject: [PATCH] Issue #2594565 by klausi, marthinal, dawehner, Berdir, yched: File extension + max_filesize should be validated using a Constraint on file fields --- .../src/Plugin/Field/FieldType/FileItem.php | 2 +- .../Plugin/Field/FieldWidget/FileWidget.php | 14 ++- .../Constraint/FileValidationConstraint.php | 22 ++++ .../FileValidationConstraintValidator.php | 34 +++++ .../file/src/Tests/FileFieldValidateTest.php | 31 +++++ .../src/Kernel/FileItemValidationTest.php | 117 ++++++++++++++++++ .../src/Plugin/Field/FieldType/ImageItem.php | 2 +- 7 files changed, 219 insertions(+), 3 deletions(-) create mode 100644 core/modules/file/src/Plugin/Validation/Constraint/FileValidationConstraint.php create mode 100644 core/modules/file/src/Plugin/Validation/Constraint/FileValidationConstraintValidator.php create mode 100644 core/modules/file/tests/src/Kernel/FileItemValidationTest.php diff --git a/core/modules/file/src/Plugin/Field/FieldType/FileItem.php b/core/modules/file/src/Plugin/Field/FieldType/FileItem.php index 1633147b2b4e..65f4f39d7115 100644 --- a/core/modules/file/src/Plugin/Field/FieldType/FileItem.php +++ b/core/modules/file/src/Plugin/Field/FieldType/FileItem.php @@ -28,7 +28,7 @@ * default_widget = "file_generic", * default_formatter = "file_default", * list_class = "\Drupal\file\Plugin\Field\FieldType\FileFieldItemList", - * constraints = {"ValidReference" = {}, "ReferenceAccess" = {}} + * constraints = {"ValidReference" = {}, "ReferenceAccess" = {}, "FileValidation" = {}} * ) */ class FileItem extends EntityReferenceItem { diff --git a/core/modules/file/src/Plugin/Field/FieldWidget/FileWidget.php b/core/modules/file/src/Plugin/Field/FieldWidget/FileWidget.php index bd511c8cf267..6634a5f20170 100644 --- a/core/modules/file/src/Plugin/Field/FieldWidget/FileWidget.php +++ b/core/modules/file/src/Plugin/Field/FieldWidget/FileWidget.php @@ -18,8 +18,9 @@ use Drupal\Core\Render\Element; use Drupal\Core\Render\ElementInfoManagerInterface; use Drupal\file\Element\ManagedFile; -use Symfony\Component\DependencyInjection\ContainerInterface; use Drupal\file\Entity\File; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\Validator\ConstraintViolationListInterface; /** * Plugin implementation of the 'file_generic' widget. @@ -575,4 +576,15 @@ public static function submit($form, FormStateInterface $form_state) { static::setWidgetState($parents, $field_name, $form_state, $field_state); } + /** + * {@inheritdoc} + */ + public function flagErrors(FieldItemListInterface $items, ConstraintViolationListInterface $violations, array $form, FormStateInterface $form_state) { + // Never flag validation errors for the remove button. + $clicked_button = end($form_state->getTriggeringElement()['#parents']); + if ($clicked_button !== 'remove_button') { + parent::flagErrors($items, $violations, $form, $form_state); + } + } + } diff --git a/core/modules/file/src/Plugin/Validation/Constraint/FileValidationConstraint.php b/core/modules/file/src/Plugin/Validation/Constraint/FileValidationConstraint.php new file mode 100644 index 000000000000..3b14feea017b --- /dev/null +++ b/core/modules/file/src/Plugin/Validation/Constraint/FileValidationConstraint.php @@ -0,0 +1,22 @@ +<?php + +/** + * @file + * Contains \Drupal\file\Plugin\Validation\Constraint\FileValidationConstraint. + */ + +namespace Drupal\file\Plugin\Validation\Constraint; + +use Symfony\Component\Validator\Constraint; + +/** + * Validation File constraint. + * + * @Constraint( + * id = "FileValidation", + * label = @Translation("File Validation", context = "Validation") + * ) + */ +class FileValidationConstraint extends Constraint { + +} diff --git a/core/modules/file/src/Plugin/Validation/Constraint/FileValidationConstraintValidator.php b/core/modules/file/src/Plugin/Validation/Constraint/FileValidationConstraintValidator.php new file mode 100644 index 000000000000..d4f48834300f --- /dev/null +++ b/core/modules/file/src/Plugin/Validation/Constraint/FileValidationConstraintValidator.php @@ -0,0 +1,34 @@ +<?php + +/** + * @file + * Contains \Drupal\file\Plugin\Validation\Constraint\FileValidationConstraintValidator. + */ + +namespace Drupal\file\Plugin\Validation\Constraint; + +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; + +/** + * Checks that a file referenced in a file field is valid. + */ +class FileValidationConstraintValidator extends ConstraintValidator { + + /** + * {@inheritdoc} + */ + public function validate($value, Constraint $constraint) { + // Get the file to execute validators. + $file = $value->get('entity')->getTarget()->getValue(); + // Get the validators. + $validators = $value->getUploadValidators(); + // Checks that a file meets the criteria specified by the validators. + if ($errors = file_validate($file, $validators)) { + foreach ($errors as $error) { + $this->context->addViolation($error); + } + } + } + +} diff --git a/core/modules/file/src/Tests/FileFieldValidateTest.php b/core/modules/file/src/Tests/FileFieldValidateTest.php index 291c43297795..12fe9255261f 100644 --- a/core/modules/file/src/Tests/FileFieldValidateTest.php +++ b/core/modules/file/src/Tests/FileFieldValidateTest.php @@ -159,4 +159,35 @@ function testFileExtension() { $this->assertFileEntryExists($node_file, 'File entry exists after uploading a file with extension checking.'); } + /** + * Checks that a file can always be removed if it does not pass validation. + */ + public function testFileRemoval() { + $node_storage = $this->container->get('entity.manager')->getStorage('node'); + $type_name = 'article'; + $field_name = 'file_test'; + $this->createFileField($field_name, 'node', $type_name); + + $test_file = $this->getTestFile('image'); + + // Disable extension checking. + $this->updateFileField($field_name, $type_name, array('file_extensions' => '')); + + // Check that the file can be uploaded with no extension checking. + $nid = $this->uploadNodeFile($test_file, $field_name, $type_name); + $node_storage->resetCache(array($nid)); + $node = $node_storage->load($nid); + $node_file = File::load($node->{$field_name}->target_id); + $this->assertFileExists($node_file, 'File exists after uploading a file with no extension checking.'); + $this->assertFileEntryExists($node_file, 'File entry exists after uploading a file with no extension checking.'); + + // Enable extension checking for text files. + $this->updateFileField($field_name, $type_name, array('file_extensions' => 'txt')); + + // Check that the file can still be removed. + $this->removeNodeFile($nid); + $this->assertNoText('Only files with the following extensions are allowed: txt.'); + $this->assertText('Article ' . $node->getTitle() . ' has been updated.'); + } + } diff --git a/core/modules/file/tests/src/Kernel/FileItemValidationTest.php b/core/modules/file/tests/src/Kernel/FileItemValidationTest.php new file mode 100644 index 000000000000..cc83b23e399b --- /dev/null +++ b/core/modules/file/tests/src/Kernel/FileItemValidationTest.php @@ -0,0 +1,117 @@ +<?php + +/** + * @file + * Contains \Drupal\Tests\file\Kernel\FileItemValidationTest. + */ + +namespace Drupal\Tests\file\Kernel; + +use Drupal\entity_test\Entity\EntityTest; +use Drupal\field\Entity\FieldConfig; +use Drupal\field\Entity\FieldStorageConfig; +use Drupal\file\Entity\File; +use Drupal\KernelTests\KernelTestBase; +use Drupal\user\Entity\User; +use org\bovigo\vfs\vfsStream; + +/** + * Tests that files referenced in file and image fields are always validated. + * + * @group file + */ +class FileItemValidationTest extends KernelTestBase { + + /** + * {@inheritdoc} + */ + public static $modules = ['file', 'image', 'entity_test', 'field', 'user', 'system']; + + /** + * A user. + * + * @var \Drupal\user\UserInterface + */ + protected $user; + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + $this->installEntitySchema('user'); + $this->installEntitySchema('file'); + $this->installSchema('file', 'file_usage'); + $this->installSchema('system', 'sequences'); + + $this->user = User::create([ + 'name' => 'username', + ]); + $this->user->save(); + } + + /** + * @covers \Drupal\file\Plugin\Validation\Constraint\FileValidationConstraint + * @covers \Drupal\file\Plugin\Validation\Constraint\FileValidationConstraintValidator + * @dataProvider getFileTypes + */ + public function testFileValidationConstraint($file_type) { + $field_storage = FieldStorageConfig::create([ + 'field_name' => 'field_test_file', + 'entity_type' => 'entity_test', + 'type' => $file_type, + ]); + $field_storage->save(); + + $field = FieldConfig::create([ + 'field_name' => 'field_test_file', + 'entity_type' => 'entity_test', + 'bundle' => 'entity_test', + 'settings' => [ + 'max_filesize' => '2k', + 'file_extensions' => 'jpg|png', + ], + ]); + $field->save(); + + vfsStream::setup('drupal_root'); + vfsStream::create([ + 'sites' => [ + 'default' => [ + 'files' => [ + 'test.txt' => str_repeat('a', 3000), + ] + ] + ] + ]); + + // Test for max filesize. + $file = File::create([ + 'uri' => 'vfs://drupal_root/sites/default/files/test.txt', + ]); + $file->save(); + + $entity_test = EntityTest::create([ + 'uid' => $this->user->id(), + 'field_test_file' => [ + 'target_id' => $file->id(), + ] + ]); + $result = $entity_test->validate(); + $this->assertCount(2, $result); + + $this->assertEquals('field_test_file.0', $result->get(0)->getPropertyPath()); + $this->assertEquals('The file is <em class="placeholder">2.93 KB</em> exceeding the maximum file size of <em class="placeholder">2 KB</em>.', (string) $result->get(0)->getMessage()); + $this->assertEquals('field_test_file.0', $result->get(1)->getPropertyPath()); + $this->assertEquals('Only files with the following extensions are allowed: <em class="placeholder">jpg|png</em>.', (string) $result->get(1)->getMessage()); + } + + /** + * Provides a list of file types to test. + */ + public function getFileTypes() { + return [['file'], ['image']]; + } + +} diff --git a/core/modules/image/src/Plugin/Field/FieldType/ImageItem.php b/core/modules/image/src/Plugin/Field/FieldType/ImageItem.php index 442929fe7fee..ecaaf1afbe05 100644 --- a/core/modules/image/src/Plugin/Field/FieldType/ImageItem.php +++ b/core/modules/image/src/Plugin/Field/FieldType/ImageItem.php @@ -43,7 +43,7 @@ * }, * }, * list_class = "\Drupal\file\Plugin\Field\FieldType\FileFieldItemList", - * constraints = {"ValidReference" = {}, "ReferenceAccess" = {}} + * constraints = {"ValidReference" = {}, "ReferenceAccess" = {}, "FileValidation" = {}} * ) */ class ImageItem extends FileItem { -- GitLab