diff --git a/CHANGELOG.md b/CHANGELOG.md index 93132a7..082c86f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add missing property in UriResolverTest ([#743](https://github.com/jsonrainbow/json-schema/pull/743)) - Correct casing of paths used in tests ([#745](https://github.com/jsonrainbow/json-schema/pull/745)) - Resolve deprecations of optional parameter ([#752](https://github.com/jsonrainbow/json-schema/pull/752)) +- Fix wrong combined paths when traversing upward, fixes #557 ([#652](https://github.com/jsonrainbow/json-schema/pull/652)) ### Changed - Bump to minimum PHP 7.2 ([#746](https://github.com/jsonrainbow/json-schema/pull/746)) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index cf92361..46ed739 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1030,21 +1030,11 @@ parameters: count: 1 path: src/JsonSchema/Uri/UriResolver.php - - - message: "#^Offset 0 does not exist on array\\{0\\?\\: string, 1\\?\\: non\\-falsy\\-string\\}\\.$#" - count: 1 - path: src/JsonSchema/Uri/UriResolver.php - - message: "#^Parameter \\#1 \\$uri of method JsonSchema\\\\Uri\\\\UriResolver\\:\\:parse\\(\\) expects string, string\\|null given\\.$#" count: 1 path: src/JsonSchema/Uri/UriResolver.php - - - message: "#^Parameter \\#3 \\$length of function array_slice expects int\\|null, float\\|int\\<1, max\\> given\\.$#" - count: 1 - path: src/JsonSchema/Uri/UriResolver.php - - message: "#^Parameter \\#3 \\$subject of function preg_replace expects array\\|string, string\\|null given\\.$#" count: 1 diff --git a/src/JsonSchema/Uri/UriResolver.php b/src/JsonSchema/Uri/UriResolver.php index 3ba66e5..b62cbaf 100644 --- a/src/JsonSchema/Uri/UriResolver.php +++ b/src/JsonSchema/Uri/UriResolver.php @@ -125,25 +125,37 @@ class UriResolver implements UriResolverInterface public static function combineRelativePathWithBasePath($relativePath, $basePath) { $relativePath = self::normalizePath($relativePath); - if ($relativePath == '') { + if (!$relativePath) { return $basePath; } - if ($relativePath[0] == '/') { + if ($relativePath[0] === '/') { return $relativePath; } - - $basePathSegments = explode('/', $basePath); - - preg_match('|^/?(\.\./(?:\./)*)*|', $relativePath, $match); - $numLevelUp = strlen($match[0]) /3 + 1; - if ($numLevelUp >= count($basePathSegments)) { + if (!$basePath) { throw new UriResolverException(sprintf("Unable to resolve URI '%s' from base '%s'", $relativePath, $basePath)); } - $basePathSegments = array_slice($basePathSegments, 0, -$numLevelUp); - $path = preg_replace('|^/?(\.\./(\./)*)*|', '', $relativePath); + $dirname = $basePath[strlen($basePath) - 1] === '/' ? $basePath : dirname($basePath); + $combined = rtrim($dirname, '/') . '/' . ltrim($relativePath, '/'); + $combinedSegments = explode('/', $combined); + $collapsedSegments = []; + while ($combinedSegments) { + $segment = array_shift($combinedSegments); + if ($segment === '..') { + if (count($collapsedSegments) <= 1) { + // Do not remove the top level (domain) + // This is not ideal - the domain should not be part of the path here. parse() and generate() + // should handle the "domain" separately, like the schema. + // Then the if-condition here would be `if (!$collapsedSegments) {`. + throw new UriResolverException(sprintf("Unable to resolve URI '%s' from base '%s'", $relativePath, $basePath)); + } + array_pop($collapsedSegments); + } else { + $collapsedSegments[] = $segment; + } + } - return implode('/', $basePathSegments) . '/' . $path; + return implode('/', $collapsedSegments); } /** diff --git a/tests/Uri/UriResolverTest.php b/tests/Uri/UriResolverTest.php index 898f360..8d524ca 100644 --- a/tests/Uri/UriResolverTest.php +++ b/tests/Uri/UriResolverTest.php @@ -7,6 +7,9 @@ use PHPUnit\Framework\TestCase; class UriResolverTest extends TestCase { + /** + * @var UriResolver + */ private $resolver; public function setUp(): void @@ -85,6 +88,21 @@ class UriResolverTest extends TestCase ); } + /** + * Covers https://github.com/justinrainbow/json-schema/issues/557 + * Relative paths yield wrong result. + */ + public function testCombineRelativePathWithBasePathTraversingUp(): void + { + $this->assertEquals( + '/var/packages/schema/UuidSchema.json', + UriResolver::combineRelativePathWithBasePath( + '../../../schema/UuidSchema.json', + '/var/packages/foo/tests/UnitTests/DemoData/../../../schema/Foo/FooSchema_latest.json' + ) + ); + } + public function testResolveAbsoluteUri(): void { $this->assertEquals(