From 78ae0886bd1a3e42c53c9ba65764b6e6357640b5 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)
---
 .../DOMDocument_loadXML_error2_gte2_11.phpt   |  34 +++
 ...> DOMDocument_loadXML_error2_pre2_11.phpt} |   4 +
 .../DOMDocument_load_error2_gte2_11.phpt      |  34 +++
 ...t => DOMDocument_load_error2_pre2_11.phpt} |   4 +
 ext/libxml/libxml.c                           |   2 +
 ext/libxml/tests/bug61367-read_2.phpt         |   2 +-
 .../tests/libxml_disable_entity_loader_2.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 +
 24 files changed, 404 insertions(+), 64 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} (90%)
 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} (90%)
 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--
+<?php
+if (LIBXML_VERSION < 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:
+- 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 <dejalatele@gmail.com>
+--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 90%
rename from ext/dom/tests/DOMDocument_loadXML_error2.phpt
rename to ext/dom/tests/DOMDocument_loadXML_error2_pre2_11.phpt
index 6d56a317ed7..0e36d209058 100644
--- a/ext/dom/tests/DOMDocument_loadXML_error2.phpt
+++ b/ext/dom/tests/DOMDocument_loadXML_error2_pre2_11.phpt
@@ -1,5 +1,9 @@
 --TEST--
 Test DOMDocument::loadXML() detects not-well formed XML
+--SKIPIF--
+<?php
+if (LIBXML_VERSION >= 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:
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--
+<?php
+if (LIBXML_VERSION < 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:
+- 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 <dejalatele@gmail.com>
+--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 90%
rename from ext/dom/tests/DOMDocument_load_error2.phpt
rename to ext/dom/tests/DOMDocument_load_error2_pre2_11.phpt
index f450cf16545..b97fff9d2f1 100644
--- a/ext/dom/tests/DOMDocument_load_error2.phpt
+++ b/ext/dom/tests/DOMDocument_load_error2_pre2_11.phpt
@@ -1,5 +1,9 @@
 --TEST--
 Test DOMDocument::load() detects not-well formed XML
+--SKIPIF--
+<?php
+if (LIBXML_VERSION >= 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:
diff --git a/ext/libxml/libxml.c b/ext/libxml/libxml.c
index 73486ae253f..c8bd1be60a4 100644
--- a/ext/libxml/libxml.c
+++ b/ext/libxml/libxml.c
@@ -525,6 +525,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 ed6576aa752..b935261cb2e 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
 
 Warning: Attempt to read property "nodeValue" on null 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 caa7356ad30..d90f909ac2b 100644
--- a/ext/libxml/tests/libxml_disable_entity_loader_2.phpt
+++ b/ext/libxml/tests/libxml_disable_entity_loader_2.phpt
@@ -38,6 +38,6 @@ bool(true)
 Deprecated: Function libxml_disable_entity_loader() is deprecated in %s on line %d
 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_variation2.phpt b/ext/libxml/tests/libxml_set_external_entity_loader_variation2.phpt
index 87894bcb91a..ddaf9bfa50e 100644
--- a/ext/libxml/tests/libxml_set_external_entity_loader_variation2.phpt
+++ b/ext/libxml/tests/libxml_set_external_entity_loader_variation2.phpt
@@ -39,6 +39,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 61d45385b62..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, string|array $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..61d45385b62
--- /dev/null
+++ b/ext/standard/tests/http/ServerClientTestCase.inc
@@ -0,0 +1,199 @@
+<?php
+
+const WORKER_ARGV_VALUE = 'RUN_WORKER';
+
+const WORKER_DEFAULT_NAME = 'server';
+
+function phpt_notify(string $worker = WORKER_DEFAULT_NAME, string $message = ""): void
+{
+    ServerClientTestCase::getInstance()->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, string|array $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 744cff9cc72..461a649b147 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 bc71fd4e411..126b77bae62 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 c40123560ef..0f04f565d6b 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 37a47df060a..aa23a96aedc 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 6c84679ff63..8ef42b5700f 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 bb7945ce62d..595f0fd9272 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 1d0e4fa70a2..99c8e025f93 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 f935b5a02ca..945225d9e06 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 078d605b671..6619db3a5dd 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 ad5ddc879ce..7eb9015d963 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 d0396e819fb..f8f67886634 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 037d2002cc5..671c82e8ee0 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--
+<?php
+if (!defined("LIBXML_VERSION")) die('skip libxml2 test');
+if (LIBXML_VERSION < 21100) die('skip libxml2 test variant for version >= 2.11');
+?>
+--FILE--
+<?php
+/*
+this test works fine with Expat but fails with libxml
+which we now use as default
+
+further investigation has shown that not only line count
+is skipped on CDATA sections but that libxml does also
+show different column numbers and byte positions depending
+on context and in opposition to what one would expect to
+see and what good old Expat reported just fine ...
+*/
+
+$xmls = array();
+
+// Case 1: CDATA Sections
+$xmls["CDATA"] ='<?xml version="1.0" encoding="iso-8859-1" ?>
+<data>
+<![CDATA[
+multi
+line
+CDATA
+block
+]]>
+</data>';
+
+// Case 2: replace some characters so that we get comments instead
+$xmls["Comment"] ='<?xml version="1.0" encoding="iso-8859-1" ?>
+<data>
+<!-- ATA[
+multi
+line
+CDATA
+block
+-->
+</data>';
+
+// Case 3: replace even more characters so that only textual data is left
+$xmls["Text"] ='<?xml version="1.0" encoding="iso-8859-1" ?>
+<data>
+-!-- ATA[
+multi
+line
+CDATA
+block
+---
+</data>';
+
+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("</$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 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
+<DATA> at line 2, col %d (byte 50)
+</DATA> at line 9, col %d (byte 96)
+Comment
+<DATA> at line 2, col %d (byte 50)
+</DATA> at line 9, col %d (byte 96)
+Text
+<DATA> at line 2, col %d (byte 50)
+</DATA> 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 b6c0b875818..90283850d24 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)
 <?php
 require_once("skipif.inc");
 if (!defined("LIBXML_VERSION")) die('skip libxml2 test');
+if (LIBXML_VERSION >= 21100) die('skip libxml2 test variant for version < 2.11');
 ?>
 --FILE--
 <?php
-- 
2.48.1

From 6e8e9f558aa0903e9650dd166a0a53c359d9e9e0 Mon Sep 17 00:00:00 2001
From: Niels Dossche <7771979+nielsdos@users.noreply.github.com>
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)
---
 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 c8bd1be60a4..554fcc34ff2 100644
--- a/ext/libxml/libxml.c
+++ b/ext/libxml/libxml.c
@@ -481,7 +481,11 @@ static void _php_libxml_free_error(void *ptr)
 	xmlResetError((xmlErrorPtr) ptr);
 }
 
-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;
@@ -734,7 +738,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);
 
@@ -1037,11 +1045,9 @@ PHP_FUNCTION(libxml_use_internal_errors)
 /* {{{ Retrieve last error from libxml */
 PHP_FUNCTION(libxml_get_last_error)
 {
-	xmlErrorPtr error;
-
 	ZEND_PARSE_PARAMETERS_NONE();
 
-	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 e5e7f2f9554..6060f634508 100644
--- a/ext/soap/php_sdl.c
+++ b/ext/soap/php_sdl.c
@@ -331,7 +331,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 6cb68826aaf68ffe8c70c8782450c38970236040 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)
---
 ext/dom/document.c                            |  6 ++--
 .../DOMDocument_loadHTMLfile_error1.phpt      |  2 +-
 .../DOMDocument_loadXML_error2_pre2_11.phpt   |  3 +-
 .../DOMDocument_load_error2_pre2_11.phpt      |  3 +-
 .../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 +-
 18 files changed, 97 insertions(+), 29 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 02522b5014f..7735e5d5dc3 100644
--- a/ext/dom/document.c
+++ b/ext/dom/document.c
@@ -1253,11 +1253,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;
@@ -1267,7 +1269,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_loadXML_error2_pre2_11.phpt b/ext/dom/tests/DOMDocument_loadXML_error2_pre2_11.phpt
index 0e36d209058..7e10771fdb7 100644
--- a/ext/dom/tests/DOMDocument_loadXML_error2_pre2_11.phpt
+++ b/ext/dom/tests/DOMDocument_loadXML_error2_pre2_11.phpt
@@ -2,6 +2,7 @@
 Test DOMDocument::loadXML() detects not-well formed XML
 --SKIPIF--
 <?php
+include('skipif.inc');
 if (LIBXML_VERSION >= 21100) die('skip libxml2 test variant for version < 2.11');
 ?>
 --DESCRIPTION--
@@ -14,8 +15,6 @@ Environment variables used in the test:
 Antonio Diaz Ruiz <dejalatele@gmail.com>
 --INI--
 assert.bail=true
---SKIPIF--
-<?php include('skipif.inc'); ?>
 --ENV--
 XML_FILE=/not_well_formed2.xml
 LOAD_OPTIONS=0
diff --git a/ext/dom/tests/DOMDocument_load_error2_pre2_11.phpt b/ext/dom/tests/DOMDocument_load_error2_pre2_11.phpt
index b97fff9d2f1..74b20c171e0 100644
--- a/ext/dom/tests/DOMDocument_load_error2_pre2_11.phpt
+++ b/ext/dom/tests/DOMDocument_load_error2_pre2_11.phpt
@@ -2,6 +2,7 @@
 Test DOMDocument::load() detects not-well formed XML
 --SKIPIF--
 <?php
+include('skipif.inc');
 if (LIBXML_VERSION >= 21100) die('skip libxml2 test variant for version < 2.11');
 ?>
 --DESCRIPTION--
@@ -14,8 +15,6 @@ Environment variables used in the test:
 Antonio Diaz Ruiz <dejalatele@gmail.com>
 --INI--
 assert.bail=true
---SKIPIF--
-<?php include('skipif.inc'); ?>
 --ENV--
 XML_FILE=/not_well_formed2.xml
 LOAD_OPTIONS=0
diff --git a/ext/dom/tests/DOMDocument_relaxNGValidate_error2.phpt b/ext/dom/tests/DOMDocument_relaxNGValidate_error2.phpt
index 3aa6a3b3189..bf8d7befa53 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 <knut@php.net>
 --SKIPIF--
 <?php
 require_once __DIR__ .'/skipif.inc';
+if (LIBXML_VERSION >= 21300) die("skip see https://gitlab.gnome.org/GNOME/libxml2/-/issues/756");
 ?>
 --FILE--
 <?php
diff --git a/ext/dom/tests/DOMDocument_saveHTMLFile_formatOutput.phpt b/ext/dom/tests/DOMDocument_saveHTMLFile_formatOutput.phpt
index 376c9a8e323..8d7baa7b7e8 100644
--- a/ext/dom/tests/DOMDocument_saveHTMLFile_formatOutput.phpt
+++ b/ext/dom/tests/DOMDocument_saveHTMLFile_formatOutput.phpt
@@ -6,6 +6,7 @@ Knut Urdalen <knut@php.net>
 --SKIPIF--
 <?php
 require_once __DIR__ .'/skipif.inc';
+if (LIBXML_VERSION >= 21300) die("skip see https://gitlab.gnome.org/GNOME/libxml2/-/issues/756");
 ?>
 --FILE--
 <?php
diff --git a/ext/dom/tests/DOMDocument_saveHTMLFile_formatOutput_gte_2_13.phpt b/ext/dom/tests/DOMDocument_saveHTMLFile_formatOutput_gte_2_13.phpt
new file mode 100644
index 00000000000..3477edfcf5f
--- /dev/null
+++ b/ext/dom/tests/DOMDocument_saveHTMLFile_formatOutput_gte_2_13.phpt
@@ -0,0 +1,32 @@
+--TEST--
+DOMDocument::saveHTMLFile() should format output on demand
+--CREDITS--
+Knut Urdalen <knut@php.net>
+#PHPTestFest2009 Norway 2009-06-09 \o/
+--EXTENSIONS--
+dom
+--SKIPIF--
+<?php
+if (LIBXML_VERSION < 21300) die("skip see https://gitlab.gnome.org/GNOME/libxml2/-/issues/756");
+?>
+--FILE--
+<?php
+$filename = __DIR__."/DOMDocument_saveHTMLFile_formatOutput_gte_2_13.html";
+$doc = new DOMDocument('1.0');
+$doc->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)
+<html><head><title>This is the title</title></head></html>
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 <knut@php.net>
+#PHPTestFest2009 Norway 2009-06-09 \o/
+--EXTENSIONS--
+dom
+--SKIPIF--
+<?php
+if (LIBXML_VERSION < 21300) die("skip see https://gitlab.gnome.org/GNOME/libxml2/-/issues/756");
+?>
+--FILE--
+<?php
+$filename = __DIR__."/DOMDocument_saveHTMLFile_basic_gte_2_13.html";
+$doc = new DOMDocument('1.0');
+$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)
+<html><head><title>This is the title</title></head></html>
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 554fcc34ff2..28dd86a55c9 100644
--- a/ext/libxml/libxml.c
+++ b/ext/libxml/libxml.c
@@ -428,8 +428,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 f5723e213a4..2634d2c7db4 100644
--- a/ext/soap/php_encoding.c
+++ b/ext/soap/php_encoding.c
@@ -3379,7 +3379,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);
@@ -3393,9 +3392,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 ed3495c1266..58c176031f9 100644
--- a/ext/soap/php_xml.c
+++ b/ext/soap/php_xml.c
@@ -92,13 +92,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);
@@ -146,6 +147,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 f945a8753e5..dd14d1afb62 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 3b2a0cdf7fb..4d1f506840a 100644
--- a/ext/xml/compat.c
+++ b/ext/xml/compat.c
@@ -714,8 +714,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 61e4a3a7d95..8a3fa1cea67 100644
--- a/ext/xmlwriter/php_xmlwriter.c
+++ b/ext/xmlwriter/php_xmlwriter.c
@@ -1001,7 +1001,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 1196e566681a34564c02173ba234b5a42587ff07 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= <tim@tideways-gmbh.com>
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)
---
 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 ++++++++++++----------
 4 files changed, 224 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

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..47212cb3410
--- /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--
+<?php
+if (@!include "./ext/standard/tests/http/server.inc") die('skip server.inc not available');
+http_server_skipif();
+?>
+--FILE--
+<?php
+require "./ext/standard/tests/http/server.inc";
+
+function genResponses($server) {
+    $uri = 'http://' . stream_socket_get_name($server, false);
+    yield "data://text/plain,HTTP/1.1 302 Moved Temporarily\r\nLocation: $uri/document.xml\r\nContent-Type: text/html;charset=utf-16\r\n\r\n";
+    $xml = <<<'EOT'
+        <!doctype html>
+        <html>
+            <head>
+                <title>GHSA-p3x9-6h7p-cgfc</title>
+
+                <meta charset="utf-8" />
+                <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
+            </head>
+
+            <body>
+                <h1>GHSA-p3x9-6h7p-cgfc</h1>
+            </body>
+        </html>
+        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) "<!DOCTYPE html>
+<html>
+    <head>
+        <title>GHSA-p3x9-6h7p-cgfc</title>
+
+        <meta charset="utf-8">
+        <meta http-equiv="Content-type" content="text/html; charset=utf-8">
+    </head>
+
+    <body>
+        <h1>GHSA-p3x9-6h7p-cgfc</h1>
+    </body>
+</html>
+"
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..a7eff3b9a8b
--- /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--
+<?php
+if (@!include "./ext/standard/tests/http/server.inc") die('skip server.inc not available');
+http_server_skipif();
+?>
+--FILE--
+<?php
+require "./ext/standard/tests/http/server.inc";
+
+function genResponses($server) {
+    $uri = 'http://' . stream_socket_get_name($server, false);
+    yield "data://text/plain,HTTP/1.1 302 Moved Temporarily\r\nLocation: $uri/document.xml\r\nContent-Type: text/html;charset=utf-16\r\n\r\n";
+    $xml = <<<'EOT'
+        <!doctype html>
+        <html>
+            <head>
+                <title>GHSA-p3x9-6h7p-cgfc</title>
+
+                <meta charset="utf-8" />
+                <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
+            </head>
+
+            <body>
+                <h1>GHSA-p3x9-6h7p-cgfc</h1>
+            </body>
+        </html>
+        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) "<!DOCTYPE html>
+<html>
+    <head>
+        <title>GHSA-p3x9-6h7p-cgfc</title>
+
+        <meta charset="utf-8">
+        <meta http-equiv="Content-type" content="text/html; charset=utf-8">
+    </head>
+
+    <body>
+        <h1>GHSA-p3x9-6h7p-cgfc</h1>
+    </body>
+</html>
+"
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..178b35f3525
--- /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--
+<?php
+if (@!include "./ext/standard/tests/http/server.inc") die('skip server.inc not available');
+http_server_skipif();
+?>
+--FILE--
+<?php
+require "./ext/standard/tests/http/server.inc";
+
+function genResponses($server) {
+    $uri = 'http://' . stream_socket_get_name($server, false);
+    yield "data://text/plain,HTTP/1.1 302 Moved Temporarily\r\nLocation: $uri/document.xml\r\nContent-Type: text/html;charset=utf-16\r\n\r\n";
+    $xml = <<<'EOT'
+        <!doctype html>
+        <html>
+            <head>
+                <title>GHSA-p3x9-6h7p-cgfc</title>
+
+                <meta charset="utf-8" />
+                <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
+            </head>
+
+            <body>
+                <h1>GHSA-p3x9-6h7p-cgfc</h1>
+            </body>
+        </html>
+        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) "<!DOCTYPE html>
+<html>
+    <head>
+        <title>GHSA-p3x9-6h7p-cgfc</title>
+
+        <meta charset="utf-8">
+        <meta http-equiv="Content-type" content="text/html; charset=utf-8">
+    </head>
+
+    <body>
+        <h1>GHSA-p3x9-6h7p-cgfc</h1>
+    </body>
+</html>
+"
diff --git a/ext/libxml/libxml.c b/ext/libxml/libxml.c
index 28dd86a55c9..7886ca79ad9 100644
--- a/ext/libxml/libxml.c
+++ b/ext/libxml/libxml.c
@@ -372,42 +372,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();
 		}
