Skip to content

Commit c3aaf11

Browse files
author
Kevin Auvinet
committed
Add the option filenameMaxLength to the File constraint
1 parent a90a733 commit c3aaf11

File tree

5 files changed

+73
-4
lines changed

5 files changed

+73
-4
lines changed

src/Symfony/Component/Validator/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ CHANGELOG
77
* Add method `getConstraint()` to `ConstraintViolationInterface`
88
* Add `Uuid::TIME_BASED_VERSIONS` to match that a UUID being validated embeds a timestamp
99
* Add the `pattern` parameter in violations of the `Regex` constraint
10+
* Add the `filenameMaxLength` option to the `File` constraint
1011

1112
6.2
1213
---

src/Symfony/Component/Validator/Constraints/File.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
/**
1818
* @Annotation
19+
*
1920
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
2021
*
2122
* @property int $maxSize
@@ -33,13 +34,15 @@ class File extends Constraint
3334
public const TOO_LARGE_ERROR = 'df8637af-d466-48c6-a59d-e7126250a654';
3435
public const INVALID_MIME_TYPE_ERROR = '744f00bc-4389-4c74-92de-9a43cde55534';
3536
public const INVALID_EXTENSION_ERROR = 'c8c7315c-6186-4719-8b71-5659e16bdcb7';
37+
public const FILENAME_TOO_LONG = 'e5706483-91a8-49d8-9a59-5e81a3c634a8';
3638

3739
protected const ERROR_NAMES = [
3840
self::NOT_FOUND_ERROR => 'NOT_FOUND_ERROR',
3941
self::NOT_READABLE_ERROR => 'NOT_READABLE_ERROR',
4042
self::EMPTY_ERROR => 'EMPTY_ERROR',
4143
self::TOO_LARGE_ERROR => 'TOO_LARGE_ERROR',
4244
self::INVALID_MIME_TYPE_ERROR => 'INVALID_MIME_TYPE_ERROR',
45+
self::FILENAME_TOO_LONG => 'FILENAME_TOO_LONG',
4346
];
4447

4548
/**
@@ -49,13 +52,15 @@ class File extends Constraint
4952

5053
public $binaryFormat;
5154
public $mimeTypes = [];
55+
public ?int $nameMaxLength = null;
5256
public array|string|null $extensions = [];
5357
public $notFoundMessage = 'The file could not be found.';
5458
public $notReadableMessage = 'The file is not readable.';
5559
public $maxSizeMessage = 'The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}.';
5660
public $mimeTypesMessage = 'The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}.';
5761
public string $extensionsMessage = 'The extension of the file is invalid ({{ extension }}). Allowed extensions are {{ extensions }}.';
5862
public $disallowEmptyMessage = 'An empty file is not allowed.';
63+
public $nameTooLongMessage = 'The filename is too long. It should have {{ name_max_length }} character or less.';
5964

6065
public $uploadIniSizeErrorMessage = 'The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}.';
6166
public $uploadFormSizeErrorMessage = 'The file is too large.';
@@ -76,11 +81,13 @@ public function __construct(
7681
int|string $maxSize = null,
7782
bool $binaryFormat = null,
7883
array|string $mimeTypes = null,
84+
int $nameMaxLength = null,
7985
string $notFoundMessage = null,
8086
string $notReadableMessage = null,
8187
string $maxSizeMessage = null,
8288
string $mimeTypesMessage = null,
8389
string $disallowEmptyMessage = null,
90+
string $nameTooLongMessage = null,
8491

8592
string $uploadIniSizeErrorMessage = null,
8693
string $uploadFormSizeErrorMessage = null,
@@ -101,13 +108,15 @@ public function __construct(
101108
$this->maxSize = $maxSize ?? $this->maxSize;
102109
$this->binaryFormat = $binaryFormat ?? $this->binaryFormat;
103110
$this->mimeTypes = $mimeTypes ?? $this->mimeTypes;
111+
$this->nameMaxLength = $nameMaxLength ?? $this->nameMaxLength;
104112
$this->extensions = $extensions ?? $this->extensions;
105113
$this->notFoundMessage = $notFoundMessage ?? $this->notFoundMessage;
106114
$this->notReadableMessage = $notReadableMessage ?? $this->notReadableMessage;
107115
$this->maxSizeMessage = $maxSizeMessage ?? $this->maxSizeMessage;
108116
$this->mimeTypesMessage = $mimeTypesMessage ?? $this->mimeTypesMessage;
109117
$this->extensionsMessage = $extensionsMessage ?? $this->extensionsMessage;
110118
$this->disallowEmptyMessage = $disallowEmptyMessage ?? $this->disallowEmptyMessage;
119+
$this->nameTooLongMessage = $nameTooLongMessage ?? $this->nameTooLongMessage;
111120
$this->uploadIniSizeErrorMessage = $uploadIniSizeErrorMessage ?? $this->uploadIniSizeErrorMessage;
112121
$this->uploadFormSizeErrorMessage = $uploadFormSizeErrorMessage ?? $this->uploadFormSizeErrorMessage;
113122
$this->uploadPartialErrorMessage = $uploadPartialErrorMessage ?? $this->uploadPartialErrorMessage;

src/Symfony/Component/Validator/Constraints/FileValidator.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,19 @@ public function validate(mixed $value, Constraint $constraint)
143143
$sizeInBytes = filesize($path);
144144
$basename = $value instanceof UploadedFile ? $value->getClientOriginalName() : basename($path);
145145

146+
if ($constraint->nameMaxLength) {
147+
$filenameLength = \strlen($basename);
148+
149+
if ($filenameLength > $constraint->nameMaxLength) {
150+
$this->context->buildViolation($constraint->nameTooLongMessage)
151+
->setParameter('{{ name_max_length }}', $this->formatValue($constraint->nameMaxLength))
152+
->setCode(File::FILENAME_TOO_LONG)
153+
->addViolation();
154+
155+
return;
156+
}
157+
}
158+
146159
if (0 === $sizeInBytes) {
147160
$this->context->buildViolation($constraint->disallowEmptyMessage)
148161
->setParameter('{{ file }}', $this->formatValue($path))

src/Symfony/Component/Validator/Constraints/Image.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
/**
1515
* @Annotation
16+
*
1617
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
1718
*
1819
* @author Benjamin Dulau <benjamin.dulau@gmail.com>
@@ -98,6 +99,7 @@ public function __construct(
9899
int|string $maxSize = null,
99100
bool $binaryFormat = null,
100101
array $mimeTypes = null,
102+
int $nameMaxLength = null,
101103
int $minWidth = null,
102104
int $maxWidth = null,
103105
int $maxHeight = null,
@@ -115,6 +117,7 @@ public function __construct(
115117
string $maxSizeMessage = null,
116118
string $mimeTypesMessage = null,
117119
string $disallowEmptyMessage = null,
120+
string $filenameTooLongMessage = null,
118121
string $uploadIniSizeErrorMessage = null,
119122
string $uploadFormSizeErrorMessage = null,
120123
string $uploadPartialErrorMessage = null,
@@ -144,11 +147,13 @@ public function __construct(
144147
$maxSize,
145148
$binaryFormat,
146149
$mimeTypes,
150+
$nameMaxLength,
147151
$notFoundMessage,
148152
$notReadableMessage,
149153
$maxSizeMessage,
150154
$mimeTypesMessage,
151155
$disallowEmptyMessage,
156+
$filenameTooLongMessage,
152157
$uploadIniSizeErrorMessage,
153158
$uploadFormSizeErrorMessage,
154159
$uploadPartialErrorMessage,

src/Symfony/Component/Validator/Tests/Constraints/FileValidatorTestCase.php

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -531,7 +531,7 @@ public function testNegativeMaxSize()
531531
}
532532

533533
/**
534-
* @dataProvider validExtensionProvider
534+
* @dataProvider providerValidExtension
535535
*/
536536
public function testExtensionValid(string $name)
537537
{
@@ -551,7 +551,7 @@ public function testExtensionValid(string $name)
551551
$this->assertNoViolation();
552552
}
553553

