-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[Validator] Add the Base64
constraint
#53360
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,40 @@ | ||||||
<?php | ||||||
|
||||||
/* | ||||||
* This file is part of the Symfony package. | ||||||
* | ||||||
* (c) Fabien Potencier <[email protected]> | ||||||
* | ||||||
* For the full copyright and license information, please view the LICENSE | ||||||
* file that was distributed with this source code. | ||||||
*/ | ||||||
|
||||||
namespace Symfony\Component\Validator\Constraints; | ||||||
|
||||||
use Symfony\Component\Validator\Constraint; | ||||||
|
||||||
/** | ||||||
* @author Alexandre Daubois <[email protected]> | ||||||
*/ | ||||||
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] | ||||||
final class Base64 extends Constraint | ||||||
{ | ||||||
public const INVALID_STRING_ERROR = '5699d0ba-3cea-4676-9aab-d3303ebd6934'; | ||||||
public const MISSING_DATA_URI_ERROR = '9380f49b-582a-49ce-a0b9-e68ff009e80f'; | ||||||
|
||||||
protected const ERROR_NAMES = [ | ||||||
self::INVALID_STRING_ERROR => 'INVALID_STRING_ERROR', | ||||||
self::MISSING_DATA_URI_ERROR => 'MISSING_DATA_URI_ERROR', | ||||||
]; | ||||||
|
||||||
public string $messageInvalidString = 'The given string is not a valid Base64 encoded string.'; | ||||||
public string $messageMissingDataUri = 'The given string is missing a data URI.'; | ||||||
|
||||||
public function __construct(public bool $requiresDataUri = false, public bool $urlEncoded = false, string $messageInvalidString = null, string $messageMissingDataUri = null, array $groups = null, mixed $payload = null, array $options = null) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. CPP mean parameters should be on their own lines Also:
Suggested change
|
||||||
{ | ||||||
parent::__construct($options, $groups, $payload); | ||||||
|
||||||
$this->messageInvalidString = $messageInvalidString ?? $this->messageInvalidString; | ||||||
$this->messageMissingDataUri = $messageMissingDataUri ?? $this->messageMissingDataUri; | ||||||
} | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <[email protected]> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Component\Validator\Constraints; | ||
|
||
use Symfony\Component\Validator\Constraint; | ||
use Symfony\Component\Validator\ConstraintValidator; | ||
use Symfony\Component\Validator\Exception\UnexpectedTypeException; | ||
use Symfony\Component\Validator\Exception\UnexpectedValueException; | ||
|
||
/** | ||
* @author Alexandre Daubois <[email protected]> | ||
*/ | ||
final class Base64Validator extends ConstraintValidator | ||
{ | ||
public function validate(mixed $value, Constraint $constraint): void | ||
{ | ||
if (!$constraint instanceof Base64) { | ||
throw new UnexpectedTypeException($constraint, Base64::class); | ||
} | ||
|
||
if (null === $value) { | ||
return; | ||
} | ||
|
||
if (!\is_string($value) && !$value instanceof \Stringable) { | ||
throw new UnexpectedValueException($value, 'string'); | ||
} | ||
|
||
if ($constraint->urlEncoded) { | ||
$value = rawurldecode($value); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Base64 URL safe encoding is not that. It's about using |
||
} | ||
|
||
if ($constraint->requiresDataUri) { | ||
preg_match('/^data:.+;base64,/', $value, $matches); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since this is a validator, it'd be great to also validate what is just after |
||
|
||
if (0 === \count($matches)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why not use |
||
$this->context->buildViolation($constraint->messageMissingDataUri) | ||
->setCode(Base64::MISSING_DATA_URI_ERROR) | ||
->addViolation(); | ||
|
||
return; | ||
} | ||
|
||
$value = str_replace($matches[0], '', $value); | ||
} | ||
|
||
preg_match('/^[a-zA-Z0-9\/\r\n+]*(==)?$/', $value, $matches); | ||
if (0 === \count($matches)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same question |
||
$this->context->buildViolation($constraint->messageInvalidString) | ||
->setCode(Base64::INVALID_STRING_ERROR) | ||
->addViolation(); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <[email protected]> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Component\Validator\Tests\Constraints; | ||
|
||
use PHPUnit\Framework\TestCase; | ||
use Symfony\Component\Validator\Constraints\Base64; | ||
use Symfony\Component\Validator\Mapping\ClassMetadata; | ||
use Symfony\Component\Validator\Mapping\Loader\AttributeLoader; | ||
|
||
class Base64Test extends TestCase | ||
{ | ||
public function testAllowDataUri() | ||
{ | ||
$encoding = new Base64(true); | ||
|
||
$this->assertTrue($encoding->requiresDataUri); | ||
} | ||
|
||
public function testDenyDataUriByDefault() | ||
{ | ||
$encoding = new Base64(); | ||
|
||
$this->assertFalse($encoding->requiresDataUri); | ||
} | ||
|
||
public function testAttributes() | ||
{ | ||
$metadata = new ClassMetadata(Base64Dummy::class); | ||
$loader = new AttributeLoader(); | ||
$this->assertTrue($loader->loadClassMetadata($metadata)); | ||
|
||
[$aConstraint] = $metadata->properties['a']->getConstraints(); | ||
$this->assertFalse($aConstraint->requiresDataUri); | ||
|
||
[$bConstraint] = $metadata->properties['b']->getConstraints(); | ||
$this->assertTrue($bConstraint->requiresDataUri); | ||
|
||
[$cConstraint] = $metadata->properties['c']->getConstraints(); | ||
$this->assertFalse($cConstraint->requiresDataUri); | ||
$this->assertTrue($cConstraint->urlEncoded); | ||
} | ||
} | ||
|
||
class Base64Dummy | ||
{ | ||
#[Base64] | ||
private string $a; | ||
|
||
#[Base64(true)] | ||
private string $b; | ||
|
||
#[Base64(urlEncoded: true)] | ||
private string $c; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <[email protected]> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Component\Validator\Tests\Constraints; | ||
|
||
use Symfony\Component\Validator\Constraints\Base64; | ||
use Symfony\Component\Validator\Constraints\Base64Validator; | ||
use Symfony\Component\Validator\Exception\UnexpectedValueException; | ||
use Symfony\Component\Validator\Test\ConstraintValidatorTestCase; | ||
|
||
class Base64ValidatorTest extends ConstraintValidatorTestCase | ||
{ | ||
protected function createValidator(): Base64Validator | ||
{ | ||
return new Base64Validator(); | ||
} | ||
|
||
/** | ||
* @dataProvider provideValidValues | ||
*/ | ||
public function testValidBase64(string|\Stringable $value) | ||
{ | ||
$this->validator->validate($value, new Base64()); | ||
|
||
$this->assertNoViolation(); | ||
} | ||
|
||
/** | ||
* @dataProvider provideUrlEncodedValidValues | ||
*/ | ||
public function testUrlEncodedValidBase64(string|\Stringable $value) | ||
{ | ||
$this->validator->validate($value, new Base64(urlEncoded: true)); | ||
|
||
$this->assertNoViolation(); | ||
} | ||
|
||
/** | ||
* @dataProvider provideInvalidValues | ||
*/ | ||
public function testInvalidValues(string $value) | ||
{ | ||
$this->validator->validate($value, new Base64()); | ||
|
||
$this->buildViolation('The given string is not a valid Base64 encoded string.') | ||
->setCode(Base64::INVALID_STRING_ERROR) | ||
->assertRaised(); | ||
} | ||
|
||
/** | ||
* @dataProvider provideInvalidTypes | ||
*/ | ||
public function testNonStringValues(mixed $value) | ||
{ | ||
$this->expectException(UnexpectedValueException::class); | ||
$this->expectExceptionMessageMatches('/Expected argument of type "string", ".*" given/'); | ||
|
||
$this->validator->validate($value, new Base64()); | ||
} | ||
|
||
public function testItRequiresDataUri() | ||
{ | ||
$this->validator->validate('data:image/png;base64,dGVzdA==', new Base64(true)); | ||
|
||
$this->assertNoViolation(); | ||
} | ||
|
||
public function testItRequiresDataUriButNoneIsGiven() | ||
{ | ||
$this->validator->validate('dGVzdA==', new Base64(true)); | ||
|
||
$this->buildViolation('The given string is missing a data URI.') | ||
->setCode(Base64::MISSING_DATA_URI_ERROR) | ||
->assertRaised(); | ||
} | ||
|
||
public function testItDoesntRequireDataUriButOneIsGiven() | ||
{ | ||
$this->validator->validate('data:image/png;base64,dGVzdA==', new Base64()); | ||
|
||
$this->buildViolation('The given string is not a valid Base64 encoded string.') | ||
->setCode(Base64::INVALID_STRING_ERROR) | ||
->assertRaised(); | ||
} | ||
|
||
public static function provideValidValues() | ||
{ | ||
yield ['']; | ||
yield ['dGVzdA==']; | ||
yield ['dGVzdA']; | ||
yield [new class() implements \Stringable { | ||
public function __toString(): string | ||
{ | ||
return 'dGVzdA=='; | ||
} | ||
}]; | ||
} | ||
|
||
public static function provideUrlEncodedValidValues() | ||
{ | ||
yield ['dGVzd%2B%2FA%3D%3D']; | ||
yield ['dGVzdA%3D%3D']; | ||
yield ['dGVzdA']; | ||
yield [new class() implements \Stringable { | ||
public function __toString(): string | ||
{ | ||
return 'dGVzdA%3D%3D'; | ||
} | ||
}]; | ||
} | ||
|
||
public static function provideInvalidValues() | ||
{ | ||
yield 'missing equal sign' => ['dGVzdA=']; | ||
yield 'illegal chars' => ['é"']; | ||
yield 'emoji' => ['😊']; | ||
yield 'url encoded' => ['dGVzdA%3D%3D']; | ||
} | ||
|
||
public static function provideInvalidTypes() | ||
{ | ||
yield [true]; | ||
yield [false]; | ||
yield [1]; | ||
yield [1.1]; | ||
yield [[]]; | ||
yield [new \stdClass()]; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BTW, the message/naming about data URI is misleading: the prefix could be there but be invalid.