diff --git a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php index 12b59e5cbebc3..93880f26455da 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php @@ -211,14 +211,28 @@ private function parseDefinition($id, $service, $file) */ private function parseFile($file) { + $internalErrors = libxml_use_internal_errors(true); + $disableEntities = libxml_disable_entity_loader(true); + libxml_clear_errors(); + $dom = new \DOMDocument(); - libxml_use_internal_errors(true); - if (!$dom->load($file, defined('LIBXML_COMPACT') ? LIBXML_COMPACT : 0)) { - throw new \InvalidArgumentException(implode("\n", $this->getXmlErrors())); - } $dom->validateOnParse = true; + if (!$dom->loadXML(file_get_contents($file), LIBXML_NONET | (defined('LIBXML_COMPACT') ? LIBXML_COMPACT : 0))) { + libxml_disable_entity_loader($disableEntities); + + throw new \InvalidArgumentException(implode("\n", $this->getXmlErrors($internalErrors))); + } $dom->normalizeDocument(); - libxml_use_internal_errors(false); + + libxml_use_internal_errors($internalErrors); + libxml_disable_entity_loader($disableEntities); + + foreach ($dom->childNodes as $child) { + if ($child->nodeType === XML_DOCUMENT_TYPE_NODE) { + throw new \InvalidArgumentException('Document types are not allowed.'); + } + } + $this->validate($dom, $file); return simplexml_import_dom($dom, 'Symfony\\Component\\DependencyInjection\\SimpleXMLElement'); @@ -360,12 +374,14 @@ private function validateSchema(\DOMDocument $dom, $file) ; $current = libxml_use_internal_errors(true); + libxml_clear_errors(); + $valid = $dom->schemaValidateSource($source); foreach ($tmpfiles as $tmpfile) { @unlink($tmpfile); } if (!$valid) { - throw new \InvalidArgumentException(implode("\n", $this->getXmlErrors())); + throw new \InvalidArgumentException(implode("\n", $this->getXmlErrors($current))); } libxml_use_internal_errors($current); } @@ -406,7 +422,7 @@ private function validateExtensions(\DOMDocument $dom, $file) * * @return array */ - private function getXmlErrors() + private function getXmlErrors($internalErrors) { $errors = array(); foreach (libxml_get_errors() as $error) { @@ -421,6 +437,7 @@ private function getXmlErrors() } libxml_clear_errors(); + libxml_use_internal_errors($internalErrors); return $errors; } diff --git a/src/Symfony/Component/DomCrawler/Crawler.php b/src/Symfony/Component/DomCrawler/Crawler.php index 02e07c8e6fbd5..f7edbd7b64450 100644 --- a/src/Symfony/Component/DomCrawler/Crawler.php +++ b/src/Symfony/Component/DomCrawler/Crawler.php @@ -119,10 +119,15 @@ public function addContent($content, $type = null) */ public function addHtmlContent($content, $charset = 'UTF-8') { + $disableEntities = libxml_disable_entity_loader(true); + $dom = new \DOMDocument('1.0', $charset); $dom->validateOnParse = true; @$dom->loadHTML($content); + + libxml_disable_entity_loader($disableEntities); + $this->addDocument($dom); $base = $this->filter('base')->extract(array('href')); @@ -142,11 +147,16 @@ public function addHtmlContent($content, $charset = 'UTF-8') */ public function addXmlContent($content, $charset = 'UTF-8') { + $disableEntities = libxml_disable_entity_loader(true); + $dom = new \DOMDocument('1.0', $charset); $dom->validateOnParse = true; // remove the default namespace to make XPath expressions simpler - @$dom->loadXML(str_replace('xmlns', 'ns', $content)); + @$dom->loadXML(str_replace('xmlns', 'ns', $content), LIBXML_NONET); + + libxml_disable_entity_loader($disableEntities); + $this->addDocument($dom); } diff --git a/src/Symfony/Component/Routing/Loader/XmlFileLoader.php b/src/Symfony/Component/Routing/Loader/XmlFileLoader.php index 1203067d23ca8..5dad9db3fae05 100644 --- a/src/Symfony/Component/Routing/Loader/XmlFileLoader.php +++ b/src/Symfony/Component/Routing/Loader/XmlFileLoader.php @@ -150,14 +150,28 @@ protected function parseRoute(RouteCollection $collection, \DOMElement $definiti */ protected function loadFile($file) { + $internalErrors = libxml_use_internal_errors(true); + $disableEntities = libxml_disable_entity_loader(true); + libxml_clear_errors(); + $dom = new \DOMDocument(); - libxml_use_internal_errors(true); - if (!$dom->load($file, defined('LIBXML_COMPACT') ? LIBXML_COMPACT : 0)) { - throw new \InvalidArgumentException(implode("\n", $this->getXmlErrors())); - } $dom->validateOnParse = true; + if (!$dom->loadXML(file_get_contents($file), LIBXML_NONET | (defined('LIBXML_COMPACT') ? LIBXML_COMPACT : 0))) { + libxml_disable_entity_loader($disableEntities); + + throw new \InvalidArgumentException(implode("\n", $this->getXmlErrors($internalErrors))); + } $dom->normalizeDocument(); - libxml_use_internal_errors(false); + + libxml_use_internal_errors($internalErrors); + libxml_disable_entity_loader($disableEntities); + + foreach ($dom->childNodes as $child) { + if ($child->nodeType === XML_DOCUMENT_TYPE_NODE) { + throw new \InvalidArgumentException('Document types are not allowed.'); + } + } + $this->validate($dom); return $dom; @@ -175,8 +189,10 @@ protected function validate(\DOMDocument $dom) $location = __DIR__.'/schema/routing/routing-1.0.xsd'; $current = libxml_use_internal_errors(true); + libxml_clear_errors(); + if (!$dom->schemaValidate($location)) { - throw new \InvalidArgumentException(implode("\n", $this->getXmlErrors())); + throw new \InvalidArgumentException(implode("\n", $this->getXmlErrors($current))); } libxml_use_internal_errors($current); } @@ -186,7 +202,7 @@ protected function validate(\DOMDocument $dom) * * @return array An array of libxml error strings */ - private function getXmlErrors() + private function getXmlErrors($internalErrors) { $errors = array(); foreach (libxml_get_errors() as $error) { @@ -201,6 +217,7 @@ private function getXmlErrors() } libxml_clear_errors(); + libxml_use_internal_errors($internalErrors); return $errors; } diff --git a/src/Symfony/Component/Serializer/Encoder/XmlEncoder.php b/src/Symfony/Component/Serializer/Encoder/XmlEncoder.php index 1d8d7216d286f..1b23c23f3f655 100644 --- a/src/Symfony/Component/Serializer/Encoder/XmlEncoder.php +++ b/src/Symfony/Component/Serializer/Encoder/XmlEncoder.php @@ -58,10 +58,20 @@ public function decode($data, $format) $disableEntities = libxml_disable_entity_loader(true); libxml_clear_errors(); - $xml = simplexml_load_string($data); + $dom = new \DOMDocument(); + $dom->loadXML($data, LIBXML_NONET); + libxml_use_internal_errors($internalErrors); libxml_disable_entity_loader($disableEntities); + foreach ($dom->childNodes as $child) { + if ($child->nodeType === XML_DOCUMENT_TYPE_NODE) { + throw new UnexpectedValueException('Document types are not allowed.'); + } + } + + $xml = simplexml_import_dom($dom); + if ($error = libxml_get_last_error()) { throw new UnexpectedValueException($error->message); } diff --git a/src/Symfony/Component/Translation/Loader/XliffFileLoader.php b/src/Symfony/Component/Translation/Loader/XliffFileLoader.php index 4bd6b69a4adf4..eb222fc552816 100644 --- a/src/Symfony/Component/Translation/Loader/XliffFileLoader.php +++ b/src/Symfony/Component/Translation/Loader/XliffFileLoader.php @@ -55,10 +55,26 @@ public function load($resource, $locale, $domain = 'messages') */ private function parseFile($file) { + $internalErrors = libxml_use_internal_errors(true); + $disableEntities = libxml_disable_entity_loader(true); + libxml_clear_errors(); + $dom = new \DOMDocument(); - $current = libxml_use_internal_errors(true); - if (!@$dom->load($file, defined('LIBXML_COMPACT') ? LIBXML_COMPACT : 0)) { - throw new \RuntimeException(implode("\n", $this->getXmlErrors())); + $dom->validateOnParse = true; + if (!@$dom->loadXML(file_get_contents($file), LIBXML_NONET | (defined('LIBXML_COMPACT') ? LIBXML_COMPACT : 0))) { + libxml_disable_entity_loader($disableEntities); + + throw new \RuntimeException(implode("\n", $this->getXmlErrors($internalErrors))); + } + + libxml_disable_entity_loader($disableEntities); + + foreach ($dom->childNodes as $child) { + if ($child->nodeType === XML_DOCUMENT_TYPE_NODE) { + libxml_use_internal_errors($internalErrors); + + throw new \RuntimeException('Document types are not allowed.'); + } } $location = str_replace('\\', '/', __DIR__).'/schema/dic/xliff-core/xml.xsd'; @@ -77,11 +93,12 @@ private function parseFile($file) $source = str_replace('http://www.w3.org/2001/xml.xsd', $location, $source); if (!@$dom->schemaValidateSource($source)) { - throw new \RuntimeException(implode("\n", $this->getXmlErrors())); + throw new \RuntimeException(implode("\n", $this->getXmlErrors($internalErrors))); } - $dom->validateOnParse = true; + $dom->normalizeDocument(); - libxml_use_internal_errors($current); + + libxml_use_internal_errors($internalErrors); return simplexml_import_dom($dom); } @@ -91,7 +108,7 @@ private function parseFile($file) * * @return array An array of errors */ - private function getXmlErrors() + private function getXmlErrors($internalErrors) { $errors = array(); foreach (libxml_get_errors() as $error) { @@ -106,7 +123,7 @@ private function getXmlErrors() } libxml_clear_errors(); - libxml_use_internal_errors(false); + libxml_use_internal_errors($internalErrors); return $errors; } diff --git a/src/Symfony/Component/Validator/Mapping/Loader/XmlFileLoader.php b/src/Symfony/Component/Validator/Mapping/Loader/XmlFileLoader.php index d95d97537774c..e44ebfe136c28 100644 --- a/src/Symfony/Component/Validator/Mapping/Loader/XmlFileLoader.php +++ b/src/Symfony/Component/Validator/Mapping/Loader/XmlFileLoader.php @@ -180,22 +180,38 @@ protected function parseOptions(\SimpleXMLElement $nodes) */ protected function parseFile($file) { + $internalErrors = libxml_use_internal_errors(true); + $disableEntities = libxml_disable_entity_loader(true); + libxml_clear_errors(); + $dom = new \DOMDocument(); - libxml_use_internal_errors(true); - if (!$dom->load($file, defined('LIBXML_COMPACT') ? LIBXML_COMPACT : 0)) { - throw new MappingException(implode("\n", $this->getXmlErrors())); + $dom->validateOnParse = true; + if (!$dom->loadXML(file_get_contents($file), LIBXML_NONET | (defined('LIBXML_COMPACT') ? LIBXML_COMPACT : 0))) { + libxml_disable_entity_loader($disableEntities); + + throw new MappingException(implode("\n", $this->getXmlErrors($internalErrors))); } + + libxml_disable_entity_loader($disableEntities); + if (!$dom->schemaValidate(__DIR__.'/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd')) { - throw new MappingException(implode("\n", $this->getXmlErrors())); + throw new MappingException(implode("\n", $this->getXmlErrors($internalErrors))); } - $dom->validateOnParse = true; + $dom->normalizeDocument(); - libxml_use_internal_errors(false); + + libxml_use_internal_errors($internalErrors); + + foreach ($dom->childNodes as $child) { + if ($child->nodeType === XML_DOCUMENT_TYPE_NODE) { + throw new MappingException('Document types are not allowed.'); + } + } return simplexml_import_dom($dom); } - protected function getXmlErrors() + protected function getXmlErrors($internalErrors) { $errors = array(); foreach (libxml_get_errors() as $error) { @@ -210,7 +226,7 @@ protected function getXmlErrors() } libxml_clear_errors(); - libxml_use_internal_errors(false); + libxml_use_internal_errors($internalErrors); return $errors; } diff --git a/tests/Symfony/Tests/Component/DependencyInjection/Fixtures/xml/withdoctype.xml b/tests/Symfony/Tests/Component/DependencyInjection/Fixtures/xml/withdoctype.xml new file mode 100644 index 0000000000000..f217d5bcacf54 --- /dev/null +++ b/tests/Symfony/Tests/Component/DependencyInjection/Fixtures/xml/withdoctype.xml @@ -0,0 +1,3 @@ + + + diff --git a/tests/Symfony/Tests/Component/DependencyInjection/Loader/XmlFileLoaderTest.php b/tests/Symfony/Tests/Component/DependencyInjection/Loader/XmlFileLoaderTest.php index c40003e669e71..e83fed4b31e64 100644 --- a/tests/Symfony/Tests/Component/DependencyInjection/Loader/XmlFileLoaderTest.php +++ b/tests/Symfony/Tests/Component/DependencyInjection/Loader/XmlFileLoaderTest.php @@ -310,4 +310,16 @@ public function testNoNamingConflictsForAnonymousServices() $inner2 = $services[(string) $args2[0]]; $this->assertEquals('BarClass2', $inner2->getClass(), '->load() uses the same configuration as for the anonymous ones'); } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Document types are not allowed. + */ + public function testDocTypeIsNotAllowed() + { + $container = new ContainerBuilder(); + + $loader1 = new XmlFileLoader($container, new FileLocator(self::$fixturesPath.'/xml')); + $loader1->load('withdoctype.xml'); + } } diff --git a/tests/Symfony/Tests/Component/Routing/Fixtures/withdoctype.xml b/tests/Symfony/Tests/Component/Routing/Fixtures/withdoctype.xml new file mode 100644 index 0000000000000..f217d5bcacf54 --- /dev/null +++ b/tests/Symfony/Tests/Component/Routing/Fixtures/withdoctype.xml @@ -0,0 +1,3 @@ + + + diff --git a/tests/Symfony/Tests/Component/Routing/Loader/XmlFileLoaderTest.php b/tests/Symfony/Tests/Component/Routing/Loader/XmlFileLoaderTest.php index e19f1e089dca8..096386849b8ff 100644 --- a/tests/Symfony/Tests/Component/Routing/Loader/XmlFileLoaderTest.php +++ b/tests/Symfony/Tests/Component/Routing/Loader/XmlFileLoaderTest.php @@ -75,6 +75,16 @@ public function getPathsToInvalidFiles() { return array(array('nonvalidnode.xml'), array('nonvalidroute.xml'), array('nonvalid.xml')); } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Document types are not allowed. + */ + public function testDocTypeIsNotAllowed() + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $loader->load('withdoctype.xml'); + } } /** diff --git a/tests/Symfony/Tests/Component/Serializer/Encoder/XmlEncoderTest.php b/tests/Symfony/Tests/Component/Serializer/Encoder/XmlEncoderTest.php index e4aef6563acf2..3c3449a6e65bb 100644 --- a/tests/Symfony/Tests/Component/Serializer/Encoder/XmlEncoderTest.php +++ b/tests/Symfony/Tests/Component/Serializer/Encoder/XmlEncoderTest.php @@ -53,6 +53,15 @@ public function testSetRootNodeName() $this->assertEquals($expected, $this->encoder->encode($obj, 'xml')); } + /** + * @expectedException UnexpectedValueException + * @expectedExceptionMessage Document types are not allowed. + */ + public function testDocTypeIsNotAllowed() + { + $this->encoder->decode('', 'foo'); + } + public function testAttributes() { $obj = new ScalarDummy; @@ -233,20 +242,22 @@ public function testDecodeArray() $this->assertEquals($expected, $this->encoder->decode($source, 'xml')); } - /** - * @expectedException Symfony\Component\Serializer\Exception\UnexpectedValueException - */ public function testPreventsComplexExternalEntities() { $oldCwd = getcwd(); chdir(__DIR__); try { - $decoded = $this->encoder->decode(']>&test;', 'xml'); + $this->encoder->decode(']>&test;', 'xml'); chdir($oldCwd); - } catch (UnexpectedValueException $e) { + + $this->fail('No exception was thrown.'); + } catch (\Exception $e) { chdir($oldCwd); - throw $e; + + if (!$e instanceof UnexpectedValueException) { + $this->fail('Expected UnexpectedValueException'); + } } } diff --git a/tests/Symfony/Tests/Component/Translation/Loader/XliffFileLoaderTest.php b/tests/Symfony/Tests/Component/Translation/Loader/XliffFileLoaderTest.php index 71b6d96fd581d..de1adfb37b0be 100644 --- a/tests/Symfony/Tests/Component/Translation/Loader/XliffFileLoaderTest.php +++ b/tests/Symfony/Tests/Component/Translation/Loader/XliffFileLoaderTest.php @@ -54,4 +54,14 @@ public function testLoadThrowsAnExceptionIfFileNotLocal() $resource = 'http://example.com/resources.xliff'; $loader->load($resource, 'en', 'domain1'); } + + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage Document types are not allowed. + */ + public function testDocTypeIsNotAllowed() + { + $loader = new XliffFileLoader(); + $loader->load(__DIR__.'/../fixtures/withdoctype.xliff', 'en', 'domain1'); + } } diff --git a/tests/Symfony/Tests/Component/Translation/fixtures/withdoctype.xliff b/tests/Symfony/Tests/Component/Translation/fixtures/withdoctype.xliff new file mode 100644 index 0000000000000..f83e834ddc477 --- /dev/null +++ b/tests/Symfony/Tests/Component/Translation/fixtures/withdoctype.xliff @@ -0,0 +1,12 @@ + + + + + + + foo + bar + + + + diff --git a/tests/Symfony/Tests/Component/Validator/Mapping/Loader/XmlFileLoaderTest.php b/tests/Symfony/Tests/Component/Validator/Mapping/Loader/XmlFileLoaderTest.php index 6b4450fa24663..9d9a8e7fc66d5 100644 --- a/tests/Symfony/Tests/Component/Validator/Mapping/Loader/XmlFileLoaderTest.php +++ b/tests/Symfony/Tests/Component/Validator/Mapping/Loader/XmlFileLoaderTest.php @@ -71,4 +71,16 @@ public function testLoadClassMetadata() $this->assertEquals($expected, $metadata); } + + /** + * @expectedException Symfony\Component\Validator\Exception\MappingException + * @expectedExceptionMessage Document types are not allowed. + */ + public function testDocTypeIsNotAllowed() + { + $loader = new XmlFileLoader(__DIR__.'/withdoctype.xml'); + $metadata = new ClassMetadata('Symfony\Tests\Component\Validator\Fixtures\Entity'); + + $loader->loadClassMetadata($metadata); + } } diff --git a/tests/Symfony/Tests/Component/Validator/Mapping/Loader/withdoctype.xml b/tests/Symfony/Tests/Component/Validator/Mapping/Loader/withdoctype.xml new file mode 100644 index 0000000000000..557cccb1a2de2 --- /dev/null +++ b/tests/Symfony/Tests/Component/Validator/Mapping/Loader/withdoctype.xml @@ -0,0 +1,7 @@ + + + + +