554-
private static function validExtensionProvider(): iterable
554+
public static function providerValidExtension(): iterable
555555
{
556556
yield ['test.gif'];
557557
yield ['test.png.gif'];
@@ -560,7 +560,7 @@ private static function validExtensionProvider(): iterable
560560
}
561561

562562
/**
563-
* @dataProvider invalidExtensionProvider
563+
* @dataProvider provideInvalidExtension
564564
*/
565565
public function testExtensionInvalid(string $name, string $extension)
566566
{
@@ -582,7 +582,7 @@ public function testExtensionInvalid(string $name, string $extension)
582582
->assertRaised();
583583
}
584584

585-
private static function invalidExtensionProvider(): iterable
585+
public static function provideInvalidExtension(): iterable
586586
{
587587
yield ['test.gif', 'gif'];
588588
yield ['test.png.gif', 'gif'];
@@ -658,5 +658,46 @@ public function testUploadedFileExtensions()
658658
$this->assertNoViolation();
659659
}
660660

661+
/**
662+
* @dataProvider provideFilenameMaxLengthIsTooLong
663+
*/
664+
public function testFilenameMaxLengthIsTooLong(File $constraintFile, string $messageViolation)
665+
{
666+
file_put_contents($this->path, '1');
667+
668+
$file = new UploadedFile($this->path, 'myFileWithATooLongOriginalFileName', null, null, true);
669+
$this->validator->validate($file, $constraintFile);
670+
671+
$this->buildViolation($messageViolation)
672+
->setParameters([
673+
'{{ name_max_length }}' => $constraintFile->nameMaxLength,
674+
])
675+
->setCode(File::FILENAME_TOO_LONG)
676+
->assertRaised();
677+
}
678+
679+
public static function provideFilenameMaxLengthIsTooLong(): \Generator
680+
{
681+
yield 'Simple case with only the parameter "filenameMaxLength" ' => [
682+
new File(nameMaxLength: 30),
683+
'The filename is too long. It should have {{ name_max_length }} character or less.',
684+
];
685+
686+
yield 'Case with the parameter "filenameMaxLength" and a custom error message' => [
687+
new File(nameMaxLength: 20, nameTooLongMessage: 'Your filename is too long. Please use at maximum {{ name_max_length }} characters'),
688+
'Your filename is too long. Please use at maximum {{ name_max_length }} characters',
689+
];
690+
}
691+
692+
public function testFilenameMaxLength()
693+
{
694+
file_put_contents($this->path, '1');
695+
696+
$file = new UploadedFile($this->path, 'tinyOriginalFileName', null, null, true);
697+
$this->validator->validate($file, new File(nameMaxLength: 20));
698+
699+
$this->assertNoViolation();
700+
}
701+
661702
abstract protected function getFile($filename);
662703
}

0 commit comments

Comments
 (0)