Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit46e00d5

Browse files
committed
feature#58485 [Validator] AddfilenameCharset andfilenameCountUnit options toFile constraint (IssamRaouf)
This PR was merged into the 7.3 branch.Discussion----------[Validator] Add `filenameCharset` and `filenameCountUnit` options to `File` constraint| Q | A| ------------- | ---| Branch? | 7.3| Bug fix? | no| New feature? | yes| Deprecations? | no| Issues |Fix#58482| License | MITCommits-------abee6ae [Validator] Add `filenameCharset` and `filenameCountUnit` options to `File` constraint
2 parentscb93a4f +abee6ae commit46e00d5

File tree

6 files changed

+163
-15
lines changed

6 files changed

+163
-15
lines changed

‎src/Symfony/Component/Validator/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CHANGELOG
44
7.3
55
---
66

7+
* Add the`filenameCharset` and`filenameCountUnit` options to the`File` constraint
78
* Deprecate defining custom constraints not supporting named arguments
89
* Deprecate passing an array of options to the constructors of the constraint classes, pass each option as a dedicated argument instead
910
* Add support for ratio checks for SVG files to the`Image` constraint

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

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
useSymfony\Component\Validator\Attribute\HasNamedArguments;
1515
useSymfony\Component\Validator\Constraint;
1616
useSymfony\Component\Validator\Exception\ConstraintDefinitionException;
17+
useSymfony\Component\Validator\Exception\InvalidArgumentException;
1718

1819
/**
1920
* Validates that a value is a valid "file".
@@ -38,6 +39,17 @@ class File extends Constraint
3839
publicconstINVALID_MIME_TYPE_ERROR ='744f00bc-4389-4c74-92de-9a43cde55534';
3940
publicconstINVALID_EXTENSION_ERROR ='c8c7315c-6186-4719-8b71-5659e16bdcb7';
4041
publicconstFILENAME_TOO_LONG ='e5706483-91a8-49d8-9a59-5e81a3c634a8';
42+
publicconstFILENAME_INVALID_CHARACTERS ='04ee58e1-42b4-45c7-8423-8a4a145fedd9';
43+
44+
publicconstFILENAME_COUNT_BYTES ='bytes';
45+
publicconstFILENAME_COUNT_CODEPOINTS ='codepoints';
46+
publicconstFILENAME_COUNT_GRAPHEMES ='graphemes';
47+
48+
privateconstFILENAME_VALID_COUNT_UNITS = [
49+
self::FILENAME_COUNT_BYTES,
50+
self::FILENAME_COUNT_CODEPOINTS,
51+
self::FILENAME_COUNT_GRAPHEMES,
52+
];
4153

4254
protectedconstERROR_NAMES = [
4355
self::NOT_FOUND_ERROR =>'NOT_FOUND_ERROR',
@@ -47,19 +59,25 @@ class File extends Constraint
4759
self::INVALID_MIME_TYPE_ERROR =>'INVALID_MIME_TYPE_ERROR',
4860
self::INVALID_EXTENSION_ERROR =>'INVALID_EXTENSION_ERROR',
4961
self::FILENAME_TOO_LONG =>'FILENAME_TOO_LONG',
62+
self::FILENAME_INVALID_CHARACTERS =>'FILENAME_INVALID_CHARACTERS',
5063
];
5164

5265
public ?bool$binaryFormat =null;
5366
publicarray|string$mimeTypes = [];
5467
public ?int$filenameMaxLength =null;
5568
publicarray|string$extensions = [];
69+
public ?string$filenameCharset =null;
70+
/** @var self::FILENAME_COUNT_* */
71+
publicstring$filenameCountUnit =self::FILENAME_COUNT_BYTES;
72+
5673
publicstring$notFoundMessage ='The file could not be found.';
5774
publicstring$notReadableMessage ='The file is not readable.';
5875
publicstring$maxSizeMessage ='The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}.';
5976
publicstring$mimeTypesMessage ='The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}.';
6077
publicstring$extensionsMessage ='The extension of the file is invalid ({{ extension }}). Allowed extensions are {{ extensions }}.';
6178
publicstring$disallowEmptyMessage ='An empty file is not allowed.';
6279
publicstring$filenameTooLongMessage ='The filename is too long. It should have {{ filename_max_length }} character or less.|The filename is too long. It should have {{ filename_max_length }} characters or less.';
80+
publicstring$filenameCharsetMessage ='This filename does not match the expected charset.';
6381

