diff --git a/.gitignore b/.gitignore index 87904ecb..f2b1db6b 100644 --- a/.gitignore +++ b/.gitignore @@ -4,5 +4,6 @@ /docs/html/ /laminas-mkdoc-theme.tgz /laminas-mkdoc-theme/ +/.phpunit.result.cache /phpunit.xml /vendor/ diff --git a/.travis.yml b/.travis.yml index d63ede4b..677679fc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,36 +12,24 @@ env: matrix: fast_finish: true include: - - php: 5.6 - env: - - DEPS=lowest - - php: 5.6 - env: - - DEPS=latest - - php: 7 - env: - - DEPS=lowest - - php: 7 - env: - - DEPS=latest - - php: 7.1 + - php: 7.3 env: - DEPS=lowest - - php: 7.1 + - php: 7.3 env: - DEPS=latest - CS_CHECK=true - TEST_COVERAGE=true - - php: 7.2 + - php: 7.4 env: - DEPS=lowest - - php: 7.2 + - php: 7.4 env: - DEPS=latest - - php: 7.3 + - php: 8.0 env: - DEPS=lowest - - php: 7.3 + - php: 8.0 env: - DEPS=latest diff --git a/composer.json b/composer.json index 0b1ccaa1..f2437f06 100644 --- a/composer.json +++ b/composer.json @@ -25,12 +25,14 @@ } }, "require": { - "php": "^5.6 || ^7.0", - "laminas/laminas-zendframework-bridge": "^1.0" + "php": "^7.3 || ~8.0.0", + "ext-dom": "*", + "ext-libxml": "*", + "laminas/laminas-zendframework-bridge": "^1.1" }, "require-dev": { "laminas/laminas-coding-standard": "~1.0.0", - "phpunit/phpunit": "^5.7.23 || ^6.4.3" + "phpunit/phpunit": "^9.4.3" }, "autoload": { "psr-4": { diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 9fbc2cf8..93d33cc0 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,17 +1,16 @@ - - - ./test - - - - - - ./src - - + xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd" + bootstrap="vendor/autoload.php" + colors="true"> + + + ./src + + + + + ./test + + diff --git a/src/Document.php b/src/Document.php index e403095c..41d6b24b 100644 --- a/src/Document.php +++ b/src/Document.php @@ -231,7 +231,7 @@ protected function setErrors($errors) protected function getDomDocumentFromString($stringDocument) { libxml_use_internal_errors(true); - libxml_disable_entity_loader(true); + $disableEntityLoaderFlag = self::disableEntityLoader(); $encoding = $this->getEncoding(); $domDoc = null === $encoding ? new DOMDocument('1.0') : new DOMDocument('1.0', $encoding); @@ -261,7 +261,7 @@ protected function getDomDocumentFromString($stringDocument) libxml_clear_errors(); } - libxml_disable_entity_loader(false); + self::disableEntityLoader($disableEntityLoaderFlag); libxml_use_internal_errors(false); if (! $success) { @@ -311,4 +311,22 @@ public function registerXpathPhpFunctions($xpathPhpFunctions = true) { $this->xpathPhpFunctions = $xpathPhpFunctions; } + + /** + * Disable the ability to load external XML entities based on libxml version + * + * If we are using libxml < 2.9, unsafe XML entity loading must be + * disabled with a flag. + * + * If we are using libxml >= 2.9, XML entity loading is disabled by default. + * + * @return bool + */ + private static function disableEntityLoader($flag = true) + { + if (LIBXML_VERSION < 20900) { + return libxml_disable_entity_loader($flag); + } + return $flag; + } } diff --git a/src/Query.php b/src/Query.php index b6bb7915..ecbff610 100644 --- a/src/Query.php +++ b/src/Query.php @@ -232,7 +232,7 @@ public function queryXpath($xpathQuery, $query = null, DOMNode $contextNode = nu $encoding = $this->getEncoding(); libxml_use_internal_errors(true); - libxml_disable_entity_loader(true); + $disableEntityLoaderFlag = self::disableEntityLoader(); if (null === $encoding) { $domDoc = new DOMDocument('1.0'); } else { @@ -261,7 +261,7 @@ public function queryXpath($xpathQuery, $query = null, DOMNode $contextNode = nu $this->documentErrors = $errors; libxml_clear_errors(); } - libxml_disable_entity_loader(false); + self::disableEntityLoader($disableEntityLoaderFlag); libxml_use_internal_errors(false); if (! $success) { @@ -320,4 +320,22 @@ protected function getNodeList($document, $xpathQuery, DOMNode $contextNode = nu $nodeList = $xpath->queryWithErrorException($xpathQuery, $contextNode); return $nodeList; } + + /** + * Disable the ability to load external XML entities based on libxml version + * + * If we are using libxml < 2.9, unsafe XML entity loading must be + * disabled with a flag. + * + * If we are using libxml >= 2.9, XML entity loading is disabled by default. + * + * @return bool + */ + private static function disableEntityLoader($flag = true) + { + if (LIBXML_VERSION < 20900) { + return libxml_disable_entity_loader($flag); + } + return $flag; + } } diff --git a/test/DOMXPathTest.php b/test/DOMXPathTest.php index 11674e4a..ead689d4 100644 --- a/test/DOMXPathTest.php +++ b/test/DOMXPathTest.php @@ -19,7 +19,7 @@ class DOMXPathTest extends TestCase /** @var DOMDocument */ private $document; - protected function setUp() + protected function setUp(): void { $this->document = new DOMDocument(''); } diff --git a/test/Document/NodeListTest.php b/test/Document/NodeListTest.php index 143d3173..56386dba 100644 --- a/test/Document/NodeListTest.php +++ b/test/Document/NodeListTest.php @@ -26,7 +26,7 @@ class NodeListTest extends TestCase /** @var NodeList|DOMNode[] */ private $nodeList; - protected function setUp() + protected function setUp(): void { $document = new DOMDocument(); $document->loadHTML(''); diff --git a/test/Document/QueryTest.php b/test/Document/QueryTest.php index d134ad7b..1ec576e5 100644 --- a/test/Document/QueryTest.php +++ b/test/Document/QueryTest.php @@ -19,7 +19,7 @@ class QueryTest extends TestCase public function testTransformShouldReturnStringByDefault() { $test = Query::cssToXpath(''); - $this->assertInternalType('string', $test); + $this->assertIsString($test); } /** @@ -28,8 +28,8 @@ public function testTransformShouldReturnStringByDefault() public function testTransformShouldReturnMultiplePathsWhenExpressionContainsCommas() { $test = Query::cssToXpath('#foo, #bar'); - $this->assertInternalType('string', $test); - $this->assertContains('|', $test); + $this->assertIsString($test); + $this->assertStringContainsString('|', $test); $this->assertCount(2, explode('|', $test)); } @@ -48,13 +48,13 @@ public function testTransformShouldRecognizeDotSymbolAsClass() public function testTransformShouldAssumeSpacesToIndicateRelativeXpathQueries() { $test = Query::cssToXpath('div#foo .bar'); - $this->assertContains('|', $test); + $this->assertStringContainsString('|', $test); $expected = [ "//div[@id='foo']//*[contains(concat(' ', normalize-space(@class), ' '), ' bar ')]", "//div[@id='foo'][contains(concat(' ', normalize-space(@class), ' '), ' bar ')]", ]; foreach ($expected as $path) { - $this->assertContains($path, $test); + $this->assertStringContainsString($path, $test); } } @@ -70,8 +70,8 @@ public function testTransformShouldWriteChildSelectorsAsAbsoluteXpathRelations() public function testMultipleComplexCssSpecificationShouldTransformToExpectedXpath() { $test = Query::cssToXpath('div#foo span.bar, #bar li.baz a'); - $this->assertInternalType('string', $test); - $this->assertContains('|', $test); + $this->assertIsString($test); + $this->assertStringContainsString('|', $test); $actual = explode('|', $test); $expected = [ "//div[@id='foo']//span[contains(concat(' ', normalize-space(@class), ' '), ' bar ')]", @@ -86,7 +86,7 @@ public function testMultipleComplexCssSpecificationShouldTransformToExpectedXpat public function testClassNotationWithoutSpecifiedTagShouldResultInMultipleQueries() { $test = Query::cssToXpath('div.foo .bar a .baz span'); - $this->assertContains('|', $test); + $this->assertStringContainsString('|', $test); // @codingStandardsIgnoreStart $segments = [ "//div[contains(concat(' ', normalize-space(@class), ' '), ' foo ')]//*[contains(concat(' ', normalize-space(@class), ' '), ' bar ')]//a//*[contains(concat(' ', normalize-space(@class), ' '), ' baz ')]//span", @@ -96,7 +96,7 @@ public function testClassNotationWithoutSpecifiedTagShouldResultInMultipleQuerie ]; // @codingStandardsIgnoreEnd foreach ($segments as $xpath) { - $this->assertContains($xpath, $test); + $this->assertStringContainsString($xpath, $test); } } diff --git a/test/DocumentTest.php b/test/DocumentTest.php index 22273847..d9f6ef8b 100644 --- a/test/DocumentTest.php +++ b/test/DocumentTest.php @@ -29,10 +29,8 @@ class DocumentTest extends TestCase /** * Sets up the fixture, for example, open a network connection. * This method is called before a test is executed. - * - * @return void */ - public function setUp() + public function setUp(): void { $this->document = new Document(); } @@ -233,17 +231,15 @@ public function testXpathPhpFunctionsShouldBeEnabledWithoutParameter() public function testXpathPhpFunctionsShouldBeNotCalledWhenSpecifiedFunction() { $this->loadHtml(); - try { - $this->document->registerXpathPhpFunctions('stripos'); - $result = Document\Query::execute( - '//meta[php:functionString("strtolower", @http-equiv) = "content-type"]', - $this->document - ); - } catch (\Exception $e) { - // $e->getMessage() - Not allowed to call handler 'strtolower() - return; - } - $this->fail('Not allowed to call handler strtolower()'); + $this->document->registerXpathPhpFunctions('stripos'); + + $this->expectException(PHP_VERSION_ID >= 80000 ? \Error::class : \ErrorException::class); + $this->expectExceptionMessageMatches('/Not allowed to call handler .strtolower()/'); + + Document\Query::execute( + '//meta[php:functionString("strtolower", @http-equiv) = "content-type"]', + $this->document + ); } /** @@ -255,7 +251,7 @@ public function testLoadingDocumentWithErrorsShouldNotRaisePhpErrors() $this->document = new Document($file); $result = Document\Query::execute('p', $this->document, Document\Query::TYPE_CSS); $errors = $this->document->getErrors(); - $this->assertInternalType('array', $errors); + $this->assertIsArray($errors); $this->assertNotEmpty($errors); }