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

Conversation

alexandre-daubois
Copy link
Member

Q A
Branch? 7.1
Bug fix? no
New feature? yes
Deprecations? no
Issues -
License MIT

Base64 encoding is commonly employed when handling file uploads through DTO. This constraint would really save some time to devs by ensuring that the Base64-encoded data representing the uploaded file content is correctly formatted and valid with a single attribute.

Because of the main use I see is file uploads in this format, I also added an option, allowDataUri, which allows the input to be prefixed by something like data:image/png;base64,.

$value = preg_replace('/^data:.+;base64,/', '', $value);
}

if (false === base64_decode($value, true)) {
Copy link
Member Author

Choose a reason for hiding this comment

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

Ensure false is strictly returned because an empty string is a valid base64 string

Copy link
Member

Choose a reason for hiding this comment

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

This way of checking consumes memory for no good reasons because it generates the decoded payload and trashes is right away.
A regexp would be better.
Also, there are two styles of base64 encoding (url-safe and not-url-safe), so we need a way to tell which one we want.

Copy link
Member Author

Choose a reason for hiding this comment

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

I updated the check to use a regex to check base64 validity.

Also, the urlEncoded option has been added. Is that what you had in mind?

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

$this->message = $message ?? $this->message;
$this->allowDataUri = $allowDataUri;
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure I understand how this is supposed to be useful.
Do you really have a use case where data URLs are accepted as input?
The main issue I see is the fact that the URL prefix is optional when enabling this: either you accept without the prefix, or you accept with the prefix, but both? really?

Copy link
Member Author

Choose a reason for hiding this comment

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

About the use case, yes: we have use cases where a user uploads, let's say, a PNG. The input data looks like data:image/png;base64,iVBORw0KGgo.... However, we also accept, in other DTOs, "simple" base64 without data uri. I think giving this option helps the constraint being flexible for different cases devs will encounter.

That said, I agree that accepting both is useless. It is either one case or another. I'll update the code to have an option like requiresDataUri which makes more sense.

@alexandre-daubois alexandre-daubois force-pushed the base64-constraint branch 4 times, most recently from 4e44d1c to f49858b Compare January 5, 2024 08:23
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)
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)

Comment on lines +30 to +31
public string $messageInvalidString = 'The given string is not a valid Base64 encoded string.';
public string $messageMissingDataUri = 'The given string is missing a data URI.';
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.

}

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);

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)?

}

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

}

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.

@alexandre-daubois
Copy link
Member Author

After discussing with Nicolas, we agreed this constraint doesn't make really sense. Thus, this can still be done by using a regex.

For anyone wondering how to achieve this, use the Regex constraint with the following (or similar): /^[a-zA-Z0-9\/\r\n+]*(==)?$/.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants