From 8a2195d2c64e0323d05656238c5c72414e8ad340 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sat, 29 Apr 2023 21:07:50 +0200 Subject: [PATCH 05/11] Fix GH-11160: Few tests failed building with new libxml 2.11.0 It's possible to categorise the failures into 2 categories: - Changed error message. In this case we either duplicate the test and modify the error message. Or if the change in error message is small, we use the EXPECTF matchers to make the test compatible with both old and new versions of libxml2. - Missing warnings. This is caused by a change in libxml2 where the parser started using SAX APIs internally [1]. In this case the error_type passed to php_libxml_internal_error_handler() changed from PHP_LIBXML_ERROR to PHP_LIBXML_CTX_WARNING because it internally started to use the SAX handlers instead of the generic handlers. However, for the SAX handlers the current input stack is empty, so nothing is actually printed. I fixed this by falling back to a regular warning without a filename & line number reference, which mimicks the old behaviour. Furthermore, this change now also shows an additional warning in a test which was previously hidden. [1] https://gitlab.gnome.org/GNOME/libxml2/-/commit/9a82b94a94bd310db426edd453b0f38c6c8f69f5 Closes GH-11162. (cherry picked from commit 7c0dfc5cf58d3c445b935fa14ea8f5f13568c419) (cherry picked from commit 78ae0886bd1a3e42c53c9ba65764b6e6357640b5) --- .../DOMDocument_loadXML_error2_gte2_11.phpt | 34 +++ ...> DOMDocument_loadXML_error2_pre2_11.phpt} | 7 +- .../DOMDocument_load_error2_gte2_11.phpt | 34 +++ ...t => DOMDocument_load_error2_pre2_11.phpt} | 7 +- ext/libxml/libxml.c | 2 + ext/libxml/tests/bug61367-read_2.phpt | 2 +- .../tests/libxml_disable_entity_loader_2.phpt | 2 +- ...xml_set_external_entity_loader_error1.phpt | 2 + ...set_external_entity_loader_variation2.phpt | 2 + ext/openssl/tests/ServerClientTestCase.inc | 65 ++---- .../tests/http/ServerClientTestCase.inc | 199 ++++++++++++++++++ .../tests/http/ghsa-52jp-hrpf-2jff-001.phpt | 2 +- .../tests/http/ghsa-52jp-hrpf-2jff-002.phpt | 2 +- .../tests/http/ghsa-hgf5-96fm-v528-001.phpt | 2 +- .../tests/http/ghsa-hgf5-96fm-v528-002.phpt | 2 +- .../tests/http/ghsa-hgf5-96fm-v528-003.phpt | 2 +- .../tests/http/ghsa-pcmh-g36c-qc44-001.phpt | 2 +- .../tests/http/ghsa-pcmh-g36c-qc44-002.phpt | 2 +- .../tests/http/ghsa-v8xr-gpvj-cx9g-001.phpt | 2 +- .../tests/http/ghsa-v8xr-gpvj-cx9g-002.phpt | 2 +- .../tests/http/ghsa-v8xr-gpvj-cx9g-003.phpt | 2 +- .../tests/http/ghsa-v8xr-gpvj-cx9g-004.phpt | 2 +- .../tests/http/ghsa-v8xr-gpvj-cx9g-005.phpt | 2 +- ext/xml/tests/bug26614_libxml_gte2_11.phpt | 95 +++++++++ ...bxml.phpt => bug26614_libxml_pre2_11.phpt} | 1 + 25 files changed, 408 insertions(+), 68 deletions(-) create mode 100644 ext/dom/tests/DOMDocument_loadXML_error2_gte2_11.phpt rename ext/dom/tests/{DOMDocument_loadXML_error2.phpt => DOMDocument_loadXML_error2_pre2_11.phpt} (89%) create mode 100644 ext/dom/tests/DOMDocument_load_error2_gte2_11.phpt rename ext/dom/tests/{DOMDocument_load_error2.phpt => DOMDocument_load_error2_pre2_11.phpt} (89%) create mode 100644 ext/standard/tests/http/ServerClientTestCase.inc create mode 100644 ext/xml/tests/bug26614_libxml_gte2_11.phpt rename ext/xml/tests/{bug26614_libxml.phpt => bug26614_libxml_pre2_11.phpt} (96%) diff --git a/ext/dom/tests/DOMDocument_loadXML_error2_gte2_11.phpt b/ext/dom/tests/DOMDocument_loadXML_error2_gte2_11.phpt new file mode 100644 index 00000000000..ff5ceb3fbed --- /dev/null +++ b/ext/dom/tests/DOMDocument_loadXML_error2_gte2_11.phpt @@ -0,0 +1,34 @@ +--TEST-- +Test DOMDocument::loadXML() detects not-well formed XML +--SKIPIF-- += 2.11'); +?> +--DESCRIPTION-- +This test verifies the method detects attributes values not closed between " or ' +Environment variables used in the test: +- XML_FILE: the xml file to load +- LOAD_OPTIONS: the second parameter to pass to the method +- EXPECTED_RESULT: the expected result +--CREDITS-- +Antonio Diaz Ruiz +--INI-- +assert.bail=true +--EXTENSIONS-- +dom +--ENV-- +XML_FILE=/not_well_formed2.xml +LOAD_OPTIONS=0 +EXPECTED_RESULT=0 +--FILE_EXTERNAL-- +domdocumentloadxml_test_method.inc +--EXPECTF-- +Warning: DOMDocument::loadXML(): AttValue: " or ' expected in Entity, line: 4 in %s on line %d + +Warning: DOMDocument::loadXML(): internal error: xmlParseStartTag: problem parsing attributes in Entity, line: 4 in %s on line %d + +Warning: DOMDocument::loadXML(): Couldn't find end of Start Tag book line 4 in Entity, line: 4 in %s on line %d + +Warning: DOMDocument::loadXML(): Opening and ending tag mismatch: books line 3 and book in Entity, line: 7 in %s on line %d + +Warning: DOMDocument::loadXML(): Extra content at the end of the document in Entity, line: 8 in %s on line %d diff --git a/ext/dom/tests/DOMDocument_loadXML_error2.phpt b/ext/dom/tests/DOMDocument_loadXML_error2_pre2_11.phpt similarity index 89% rename from ext/dom/tests/DOMDocument_loadXML_error2.phpt rename to ext/dom/tests/DOMDocument_loadXML_error2_pre2_11.phpt index 6d56a317ed7..7e10771fdb7 100644 --- a/ext/dom/tests/DOMDocument_loadXML_error2.phpt +++ b/ext/dom/tests/DOMDocument_loadXML_error2_pre2_11.phpt @@ -1,5 +1,10 @@ --TEST-- Test DOMDocument::loadXML() detects not-well formed XML +--SKIPIF-- += 21100) die('skip libxml2 test variant for version < 2.11'); +?> --DESCRIPTION-- This test verifies the method detects attributes values not closed between " or ' Environment variables used in the test: @@ -10,8 +15,6 @@ Environment variables used in the test: Antonio Diaz Ruiz --INI-- assert.bail=true ---SKIPIF-- - --ENV-- XML_FILE=/not_well_formed2.xml LOAD_OPTIONS=0 diff --git a/ext/dom/tests/DOMDocument_load_error2_gte2_11.phpt b/ext/dom/tests/DOMDocument_load_error2_gte2_11.phpt new file mode 100644 index 00000000000..32b6bf16114 --- /dev/null +++ b/ext/dom/tests/DOMDocument_load_error2_gte2_11.phpt @@ -0,0 +1,34 @@ +--TEST-- +Test DOMDocument::load() detects not-well formed +--SKIPIF-- += 2.11'); +?> +--DESCRIPTION-- +This test verifies the method detects attributes values not closed between " or ' +Environment variables used in the test: +- XML_FILE: the xml file to load +- LOAD_OPTIONS: the second parameter to pass to the method +- EXPECTED_RESULT: the expected result +--CREDITS-- +Antonio Diaz Ruiz +--INI-- +assert.bail=true +--EXTENSIONS-- +dom +--ENV-- +XML_FILE=/not_well_formed2.xml +LOAD_OPTIONS=0 +EXPECTED_RESULT=0 +--FILE_EXTERNAL-- +domdocumentload_test_method.inc +--EXPECTF-- +Warning: DOMDocument::load(): AttValue: " or ' expected in %s on line %d + +Warning: DOMDocument::load(): internal error: xmlParseStartTag: problem parsing attributes in %s on line %d + +Warning: DOMDocument::load(): Couldn't find end of Start Tag book line 4 in %s on line %d + +Warning: DOMDocument::load(): Opening and ending tag mismatch: books line 3 and book in %s on line %d + +Warning: DOMDocument::load(): Extra content at the end of the document in %s on line %d diff --git a/ext/dom/tests/DOMDocument_load_error2.phpt b/ext/dom/tests/DOMDocument_load_error2_pre2_11.phpt similarity index 89% rename from ext/dom/tests/DOMDocument_load_error2.phpt rename to ext/dom/tests/DOMDocument_load_error2_pre2_11.phpt index f450cf16545..74b20c171e0 100644 --- a/ext/dom/tests/DOMDocument_load_error2.phpt +++ b/ext/dom/tests/DOMDocument_load_error2_pre2_11.phpt @@ -1,5 +1,10 @@ --TEST-- Test DOMDocument::load() detects not-well formed XML +--SKIPIF-- += 21100) die('skip libxml2 test variant for version < 2.11'); +?> --DESCRIPTION-- This test verifies the method detects attributes values not closed between " or ' Environment variables used in the test: @@ -10,8 +15,6 @@ Environment variables used in the test: Antonio Diaz Ruiz --INI-- assert.bail=true ---SKIPIF-- - --ENV-- XML_FILE=/not_well_formed2.xml LOAD_OPTIONS=0 diff --git a/ext/libxml/libxml.c b/ext/libxml/libxml.c index d343135b98d..5d9c23e0d7e 100644 --- a/ext/libxml/libxml.c +++ b/ext/libxml/libxml.c @@ -574,6 +574,8 @@ static void php_libxml_ctx_error_level(int level, void *ctx, const char *msg) } else { php_error_docref(NULL, level, "%s in Entity, line: %d", msg, parser->input->line); } + } else { + php_error_docref(NULL, E_WARNING, "%s", msg); } } diff --git a/ext/libxml/tests/bug61367-read_2.phpt b/ext/libxml/tests/bug61367-read_2.phpt index 8cc0b50144c..12743adab1c 100644 --- a/ext/libxml/tests/bug61367-read_2.phpt +++ b/ext/libxml/tests/bug61367-read_2.phpt @@ -55,6 +55,6 @@ bool(true) int(4) bool(true) -Warning: DOMDocument::loadXML(): I/O warning : failed to load external entity "file:///%s/test_bug_61367-read/bad" in %s on line %d +Warning: DOMDocument::loadXML(): %Sfailed to load external entity "file:///%s/test_bug_61367-read/bad" in %s on line %d Notice: Trying to get property 'nodeValue' of non-object in %s on line %d diff --git a/ext/libxml/tests/libxml_disable_entity_loader_2.phpt b/ext/libxml/tests/libxml_disable_entity_loader_2.phpt index 845bd4bbe3c..55d8e61ee09 100644 --- a/ext/libxml/tests/libxml_disable_entity_loader_2.phpt +++ b/ext/libxml/tests/libxml_disable_entity_loader_2.phpt @@ -36,6 +36,6 @@ echo "Done\n"; bool(true) bool(false) -Warning: DOMDocument::loadXML(): I/O warning : failed to load external entity "%s" in %s on line %d +Warning: DOMDocument::loadXML(): %Sfailed to load external entity "%s" in %s on line %d bool(true) Done diff --git a/ext/libxml/tests/libxml_set_external_entity_loader_error1.phpt b/ext/libxml/tests/libxml_set_external_entity_loader_error1.phpt index 40b31ea85d3..00e06eb8a25 100644 --- a/ext/libxml/tests/libxml_set_external_entity_loader_error1.phpt +++ b/ext/libxml/tests/libxml_set_external_entity_loader_error1.phpt @@ -35,6 +35,8 @@ Warning: libxml_set_external_entity_loader() expects exactly 1 parameter, 2 give NULL bool(true) +Warning: DOMDocument::validate(): Call to user entity loader callback %s + Warning: DOMDocument::validate(): Could not load the external subset "http://example.com/foobar" in %s on line %d Exception: Too few arguments to function {closure}(), 3 passed and exactly 4 expected Done. diff --git a/ext/libxml/tests/libxml_set_external_entity_loader_variation2.phpt b/ext/libxml/tests/libxml_set_external_entity_loader_variation2.phpt index e51869cf47f..0664de1ea6b 100644 --- a/ext/libxml/tests/libxml_set_external_entity_loader_variation2.phpt +++ b/ext/libxml/tests/libxml_set_external_entity_loader_variation2.phpt @@ -38,6 +38,8 @@ echo "Done.\n"; string(10) "-//FOO/BAR" string(%d) "%sfoobar.dtd" +Warning: DOMDocument::validate(): Failed to load external entity "-//FOO/BAR" in %s on line %d + Warning: DOMDocument::validate(): Could not load the external subset "foobar.dtd" in %s on line %d bool(false) bool(true) diff --git a/ext/openssl/tests/ServerClientTestCase.inc b/ext/openssl/tests/ServerClientTestCase.inc index c74da444102..753366df6f4 100644 --- a/ext/openssl/tests/ServerClientTestCase.inc +++ b/ext/openssl/tests/ServerClientTestCase.inc @@ -4,19 +4,14 @@ const WORKER_ARGV_VALUE = 'RUN_WORKER'; const WORKER_DEFAULT_NAME = 'server'; -function phpt_notify(string $worker = WORKER_DEFAULT_NAME, string $message = ""): void +function phpt_notify($worker = WORKER_DEFAULT_NAME) { - ServerClientTestCase::getInstance()->notify($worker, $message); + ServerClientTestCase::getInstance()->notify($worker); } -function phpt_wait($worker = WORKER_DEFAULT_NAME, $timeout = null): ?string +function phpt_wait($worker = WORKER_DEFAULT_NAME, $timeout = null) { - return ServerClientTestCase::getInstance()->wait($worker, $timeout); -} - -function phpt_notify_server_start($server): void -{ - ServerClientTestCase::getInstance()->notify_server_start($server); + ServerClientTestCase::getInstance()->wait($worker, $timeout); } function phpt_has_sslv3() { @@ -124,73 +119,43 @@ class ServerClientTestCase eval($code); } - /** - * Run client and all workers - * - * @param string $clientCode The client PHP code - * @param string|array $workerCode - * @param bool $ephemeral Select whether automatic port selection and automatic awaiting is used - * @return void - * @throws Exception - */ - public function run(string $clientCode, $workerCode, bool $ephemeral = true): void + public function run($masterCode, $workerCode) { if (!is_array($workerCode)) { $workerCode = [WORKER_DEFAULT_NAME => $workerCode]; } - reset($workerCode); - $code = current($workerCode); - $worker = key($workerCode); - while ($worker != null) { + foreach ($workerCode as $worker => $code) { $this->spawnWorkerProcess($worker, $this->stripPhpTagsFromCode($code)); - $code = next($workerCode); - if ($ephemeral) { - $addr = trim($this->wait($worker)); - if (empty($addr)) { - throw new \Exception("Failed server start"); - } - if ($code === false) { - $clientCode = preg_replace('/{{\s*ADDR\s*}}/', $addr, $clientCode); - } else { - $code = preg_replace('/{{\s*ADDR\s*}}/', $addr, $code); - } - } - $worker = key($workerCode); } - - eval($this->stripPhpTagsFromCode($clientCode)); + eval($this->stripPhpTagsFromCode($masterCode)); foreach ($workerCode as $worker => $code) { $this->cleanupWorkerProcess($worker); } } - public function wait($worker, $timeout = null): ?string + public function wait($worker, $timeout = null) { $handle = $this->isWorker ? STDIN : $this->workerStdOut[$worker]; if ($timeout === null) { - return fgets($handle); + fgets($handle); + return true; } stream_set_blocking($handle, false); $read = [$handle]; $result = stream_select($read, $write, $except, $timeout); if (!$result) { - return null; + return false; } - $result = fgets($handle); + fgets($handle); stream_set_blocking($handle, true); - return $result; - } - - public function notify(string $worker, string $message = ""): void - { - fwrite($this->isWorker ? STDOUT : $this->workerStdIn[$worker], "$message\n"); + return true; } - public function notify_server_start($server): void + public function notify($worker) { - echo stream_socket_get_name($server, false) . "\n"; + fwrite($this->isWorker ? STDOUT : $this->workerStdIn[$worker], "\n"); } } diff --git a/ext/standard/tests/http/ServerClientTestCase.inc b/ext/standard/tests/http/ServerClientTestCase.inc new file mode 100644 index 00000000000..c74da444102 --- /dev/null +++ b/ext/standard/tests/http/ServerClientTestCase.inc @@ -0,0 +1,199 @@ +notify($worker, $message); +} + +function phpt_wait($worker = WORKER_DEFAULT_NAME, $timeout = null): ?string +{ + return ServerClientTestCase::getInstance()->wait($worker, $timeout); +} + +function phpt_notify_server_start($server): void +{ + ServerClientTestCase::getInstance()->notify_server_start($server); +} + +function phpt_has_sslv3() { + static $result = null; + if (!is_null($result)) { + return $result; + } + $server = @stream_socket_server('sslv3://127.0.0.1:10013'); + if ($result = !!$server) { + fclose($server); + } + return $result; +} + +/** + * This is a singleton to let the wait/notify functions work + * I know it's horrible, but it's a means to an end + */ +class ServerClientTestCase +{ + private $isWorker = false; + + private $workerHandle = []; + + private $workerStdIn = []; + + private $workerStdOut = []; + + private static $instance; + + public static function getInstance($isWorker = false) + { + if (!isset(self::$instance)) { + self::$instance = new self($isWorker); + } + + return self::$instance; + } + + public function __construct($isWorker = false) + { + if (!isset(self::$instance)) { + self::$instance = $this; + } + + $this->isWorker = $isWorker; + } + + private function spawnWorkerProcess($worker, $code) + { + if (defined("PHP_WINDOWS_VERSION_MAJOR")) { + $ini = php_ini_loaded_file(); + $cmd = sprintf( + '%s %s "%s" %s', + PHP_BINARY, $ini ? "-n -c $ini" : "", + __FILE__, + WORKER_ARGV_VALUE + ); + } else { + $cmd = sprintf( + '%s "%s" %s %s', + PHP_BINARY, + __FILE__, + WORKER_ARGV_VALUE, + $worker + ); + } + $this->workerHandle[$worker] = proc_open( + $cmd, + [['pipe', 'r'], ['pipe', 'w'], STDERR], + $pipes + ); + $this->workerStdIn[$worker] = $pipes[0]; + $this->workerStdOut[$worker] = $pipes[1]; + + fwrite($this->workerStdIn[$worker], $code . "\n---\n"); + } + + private function cleanupWorkerProcess($worker) + { + fclose($this->workerStdIn[$worker]); + fclose($this->workerStdOut[$worker]); + proc_close($this->workerHandle[$worker]); + } + + private function stripPhpTagsFromCode($code) + { + return preg_replace('/^\s*<\?(?:php)?|\?>\s*$/i', '', $code); + } + + public function runWorker() + { + $code = ''; + + while (1) { + $line = fgets(STDIN); + + if (trim($line) === "---") { + break; + } + + $code .= $line; + } + + eval($code); + } + + /** + * Run client and all workers + * + * @param string $clientCode The client PHP code + * @param string|array $workerCode + * @param bool $ephemeral Select whether automatic port selection and automatic awaiting is used + * @return void + * @throws Exception + */ + public function run(string $clientCode, $workerCode, bool $ephemeral = true): void + { + if (!is_array($workerCode)) { + $workerCode = [WORKER_DEFAULT_NAME => $workerCode]; + } + reset($workerCode); + $code = current($workerCode); + $worker = key($workerCode); + while ($worker != null) { + $this->spawnWorkerProcess($worker, $this->stripPhpTagsFromCode($code)); + $code = next($workerCode); + if ($ephemeral) { + $addr = trim($this->wait($worker)); + if (empty($addr)) { + throw new \Exception("Failed server start"); + } + if ($code === false) { + $clientCode = preg_replace('/{{\s*ADDR\s*}}/', $addr, $clientCode); + } else { + $code = preg_replace('/{{\s*ADDR\s*}}/', $addr, $code); + } + } + $worker = key($workerCode); + } + + eval($this->stripPhpTagsFromCode($clientCode)); + foreach ($workerCode as $worker => $code) { + $this->cleanupWorkerProcess($worker); + } + } + + public function wait($worker, $timeout = null): ?string + { + $handle = $this->isWorker ? STDIN : $this->workerStdOut[$worker]; + if ($timeout === null) { + return fgets($handle); + } + + stream_set_blocking($handle, false); + $read = [$handle]; + $result = stream_select($read, $write, $except, $timeout); + if (!$result) { + return null; + } + + $result = fgets($handle); + stream_set_blocking($handle, true); + return $result; + } + + public function notify(string $worker, string $message = ""): void + { + fwrite($this->isWorker ? STDOUT : $this->workerStdIn[$worker], "$message\n"); + } + + public function notify_server_start($server): void + { + echo stream_socket_get_name($server, false) . "\n"; + } +} + +if (isset($argv[1]) && $argv[1] === WORKER_ARGV_VALUE) { + ServerClientTestCase::getInstance(true)->runWorker(); +} diff --git a/ext/standard/tests/http/ghsa-52jp-hrpf-2jff-001.phpt b/ext/standard/tests/http/ghsa-52jp-hrpf-2jff-001.phpt index 46d77ec4aff..3475a03beed 100644 --- a/ext/standard/tests/http/ghsa-52jp-hrpf-2jff-001.phpt +++ b/ext/standard/tests/http/ghsa-52jp-hrpf-2jff-001.phpt @@ -39,7 +39,7 @@ $clientCode = <<<'CODE' var_dump($http_response_header); CODE; -include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__); +include sprintf("%s/ServerClientTestCase.inc", __DIR__); ServerClientTestCase::getInstance()->run($clientCode, $serverCode); ?> --EXPECTF-- diff --git a/ext/standard/tests/http/ghsa-52jp-hrpf-2jff-002.phpt b/ext/standard/tests/http/ghsa-52jp-hrpf-2jff-002.phpt index d25c89d06e5..706a85f410b 100644 --- a/ext/standard/tests/http/ghsa-52jp-hrpf-2jff-002.phpt +++ b/ext/standard/tests/http/ghsa-52jp-hrpf-2jff-002.phpt @@ -39,7 +39,7 @@ $clientCode = <<<'CODE' var_dump($http_response_header); CODE; -include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__); +include sprintf("%s/ServerClientTestCase.inc", __DIR__); ServerClientTestCase::getInstance()->run($clientCode, $serverCode); ?> --EXPECTF-- diff --git a/ext/standard/tests/http/ghsa-hgf5-96fm-v528-001.phpt b/ext/standard/tests/http/ghsa-hgf5-96fm-v528-001.phpt index c8dcd47a4a4..121f077c9f5 100644 --- a/ext/standard/tests/http/ghsa-hgf5-96fm-v528-001.phpt +++ b/ext/standard/tests/http/ghsa-hgf5-96fm-v528-001.phpt @@ -36,7 +36,7 @@ $clientCode = <<<'CODE' var_dump($http_response_header); CODE; -include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__); +include sprintf("%s/ServerClientTestCase.inc", __DIR__); ServerClientTestCase::getInstance()->run($clientCode, $serverCode); ?> --EXPECTF-- diff --git a/ext/standard/tests/http/ghsa-hgf5-96fm-v528-002.phpt b/ext/standard/tests/http/ghsa-hgf5-96fm-v528-002.phpt index ca8f75f0327..0d141f93af3 100644 --- a/ext/standard/tests/http/ghsa-hgf5-96fm-v528-002.phpt +++ b/ext/standard/tests/http/ghsa-hgf5-96fm-v528-002.phpt @@ -36,7 +36,7 @@ $clientCode = <<<'CODE' var_dump($http_response_header); CODE; -include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__); +include sprintf("%s/ServerClientTestCase.inc", __DIR__); ServerClientTestCase::getInstance()->run($clientCode, $serverCode); ?> --EXPECTF-- diff --git a/ext/standard/tests/http/ghsa-hgf5-96fm-v528-003.phpt b/ext/standard/tests/http/ghsa-hgf5-96fm-v528-003.phpt index 4cfbc7ee804..8041487d044 100644 --- a/ext/standard/tests/http/ghsa-hgf5-96fm-v528-003.phpt +++ b/ext/standard/tests/http/ghsa-hgf5-96fm-v528-003.phpt @@ -36,7 +36,7 @@ $clientCode = <<<'CODE' var_dump($http_response_header); CODE; -include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__); +include sprintf("%s/ServerClientTestCase.inc", __DIR__); ServerClientTestCase::getInstance()->run($clientCode, $serverCode); ?> --EXPECTF-- diff --git a/ext/standard/tests/http/ghsa-pcmh-g36c-qc44-001.phpt b/ext/standard/tests/http/ghsa-pcmh-g36c-qc44-001.phpt index 53baa1c92d6..f491acfae27 100644 --- a/ext/standard/tests/http/ghsa-pcmh-g36c-qc44-001.phpt +++ b/ext/standard/tests/http/ghsa-pcmh-g36c-qc44-001.phpt @@ -35,7 +35,7 @@ $clientCode = <<<'CODE' var_dump($http_response_header); CODE; -include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__); +include sprintf("%s/ServerClientTestCase.inc", __DIR__); ServerClientTestCase::getInstance()->run($clientCode, $serverCode); ?> --EXPECTF-- diff --git a/ext/standard/tests/http/ghsa-pcmh-g36c-qc44-002.phpt b/ext/standard/tests/http/ghsa-pcmh-g36c-qc44-002.phpt index 5aa0ee00618..4320b17b97d 100644 --- a/ext/standard/tests/http/ghsa-pcmh-g36c-qc44-002.phpt +++ b/ext/standard/tests/http/ghsa-pcmh-g36c-qc44-002.phpt @@ -35,7 +35,7 @@ $clientCode = <<<'CODE' var_dump($http_response_header); CODE; -include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__); +include sprintf("%s/ServerClientTestCase.inc", __DIR__); ServerClientTestCase::getInstance()->run($clientCode, $serverCode); ?> --EXPECTF-- diff --git a/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-001.phpt b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-001.phpt index 64904bfcd1d..3f1cc79bd9c 100644 --- a/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-001.phpt +++ b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-001.phpt @@ -35,7 +35,7 @@ $clientCode = <<<'CODE' var_dump($http_response_header); CODE; -include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__); +include sprintf("%s/ServerClientTestCase.inc", __DIR__); ServerClientTestCase::getInstance()->run($clientCode, $serverCode); ?> --EXPECTF-- diff --git a/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-002.phpt b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-002.phpt index a6d9d00fd58..c7c13877fef 100644 --- a/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-002.phpt +++ b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-002.phpt @@ -35,7 +35,7 @@ $clientCode = <<<'CODE' var_dump($http_response_header); CODE; -include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__); +include sprintf("%s/ServerClientTestCase.inc", __DIR__); ServerClientTestCase::getInstance()->run($clientCode, $serverCode); ?> --EXPECTF-- diff --git a/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-003.phpt b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-003.phpt index 4eff7fc63f3..c67663b65f7 100644 --- a/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-003.phpt +++ b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-003.phpt @@ -35,7 +35,7 @@ $clientCode = <<<'CODE' var_dump($http_response_header); CODE; -include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__); +include sprintf("%s/ServerClientTestCase.inc", __DIR__); ServerClientTestCase::getInstance()->run($clientCode, $serverCode); ?> --EXPECTF-- diff --git a/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-004.phpt b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-004.phpt index 71aed2fa2e8..7a59e2688fd 100644 --- a/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-004.phpt +++ b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-004.phpt @@ -35,7 +35,7 @@ $clientCode = <<<'CODE' var_dump($http_response_header); CODE; -include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__); +include sprintf("%s/ServerClientTestCase.inc", __DIR__); ServerClientTestCase::getInstance()->run($clientCode, $serverCode); ?> --EXPECTF-- diff --git a/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-005.phpt b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-005.phpt index 49d845d84b4..f097762ef9e 100644 --- a/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-005.phpt +++ b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-005.phpt @@ -35,7 +35,7 @@ $clientCode = <<<'CODE' var_dump($http_response_header); CODE; -include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__); +include sprintf("%s/ServerClientTestCase.inc", __DIR__); ServerClientTestCase::getInstance()->run($clientCode, $serverCode); ?> --EXPECTF-- diff --git a/ext/xml/tests/bug26614_libxml_gte2_11.phpt b/ext/xml/tests/bug26614_libxml_gte2_11.phpt new file mode 100644 index 00000000000..9a81b67686d --- /dev/null +++ b/ext/xml/tests/bug26614_libxml_gte2_11.phpt @@ -0,0 +1,95 @@ +--TEST-- +Bug #26614 (CDATA sections skipped on line count) +--EXTENSIONS-- +xml +--SKIPIF-- += 2.11'); +?> +--FILE-- + + + +'; + +// Case 2: replace some characters so that we get comments instead +$xmls["Comment"] =' + + +'; + +// Case 3: replace even more characters so that only textual data is left +$xmls["Text"] =' + +-!-- ATA[ +multi +line +CDATA +block +--- +'; + +function startElement($parser, $name, $attrs) { + printf("<$name> at line %d, col %d (byte %d)\n", + xml_get_current_line_number($parser), + xml_get_current_column_number($parser), + xml_get_current_byte_index($parser)); +} + +function endElement($parser, $name) { + printf(" at line %d, col %d (byte %d)\n", + xml_get_current_line_number($parser), + xml_get_current_column_number($parser), + xml_get_current_byte_index($parser)); +} + +function characterData($parser, $data) { + // dummy +} + +foreach ($xmls as $desc => $xml) { + echo "$desc\n"; + $xml_parser = xml_parser_create(); + xml_set_element_handler($xml_parser, "startElement", "endElement"); + xml_set_character_data_handler($xml_parser, "characterData"); + if (!xml_parse($xml_parser, $xml, true)) + echo "Error: ".xml_error_string(xml_get_error_code($xml_parser))."\n"; + xml_parser_free($xml_parser); +} +?> +--EXPECTF-- +CDATA + at line 2, col %d (byte 50) + at line 9, col %d (byte 96) +Comment + at line 2, col %d (byte 50) + at line 9, col %d (byte 96) +Text + at line 2, col %d (byte 50) + at line 9, col %d (byte 96) diff --git a/ext/xml/tests/bug26614_libxml.phpt b/ext/xml/tests/bug26614_libxml_pre2_11.phpt similarity index 96% rename from ext/xml/tests/bug26614_libxml.phpt rename to ext/xml/tests/bug26614_libxml_pre2_11.phpt index 3ddd35ed0ea..afacaa1c59a 100644 --- a/ext/xml/tests/bug26614_libxml.phpt +++ b/ext/xml/tests/bug26614_libxml_pre2_11.phpt @@ -4,6 +4,7 @@ Bug #26614 (CDATA sections skipped on line count) = 21100) die('skip libxml2 test variant for version < 2.11'); ?> --FILE-- Date: Fri, 1 Dec 2023 18:03:35 +0100 Subject: [PATCH 06/11] Backport 0a39890c: Fix libxml2 2.12 build due to API breaks See https://github.com/php/php-src/actions/runs/7062192818/job/19225478601 (cherry picked from commit fa6a0f80f644932506666beb7c85e4041c4a4646) (cherry picked from commit 6e8e9f558aa0903e9650dd166a0a53c359d9e9e0) --- ext/libxml/libxml.c | 14 ++++++++++---- ext/soap/php_sdl.c | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/ext/libxml/libxml.c b/ext/libxml/libxml.c index 5d9c23e0d7e..7917f636a9e 100644 --- a/ext/libxml/libxml.c +++ b/ext/libxml/libxml.c @@ -530,7 +530,11 @@ static int _php_libxml_free_error(xmlErrorPtr error) return 1; } -static void _php_list_set_error_structure(xmlErrorPtr error, const char *msg) +#if LIBXML_VERSION >= 21200 +static void _php_list_set_error_structure(const xmlError *error, const char *msg) +#else +static void _php_list_set_error_structure(xmlError *error, const char *msg) +#endif { xmlError error_copy; int ret; @@ -784,7 +788,11 @@ PHP_LIBXML_API void php_libxml_ctx_warning(void *ctx, const char *msg, ...) va_end(args); } +#if LIBXML_VERSION >= 21200 +PHP_LIBXML_API void php_libxml_structured_error_handler(void *userData, const xmlError *error) +#else PHP_LIBXML_API void php_libxml_structured_error_handler(void *userData, xmlErrorPtr error) +#endif { _php_list_set_error_structure(error, NULL); @@ -1063,9 +1071,7 @@ static PHP_FUNCTION(libxml_use_internal_errors) Retrieve last error from libxml */ static PHP_FUNCTION(libxml_get_last_error) { - xmlErrorPtr error; - - error = xmlGetLastError(); + const xmlError *error = xmlGetLastError(); if (error) { object_init_ex(return_value, libxmlerror_class_entry); diff --git a/ext/soap/php_sdl.c b/ext/soap/php_sdl.c index 26a23f57db2..3df532a2d65 100644 --- a/ext/soap/php_sdl.c +++ b/ext/soap/php_sdl.c @@ -333,7 +333,7 @@ static void load_wsdl_ex(zval *this_ptr, char *struri, sdlCtx *ctx, int include) sdl_restore_uri_credentials(ctx); if (!wsdl) { - xmlErrorPtr xmlErrorPtr = xmlGetLastError(); + const xmlError *xmlErrorPtr = xmlGetLastError(); if (xmlErrorPtr) { soap_error2(E_ERROR, "Parsing WSDL: Couldn't load from '%s' : %s", struri, xmlErrorPtr->message); -- 2.48.1 From aa8817ab42f758c988dfd3158f705da770238a88 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Thu, 4 Jul 2024 06:29:50 -0700 Subject: [PATCH 07/11] Backport 4fe82131: Backport libxml2 2.13.2 fixes (#14816) Backproted from https://github.com/php/php-src/pull/14789 (cherry picked from commit bb46b4b799b583528025a775af45308133bfd4c1) (cherry picked from commit 6cb68826aaf68ffe8c70c8782450c38970236040) --- ext/dom/document.c | 6 ++-- .../DOMDocument_loadHTMLfile_error1.phpt | 2 +- .../DOMDocument_relaxNGValidate_error2.phpt | 2 +- .../tests/DOMDocument_saveHTMLFile_basic.phpt | 1 + ...DOMDocument_saveHTMLFile_formatOutput.phpt | 1 + ...nt_saveHTMLFile_formatOutput_gte_2_13.phpt | 32 +++++++++++++++++++ .../DOMDocument_saveHTML_basic_gte_2_13.phpt | 31 ++++++++++++++++++ .../DOMDocument_schemaValidate_error5.phpt | 2 +- ext/dom/tests/dom_create_element.phpt | 14 +++----- ext/libxml/libxml.c | 4 ++- ext/simplexml/tests/bug79971_1.phpt | 2 +- ext/soap/php_encoding.c | 9 ++++-- ext/soap/php_xml.c | 4 ++- ext/soap/tests/bugs/bug42151.phpt | 4 +-- ext/xml/compat.c | 3 +- ext/xmlwriter/php_xmlwriter.c | 3 +- 16 files changed, 95 insertions(+), 25 deletions(-) create mode 100644 ext/dom/tests/DOMDocument_saveHTMLFile_formatOutput_gte_2_13.phpt create mode 100644 ext/dom/tests/DOMDocument_saveHTML_basic_gte_2_13.phpt diff --git a/ext/dom/document.c b/ext/dom/document.c index 989b5b3dd24..af06fb41240 100644 --- a/ext/dom/document.c +++ b/ext/dom/document.c @@ -1457,11 +1457,13 @@ static xmlDocPtr dom_document_parser(zval *id, int mode, char *source, size_t so if (keep_blanks == 0 && ! (options & XML_PARSE_NOBLANKS)) { options |= XML_PARSE_NOBLANKS; } + if (recover) { + options |= XML_PARSE_RECOVER; + } php_libxml_sanitize_parse_ctxt_options(ctxt); xmlCtxtUseOptions(ctxt, options); - ctxt->recovery = recover; if (recover) { old_error_reporting = EG(error_reporting); EG(error_reporting) = old_error_reporting | E_WARNING; @@ -1471,7 +1473,7 @@ static xmlDocPtr dom_document_parser(zval *id, int mode, char *source, size_t so if (ctxt->wellFormed || recover) { ret = ctxt->myDoc; - if (ctxt->recovery) { + if (recover) { EG(error_reporting) = old_error_reporting; } /* If loading from memory, set the base reference uri for the document */ diff --git a/ext/dom/tests/DOMDocument_loadHTMLfile_error1.phpt b/ext/dom/tests/DOMDocument_loadHTMLfile_error1.phpt index cfb41686e87..fc78273c85f 100644 --- a/ext/dom/tests/DOMDocument_loadHTMLfile_error1.phpt +++ b/ext/dom/tests/DOMDocument_loadHTMLfile_error1.phpt @@ -15,4 +15,4 @@ $result = $doc->loadHTMLFile(__DIR__ . "/ffff/test.html"); assert($result === false); ?> --EXPECTF-- -%r(PHP ){0,1}%rWarning: DOMDocument::loadHTMLFile(): I/O warning : failed to load external entity %s +%r(PHP ){0,1}%rWarning: DOMDocument::loadHTMLFile(): I/O %s diff --git a/ext/dom/tests/DOMDocument_relaxNGValidate_error2.phpt b/ext/dom/tests/DOMDocument_relaxNGValidate_error2.phpt index cdd6e64194c..19bb4dce2d6 100644 --- a/ext/dom/tests/DOMDocument_relaxNGValidate_error2.phpt +++ b/ext/dom/tests/DOMDocument_relaxNGValidate_error2.phpt @@ -22,7 +22,7 @@ $result = $doc->relaxNGValidate($rng); var_dump($result); ?> --EXPECTF-- -Warning: DOMDocument::relaxNGValidate(): I/O warning : failed to load external entity "%s/foo.rng" in %s on line %d +Warning: DOMDocument::relaxNGValidate(): I/O %s : failed to load %s Warning: DOMDocument::relaxNGValidate(): xmlRelaxNGParse: could not load %s/foo.rng in %s on line %d diff --git a/ext/dom/tests/DOMDocument_saveHTMLFile_basic.phpt b/ext/dom/tests/DOMDocument_saveHTMLFile_basic.phpt index f71db0c32a3..c51852e120c 100644 --- a/ext/dom/tests/DOMDocument_saveHTMLFile_basic.phpt +++ b/ext/dom/tests/DOMDocument_saveHTMLFile_basic.phpt @@ -6,6 +6,7 @@ Knut Urdalen --SKIPIF-- = 21300) die("skip see https://gitlab.gnome.org/GNOME/libxml2/-/issues/756"); ?> --FILE-- --SKIPIF-- = 21300) die("skip see https://gitlab.gnome.org/GNOME/libxml2/-/issues/756"); ?> --FILE-- +#PHPTestFest2009 Norway 2009-06-09 \o/ +--EXTENSIONS-- +dom +--SKIPIF-- + +--FILE-- +formatOutput = true; +$root = $doc->createElement('html'); +$root = $doc->appendChild($root); +$head = $doc->createElement('head'); +$head = $root->appendChild($head); +$title = $doc->createElement('title'); +$title = $head->appendChild($title); +$text = $doc->createTextNode('This is the title'); +$text = $title->appendChild($text); +$bytes = $doc->saveHTMLFile($filename); +var_dump($bytes); +echo file_get_contents($filename); +unlink($filename); +?> +--EXPECT-- +int(59) +This is the title diff --git a/ext/dom/tests/DOMDocument_saveHTML_basic_gte_2_13.phpt b/ext/dom/tests/DOMDocument_saveHTML_basic_gte_2_13.phpt new file mode 100644 index 00000000000..c0be105253d --- /dev/null +++ b/ext/dom/tests/DOMDocument_saveHTML_basic_gte_2_13.phpt @@ -0,0 +1,31 @@ +--TEST-- +DOMDocument::saveHTMLFile() should dump the internal document into a file using HTML formatting +--CREDITS-- +Knut Urdalen +#PHPTestFest2009 Norway 2009-06-09 \o/ +--EXTENSIONS-- +dom +--SKIPIF-- + +--FILE-- +createElement('html'); +$root = $doc->appendChild($root); +$head = $doc->createElement('head'); +$head = $root->appendChild($head); +$title = $doc->createElement('title'); +$title = $head->appendChild($title); +$text = $doc->createTextNode('This is the title'); +$text = $title->appendChild($text); +$bytes = $doc->saveHTMLFile($filename); +var_dump($bytes); +echo file_get_contents($filename); +unlink($filename); +?> +--EXPECT-- +int(59) +This is the title diff --git a/ext/dom/tests/DOMDocument_schemaValidate_error5.phpt b/ext/dom/tests/DOMDocument_schemaValidate_error5.phpt index cb57b55b41a..44ea52c2d06 100644 --- a/ext/dom/tests/DOMDocument_schemaValidate_error5.phpt +++ b/ext/dom/tests/DOMDocument_schemaValidate_error5.phpt @@ -17,7 +17,7 @@ var_dump($result); ?> --EXPECTF-- -Warning: DOMDocument::schemaValidate(): I/O warning : failed to load external entity "%snon-existent-file" in %s.php on line %d +Warning: DOMDocument::schemaValidate(): I/O %s : failed to load %s Warning: DOMDocument::schemaValidate(): Failed to locate the main schema resource at '%s/non-existent-file'. in %s.php on line %d diff --git a/ext/dom/tests/dom_create_element.phpt b/ext/dom/tests/dom_create_element.phpt index bd2c8f11dae..70ae54a11bb 100644 --- a/ext/dom/tests/dom_create_element.phpt +++ b/ext/dom/tests/dom_create_element.phpt @@ -251,14 +251,10 @@ try { print $e->getMessage() . "\n"; } -/* This isn't because the xml namespace isn't there and we can't create it */ -print "29 DOMElement::__construct('xml:valid', '', 'http://www.w3.org/XML/1998/namespace')\n"; -try { - $element = new DomElement('xml:valid', '', 'http://www.w3.org/XML/1998/namespace'); - print "valid\n"; -} catch (Exception $e) { - print $e->getMessage() . "\n"; -} +/* There used to be a 29 here that tested DOMElement::__construct('xml:valid', '', 'http://www.w3.org/XML/1998/namespace'). + * In libxml2 version 2.12 or prior this didn't work because the xml namespace isn't there and you can't create it without + * a document. Starting from libxml2 version 2.13 it does actually work because the XML namespace is statically defined. + * The behaviour from version 2.13 is actually the desired behaviour anyway. */ /* the qualifiedName or its prefix is "xmlns" and the namespaceURI is @@ -378,8 +374,6 @@ Namespace Error Namespace Error 28 DOMDocument::createElementNS('http://www.w3.org/XML/1998/namespace', 'xml:valid') valid -29 DOMElement::__construct('xml:valid', '', 'http://www.w3.org/XML/1998/namespace') -Namespace Error 30 DOMDocument::createElementNS('http://wrong.namespaceURI.com', 'xmlns:valid') Namespace Error 31 DOMElement::__construct('xmlns:valid', '', 'http://wrong.namespaceURI.com') diff --git a/ext/libxml/libxml.c b/ext/libxml/libxml.c index 7917f636a9e..4b9e6a918d4 100644 --- a/ext/libxml/libxml.c +++ b/ext/libxml/libxml.c @@ -476,8 +476,10 @@ php_libxml_input_buffer_create_filename(const char *URI, xmlCharEncoding enc) static xmlOutputBufferPtr php_libxml_output_buffer_create_filename(const char *URI, xmlCharEncodingHandlerPtr encoder, - int compression ATTRIBUTE_UNUSED) + int compression) { + ZEND_IGNORE_VALUE(compression); + xmlOutputBufferPtr ret; xmlURIPtr puri; void *context = NULL; diff --git a/ext/simplexml/tests/bug79971_1.phpt b/ext/simplexml/tests/bug79971_1.phpt index 197776d82d3..2ee24e89f12 100644 --- a/ext/simplexml/tests/bug79971_1.phpt +++ b/ext/simplexml/tests/bug79971_1.phpt @@ -20,7 +20,7 @@ var_dump($sxe->asXML("$uri.out%00foo")); --EXPECTF-- Warning: simplexml_load_file(): URI must not contain percent-encoded NUL bytes in %s on line %d -Warning: simplexml_load_file(): I/O warning : failed to load external entity "%s/bug79971_1.xml%00foo" in %s on line %d +Warning: simplexml_load_file(): I/O warning : failed to load %s bool(false) Warning: SimpleXMLElement::asXML(): URI must not contain percent-encoded NUL bytes in %s on line %d diff --git a/ext/soap/php_encoding.c b/ext/soap/php_encoding.c index e0cf63dd1da..0a6edbf5a41 100644 --- a/ext/soap/php_encoding.c +++ b/ext/soap/php_encoding.c @@ -3381,7 +3381,6 @@ xmlNsPtr encode_add_ns(xmlNodePtr node, const char* ns) } else { smart_str prefix = {0}; int num = ++SOAP_GLOBAL(cur_uniq_ns); - xmlChar *enc_ns; while (1) { smart_str_appendl(&prefix, "ns", 2); @@ -3395,9 +3394,15 @@ xmlNsPtr encode_add_ns(xmlNodePtr node, const char* ns) num = ++SOAP_GLOBAL(cur_uniq_ns); } - enc_ns = xmlEncodeSpecialChars(node->doc, BAD_CAST(ns)); + /* Starting with libxml 2.13, we don't have to do this workaround anymore, otherwise we get double-encoded + * entities. See libxml2 commit f506ec66547ef9bac97a2bf306d368ecea8c0c9e. */ +#if LIBXML_VERSION < 21300 + xmlChar *enc_ns = xmlEncodeSpecialChars(node->doc, BAD_CAST(ns)); xmlns = xmlNewNs(node->doc->children, enc_ns, BAD_CAST(prefix.s ? ZSTR_VAL(prefix.s) : "")); xmlFree(enc_ns); +#else + xmlns = xmlNewNs(node->doc->children, BAD_CAST(ns), BAD_CAST(prefix.s ? ZSTR_VAL(prefix.s) : "")); +#endif smart_str_free(&prefix); } } diff --git a/ext/soap/php_xml.c b/ext/soap/php_xml.c index 1bb7fa00a37..446017eb5c8 100644 --- a/ext/soap/php_xml.c +++ b/ext/soap/php_xml.c @@ -94,13 +94,14 @@ xmlDocPtr soap_xmlParseFile(const char *filename) zend_bool old; php_libxml_sanitize_parse_ctxt_options(ctxt); + /* TODO: In libxml2 2.14.0 change this to the new options API so we don't rely on deprecated APIs. */ ctxt->keepBlanks = 0; + ctxt->options |= XML_PARSE_HUGE; ctxt->sax->ignorableWhitespace = soap_ignorableWhitespace; ctxt->sax->comment = soap_Comment; ctxt->sax->warning = NULL; ctxt->sax->error = NULL; /*ctxt->sax->fatalError = NULL;*/ - ctxt->options |= XML_PARSE_HUGE; old = php_libxml_disable_entity_loader(1); xmlParseDocument(ctxt); php_libxml_disable_entity_loader(old); @@ -148,6 +149,7 @@ xmlDocPtr soap_xmlParseMemory(const void *buf, size_t buf_size) ctxt->sax->warning = NULL; ctxt->sax->error = NULL; /*ctxt->sax->fatalError = NULL;*/ + /* TODO: In libxml2 2.14.0 change this to the new options API so we don't rely on deprecated APIs. */ ctxt->options |= XML_PARSE_HUGE; old = php_libxml_disable_entity_loader(1); xmlParseDocument(ctxt); diff --git a/ext/soap/tests/bugs/bug42151.phpt b/ext/soap/tests/bugs/bug42151.phpt index ee53e6d525d..d1bcae83364 100644 --- a/ext/soap/tests/bugs/bug42151.phpt +++ b/ext/soap/tests/bugs/bug42151.phpt @@ -25,8 +25,8 @@ try { } echo "ok\n"; ?> ---EXPECT-- -SOAP-ERROR: Parsing WSDL: Couldn't load from 'httpx://' : failed to load external entity "httpx://" +--EXPECTF-- +SOAP-ERROR: Parsing WSDL: Couldn't load from 'httpx://' : failed to load %s ok I don't get executed either. diff --git a/ext/xml/compat.c b/ext/xml/compat.c index 57eb00dd429..ea1fd835059 100644 --- a/ext/xml/compat.c +++ b/ext/xml/compat.c @@ -716,8 +716,7 @@ XML_GetCurrentByteCount(XML_Parser parser) { /* WARNING: this is identical to ByteIndex; it should probably * be different */ - return parser->parser->input->consumed + - (parser->parser->input->cur - parser->parser->input->base); + return XML_GetCurrentByteIndex(parser); } PHP_XML_API const XML_Char *XML_ExpatVersion(void) diff --git a/ext/xmlwriter/php_xmlwriter.c b/ext/xmlwriter/php_xmlwriter.c index 5cb141dad39..55874420f3b 100644 --- a/ext/xmlwriter/php_xmlwriter.c +++ b/ext/xmlwriter/php_xmlwriter.c @@ -1785,7 +1785,8 @@ static void php_xmlwriter_flush(INTERNAL_FUNCTION_PARAMETERS, int force_string) } output_bytes = xmlTextWriterFlush(ptr); if (buffer) { - RETVAL_STRING((char *) buffer->content); + const xmlChar *content = xmlBufferContent(buffer); + RETVAL_STRING((const char *) content); if (empty) { xmlBufferEmpty(buffer); } -- 2.48.1 From 238d5f0aeaedc9c355f1bc1159b01e357bdaf344 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Wed, 20 Nov 2024 10:47:27 +0100 Subject: [PATCH 08/11] Fix GHSA-p3x9-6h7p-cgfc: libxml streams wrong `content-type` on redirect libxml streams use wrong content-type header when requesting a redirected resource. (cherry picked from commit b6004a043c16b211d462218fbb3f72db68ec2b18) (cherry picked from commit 1196e566681a34564c02173ba234b5a42587ff07) --- ext/dom/tests/ghsa-p3x9-6h7p-cgfc_001.phpt | 60 ++++++++++ ext/dom/tests/ghsa-p3x9-6h7p-cgfc_002.phpt | 60 ++++++++++ ext/dom/tests/ghsa-p3x9-6h7p-cgfc_003.phpt | 60 ++++++++++ ext/libxml/libxml.c | 77 +++++++------ ext/standard/tests/http/newserver.inc | 124 +++++++++++++++++++++ 5 files changed, 348 insertions(+), 33 deletions(-) create mode 100644 ext/dom/tests/ghsa-p3x9-6h7p-cgfc_001.phpt create mode 100644 ext/dom/tests/ghsa-p3x9-6h7p-cgfc_002.phpt create mode 100644 ext/dom/tests/ghsa-p3x9-6h7p-cgfc_003.phpt create mode 100644 ext/standard/tests/http/newserver.inc diff --git a/ext/dom/tests/ghsa-p3x9-6h7p-cgfc_001.phpt b/ext/dom/tests/ghsa-p3x9-6h7p-cgfc_001.phpt new file mode 100644 index 00000000000..87cb2aa0b1f --- /dev/null +++ b/ext/dom/tests/ghsa-p3x9-6h7p-cgfc_001.phpt @@ -0,0 +1,60 @@ +--TEST-- +GHSA-p3x9-6h7p-cgfc: libxml streams use wrong `content-type` header when requesting a redirected resource (Basic) +--EXTENSIONS-- +dom +--SKIPIF-- + +--FILE-- + + + + GHSA-p3x9-6h7p-cgfc + + + + + + +

