Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 98f6a2e

Browse filesBrowse files
committed
Added DomainValidator
1 parent 519ba3c commit 98f6a2e
Copy full SHA for 98f6a2e

File tree

3 files changed

+273
-0
lines changed
Filter options

3 files changed

+273
-0
lines changed
+29Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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 <dpoddubny@gmail.com>
21+
*/
22+
class Domain extends Constraint
23+
{
24+
const INVALID_FORMAT_ERROR = '7057ffdb-0af4-4f7e-bd5e-e9acfa6d7a2d';
25+
26+
public $message = 'This value is not a valid domain';
27+
28+
public $requireTld = true;
29+
}
+79Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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 <dpoddubny@gmail.com>
21+
*/
22+
class DomainValidator extends ConstraintValidator
23+
{
24+
/**
25+
* https://tools.ietf.org/html/rfc2606
26+
* four domain names are reserved as listed and described below.
27+
*/
28+
private const RESERVED = [
29+
'example',
30+
'invalid',
31+
'localhost',
32+
'test',
33+
];
34+
35+
public function validate($value, Constraint $constraint)
36+
{
37+
if (!$constraint instanceof Domain) {
38+
throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Domain');
39+
}
40+
41+
if (null === $value || '' === $value) {
42+
return;
43+
}
44+
45+
if (!is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) {
46+
throw new UnexpectedValueException($value, 'string');
47+
}
48+
49+
$value = (string) $value;
50+
if ('' === $value) {
51+
return;
52+
}
53+
if (!$this->isValid($value) || ($constraint->requireTld && !$this->hasValidTld($value))) {
54+
$this->context->buildViolation($constraint->message)
55+
->setParameter('{{ value }}', $this->formatValue($value))
56+
->setCode(Domain::INVALID_FORMAT_ERROR)
57+
->addViolation();
58+
}
59+
}
60+
61+
private function isValid(string $domain): bool
62+
{
63+
return false !== filter_var($domain, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME);
64+
}
65+
66+
private function hasValidTld(string $domain): bool
67+
{
68+
return (false !== strpos($domain, '.')) && (!$this->hasReservedTld($domain));
69+
}
70+
71+
private function hasReservedTld(string $domain): bool
72+
{
73+
$parts = explode('.', $domain);
74+
$domain = end($parts);
75+
$domain = strtolower($domain);
76+
77+
return \in_array($domain, self::RESERVED);
78+
}
79+
}
+165Lines 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 <fabien@symfony.com>
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\Domain;
15+
use Symfony\Component\Validator\Constraints\DomainValidator;
16+
use Symfony\Component\Validator\Test\ConstraintValidatorTestCase;
17+
18+
/**
19+
* @author Dmitrii Poddubnyi <dpoddubny@gmail.com>
20+
*/
21+
class DomainValidatorTest extends ConstraintValidatorTestCase
22+
{
23+
protected function createValidator()
24+
{
25+
return new DomainValidator();
26+
}
27+
28+
public function testNullIsValid()
29+
{
30+
$this->validator->validate(null, new Domain());
31+
32+
$this->assertNoViolation();
33+
}
34+
35+
public function testEmptyStringIsValid()
36+
{
37+
$this->validator->validate('', new Domain());
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 Domain());
48+
}
49+
50+
/**
51+
* @dataProvider getValidTldDomains
52+
*/
53+
public function testValidTldDomainsPassValidationIfTldRequired($domain)
54+
{
55+
$this->validator->validate($domain, new Domain());
56+
57+
$this->assertNoViolation();
58+
}
59+
60+
/**
61+
* @dataProvider getValidTldDomains
62+
*/
63+
public function testValidTldDomainsPassValidationIfTldNotRequired($domain)
64+
{
65+
$this->validator->validate($domain, new Domain(['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 Domain([
90+
'message' => 'myMessage',
91+
]));
92+
93+
$this->buildViolation('myMessage')
94+
->setParameter('{{ value }}', '"'.$domain.'"')
95+
->setCode(Domain::INVALID_FORMAT_ERROR)
96+
->assertRaised();
97+
}
98+
99+
/**
100+
* @dataProvider getInvalidDomains
101+
*/
102+
public function testInvalidDomainsRaiseViolationIfTldNotRequired($domain)
103+
{
104+
$this->validator->validate($domain, new Domain([
105+
'message' => 'myMessage',
106+
'requireTld' => false,
107+
]));
108+
109+
$this->buildViolation('myMessage')
110+
->setParameter('{{ value }}', '"'.$domain.'"')
111+
->setCode(Domain::INVALID_FORMAT_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 Domain(['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 Domain([
142+
'message' => 'myMessage',
143+
'requireTld' => true,
144+
]));
145+
146+
$this->buildViolation('myMessage')
147+
->setParameter('{{ value }}', '"'.$domain.'"')
148+
->setCode(Domain::INVALID_FORMAT_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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.