Skip to content

Commit 926ad7c

Browse files
author
symfonyaml
committed
[Validator] Add the DataUri constraint for validating Data URI content
1 parent 542891f commit 926ad7c

File tree

4 files changed

+214
-0
lines changed

4 files changed

+214
-0
lines changed

src/Symfony/Component/Validator/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ CHANGELOG
1212
* Add the `WordCount` constraint
1313
* Add the `Week` constraint
1414
* Add `CompoundConstraintTestCase` to ease testing Compound Constraints
15+
* Add the `DataUri` constraint for validating Data URI content
1516

1617
7.1
1718
---
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Validator\Constraints;
13+
14+
use Symfony\Component\Validator\Constraint;
15+
16+
/**
17+
* Validates that a value is a valid data URI string.
18+
*
19+
* @author Kev <https://github.com/symfonyaml>
20+
*/
21+
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
22+
class DataUri extends Constraint
23+
{
24+
public const INVALID_DATA_URI_ERROR = 'b9e175d1-8d7a-4e28-bf65-ad2448a3b3cf';
25+
26+
protected const ERROR_NAMES = [
27+
self::INVALID_DATA_URI_ERROR => 'INVALID_DATA_URI_ERROR',
28+
];
29+
30+
public string $message = 'This value is not a valid data URI.';
31+
32+
/**
33+
* @param array<string,mixed>|null $options
34+
* @param string[]|null $groups
35+
*/
36+
public function __construct(
37+
?array $options = null,
38+
?string $message = null,
39+
?array $groups = null,
40+
mixed $payload = null,
41+
) {
42+
parent::__construct($options ?? [], $groups, $payload);
43+
44+
$this->message = $message ?? $this->message;
45+
}
46+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Validator\Constraints;
13+
14+
use Symfony\Component\Validator\Constraint;
15+
use Symfony\Component\Validator\ConstraintValidator;
16+
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
17+
use Symfony\Component\Validator\Exception\UnexpectedValueException;
18+
19+
/**
20+
* @author Kev <https://github.com/symfonyaml>
21+
* @see https://datatracker.ietf.org/doc/html/rfc2397
22+
*/
23+
class DataUriValidator extends ConstraintValidator
24+
{
25+
/** maximum number of characters allowed in a error message */
26+
private const MAX_MESSAGE_VALUE_LENGTH = 30;
27+
/** data-uri regexp */
28+
public const PATTERN = '~^
29+
data:
30+
(?:\w+\/(?:(?!;).)+)? # MIME-type
31+
(?:;[\w\W]*?[^;])* # parameters
32+
(;base64)? # encoding
33+
,
34+
[^$]+ # data
35+
$~ixuD';
36+
37+
public function validate(mixed $value, Constraint $constraint): void
38+
{
39+
if (!$constraint instanceof DataUri) {
40+
throw new UnexpectedTypeException($constraint, DataUri::class);
41+
}
42+
43+
if (null === $value || '' === $value) {
44+
return;
45+
}
46+
47+
if (!\is_scalar($value) && !$value instanceof \Stringable) {
48+
throw new UnexpectedValueException($value, 'string');
49+
}
50+
51+
$value = (string) $value;
52+
if ('' === $value) {
53+
return;
54+
}
55+
56+
if (!preg_match(static::PATTERN, $value)) {
57+
if (strlen($value) > self::MAX_MESSAGE_VALUE_LENGTH) {
58+
$value = sprintf('%s (truncated)', $this->formatValue(substr($value, 0, self::MAX_MESSAGE_VALUE_LENGTH) . '...'));
59+
} else {
60+
$value = $this->formatValue($value);
61+
}
62+
$this->context->buildViolation($constraint->message)
63+
->setParameter('{{ value }}', $value)
64+
->setCode(DataUri::INVALID_DATA_URI_ERROR)
65+
->addViolation();
66+
}
67+
}
68+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Validator\Tests\Constraints;
13+
14+
use Symfony\Component\Validator\Constraints\DataUri;
15+
use Symfony\Component\Validator\Constraints\DataUriValidator;
16+
use Symfony\Component\Validator\Test\ConstraintValidatorTestCase;
17+
18+
/**
19+
* @author Kev <https://github.com/symfonyaml>
20+
*/
21+
class DataUriValidatorTest extends ConstraintValidatorTestCase
22+
{
23+
protected function createValidator(): DataUriValidator
24+
{
25+
return new DataUriValidator();
26+
}
27+
28+
public function testNullIsValid()
29+
{
30+
$this->validator->validate(null, new DataUri());
31+
32+
$this->assertNoViolation();
33+
}
34+
35+
public function testBlankIsValid()
36+
{
37+
$this->validator->validate('', new DataUri());
38+
39+
$this->assertNoViolation();
40+
}
41+
42+
/**
43+
* @dataProvider getValidValues
44+
*/
45+
public function testValidValues(string $value)
46+
{
47+
$this->validator->validate($value, new DataUri());
48+
49+
$this->assertNoViolation();
50+
}
51+
52+
public static function getValidValues()
53+
{
54+
return [
55+
'mime type is omitted' => ['data:,FooBar'],
56+
'just charset' => ['data:;charset=UTF-8,FooBar'],
57+
'plain text' => ['data:text/plain;base64,SGVsbG8sIFdvcmxkIQ=='],
58+
'text html' => ['data:text/html,%3Ch1%3EHello%2C%20World%21%3C%2Fh1%3E'],
59+
'plain text with charset' => ['data:text/plain;charset=UTF-8,the%20data:1234,5678'],
60+
'with meta key=value' => ['data:image/jpeg;key=value;base64,UEsDBBQAAAAI'],
61+
'without base64 key name' => ['data:image/jpeg;key=value,UEsDBBQAAAAI'],
62+
'jpeg image' => ['data:image/jpeg;base64,/9j/4AAQSkZJRgABAgAAZABkAAD'],
63+
'png image' => ['data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=='],
64+
'gif image' => ['data:image/gif;base64,R0lGODlhyAAiALM...DfD0QAADs='],
65+
'svg' => ['data:image/svg+xml,%3Csvg%20version%3D%221.1%22%3E%3C%2Fsvg%3E'],
66+
'networking applications' => ['data:application/vnd-xxx-query,select_vcount,fcol_from_fieldtable/local,123456789'],
67+
];
68+
}
69+
70+
/**
71+
* @dataProvider getInvalidValues
72+
*/
73+
public function testInvalidValues($value, $valueAsString)
74+
{
75+
$constraint = new DataUri([
76+
'message' => 'myMessage',
77+
]);
78+
79+
$this->validator->validate($value, $constraint);
80+
81+
$this->buildViolation('myMessage')
82+
->setParameter('{{ value }}', $valueAsString)
83+
->setCode(DataUri::INVALID_DATA_URI_ERROR)
84+
->assertRaised();
85+
}
86+
87+
public static function getInvalidValues()
88+
{
89+
return [
90+
'random string' => ['foobar', '"foobar"'],
91+
'zero' => [0, '"0"'],
92+
'integer' => [1234, '"1234"'],
93+
'truncated invalid value' => [
94+
'1234567890123456789012345678901', // 31 chars
95+
'"123456789012345678901234567890..." (truncated)',
96+
],
97+
];
98+
}
99+
}

0 commit comments

Comments
 (0)