diff --git a/src/JsonSchema/RefResolver.php b/src/JsonSchema/RefResolver.php index 7860601f..8db72ae6 100644 --- a/src/JsonSchema/RefResolver.php +++ b/src/JsonSchema/RefResolver.php @@ -9,7 +9,9 @@ namespace JsonSchema; +use JsonSchema\Uri\UriRetriever; use JsonSchema\Uri\Retrievers\UriRetrieverInterface; +use JsonSchema\Exception\ResourceNotFoundException; /** * Take in an object that's a JSON schema and take care of all $ref references @@ -41,7 +43,7 @@ public function __construct($retriever = null) */ public function fetchRef($ref, $sourceUri) { - $retriever = $this->getUriRetriever(); + $retriever = $this->getUriRetriever(); $jsonSchema = $retriever->retrieve($ref, $sourceUri); $this->resolve($jsonSchema); diff --git a/src/JsonSchema/Uri/Retrievers/FileGetContents.php b/src/JsonSchema/Uri/Retrievers/FileGetContents.php index dca1951c..3c4dd033 100644 --- a/src/JsonSchema/Uri/Retrievers/FileGetContents.php +++ b/src/JsonSchema/Uri/Retrievers/FileGetContents.php @@ -9,6 +9,7 @@ namespace JsonSchema\Uri\Retrievers; +use JsonSchema\Exception\ResourceNotFoundException; use JsonSchema\Validator; /** @@ -31,9 +32,14 @@ public function retrieve($uri) $response = file_get_contents($uri); if (false === $response) { - throw new ResourceNotFoundException('JSON schema not found'); + throw new ResourceNotFoundException('JSON schema not found at ' . $uri); } - + if ($response == '' + && substr($uri, 0, 7) == 'file://' && substr($uri, -1) == '/' + ) { + throw new ResourceNotFoundException('JSON schema not found at ' . $uri); + } + $this->messageBody = $response; if (! empty($http_response_header)) { $this->fetchContentType($http_response_header); diff --git a/src/JsonSchema/Uri/UriResolver.php b/src/JsonSchema/Uri/UriResolver.php index ee89bed3..ca2c4354 100644 --- a/src/JsonSchema/Uri/UriResolver.php +++ b/src/JsonSchema/Uri/UriResolver.php @@ -62,7 +62,7 @@ public function generate(array $components) $uri .= $components['query']; } if (array_key_exists('fragment', $components)) { - $uri .= $components['fragment']; + $uri .= '#' . $components['fragment']; } return $uri; @@ -73,10 +73,14 @@ public function generate(array $components) * * @param string $uri Absolute or relative * @param type $baseUri Optional base URI - * @return string + * @return string Absolute URI */ public function resolve($uri, $baseUri = null) { + if ($uri == '') { + return $baseUri; + } + $components = $this->parse($uri); $path = $components['path']; @@ -87,7 +91,10 @@ public function resolve($uri, $baseUri = null) $basePath = $baseComponents['path']; $baseComponents['path'] = self::combineRelativePathWithBasePath($path, $basePath); - + if (isset($components['fragment'])) { + $baseComponents['fragment'] = $components['fragment']; + } + return $this->generate($baseComponents); } @@ -99,9 +106,16 @@ public function resolve($uri, $baseUri = null) * @return string Merged path * @throws UriResolverException */ - private static function combineRelativePathWithBasePath($relativePath, $basePath) + public static function combineRelativePathWithBasePath($relativePath, $basePath) { $relativePath = self::normalizePath($relativePath); + if ($relativePath == '') { + return $basePath; + } + if ($relativePath{0} == '/') { + return $relativePath; + } + $basePathSegments = self::getPathSegments($basePath); preg_match('|^/?(\.\./(?:\./)*)*|', $relativePath, $match); @@ -111,13 +125,13 @@ private static function combineRelativePathWithBasePath($relativePath, $basePath } $basePathSegments = array_slice($basePathSegments, 0, -$numLevelUp); $path = preg_replace('|^/?(\.\./(\./)*)*|', '', $relativePath); - + return implode(DIRECTORY_SEPARATOR, $basePathSegments) . '/' . $path; } - + /** * Normalizes a URI path component by removing dot-slash and double slashes - * + * * @param string $path * @return string */ diff --git a/src/JsonSchema/Uri/UriRetriever.php b/src/JsonSchema/Uri/UriRetriever.php index 9e442867..301c9ea7 100644 --- a/src/JsonSchema/Uri/UriRetriever.php +++ b/src/JsonSchema/Uri/UriRetriever.php @@ -29,7 +29,7 @@ class UriRetriever * * @throws InvalidSchemaMediaTypeException */ - public function confirmMediaType($uriRetriever) + public function confirmMediaType($uriRetriever, $uri) { $contentType = $uriRetriever->getContentType(); @@ -42,6 +42,11 @@ public function confirmMediaType($uriRetriever) return; } + if (substr($uri, 0, 23) == 'http://json-schema.org/') { + //HACK; they deliver broken content types + return true; + } + throw new InvalidSchemaMediaTypeException(sprintf('Media type %s expected', Validator::SCHEMA_MEDIA_TYPE)); } @@ -72,6 +77,8 @@ public function getUriRetriever() * @param object $jsonSchema JSON Schema contents * @param string $uri JSON Schema URI * @return object JSON Schema after walking down the fragment pieces + * + * @throws \JsonSchema\Exception\ResourceNotFoundException */ public function resolvePointer($jsonSchema, $uri) { @@ -90,11 +97,17 @@ public function resolvePointer($jsonSchema, $uri) if (! empty($jsonSchema->$pathElement)) { $jsonSchema = $jsonSchema->$pathElement; } else { - $jsonSchema = new \stdClass(); + throw new \JsonSchema\Exception\ResourceNotFoundException( + 'Fragment "' . $parsed['fragment'] . '" not found' + . ' in ' . $uri + ); } if (! is_object($jsonSchema)) { - $jsonSchema = new \stdClass(); + throw new \JsonSchema\Exception\ResourceNotFoundException( + 'Fragment part "' . $pathElement . '" is no object ' + . ' in ' . $uri + ); } } } @@ -112,10 +125,18 @@ public function resolvePointer($jsonSchema, $uri) public function retrieve($uri, $baseUri = null) { $resolver = new UriResolver(); - $resolvedUri = $resolver->resolve($uri, $baseUri); + $resolvedUri = $fetchUri = $resolver->resolve($uri, $baseUri); + + //fetch URL without #fragment + $arParts = $resolver->parse($resolvedUri); + if (isset($arParts['fragment'])) { + unset($arParts['fragment']); + $fetchUri = $resolver->generate($arParts); + } + $uriRetriever = $this->getUriRetriever(); - $contents = $this->uriRetriever->retrieve($resolvedUri); - $this->confirmMediaType($uriRetriever); + $contents = $this->uriRetriever->retrieve($fetchUri); + $this->confirmMediaType($uriRetriever, $fetchUri); $jsonSchema = json_decode($contents); if (JSON_ERROR_NONE < $error = json_last_error()) { diff --git a/tests/JsonSchema/Tests/RefResolverTest.php b/tests/JsonSchema/Tests/RefResolverTest.php index f44ac287..e12eab1f 100644 --- a/tests/JsonSchema/Tests/RefResolverTest.php +++ b/tests/JsonSchema/Tests/RefResolverTest.php @@ -184,4 +184,103 @@ public function refProvider() { ), ); } + + public function testFetchRefAbsolute() + { + $retr = new \JsonSchema\Uri\Retrievers\PredefinedArray( + array( + 'http://example.org/schema' => <<getUriRetriever()->setUriRetriever($retr); + + $this->assertEquals( + (object) array( + 'title' => 'schema', + 'type' => 'object', + 'id' => 'http://example.org/schema' + ), + $res->fetchRef('http://example.org/schema', 'http://example.org/schema') + ); + } + + public function testFetchRefAbsoluteAnchor() + { + $retr = new \JsonSchema\Uri\Retrievers\PredefinedArray( + array( + 'http://example.org/schema' => <<getUriRetriever()->setUriRetriever($retr); + + $this->assertEquals( + (object) array( + 'title' => 'foo', + 'type' => 'object', + 'id' => 'http://example.org/schema#/definitions/foo', + ), + $res->fetchRef( + 'http://example.org/schema#/definitions/foo', + 'http://example.org/schema' + ) + ); + } + + public function testFetchRefRelativeAnchor() + { + $retr = new \JsonSchema\Uri\Retrievers\PredefinedArray( + array( + 'http://example.org/schema' => <<getUriRetriever()->setUriRetriever($retr); + + $this->assertEquals( + (object) array( + 'title' => 'foo', + 'type' => 'object', + 'id' => 'http://example.org/schema#/definitions/foo', + ), + $res->fetchRef( + '#/definitions/foo', + 'http://example.org/schema' + ) + ); + } } diff --git a/tests/JsonSchema/Tests/Uri/UriResolverTest.php b/tests/JsonSchema/Tests/Uri/UriResolverTest.php new file mode 100644 index 00000000..872972fd --- /dev/null +++ b/tests/JsonSchema/Tests/Uri/UriResolverTest.php @@ -0,0 +1,174 @@ +resolver = new UriResolver(); + } + + public function testParse() + { + $this->assertEquals( + array( + 'scheme' => 'http', + 'authority' => 'example.org', + 'path' => '/path/to/file.json' + ), + $this->resolver->parse('http://example.org/path/to/file.json') + ); + } + + public function testParseAnchor() + { + $this->assertEquals( + array( + 'scheme' => 'http', + 'authority' => 'example.org', + 'path' => '/path/to/file.json', + 'query' => '', + 'fragment' => 'foo' + ), + $this->resolver->parse('http://example.org/path/to/file.json#foo') + ); + } + + public function testCombineRelativePathWithBasePath() + { + $this->assertEquals( + '/foo/baz.json', + UriResolver::combineRelativePathWithBasePath( + 'baz.json', + '/foo/bar.json' + ) + ); + } + + public function testCombineRelativePathWithBasePathAbsolute() + { + $this->assertEquals( + '/baz/data.json', + UriResolver::combineRelativePathWithBasePath( + '/baz/data.json', + '/foo/bar.json' + ) + ); + } + + public function testCombineRelativePathWithBasePathRelativeSub() + { + $this->assertEquals( + '/foo/baz/data.json', + UriResolver::combineRelativePathWithBasePath( + 'baz/data.json', + '/foo/bar.json' + ) + ); + } + + public function testCombineRelativePathWithBasePathNoPath() + { + //needed for anchor-only urls + $this->assertEquals( + '/foo/bar.json', + UriResolver::combineRelativePathWithBasePath( + '', + '/foo/bar.json' + ) + ); + } + + public function testResolveAbsoluteUri() + { + $this->assertEquals( + 'http://example.org/foo/bar.json', + $this->resolver->resolve( + 'http://example.org/foo/bar.json', + null + ) + ); + } + + /** + * @expectedException JsonSchema\Exception\UriResolverException + */ + public function testResolveRelativeUriNoBase() + { + $this->assertEquals( + 'http://example.org/foo/bar.json', + $this->resolver->resolve( + 'bar.json', + null + ) + ); + } + + public function testResolveRelativeUriBaseDir() + { + $this->assertEquals( + 'http://example.org/foo/bar.json', + $this->resolver->resolve( + 'bar.json', + 'http://example.org/foo/' + ) + ); + } + + public function testResolveRelativeUriBaseFile() + { + $this->assertEquals( + 'http://example.org/foo/bar.json', + $this->resolver->resolve( + 'bar.json', + 'http://example.org/foo/baz.json' + ) + ); + } + + public function testResolveAnchor() + { + $this->assertEquals( + 'http://example.org/foo/bar.json#baz', + $this->resolver->resolve( + '#baz', + 'http://example.org/foo/bar.json' + ) + ); + } + + public function testResolveAnchorWithFile() + { + $this->assertEquals( + 'http://example.org/foo/baz.json#baz', + $this->resolver->resolve( + 'baz.json#baz', + 'http://example.org/foo/bar.json' + ) + ); + } + public function testResolveAnchorAnchor() + { + $this->assertEquals( + 'http://example.org/foo/bar.json#bazinga', + $this->resolver->resolve( + '#bazinga', + 'http://example.org/foo/bar.json#baz' + ) + ); + } + + public function testResolveEmpty() + { + $this->assertEquals( + 'http://example.org/foo/bar.json', + $this->resolver->resolve( + '', + 'http://example.org/foo/bar.json' + ) + ); + } +} +?> diff --git a/tests/JsonSchema/Tests/Uri/UriRetrieverTest.php b/tests/JsonSchema/Tests/Uri/UriRetrieverTest.php index 20a3a09a..6793a68e 100644 --- a/tests/JsonSchema/Tests/Uri/UriRetrieverTest.php +++ b/tests/JsonSchema/Tests/Uri/UriRetrieverTest.php @@ -144,4 +144,79 @@ public function jsonProvider() array($childSchema, $parentSchema) ); } + + public function testResolvePointerNoFragment() + { + $schema = (object) array( + 'title' => 'schema' + ); + + $retriever = new \JsonSchema\Uri\UriRetriever(); + $this->assertEquals( + $schema, + $retriever->resolvePointer( + $schema, 'http://example.org/schema.json' + ) + ); + } + + public function testResolvePointerFragment() + { + $schema = (object) array( + 'definitions' => (object) array( + 'foo' => (object) array( + 'title' => 'foo' + ) + ), + 'title' => 'schema' + ); + + $retriever = new \JsonSchema\Uri\UriRetriever(); + $this->assertEquals( + $schema->definitions->foo, + $retriever->resolvePointer( + $schema, 'http://example.org/schema.json#/definitions/foo' + ) + ); + } + + /** + * @expectedException JsonSchema\Exception\ResourceNotFoundException + */ + public function testResolvePointerFragmentNotFound() + { + $schema = (object) array( + 'definitions' => (object) array( + 'foo' => (object) array( + 'title' => 'foo' + ) + ), + 'title' => 'schema' + ); + + $retriever = new \JsonSchema\Uri\UriRetriever(); + $retriever->resolvePointer( + $schema, 'http://example.org/schema.json#/definitions/bar' + ); + } + + /** + * @expectedException JsonSchema\Exception\ResourceNotFoundException + */ + public function testResolvePointerFragmentNoArray() + { + $schema = (object) array( + 'definitions' => (object) array( + 'foo' => array( + 'title' => 'foo' + ) + ), + 'title' => 'schema' + ); + + $retriever = new \JsonSchema\Uri\UriRetriever(); + $retriever->resolvePointer( + $schema, 'http://example.org/schema.json#/definitions/foo' + ); + } }