Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[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

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Symfony/Component/Validator/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ CHANGELOG
* Add `MacAddress` constraint
* Add `list` and `associative_array` types to `Type` constraint
* Add the `Charset` constraint
* Add the `Base64` constraint

7.0
---
Expand Down
40 changes: 40 additions & 0 deletions src/Symfony/Component/Validator/Constraints/Base64.php
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.';
Comment on lines +30 to +31
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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 string $messageInvalidString = 'This value is not a valid Base64 encoded string.';
public string $messageMissingDataUri = 'This value is missing a data URI prefix.';

Copy link
Member

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.


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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CPP mean parameters should be on their own lines

Also:

Suggested change
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)
public function __construct(public bool $dataUri = false, public bool $urlSafe = false, string $message = null, string $messageMissingDataUri = null, array $groups = null, mixed $payload = null, array $options = null)

{
parent::__construct($options, $groups, $payload);

$this->messageInvalidString = $messageInvalidString ?? $this->messageInvalidString;
$this->messageMissingDataUri = $messageMissingDataUri ?? $this->messageMissingDataUri;
}
}
63 changes: 63 additions & 0 deletions src/Symfony/Component/Validator/Constraints/Base64Validator.php
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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Base64 URL safe encoding is not that. It's about using -_ instead of +/.

}

if ($constraint->requiresDataUri) {
preg_match('/^data:.+;base64,/', $value, $matches);
Copy link
Member

Choose a reason for hiding this comment

The 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 data:. I don't know the rules, but google or gpt will.


if (0 === \count($matches)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not use if (!preg_match)?

$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)) {
Copy link
Member

Choose a reason for hiding this comment

The 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();
}
}
}
63 changes: 63 additions & 0 deletions src/Symfony/Component/Validator/Tests/Constraints/Base64Test.php
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()];
}
}