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

Skip to content

Commit e6fc5af

Browse files
committed
Added HostnameValidator
1 parent a0bbae7 commit e6fc5af

File tree

8 files changed

+318
-0
lines changed

8 files changed

+318
-0
lines changed

src/Symfony/Component/Validator/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ CHANGELOG
2424
* Overriding the methods `ConstraintValidatorTestCase::setUp()` and `ConstraintValidatorTestCase::tearDown()` without the `void` return-type is deprecated.
2525
* deprecated `Symfony\Component\Validator\Mapping\Cache\CacheInterface` in favor of PSR-6.
2626
* deprecated `ValidatorBuilder::setMetadataCache`, use `ValidatorBuilder::setMappingCache` instead.
27+
* added the `Hostname` constraint and validator
2728

2829
4.3.0
2930
-----
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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+
* @Annotation
18+
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
19+
*
20+
* @author Dmitrii Poddubnyi <[email protected]>
21+
*/
22+
class Hostname extends Constraint
23+
{
24+
const INVALID_HOSTNAME_ERROR = '7057ffdb-0af4-4f7e-bd5e-e9acfa6d7a2d';
25+
26+
protected static $errorNames = [
27+
self::INVALID_HOSTNAME_ERROR => 'INVALID_HOSTNAME_ERROR',
28+
];
29+
30+
public $message = 'This value is not a valid hostname.';
31+
public $requireTld = true;
32+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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 Dmitrii Poddubnyi <[email protected]>
21+
*/
22+
class HostnameValidator extends ConstraintValidator
23+
{
24+
/**
25+
* https://tools.ietf.org/html/rfc2606.
26+
*/
27+
private const RESERVED_TLDS = [
28+
'example',
29+
'invalid',
30+
'localhost',
31+
'test',
32+
];
33+
34+
public function validate($value, Constraint $constraint)
35+
{
36+
if (!$constraint instanceof Hostname) {
37+
throw new UnexpectedTypeException($constraint, Hostname::class);
38+
}
39+
40+
if (null === $value || '' === $value) {
41+
return;
42+
}
43+
44+
if (!is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) {
45+
throw new UnexpectedValueException($value, 'string');
46+
}
47+
48+
$value = (string) $value;
49+
if ('' === $value) {
50+
return;
51+
}
52+
if (!$this->isValid($value) || ($constraint->requireTld && !$this->hasValidTld($value))) {
53+
$this->context->buildViolation($constraint->message)
54+
->setParameter('{{ value }}', $this->formatValue($value))
55+
->setCode(Hostname::INVALID_HOSTNAME_ERROR)
56+
->addViolation();
57+
}
58+
}
59+
60+
private function isValid(string $domain): bool
61+
{
62+
return false !== filter_var($domain, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME);
63+
}
64+
65+
private function hasValidTld(string $domain): bool
66+
{
67+
return false !== mb_strpos($domain, '.') && !\in_array(mb_substr($domain, mb_strrpos($domain, '.') + 1), self::RESERVED_TLDS, true);
68+
}
69+
}

src/Symfony/Component/Validator/Resources/translations/validators.de.xlf

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,10 @@
366366
<source>This value should be between {{ min }} and {{ max }}.</source>
367367
<target>Dieser Wert sollte zwischen {{ min }} und {{ max }} sein.</target>
368368
</trans-unit>
369+
<trans-unit id="95">
370+
<source>This value is not a valid hostname.</source>
371+
<target>Dieser Wert ist kein gültiger Hostname.</target>
372+
</trans-unit>
369373
</body>
370374
</file>
371375
</xliff>

src/Symfony/Component/Validator/Resources/translations/validators.en.xlf

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,10 @@
366366
<source>This value should be between {{ min }} and {{ max }}.</source>
367367
<target>This value should be between {{ min }} and {{ max }}.</target>
368368
</trans-unit>
369+
<trans-unit id="95">
370+
<source>This value is not a valid hostname.</source>
371+
<target>This value is not a valid hostname.</target>
372+
</trans-unit>
369373
</body>
370374
</file>
371375
</xliff>

src/Symfony/Component/Validator/Resources/translations/validators.fr.xlf

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,10 @@
366366
<source>This value should be between {{ min }} and {{ max }}.</source>
367367
<target>Cette valeur doit être comprise entre {{ min }} et {{ max }}.</target>
368368
</trans-unit>
369+
<trans-unit id="95">
370+
<source>This value is not a valid hostname.</source>
371+
<target>Cette valeur n'est pas un nom d'hôte valide.</target>
372+
</trans-unit>
369373
</body>
370374
</file>
371375
</xliff>

src/Symfony/Component/Validator/Resources/translations/validators.ru.xlf

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,10 @@
366366
<source>This value should be between {{ min }} and {{ max }}.</source>
367367
<target>Значение должно быть между {{ min }} и {{ max }}.</target>
368368
</trans-unit>
369+
<trans-unit id="95">
370+
<source>This value is not a valid hostname.</source>
371+
<target>Значение не является корректным именем хоста.</target>
372+
</trans-unit>
369373
</body>
370374
</file>
371375
</xliff>
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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\Hostname;
15+
use Symfony\Component\Validator\Constraints\HostnameValidator;
16+
use Symfony\Component\Validator\Test\ConstraintValidatorTestCase;
17+
18+
/**
19+
* @author Dmitrii Poddubnyi <[email protected]>
20+
*/
21+
class HostnameValidatorTest extends ConstraintValidatorTestCase
22+
{
23+
public function testNullIsValid()
24+
{
25+
$this->validator->validate(null, new Hostname());
26+
27+
$this->assertNoViolation();
28+
}
29+
30+
public function testEmptyStringIsValid()
31+
{
32+
$this->validator->validate('', new Hostname());
33+
34+
$this->assertNoViolation();
35+
}
36+
37+
public function testExpectsStringCompatibleType()
38+
{
39+
$this->expectException(\Symfony\Component\Validator\Exception\UnexpectedValueException::class);
40+
41+
$this->validator->validate(new \stdClass(), new Hostname());
42+
}
43+
44+
/**
45+
* @dataProvider getValidMultilevelDomains
46+
*/
47+
public function testValidTldDomainsPassValidationIfTldRequired($domain)
48+
{
49+
$this->validator->validate($domain, new Hostname());
50+
51+
$this->assertNoViolation();
52+
}
53+
54+
/**
55+
* @dataProvider getValidMultilevelDomains
56+
*/
57+
public function testValidTldDomainsPassValidationIfTldNotRequired($domain)
58+
{
59+
$this->validator->validate($domain, new Hostname(['requireTld' => false]));
60+
61+
$this->assertNoViolation();
62+
}
63+
64+
public function getValidMultilevelDomains()
65+
{
66+
return [
67+
['symfony.com'],
68+
['example.co.uk'],
69+
['example.fr'],
70+
['example.com'],
71+
['xn--diseolatinoamericano-66b.com'],
72+
['xn--ggle-0nda.com'],
73+
['www.xn--simulateur-prt-2kb.fr'],
74+
[sprintf('%s.com', str_repeat('a', 20))],
75+
];
76+
}
77+
78+
/**
79+
* @dataProvider getInvalidDomains
80+
*/
81+
public function testInvalidDomainsRaiseViolationIfTldRequired($domain)
82+
{
83+
$this->validator->validate($domain, new Hostname([
84+
'message' => 'myMessage',
85+
]));
86+
87+
$this->buildViolation('myMessage')
88+
->setParameter('{{ value }}', '"'.$domain.'"')
89+
->setCode(Hostname::INVALID_HOSTNAME_ERROR)
90+
->assertRaised();
91+
}
92+
93+
/**
94+
* @dataProvider getInvalidDomains
95+
*/
96+
public function testInvalidDomainsRaiseViolationIfTldNotRequired($domain)
97+
{
98+
$this->validator->validate($domain, new Hostname([
99+
'message' => 'myMessage',
100+
'requireTld' => false,
101+
]));
102+
103+
$this->buildViolation('myMessage')
104+
->setParameter('{{ value }}', '"'.$domain.'"')
105+
->setCode(Hostname::INVALID_HOSTNAME_ERROR)
106+
->assertRaised();
107+
}
108+
109+
public function getInvalidDomains()
110+
{
111+
return [
112+
['acme..com'],
113+
['qq--.com'],
114+
['-example.com'],
115+
['example-.com'],
116+
[sprintf('%s.com', str_repeat('a', 300))],
117+
];
118+
}
119+
120+
/**
121+
* @dataProvider getReservedDomains
122+
*/
123+
public function testReservedDomainsPassValidationIfTldNotRequired($domain)
124+
{
125+
$this->validator->validate($domain, new Hostname(['requireTld' => false]));
126+
127+
$this->assertNoViolation();
128+
}
129+
130+
/**
131+
* @dataProvider getReservedDomains
132+
*/
133+
public function testReservedDomainsRaiseViolationIfTldRequired($domain)
134+
{
135+
$this->validator->validate($domain, new Hostname([
136+
'message' => 'myMessage',
137+
'requireTld' => true,
138+
]));
139+
140+
$this->buildViolation('myMessage')
141+
->setParameter('{{ value }}', '"'.$domain.'"')
142+
->setCode(Hostname::INVALID_HOSTNAME_ERROR)
143+
->assertRaised();
144+
}
145+
146+
public function getReservedDomains()
147+
{
148+
return [
149+
['example'],
150+
['foo.example'],
151+
['invalid'],
152+
['bar.invalid'],
153+
['localhost'],
154+
['lol.localhost'],
155+
['test'],
156+
['abc.test'],
157+
];
158+
}
159+
160+
/**
161+
* @dataProvider getTopLevelDomains
162+
*/
163+
public function testTopLevelDomainsPassValidationIfTldNotRequired($domain)
164+
{
165+
$this->validator->validate($domain, new Hostname(['requireTld' => false]));
166+
167+
$this->assertNoViolation();
168+
}
169+
170+
/**
171+
* @dataProvider getTopLevelDomains
172+
*/
173+
public function testTopLevelDomainsRaiseViolationIfTldRequired($domain)
174+
{
175+
$this->validator->validate($domain, new Hostname([
176+
'message' => 'myMessage',
177+
'requireTld' => true,
178+
]));
179+
180+
$this->buildViolation('myMessage')
181+
->setParameter('{{ value }}', '"'.$domain.'"')
182+
->setCode(Hostname::INVALID_HOSTNAME_ERROR)
183+
->assertRaised();
184+
}
185+
186+
public function getTopLevelDomains()
187+
{
188+
return [
189+
['com'],
190+
['net'],
191+
['org'],
192+
['etc'],
193+
];
194+
}
195+
196+
protected function createValidator()
197+
{
198+
return new HostnameValidator();
199+
}
200+
}

0 commit comments

Comments
 (0)