php/php-cve-2025-1219.patch
Remi Collet 1d1654b533 Fix CVEs up to 8.1.34:
Fix Heap-Use-After-Free in sapi_read_post_data Processing in CLI SAPI Interface  GHSA-4w77-75f9-2c8w
Fix Configuring a proxy in a stream context might allow for CRLF injection in URIs  CVE-2024-11234
Fix Single byte overread with convert.quoted-printable-decode filter  CVE-2024-11233
Fix Leak partial content of the heap through heap buffer over-read  CVE-2024-8929
Fix libxml streams use wrong `content-type` header when requesting a redirected resource  CVE-2025-1219
Fix Stream HTTP wrapper header check might omit basic auth header  CVE-2025-1736
Fix Stream HTTP wrapper truncate redirect location to 1024 bytes  CVE-2025-1861
Fix Streams HTTP wrapper does not fail for headers without colon  CVE-2025-1734
Fix Header parser of `http` stream wrapper does not handle folded headers  CVE-2025-1217
Fix pgsql extension does not check for errors during escaping  CVE-2025-1735
Fix NULL Pointer Dereference in PHP SOAP Extension via Large XML Namespace Prefix  CVE-2025-6491
Fix Null byte termination in hostnames  CVE-2025-1220
Fix Null byte termination in dns_get_record()  GHSA-www2-q4fc-65wf
Fix Heap buffer overflow in array_merge()  CVE-2025-14178
Fix Information Leak of Memory in getimagesize  CVE-2025-14177

Resolves: RHEL-141181
2026-01-19 08:43:37 +01:00

1907 lines
66 KiB
Diff

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--
+<?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 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--
+<?php
+include('skipif.inc');
+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:
@@ -10,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_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 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--
+<?php
+include('skipif.inc');
+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:
@@ -10,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/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 @@
+<?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, $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--
+<?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 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)
<?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 087e974d74efc977dfbd18fd3cd568b60fb7675d 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)
(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 <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 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?= <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)
(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--
+<?php
+if (@!include "./ext/standard/tests/http/newserver.inc") die('skip server.inc not available');
+http_server_skipif();
+?>
+--FILE--
+<?php
+require "./ext/standard/tests/http/newserver.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..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--
+<?php
+if (@!include "./ext/standard/tests/http/newserver.inc") die('skip server.inc not available');
+http_server_skipif();
+?>
+--FILE--
+<?php
+require "./ext/standard/tests/http/newserver.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..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--
+<?php
+if (@!include "./ext/standard/tests/http/newserver.inc") die('skip server.inc not available');
+http_server_skipif();
+?>
+--FILE--
+<?php
+require "./ext/standard/tests/http/newserver.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 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 @@
+<?php declare(strict_types=1);
+
+function http_server_skipif() {
+
+ if (!function_exists('pcntl_fork')) die('skip pcntl_fork() not available');
+ if (!function_exists('posix_kill')) die('skip posix_kill() not available');
+ if (!stream_socket_server('tcp://localhost:0')) die('skip stream_socket_server() failed');
+}
+
+function http_server_init(&$output = null) {
+ pcntl_alarm(60);
+
+ $server = stream_socket_server('tcp://localhost:0', $errno, $errstr);
+ if (!$server) {
+ return false;
+ }
+
+ if ($output === null) {
+ $output = tmpfile();
+ if ($output === false) {
+ return false;
+ }
+ }
+
+ $pid = pcntl_fork();
+ if ($pid == -1) {
+ die('could not fork');
+ } else if ($pid) {
+ return [
+ 'pid' => $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--
+<?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 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 <github@nono303.net>
(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 <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 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 <libxml/tree.h>
+#include <libxml/parser.h>
#define LIBXML_SAVE_NOEMPTYTAG 1<<2
--
2.48.1
From 8ab957ca87b42b808aec7fd472fbc4063073a119 Mon Sep 17 00:00:00 2001
From: Remi Collet <remi@remirepo.net>
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