diff --git a/src/Curl/Url.php b/src/Curl/Url.php index 3c5b0f3..866903c 100644 --- a/src/Curl/Url.php +++ b/src/Curl/Url.php @@ -156,6 +156,68 @@ class Url * Parse url into components of a URI as specified by RFC 3986. */ private function parseUrl($url) + { + // RFC 3986 - Parsing a URI Reference with a Regular Expression. + // ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))? + // 12 3 4 5 6 7 8 9 + // + // "http://www.ics.uci.edu/pub/ietf/uri/#Related" + // $1 = http: (scheme) + // $2 = http (scheme) + // $3 = //www.ics.uci.edu (ignore) + // $4 = www.ics.uci.edu (authority) + // $5 = /pub/ietf/uri/ (path) + // $6 = (ignore) + // $7 = (query) + // $8 = #Related (ignore) + // $9 = Related (fragment) + preg_match('/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/', $url, $output_array); + + $parts = array(); + if (isset($output_array['1']) && $output_array['1'] !== '') { + $parts['scheme'] = $output_array['1']; + } + if (isset($output_array['2']) && $output_array['2'] !== '') { + $parts['scheme'] = $output_array['2']; + } + if (isset($output_array['4']) && $output_array['4'] !== '') { + // authority = [ userinfo "@" ] host [ ":" port ] + $parts['host'] = $output_array['4']; + if (strpos($parts['host'], ':') !== false) { + $host_parts = explode(':', $output_array['4']); + $parts['port'] = array_pop($host_parts); + $parts['host'] = implode(':', $host_parts); + if (strpos($parts['host'], '@') !== false) { + $host_parts = explode('@', $parts['host']); + $parts['host'] = array_pop($host_parts); + $parts['user'] = implode('@', $host_parts); + if (strpos($parts['user'], ':') !== false) { + $user_parts = explode(':', $parts['user'], 2); + $parts['user'] = array_shift($user_parts); + $parts['pass'] = implode(':', $user_parts); + } + } + } + } + if (isset($output_array['5']) && $output_array['5'] !== '') { + $parts['path'] = $this->percentEncodeChars($output_array['5']); + } + if (isset($output_array['7']) && $output_array['7'] !== '') { + $parts['query'] = $output_array['7']; + } + if (isset($output_array['9']) && $output_array['9'] !== '') { + $parts['fragment'] = $output_array['9']; + } + return $parts; + } + + /** + * Percent-encode characters. + * + * Percent-encode characters to represent a data octet in a component when + * that octet's corresponding character is outside the allowed set. + */ + private function percentEncodeChars($chars) { // ALPHA = A-Z / a-z $alpha = 'A-Za-z'; @@ -177,14 +239,14 @@ class Url $hexdig .= 'a-f'; $pattern = '/(?:[^' . $unreserved . $sub_delims . preg_quote(':@%/?', '/') . ']++|%(?![' . $hexdig . ']{2}))/'; - $url = preg_replace_callback( + $percent_encoded_chars = preg_replace_callback( $pattern, function ($matches) { return rawurlencode($matches[0]); }, - $url + $chars ); - return parse_url($url); + return $percent_encoded_chars; } /** diff --git a/tests/PHPCurlClass/UrlTest.php b/tests/PHPCurlClass/UrlTest.php index e56687c..2b98c83 100644 --- a/tests/PHPCurlClass/UrlTest.php +++ b/tests/PHPCurlClass/UrlTest.php @@ -76,4 +76,73 @@ class UrlTest extends \PHPUnit\Framework\TestCase $url = new Url($original_url); $this->assertEquals($expected_url, $url); } + + public function testParseUrlSyntaxComponents() + { + // RFC 3986 - Syntax Components. + // The following are two example URIs and their component parts: + // + // foo://example.com:8042/over/there?name=ferret#nose + // \_/ \______________/\_________/ \_________/ \__/ + // | | | | | + // scheme authority path query fragment + $input_url = 'foo://example.com:8042/over/there?name=ferret#nose'; + $expected_parts = array( + 'scheme' => 'foo', + 'host' => 'example.com', + 'port' => '8042', + 'path' => '/over/there', + 'query' => 'name=ferret', + 'fragment' => 'nose', + ); + + $this->assertEquals($expected_parts, parse_url($input_url)); + + $reflector = new \ReflectionClass('Curl\Url'); + $reflection_method = $reflector->getMethod('parseUrl'); + $reflection_method->setAccessible(true); + + $url = new Url(null); + $result = $reflection_method->invoke($url, $input_url); + $this->assertEquals($expected_parts, $result); + } + + public function testParseUrlExample() + { + $input_url = 'http://username:password@hostname:9090/path?arg=value#anchor'; + $expected_parts = array( + 'scheme' => 'http', + 'host' => 'hostname', + 'port' => '9090', + 'user' => 'username', + 'pass' => 'password', + 'path' => '/path', + 'query' => 'arg=value', + 'fragment' => 'anchor', + ); + + $this->assertEquals($expected_parts, parse_url($input_url)); + + $reflector = new \ReflectionClass('Curl\Url'); + $reflection_method = $reflector->getMethod('parseUrl'); + $reflection_method->setAccessible(true); + + $url = new Url(null); + $result = $reflection_method->invoke($url, $input_url); + $this->assertEquals($expected_parts, $result); + } + + public function testIpv6NoPort() + { + $expected_url = 'http://[::1]/test'; + $actual_url = new Url($expected_url); + $this->assertEquals($expected_url, $actual_url); + } + + public function testIpv6Port() + { + $expected_url = 'http://[::1]:80/test'; + $actual_url = new Url($expected_url); + $this->assertEquals($expected_url, $actual_url); + } }