Skip to content

Commit 48bd037

Browse files
author
Christian Weiske
committed
Properly resolve relative URIs with fragments: "base.json#/definitions/foo"
1 parent ed06b67 commit 48bd037

File tree

7 files changed

+400
-14
lines changed

7 files changed

+400
-14
lines changed

src/JsonSchema/RefResolver.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99

1010
namespace JsonSchema;
1111

12+
use JsonSchema\Uri\UriRetriever;
1213
use JsonSchema\Uri\Retrievers\UriRetrieverInterface;
14+
use JsonSchema\Exception\ResourceNotFoundException;
1315

1416
/**
1517
* 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)
4143
*/
4244
public function fetchRef($ref, $sourceUri)
4345
{
44-
$retriever = $this->getUriRetriever();
46+
$retriever = $this->getUriRetriever();
4547
$jsonSchema = $retriever->retrieve($ref, $sourceUri);
4648
$this->resolve($jsonSchema);
4749

src/JsonSchema/Uri/Retrievers/FileGetContents.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
namespace JsonSchema\Uri\Retrievers;
1111

12+
use JsonSchema\Exception\ResourceNotFoundException;
1213
use JsonSchema\Validator;
1314

1415
/**
@@ -31,9 +32,14 @@ public function retrieve($uri)
3132

3233
$response = file_get_contents($uri);
3334
if (false === $response) {
34-
throw new ResourceNotFoundException('JSON schema not found');
35+
throw new ResourceNotFoundException('JSON schema not found at ' . $uri);
3536
}
36-
37+
if ($response == ''
38+
&& substr($uri, 0, 7) == 'file://' && substr($uri, -1) == '/'
39+
) {
40+
throw new ResourceNotFoundException('JSON schema not found at ' . $uri);
41+
}
42+
3743
$this->messageBody = $response;
3844
if (! empty($http_response_header)) {
3945
$this->fetchContentType($http_response_header);

src/JsonSchema/Uri/UriResolver.php

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public function generate(array $components)
6262
$uri .= $components['query'];
6363
}
6464
if (array_key_exists('fragment', $components)) {
65-
$uri .= $components['fragment'];
65+
$uri .= '#' . $components['fragment'];
6666
}
6767

6868
return $uri;
@@ -73,10 +73,14 @@ public function generate(array $components)
7373
*
7474
* @param string $uri Absolute or relative
7575
* @param type $baseUri Optional base URI
76-
* @return string
76+
* @return string Absolute URI
7777
*/
7878
public function resolve($uri, $baseUri = null)
7979
{
80+
if ($uri == '') {
81+
return $baseUri;
82+
}
83+
8084
$components = $this->parse($uri);
8185
$path = $components['path'];
8286

@@ -87,7 +91,10 @@ public function resolve($uri, $baseUri = null)
8791
$basePath = $baseComponents['path'];
8892

8993
$baseComponents['path'] = self::combineRelativePathWithBasePath($path, $basePath);
90-
94+
if (isset($components['fragment'])) {
95+
$baseComponents['fragment'] = $components['fragment'];
96+
}
97+
9198
return $this->generate($baseComponents);
9299
}
93100

@@ -99,9 +106,16 @@ public function resolve($uri, $baseUri = null)
99106
* @return string Merged path
100107
* @throws UriResolverException
101108
*/
102-
private static function combineRelativePathWithBasePath($relativePath, $basePath)
109+
public static function combineRelativePathWithBasePath($relativePath, $basePath)
103110
{
104111
$relativePath = self::normalizePath($relativePath);
112+
if ($relativePath == '') {
113+
return $basePath;
114+
}
115+
if ($relativePath{0} == '/') {
116+
return $relativePath;
117+
}
118+
105119
$basePathSegments = self::getPathSegments($basePath);
106120

107121
preg_match('|^/?(\.\./(?:\./)*)*|', $relativePath, $match);
@@ -111,13 +125,13 @@ private static function combineRelativePathWithBasePath($relativePath, $basePath
111125
}
112126
$basePathSegments = array_slice($basePathSegments, 0, -$numLevelUp);
113127
$path = preg_replace('|^/?(\.\./(\./)*)*|', '', $relativePath);
114-
128+
115129
return implode(DIRECTORY_SEPARATOR, $basePathSegments) . '/' . $path;
116130
}
117-
131+
118132
/**
119133
* Normalizes a URI path component by removing dot-slash and double slashes
120-
*
134+
*
121135
* @param string $path
122136
* @return string
123137
*/

src/JsonSchema/Uri/UriRetriever.php

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ public function getUriRetriever()
7272
* @param object $jsonSchema JSON Schema contents
7373
* @param string $uri JSON Schema URI
7474
* @return object JSON Schema after walking down the fragment pieces
75+
*
76+
* @throws \JsonSchema\Exception\ResourceNotFoundException
7577
*/
7678
public function resolvePointer($jsonSchema, $uri)
7779
{
@@ -90,11 +92,17 @@ public function resolvePointer($jsonSchema, $uri)
9092
if (! empty($jsonSchema->$pathElement)) {
9193
$jsonSchema = $jsonSchema->$pathElement;
9294
} else {
93-
$jsonSchema = new \stdClass();
95+
throw new \JsonSchema\Exception\ResourceNotFoundException(
96+
'Fragment "' . $parsed['fragment'] . '" not found'
97+
. ' in ' . $uri
98+
);
9499
}
95100

96101
if (! is_object($jsonSchema)) {
97-
$jsonSchema = new \stdClass();
102+
throw new \JsonSchema\Exception\ResourceNotFoundException(
103+
'Fragment part "' . $pathElement . '" is no object '
104+
. ' in ' . $uri
105+
);
98106
}
99107
}
100108
}
@@ -112,9 +120,17 @@ public function resolvePointer($jsonSchema, $uri)
112120
public function retrieve($uri, $baseUri = null)
113121
{
114122
$resolver = new UriResolver();
115-
$resolvedUri = $resolver->resolve($uri, $baseUri);
123+
$resolvedUri = $fetchUri = $resolver->resolve($uri, $baseUri);
124+
125+
//fetch URL without #fragment
126+
$arParts = $resolver->parse($resolvedUri);
127+
if (isset($arParts['fragment'])) {
128+
unset($arParts['fragment']);
129+
$fetchUri = $resolver->generate($arParts);
130+
}
131+
116132
$uriRetriever = $this->getUriRetriever();
117-
$contents = $this->uriRetriever->retrieve($resolvedUri);
133+
$contents = $this->uriRetriever->retrieve($fetchUri);
118134
$this->confirmMediaType($uriRetriever);
119135
$jsonSchema = json_decode($contents);
120136

tests/JsonSchema/Tests/RefResolverTest.php

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,4 +184,103 @@ public function refProvider() {
184184
),
185185
);
186186
}
187+
188+
public function testFetchRefAbsolute()
189+
{
190+
$retr = new \JsonSchema\Uri\Retrievers\PredefinedArray(
191+
array(
192+
'http://example.org/schema' => <<<JSN
193+
{
194+
"title": "schema",
195+
"type": "object",
196+
"id": "http://example.org/schema"
197+
}
198+
JSN
199+
)
200+
);
201+
202+
$res = new \JsonSchema\RefResolver();
203+
$res->getUriRetriever()->setUriRetriever($retr);
204+
205+
$this->assertEquals(
206+
(object) array(
207+
'title' => 'schema',
208+
'type' => 'object',
209+
'id' => 'http://example.org/schema'
210+
),
211+
$res->fetchRef('http://example.org/schema', 'http://example.org/schema')
212+
);
213+
}
214+
215+
public function testFetchRefAbsoluteAnchor()
216+
{
217+
$retr = new \JsonSchema\Uri\Retrievers\PredefinedArray(
218+
array(
219+
'http://example.org/schema' => <<<JSN
220+
{
221+
"title": "schema",
222+
"type": "object",
223+
"id": "http://example.org/schema",
224+
"definitions": {
225+
"foo": {
226+
"type": "object",
227+
"title": "foo"
228+
}
229+
}
230+
}
231+
JSN
232+
)
233+
);
234+
235+
$res = new \JsonSchema\RefResolver();
236+
$res->getUriRetriever()->setUriRetriever($retr);
237+
238+
$this->assertEquals(
239+
(object) array(
240+
'title' => 'foo',
241+
'type' => 'object',
242+
'id' => 'http://example.org/schema#/definitions/foo',
243+
),
244+
$res->fetchRef(
245+
'http://example.org/schema#/definitions/foo',
246+
'http://example.org/schema'
247+
)
248+
);
249+
}
250+
251+
public function testFetchRefRelativeAnchor()
252+
{
253+
$retr = new \JsonSchema\Uri\Retrievers\PredefinedArray(
254+
array(
255+
'http://example.org/schema' => <<<JSN
256+
{
257+
"title": "schema",
258+
"type": "object",
259+
"id": "http://example.org/schema",
260+
"definitions": {
261+
"foo": {
262+
"type": "object",
263+
"title": "foo"
264+
}
265+
}
266+
}
267+
JSN
268+
)
269+
);
270+
271+
$res = new \JsonSchema\RefResolver();
272+
$res->getUriRetriever()->setUriRetriever($retr);
273+
274+
$this->assertEquals(
275+
(object) array(
276+
'title' => 'foo',
277+
'type' => 'object',
278+
'id' => 'http://example.org/schema#/definitions/foo',
279+
),
280+
$res->fetchRef(
281+
'#/definitions/foo',
282+
'http://example.org/schema'
283+
)
284+
);
285+
}
187286
}

0 commit comments

Comments
 (0)