Skip to content

Navigation Menu

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

Appearance settings

Commit c4ad092

Browse filesBrowse files
maximecolinfabpot
authored andcommitted
[Validator] Validate SVG ratio in Image validator
1 parent 0a9eb28 commit c4ad092
Copy full SHA for c4ad092

10 files changed

+218
-7
lines changed

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Validator/CHANGELOG.md
+5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
7.3
5+
---
6+
7+
* Add support for ratio checks for SVG files to the `Image` constraint
8+
49
7.2
510
---
611

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Validator/Constraints/ImageValidator.php
+63-7
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
namespace Symfony\Component\Validator\Constraints;
1313

14+
use Symfony\Component\HttpFoundation\File\File;
15+
use Symfony\Component\Mime\MimeTypes;
1416
use Symfony\Component\Validator\Constraint;
1517
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
1618
use Symfony\Component\Validator\Exception\LogicException;
@@ -50,7 +52,13 @@ public function validate(mixed $value, Constraint $constraint): void
5052
return;
5153
}
5254

53-
$size = @getimagesize($value);
55+
$isSvg = $this->isSvg($value);
56+
57+
if ($isSvg) {
58+
$size = $this->getSvgSize($value);
59+
} else {
60+
$size = @getimagesize($value);
61+
}
5462

