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
12namespace Symfony\Component\Validator\Tests\Constraints;
13
14use Symfony\Bridge\PhpUnit\DnsMock;
15use Symfony\Component\Validator\Constraints\Url;
16use Symfony\Component\Validator\Constraints\UrlValidator;
17use Symfony\Component\Validator\Test\ConstraintValidatorTestCase;
18
19/**
20 * @group dns-sensitive
21 */
22class UrlValidatorTest extends ConstraintValidatorTestCase
23{
24    protected function createValidator()
25    {
26        return new UrlValidator();
27    }
28
29    public function testNullIsValid()
30    {
31        $this->validator->validate(null, new Url());
32
33        $this->assertNoViolation();
34    }
35
36    public function testEmptyStringIsValid()
37    {
38        $this->validator->validate('', new Url());
39
40        $this->assertNoViolation();
41    }
42
43    public function testEmptyStringFromObjectIsValid()
44    {
45        $this->validator->validate(new EmailProvider(), new Url());
46
47        $this->assertNoViolation();
48    }
49
50    public function testExpectsStringCompatibleType()
51    {
52        $this->expectException('Symfony\Component\Validator\Exception\UnexpectedTypeException');
53        $this->validator->validate(new \stdClass(), new Url());
54    }
55
56    /**
57     * @dataProvider getValidUrls
58     */
59    public function testValidUrls($url)
60    {
61        $this->validator->validate($url, new Url());
62
63        $this->assertNoViolation();
64    }
65
66    public function getValidUrls()
67    {
68        return [
69            ['http://a.pl'],
70            ['http://www.example.com'],
71            ['http://www.example.com.'],
72            ['http://www.example.museum'],
73            ['https://example.com/'],
74            ['https://example.com:80/'],
75            ['http://examp_le.com'],
76            ['http://www.sub_domain.examp_le.com'],
77            ['http://www.example.coop/'],
78            ['http://www.test-example.com/'],
79            ['http://www.symfony.com/'],
80            ['http://symfony.fake/blog/'],
81            ['http://symfony.com/?'],
82            ['http://symfony.com/search?type=&q=url+validator'],
83            ['http://symfony.com/#'],
84            ['http://symfony.com/#?'],
85            ['http://www.symfony.com/doc/current/book/validation.html#supported-constraints'],
86            ['http://very.long.domain.name.com/'],
87            ['http://localhost/'],
88            ['http://myhost123/'],
89            ['http://127.0.0.1/'],
90            ['http://127.0.0.1:80/'],
91            ['http://[::1]/'],
92            ['http://[::1]:80/'],
93            ['http://[1:2:3::4:5:6:7]/'],
94            ['http://sãopaulo.com/'],
95            ['http://xn--sopaulo-xwa.com/'],
96            ['http://sãopaulo.com.br/'],
97            ['http://xn--sopaulo-xwa.com.br/'],
98            ['http://пример.испытание/'],
99            ['http://xn--e1afmkfd.xn--80akhbyknj4f/'],
100            ['http://مثال.إختبار/'],
101            ['http://xn--mgbh0fb.xn--kgbechtv/'],
102            ['http://例子.测试/'],
103            ['http://xn--fsqu00a.xn--0zwm56d/'],
104            ['http://例子.測試/'],
105            ['http://xn--fsqu00a.xn--g6w251d/'],
106            ['http://例え.テスト/'],
107            ['http://xn--r8jz45g.xn--zckzah/'],
108            ['http://مثال.آزمایشی/'],
109            ['http://xn--mgbh0fb.xn--hgbk6aj7f53bba/'],
110            ['http://실례.테스트/'],
111            ['http://xn--9n2bp8q.xn--9t4b11yi5a/'],
112            ['http://العربية.idn.icann.org/'],
113            ['http://xn--ogb.idn.icann.org/'],
114            ['http://xn--e1afmkfd.xn--80akhbyknj4f.xn--e1afmkfd/'],
115            ['http://xn--espaa-rta.xn--ca-ol-fsay5a/'],
116            ['http://xn--d1abbgf6aiiy.xn--p1ai/'],
117            ['http://☎.com/'],
118            ['http://username:password@symfony.com'],
119            ['http://user.name:password@symfony.com'],
120            ['http://user_name:pass_word@symfony.com'],
121            ['http://username:pass.word@symfony.com'],
122            ['http://user.name:pass.word@symfony.com'],
123            ['http://user-name@symfony.com'],
124            ['http://user_name@symfony.com'],
125            ['http://u%24er:password@symfony.com'],
126            ['http://user:pa%24%24word@symfony.com'],
127            ['http://symfony.com?'],
128            ['http://symfony.com?query=1'],
129            ['http://symfony.com/?query=1'],
130            ['http://symfony.com#'],
131            ['http://symfony.com#fragment'],
132            ['http://symfony.com/#fragment'],
133            ['http://symfony.com/#one_more%20test'],
134        ];
135    }
136
137    /**
138     * @dataProvider getInvalidUrls
139     */
140    public function testInvalidUrls($url)
141    {
142        $constraint = new Url([
143            'message' => 'myMessage',
144        ]);
145
146        $this->validator->validate($url, $constraint);
147
148        $this->buildViolation('myMessage')
149            ->setParameter('{{ value }}', '"'.$url.'"')
150            ->setCode(Url::INVALID_URL_ERROR)
151            ->assertRaised();
152    }
153
154    public function getInvalidUrls()
155    {
156        return [
157            ['example.com'],
158            ['://example.com'],
159            ['http ://example.com'],
160            ['http:/example.com'],
161            ['http://example.com::aa'],
162            ['http://example.com:aa'],
163            ['ftp://example.fr'],
164            ['faked://example.fr'],
165            ['http://127.0.0.1:aa/'],
166            ['ftp://[::1]/'],
167            ['http://[::1'],
168            ['http://hello.☎/'],
169            ['http://:password@symfony.com'],
170            ['http://:password@@symfony.com'],
171            ['http://username:passwordsymfony.com'],
172            ['http://usern@me:password@symfony.com'],
173            ['http://nota%hex:password@symfony.com'],
174            ['http://username:nota%hex@symfony.com'],
175            ['http://example.com/exploit.html?<script>alert(1);</script>'],
176            ['http://example.com/exploit.html?hel lo'],
177            ['http://example.com/exploit.html?not_a%hex'],
178            ['http://'],
179        ];
180    }
181
182    /**
183     * @dataProvider getValidCustomUrls
184     */
185    public function testCustomProtocolIsValid($url)
186    {
187        $constraint = new Url([
188            'protocols' => ['ftp', 'file', 'git'],
189        ]);
190
191        $this->validator->validate($url, $constraint);
192
193        $this->assertNoViolation();
194    }
195
196    public function getValidCustomUrls()
197    {
198        return [
199            ['ftp://example.com'],
200            ['file://127.0.0.1'],
201            ['git://[::1]/'],
202        ];
203    }
204
205    /**
206     * @dataProvider getCheckDns
207     * @requires function Symfony\Bridge\PhpUnit\DnsMock::withMockedHosts
208     */
209    public function testCheckDns($violation)
210    {
211        DnsMock::withMockedHosts(['example.com' => [['type' => $violation ? '' : 'A']]]);
212
213        $constraint = new Url([
214            'checkDNS' => 'ANY',
215            'dnsMessage' => 'myMessage',
216        ]);
217
218        $this->validator->validate('http://example.com', $constraint);
219
220        if (!$violation) {
221            $this->assertNoViolation();
222        } else {
223            $this->buildViolation('myMessage')
224                ->setParameter('{{ value }}', '"example.com"')
225                ->setCode(Url::INVALID_URL_ERROR)
226                ->assertRaised();
227        }
228    }
229
230    public function getCheckDns()
231    {
232        return [[true], [false]];
233    }
234
235    /**
236     * @dataProvider getCheckDnsTypes
237     * @requires function Symfony\Bridge\PhpUnit\DnsMock::withMockedHosts
238     */
239    public function testCheckDnsByType($type)
240    {
241        DnsMock::withMockedHosts(['example.com' => [['type' => $type]]]);
242
243        $constraint = new Url([
244            'checkDNS' => $type,
245            'dnsMessage' => 'myMessage',
246        ]);
247
248        $this->validator->validate('http://example.com', $constraint);
249
250        $this->assertNoViolation();
251    }
252
253    public function getCheckDnsTypes()
254    {
255        return [
256            ['ANY'],
257            ['A'],
258            ['A6'],
259            ['AAAA'],
260            ['CNAME'],
261            ['MX'],
262            ['NAPTR'],
263            ['NS'],
264            ['PTR'],
265            ['SOA'],
266            ['SRV'],
267            ['TXT'],
268        ];
269    }
270
271    /**
272     * @group legacy
273     */
274    public function testCheckDnsWithBoolean()
275    {
276        DnsMock::withMockedHosts(['example.com' => [['type' => 'A']]]);
277
278        $constraint = new Url([
279            'checkDNS' => true,
280            'dnsMessage' => 'myMessage',
281        ]);
282
283        $this->validator->validate('http://example.com', $constraint);
284
285        $this->assertNoViolation();
286    }
287
288    /**
289     * @requires function Symfony\Bridge\PhpUnit\DnsMock::withMockedHosts
290     */
291    public function testCheckDnsWithInvalidType()
292    {
293        $this->expectException('Symfony\Component\Validator\Exception\InvalidOptionsException');
294        DnsMock::withMockedHosts(['example.com' => [['type' => 'A']]]);
295
296        $constraint = new Url([
297            'checkDNS' => 'BOGUS',
298            'dnsMessage' => 'myMessage',
299        ]);
300
301        $this->validator->validate('http://example.com', $constraint);
302    }
303}
304
305class EmailProvider
306{
307    public function __toString()
308    {
309        return '';
310    }
311}
312