diff --git a/CHANGELOG.md b/CHANGELOG.md index 04d1b7c..3c88570 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,26 @@ All Notable changes to `league-uri-parser` will be documented in this file +## 1.1.0 - TBD + +### Added + +- `League\Uri\build` function de build and URI from the result from `League\Uri\Parser::__invoke` or `parse_url` +- `League\Uri\parse` function version of `League\Uri\Parser::__invoke` +- `League\Uri\is_host` function version of `League\Uri\Parser::isHost` + +### Fixed + +- None + +### Deprecated + +- None + +### Removed + +- None + ## 1.0.5 - 2017-04-19 ### Added diff --git a/composer.json b/composer.json index 13f3ce4..b54f22c 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,8 @@ "autoload": { "psr-4": { "League\\Uri\\": "src" - } + }, + "files": ["src/functions_include.php"] }, "autoload-dev": { "psr-4": { diff --git a/phpunit.xml b/phpunit.xml index c9c1c91..4962403 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -20,6 +20,9 @@ src + + src + diff --git a/src/functions.php b/src/functions.php new file mode 100644 index 0000000..b2e5cbd --- /dev/null +++ b/src/functions.php @@ -0,0 +1,109 @@ + + * @license https://github.com/thephpleague/uri-parser/blob/master/LICENSE (MIT License) + * @version 1.1.0 + * @link https://github.com/thephpleague/uri-parser/ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +declare(strict_types=1); + +namespace League\Uri; + +/** + * Returns whether the URI host component is valid according to RFC3986. + * + * @see https://tools.ietf.org/html/rfc3986#section-3.2.2 + * @see Parser::isHost() + * + * @param string $host + * + * @return bool + */ +function is_host(string $host): bool +{ + static $parser; + + $parser = $parser ?? new Parser(); + + return $parser->isHost($host); +} + +/** + * Parse an URI string into its components. + * + * This method parses a URL and returns an associative array containing any + * of the various components of the URL that are present. + * + * @see https://tools.ietf.org/html/rfc3986 + * @see https://tools.ietf.org/html/rfc3986#section-2 + * @see Parser::__invoke() + * + * @param string $uri + * + * @throws Exception if the URI contains invalid characters + * + * @return array + */ +function parse(string $uri): array +{ + static $parser; + + $parser = $parser ?? new Parser(); + + return $parser($uri); +} + +/** + * Generate the URI string representation from its hash representation + * returned by Parser::__invoke() or PHP's parse_url + * + * If you supply your own hash you are responsible for providing + * valid components without their URI delimiters. + * + * For security reason the pass component has been deprecated + * as per RFC3986 and is never returned in the URI string + * + * @see https://tools.ietf.org/html/rfc3986#section-5.3 + * @see https://tools.ietf.org/html/rfc3986#section-7.5 + * + * @param array $components + * + * @return string + */ +function build(array $components): string +{ + $uri = $components['path'] ?? ''; + if (isset($components['query'])) { + $uri .= '?'.$components['query']; + } + + if (isset($components['fragment'])) { + $uri .= '#'.$components['fragment']; + } + + if (isset($components['host'])) { + $authority = $components['host']; + if (isset($components['port'])) { + $authority .= ':'.$components['port']; + } + + if (isset($components['user'])) { + $authority = $components['user'].'@'.$authority; + } + + $uri = '//'.$authority.$uri; + } + + if (isset($components['scheme'])) { + return $components['scheme'].':'.$uri; + } + + return $uri; +} diff --git a/src/functions_include.php b/src/functions_include.php new file mode 100644 index 0000000..18e3876 --- /dev/null +++ b/src/functions_include.php @@ -0,0 +1,6 @@ +assertSame($expected, $components); + $this->assertSame($expected, parse($uri)); } public function validUriProvider() @@ -646,7 +648,7 @@ public function validUriProvider() public function testParseFailed($uri) { $this->expectException(Exception::class); - (new Parser())($uri); + parse($uri); } public function invalidUriProvider() @@ -679,7 +681,7 @@ public function invalidUriProvider() */ public function testHost($host, $expected) { - $this->assertSame($expected, (new Parser())->isHost($host)); + $this->assertSame($expected, is_host($host)); } public function validHostProvider() @@ -710,4 +712,208 @@ public function validHostProvider() 'non idn like host #issue 5 (3)' => ['om--tat-sat.co.uk', true], ]; } + + /** + * @dataProvider buildUriProvider + * @param string $uri + * @param string $expected + */ + public function testBuild($uri, $expected) + { + $this->assertSame($expected, build(parse($uri))); + } + + public function buildUriProvider() + { + return [ + 'complete URI' => [ + 'scheme://user:pass@host:81/path?query#fragment', + 'scheme://user@host:81/path?query#fragment', + ], + 'URI is not normalized' => [ + 'ScheMe://user:pass@HoSt:81/path?query#fragment', + 'ScheMe://user@HoSt:81/path?query#fragment', + ], + 'URI without scheme' => [ + '//user:pass@HoSt:81/path?query#fragment', + '//user@HoSt:81/path?query#fragment', + ], + 'URI without empty authority only' => [ + '//', + '//', + ], + 'URI without userinfo' => [ + 'scheme://HoSt:81/path?query#fragment', + 'scheme://HoSt:81/path?query#fragment', + ], + 'URI with empty userinfo' => [ + 'scheme://@HoSt:81/path?query#fragment', + 'scheme://@HoSt:81/path?query#fragment', + ], + 'URI without port' => [ + 'scheme://user:pass@host/path?query#fragment', + 'scheme://user@host/path?query#fragment', + ], + 'URI with an empty port' => [ + 'scheme://user:pass@host:/path?query#fragment', + 'scheme://user@host/path?query#fragment', + ], + 'URI without user info and port' => [ + 'scheme://host/path?query#fragment', + 'scheme://host/path?query#fragment', + ], + 'URI with host IP' => [ + 'scheme://10.0.0.2/p?q#f', + 'scheme://10.0.0.2/p?q#f', + ], + 'URI with scoped IP' => [ + 'scheme://[fe80:1234::%251]/p?q#f', + 'scheme://[fe80:1234::%251]/p?q#f', + ], + 'URI without authority' => [ + 'scheme:path?query#fragment', + 'scheme:path?query#fragment', + ], + 'URI without authority and scheme' => [ + '/path', + '/path', + ], + 'URI with empty host' => [ + 'scheme:///path?query#fragment', + 'scheme:///path?query#fragment', + ], + 'URI with empty host and without scheme' => [ + '///path?query#fragment', + '///path?query#fragment', + ], + 'URI without path' => [ + 'scheme://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]?query#fragment', + 'scheme://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]?query#fragment', + ], + 'URI without path and scheme' => [ + '//[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]?query#fragment', + '//[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]?query#fragment', + ], + 'URI without scheme with IPv6 host and port' => [ + '//[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:42?query#fragment', + '//[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:42?query#fragment', + ], + 'complete URI without scheme' => [ + '//user@[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:42?q#f', + '//user@[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:42?q#f', + ], + 'URI without authority and query' => [ + 'scheme:path#fragment', + 'scheme:path#fragment', + ], + 'URI with empty query' => [ + 'scheme:path?#fragment', + 'scheme:path?#fragment', + ], + 'URI with query only' => [ + '?query', + '?query', + ], + 'URI without fragment' => [ + 'tel:05000', + 'tel:05000', + ], + 'URI with empty fragment' => [ + 'scheme:path#', + 'scheme:path#', + ], + 'URI with fragment only' => [ + '#fragment', + '#fragment', + ], + 'URI with empty fragment only' => [ + '#', + '#', + ], + 'URI without authority 2' => [ + 'path#fragment', + 'path#fragment', + ], + 'URI with empty query and fragment' => [ + '?#', + '?#', + ], + 'URI with absolute path' => [ + '/?#', + '/?#', + ], + 'URI with absolute authority' => [ + 'https://thephpleague.com./p?#f', + 'https://thephpleague.com./p?#f', + ], + 'URI with absolute path only' => [ + '/', + '/', + ], + 'URI with empty query only' => [ + '?', + '?', + ], + 'relative path' => [ + '../relative/path', + '../relative/path', + ], + 'complex authority' => [ + 'http://a_.!~*\'(-)n0123Di%25%26:pass;:&=+$,word@www.zend.com', + 'http://a_.!~*\'(-)n0123Di%25%26@www.zend.com', + ], + 'complex authority without scheme' => [ + '//a_.!~*\'(-)n0123Di%25%26:pass;:&=+$,word@www.zend.com', + '//a_.!~*\'(-)n0123Di%25%26@www.zend.com', + ], + 'single word is a path' => [ + 'http', + 'http', + ], + 'URI scheme with an empty authority' => [ + 'http://', + 'http://', + ], + 'single word is a path, no' => [ + 'http:::/path', + 'http:::/path', + ], + 'fragment with pseudo segment' => [ + 'http://example.com#foo=1/bar=2', + 'http://example.com#foo=1/bar=2', + ], + 'empty string' => [ + '', + '', + ], + 'complex URI' => [ + 'htà+d/s:totot', + 'htà+d/s:totot', + ], + 'scheme only URI' => [ + 'http:', + 'http:', + ], + 'RFC3986 LDAP example' => [ + 'ldap://[2001:db8::7]/c=GB?objectClass?one', + 'ldap://[2001:db8::7]/c=GB?objectClass?one', + ], + 'RFC3987 example' => [ + 'http://bébé.bé./有词法别名.zh', + 'http://bébé.bé./有词法别名.zh', + ], + 'colon detection respect RFC3986 (1)' => [ + 'http://example.org/hello:12?foo=bar#test', + 'http://example.org/hello:12?foo=bar#test', + ], + 'colon detection respect RFC3986 (2)' => [ + '/path/to/colon:34', + '/path/to/colon:34', + ], + 'scheme with hyphen' => [ + 'android-app://org.wikipedia/http/en.m.wikipedia.org/wiki/The_Hitchhiker%27s_Guide_to_the_Galaxy', + 'android-app://org.wikipedia/http/en.m.wikipedia.org/wiki/The_Hitchhiker%27s_Guide_to_the_Galaxy', + ], + ]; + } }