diff --git a/CHANGELOG.md b/CHANGELOG.md index 39a39bf..faeba20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ ChangeLog ========= +1.5.1 (2019-01-09) +------------------ + +* #161: Prevent infinite loop on empty xml elements + + 1.5.0 (2016-10-09) ------------------ diff --git a/lib/Deserializer/functions.php b/lib/Deserializer/functions.php index 2e5d877..07038d9 100644 --- a/lib/Deserializer/functions.php +++ b/lib/Deserializer/functions.php @@ -66,9 +66,20 @@ function keyValue(Reader $reader, $namespace = null) { return []; } + if (!$reader->read()) { + $reader->next(); + + return []; + } + + if (Reader::END_ELEMENT === $reader->nodeType) { + $reader->next(); + + return []; + } + $values = []; - $reader->read(); do { if ($reader->nodeType === Reader::ELEMENT) { @@ -79,7 +90,9 @@ function keyValue(Reader $reader, $namespace = null) { $values[$clark] = $reader->parseCurrentElement()['value']; } } else { - $reader->read(); + if (!$reader->read()) { + break; + } } } while ($reader->nodeType !== Reader::END_ELEMENT); @@ -144,7 +157,17 @@ function enum(Reader $reader, $namespace = null) { $reader->next(); return []; } - $reader->read(); + if (!$reader->read()) { + $reader->next(); + + return []; + } + + if (Reader::END_ELEMENT === $reader->nodeType) { + $reader->next(); + + return []; + } $currentDepth = $reader->depth; $values = []; @@ -204,7 +227,9 @@ function valueObject(Reader $reader, $className, $namespace) { $reader->next(); } } else { - $reader->read(); + if (!$reader->read()) { + break; + } } } while ($reader->nodeType !== Reader::END_ELEMENT); diff --git a/tests/Sabre/Xml/Deserializer/EnumTest.php b/tests/Sabre/Xml/Deserializer/EnumTest.php index 2eea9bb..4e32544 100644 --- a/tests/Sabre/Xml/Deserializer/EnumTest.php +++ b/tests/Sabre/Xml/Deserializer/EnumTest.php @@ -59,4 +59,30 @@ function testDeserializeDefaultNamespace() { } + function testEmptyEnum() + { + $service = new Service(); + $service->elementMap['{urn:test}enum'] = 'Sabre\Xml\Deserializer\enum'; + + $xml = << + + + + + +XML; + + $result = $service->parse($xml); + + $this->assertEquals([[ + 'name' => '{urn:test}inner', + 'value' => [[ + 'name' => '{urn:test}enum', + 'value' => [], + 'attributes' => [], + ]], + 'attributes' => [], + ]], $result); + } } diff --git a/tests/Sabre/Xml/Deserializer/KeyValueTest.php b/tests/Sabre/Xml/Deserializer/KeyValueTest.php index a94ff4e..2f0eda3 100644 --- a/tests/Sabre/Xml/Deserializer/KeyValueTest.php +++ b/tests/Sabre/Xml/Deserializer/KeyValueTest.php @@ -19,6 +19,7 @@ function testKeyValue() { foo foo & bar + BLA; @@ -51,7 +52,8 @@ function testKeyValue() { 'value' => 'foo & bar', 'attributes' => [], ], - ] + ], + 'elem6' => null, ], 'attributes' => [], ] @@ -109,4 +111,43 @@ function testKeyValueLoop() { } + function testEmptyKeyValue() + { + // the nested structure below is necessary to detect if one of the deserialization functions eats to much elements + $input = << + + + + + +BLA; + + $reader = new Reader(); + $reader->elementMap = [ + '{http://sabredav.org/ns}struct' => function(Reader $reader) { + return keyValue($reader, 'http://sabredav.org/ns'); + }, + ]; + $reader->xml($input); + $output = $reader->parse(); + + $this->assertEquals([ + 'name' => '{http://sabredav.org/ns}root', + 'value' => [ + [ + 'name' => '{http://sabredav.org/ns}inner', + 'value' => [ + [ + 'name' => '{http://sabredav.org/ns}struct', + 'value' => [], + 'attributes' => [], + ], + ], + 'attributes' => [], + ], + ], + 'attributes' => [], + ], $output); + } } diff --git a/tests/Sabre/Xml/ServiceTest.php b/tests/Sabre/Xml/ServiceTest.php index e6fcf14..b03d680 100644 --- a/tests/Sabre/Xml/ServiceTest.php +++ b/tests/Sabre/Xml/ServiceTest.php @@ -2,6 +2,8 @@ namespace Sabre\Xml; +use Sabre\Xml\Element\KeyValue; + class ServiceTest extends \PHPUnit_Framework_TestCase { function testGetReader() { @@ -123,6 +125,40 @@ function testExpect() { ); } + /** + * @expectedException \Sabre\Xml\LibXMLException + */ + function testInvalidNameSpace() + { + $xml = ''; + + $util = new Service(); + $util->elementMap = [ + '{DAV:}propfind' => PropFindTestAsset::class, + ]; + $util->namespaceMap = [ + 'http://sabre.io/ns' => 's', + ]; + $result = $util->expect('{DAV:}propfind', $xml); + } + + /** + * @dataProvider providesEmptyPropfinds + */ + function testEmptyPropfind($xml) + { + $util = new Service(); + $util->elementMap = [ + '{DAV:}propfind' => PropFindTestAsset::class, + ]; + $util->namespaceMap = [ + 'http://sabre.io/ns' => 's', + ]; + $result = $util->expect('{DAV:}propfind', $xml); + $this->assertEquals(false, $result->allProp); + $this->assertEquals([], $result->properties); + } + /** * @depends testGetReader */ @@ -303,6 +339,16 @@ function testParseClarkNotationFail() { } + function providesEmptyPropfinds() + { + return [ + [''], + [''], + [''], + [''], + [' '], + ]; + } } /** @@ -326,3 +372,38 @@ class OrderStatus { public $id; public $label; } + + +/** + * asset for testInvalidNameSpace. + * + * @internal + */ +class PropFindTestAsset implements XmlDeserializable +{ + public $allProp = false; + + public $properties; + + static function xmlDeserialize(Reader $reader) + { + $self = new self(); + + $reader->pushContext(); + $reader->elementMap['{DAV:}prop'] = 'Sabre\Xml\Element\Elements'; + + foreach (KeyValue::xmlDeserialize($reader) as $k => $v) { + switch ($k) { + case '{DAV:}prop': + $self->properties = $v; + break; + case '{DAV:}allprop': + $self->allProp = true; + } + } + + $reader->popContext(); + + return $self; + } +}