-- 
2.48.1

From 294140ee981fda6a38244215e4b16e53b7f5b2a6 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)
---
 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--
+<?php
+$sample = "<?xml version=\"1.0\"?><test><child/></test>";
+$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 cc1457d9705..cac86ca7508 100644
--- a/ext/xml/xml.c
+++ b/ext/xml/xml.c
@@ -668,9 +668,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);
 
@@ -737,9 +739,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 d7ab2bb9856d938fca7989575695c14c25892589 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 <github@nono303.net>
(cherry picked from commit 6a76e5d0a2dcf46b4ab74cc3ffcbfeb860c4fdb3)
---
 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 7735e5d5dc3..5ef5dc479d6 100644
--- a/ext/dom/document.c
+++ b/ext/dom/document.c
@@ -23,6 +23,7 @@
 #if defined(HAVE_LIBXML) && defined(HAVE_DOM)
 #include "php_dom.h"
 #include <libxml/SAX.h>
+#include <libxml/xmlsave.h>
 #ifdef LIBXML_SCHEMAS_ENABLED
 #include <libxml/relaxng.h>
 #include <libxml/xmlschemas.h>
diff --git a/ext/libxml/php_libxml.h b/ext/libxml/php_libxml.h
index d0ce7cec714..02717417a71 100644
--- a/ext/libxml/php_libxml.h
+++ b/ext/libxml/php_libxml.h
@@ -35,6 +35,7 @@ extern zend_module_entry libxml_module_entry;
 
 #include "zend_smart_str.h"
 #include <libxml/tree.h>
+#include <libxml/parser.h>
 
 #define LIBXML_SAVE_NOEMPTYTAG 1<<2
 
-- 
2.48.1