GHSA-p3x9-6h7p-cgfc

+ + + EOT; + // Intentionally using non-standard casing for content-type to verify it is matched not case sensitively. + yield "data://text/plain,HTTP/1.1 200 OK\r\nconteNt-tyPe: text/html; charset=utf-8\r\n\r\n{$xml}"; +} + +['pid' => $pid, 'uri' => $uri] = http_server('genResponses', $output); +$document = new \DOMDocument(); +$document->loadHTMLFile($uri); + +$h1 = $document->getElementsByTagName('h1'); +var_dump($h1->length); +var_dump($document->saveHTML()); +http_server_kill($pid); +?> +--EXPECT-- +int(1) +string(266) " + + + GHSA-p3x9-6h7p-cgfc + + + + + + +

GHSA-p3x9-6h7p-cgfc

+ + +" diff --git a/ext/dom/tests/ghsa-p3x9-6h7p-cgfc_002.phpt b/ext/dom/tests/ghsa-p3x9-6h7p-cgfc_002.phpt new file mode 100644 index 00000000000..1ce468c3b19 --- /dev/null +++ b/ext/dom/tests/ghsa-p3x9-6h7p-cgfc_002.phpt @@ -0,0 +1,60 @@ +--TEST-- +GHSA-p3x9-6h7p-cgfc: libxml streams use wrong `content-type` header when requesting a redirected resource (Missing content-type) +--EXTENSIONS-- +dom +--SKIPIF-- + +--FILE-- + + + + GHSA-p3x9-6h7p-cgfc + + + + + + +

