Chris@0: ' &#//+%20@۞'], 'a=%20%26%23//%2B%2520%40%DB%9E', 'Value was properly encoded.'], Chris@0: [[' &#//+%20@۞' => 'a'], '%20%26%23%2F%2F%2B%2520%40%DB%9E=a', 'Key was properly encoded.'], Chris@0: [['a' => '1', 'b' => '2', 'c' => '3'], 'a=1&b=2&c=3', 'Multiple values were properly concatenated.'], Chris@0: [['a' => ['b' => '2', 'c' => '3'], 'd' => 'foo'], 'a%5Bb%5D=2&a%5Bc%5D=3&d=foo', 'Nested array was properly encoded.'], Chris@0: [['foo' => NULL], 'foo', 'Simple parameters are properly added.'], Chris@0: ]; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Tests query building. Chris@0: * Chris@0: * @dataProvider providerTestBuildQuery Chris@0: * @covers ::buildQuery Chris@0: * Chris@0: * @param array $query Chris@0: * The array of query parameters. Chris@0: * @param string $expected Chris@0: * The expected query string. Chris@0: * @param string $message Chris@0: * The assertion message. Chris@0: */ Chris@0: public function testBuildQuery($query, $expected, $message) { Chris@0: $this->assertEquals(UrlHelper::buildQuery($query), $expected, $message); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Data provider for testValidAbsolute(). Chris@0: * Chris@0: * @return array Chris@0: */ Chris@0: public function providerTestValidAbsoluteData() { Chris@0: $urls = [ Chris@0: 'example.com', Chris@0: 'www.example.com', Chris@0: 'ex-ample.com', Chris@0: '3xampl3.com', Chris@0: 'example.com/parenthesis', Chris@0: 'example.com/index.html#pagetop', Chris@0: 'example.com:8080', Chris@0: 'subdomain.example.com', Chris@0: 'example.com/index.php/node', Chris@0: 'example.com/index.php/node?param=false', Chris@0: 'user@www.example.com', Chris@0: 'user:pass@www.example.com:8080/login.php?do=login&style=%23#pagetop', Chris@0: '127.0.0.1', Chris@0: 'example.org?', Chris@0: 'john%20doe:secret:foo@example.org/', Chris@0: 'example.org/~,$\'*;', Chris@0: 'caf%C3%A9.example.org', Chris@0: '[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html', Chris@0: ]; Chris@0: Chris@0: return $this->dataEnhanceWithScheme($urls); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Tests valid absolute URLs. Chris@0: * Chris@0: * @dataProvider providerTestValidAbsoluteData Chris@0: * @covers ::isValid Chris@0: * Chris@0: * @param string $url Chris@0: * The url to test. Chris@0: * @param string $scheme Chris@0: * The scheme to test. Chris@0: */ Chris@0: public function testValidAbsolute($url, $scheme) { Chris@0: $test_url = $scheme . '://' . $url; Chris@0: $valid_url = UrlHelper::isValid($test_url, TRUE); Chris@0: $this->assertTrue($valid_url, $test_url . ' is a valid URL.'); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Provides data for testInvalidAbsolute(). Chris@0: * Chris@0: * @return array Chris@0: */ Chris@0: public function providerTestInvalidAbsolute() { Chris@0: $data = [ Chris@0: '', Chris@0: 'ex!ample.com', Chris@0: 'ex%ample.com', Chris@0: ]; Chris@0: return $this->dataEnhanceWithScheme($data); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Tests invalid absolute URLs. Chris@0: * Chris@0: * @dataProvider providerTestInvalidAbsolute Chris@0: * @covers ::isValid Chris@0: * Chris@0: * @param string $url Chris@0: * The url to test. Chris@0: * @param string $scheme Chris@0: * The scheme to test. Chris@0: */ Chris@0: public function testInvalidAbsolute($url, $scheme) { Chris@0: $test_url = $scheme . '://' . $url; Chris@0: $valid_url = UrlHelper::isValid($test_url, TRUE); Chris@0: $this->assertFalse($valid_url, $test_url . ' is NOT a valid URL.'); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Provides data for testValidRelative(). Chris@0: * Chris@0: * @return array Chris@0: */ Chris@0: public function providerTestValidRelativeData() { Chris@0: $data = [ Chris@0: 'paren(the)sis', Chris@0: 'index.html#pagetop', Chris@0: 'index.php/node', Chris@0: 'index.php/node?param=false', Chris@0: 'login.php?do=login&style=%23#pagetop', Chris@0: ]; Chris@0: Chris@0: return $this->dataEnhanceWithPrefix($data); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Tests valid relative URLs. Chris@0: * Chris@0: * @dataProvider providerTestValidRelativeData Chris@0: * @covers ::isValid Chris@0: * Chris@0: * @param string $url Chris@0: * The url to test. Chris@0: * @param string $prefix Chris@0: * The prefix to test. Chris@0: */ Chris@0: public function testValidRelative($url, $prefix) { Chris@0: $test_url = $prefix . $url; Chris@0: $valid_url = UrlHelper::isValid($test_url); Chris@0: $this->assertTrue($valid_url, $test_url . ' is a valid URL.'); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Provides data for testInvalidRelative(). Chris@0: * Chris@0: * @return array Chris@0: */ Chris@0: public function providerTestInvalidRelativeData() { Chris@0: $data = [ Chris@0: 'ex^mple', Chris@0: 'example<>', Chris@0: 'ex%ample', Chris@0: ]; Chris@0: return $this->dataEnhanceWithPrefix($data); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Tests invalid relative URLs. Chris@0: * Chris@0: * @dataProvider providerTestInvalidRelativeData Chris@0: * @covers ::isValid Chris@0: * Chris@0: * @param string $url Chris@0: * The url to test. Chris@0: * @param string $prefix Chris@0: * The prefix to test. Chris@0: */ Chris@0: public function testInvalidRelative($url, $prefix) { Chris@0: $test_url = $prefix . $url; Chris@0: $valid_url = UrlHelper::isValid($test_url); Chris@0: $this->assertFalse($valid_url, $test_url . ' is NOT a valid URL.'); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Tests query filtering. Chris@0: * Chris@0: * @dataProvider providerTestFilterQueryParameters Chris@0: * @covers ::filterQueryParameters Chris@0: * Chris@0: * @param array $query Chris@0: * The array of query parameters. Chris@0: * @param array $exclude Chris@0: * A list of $query array keys to remove. Use "parent[child]" to exclude Chris@0: * nested items. Chris@0: * @param array $expected Chris@0: * An array containing query parameters. Chris@0: */ Chris@0: public function testFilterQueryParameters($query, $exclude, $expected) { Chris@0: $filtered = UrlHelper::filterQueryParameters($query, $exclude); Chris@0: $this->assertEquals($expected, $filtered, 'The query was not properly filtered.'); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Provides data to self::testFilterQueryParameters(). Chris@0: * Chris@0: * @return array Chris@0: */ Chris@0: public static function providerTestFilterQueryParameters() { Chris@0: return [ Chris@0: // Test without an exclude filter. Chris@0: [ Chris@0: 'query' => ['a' => ['b' => 'c']], Chris@0: 'exclude' => [], Chris@0: 'expected' => ['a' => ['b' => 'c']], Chris@0: ], Chris@0: // Exclude the 'b' element. Chris@0: [ Chris@0: 'query' => ['a' => ['b' => 'c', 'd' => 'e']], Chris@0: 'exclude' => ['a[b]'], Chris@0: 'expected' => ['a' => ['d' => 'e']], Chris@0: ], Chris@0: ]; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Tests url parsing. Chris@0: * Chris@0: * @dataProvider providerTestParse Chris@0: * @covers ::parse Chris@0: * Chris@0: * @param string $url Chris@0: * URL to test. Chris@0: * @param array $expected Chris@0: * Associative array with expected parameters. Chris@0: */ Chris@0: public function testParse($url, $expected) { Chris@0: $parsed = UrlHelper::parse($url); Chris@0: $this->assertEquals($expected, $parsed, 'The URL was not properly parsed.'); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Provides data for self::testParse(). Chris@0: * Chris@0: * @return array Chris@0: */ Chris@0: public static function providerTestParse() { Chris@0: return [ Chris@0: [ Chris@0: 'http://www.example.com/my/path', Chris@0: [ Chris@0: 'path' => 'http://www.example.com/my/path', Chris@0: 'query' => [], Chris@0: 'fragment' => '', Chris@0: ], Chris@0: ], Chris@0: [ Chris@0: 'http://www.example.com/my/path?destination=home#footer', Chris@0: [ Chris@0: 'path' => 'http://www.example.com/my/path', Chris@0: 'query' => [ Chris@0: 'destination' => 'home', Chris@0: ], Chris@0: 'fragment' => 'footer', Chris@0: ], Chris@0: ], Chris@0: 'absolute fragment, no query' => [ Chris@0: 'http://www.example.com/my/path#footer', Chris@0: [ Chris@0: 'path' => 'http://www.example.com/my/path', Chris@0: 'query' => [], Chris@0: 'fragment' => 'footer', Chris@0: ], Chris@0: ], Chris@0: [ Chris@0: 'http://', Chris@0: [ Chris@0: 'path' => '', Chris@0: 'query' => [], Chris@0: 'fragment' => '', Chris@0: ], Chris@0: ], Chris@0: [ Chris@0: 'https://', Chris@0: [ Chris@0: 'path' => '', Chris@0: 'query' => [], Chris@0: 'fragment' => '', Chris@0: ], Chris@0: ], Chris@0: [ Chris@0: '/my/path?destination=home#footer', Chris@0: [ Chris@0: 'path' => '/my/path', Chris@0: 'query' => [ Chris@0: 'destination' => 'home', Chris@0: ], Chris@0: 'fragment' => 'footer', Chris@0: ], Chris@0: ], Chris@0: 'relative fragment, no query' => [ Chris@0: '/my/path#footer', Chris@0: [ Chris@0: 'path' => '/my/path', Chris@0: 'query' => [], Chris@0: 'fragment' => 'footer', Chris@0: ], Chris@0: ], Chris@0: ]; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Tests path encoding. Chris@0: * Chris@0: * @dataProvider providerTestEncodePath Chris@0: * @covers ::encodePath Chris@0: * Chris@0: * @param string $path Chris@0: * A path to encode. Chris@0: * @param string $expected Chris@0: * The expected encoded path. Chris@0: */ Chris@0: public function testEncodePath($path, $expected) { Chris@0: $encoded = UrlHelper::encodePath($path); Chris@0: $this->assertEquals($expected, $encoded); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Provides data for self::testEncodePath(). Chris@0: * Chris@0: * @return array Chris@0: */ Chris@0: public static function providerTestEncodePath() { Chris@0: return [ Chris@0: ['unencoded path with spaces', 'unencoded%20path%20with%20spaces'], Chris@0: ['slashes/should/be/preserved', 'slashes/should/be/preserved'], Chris@0: ]; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Tests external versus internal paths. Chris@0: * Chris@0: * @dataProvider providerTestIsExternal Chris@0: * @covers ::isExternal Chris@0: * Chris@0: * @param string $path Chris@0: * URL or path to test. Chris@0: * @param bool $expected Chris@0: * Expected result. Chris@0: */ Chris@0: public function testIsExternal($path, $expected) { Chris@0: $isExternal = UrlHelper::isExternal($path); Chris@0: $this->assertEquals($expected, $isExternal); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Provides data for self::testIsExternal(). Chris@0: * Chris@0: * @return array Chris@0: */ Chris@0: public static function providerTestIsExternal() { Chris@0: return [ Chris@0: ['/internal/path', FALSE], Chris@0: ['https://example.com/external/path', TRUE], Chris@0: ['javascript://fake-external-path', FALSE], Chris@0: // External URL without an explicit protocol. Chris@0: ['//www.drupal.org/foo/bar?foo=bar&bar=baz&baz#foo', TRUE], Chris@0: // Internal URL starting with a slash. Chris@0: ['/www.drupal.org', FALSE], Chris@0: // Simple external URLs. Chris@0: ['http://example.com', TRUE], Chris@0: ['https://example.com', TRUE], Chris@0: ['http://drupal.org/foo/bar?foo=bar&bar=baz&baz#foo', TRUE], Chris@0: ['//drupal.org', TRUE], Chris@0: // Some browsers ignore or strip leading control characters. Chris@0: ["\x00//www.example.com", TRUE], Chris@0: ["\x08//www.example.com", TRUE], Chris@0: ["\x1F//www.example.com", TRUE], Chris@0: ["\n//www.example.com", TRUE], Chris@0: // JSON supports decoding directly from UTF-8 code points. Chris@0: [json_decode('"\u00AD"') . "//www.example.com", TRUE], Chris@0: [json_decode('"\u200E"') . "//www.example.com", TRUE], Chris@0: [json_decode('"\uE0020"') . "//www.example.com", TRUE], Chris@0: [json_decode('"\uE000"') . "//www.example.com", TRUE], Chris@0: // Backslashes should be normalized to forward. Chris@0: ['\\\\example.com', TRUE], Chris@0: // Local URLs. Chris@0: ['node', FALSE], Chris@0: ['/system/ajax', FALSE], Chris@0: ['?q=foo:bar', FALSE], Chris@0: ['node/edit:me', FALSE], Chris@0: ['/drupal.org', FALSE], Chris@0: ['', FALSE], Chris@0: ]; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Tests bad protocol filtering and escaping. Chris@0: * Chris@0: * @dataProvider providerTestFilterBadProtocol Chris@0: * @covers ::setAllowedProtocols Chris@0: * @covers ::filterBadProtocol Chris@0: * Chris@0: * @param string $uri Chris@0: * Protocol URI. Chris@0: * @param string $expected Chris@0: * Expected escaped value. Chris@0: * @param array $protocols Chris@0: * Protocols to allow. Chris@0: */ Chris@0: public function testFilterBadProtocol($uri, $expected, $protocols) { Chris@0: UrlHelper::setAllowedProtocols($protocols); Chris@0: $this->assertEquals($expected, UrlHelper::filterBadProtocol($uri)); Chris@0: // Multiple calls to UrlHelper::filterBadProtocol() do not cause double Chris@0: // escaping. Chris@0: $this->assertEquals($expected, UrlHelper::filterBadProtocol(UrlHelper::filterBadProtocol($uri))); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Provides data for self::testTestFilterBadProtocol(). Chris@0: * Chris@0: * @return array Chris@0: */ Chris@0: public static function providerTestFilterBadProtocol() { Chris@0: return [ Chris@0: ['javascript://example.com?foo&bar', '//example.com?foo&bar', ['http', 'https']], Chris@0: // Test custom protocols. Chris@0: ['http://example.com?foo&bar', '//example.com?foo&bar', ['https']], Chris@0: // Valid protocol. Chris@0: ['http://example.com?foo&bar', 'http://example.com?foo&bar', ['https', 'http']], Chris@0: // Colon not part of the URL scheme. Chris@0: ['/test:8888?foo&bar', '/test:8888?foo&bar', ['http']], Chris@0: ]; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Tests dangerous url protocol filtering. Chris@0: * Chris@0: * @dataProvider providerTestStripDangerousProtocols Chris@0: * @covers ::setAllowedProtocols Chris@0: * @covers ::stripDangerousProtocols Chris@0: * Chris@0: * @param string $uri Chris@0: * Protocol URI. Chris@0: * @param string $expected Chris@0: * Expected escaped value. Chris@0: * @param array $protocols Chris@0: * Protocols to allow. Chris@0: */ Chris@0: public function testStripDangerousProtocols($uri, $expected, $protocols) { Chris@0: UrlHelper::setAllowedProtocols($protocols); Chris@0: $stripped = UrlHelper::stripDangerousProtocols($uri); Chris@0: $this->assertEquals($expected, $stripped); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Provides data for self::testStripDangerousProtocols(). Chris@0: * Chris@0: * @return array Chris@0: */ Chris@0: public static function providerTestStripDangerousProtocols() { Chris@0: return [ Chris@0: ['javascript://example.com', '//example.com', ['http', 'https']], Chris@0: // Test custom protocols. Chris@0: ['http://example.com', '//example.com', ['https']], Chris@0: // Valid protocol. Chris@0: ['http://example.com', 'http://example.com', ['https', 'http']], Chris@0: // Colon not part of the URL scheme. Chris@0: ['/test:8888', '/test:8888', ['http']], Chris@0: ]; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Enhances test urls with schemes Chris@0: * Chris@0: * @param array $urls Chris@0: * The list of urls. Chris@0: * Chris@0: * @return array Chris@0: * A list of provider data with schemes. Chris@0: */ Chris@0: protected function dataEnhanceWithScheme(array $urls) { Chris@0: $url_schemes = ['http', 'https', 'ftp']; Chris@0: $data = []; Chris@0: foreach ($url_schemes as $scheme) { Chris@0: foreach ($urls as $url) { Chris@0: $data[] = [$url, $scheme]; Chris@0: } Chris@0: } Chris@0: return $data; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Enhances test urls with prefixes. Chris@0: * Chris@0: * @param array $urls Chris@0: * The list of urls. Chris@0: * Chris@0: * @return array Chris@0: * A list of provider data with prefixes. Chris@0: */ Chris@0: protected function dataEnhanceWithPrefix(array $urls) { Chris@0: $prefixes = ['', '/']; Chris@0: $data = []; Chris@0: foreach ($prefixes as $prefix) { Chris@0: foreach ($urls as $url) { Chris@0: $data[] = [$url, $prefix]; Chris@0: } Chris@0: } Chris@0: return $data; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Test detecting external urls that point to local resources. Chris@0: * Chris@0: * @param string $url Chris@0: * The external url to test. Chris@0: * @param string $base_url Chris@0: * The base url. Chris@0: * @param bool $expected Chris@0: * TRUE if an external URL points to this installation as determined by the Chris@0: * base url. Chris@0: * Chris@0: * @covers ::externalIsLocal Chris@0: * @dataProvider providerTestExternalIsLocal Chris@0: */ Chris@0: public function testExternalIsLocal($url, $base_url, $expected) { Chris@0: $this->assertSame($expected, UrlHelper::externalIsLocal($url, $base_url)); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Provider for local external url detection. Chris@0: * Chris@0: * @see \Drupal\Tests\Component\Utility\UrlHelperTest::testExternalIsLocal() Chris@0: */ Chris@0: public function providerTestExternalIsLocal() { Chris@0: return [ Chris@0: // Different mixes of trailing slash. Chris@0: ['http://example.com', 'http://example.com', TRUE], Chris@0: ['http://example.com/', 'http://example.com', TRUE], Chris@0: ['http://example.com', 'http://example.com/', TRUE], Chris@0: ['http://example.com/', 'http://example.com/', TRUE], Chris@0: // Sub directory of site. Chris@0: ['http://example.com/foo', 'http://example.com/', TRUE], Chris@0: ['http://example.com/foo/bar', 'http://example.com/foo', TRUE], Chris@0: ['http://example.com/foo/bar', 'http://example.com/foo/', TRUE], Chris@0: // Different sub-domain. Chris@0: ['http://example.com', 'http://www.example.com/', FALSE], Chris@0: ['http://example.com/', 'http://www.example.com/', FALSE], Chris@0: ['http://example.com/foo', 'http://www.example.com/', FALSE], Chris@0: // Different TLD. Chris@0: ['http://example.com', 'http://example.ca', FALSE], Chris@0: ['http://example.com', 'http://example.ca/', FALSE], Chris@0: ['http://example.com/', 'http://example.ca/', FALSE], Chris@0: ['http://example.com/foo', 'http://example.ca', FALSE], Chris@0: ['http://example.com/foo', 'http://example.ca/', FALSE], Chris@0: // Different site path. Chris@0: ['http://example.com/foo', 'http://example.com/bar', FALSE], Chris@0: ['http://example.com', 'http://example.com/bar', FALSE], Chris@0: ['http://example.com/bar', 'http://example.com/bar/', FALSE], Chris@17: // Ensure \ is normalised to / since some browsers do that. Chris@17: ['http://www.example.ca\@example.com', 'http://example.com', FALSE], Chris@17: // Some browsers ignore or strip leading control characters. Chris@17: ["\x00//www.example.ca", 'http://example.com', FALSE], Chris@0: ]; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Test invalid url arguments. Chris@0: * Chris@0: * @param string $url Chris@0: * The url to test. Chris@0: * @param string $base_url Chris@0: * The base url. Chris@0: * Chris@0: * @covers ::externalIsLocal Chris@0: * @dataProvider providerTestExternalIsLocalInvalid Chris@0: */ Chris@0: public function testExternalIsLocalInvalid($url, $base_url) { Chris@14: if (method_exists($this, 'expectException')) { Chris@14: $this->expectException(\InvalidArgumentException::class); Chris@14: } Chris@14: else { Chris@14: $this->setExpectedException(\InvalidArgumentException::class); Chris@14: } Chris@0: UrlHelper::externalIsLocal($url, $base_url); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Provides invalid argument data for local external url detection. Chris@0: * Chris@0: * @see \Drupal\Tests\Component\Utility\UrlHelperTest::testExternalIsLocalInvalid() Chris@0: */ Chris@0: public function providerTestExternalIsLocalInvalid() { Chris@0: return [ Chris@0: ['http://example.com/foo', ''], Chris@0: ['http://example.com/foo', 'bar'], Chris@0: ['http://example.com/foo', 'http://'], Chris@0: // Invalid destination urls. Chris@0: ['', 'http://example.com/foo'], Chris@0: ['bar', 'http://example.com/foo'], Chris@0: ['/bar', 'http://example.com/foo'], Chris@0: ['bar/', 'http://example.com/foo'], Chris@0: ['http://', 'http://example.com/foo'], Chris@0: ]; Chris@0: } Chris@0: Chris@0: }