diff --git a/src/Symfony/Component/Validator/CHANGELOG.md b/src/Symfony/Component/Validator/CHANGELOG.md
index 85baf4340c129..be1c9015ae5d9 100644
--- a/src/Symfony/Component/Validator/CHANGELOG.md
+++ b/src/Symfony/Component/Validator/CHANGELOG.md
@@ -3,7 +3,8 @@ CHANGELOG
6.1
---
-
+
+ * Support `SVGs` when validating image dimensions
* Deprecate `Constraint::$errorNames`, use `Constraint::ERROR_NAMES` instead
6.0
diff --git a/src/Symfony/Component/Validator/Constraints/ImageValidator.php b/src/Symfony/Component/Validator/Constraints/ImageValidator.php
index 3246215fef53a..a2442356f2708 100644
--- a/src/Symfony/Component/Validator/Constraints/ImageValidator.php
+++ b/src/Symfony/Component/Validator/Constraints/ImageValidator.php
@@ -11,6 +11,8 @@
namespace Symfony\Component\Validator\Constraints;
+use Symfony\Component\HttpFoundation\File\File as FileObject;
+use Symfony\Component\Mime\MimeTypes;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
use Symfony\Component\Validator\Exception\LogicException;
@@ -53,7 +55,18 @@ public function validate(mixed $value, Constraint $constraint)
return;
}
- $size = @getimagesize($value);
+ if ($this->isSvg($value)) {
+ $size = $this->getSvgSize($value);
+ if (null === $size) {
+ $this->context->buildViolation($constraint->corruptedMessage)
+ ->setCode(Image::CORRUPTED_IMAGE_ERROR)
+ ->addViolation();
+
+ return;
+ }
+ } else {
+ $size = @getimagesize($value);
+ }
if (empty($size) || (0 === $size[0]) || (0 === $size[1])) {
$this->context->buildViolation($constraint->sizeNotDetectedMessage)
@@ -234,4 +247,46 @@ public function validate(mixed $value, Constraint $constraint)
imagedestroy($resource);
}
}
+
+ /**
+ * Check whether a value is an SVG image
+ *
+ * @param mixed $value
+ *
+ * @return bool
+ * TRUE if value is an SVG, FALSE if it's not, or if we can't detect its MimeType;
+ */
+ private function isSvg(mixed $value)
+ {
+ if ($value instanceof FileObject) {
+ $mime = $value->getMimeType();
+ } elseif (class_exists(MimeTypes::class)) {
+ $mime = MimeTypes::getDefault()->guessMimeType($value);
+ } elseif (!class_exists(FileObject::class)) {
+ return false;
+ } else {
+ $mime = (new FileObject($value))->getMimeType();
+ }
+
+ return 'image/svg+xml' === $mime;
+ }
+
+ /**
+ * @param mixed $value
+ *
+ * @return array|null
+ * array contains the width and height of the image
+ * null if the XML is corrupted
+ */
+ private function getSvgSize(mixed $value)
+ {
+ if (false === $xmlValue = simplexml_load_file($value)) {
+ return null;
+ }
+ $svgAttributes = $xmlValue->attributes();
+ $width = intval($svgAttributes->width);
+ $height = intval($svgAttributes->height);
+
+ return [$width, $height];
+ }
}
diff --git a/src/Symfony/Component/Validator/Tests/Constraints/Fixtures/test_svg.svg b/src/Symfony/Component/Validator/Tests/Constraints/Fixtures/test_svg.svg
new file mode 100644
index 0000000000000..5f5e4bf0e92d8
--- /dev/null
+++ b/src/Symfony/Component/Validator/Tests/Constraints/Fixtures/test_svg.svg
@@ -0,0 +1,6 @@
+
+
diff --git a/src/Symfony/Component/Validator/Tests/Constraints/Fixtures/test_svg_landscape.svg b/src/Symfony/Component/Validator/Tests/Constraints/Fixtures/test_svg_landscape.svg
new file mode 100644
index 0000000000000..274298eabc674
--- /dev/null
+++ b/src/Symfony/Component/Validator/Tests/Constraints/Fixtures/test_svg_landscape.svg
@@ -0,0 +1,6 @@
+
+
diff --git a/src/Symfony/Component/Validator/Tests/Constraints/Fixtures/test_svg_portrait.svg b/src/Symfony/Component/Validator/Tests/Constraints/Fixtures/test_svg_portrait.svg
new file mode 100644
index 0000000000000..1c0126da5e543
--- /dev/null
+++ b/src/Symfony/Component/Validator/Tests/Constraints/Fixtures/test_svg_portrait.svg
@@ -0,0 +1,6 @@
+
+
diff --git a/src/Symfony/Component/Validator/Tests/Constraints/ImageValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/ImageValidatorTest.php
index cedab95e1e202..cca8419d0b44c 100644
--- a/src/Symfony/Component/Validator/Tests/Constraints/ImageValidatorTest.php
+++ b/src/Symfony/Component/Validator/Tests/Constraints/ImageValidatorTest.php
@@ -30,6 +30,9 @@ class ImageValidatorTest extends ConstraintValidatorTestCase
protected $image4By3;
protected $imageCorrupted;
protected $notAnImage;
+ protected $imageSvg;
+ protected $imageSvgLandscape;
+ protected $imageSvgPortrait;
protected function createValidator()
{
@@ -47,6 +50,9 @@ protected function setUp(): void
$this->image16By9 = __DIR__.'/Fixtures/test_16by9.gif';
$this->imageCorrupted = __DIR__.'/Fixtures/test_corrupted.gif';
$this->notAnImage = __DIR__.'/Fixtures/ccc.txt';
+ $this->imageSvg = __DIR__.'/Fixtures/test_svg.svg';
+ $this->imageSvgLandscape = __DIR__.'/Fixtures/test_svg_landscape.svg';
+ $this->imageSvgPortrait = __DIR__.'/Fixtures/test_svg_portrait.svg';
}
public function testNullIsValid()
@@ -536,6 +542,62 @@ public function testInvalidMimeType()
->assertRaised();
}
+ public function testSvgSize()
+ {
+ $constraint = new Image([
+ 'minWidth' => 1,
+ 'maxWidth' => 2,
+ 'minHeight' => 1,
+ 'maxHeight' => 2,
+ ]);
+
+ $this->validator->validate($this->imageSvg, $constraint);
+
+ $this->assertNoViolation();
+ }
+
+ /**
+ * @dataProvider provideAllowSquareConstraints
+ */
+ public function testSquareNotAllowedOnSvg(Image $constraint)
+ {
+ $this->validator->validate($this->imageSvg, $constraint);
+
+ $this->buildViolation('myMessage')
+ ->setParameter('{{ width }}', 1)
+ ->setParameter('{{ height }}', 1)
+ ->setCode(Image::SQUARE_NOT_ALLOWED_ERROR)
+ ->assertRaised();
+ }
+
+ /**
+ * @dataProvider provideAllowLandscapeConstraints
+ */
+ public function testLandscapeNotAllowedOnSvg(Image $constraint)
+ {
+ $this->validator->validate($this->imageSvgLandscape, $constraint);
+
+ $this->buildViolation('myMessage')
+ ->setParameter('{{ width }}', 2)
+ ->setParameter('{{ height }}', 1)
+ ->setCode(Image::LANDSCAPE_NOT_ALLOWED_ERROR)
+ ->assertRaised();
+ }
+
+ /**
+ * @dataProvider provideAllowPortraitConstraints
+ */
+ public function testPortraitNotAllowedOnSvg(Image $constraint)
+ {
+ $this->validator->validate($this->imageSvgPortrait, $constraint);
+
+ $this->buildViolation('myMessage')
+ ->setParameter('{{ width }}', 1)
+ ->setParameter('{{ height }}', 2)
+ ->setCode(Image::PORTRAIT_NOT_ALLOWED_ERROR)
+ ->assertRaised();
+ }
+
public function provideDetectCorruptedConstraints(): iterable
{
yield 'Doctrine style' => [new Image([