GHSA-p3x9-6h7p-cgfc

+ + + EOT; + // Missing content-type in actual response. + yield "data://text/plain,HTTP/1.1 200 OK\r\n\r\n{$xml}"; +} + +['pid' => $pid, 'uri' => $uri] = http_server('genResponses', $output); +$document = new \DOMDocument(); +$document->loadHTMLFile($uri); + +$h1 = $document->getElementsByTagName('h1'); +var_dump($h1->length); +var_dump($document->saveHTML()); +http_server_kill($pid); +?> +--EXPECT-- +int(1) +string(266) " + + + GHSA-p3x9-6h7p-cgfc + + + + + + +

GHSA-p3x9-6h7p-cgfc

+ + +" diff --git a/ext/dom/tests/ghsa-p3x9-6h7p-cgfc_003.phpt b/ext/dom/tests/ghsa-p3x9-6h7p-cgfc_003.phpt new file mode 100644 index 00000000000..b8cac7e3247 --- /dev/null +++ b/ext/dom/tests/ghsa-p3x9-6h7p-cgfc_003.phpt @@ -0,0 +1,60 @@ +--TEST-- +GHSA-p3x9-6h7p-cgfc: libxml streams use wrong `content-type` header when requesting a redirected resource (Reason with colon) +--EXTENSIONS-- +dom +--SKIPIF-- + +--FILE-- + + + + GHSA-p3x9-6h7p-cgfc + + + + + + +

