Get merging relative URL into a base URI working in the URI class.

This commit is contained in:
Lonnie Ezell 2015-11-13 14:25:35 -06:00
parent a40317fb9c
commit 5fa6238e2e
2 changed files with 214 additions and 27 deletions

View File

@ -473,7 +473,15 @@ class URI
{ {
$parts = parse_url($str); $parts = parse_url($str);
if (empty($parts['host']) && ! empty($parts['path']))
{
$parts['host'] = $parts['path'];
unset($parts['path']);
}
$this->applyParts($parts); $this->applyParts($parts);
return $this;
} }
//-------------------------------------------------------------------- //--------------------------------------------------------------------
@ -706,14 +714,18 @@ class URI
*/ */
protected function filterPath($path) protected function filterPath($path)
{ {
$orig = $path;
// Decode/normalize percent-encoded chars so // Decode/normalize percent-encoded chars so
// we can always have matching for Routes, etc. // we can always have matching for Routes, etc.
$path = urldecode($path); $path = urldecode($path);
// Remove dot segments // Remove dot segments
$path = str_replace('../', '/', $path); $path = $this->removeDotSegments($path);
$path = str_replace('./', '/', $path);
$path = str_replace('//', '/', $path); // Fix up some leading slash edge cases...
if (strpos($orig, './') === 0) $path = '/'. $path;
if (strpos($orig, '../') === 0) $path = '/'. $path;
// Encode characters // Encode characters
$path = preg_replace_callback( $path = preg_replace_callback(
@ -739,11 +751,11 @@ class URI
*/ */
protected function applyParts($parts) protected function applyParts($parts)
{ {
$this->host = isset($parts['host']) ? $parts['host'] : ''; if (! empty($parts['host'])) $this->host = $parts['host'];
$this->user = isset($parts['user']) ? $parts['user'] : ''; if (! empty($parts['user'])) $this->user = $parts['user'];
$this->path = isset($parts['path']) ? $this->filterPath($parts['path']) : ''; if (! empty($parts['path'])) $this->path = $this->filterPath($parts['path']);
$this->query = isset($parts['query']) ? $this->filterQuery($parts['query']) : ''; if (! empty($parts['query'])) $this->query = $this->filterQuery($parts['query']);
$this->fragment = isset($parts['fragment']) ? $this->filterQuery($parts['fragment']) : ''; if (! empty($parts['fragment'])) $this->fragment = $this->filterQuery($parts['fragment']);
// Scheme // Scheme
if (isset($parts['scheme'])) if (isset($parts['scheme']))
@ -791,21 +803,158 @@ class URI
*/ */
public function resolveRelativeURI(string $uri) public function resolveRelativeURI(string $uri)
{ {
$relative = new URI($uri); /*
* NOTE: We don't use removeDotSegments in this
* algorithm since it's already done by this line!
*/
$relative = new URI();
$relative->setURI($uri);
if ($relative->scheme() == $this->scheme()) if ($relative->scheme() == $this->scheme())
{ {
$relative->setScheme(''); $relative->setScheme('');
} }
$transformed = clone $relative;
// 5.2.2 Transform References // 5.2.2 Transform References
if (! empty($relative->scheme())) if (! empty($relative->scheme()))
{ {
$transformed->setScheme($relative->scheme()) $transformed->setScheme($relative->scheme())
->setAuth; ->setAuthority($relative->authority())
->setPath($relative->path())
->setQuery($relative->query());
}
else
{
if (! empty($relative->authority()))
{
$transformed->setAuthority($relative->authority())
->setPath($relative->path())
->setQuery($relative->query());
}
else
{
if ($relative->path() == '')
{
$transformed->setPath($this->path());
if (! is_null($relative->query()))
{
$transformed->setQuery($relative->query());
}
else
{
$transformed->setQuery($this->query());
}
}
else
{
if (substr($relative->path(), 0, 1) == '/')
{
$transformed->setPath($relative->path());
}
else
{
$transformed->setPath($this->mergePaths($this, $relative));
}
$transformed->setQuery($relative->query());
}
$transformed->setAuthority($this->authority());
}
$transformed->setScheme($this->scheme());
} }
die((string)$relative ."\n"); $transformed->setFragment($relative->fragment());
return $transformed;
}
//--------------------------------------------------------------------
/**
* Given 2 paths, will merge them according to rules set out in RFC 2986,
* Section 5.2
*
* @see http://tools.ietf.org/html/rfc3986#section-5.2.3
*
* @param $path1
* @param $path2
*/
protected function mergePaths(URI $base, URI $reference)
{
if (! empty($base->authority()) && empty($base->path()))
{
return '/'. ltrim($base->path(), '/ ');
}
$path = explode('/', $base->path());
if (empty($path[0])) unset($path[0]);
array_pop($path);
array_push($path, $reference->path());
return implode('/', $path);
}
//--------------------------------------------------------------------
/**
* Used when resolving and merging paths to correctly interpret and
* remove single and double dot segments from the path per
* RFC 3986 Section 5.2.4
*
* @see http://tools.ietf.org/html/rfc3986#section-5.2.4
*
* @param URI $uri
*/
public function removeDotSegments(string $path): string
{
if (empty($path) || $path == '/') return $path;
$output = [];
$input = explode('/', $path);
if (empty($input[0]))
{
unset($input[0]);
$input = array_values($input);
}
// This is not a perfect representation of the
// RFC, but matches most cases and is pretty
// much what Guzzle uses. Should be good enough
// for almost every real use case.
foreach ($input as $segment)
{
if ($segment == '..')
{
array_pop($output);
}
else if ($segment != '.' && $segment != '')
{
array_push($output, $segment);
}
}
$output = implode('/', $output);
$output = ltrim($output, '/ ');
if ($output != '/')
{
// Add leading slash if necessary
if (substr($path, 0, 1) == '/') $output = '/'. $output;
// Add trailing slash if necessary
if (substr($path, -1, 1) == '/') $output .= '/';
}
return $output;
} }
//-------------------------------------------------------------------- //--------------------------------------------------------------------

View File

@ -316,15 +316,6 @@ class URITest extends PHPUnit_Framework_TestCase
//-------------------------------------------------------------------- //--------------------------------------------------------------------
public function defaultResolutions()
{
return [
'replace_last' => ['q', 'http://a/b/c/q']
];
}
//--------------------------------------------------------------------
public function testSetAuthorityReconstitutes() public function testSetAuthorityReconstitutes()
{ {
$authority = 'me@foo.com:3000'; $authority = 'me@foo.com:3000';
@ -337,19 +328,66 @@ class URITest extends PHPUnit_Framework_TestCase
//-------------------------------------------------------------------- //--------------------------------------------------------------------
public function defaultDots()
{
return array(
array('/foo/..', '/'),
array('//foo//..', '/'),
array('/foo/../..', '/'),
array('/foo/../.', '/'),
array('/./foo/..', '/'),
array('/./foo', '/foo'),
array('/./foo/', '/foo/'),
array('/./foo/bar/baz/pho/../..', '/foo/bar'),
array('*', '*'),
array('/foo', '/foo'),
array('/abc/123/../foo/', '/abc/foo/'),
array('/a/b/c/./../../g', '/a/g'),
array('/b/c/./../../g', '/g'),
array('/b/c/./../../g', '/g'),
array('/c/./../../g', '/g'),
array('/./../../g', '/g'),
);
}
//--------------------------------------------------------------------
/**
* @dataProvider defaultDots
*/
public function testRemoveDotSegments($path, $expected)
{
$uri = new URI();
$this->assertEquals($expected, $uri->removeDotSegments($path));
}
//--------------------------------------------------------------------
public function defaultResolutions()
{
return [
['g', 'http://a/b/c/g'],
['g/', 'http://a/b/c/g/'],
['/g', 'http://a/g'],
['#s', 'http://a/b/c/d#s'],
];
}
//--------------------------------------------------------------------
/** /**
* @dataProvider defaultResolutions * @dataProvider defaultResolutions
* @group single
*/ */
public function testResolveRelativeURI($rel, $expected) public function testResolveRelativeURI($rel, $expected)
{ {
// $base = 'http://a/b/c/d;p?q'; $base = 'http://a/b/c/d';
//
// $uri = new URI($base); $uri = new URI($base);
//
// $uri->resolveRelativeURI($rel); $new = $uri->resolveRelativeURI($rel);
//
// $this->assertEquals($expected, (string)$uri); $this->assertEquals($expected, (string)$new);
} }
//-------------------------------------------------------------------- //--------------------------------------------------------------------