5563
if (!$size || (0 === $size[0]) || (0 === $size[1])) {
5664
$this->context->buildViolation($constraint->sizeNotDetectedMessage)
@@ -63,7 +71,7 @@ public function validate(mixed $value, Constraint $constraint): void
6371
$width = $size[0];
6472
$height = $size[1];
6573

66-
if ($constraint->minWidth) {
74+
if (!$isSvg && $constraint->minWidth) {
6775
if (!ctype_digit((string) $constraint->minWidth)) {
6876
throw new ConstraintDefinitionException(\sprintf('"%s" is not a valid minimum width.', $constraint->minWidth));
6977
}
@@ -79,7 +87,7 @@ public function validate(mixed $value, Constraint $constraint): void
7987
}
8088
}
8189

82-
if ($constraint->maxWidth) {
90+
if (!$isSvg && $constraint->maxWidth) {
8391
if (!ctype_digit((string) $constraint->maxWidth)) {
8492
throw new ConstraintDefinitionException(\sprintf('"%s" is not a valid maximum width.', $constraint->maxWidth));
8593
}
@@ -95,7 +103,7 @@ public function validate(mixed $value, Constraint $constraint): void
95103
}
96104
}
97105

98-
if ($constraint->minHeight) {
106+
if (!$isSvg && $constraint->minHeight) {
99107
if (!ctype_digit((string) $constraint->minHeight)) {
100108
throw new ConstraintDefinitionException(\sprintf('"%s" is not a valid minimum height.', $constraint->minHeight));
101109
}
@@ -111,7 +119,7 @@ public function validate(mixed $value, Constraint $constraint): void
111119
}
112120
}
113121

114-
if ($constraint->maxHeight) {
122+
if (!$isSvg && $constraint->maxHeight) {
115123
if (!ctype_digit((string) $constraint->maxHeight)) {
116124
throw new ConstraintDefinitionException(\sprintf('"%s" is not a valid maximum height.', $constraint->maxHeight));
117125
}
@@ -127,7 +135,7 @@ public function validate(mixed $value, Constraint $constraint): void
127135

128136
$pixels = $width * $height;
129137

130-
if (null !== $constraint->minPixels) {
138+
if (!$isSvg && null !== $constraint->minPixels) {
131139
if (!ctype_digit((string) $constraint->minPixels)) {
132140
throw new ConstraintDefinitionException(\sprintf('"%s" is not a valid minimum amount of pixels.', $constraint->minPixels));
133141
}
@@ -143,7 +151,7 @@ public function validate(mixed $value, Constraint $constraint): void
143151
}
144152
}
145153

146-
if (null !== $constraint->maxPixels) {
154+
if (!$isSvg && null !== $constraint->maxPixels) {
147155
if (!ctype_digit((string) $constraint->maxPixels)) {
148156
throw new ConstraintDefinitionException(\sprintf('"%s" is not a valid maximum amount of pixels.', $constraint->maxPixels));
149157
}
@@ -231,4 +239,52 @@ public function validate(mixed $value, Constraint $constraint): void
231239
imagedestroy($resource);
232240
}
233241
}
242+
243+
private function isSvg(mixed $value): bool
244+
{
245+
if ($value instanceof File) {
246+
$mime = $value->getMimeType();
247+
} elseif (class_exists(MimeTypes::class)) {
248+
$mime = MimeTypes::getDefault()->guessMimeType($value);
249+
} elseif (!class_exists(File::class)) {
250+
return false;
251+
} else {
252+
$mime = (new File($value))->getMimeType();
253+
}
254+
255+
return 'image/svg+xml' === $mime;
256+
}
257+
258+
/**
259+
* @return array{int, int}|null index 0 and 1 contains respectively the width and the height of the image, null if size can't be found
260+
*/
261+
private function getSvgSize(mixed $value): ?array
262+
{
263+
if ($value instanceof File) {
264+
$content = $value->getContent();
265+
} elseif (!class_exists(File::class)) {
266+
return null;
267+
} else {
268+
$content = (new File($value))->getContent();
269+
}
270+
271+
if (1 === preg_match('/<svg[^<>]+width="(?<width>[0-9]+)"[^<>]*>/', $content, $widthMatches)) {
272+
$width = (int) $widthMatches['width'];
273+
}
274+
275+
if (1 === preg_match('/<svg[^<>]+height="(?<height>[0-9]+)"[^<>]*>/', $content, $heightMatches)) {
276+
$height = (int) $heightMatches['height'];
277+
}
278+
279+
if (1 === preg_match('/<svg[^<>]+viewBox="-?[0-9]+ -?[0-9]+ (?<width>-?[0-9]+) (?<height>-?[0-9]+)"[^<>]*>/', $content, $viewBoxMatches)) {
280+
$width ??= (int) $viewBoxMatches['width'];
281+
$height ??= (int) $viewBoxMatches['height'];
282+
}
283+
284+
if (isset($width) && isset($height)) {
285+
return [$width, $height];
286+
}
287+
288+
return null;
289+
}
234290
}
Loading
Loading
Loading
Loading
Loading
Loading

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Validator/Tests/Constraints/ImageValidatorTest.php
+136
Original file line numberDiff line numberDiff line change
@@ -498,4 +498,140 @@ public static function provideInvalidMimeTypeWithNarrowedSet()
498498
]),
499499
];
500500
}
501+
502+
/** @dataProvider provideSvgWithViolation */
503+
public function testSvgWithViolation(string $image, Image $constraint, string $violation, array $parameters = [])
504+
{
505+
$this->validator->validate($image, $constraint);
506+
507+
$this->buildViolation('myMessage')
508+
->setCode($violation)
509+
->setParameters($parameters)
510+
->assertRaised();
511+
}
512+
513+
public static function provideSvgWithViolation(): iterable
514+
{
515+
yield 'No size svg' => [
516+
__DIR__.'/Fixtures/test_no_size.svg',
517+
new Image(allowLandscape: false, sizeNotDetectedMessage: 'myMessage'),
518+
Image::SIZE_NOT_DETECTED_ERROR,
519+
];
520+
521+
yield 'Landscape SVG not allowed' => [
522+
__DIR__.'/Fixtures/test_landscape.svg',
523+
new Image(allowLandscape: false, allowLandscapeMessage: 'myMessage'),
524+
Image::LANDSCAPE_NOT_ALLOWED_ERROR,
525+
[
526+
'{{ width }}' => 500,
527+
'{{ height }}' => 200,
528+
],
529+
];
530+
531+
yield 'Portrait SVG not allowed' => [
532+
__DIR__.'/Fixtures/test_portrait.svg',
533+
new Image(allowPortrait: false, allowPortraitMessage: 'myMessage'),
534+
Image::PORTRAIT_NOT_ALLOWED_ERROR,
535+
[
536+
'{{ width }}' => 200,
537+
'{{ height }}' => 500,
538+
],
539+
];
540+
541+
yield 'Square SVG not allowed' => [
542+
__DIR__.'/Fixtures/test_square.svg',
543+
new Image(allowSquare: false, allowSquareMessage: 'myMessage'),
544+
Image::SQUARE_NOT_ALLOWED_ERROR,
545+
[
546+
'{{ width }}' => 500,
547+
'{{ height }}' => 500,
548+
],
549+
];
550+
551+
yield 'Landscape with width attribute SVG allowed' => [
552+
__DIR__.'/Fixtures/test_landscape_width.svg',
553+
new Image(allowLandscape: false, allowLandscapeMessage: 'myMessage'),
554+
Image::LANDSCAPE_NOT_ALLOWED_ERROR,
555+
[
556+
'{{ width }}' => 600,
557+
'{{ height }}' => 200,
558+
],
559+
];
560+
561+
yield 'Landscape with height attribute SVG not allowed' => [
562+
__DIR__.'/Fixtures/test_landscape_height.svg',
563+
new Image(allowLandscape: false, allowLandscapeMessage: 'myMessage'),
564+
Image::LANDSCAPE_NOT_ALLOWED_ERROR,
565+
[
566+
'{{ width }}' => 500,
567+
'{{ height }}' => 300,
568+
],
569+
];
570+
571+
yield 'Landscape with width and height attribute SVG not allowed' => [
572+
__DIR__.'/Fixtures/test_landscape_width_height.svg',
573+
new Image(allowLandscape: false, allowLandscapeMessage: 'myMessage'),
574+
Image::LANDSCAPE_NOT_ALLOWED_ERROR,
575+
[
576+
'{{ width }}' => 600,
577+
'{{ height }}' => 300,
578+
],
579+
];
580+
581+
yield 'SVG Min ratio 2' => [
582+
__DIR__.'/Fixtures/test_square.svg',
583+
new Image(minRatio: 2, minRatioMessage: 'myMessage'),
584+
Image::RATIO_TOO_SMALL_ERROR,
585+
[
586+
'{{ ratio }}' => '1',
587+
'{{ min_ratio }}' => '2',
588+
],
589+
];
590+
591+
yield 'SVG Min ratio 0.5' => [
592+
__DIR__.'/Fixtures/test_square.svg',
593+
new Image(maxRatio: 0.5, maxRatioMessage: 'myMessage'),
594+
Image::RATIO_TOO_BIG_ERROR,
595+
[
596+
'{{ ratio }}' => '1',
597+
'{{ max_ratio }}' => '0.5',
598+
],
599+
];
600+
}
601+
602+
/** @dataProvider provideSvgWithoutViolation */
603+
public function testSvgWithoutViolation(string $image, Image $constraint)
604+
{
605+
$this->validator->validate($image, $constraint);
606+
607+
$this->assertNoViolation();
608+
}
609+
610+
public static function provideSvgWithoutViolation(): iterable
611+
{
612+
yield 'Landscape SVG allowed' => [
613+
__DIR__.'/Fixtures/test_landscape.svg',
614+
new Image(allowLandscape: true, allowLandscapeMessage: 'myMessage'),
615+
];
616+
617+
yield 'Portrait SVG allowed' => [
618+
__DIR__.'/Fixtures/test_portrait.svg',
619+
new Image(allowPortrait: true, allowPortraitMessage: 'myMessage'),
620+
];
621+
622+
yield 'Square SVG allowed' => [
623+
__DIR__.'/Fixtures/test_square.svg',
624+
new Image(allowSquare: true, allowSquareMessage: 'myMessage'),
625+
];
626+
627+
yield 'SVG Min ratio 1' => [
628+
__DIR__.'/Fixtures/test_square.svg',
629+
new Image(minRatio: 1, minRatioMessage: 'myMessage'),
630+
];
631+
632+
yield 'SVG Max ratio 1' => [
633+
__DIR__.'/Fixtures/test_square.svg',
634+
new Image(maxRatio: 1, maxRatioMessage: 'myMessage'),
635+
];
636+
}
501637
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.