Skip to content

Commit 3948abb

Browse files
eraydbighappyface
authored andcommitted
[BUGFIX] Ignore $ref siblings & abort on infinite-loop references (#437)
* Add tests for $ref enforcement * Overriding schema properties from $ref is not allowed From the spec [1]: An object schema with a "$ref" property MUST be interpreted as a "$ref" reference. The value of the "$ref" property MUST be a URI Reference. Resolved against the current URI base, it identifies the URI of a schema to use. All other properties in a "$ref" object MUST be ignored. [1] https://tools.ietf.org/html/draft-wright-json-schema-01#section-8 * Add test for infinite-loop circular reference * Return dereferenced schema directly and check for infinite-loops Schemas containing $ref MUST ignore all other properties, so just return the reference target directly. Because some circular references may result in an infinite loop, keep track of the objects that have been dereferenced during the current call to SchemaStorage and abort if the same object is encountered more than once. * Code style fix (remove blank line)
1 parent 024f3d8 commit 3948abb

File tree

3 files changed

+92
-8
lines changed

3 files changed

+92
-8
lines changed

src/JsonSchema/SchemaStorage.php

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ public function getSchema($id)
9494
/**
9595
* {@inheritdoc}
9696
*/
97-
public function resolveRef($ref)
97+
public function resolveRef($ref, $resolveStack = array())
9898
{
9999
$jsonPointer = new JsonPointer($ref);
100100

@@ -111,9 +111,9 @@ public function resolveRef($ref)
111111
$refSchema = $this->getSchema($fileName);
112112
foreach ($jsonPointer->getPropertyPaths() as $path) {
113113
if (is_object($refSchema) && property_exists($refSchema, $path)) {
114-
$refSchema = $this->resolveRefSchema($refSchema->{$path});
114+
$refSchema = $this->resolveRefSchema($refSchema->{$path}, $resolveStack);
115115
} elseif (is_array($refSchema) && array_key_exists($path, $refSchema)) {
116-
$refSchema = $this->resolveRefSchema($refSchema[$path]);
116+
$refSchema = $this->resolveRefSchema($refSchema[$path], $resolveStack);
117117
} else {
118118
throw new UnresolvableJsonPointerException(sprintf(
119119
'File: %s is found, but could not resolve fragment: %s',
@@ -129,12 +129,18 @@ public function resolveRef($ref)
129129
/**
130130
* {@inheritdoc}
131131
*/
132-
public function resolveRefSchema($refSchema)
132+
public function resolveRefSchema($refSchema, $resolveStack = array())
133133
{
134134
if (is_object($refSchema) && property_exists($refSchema, '$ref') && is_string($refSchema->{'$ref'})) {
135-
$newSchema = $this->resolveRef($refSchema->{'$ref'});
136-
$refSchema = (object) (get_object_vars($refSchema) + get_object_vars($newSchema));
137-
unset($refSchema->{'$ref'});
135+
if (in_array($refSchema, $resolveStack, true)) {
136+
throw new UnresolvableJsonPointerException(sprintf(
137+
'Dereferencing a pointer to %s results in an infinite loop',
138+
$refSchema->{'$ref'}
139+
));
140+
}
141+
$resolveStack[] = $refSchema;
142+
143+
return $this->resolveRef($refSchema->{'$ref'}, $resolveStack);
138144
}
139145

140146
return $refSchema;

tests/RefTest.php

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the JsonSchema package.
5+
*
6+
* For the full copyright and license information, please view the LICENSE
7+
* file that was distributed with this source code.
8+
*/
9+
10+
namespace JsonSchema\Tests;
11+
12+
use JsonSchema\Validator;
13+
14+
class RefTest extends \PHPUnit_Framework_TestCase
15+
{
16+
public function dataRefIgnoresSiblings()
17+
{
18+
return array(
19+
// #0 check that $ref is resolved and the instance is validated against
20+
// the referenced schema
21+
array(
22+
'{
23+
"definitions":{"test": {"type": "integer"}},
24+
"properties": {
25+
"propertyOne": {"$ref": "#/definitions/test"}
26+
}
27+
}',
28+
'{"propertyOne": "not an integer"}',
29+
false
30+
),
31+
// #1 check that sibling properties of $ref are ignored during validation
32+
array(
33+
'{
34+
"definitions":{
35+
"test": {"type": "integer"}
36+
},
37+
"properties": {
38+
"propertyOne": {
39+
"$ref": "#/definitions/test",
40+
"maximum": 5
41+
}
42+
}
43+
}',
44+
'{"propertyOne": 10}',
45+
true
46+
),
47+
// #2 infinite-loop / unresolveable circular reference
48+
array(
49+
'{
50+
"definitions": {
51+
"test1": {"$ref": "#/definitions/test2"},
52+
"test2": {"$ref": "#/definitions/test1"}
53+
},
54+
"properties": {"propertyOne": {"$ref": "#/definitions/test1"}}
55+
}',
56+
'{"propertyOne": 5}',
57+
true,
58+
'\JsonSchema\Exception\UnresolvableJsonPointerException'
59+
)
60+
);
61+
}
62+
63+
/** @dataProvider dataRefIgnoresSiblings */
64+
public function testRefIgnoresSiblings($schema, $document, $isValid, $exception = null)
65+
{
66+
$document = json_decode($document);
67+
$schema = json_decode($schema);
68+
69+
$v = new Validator();
70+
if ($exception) {
71+
$this->setExpectedException($exception);
72+
}
73+
74+
$v->validate($document, $schema);
75+
76+
$this->assertEquals($isValid, $v->isValid());
77+
}
78+
}

tests/SchemaStorageTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ public function testSchemaWithLocalAndExternalReferencesWithCircularReference()
7979
);
8080

8181
// local ref with overriding
82-
$this->assertNotEquals(
82+
$this->assertEquals(
8383
$schemaStorage->resolveRef("$mainSchemaPath#/definitions/house/additionalProperties"),
8484
$schemaStorage->resolveRef("$mainSchemaPath#/properties/house/additionalProperties")
8585
);

0 commit comments

Comments
 (0)