Stream path

master
Anton Smirnov 2 years ago
parent 90d6a2acc4
commit e581028134

@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace Arokettu\Path;
final class StreamPath 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
{
if (!preg_match('@^[-.+a-zA-Z0-9]+://@', $path, $matches)) {
throw new \InvalidArgumentException('The path does not appear to be a PHP stream path');
}
$prefix = $matches[0];
$restOfPath = substr($path, \strlen($prefix));
$components = explode('/', $restOfPath);
$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;
}
}

@ -19,7 +19,7 @@ final class UrlPath extends AbstractAbsolutePath
throw new \InvalidArgumentException('Url is malformed');
}
$path = $urlComponents['path'] ?? '';
$urlPath = $urlComponents['path'] ?? '';
$prefix = '';
if (isset($urlComponents['scheme'])) {
@ -37,7 +37,7 @@ final class UrlPath extends AbstractAbsolutePath
$prefix .= $urlComponents['host'] . '/';
}
$components = explode('/', $path);
$components = explode('/', $urlPath);
$parsedComponents = $this->normalize($components);

@ -0,0 +1,282 @@
<?php
declare(strict_types=1);
namespace Arokettu\Path\Tests;
use Arokettu\Path\RelativePath;
use Arokettu\Path\StreamPath;
use Arokettu\Path\UnixPath;
use PHPUnit\Framework\TestCase;
class StreamPathTest extends TestCase
{
public function testCreate(): void
{
$path = StreamPath::parse('vfs://i/./am/./skipme/./.././test/./unix/path');
self::assertEquals('vfs://i/am/test/unix/path', $path->toString());
$path = StreamPath::parse('vfs://invalid/level/of/nesting/../../../../../../../../../../i/am/test/unix/path');
self::assertEquals('vfs://i/am/test/unix/path', $path->toString());
$path = StreamPath::parse('vfs://i/./am/./skipme/./.././test/./unix/path', true);
self::assertEquals('vfs://i/am/test/unix/path', $path->toString());
// root path
$path = StreamPath::parse('vfs://', true);
self::assertEquals('vfs://', $path->toString());
}
public function testCreateStrict(): void
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('Path went beyond root');
StreamPath::parse('vfs://invalid/level/of/nesting/../../../../../../../../../../i/am/test/unix/path', true);
}
public function testCreateInvalid(): void
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('The path does not appear to be a PHP stream path');
StreamPath::parse('not/starting/with/scheme', true);
}
public function testResolveRelative(): void
{
$path = StreamPath::parse('vfs://i/am/test/unix/path');
$rp = new RelativePath('/i/am/test/relative/path');
self::assertEquals(
'vfs://i/am/test/relative/path',
$path->resolveRelative($rp)->toString()
);
$rp = new RelativePath('i/am/test/relative/path');
self::assertEquals(
'vfs://i/am/test/unix/path/i/am/test/relative/path',
$path->resolveRelative($rp)->toString()
);
$rp = new RelativePath('../../i/am/test/relative/path');
self::assertEquals(
'vfs://i/am/test/i/am/test/relative/path',
$path->resolveRelative($rp)->toString()
);
$rp = new RelativePath('../../../../../../../../i/am/test/relative/path');
self::assertEquals(
'vfs://i/am/test/relative/path',
$path->resolveRelative($rp)->toString()
);
$rp = new RelativePath('..');
self::assertEquals(
'vfs://i/am/test/unix',
$path->resolveRelative($rp)->toString()
);
$rp = new RelativePath('.');
self::assertEquals(
'vfs://i/am/test/unix/path',
$path->resolveRelative($rp)->toString()
);
}
public function testResolveRelativeStrict(): void
{
$path = StreamPath::parse('vfs://i/am/test/unix/path');
$rp = new RelativePath('/i/am/test/relative/path');
self::assertEquals(
'vfs://i/am/test/relative/path',
$path->resolveRelative($rp, true)->toString()
);
$rp = new RelativePath('i/am/test/relative/path');
self::assertEquals(
'vfs://i/am/test/unix/path/i/am/test/relative/path',
$path->resolveRelative($rp, true)->toString()
);
$rp = new RelativePath('../../i/am/test/relative/path');
self::assertEquals(
'vfs://i/am/test/i/am/test/relative/path',
$path->resolveRelative($rp, true)->toString()
);
$rp = new RelativePath('..');
self::assertEquals(
'vfs://i/am/test/unix',
$path->resolveRelative($rp)->toString()
);
$rp = new RelativePath('.');
self::assertEquals(
'vfs://i/am/test/unix/path',
$path->resolveRelative($rp)->toString()
);
}
public function testResolveRelativeStrictInvalid(): void
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('Relative path went beyond root');
$path = StreamPath::parse('vfs://i/am/test/unix/path');
$rp4 = new RelativePath('../../../../../../../../i/am/test/relative/path');
$path->resolveRelative($rp4, true);
}
public function testMakeRelative(): void
{
$paths = [
StreamPath::parse('vfs://i/am/test/unix/path'),
StreamPath::parse('vfs://i/am/another/unix/test/path'),
StreamPath::parse('vfs://i/am'),
StreamPath::parse('vfs://i/am/test/unix/path'), // different instance, same path
];
$matrix = [
[
'.',
'../../../another/unix/test/path',
'../../..',
'.',
],
[
'../../../../test/unix/path',
'.',
'../../../..',
'../../../../test/unix/path',
],
[
'test/unix/path',
'another/unix/test/path',
'.',
'test/unix/path',
],
[
'.',
'../../../another/unix/test/path',
'../../..',
'.',
],
];
foreach ($paths as $bpi => $bp) {
foreach ($paths as $tpi => $tp) {
$result = $matrix[$bpi][$tpi];
self::assertEquals($result, $bp->makeRelative($tp)->toString());
}
}
}
public function testMakeRelativeTrailingSlash(): void
{
$paths = [
new StreamPath('vfs://path/path1'),
new StreamPath('vfs://path/path1/'),
new StreamPath('vfs://path/path2'),
new StreamPath('vfs://path/path2/'),
];
$matrix = [
[
'.',
'./',
'../path2',
'../path2/',
],
[
'.',
'./',
'../path2',
'../path2/',
],
[
'../path1',
'../path1/',
'.',
'./',
],
[
'../path1',
'../path1/',
'.',
'./',
],
];
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 testMakeRelativeRoot(): void
{
$paths = [
new StreamPath('vfs://'),
new StreamPath('vfs://'), // same path, different instance
new StreamPath('vfs://path'),
new StreamPath('vfs://path/'),
];
$matrix = [
[
'.',
'.',
'path',
'path/',
],
[
'.',
'.',
'path',
'path/',
],
[
'..',
'..',
'.',
'./',
],
[
'..',
'..',
'.',
'./',
],
];
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');
StreamPath::parse('vfs://i/am/test/unix/path')->makeRelative(UnixPath::parse('/i/am/test/unix/path'));
}
}

@ -159,7 +159,7 @@ class UrlPathTest extends TestCase
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('Relative path went beyond root');
$path = $path = UrlPath::parse('https://example.com/i/am/test/url/');
$path = UrlPath::parse('https://example.com/i/am/test/url/');
$rp = new RelativePath('../../../../../../../../i/am/test/relative/path');
$path->resolveRelative($rp, true);
}

Loading…
Cancel
Save