master
Anton Smirnov 2021-11-02 02:16:27 +02:00
parent 1677908239
commit 90d6a2acc4
2 changed files with 292 additions and 0 deletions

57
src/UrlPath.php Normal file
View File

@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
namespace Arokettu\Path;
final class UrlPath extends AbstractAbsolutePath
{
public static function parse(string $path, bool $strict = false): self
{
return new self($path, $strict);
}
protected function parsePath(string $path, bool $strict): void
{
$urlComponents = parse_url($path);
if ($urlComponents === false) {
throw new \InvalidArgumentException('Url is malformed');
}
$path = $urlComponents['path'] ?? '';
$prefix = '';
if (isset($urlComponents['scheme'])) {
$prefix .= $urlComponents['scheme'] . ':';
}
if (isset($urlComponents['host'])) {
$prefix .= '//';
if (isset($urlComponents['user'])) {
$prefix .= $urlComponents['user'];
if (isset($urlComponents['pass'])) {
$prefix .= ':' . $urlComponents['pass'];
}
$prefix .= '@';
}
$prefix .= $urlComponents['host'] . '/';
}
$components = explode('/', $path);
$parsedComponents = $this->normalize($components);
if ($parsedComponents->count() > 0 && $parsedComponents[0] === '..') {
if ($strict) {
throw new \InvalidArgumentException('Path went beyond root');
}
do {
$parsedComponents->shift();
} while ($parsedComponents[0] === '..');
}
$this->prefix = $prefix;
$this->components = $parsedComponents;
}
}

235
tests/UrlPathTest.php Normal file
View File

