Version 1
[yaffs-website] / web / core / tests / Drupal / Tests / Component / Utility / UrlHelperTest.php
1 <?php
2
3 namespace Drupal\Tests\Component\Utility;
4
5 use Drupal\Component\Utility\UrlHelper;
6 use Drupal\Tests\UnitTestCase;
7
8 /**
9  * @group Utility
10  *
11  * @coversDefaultClass \Drupal\Component\Utility\UrlHelper
12  */
13 class UrlHelperTest extends UnitTestCase {
14
15   /**
16    * Provides test data for testBuildQuery().
17    *
18    * @return array
19    */
20   public function providerTestBuildQuery() {
21     return [
22       [['a' => ' &#//+%20@۞'], 'a=%20%26%23//%2B%2520%40%DB%9E', 'Value was properly encoded.'],
23       [[' &#//+%20@۞' => 'a'], '%20%26%23%2F%2F%2B%2520%40%DB%9E=a', 'Key was properly encoded.'],
24       [['a' => '1', 'b' => '2', 'c' => '3'], 'a=1&b=2&c=3', 'Multiple values were properly concatenated.'],
25       [['a' => ['b' => '2', 'c' => '3'], 'd' => 'foo'], 'a%5Bb%5D=2&a%5Bc%5D=3&d=foo', 'Nested array was properly encoded.'],
26       [['foo' => NULL], 'foo', 'Simple parameters are properly added.'],
27     ];
28   }
29
30   /**
31    * Tests query building.
32    *
33    * @dataProvider providerTestBuildQuery
34    * @covers ::buildQuery
35    *
36    * @param array $query
37    *   The array of query parameters.
38    * @param string $expected
39    *   The expected query string.
40    * @param string $message
41    *   The assertion message.
42    */
43   public function testBuildQuery($query, $expected, $message) {
44     $this->assertEquals(UrlHelper::buildQuery($query), $expected, $message);
45   }
46
47   /**
48    * Data provider for testValidAbsolute().
49    *
50    * @return array
51    */
52   public function providerTestValidAbsoluteData() {
53     $urls = [
54       'example.com',
55       'www.example.com',
56       'ex-ample.com',
57       '3xampl3.com',
58       'example.com/parenthesis',
59       'example.com/index.html#pagetop',
60       'example.com:8080',
61       'subdomain.example.com',
62       'example.com/index.php/node',
63       'example.com/index.php/node?param=false',
64       'user@www.example.com',
65       'user:pass@www.example.com:8080/login.php?do=login&style=%23#pagetop',
66       '127.0.0.1',
67       'example.org?',
68       'john%20doe:secret:foo@example.org/',
69       'example.org/~,$\'*;',
70       'caf%C3%A9.example.org',
71       '[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html',
72     ];
73
74     return $this->dataEnhanceWithScheme($urls);
75   }
76
77   /**
78    * Tests valid absolute URLs.
79    *
80    * @dataProvider providerTestValidAbsoluteData
81    * @covers ::isValid
82    *
83    * @param string $url
84    *   The url to test.
85    * @param string $scheme
86    *   The scheme to test.
87    */
88   public function testValidAbsolute($url, $scheme) {
89     $test_url = $scheme . '://' . $url;
90     $valid_url = UrlHelper::isValid($test_url, TRUE);
91     $this->assertTrue($valid_url, $test_url . ' is a valid URL.');
92   }
93
94   /**
95    * Provides data for testInvalidAbsolute().
96    *
97    * @return array
98    */
99   public function providerTestInvalidAbsolute() {
100     $data = [
101       '',
102       'ex!ample.com',
103       'ex%ample.com',
104     ];
105     return $this->dataEnhanceWithScheme($data);
106   }
107
108   /**
109    * Tests invalid absolute URLs.
110    *
111    * @dataProvider providerTestInvalidAbsolute
112    * @covers ::isValid
113    *
114    * @param string $url
115    *   The url to test.
116    * @param string $scheme
117    *   The scheme to test.
118    */
119   public function testInvalidAbsolute($url, $scheme) {
120     $test_url = $scheme . '://' . $url;
121     $valid_url = UrlHelper::isValid($test_url, TRUE);
122     $this->assertFalse($valid_url, $test_url . ' is NOT a valid URL.');
123   }
124
125   /**
126    * Provides data for testValidRelative().
127    *
128    * @return array
129    */
130   public function providerTestValidRelativeData() {
131     $data = [
132       'paren(the)sis',
133       'index.html#pagetop',
134       'index.php/node',
135       'index.php/node?param=false',
136       'login.php?do=login&style=%23#pagetop',
137     ];
138
139     return $this->dataEnhanceWithPrefix($data);
140   }
141
142   /**
143    * Tests valid relative URLs.
144    *
145    * @dataProvider providerTestValidRelativeData
146    * @covers ::isValid
147    *
148    * @param string $url
149    *   The url to test.
150    * @param string $prefix
151    *   The prefix to test.
152    */
153   public function testValidRelative($url, $prefix) {
154     $test_url = $prefix . $url;
155     $valid_url = UrlHelper::isValid($test_url);
156     $this->assertTrue($valid_url, $test_url . ' is a valid URL.');
157   }
158
159   /**
160    * Provides data for testInvalidRelative().
161    *
162    * @return array
163    */
164   public function providerTestInvalidRelativeData() {
165     $data = [
166       'ex^mple',
167       'example<>',
168       'ex%ample',
169     ];
170     return $this->dataEnhanceWithPrefix($data);
171   }
172
173   /**
174    * Tests invalid relative URLs.
175    *
176    * @dataProvider providerTestInvalidRelativeData
177    * @covers ::isValid
178    *
179    * @param string $url
180    *   The url to test.
181    * @param string $prefix
182    *   The prefix to test.
183    */
184   public function testInvalidRelative($url, $prefix) {
185     $test_url = $prefix . $url;
186     $valid_url = UrlHelper::isValid($test_url);
187     $this->assertFalse($valid_url, $test_url . ' is NOT a valid URL.');
188   }
189
190   /**
191    * Tests query filtering.
192    *
193    * @dataProvider providerTestFilterQueryParameters
194    * @covers ::filterQueryParameters
195    *
196    * @param array $query
197    *   The array of query parameters.
198    * @param array $exclude
199    *   A list of $query array keys to remove. Use "parent[child]" to exclude
200    *   nested items.
201    * @param array $expected
202    *   An array containing query parameters.
203    */
204   public function testFilterQueryParameters($query, $exclude, $expected) {
205     $filtered = UrlHelper::filterQueryParameters($query, $exclude);
206     $this->assertEquals($expected, $filtered, 'The query was not properly filtered.');
207   }
208
209   /**
210    * Provides data to self::testFilterQueryParameters().
211    *
212    * @return array
213    */
214   public static function providerTestFilterQueryParameters() {
215     return [
216       // Test without an exclude filter.
217       [
218         'query' => ['a' => ['b' => 'c']],
219         'exclude' => [],
220         'expected' => ['a' => ['b' => 'c']],
221       ],
222       // Exclude the 'b' element.
223       [
224         'query' => ['a' => ['b' => 'c', 'd' => 'e']],
225         'exclude' => ['a[b]'],
226         'expected' => ['a' => ['d' => 'e']],
227       ],
228     ];
229   }
230
231   /**
232    * Tests url parsing.
233    *
234    * @dataProvider providerTestParse
235    * @covers ::parse
236    *
237    * @param string $url
238    *   URL to test.
239    * @param array $expected
240    *   Associative array with expected parameters.
241    */
242   public function testParse($url, $expected) {
243     $parsed = UrlHelper::parse($url);
244     $this->assertEquals($expected, $parsed, 'The URL was not properly parsed.');
245   }
246
247   /**
248    * Provides data for self::testParse().
249    *
250    * @return array
251    */
252   public static function providerTestParse() {
253     return [
254       [
255         'http://www.example.com/my/path',
256         [
257           'path' => 'http://www.example.com/my/path',
258           'query' => [],
259           'fragment' => '',
260         ],
261       ],
262       [
263         'http://www.example.com/my/path?destination=home#footer',
264         [
265           'path' => 'http://www.example.com/my/path',
266           'query' => [
267             'destination' => 'home',
268           ],
269           'fragment' => 'footer',
270         ],
271       ],
272       [
273         'http://',
274         [
275           'path' => '',
276           'query' => [],
277           'fragment' => '',
278         ],
279       ],
280       [
281         'https://',
282         [
283           'path' => '',
284           'query' => [],
285           'fragment' => '',
286         ],
287       ],
288       [
289         '/my/path?destination=home#footer',
290         [
291           'path' => '/my/path',
292           'query' => [
293             'destination' => 'home',
294           ],
295           'fragment' => 'footer',
296         ],
297       ],
298     ];
299   }
300
301   /**
302    * Tests path encoding.
303    *
304    * @dataProvider providerTestEncodePath
305    * @covers ::encodePath
306    *
307    * @param string $path
308    *   A path to encode.
309    * @param string $expected
310    *   The expected encoded path.
311    */
312   public function testEncodePath($path, $expected) {
313     $encoded = UrlHelper::encodePath($path);
314     $this->assertEquals($expected, $encoded);
315   }
316
317   /**
318    * Provides data for self::testEncodePath().
319    *
320    * @return array
321    */
322   public static function providerTestEncodePath() {
323     return [
324       ['unencoded path with spaces', 'unencoded%20path%20with%20spaces'],
325       ['slashes/should/be/preserved', 'slashes/should/be/preserved'],
326     ];
327   }
328
329   /**
330    * Tests external versus internal paths.
331    *
332    * @dataProvider providerTestIsExternal
333    * @covers ::isExternal
334    *
335    * @param string $path
336    *   URL or path to test.
337    * @param bool $expected
338    *   Expected result.
339    */
340   public function testIsExternal($path, $expected) {
341     $isExternal = UrlHelper::isExternal($path);
342     $this->assertEquals($expected, $isExternal);
343   }
344
345   /**
346    * Provides data for self::testIsExternal().
347    *
348    * @return array
349    */
350   public static function providerTestIsExternal() {
351     return [
352       ['/internal/path', FALSE],
353       ['https://example.com/external/path', TRUE],
354       ['javascript://fake-external-path', FALSE],
355       // External URL without an explicit protocol.
356       ['//www.drupal.org/foo/bar?foo=bar&bar=baz&baz#foo', TRUE],
357       // Internal URL starting with a slash.
358       ['/www.drupal.org', FALSE],
359       // Simple external URLs.
360       ['http://example.com', TRUE],
361       ['https://example.com', TRUE],
362       ['http://drupal.org/foo/bar?foo=bar&bar=baz&baz#foo', TRUE],
363       ['//drupal.org', TRUE],
364       // Some browsers ignore or strip leading control characters.
365       ["\x00//www.example.com", TRUE],
366       ["\x08//www.example.com", TRUE],
367       ["\x1F//www.example.com", TRUE],
368       ["\n//www.example.com", TRUE],
369       // JSON supports decoding directly from UTF-8 code points.
370       [json_decode('"\u00AD"') . "//www.example.com", TRUE],
371       [json_decode('"\u200E"') . "//www.example.com", TRUE],
372       [json_decode('"\uE0020"') . "//www.example.com", TRUE],
373       [json_decode('"\uE000"') . "//www.example.com", TRUE],
374       // Backslashes should be normalized to forward.
375       ['\\\\example.com', TRUE],
376       // Local URLs.
377       ['node', FALSE],
378       ['/system/ajax', FALSE],
379       ['?q=foo:bar', FALSE],
380       ['node/edit:me', FALSE],
381       ['/drupal.org', FALSE],
382       ['<front>', FALSE],
383     ];
384   }
385
386   /**
387    * Tests bad protocol filtering and escaping.
388    *
389    * @dataProvider providerTestFilterBadProtocol
390    * @covers ::setAllowedProtocols
391    * @covers ::filterBadProtocol
392    *
393    * @param string $uri
394    *    Protocol URI.
395    * @param string $expected
396    *    Expected escaped value.
397    * @param array $protocols
398    *    Protocols to allow.
399    */
400   public function testFilterBadProtocol($uri, $expected, $protocols) {
401     UrlHelper::setAllowedProtocols($protocols);
402     $this->assertEquals($expected, UrlHelper::filterBadProtocol($uri));
403     // Multiple calls to UrlHelper::filterBadProtocol() do not cause double
404     // escaping.
405     $this->assertEquals($expected, UrlHelper::filterBadProtocol(UrlHelper::filterBadProtocol($uri)));
406   }
407
408   /**
409    * Provides data for self::testTestFilterBadProtocol().
410    *
411    * @return array
412    */
413   public static function providerTestFilterBadProtocol() {
414     return [
415       ['javascript://example.com?foo&bar', '//example.com?foo&amp;bar', ['http', 'https']],
416       // Test custom protocols.
417       ['http://example.com?foo&bar', '//example.com?foo&amp;bar', ['https']],
418       // Valid protocol.
419       ['http://example.com?foo&bar', 'http://example.com?foo&amp;bar', ['https', 'http']],
420       // Colon not part of the URL scheme.
421       ['/test:8888?foo&bar', '/test:8888?foo&amp;bar', ['http']],
422     ];
423   }
424
425   /**
426    * Tests dangerous url protocol filtering.
427    *
428    * @dataProvider providerTestStripDangerousProtocols
429    * @covers ::setAllowedProtocols
430    * @covers ::stripDangerousProtocols
431    *
432    * @param string $uri
433    *    Protocol URI.
434    * @param string $expected
435    *    Expected escaped value.
436    * @param array $protocols
437    *    Protocols to allow.
438    */
439   public function testStripDangerousProtocols($uri, $expected, $protocols) {
440     UrlHelper::setAllowedProtocols($protocols);
441     $stripped = UrlHelper::stripDangerousProtocols($uri);
442     $this->assertEquals($expected, $stripped);
443   }
444
445   /**
446    * Provides data for self::testStripDangerousProtocols().
447    *
448    * @return array
449    */
450   public static function providerTestStripDangerousProtocols() {
451     return [
452       ['javascript://example.com', '//example.com', ['http', 'https']],
453       // Test custom protocols.
454       ['http://example.com', '//example.com', ['https']],
455       // Valid protocol.
456       ['http://example.com', 'http://example.com', ['https', 'http']],
457       // Colon not part of the URL scheme.
458       ['/test:8888', '/test:8888', ['http']],
459     ];
460   }
461
462   /**
463    * Enhances test urls with schemes
464    *
465    * @param array $urls
466    *   The list of urls.
467    *
468    * @return array
469    *   A list of provider data with schemes.
470    */
471   protected function dataEnhanceWithScheme(array $urls) {
472     $url_schemes = ['http', 'https', 'ftp'];
473     $data = [];
474     foreach ($url_schemes as $scheme) {
475       foreach ($urls as $url) {
476         $data[] = [$url, $scheme];
477       }
478     }
479     return $data;
480   }
481
482   /**
483    * Enhances test urls with prefixes.
484    *
485    * @param array $urls
486    *   The list of urls.
487    *
488    * @return array
489    *   A list of provider data with prefixes.
490    */
491   protected function dataEnhanceWithPrefix(array $urls) {
492     $prefixes = ['', '/'];
493     $data = [];
494     foreach ($prefixes as $prefix) {
495       foreach ($urls as $url) {
496         $data[] = [$url, $prefix];
497       }
498     }
499     return $data;
500   }
501
502   /**
503    * Test detecting external urls that point to local resources.
504    *
505    * @param string $url
506    *   The external url to test.
507    * @param string $base_url
508    *   The base url.
509    * @param bool $expected
510    *   TRUE if an external URL points to this installation as determined by the
511    *   base url.
512    *
513    * @covers ::externalIsLocal
514    * @dataProvider providerTestExternalIsLocal
515    */
516   public function testExternalIsLocal($url, $base_url, $expected) {
517     $this->assertSame($expected, UrlHelper::externalIsLocal($url, $base_url));
518   }
519
520   /**
521    * Provider for local external url detection.
522    *
523    * @see \Drupal\Tests\Component\Utility\UrlHelperTest::testExternalIsLocal()
524    */
525   public function providerTestExternalIsLocal() {
526     return [
527       // Different mixes of trailing slash.
528       ['http://example.com', 'http://example.com', TRUE],
529       ['http://example.com/', 'http://example.com', TRUE],
530       ['http://example.com', 'http://example.com/', TRUE],
531       ['http://example.com/', 'http://example.com/', TRUE],
532       // Sub directory of site.
533       ['http://example.com/foo', 'http://example.com/', TRUE],
534       ['http://example.com/foo/bar', 'http://example.com/foo', TRUE],
535       ['http://example.com/foo/bar', 'http://example.com/foo/', TRUE],
536       // Different sub-domain.
537       ['http://example.com', 'http://www.example.com/', FALSE],
538       ['http://example.com/', 'http://www.example.com/', FALSE],
539       ['http://example.com/foo', 'http://www.example.com/', FALSE],
540       // Different TLD.
541       ['http://example.com', 'http://example.ca', FALSE],
542       ['http://example.com', 'http://example.ca/', FALSE],
543       ['http://example.com/', 'http://example.ca/', FALSE],
544       ['http://example.com/foo', 'http://example.ca', FALSE],
545       ['http://example.com/foo', 'http://example.ca/', FALSE],
546       // Different site path.
547       ['http://example.com/foo', 'http://example.com/bar', FALSE],
548       ['http://example.com', 'http://example.com/bar', FALSE],
549       ['http://example.com/bar', 'http://example.com/bar/', FALSE],
550     ];
551   }
552
553   /**
554    * Test invalid url arguments.
555    *
556    * @param string $url
557    *   The url to test.
558    * @param string $base_url
559    *   The base url.
560    *
561    * @covers ::externalIsLocal
562    * @dataProvider providerTestExternalIsLocalInvalid
563    */
564   public function testExternalIsLocalInvalid($url, $base_url) {
565     $this->setExpectedException(\InvalidArgumentException::class);
566     UrlHelper::externalIsLocal($url, $base_url);
567   }
568
569   /**
570    * Provides invalid argument data for local external url detection.
571    *
572    * @see \Drupal\Tests\Component\Utility\UrlHelperTest::testExternalIsLocalInvalid()
573    */
574   public function providerTestExternalIsLocalInvalid() {
575     return [
576       ['http://example.com/foo', ''],
577       ['http://example.com/foo', 'bar'],
578       ['http://example.com/foo', 'http://'],
579       // Invalid destination urls.
580       ['', 'http://example.com/foo'],
581       ['bar', 'http://example.com/foo'],
582       ['/bar', 'http://example.com/foo'],
583       ['bar/', 'http://example.com/foo'],
584       ['http://', 'http://example.com/foo'],
585     ];
586   }
587
588 }