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

Skip to content

Commit 13d3e65

Browse files
committed
Added HostnameValidator
1 parent 7dee61c commit 13d3e65

File tree

3 files changed

+275
-0
lines changed

3 files changed

+275
-0
lines changed
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_FORMAT_ERROR',
28+
];
29+
30+
public $message = 'This value is not a valid hostname';
31+
public $requireTld = true;
32+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
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, __NAMESPACE__.'\Domain');
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, '.') && !$this->hasReservedTld($domain);
68+
}
69+
70+
private function hasReservedTld(string $domain): bool
71+
{
72+
$parts = explode('.', $domain);
73+
$domain = end($parts);
74+
$domain = mb_strtolower($domain);
75+
76+
return \in_array($domain, self::RESERVED_TLDS, true);
77+
}
78+
}
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
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+
protected function createValidator()
24+
{
25+
return new HostnameValidator();
26+
}
27+
28+
public function testNullIsValid()
29+
{
30+
$this->validator->validate(null, new Hostname());
31+
32+
$this->assertNoViolation();
33+
}
34+
35+
public function testEmptyStringIsValid()
36+
{
37+
$this->validator->validate('', new Hostname());
38+
39+
$this->assertNoViolation();
40+
}
41+
42+
/**
43+
* @expectedException \Symfony\Component\Validator\Exception\UnexpectedValueException
44+
*/
45+
public function testExpectsStringCompatibleType()
46+
{
47+
$this->validator->validate(new \stdClass(), new Hostname());
48+
}
49+
50+
/**
51+
* @dataProvider getValidTldDomains
52+
*/
53+
public function testValidTldDomainsPassValidationIfTldRequired($domain)
54+
{
55+
$this->validator->validate($domain, new Hostname());
56+
57+
$this->assertNoViolation();
58+
}
59+
60+
/**
61+
* @dataProvider getValidTldDomains
62+
*/
63+
public function testValidTldDomainsPassValidationIfTldNotRequired($domain)
64+
{
65+
$this->validator->validate($domain, new Hostname(['requireTld' => false]));
66+
67+
$this->assertNoViolation();
68+
}
69+
70+
public function getValidTldDomains()
71+
{
72+
return [
73+
['symfony.com'],
74+
['example.co.uk'],
75+
['example.fr'],
76+
['example.com'],
77+
['xn--diseolatinoamericano-66b.com'],
78+
['xn--ggle-0nda.com'],
79+
['www.xn--simulateur-prt-2kb.fr'],
80+
[sprintf('%s.com', str_repeat('a', 20))],
81+
];
82+
}
83+
84+
/**
85+
* @dataProvider getInvalidDomains
86+
*/
87+
public function testInvalidDomainsRaiseViolationIfTldRequired($domain)
88+
{
89+
$this->validator->validate($domain, new Hostname([
90+
'message' => 'myMessage',
91+
]));
92+
93+
$this->buildViolation('myMessage')
94+
->setParameter('{{ value }}', '"'.$domain.'"')
95+
->setCode(Hostname::INVALID_HOSTNAME_ERROR)
96+
->assertRaised();
97+
}
98+
99+
/**
100+
* @dataProvider getInvalidDomains
101+
*/
102+
public function testInvalidDomainsRaiseViolationIfTldNotRequired($domain)
103+
{
104+
$this->validator->validate($domain, new Hostname([
105+
'message' => 'myMessage',
106+
'requireTld' => false,
107+
]));
108+
109+
$this->buildViolation('myMessage')
110+
->setParameter('{{ value }}', '"'.$domain.'"')
111+
->setCode(Hostname::INVALID_HOSTNAME_ERROR)
112+
->assertRaised();
113+
}
114+
115+
public function getInvalidDomains()
116+
{
117+
return [
118+
['acme..com'],
119+
['qq--.com'],
120+
['-example.com'],
121+
['example-.com'],
122+
[sprintf('%s.com', str_repeat('a', 300))],
123+
];
124+
}
125+
126+
/**
127+
* @dataProvider getReservedDomains
128+
*/
129+
public function testReservedDomainsPassValidationIfTldNotRequired($domain)
130+
{
131+
$this->validator->validate($domain, new Hostname(['requireTld' => false]));
132+
133+
$this->assertNoViolation();
134+
}
135+
136+
/**
137+
* @dataProvider getReservedDomains
138+
*/
139+
public function testReservedDomainsRaiseViolationIfTldRequired($domain)
140+
{
141+
$this->validator->validate($domain, new Hostname([
142+
'message' => 'myMessage',
143+
'requireTld' => true,
144+
]));
145+
146+
$this->buildViolation('myMessage')
147+
->setParameter('{{ value }}', '"'.$domain.'"')
148+
->setCode(Hostname::INVALID_HOSTNAME_ERROR)
149+
->assertRaised();
150+
}
151+
152+
public function getReservedDomains()
153+
{
154+
return [
155+
['example'],
156+
['foo.example'],
157+
['invalid'],
158+
['bar.invalid'],
159+
['localhost'],
160+
['lol.localhost'],
161+
['test'],
162+
['abc.test'],
163+
];
164+
}
165+
}

0 commit comments

Comments
 (0)