Skip to content

Commit 2ea4167

Browse files
author
alekLexis
committed
[Validator] Add option to allow ANY protocol in Assert\Url constraint
1 parent 77bcded commit 2ea4167

File tree

3 files changed

+71
-2
lines changed

3 files changed

+71
-2
lines changed

src/Symfony/Component/Validator/Constraints/Url.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Symfony\Component\Validator\Attribute\HasNamedArguments;
1515
use Symfony\Component\Validator\Constraint;
16+
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
1617
use Symfony\Component\Validator\Exception\InvalidArgumentException;
1718

1819
/**
@@ -36,6 +37,7 @@ class Url extends Constraint
3637
public array $protocols = ['http', 'https'];
3738
public bool $relativeProtocol = false;
3839
public bool $requireTld = false;
40+
public bool $allowAnyProtocol = false;
3941
/** @var callable|null */
4042
public $normalizer;
4143

@@ -57,6 +59,7 @@ public function __construct(
5759
mixed $payload = null,
5860
?bool $requireTld = null,
5961
?string $tldMessage = null,
62+
bool $allowAnyProtocol = false,
6063
) {
6164
if (\is_array($options)) {
6265
trigger_deprecation('symfony/validator', '7.3', 'Passing an array of options to configure the "%s" constraint is deprecated, use named arguments instead.', static::class);
@@ -68,12 +71,21 @@ public function __construct(
6871
trigger_deprecation('symfony/validator', '7.1', 'Not passing a value for the "requireTld" option to the Url constraint is deprecated. Its default value will change to "true".');
6972
}
7073

74+
if ($this->allowAnyProtocol && $this->relativeProtocol) {
75+
throw new ConstraintDefinitionException(sprintf(
76+
'The "%s" option cannot be combined with the "%s" option.',
77+
'allowAnyProtocol',
78+
'relativeProtocol'
79+
));
80+
}
81+
7182
$this->message = $message ?? $this->message;
7283
$this->protocols = $protocols ?? $this->protocols;
7384
$this->relativeProtocol = $relativeProtocol ?? $this->relativeProtocol;
7485
$this->normalizer = $normalizer ?? $this->normalizer;
7586
$this->requireTld = $requireTld ?? $this->requireTld;
7687
$this->tldMessage = $tldMessage ?? $this->tldMessage;
88+
$this->allowAnyProtocol = $allowAnyProtocol;
7789

7890
if (null !== $this->normalizer && !\is_callable($this->normalizer)) {
7991
throw new InvalidArgumentException(\sprintf('The "normalizer" option must be a valid callable ("%s" given).', get_debug_type($this->normalizer)));

src/Symfony/Component/Validator/Constraints/UrlValidator.php

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Symfony\Component\Validator\Constraint;
1515
use Symfony\Component\Validator\ConstraintValidator;
16+
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
1617
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
1718
use Symfony\Component\Validator\Exception\UnexpectedValueException;
1819

@@ -73,8 +74,14 @@ public function validate(mixed $value, Constraint $constraint): void
7374
$value = ($constraint->normalizer)($value);
7475
}
7576

76-
$pattern = $constraint->relativeProtocol ? str_replace('(%s):', '(?:(%s):)?', static::PATTERN) : static::PATTERN;
77-
$pattern = \sprintf($pattern, implode('|', $constraint->protocols));
77+
if ($constraint->allowAnyProtocol) {
78+
$genericScheme = '[A-Za-z][A-Za-z0-9+\-\.]*';
79+
$pattern = str_replace('(%s)', '('.$genericScheme.')', static::PATTERN);
80+
} else {
81+
$pattern = $constraint->relativeProtocol ? str_replace('(%s):', '(?:(%s):)?', static::PATTERN) : static::PATTERN;
82+
$pattern = sprintf($pattern, implode('|', $constraint->protocols));
83+
}
84+
7885

7986
if (!preg_match($pattern, $value)) {
8087
$this->context->buildViolation($constraint->message)

src/Symfony/Component/Validator/Tests/Constraints/UrlValidatorTest.php

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,56 @@ public function testValidRelativeUrlWithNewLine(string $url)
118118
->assertRaised();
119119
}
120120

121+
public function testCustomSchemeAllowedWithAllowAnyProtocol()
122+
{
123+
$constraint = new Url(allowAnyProtocol: true);
124+
125+
$this->validator->validate('myapp://example.com/path?query=1', $constraint);
126+
127+
$this->assertNoViolation();
128+
}
129+
130+
public function testMissingSchemeDisallowedWithAllowAnyProtocol()
131+
{
132+
$constraint = new Url(allowAnyProtocol: true);
133+
134+
$this->validator->validate('example.com/path', $constraint);
135+
136+
$this->buildViolation($constraint->message)
137+
->assertRaised();
138+
}
139+
140+
public function testAllowAnyProtocolIgnoresProtocolsList()
141+
{
142+
$constraint = new Url(
143+
protocols: ['http'],
144+
allowAnyProtocol: true,
145+
);
146+
147+
$this->validator->validate('custom+scheme://foo.bar', $constraint);
148+
149+
$this->assertNoViolation();
150+
}
151+
152+
public function testCustomSchemeDisallowedByDefault()
153+
{
154+
$constraint = new Url();
155+
156+
$this->validator->validate('myapp://example.com', $constraint);
157+
158+
$this->buildViolation($constraint->message)
159+
->assertRaised();
160+
}
161+
162+
public function testHttpSchemeStillAllowed()
163+
{
164+
$constraint = new Url();
165+
166+
$this->validator->validate('http://example.com', $constraint);
167+
168+
$this->assertNoViolation();
169+
}
170+
121171
public static function getValidRelativeUrls()
122172
{
123173
return [

0 commit comments

Comments
 (0)