@ -0,0 +1,235 @@
<?php
declare(strict_types=1);
namespace Arokettu\Path\Tests;
use Arokettu\Path\RelativePath;
use Arokettu\Path\UnixPath;
use Arokettu\Path\UrlPath;
use PHPUnit\Framework\TestCase;
class UrlPathTest extends TestCase
{
public function testCreate(): void
{
$path = UrlPath::parse('https://example.com/i////./am/test/./skipme/../url');
self::assertEquals('https://example.com/i/am/test/url', $path->toString());
self::assertEquals('https://example.com/', $path->getPrefix());
$path = UrlPath::parse('https://example.com/../../i/am/test/url');
self::assertEquals('https://example.com/i/am/test/url', $path->toString());
self::assertEquals('https://example.com/', $path->getPrefix());
$path = UrlPath::parse('https://user:pass@example.com/i/am/test/url');
self::assertEquals('https://user:pass@example.com/i/am/test/url', $path->toString());
self::assertEquals('https://user:pass@example.com/', $path->getPrefix());
// trailing slash
$path = UrlPath::parse('https://example.com/i/./am/test/./skipme/../url/');
self::assertEquals('https://example.com/i/am/test/url/', $path->toString());
self::assertEquals('https://example.com/', $path->getPrefix());
// no trailing slash after host: normalize to slash
$path = UrlPath::parse('https://example.com');
self::assertEquals('https://example.com/', $path->toString());
self::assertEquals('https://example.com/', $path->getPrefix());
// trailing slash after host
$path = UrlPath::parse('https://example.com/');
self::assertEquals('https://example.com/', $path->toString());
self::assertEquals('https://example.com/', $path->getPrefix());
}
public function testCreateStrict(): void
{
$path = UrlPath::parse('https://example.com/i/./am/test/./skipme/../url', true);
self::assertEquals('https://example.com/i/am/test/url', $path->toString());
self::assertEquals('https://example.com/', $path->getPrefix());
$path = UrlPath::parse('https://example.com/i/./am/test/./skipme/../url/', true);
self::assertEquals('https://example.com/i/am/test/url/', $path->toString());
self::assertEquals('https://example.com/', $path->getPrefix());
$path = UrlPath::parse('https://user:pass@example.com/i/am/test/url', true);
self::assertEquals('https://user:pass@example.com/i/am/test/url', $path->toString());
self::assertEquals('https://user:pass@example.com/', $path->getPrefix());
}
public function testCreateInvalid(): void
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('Path went beyond root');
$path = UrlPath::parse('https://example.com/../../i/am/test/url', true);
self::assertEquals('https://example.com/i/am/test/url', $path->toString());
self::assertEquals('https://example.com/', $path->getPrefix());
}
public function testResolveRelative(): void
{
$path = UrlPath::parse('https://example.com/i/am/test/url');
$rp = new RelativePath('/i/am/test/relative/path');
self::assertEquals(
'https://example.com/i/am/test/relative/path',
$path->resolveRelative($rp)->toString()
);
$rp = new RelativePath('i/am/test/relative/path');
self::assertEquals(
'https://example.com/i/am/test/url/i/am/test/relative/path',
$path->resolveRelative($rp)->toString()
);
$rp = new RelativePath('../../i/am/test/relative/path');
self::assertEquals(
'https://example.com/i/am/i/am/test/relative/path',
$path->resolveRelative($rp)->toString()
);
$rp = new RelativePath('../../../../../../../../i/am/test/relative/path');
self::assertEquals(
'https://example.com/i/am/test/relative/path',
$path->resolveRelative($rp)->toString()
);
$rp = new RelativePath('..');
self::assertEquals(
'https://example.com/i/am/test',
$path->resolveRelative($rp)->toString()
);
$rp = new RelativePath('.');
self::assertEquals(
'https://example.com/i/am/test/url',
$path->resolveRelative($rp)->toString()
);
$rp = new RelativePath('../');
self::assertEquals(
'https://example.com/i/am/test/',
$path->resolveRelative($rp)->toString()
);
$rp = new RelativePath('./');
self::assertEquals(
'https://example.com/i/am/test/url/',
$path->resolveRelative($rp)->toString()
);
}
public function testResolveRelativeStrict(): void
{
$path = UrlPath::parse('https://example.com/i/am/test/url/');
$rp = new RelativePath('/i/am/test/relative/path');
self::assertEquals(
'https://example.com/i/am/test/relative/path',
$path->resolveRelative($rp, true)->toString()
);
$rp = new RelativePath('i/am/test/relative/path');
self::assertEquals(
'https://example.com/i/am/test/url/i/am/test/relative/path',
$path->resolveRelative($rp, true)->toString()
);
$rp = new RelativePath('../../i/am/test/relative/path');
self::assertEquals(
'https://example.com/i/am/i/am/test/relative/path',
$path->resolveRelative($rp, true)->toString()
);
$rp = new RelativePath('..');
self::assertEquals(
'https://example.com/i/am/test',
$path->resolveRelative($rp)->toString()
);
$rp = new RelativePath('.');
self::assertEquals(
'https://example.com/i/am/test/url',
$path->resolveRelative($rp)->toString()
);
}
public function testResolveRelativeStrictInvalid(): void
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('Relative path went beyond root');
$path = $path = UrlPath::parse('https://example.com/i/am/test/url/');
$rp = new RelativePath('../../../../../../../../i/am/test/relative/path');
$path->resolveRelative($rp, true);
}
public function testMakeRelative(): void
{
$paths = [
UrlPath::parse('udp://example.net/i/am/test/url'),
UrlPath::parse('udp://example.net/i/am/another/url/test'),
UrlPath::parse('udp://example.net/i/am/'),
UrlPath::parse('udp://example.net/i/am/test/url'), // different instance, same path
UrlPath::parse('udp://example.net/i/am/'), // different instance, same path
];
$matrix = [
[
'.',
'../../another/url/test',
'../../',
'.',
'../../',
],
[
'../../../test/url',
'.',
'../../../',
'../../../test/url',
'../../../',
],
[
'test/url',
'another/url/test',
'./',
'test/url',
'./',
],
[
'.',
'../../another/url/test',
'../../',
'.',
'../../',
],
[
'test/url',
'another/url/test',
'./',
'test/url',
'./',
],
];
foreach ($paths as $bpi => $bp) {
foreach ($paths as $tpi => $tp) {
$result = $matrix[$bpi][$tpi];
self::assertEquals(
$result,
$bp->makeRelative($tp)->toString(),
sprintf('Unexpected relative of base %s and target %s', \strval($bp), \strval($tp)),
);
}
}
}
public function testMakeRelativeWrongType(): void
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('You can only make relative path from paths of same type and same prefix');
UrlPath::parse('https://example.com/')->makeRelative(UnixPath::parse('/i/am/test/unix/path'));
}
}