<?php /** * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Framework\Xml; use DOMDocument; /** * Class Security */ class Security { /** * Heuristic scan to detect entity in XML * * @param string $xmlContent * @return bool */ private function heuristicScan($xmlContent) { return strpos($xmlContent, '<!ENTITY') === false; } /** * Return true if PHP is running with PHP-FPM * * @return bool */ private function isPhpFpm() { return substr(php_sapi_name(), 0, 3) === 'fpm'; } /** * Security check loaded XML document * * @param string $xmlContent * @return bool * * @SuppressWarnings(PHPMD.UnusedLocalVariable) */ public function scan($xmlContent) { /** * If running with PHP-FPM we perform an heuristic scan * We cannot use libxml_disable_entity_loader because of this bug * @see https://bugs.php.net/bug.php?id=64938 */ if ($this->isPhpFpm()) { return $this->heuristicScan($xmlContent); } $document = new DOMDocument(); $loadEntities = libxml_disable_entity_loader(true); $useInternalXmlErrors = libxml_use_internal_errors(true); /** * Load XML with network access disabled (LIBXML_NONET) * error disabled with @ for PHP-FPM scenario */ set_error_handler( function ($errno, $errstr) { if (substr_count($errstr, 'DOMDocument::loadXML()') > 0) { return true; } return false; }, E_WARNING ); $result = (bool)$document->loadXML($xmlContent, LIBXML_NONET); restore_error_handler(); // Entity load to previous setting libxml_disable_entity_loader($loadEntities); libxml_use_internal_errors($useInternalXmlErrors); if (!$result) { return false; } foreach ($document->childNodes as $child) { if ($child->nodeType === XML_DOCUMENT_TYPE_NODE) { if ($child->entities->length > 0) { return false; } } } return true; } }