GHSA-p3x9-6h7p-cgfc

+ + + EOT; + // Missing content-type in actual response. + yield "data://text/plain,HTTP/1.1 200 OK: This is fine\r\n\r\n{$xml}"; +} + +['pid' => $pid, 'uri' => $uri] = http_server('genResponses', $output); +$document = new \DOMDocument(); +$document->loadHTMLFile($uri); + +$h1 = $document->getElementsByTagName('h1'); +var_dump($h1->length); +var_dump($document->saveHTML()); +http_server_kill($pid); +?> +--EXPECT-- +int(1) +string(266) " + + + GHSA-p3x9-6h7p-cgfc + + + + + + +

GHSA-p3x9-6h7p-cgfc

+ + +" diff --git a/ext/libxml/libxml.c b/ext/libxml/libxml.c index 4b9e6a918d4..1866b7b21f4 100644 --- a/ext/libxml/libxml.c +++ b/ext/libxml/libxml.c @@ -420,42 +420,53 @@ php_libxml_input_buffer_create_filename(const char *URI, xmlCharEncoding enc) if (Z_TYPE(s->wrapperdata) == IS_ARRAY) { zval *header; - ZEND_HASH_FOREACH_VAL_IND(Z_ARRVAL(s->wrapperdata), header) { + /* Scan backwards: The header array might contain the headers for multiple responses, if + * a redirect was followed. + */ + ZEND_HASH_REVERSE_FOREACH_VAL_IND(Z_ARRVAL(s->wrapperdata), header) { const char buf[] = "Content-Type:"; - if (Z_TYPE_P(header) == IS_STRING && - !zend_binary_strncasecmp(Z_STRVAL_P(header), Z_STRLEN_P(header), buf, sizeof(buf)-1, sizeof(buf)-1)) { - char *needle = estrdup("charset="); - char *haystack = estrndup(Z_STRVAL_P(header), Z_STRLEN_P(header)); - char *encoding = php_stristr(haystack, needle, Z_STRLEN_P(header), sizeof("charset=")-1); - - if (encoding) { - char *end; - - encoding += sizeof("charset=")-1; - if (*encoding == '"') { - encoding++; - } - end = strchr(encoding, ';'); - if (end == NULL) { - end = encoding + strlen(encoding); - } - end--; /* end == encoding-1 isn't a buffer underrun */ - while (*end == ' ' || *end == '\t') { - end--; - } - if (*end == '"') { - end--; - } - if (encoding >= end) continue; - *(end+1) = '\0'; - enc = xmlParseCharEncoding(encoding); - if (enc <= XML_CHAR_ENCODING_NONE) { - enc = XML_CHAR_ENCODING_NONE; + if (Z_TYPE_P(header) == IS_STRING) { + /* If no colon is found in the header, we assume it's the HTTP status line and bail out. */ + char *colon = memchr(Z_STRVAL_P(header), ':', Z_STRLEN_P(header)); + char *space = memchr(Z_STRVAL_P(header), ' ', Z_STRLEN_P(header)); + if (colon == NULL || space < colon) { + break; + } + + if (!zend_binary_strncasecmp(Z_STRVAL_P(header), Z_STRLEN_P(header), buf, sizeof(buf)-1, sizeof(buf)-1)) { + char *needle = estrdup("charset="); + char *haystack = estrndup(Z_STRVAL_P(header), Z_STRLEN_P(header)); + char *encoding = php_stristr(haystack, needle, Z_STRLEN_P(header), sizeof("charset=")-1); + + if (encoding) { + char *end; + + encoding += sizeof("charset=")-1; + if (*encoding == '"') { + encoding++; + } + end = strchr(encoding, ';'); + if (end == NULL) { + end = encoding + strlen(encoding); + } + end--; /* end == encoding-1 isn't a buffer underrun */ + while (*end == ' ' || *end == '\t') { + end--; + } + if (*end == '"') { + end--; + } + if (encoding >= end) continue; + *(end+1) = '\0'; + enc = xmlParseCharEncoding(encoding); + if (enc <= XML_CHAR_ENCODING_NONE) { + enc = XML_CHAR_ENCODING_NONE; + } } + efree(haystack); + efree(needle); + break; /* found content-type */ } - efree(haystack); - efree(needle); - break; /* found content-type */ } } ZEND_HASH_FOREACH_END(); } diff --git a/ext/standard/tests/http/newserver.inc b/ext/standard/tests/http/newserver.inc new file mode 100644 index 00000000000..5c636705e8c --- /dev/null +++ b/ext/standard/tests/http/newserver.inc @@ -0,0 +1,124 @@ + $pid, + 'uri' => 'http://' . stream_socket_get_name($server, false), + ]; + } + + return $server; +} + +/* Minimal HTTP server with predefined responses. + * + * $socket_string is the socket to create and listen on (e.g. tcp://127.0.0.1:1234) + * $files is an iterable of files or callable generator yielding files. + * containing N responses for N expected requests. Server dies after N requests. + * $output is a stream on which everything sent by clients is written to + */ +function http_server($files, &$output = null) { + + if (!is_resource($server = http_server_init($output))) { + return $server; + } + + if (is_callable($files)) { + $files = $files($server); + } + + foreach($files as $file) { + + $sock = stream_socket_accept($server); + if (!$sock) { + exit(1); + } + + // read headers + + $content_length = 0; + + stream_set_blocking($sock, false); + while (!feof($sock)) { + + list($r, $w, $e) = array(array($sock), null, null); + if (!stream_select($r, $w, $e, 1)) continue; + + $line = stream_get_line($sock, 8192, "\r\n"); + if ($line === '') { + fwrite($output, "\r\n"); + break; + } else if ($line !== false) { + fwrite($output, "$line\r\n"); + + if (preg_match('#^Content-Length\s*:\s*([[:digit:]]+)\s*$#i', $line, $matches)) { + $content_length = (int) $matches[1]; + } + } + } + stream_set_blocking($sock, true); + + // read content + + if ($content_length > 0) { + stream_copy_to_stream($sock, $output, $content_length); + } + + // send response + + $fd = fopen($file, 'rb'); + stream_copy_to_stream($fd, $sock); + + fclose($sock); + } + + exit(0); +} + +function http_server_sleep($micro_seconds = 500000) +{ + if (!is_resource($server = http_server_init($output))) { + return $server; + } + + $sock = stream_socket_accept($server); + if (!$sock) { + exit(1); + } + + usleep($micro_seconds); + + fclose($sock); + + exit(0); +} + +function http_server_kill(int $pid) { + posix_kill($pid, SIGTERM); + pcntl_waitpid($pid, $status); +} -- 2.48.1 From c5e836c5f98c6a01778595d448bb6a5b84eccec1 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Wed, 18 Dec 2024 18:44:05 +0100 Subject: [PATCH 09/11] Fix GHSA-wg4p-4hqh-c3g9 (cherry picked from commit 0e715e71d945b68f8ccedd62c5960df747af6625) (cherry picked from commit 294140ee981fda6a38244215e4b16e53b7f5b2a6) --- ext/xml/tests/toffset_bounds.phpt | 42 +++++++++++++++++++++++++++++++ ext/xml/xml.c | 12 ++++++--- 2 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 ext/xml/tests/toffset_bounds.phpt diff --git a/ext/xml/tests/toffset_bounds.phpt b/ext/xml/tests/toffset_bounds.phpt new file mode 100644 index 00000000000..5a3fd22f86c --- /dev/null +++ b/ext/xml/tests/toffset_bounds.phpt @@ -0,0 +1,42 @@ +--TEST-- +XML_OPTION_SKIP_TAGSTART bounds +--EXTENSIONS-- +xml +--FILE-- +"; +$parser = xml_parser_create(); +xml_parser_set_option($parser, XML_OPTION_SKIP_TAGSTART, 100); +$res = xml_parse_into_struct($parser,$sample,$vals,$index); +var_dump($vals); +?> +--EXPECT-- +array(3) { + [0]=> + array(3) { + ["tag"]=> + string(0) "" + ["type"]=> + string(4) "open" + ["level"]=> + int(1) + } + [1]=> + array(3) { + ["tag"]=> + string(0) "" + ["type"]=> + string(8) "complete" + ["level"]=> + int(2) + } + [2]=> + array(3) { + ["tag"]=> + string(0) "" + ["type"]=> + string(5) "close" + ["level"]=> + int(1) + } +} diff --git a/ext/xml/xml.c b/ext/xml/xml.c index 6fe6151c7a1..b56bf79f55d 100644 --- a/ext/xml/xml.c +++ b/ext/xml/xml.c @@ -773,9 +773,11 @@ void _xml_startElementHandler(void *userData, const XML_Char *name, const XML_Ch array_init(&tag); array_init(&atr); - _xml_add_to_info(parser, ZSTR_VAL(tag_name) + parser->toffset); + char *skipped_tag_name = SKIP_TAGSTART(ZSTR_VAL(tag_name)); - add_assoc_string(&tag, "tag", SKIP_TAGSTART(ZSTR_VAL(tag_name))); /* cast to avoid gcc-warning */ + _xml_add_to_info(parser, skipped_tag_name); + + add_assoc_string(&tag, "tag", skipped_tag_name); add_assoc_string(&tag, "type", "open"); add_assoc_long(&tag, "level", parser->level); @@ -842,9 +844,11 @@ void _xml_endElementHandler(void *userData, const XML_Char *name) } else { array_init(&tag); - _xml_add_to_info(parser, ZSTR_VAL(tag_name) + parser->toffset); + char *skipped_tag_name = SKIP_TAGSTART(ZSTR_VAL(tag_name)); + + _xml_add_to_info(parser, skipped_tag_name); - add_assoc_string(&tag, "tag", SKIP_TAGSTART(ZSTR_VAL(tag_name))); /* cast to avoid gcc-warning */ + add_assoc_string(&tag, "tag", skipped_tag_name); add_assoc_string(&tag, "type", "close"); add_assoc_long(&tag, "level", parser->level); -- 2.48.1 From 3faf7b2017ccd1e7347c30cf64cddcb684300cba Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Fri, 17 Nov 2023 19:45:40 +0100 Subject: [PATCH 10/11] Fix GH-12702: libxml2 2.12.0 issue building from src Fixes GH-12702. Co-authored-by: nono303 (cherry picked from commit 6a76e5d0a2dcf46b4ab74cc3ffcbfeb860c4fdb3) (cherry picked from commit d7ab2bb9856d938fca7989575695c14c25892589) --- ext/dom/document.c | 1 + ext/libxml/php_libxml.h | 1 + 2 files changed, 2 insertions(+) diff --git a/ext/dom/document.c b/ext/dom/document.c index af06fb41240..f8071774b92 100644 --- a/ext/dom/document.c +++ b/ext/dom/document.c @@ -25,6 +25,7 @@ #if HAVE_LIBXML && HAVE_DOM #include "php_dom.h" #include +#include #ifdef LIBXML_SCHEMAS_ENABLED #include #include diff --git a/ext/libxml/php_libxml.h b/ext/libxml/php_libxml.h index 92028d5703e..6f3295b5241 100644 --- a/ext/libxml/php_libxml.h +++ b/ext/libxml/php_libxml.h @@ -37,6 +37,7 @@ extern zend_module_entry libxml_module_entry; #include "zend_smart_str.h" #include +#include #define LIBXML_SAVE_NOEMPTYTAG 1<<2 -- 2.48.1 From 8ab957ca87b42b808aec7fd472fbc4063073a119 Mon Sep 17 00:00:00 2001 From: Remi Collet Date: Thu, 13 Mar 2025 09:39:19 +0100 Subject: [PATCH 11/11] NEWS (cherry picked from commit adae2b8de8963ac6f92103803bf91a5174172f88) --- NEWS | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/NEWS b/NEWS index 09cf2cfa0bb..fda646c7010 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,23 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| +Backported from 8.1.32 + +- LibXML: + . Fixed GHSA-wg4p-4hqh-c3g9 (Reocurrence of #72714). (nielsdos) + . Fixed GHSA-p3x9-6h7p-cgfc (libxml streams use wrong `content-type` header + when requesting a redirected resource). (CVE-2025-1219) (timwolla) + +- Streams: + . Fixed GHSA-hgf54-96fm-v528 (Stream HTTP wrapper header check might omit + basic auth header). (CVE-2025-1736) (Jakub Zelenka) + . Fixed GHSA-52jp-hrpf-2jff (Stream HTTP wrapper truncate redirect location + to 1024 bytes). (CVE-2025-1861) (Jakub Zelenka) + . Fixed GHSA-pcmh-g36c-qc44 (Streams HTTP wrapper does not fail for headers + without colon). (CVE-2025-1734) (Jakub Zelenka) + . Fixed GHSA-v8xr-gpvj-cx9g (Header parser of `http` stream wrapper does not + handle folded headers). (CVE-2025-1217) (Jakub Zelenka) + Backported from 8.1.31 - CLI: -- 2.48.1