6482
publicstring$uploadIniSizeErrorMessage ='The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}.';
6583
publicstring$uploadFormSizeErrorMessage ='The file is too large.';
@@ -87,6 +105,8 @@ class File extends Constraint
87105
* @param string|null $uploadErrorMessage Message if an unknown error occurred on upload
88106
* @param string[]|null $groups
89107
* @param array<string|string[]>|string|null $extensions A list of valid extensions to check. Related media types are also enforced ({@see https://symfony.com/doc/current/reference/constraints/File.html#extensions})
108+
* @param string|null $filenameCharset The charset to be used when computing filename length (defaults to null)
109+
* @param self::FILENAME_COUNT_*|null $filenameCountUnit The character count unit used for checking the filename length (defaults to {@see File::FILENAME_COUNT_BYTES})
90110
*
91111
* @see https://www.iana.org/assignments/media-types/media-types.xhtml Existing media types
92112
*/
@@ -114,9 +134,11 @@ public function __construct(
114134
?string$uploadErrorMessage =null,
115135
?array$groups =null,
116136
mixed$payload =null,
117-
118137
array|string|null$extensions =null,
119138
?string$extensionsMessage =null,
139+
?string$filenameCharset =null,
140+
?string$filenameCountUnit =null,
141+
?string$filenameCharsetMessage =null,
120142
) {
121143
if (\is_array($options)) {
122144
trigger_deprecation('symfony/validator','7.3','Passing an array of options to configure the "%s" constraint is deprecated, use named arguments instead.',static::class);
@@ -128,6 +150,8 @@ public function __construct(
128150
$this->binaryFormat =$binaryFormat ??$this->binaryFormat;
129151
$this->mimeTypes =$mimeTypes ??$this->mimeTypes;
130152
$this->filenameMaxLength =$filenameMaxLength ??$this->filenameMaxLength;
153+
$this->filenameCharset =$filenameCharset ??$this->filenameCharset;
154+
$this->filenameCountUnit =$filenameCountUnit ??$this->filenameCountUnit;
131155
$this->extensions =$extensions ??$this->extensions;
132156
$this->notFoundMessage =$notFoundMessage ??$this->notFoundMessage;
133157
$this->notReadableMessage =$notReadableMessage ??$this->notReadableMessage;
@@ -136,6 +160,7 @@ public function __construct(
136160
$this->extensionsMessage =$extensionsMessage ??$this->extensionsMessage;
137161
$this->disallowEmptyMessage =$disallowEmptyMessage ??$this->disallowEmptyMessage;
138162
$this->filenameTooLongMessage =$filenameTooLongMessage ??$this->filenameTooLongMessage;
163+
$this->filenameCharsetMessage =$filenameCharsetMessage ??$this->filenameCharsetMessage;
139164
$this->uploadIniSizeErrorMessage =$uploadIniSizeErrorMessage ??$this->uploadIniSizeErrorMessage;
140165
$this->uploadFormSizeErrorMessage =$uploadFormSizeErrorMessage ??$this->uploadFormSizeErrorMessage;
141166
$this->uploadPartialErrorMessage =$uploadPartialErrorMessage ??$this->uploadPartialErrorMessage;
@@ -148,6 +173,10 @@ public function __construct(
148173
if (null !==$this->maxSize) {
149174
$this->normalizeBinaryFormat($this->maxSize);
150175
}
176+
177+
if (!\in_array($this->filenameCountUnit,self::FILENAME_VALID_COUNT_UNITS,true)) {
178+
thrownewInvalidArgumentException(\sprintf('The "filenameCountUnit" option must be one of the "%s::FILENAME_COUNT_*" constants ("%s" given).',__CLASS__,$this->filenameCountUnit));
179+
}
151180
}
152181

153182
publicfunction__set(string$option,mixed$value):void

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

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,10 +137,36 @@ public function validate(mixed $value, Constraint $constraint): void
137137
return;
138138
}
139139

140-
$sizeInBytes =filesize($path);
141140
$basename =$valueinstanceof UploadedFile ?$value->getClientOriginalName() :basename($path);
141+
$filenameCharset =$constraint->filenameCharset ?? (File::FILENAME_COUNT_BYTES !==$constraint->filenameCountUnit ?'UTF-8' :null);
142+
143+
if ($invalidFilenameCharset =null !==$filenameCharset) {
144+
try {
145+
$invalidFilenameCharset = !@mb_check_encoding($basename,$constraint->filenameCharset);
146+
}catch (\ValueError$e) {
147+
if (!str_starts_with($e->getMessage(),'mb_check_encoding(): Argument #2 ($encoding) must be a valid encoding')) {
148+
throw$e;
149+
}
150+
}
151+
}
152+
153+
$filenameLength =$invalidFilenameCharset ?0 :match ($constraint->filenameCountUnit) {
154+
File::FILENAME_COUNT_BYTES =>\strlen($basename),
155+
File::FILENAME_COUNT_CODEPOINTS =>mb_strlen($basename,$filenameCharset),
156+
File::FILENAME_COUNT_GRAPHEMES =>grapheme_strlen($basename),
157+
};
158+
159+
if ($invalidFilenameCharset ||false === ($filenameLength ??false)) {
160+
$this->context->buildViolation($constraint->filenameCharsetMessage)
161+
->setParameter('{{ name }}',$this->formatValue($basename))
162+
->setParameter('{{ charset }}',$filenameCharset)
163+
->setCode(File::FILENAME_INVALID_CHARACTERS)
164+
->addViolation();
165+
166+
return;
167+
}
142168

143-
if ($constraint->filenameMaxLength &&$constraint->filenameMaxLength <$filenameLength =\strlen($basename)) {
169+
if ($constraint->filenameMaxLength &&$constraint->filenameMaxLength <$filenameLength) {
144170
$this->context->buildViolation($constraint->filenameTooLongMessage)
145171
->setParameter('{{ filename_max_length }}',$this->formatValue($constraint->filenameMaxLength))
146172
->setCode(File::FILENAME_TOO_LONG)
@@ -150,7 +176,7 @@ public function validate(mixed $value, Constraint $constraint): void
150176
return;
151177
}
152178

153-
if (0 ===$sizeInBytes) {
179+
if (!$sizeInBytes =filesize($path)) {
154180
$this->context->buildViolation($constraint->disallowEmptyMessage)
155181
->setParameter('{{ file }}',$this->formatValue($path))
156182
->setParameter('{{ name }}',$this->formatValue($basename))

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,9 @@ public function __construct(
165165
?string$corruptedMessage =null,
166166
?array$groups =null,
167167
mixed$payload =null,
168+
?string$filenameCharset =null,
169+
?string$filenameCountUnit =null,
170+
?string$filenameCharsetMessage =null,
168171
) {
169172
parent::__construct(
170173
$options,
@@ -187,7 +190,10 @@ public function __construct(
187190
$uploadExtensionErrorMessage,
188191
$uploadErrorMessage,
189192
$groups,
190-
$payload
193+
$payload,
194+
$filenameCharset,
195+
$filenameCountUnit,
196+
$filenameCharsetMessage,
191197
);
192198

193199
$this->minWidth =$minWidth ??$this->minWidth;

‎src/Symfony/Component/Validator/Tests/Constraints/FileTest.php

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
usePHPUnit\Framework\TestCase;
1515
useSymfony\Component\Validator\Constraints\File;
1616
useSymfony\Component\Validator\Exception\ConstraintDefinitionException;
17+
useSymfony\Component\Validator\Exception\InvalidArgumentException;
1718
useSymfony\Component\Validator\Mapping\ClassMetadata;
1819
useSymfony\Component\Validator\Mapping\Loader\AttributeLoader;
1920

@@ -79,6 +80,31 @@ public function testMaxSizeCannotBeSetToInvalidValueAfterInitialization($maxSize
7980
$this->assertSame(1000,$file->maxSize);
8081
}
8182

83+
publicfunctiontestFilenameMaxLength()
84+
{
85+
$file =newFile(filenameMaxLength:30);
86+
$this->assertSame(30,$file->filenameMaxLength);
87+
}
88+
89+
publicfunctiontestDefaultFilenameCountUnitIsUsed()
90+
{
91+
$file =newFile();
92+
self::assertSame(File::FILENAME_COUNT_BYTES,$file->filenameCountUnit);
93+
}
94+
95+
publicfunctiontestFilenameCharsetDefaultsToNull()
96+
{
97+
$file =newFile();
98+
self::assertNull($file->filenameCharset);
99+
}
100+
101+
publicfunctiontestInvalidFilenameCountUnitThrowsException()
102+
{
103+
self::expectException(InvalidArgumentException::class);
104+
self::expectExceptionMessage(\sprintf('The "filenameCountUnit" option must be one of the "%s::FILENAME_COUNT_*" constants ("%s" given).', File::class,'nonExistentCountUnit'));
105+
$file =newFile(filenameCountUnit:'nonExistentCountUnit');
106+
}
107+
82108
/**
83109
* @dataProvider provideInValidSizes
84110
*/
@@ -162,6 +188,9 @@ public function testAttributes()
162188
self::assertSame(100000,$cConstraint->maxSize);
163189
self::assertSame(['my_group'],$cConstraint->groups);
164190
self::assertSame('some attached data',$cConstraint->payload);
191+
self::assertSame(30,$cConstraint->filenameMaxLength);
192+
self::assertSame('ISO-8859-15',$cConstraint->filenameCharset);
193+
self::assertSame(File::FILENAME_COUNT_CODEPOINTS,$cConstraint->filenameCountUnit);
165194
}
166195
}
167196

@@ -173,6 +202,6 @@ class FileDummy
173202
#[File(maxSize:100, notFoundMessage:'myMessage')]
174203
private$b;
175204

176-
#[File(maxSize:'100K', groups: ['my_group'], payload:'some attached data')]
205+
#[File(maxSize:'100K',filenameMaxLength:30, filenameCharset:'ISO-8859-15', filenameCountUnit: File::FILENAME_COUNT_CODEPOINTS,groups: ['my_group'], payload:'some attached data')]
177206
private$c;
178207
}

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

Lines changed: 66 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -675,11 +675,11 @@ public function testUploadedFileExtensions()
675675
/**
676676
* @dataProvider provideFilenameMaxLengthIsTooLong
677677
*/
678-
publicfunctiontestFilenameMaxLengthIsTooLong(File$constraintFile,string$messageViolation)
678+
publicfunctiontestFilenameMaxLengthIsTooLong(File$constraintFile,string$filename,string$messageViolation)
679679
{
680680
file_put_contents($this->path,'1');
681681

682-
$file =newUploadedFile($this->path,'myFileWithATooLongOriginalFileName',null,null,true);
682+
$file =newUploadedFile($this->path,$filename,null,null,true);
683683
$this->validator->validate($file,$constraintFile);
684684

685685
$this->buildViolation($messageViolation)
@@ -693,26 +693,83 @@ public function testFilenameMaxLengthIsTooLong(File $constraintFile, string $mes
693693

694694
publicstaticfunctionprovideFilenameMaxLengthIsTooLong():\Generator
695695
{
696-
yield'Simple case with only the parameter "filenameMaxLength"' => [
696+
yield'Codepoints and UTF-8 : default' => [
697697
newFile(filenameMaxLength:30),
698+
'myFileWithATooLongOriginalFileName',
698699
'The filename is too long. It should have {{ filename_max_length }} character or less.|The filename is too long. It should have {{ filename_max_length }} characters or less.',
699700
];
700701

701-
yield'Case with the parameter "filenameMaxLength" and a custom error message' => [
702-
newFile(filenameMaxLength:20, filenameTooLongMessage:'Your filename is too long. Please use at maximum {{ filename_max_length }} characters'),
703-
'Your filename is too long. Please use at maximum {{ filename_max_length }} characters',
702+
yield'Codepoints and UTF-8: custom error message' => [
703+
newFile(filenameMaxLength:20, filenameTooLongMessage:'myMessage'),
704+
'myFileWithATooLongOriginalFileName',
705+
'myMessage',
706+
];
707+
708+
yield'Graphemes' => [
709+
newFile(filenameMaxLength:1, filenameCountUnit: File::FILENAME_COUNT_GRAPHEMES, filenameTooLongMessage:'myMessage'),
710+
"A\u{0300}A\u{0300}",
711+
'myMessage',
712+
];
713+
714+
yield'Bytes' => [
715+
newFile(filenameMaxLength:5, filenameCountUnit: File::FILENAME_COUNT_BYTES, filenameTooLongMessage:'myMessage'),
716+
"A\u{0300}A\u{0300}",
717+
'myMessage',
704718
];
705719
}
706720

707-
publicfunctiontestFilenameMaxLength()
721+
/**
722+
* @dataProvider provideFilenameCountUnit
723+
*/
724+
publicfunctiontestValidCountUnitFilenameMaxLength(int$maxLength,string$countUnit)
708725
{
709726
file_put_contents($this->path,'1');
710727

711-
$file =newUploadedFile($this->path,'tinyOriginalFileName',null,null,true);
712-
$this->validator->validate($file,newFile(filenameMaxLength:20));
728+
$file =newUploadedFile($this->path,"A\u{0300}",null,null,true);
729+
$this->validator->validate($file,newFile(filenameMaxLength:$maxLength, filenameCountUnit:$countUnit));
713730

714731
$this->assertNoViolation();
715732
}
716733

734+
/**
735+
* @dataProvider provideFilenameCharset
736+
*/
737+
publicfunctiontestFilenameCharset(string$filename,string$charset,bool$isValid)
738+
{
739+
file_put_contents($this->path,'1');
740+
741+
$file =newUploadedFile($this->path,$filename,null,null,true);
742+
$this->validator->validate($file,newFile(filenameCharset:$charset, filenameCharsetMessage:'myMessage'));
743+
744+
if ($isValid) {
745+
$this->assertNoViolation();
746+
}else {
747+
$this->buildViolation('myMessage')
748+
->setParameter('{{ name }}','"'.$filename.'"')
749+
->setParameter('{{ charset }}',$charset)
750+
->setCode(File::FILENAME_INVALID_CHARACTERS)
751+
->assertRaised();
752+
}
753+
}
754+
755+
publicstaticfunctionprovideFilenameCountUnit():array
756+
{
757+
return [
758+
'graphemes' => [1, File::FILENAME_COUNT_GRAPHEMES],
759+
'codepoints' => [2, File::FILENAME_COUNT_CODEPOINTS],
760+
'bytes' => [3, File::FILENAME_COUNT_BYTES],
761+
];
762+
}
763+
764+
publicstaticfunctionprovideFilenameCharset():array
765+
{
766+
return [
767+
['é','utf8',true],
768+
["\xE9",'CP1252',true],
769+
["\xE9",'XXX',false],
770+
["\xE9",'utf8',false],
771+
];
772+
}
773+
717774
abstractprotectedfunctiongetFile($filename);
718775
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp