Fix #625: Update Url class to handle IPv6 addresses with bracket notation.

This commit is contained in:
Zach Borboa
2020-04-22 22:18:47 -07:00
parent 1f62ac4428
commit 75c9fc144b
2 changed files with 134 additions and 3 deletions
+65 -3
View File
@@ -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 = <undefined> (ignore)
// $7 = <undefined> (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;
}
/**
+69
View File
@@ -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);
}
}