Updated Drupal to 8.6. This goes with the following updates because it's possible...
[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 PHPUnit\Framework\TestCase;
7
8 /**
9  * @group Utility
10  *
11  * @coversDefaultClass \Drupal\Component\Utility\UrlHelper
12  */
13 class UrlHelperTest extends TestCase {
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       'absolute fragment, no query' => [
273         'http://www.example.com/my/path#footer',
274         [
275           'path' => 'http://www.example.com/my/path',
276           'query' => [],
277           'fragment' => 'footer',
278         ],
279       ],
280       [
281         'http://',
282         [
283           'path' => '',
284           'query' => [],
285           'fragment' => '',
286         ],
287       ],
288       [
289         'https://',
290         [
291           'path' => '',
292           'query' => [],
293           'fragment' => '',
294         ],
295       ],
296       [
297         '/my/path?destination=home#footer',
298         [
299           'path' => '/my/path',
300           'query' => [
301             'destination' => 'home',
302           ],
303           'fragment' => 'footer',
304         ],
305       ],
306       'relative fragment, no query' => [
307         '/my/path#footer',
308         [
309           'path' => '/my/path',
310           'query' => [],
311           'fragment' => 'footer',
312         ],
313       ],
314     ];
315   }
316
317   /**
318    * Tests path encoding.
319    *
320    * @dataProvider providerTestEncodePath
321    * @covers ::encodePath
322    *
323    * @param string $path
324    *   A path to encode.
325    * @param string $expected
326    *   The expected encoded path.
327    */
328   public function testEncodePath($path, $expected) {
329     $encoded = UrlHelper::encodePath($path);
330     $this->assertEquals($expected, $encoded);
331   }
332
333   /**
334    * Provides data for self::testEncodePath().
335    *
336    * @return array
337    */
338   public static function providerTestEncodePath() {
339     return [
340       ['unencoded path with spaces', 'unencoded%20path%20with%20spaces'],
341       ['slashes/should/be/preserved', 'slashes/should/be/preserved'],
342     ];
343   }
344
345   /**
346    * Tests external versus internal paths.
347    *
348    * @dataProvider providerTestIsExternal
349    * @covers ::isExternal
350    *
351    * @param string $path
352    *   URL or path to test.
353    * @param bool $expected
354    *   Expected result.
355    */
356   public function testIsExternal($path, $expected) {
357     $isExternal = UrlHelper::isExternal($path);
358     $this->assertEquals($expected, $isExternal);
359   }
360
361   /**
362    * Provides data for self::testIsExternal().
363    *
364    * @return array
365    */
366   public static function providerTestIsExternal() {
367     return [
368       ['/internal/path', FALSE],
369       ['https://example.com/external/path', TRUE],
370       ['javascript://fake-external-path', FALSE],
371       // External URL without an explicit protocol.
372       ['//www.drupal.org/foo/bar?foo=bar&bar=baz&baz#foo', TRUE],
373       // Internal URL starting with a slash.
374       ['/www.drupal.org', FALSE],
375       // Simple external URLs.
376       ['http://example.com', TRUE],
377       ['https://example.com', TRUE],
378       ['http://drupal.org/foo/bar?foo=bar&bar=baz&baz#foo', TRUE],
379       ['//drupal.org', TRUE],
380       // Some browsers ignore or strip leading control characters.
381       ["\x00//www.example.com", TRUE],
382       ["\x08//www.example.com", TRUE],
383       ["\x1F//www.example.com", TRUE],
384       ["\n//www.example.com", TRUE],
385       // JSON supports decoding directly from UTF-8 code points.
386       [json_decode('"\u00AD"') . "//www.example.com", TRUE],
387       [json_decode('"\u200E"') . "//www.example.com", TRUE],
388       [json_decode('"\uE0020"') . "//www.example.com", TRUE],
389       [json_decode('"\uE000"') . "//www.example.com", TRUE],
390       // Backslashes should be normalized to forward.
391       ['\\\\example.com', TRUE],
392       // Local URLs.
393       ['node', FALSE],
394       ['/system/ajax', FALSE],
395       ['?q=foo:bar', FALSE],
396       ['node/edit:me', FALSE],
397       ['/drupal.org', FALSE],
398       ['<front>', FALSE],
399     ];
400   }
401
402   /**
403    * Tests bad protocol filtering and escaping.
404    *
405    * @dataProvider providerTestFilterBadProtocol
406    * @covers ::setAllowedProtocols
407    * @covers ::filterBadProtocol
408    *
409    * @param string $uri
410    *   Protocol URI.
411    * @param string $expected
412    *   Expected escaped value.
413    * @param array $protocols
414    *   Protocols to allow.
415    */
416   public function testFilterBadProtocol($uri, $expected, $protocols) {
417     UrlHelper::setAllowedProtocols($protocols);
418     $this->assertEquals($expected, UrlHelper::filterBadProtocol($uri));
419     // Multiple calls to UrlHelper::filterBadProtocol() do not cause double
420     // escaping.
421     $this->assertEquals($expected, UrlHelper::filterBadProtocol(UrlHelper::filterBadProtocol($uri)));
422   }
423
424   /**
425    * Provides data for self::testTestFilterBadProtocol().
426    *
427    * @return array
428    */
429   public static function providerTestFilterBadProtocol() {
430     return [
431       ['javascript://example.com?foo&bar', '//example.com?foo&amp;bar', ['http', 'https']],
432       // Test custom protocols.
433       ['http://example.com?foo&bar', '//example.com?foo&amp;bar', ['https']],
434       // Valid protocol.
435       ['http://example.com?foo&bar', 'http://example.com?foo&amp;bar', ['https', 'http']],
436       // Colon not part of the URL scheme.
437       ['/test:8888?foo&bar', '/test:8888?foo&amp;bar', ['http']],
438     ];
439   }
440
441   /**
442    * Tests dangerous url protocol filtering.
443    *
444    * @dataProvider providerTestStripDangerousProtocols
445    * @covers ::setAllowedProtocols
446    * @covers ::stripDangerousProtocols
447    *
448    * @param string $uri
449    *   Protocol URI.
450    * @param string $expected
451    *   Expected escaped value.
452    * @param array $protocols
453    *   Protocols to allow.
454    */
455   public function testStripDangerousProtocols($uri, $expected, $protocols) {
456     UrlHelper::setAllowedProtocols($protocols);
457     $stripped = UrlHelper::stripDangerousProtocols($uri);
458     $this->assertEquals($expected, $stripped);
459   }
460
461   /**
462    * Provides data for self::testStripDangerousProtocols().
463    *
464    * @return array
465    */
466   public static function providerTestStripDangerousProtocols() {
467     return [
468       ['javascript://example.com', '//example.com', ['http', 'https']],
469       // Test custom protocols.
470       ['http://example.com', '//example.com', ['https']],
471       // Valid protocol.
472       ['http://example.com', 'http://example.com', ['https', 'http']],
473       // Colon not part of the URL scheme.
474       ['/test:8888', '/test:8888', ['http']],
475     ];
476   }
477
478   /**
479    * Enhances test urls with schemes
480    *
481    * @param array $urls
482    *   The list of urls.
483    *
484    * @return array
485    *   A list of provider data with schemes.
486    */
487   protected function dataEnhanceWithScheme(array $urls) {
488     $url_schemes = ['http', 'https', 'ftp'];
489     $data = [];
490     foreach ($url_schemes as $scheme) {
491       foreach ($urls as $url) {
492         $data[] = [$url, $scheme];
493       }
494     }
495     return $data;
496   }
497
498   /**
499    * Enhances test urls with prefixes.
500    *
501    * @param array $urls
502    *   The list of urls.
503    *
504    * @return array
505    *   A list of provider data with prefixes.
506    */
507   protected function dataEnhanceWithPrefix(array $urls) {
508     $prefixes = ['', '/'];
509     $data = [];
510     foreach ($prefixes as $prefix) {
511       foreach ($urls as $url) {
512         $data[] = [$url, $prefix];
513       }
514     }
515     return $data;
516   }
517
518   /**
519    * Test detecting external urls that point to local resources.
520    *
521    * @param string $url
522    *   The external url to test.
523    * @param string $base_url
524    *   The base url.
525    * @param bool $expected
526    *   TRUE if an external URL points to this installation as determined by the
527    *   base url.
528    *
529    * @covers ::externalIsLocal
530    * @dataProvider providerTestExternalIsLocal
531    */
532   public function testExternalIsLocal($url, $base_url, $expected) {
533     $this->assertSame($expected, UrlHelper::externalIsLocal($url, $base_url));
534   }
535
536   /**
537    * Provider for local external url detection.
538    *
539    * @see \Drupal\Tests\Component\Utility\UrlHelperTest::testExternalIsLocal()
540    */
541   public function providerTestExternalIsLocal() {
542     return [
543       // Different mixes of trailing slash.
544       ['http://example.com', 'http://example.com', TRUE],
545       ['http://example.com/', 'http://example.com', TRUE],
546       ['http://example.com', 'http://example.com/', TRUE],
547       ['http://example.com/', 'http://example.com/', TRUE],
548       // Sub directory of site.
549       ['http://example.com/foo', 'http://example.com/', TRUE],
550       ['http://example.com/foo/bar', 'http://example.com/foo', TRUE],
551       ['http://example.com/foo/bar', 'http://example.com/foo/', TRUE],
552       // Different sub-domain.
553       ['http://example.com', 'http://www.example.com/', FALSE],
554       ['http://example.com/', 'http://www.example.com/', FALSE],
555       ['http://example.com/foo', 'http://www.example.com/', FALSE],
556       // Different TLD.
557       ['http://example.com', 'http://example.ca', FALSE],
558       ['http://example.com', 'http://example.ca/', FALSE],
559       ['http://example.com/', 'http://example.ca/', FALSE],
560       ['http://example.com/foo', 'http://example.ca', FALSE],
561       ['http://example.com/foo', 'http://example.ca/', FALSE],
562       // Different site path.
563       ['http://example.com/foo', 'http://example.com/bar', FALSE],
564       ['http://example.com', 'http://example.com/bar', FALSE],
565       ['http://example.com/bar', 'http://example.com/bar/', FALSE],
566       // Ensure \ is normalised to / since some browsers do that.
567       ['http://www.example.ca\@example.com', 'http://example.com', FALSE],
568       // Some browsers ignore or strip leading control characters.
569       ["\x00//www.example.ca", 'http://example.com', FALSE],
570     ];
571   }
572
573   /**
574    * Test invalid url arguments.
575    *
576    * @param string $url
577    *   The url to test.
578    * @param string $base_url
579    *   The base url.
580    *
581    * @covers ::externalIsLocal
582    * @dataProvider providerTestExternalIsLocalInvalid
583    */
584   public function testExternalIsLocalInvalid($url, $base_url) {
585     if (method_exists($this, 'expectException')) {
586       $this->expectException(\InvalidArgumentException::class);
587     }
588     else {
589       $this->setExpectedException(\InvalidArgumentException::class);
590     }
591     UrlHelper::externalIsLocal($url, $base_url);
592   }
593
594   /**
595    * Provides invalid argument data for local external url detection.
596    *
597    * @see \Drupal\Tests\Component\Utility\UrlHelperTest::testExternalIsLocalInvalid()
598    */
599   public function providerTestExternalIsLocalInvalid() {
600     return [
601       ['http://example.com/foo', ''],
602       ['http://example.com/foo', 'bar'],
603       ['http://example.com/foo', 'http://'],
604       // Invalid destination urls.
605       ['', 'http://example.com/foo'],
606       ['bar', 'http://example.com/foo'],
607       ['/bar', 'http://example.com/foo'],
608       ['bar/', 'http://example.com/foo'],
609       ['http://', 'http://example.com/foo'],
610     ];
611   }
612
613 }