summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRemi Collet <remi@remirepo.net>2025-03-13 11:10:36 +0100
committerRemi Collet <remi@php.net>2025-03-13 11:10:36 +0100
commit6e7731500aec13a717379932940501d43e49d1bb (patch)
treebfc2c8e8781b985207699ef2bb9de7e4acacdf93
parente3b1264950afa02a001c9c62eea886838e436c27 (diff)
Fix libxml streams use wrong `content-type` header when requesting a redirected resourceHEADmaster
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 use oracle client library version 23.7 on x86_64 and aarch64
-rw-r--r--failed.txt35
-rw-r--r--php-8.0.30-libxml212.patch681
-rw-r--r--php-8.0.30-pcretests.patch43
-rw-r--r--php-cve-2025-1217.patch909
-rw-r--r--php-cve-2025-1219.patch1779
-rw-r--r--php-cve-2025-1734.patch300
-rw-r--r--php-cve-2025-1736.patch241
-rw-r--r--php-cve-2025-1861.patch348
-rw-r--r--php.spec59
9 files changed, 3668 insertions, 727 deletions
diff --git a/failed.txt b/failed.txt
index 622e3ca..c75ec72 100644
--- a/failed.txt
+++ b/failed.txt
@@ -1,30 +1,21 @@
-===== 8.0.30-10 (2024-11-22)
+===== 8.0.30-13 (2025-03-13)
$ grep -ar 'Tests failed' /var/lib/mock/*/build.log
-/var/lib/mock/scl80el8a/build.log:Tests failed : 3
-/var/lib/mock/scl80el8x/build.log:Tests failed : 3
-/var/lib/mock/scl80el9a/build.log:Tests failed : 3
-/var/lib/mock/scl80el9x/build.log:Tests failed : 3
-/var/lib/mock/scl80el10a/build.log:Tests failed : 3
-/var/lib/mock/scl80el10x/build.log:Tests failed : 3
-/var/lib/mock/scl80fc39a/build.log:Tests failed : 2
-/var/lib/mock/scl80fc39x/build.log:Tests failed : 2
-/var/lib/mock/scl80fc40a/build.log:Tests failed : 3
-/var/lib/mock/scl80fc40x/build.log:Tests failed : 3
-/var/lib/mock/scl80fc41a/build.log:Tests failed : 3
-/var/lib/mock/scl80fc41x/build.log:Tests failed : 3
+/var/lib/mock/scl80el8a/build.log:Tests failed : 0
+/var/lib/mock/scl80el8x/build.log:Tests failed : 0
+/var/lib/mock/scl80el9a/build.log:Tests failed : 0
+/var/lib/mock/scl80el9x/build.log:Tests failed : 0
+/var/lib/mock/scl80el10a/build.log:Tests failed : 0
+/var/lib/mock/scl80el10x/build.log:Tests failed : 0
+/var/lib/mock/scl80fc40a/build.log:Tests failed : 0
+/var/lib/mock/scl80fc40x/build.log:Tests failed : 0
+/var/lib/mock/scl80fc41a/build.log:Tests failed : 0
+/var/lib/mock/scl80fc41x/build.log:Tests failed : 0
+/var/lib/mock/scl80fc42a/build.log:Tests failed : 0
+/var/lib/mock/scl80fc42x/build.log:Tests failed : 0
-el8:
- 3 openssl_error_string() tests [ext/openssl/tests/openssl_error_string_basic.phpt]
- 3 openssl_open() tests [ext/openssl/tests/openssl_open_basic.phpt]
-all:
- 3 openssl_private_decrypt() tests [ext/openssl/tests/openssl_private_decrypt_basic.phpt]
-fc39, fc40, fc41, el9, el10:
- 3 openssl_error_string() tests (OpenSSL >= 3.0) [ext/openssl/tests/openssl_error_string_basic_openssl3.phpt]
-fc40, fc41, el9, el10:
- 3 openssl_x509_parse() tests [ext/openssl/tests/openssl_x509_parse_basic.phpt]
(1) proc_open give erratic test results :(
diff --git a/php-8.0.30-libxml212.patch b/php-8.0.30-libxml212.patch
deleted file mode 100644
index 2fb4cd6..0000000
--- a/php-8.0.30-libxml212.patch
+++ /dev/null
@@ -1,681 +0,0 @@
-From fe8de9dc9194de70b32afd78b89d7cc8ca577b27 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 1/4] Fix GH-12702: libxml2 2.12.0 issue building from src
-
-Fixes GH-12702.
-
-Co-authored-by: nono303 <github@nono303.net>
----
- 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 02522b5014..6d1b074085 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 d0ce7cec71..02717417a7 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.43.0
-
-From f344d24ed5d723a132f3a793eb2ee8204195067c 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 2/4] Fix libxml2 2.12 build due to API breaks
-
-See https://github.com/php/php-src/actions/runs/7062192818/job/19225478601
----
- 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 73486ae253..5700492f0a 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;
-@@ -732,7 +736,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);
-
-@@ -1035,11 +1043,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 e5e7f2f955..6060f63450 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.43.0
-
-From 8e1a378b5e4d01c99c4654114e4cf4c0953b5b5a 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 3/4] 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.
----
- .../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/xml/tests/bug26614_libxml_gte2_11.phpt | 95 +++++++++++++++++++
- ...bxml.phpt => bug26614_libxml_pre2_11.phpt} | 1 +
- 10 files changed, 178 insertions(+), 2 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/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 0000000000..ff5ceb3fbe
---- /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 6d56a317ed..0e36d20905 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 0000000000..32b6bf1611
---- /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 f450cf1654..b97fff9d2f 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 5700492f0a..554fcc34ff 100644
---- a/ext/libxml/libxml.c
-+++ b/ext/libxml/libxml.c
-@@ -529,6 +529,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 ed6576aa75..b935261cb2 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 caa7356ad3..d90f909ac2 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 87894bcb91..ddaf9bfa50 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/xml/tests/bug26614_libxml_gte2_11.phpt b/ext/xml/tests/bug26614_libxml_gte2_11.phpt
-new file mode 100644
-index 0000000000..9a81b67686
---- /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 b6c0b87581..90283850d2 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.43.0
-
-From 09f3b08ff356e9408dce778567e7592af6b970f2 Mon Sep 17 00:00:00 2001
-From: Niels Dossche <7771979+nielsdos@users.noreply.github.com>
-Date: Wed, 29 Nov 2023 20:49:29 +0100
-Subject: [PATCH 4/4] Test fixes for libxml2 2.12.0
-
----
- ext/dom/tests/DOMDocument_loadXML_error1.phpt | 4 +++
- .../DOMDocument_loadXML_error1_gte2_12.phpt | 26 ++++++++++++++++
- .../DOMDocument_loadXML_error2_gte2_11.phpt | 2 +-
- .../DOMDocument_loadXML_error2_gte2_12.phpt | 30 +++++++++++++++++++
- ext/dom/tests/DOMDocument_load_error1.phpt | 4 +++
- .../DOMDocument_load_error1_gte2_12.phpt | 26 ++++++++++++++++
- .../DOMDocument_load_error2_gte2_11.phpt | 2 +-
- .../DOMDocument_load_error2_gte2_12.phpt | 30 +++++++++++++++++++
- ext/xml/tests/bug81351.phpt | 4 +--
- ext/xml/tests/xml_error_string_basic.phpt | 6 ++--
- 10 files changed, 127 insertions(+), 7 deletions(-)
- create mode 100644 ext/dom/tests/DOMDocument_loadXML_error1_gte2_12.phpt
- create mode 100644 ext/dom/tests/DOMDocument_loadXML_error2_gte2_12.phpt
- create mode 100644 ext/dom/tests/DOMDocument_load_error1_gte2_12.phpt
- create mode 100644 ext/dom/tests/DOMDocument_load_error2_gte2_12.phpt
-
-diff --git a/ext/dom/tests/DOMDocument_loadXML_error1.phpt b/ext/dom/tests/DOMDocument_loadXML_error1.phpt
-index 4d3b81db79..0549d67630 100644
---- a/ext/dom/tests/DOMDocument_loadXML_error1.phpt
-+++ b/ext/dom/tests/DOMDocument_loadXML_error1.phpt
-@@ -1,5 +1,9 @@
- --TEST--
- Test DOMDocument::loadXML() detects not-well formed XML
-+--SKIPIF--
-+<?php
-+if (LIBXML_VERSION >= 21200) die('skip libxml2 test variant for version < 2.12');
-+?>
- --DESCRIPTION--
- This test verifies the method detects an opening and ending tag mismatch
- Environment variables used in the test:
-diff --git a/ext/dom/tests/DOMDocument_loadXML_error1_gte2_12.phpt b/ext/dom/tests/DOMDocument_loadXML_error1_gte2_12.phpt
-new file mode 100644
-index 0000000000..e1ded0ffad
---- /dev/null
-+++ b/ext/dom/tests/DOMDocument_loadXML_error1_gte2_12.phpt
-@@ -0,0 +1,26 @@
-+--TEST--
-+Test DOMDocument::loadXML() detects not-well formed XML
-+--SKIPIF--
-+<?php
-+if (LIBXML_VERSION < 21200) die('skip libxml2 test variant for version >= 2.12');
-+?>
-+--DESCRIPTION--
-+This test verifies the method detects an opening and ending tag mismatch
-+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>
-+--EXTENSIONS--
-+dom
-+--ENV--
-+XML_FILE=/not_well_formed.xml
-+LOAD_OPTIONS=0
-+EXPECTED_RESULT=0
-+--FILE_EXTERNAL--
-+domdocumentloadxml_test_method.inc
-+--EXPECTF--
-+Warning: DOMDocument::load%r(XML){0,1}%r(): Opening and ending tag mismatch: title line 5 and book %s
-+
-+Warning: DOMDocument::load%r(XML){0,1}%r(): %rexpected '>'|Opening and ending tag mismatch: book line (4|5) and books%r %s
-diff --git a/ext/dom/tests/DOMDocument_loadXML_error2_gte2_11.phpt b/ext/dom/tests/DOMDocument_loadXML_error2_gte2_11.phpt
-index ff5ceb3fbe..f52d334813 100644
---- a/ext/dom/tests/DOMDocument_loadXML_error2_gte2_11.phpt
-+++ b/ext/dom/tests/DOMDocument_loadXML_error2_gte2_11.phpt
-@@ -2,7 +2,7 @@
- Test DOMDocument::loadXML() detects not-well formed XML
- --SKIPIF--
- <?php
--if (LIBXML_VERSION < 21100) die('skip libxml2 test variant for version >= 2.11');
-+if (LIBXML_VERSION < 21100 || LIBXML_VERSION >= 21200) die('skip libxml2 test variant for version >= 2.11 && <= 2.12');
- ?>
- --DESCRIPTION--
- This test verifies the method detects attributes values not closed between " or '
-diff --git a/ext/dom/tests/DOMDocument_loadXML_error2_gte2_12.phpt b/ext/dom/tests/DOMDocument_loadXML_error2_gte2_12.phpt
-new file mode 100644
-index 0000000000..6a3ff5841f
---- /dev/null
-+++ b/ext/dom/tests/DOMDocument_loadXML_error2_gte2_12.phpt
-@@ -0,0 +1,30 @@
-+--TEST--
-+Test DOMDocument::loadXML() detects not-well formed XML
-+--SKIPIF--
-+<?php
-+if (LIBXML_VERSION < 21200) die('skip libxml2 test variant for version >= 2.12');
-+?>
-+--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>
-+--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
-diff --git a/ext/dom/tests/DOMDocument_load_error1.phpt b/ext/dom/tests/DOMDocument_load_error1.phpt
-index 8ac181d769..4416f5f6fe 100644
---- a/ext/dom/tests/DOMDocument_load_error1.phpt
-+++ b/ext/dom/tests/DOMDocument_load_error1.phpt
-@@ -1,5 +1,9 @@
- --TEST--
- Test DOMDocument::load() detects not-well formed XML
-+--SKIPIF--
-+<?php
-+if (LIBXML_VERSION >= 21200) die('skip libxml2 test variant for version < 2.12');
-+?>
- --DESCRIPTION--
- This test verifies the method detects an opening and ending tag mismatch
- Environment variables used in the test:
-diff --git a/ext/dom/tests/DOMDocument_load_error1_gte2_12.phpt b/ext/dom/tests/DOMDocument_load_error1_gte2_12.phpt
-new file mode 100644
-index 0000000000..183c8406fd
---- /dev/null
-+++ b/ext/dom/tests/DOMDocument_load_error1_gte2_12.phpt
-@@ -0,0 +1,26 @@
-+--TEST--
-+Test DOMDocument::load() detects not-well formed XML
-+--SKIPIF--
-+<?php
-+if (LIBXML_VERSION < 21200) die('skip libxml2 test variant for version >= 2.12');
-+?>
-+--DESCRIPTION--
-+This test verifies the method detects an opening and ending tag mismatch
-+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>
-+--EXTENSIONS--
-+dom
-+--ENV--
-+XML_FILE=/not_well_formed.xml
-+LOAD_OPTIONS=0
-+EXPECTED_RESULT=0
-+--FILE_EXTERNAL--
-+domdocumentload_test_method.inc
-+--EXPECTF--
-+Warning: DOMDocument::load%r(XML){0,1}%r(): Opening and ending tag mismatch: title line 5 and book %s
-+
-+Warning: DOMDocument::load%r(XML){0,1}%r(): %rexpected '>'|Opening and ending tag mismatch: book line (4|5) and books%r %s
-diff --git a/ext/dom/tests/DOMDocument_load_error2_gte2_11.phpt b/ext/dom/tests/DOMDocument_load_error2_gte2_11.phpt
-index 32b6bf1611..4d9f992b3b 100644
---- a/ext/dom/tests/DOMDocument_load_error2_gte2_11.phpt
-+++ b/ext/dom/tests/DOMDocument_load_error2_gte2_11.phpt
-@@ -2,7 +2,7 @@
- Test DOMDocument::load() detects not-well formed
- --SKIPIF--
- <?php
--if (LIBXML_VERSION < 21100) die('skip libxml2 test variant for version >= 2.11');
-+if (LIBXML_VERSION < 21100 || LIBXML_VERSION >= 21200) die('skip libxml2 test variant for version >= 2.11 && <= 2.12');
- ?>
- --DESCRIPTION--
- This test verifies the method detects attributes values not closed between " or '
-diff --git a/ext/dom/tests/DOMDocument_load_error2_gte2_12.phpt b/ext/dom/tests/DOMDocument_load_error2_gte2_12.phpt
-new file mode 100644
-index 0000000000..4fadf41736
---- /dev/null
-+++ b/ext/dom/tests/DOMDocument_load_error2_gte2_12.phpt
-@@ -0,0 +1,30 @@
-+--TEST--
-+Test DOMDocument::load() detects not-well formed
-+--SKIPIF--
-+<?php
-+if (LIBXML_VERSION < 21200) die('skip libxml2 test variant for version >= 2.12');
-+?>
-+--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>
-+--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
-diff --git a/ext/xml/tests/bug81351.phpt b/ext/xml/tests/bug81351.phpt
-index 19e4ca590b..dc934001be 100644
---- a/ext/xml/tests/bug81351.phpt
-+++ b/ext/xml/tests/bug81351.phpt
-@@ -23,6 +23,6 @@ $code = xml_get_error_code($parser);
- $error = xml_error_string($code);
- echo "xml_parse returned $success, xml_get_error_code = $code, xml_error_string = $error\r\n";
- ?>
----EXPECT--
-+--EXPECTF--
- xml_parse returned 1, xml_get_error_code = 0, xml_error_string = No error
--xml_parse returned 0, xml_get_error_code = 5, xml_error_string = Invalid document end
-+%rxml_parse returned 0, xml_get_error_code = 5, xml_error_string = Invalid document end|xml_parse returned 0, xml_get_error_code = 77, xml_error_string = Tag not finished%r
-diff --git a/ext/xml/tests/xml_error_string_basic.phpt b/ext/xml/tests/xml_error_string_basic.phpt
-index e72fa68bd1..9020415f1f 100644
---- a/ext/xml/tests/xml_error_string_basic.phpt
-+++ b/ext/xml/tests/xml_error_string_basic.phpt
-@@ -25,9 +25,9 @@ foreach ($xmls as $xml) {
- xml_parser_free($xml_parser);
- }
- ?>
----EXPECT--
--int(5)
--string(20) "Invalid document end"
-+--EXPECTF--
-+int(%r5|77%r)
-+string(%d) %r"Invalid document end"|"Tag not finished"%r
- int(47)
- string(35) "Processing Instruction not finished"
- int(57)
---
-2.43.0
-
diff --git a/php-8.0.30-pcretests.patch b/php-8.0.30-pcretests.patch
new file mode 100644
index 0000000..c226661
--- /dev/null
+++ b/php-8.0.30-pcretests.patch
@@ -0,0 +1,43 @@
+From c3150fcc89825f50d476b1b1971870aeb71f167d Mon Sep 17 00:00:00 2001
+From: Remi Collet <remi@remirepo.net>
+Date: Wed, 12 Mar 2025 07:48:05 +0100
+Subject: [PATCH 1/2] Relax test expectation for pcre2lib 10.45 Using
+ e92848789acd8aa5cf32fedb519ba9378ac64e02
+
+---
+ ext/pcre/tests/bug75457.phpt | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/ext/pcre/tests/bug75457.phpt b/ext/pcre/tests/bug75457.phpt
+index ee5ab162f8a6c..87dc12a1ad056 100644
+--- a/ext/pcre/tests/bug75457.phpt
++++ b/ext/pcre/tests/bug75457.phpt
+@@ -6,5 +6,5 @@ $pattern = "/(((?(?C)0?=))(?!()0|.(?0)0)())/";
+ var_dump(preg_match($pattern, "hello"));
+ ?>
+ --EXPECTF--
+-Warning: preg_match(): Compilation failed: assertion expected after (?( or (?(?C) at offset 8 in %sbug75457.php on line %d
++Warning: preg_match(): Compilation failed: %r(atomic|)%r assertion expected after (?( or (?(?C) at offset 8 in %sbug75457.php on line %d
+ bool(false)
+
+From 126095700a02b9aa1f33764a63c93a70e8373ad8 Mon Sep 17 00:00:00 2001
+From: Remi Collet <remi@famillecollet.com>
+Date: Wed, 12 Mar 2025 09:36:33 +0100
+Subject: [PATCH 2/2] Update ext/pcre/tests/bug75457.phpt
+
+Co-authored-by: Niels Dossche <7771979+nielsdos@users.noreply.github.com>
+---
+ ext/pcre/tests/bug75457.phpt | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/ext/pcre/tests/bug75457.phpt b/ext/pcre/tests/bug75457.phpt
+index 87dc12a1ad056..1401b25ff6fb7 100644
+--- a/ext/pcre/tests/bug75457.phpt
++++ b/ext/pcre/tests/bug75457.phpt
+@@ -6,5 +6,5 @@ $pattern = "/(((?(?C)0?=))(?!()0|.(?0)0)())/";
+ var_dump(preg_match($pattern, "hello"));
+ ?>
+ --EXPECTF--
+-Warning: preg_match(): Compilation failed: %r(atomic|)%r assertion expected after (?( or (?(?C) at offset 8 in %sbug75457.php on line %d
++Warning: preg_match(): Compilation failed:%r( atomic|)%r assertion expected after (?( or (?(?C) at offset 8 in %sbug75457.php on line %d
+ bool(false)
diff --git a/php-cve-2025-1217.patch b/php-cve-2025-1217.patch
new file mode 100644
index 0000000..1778bae
--- /dev/null
+++ b/php-cve-2025-1217.patch
@@ -0,0 +1,909 @@
+From 4fec08542748c25573063ffc53ea89cd5de1edf0 Mon Sep 17 00:00:00 2001
+From: Jakub Zelenka <bukka@php.net>
+Date: Tue, 31 Dec 2024 18:57:02 +0100
+Subject: [PATCH 01/11] Fix GHSA-ghsa-v8xr-gpvj-cx9g: http header folding
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+This adds HTTP header folding support for HTTP wrapper response
+headers.
+
+Reviewed-by: Tim Düsterhus <tim@tideways-gmbh.com>
+(cherry picked from commit d20b4c97a9f883b62b65b82d939c5af9a2028ef1)
+---
+ ext/openssl/tests/ServerClientTestCase.inc | 65 +++-
+ ext/standard/http_fopen_wrapper.c | 343 ++++++++++++------
+ .../tests/http/ghsa-v8xr-gpvj-cx9g-001.phpt | 49 +++
+ .../tests/http/ghsa-v8xr-gpvj-cx9g-002.phpt | 51 +++
+ .../tests/http/ghsa-v8xr-gpvj-cx9g-003.phpt | 49 +++
+ .../tests/http/ghsa-v8xr-gpvj-cx9g-004.phpt | 48 +++
+ .../tests/http/ghsa-v8xr-gpvj-cx9g-005.phpt | 48 +++
+ .../tests/http/http_response_header_05.phpt | 30 --
+ 8 files changed, 534 insertions(+), 149 deletions(-)
+ create mode 100644 ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-001.phpt
+ create mode 100644 ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-002.phpt
+ create mode 100644 ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-003.phpt
+ create mode 100644 ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-004.phpt
+ create mode 100644 ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-005.phpt
+ delete mode 100644 ext/standard/tests/http/http_response_header_05.phpt
+
+diff --git a/ext/openssl/tests/ServerClientTestCase.inc b/ext/openssl/tests/ServerClientTestCase.inc
+index 753366df6f4..61d45385b62 100644
+--- a/ext/openssl/tests/ServerClientTestCase.inc
++++ b/ext/openssl/tests/ServerClientTestCase.inc
+@@ -4,14 +4,19 @@ const WORKER_ARGV_VALUE = 'RUN_WORKER';
+
+ const WORKER_DEFAULT_NAME = 'server';
+
+-function phpt_notify($worker = WORKER_DEFAULT_NAME)
++function phpt_notify(string $worker = WORKER_DEFAULT_NAME, string $message = ""): void
+ {
+- ServerClientTestCase::getInstance()->notify($worker);
++ ServerClientTestCase::getInstance()->notify($worker, $message);
+ }
+
+-function phpt_wait($worker = WORKER_DEFAULT_NAME, $timeout = null)
++function phpt_wait($worker = WORKER_DEFAULT_NAME, $timeout = null): ?string
+ {
+- ServerClientTestCase::getInstance()->wait($worker, $timeout);
++ return ServerClientTestCase::getInstance()->wait($worker, $timeout);
++}
++
++function phpt_notify_server_start($server): void
++{
++ ServerClientTestCase::getInstance()->notify_server_start($server);
+ }
+
+ function phpt_has_sslv3() {
+@@ -119,43 +124,73 @@ class ServerClientTestCase
+ eval($code);
+ }
+
+- public function run($masterCode, $workerCode)
++ /**
++ * 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];
+ }
+- foreach ($workerCode as $worker => $code) {
++ 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($masterCode));
++
++ eval($this->stripPhpTagsFromCode($clientCode));
+ foreach ($workerCode as $worker => $code) {
+ $this->cleanupWorkerProcess($worker);
+ }
+ }
+
+- public function wait($worker, $timeout = null)
++ public function wait($worker, $timeout = null): ?string
+ {
+ $handle = $this->isWorker ? STDIN : $this->workerStdOut[$worker];
+ if ($timeout === null) {
+- fgets($handle);
+- return true;
++ return fgets($handle);
+ }
+
+ stream_set_blocking($handle, false);
+ $read = [$handle];
+ $result = stream_select($read, $write, $except, $timeout);
+ if (!$result) {
+- return false;
++ return null;
+ }
+
+- fgets($handle);
++ $result = fgets($handle);
+ stream_set_blocking($handle, true);
+- return true;
++ return $result;
++ }
++
++ public function notify(string $worker, string $message = ""): void
++ {
++ fwrite($this->isWorker ? STDOUT : $this->workerStdIn[$worker], "$message\n");
+ }
+
+- public function notify($worker)
++ public function notify_server_start($server): void
+ {
+- fwrite($this->isWorker ? STDOUT : $this->workerStdIn[$worker], "\n");
++ echo stream_socket_get_name($server, false) . "\n";
+ }
+ }
+
+diff --git a/ext/standard/http_fopen_wrapper.c b/ext/standard/http_fopen_wrapper.c
+index 40e6f3dd4c3..bfc88a74545 100644
+--- a/ext/standard/http_fopen_wrapper.c
++++ b/ext/standard/http_fopen_wrapper.c
+@@ -114,6 +114,171 @@ static zend_bool check_has_header(const char *headers, const char *header) {
+ return 0;
+ }
+
++typedef struct _php_stream_http_response_header_info {
++ php_stream_filter *transfer_encoding;
++ size_t file_size;
++ bool follow_location;
++ char location[HTTP_HEADER_BLOCK_SIZE];
++} php_stream_http_response_header_info;
++
++static void php_stream_http_response_header_info_init(
++ php_stream_http_response_header_info *header_info)
++{
++ header_info->transfer_encoding = NULL;
++ header_info->file_size = 0;
++ header_info->follow_location = 1;
++ header_info->location[0] = '\0';
++}
++
++/* Trim white spaces from response header line and update its length */
++static bool php_stream_http_response_header_trim(char *http_header_line,
++ size_t *http_header_line_length)
++{
++ char *http_header_line_end = http_header_line + *http_header_line_length - 1;
++ while (http_header_line_end >= http_header_line &&
++ (*http_header_line_end == '\n' || *http_header_line_end == '\r')) {
++ http_header_line_end--;
++ }
++
++ /* The primary definition of an HTTP header in RFC 7230 states:
++ * > Each header field consists of a case-insensitive field name followed
++ * > by a colon (":"), optional leading whitespace, the field value, and
++ * > optional trailing whitespace. */
++
++ /* Strip trailing whitespace */
++ bool space_trim = (*http_header_line_end == ' ' || *http_header_line_end == '\t');
++ if (space_trim) {
++ do {
++ http_header_line_end--;
++ } while (http_header_line_end >= http_header_line &&
++ (*http_header_line_end == ' ' || *http_header_line_end == '\t'));
++ }
++ http_header_line_end++;
++ *http_header_line_end = '\0';
++ *http_header_line_length = http_header_line_end - http_header_line;
++
++ return space_trim;
++}
++
++/* Process folding headers of the current line and if there are none, parse last full response
++ * header line. It returns NULL if the last header is finished, otherwise it returns updated
++ * last header line. */
++static zend_string *php_stream_http_response_headers_parse(php_stream *stream,
++ php_stream_context *context, int options, zend_string *last_header_line_str,
++ char *header_line, size_t *header_line_length, int response_code,
++ zval *response_header, php_stream_http_response_header_info *header_info)
++{
++ char *last_header_line = ZSTR_VAL(last_header_line_str);
++ size_t last_header_line_length = ZSTR_LEN(last_header_line_str);
++ char *last_header_line_end = ZSTR_VAL(last_header_line_str) + ZSTR_LEN(last_header_line_str) - 1;
++
++ /* Process non empty header line. */
++ if (header_line && (*header_line != '\n' && *header_line != '\r')) {
++ /* Removing trailing white spaces. */
++ if (php_stream_http_response_header_trim(header_line, header_line_length) &&
++ *header_line_length == 0) {
++ /* Only spaces so treat as an empty folding header. */
++ return last_header_line_str;
++ }
++
++ /* Process folding headers if starting with a space or a tab. */
++ if (header_line && (*header_line == ' ' || *header_line == '\t')) {
++ char *http_folded_header_line = header_line;
++ size_t http_folded_header_line_length = *header_line_length;
++ /* Remove the leading white spaces. */
++ while (*http_folded_header_line == ' ' || *http_folded_header_line == '\t') {
++ http_folded_header_line++;
++ http_folded_header_line_length--;
++ }
++ /* It has to have some characters because it would get returned after the call
++ * php_stream_http_response_header_trim above. */
++ ZEND_ASSERT(http_folded_header_line_length > 0);
++ /* Concatenate last header line, space and current header line. */
++ zend_string *extended_header_str = zend_string_concat3(
++ last_header_line, last_header_line_length,
++ " ", 1,
++ http_folded_header_line, http_folded_header_line_length);
++ zend_string_efree(last_header_line_str);
++ last_header_line_str = extended_header_str;
++ /* Return new header line. */
++ return last_header_line_str;
++ }
++ }
++
++ /* Find header separator position. */
++ char *last_header_value = memchr(last_header_line, ':', last_header_line_length);
++ if (last_header_value) {
++ last_header_value++; /* Skip ':'. */
++
++ /* Strip leading whitespace. */
++ while (last_header_value < last_header_line_end
++ && (*last_header_value == ' ' || *last_header_value == '\t')) {
++ last_header_value++;
++ }
++ } else {
++ /* There is no colon. Set the value to the end of the header line, which is effectively
++ * an empty string. */
++ last_header_value = last_header_line_end;
++ }
++
++ bool store_header = true;
++ zval *tmpzval = NULL;
++
++ if (!strncasecmp(last_header_line, "Location:", sizeof("Location:")-1)) {
++ /* Check if the location should be followed. */
++ if (context && (tmpzval = php_stream_context_get_option(context, "http", "follow_location")) != NULL) {
++ header_info->follow_location = zval_is_true(tmpzval);
++ } else if (!((response_code >= 300 && response_code < 304)
++ || 307 == response_code || 308 == response_code)) {
++ /* The redirection should not be automatic if follow_location is not set and
++ * response_code not in (300, 301, 302, 303 and 307)
++ * see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.1
++ * RFC 7238 defines 308: http://tools.ietf.org/html/rfc7238 */
++ header_info->follow_location = 0;
++ }
++ strlcpy(header_info->location, last_header_value, sizeof(header_info->location));
++ } else if (!strncasecmp(last_header_line, "Content-Type:", sizeof("Content-Type:")-1)) {
++ php_stream_notify_info(context, PHP_STREAM_NOTIFY_MIME_TYPE_IS, last_header_value, 0);
++ } else if (!strncasecmp(last_header_line, "Content-Length:", sizeof("Content-Length:")-1)) {
++ header_info->file_size = atoi(last_header_value);
++ php_stream_notify_file_size(context, header_info->file_size, last_header_line, 0);
++ } else if (
++ !strncasecmp(last_header_line, "Transfer-Encoding:", sizeof("Transfer-Encoding:")-1)
++ && !strncasecmp(last_header_value, "Chunked", sizeof("Chunked")-1)
++ ) {
++ /* Create filter to decode response body. */
++ if (!(options & STREAM_ONLY_GET_HEADERS)) {
++ zend_long decode = 1;
++
++ if (context && (tmpzval = php_stream_context_get_option(context, "http", "auto_decode")) != NULL) {
++ decode = zend_is_true(tmpzval);
++ }
++ if (decode) {
++ if (header_info->transfer_encoding != NULL) {
++ /* Prevent a memory leak in case there are more transfer-encoding headers. */
++ php_stream_filter_free(header_info->transfer_encoding);
++ }
++ header_info->transfer_encoding = php_stream_filter_create(
++ "dechunk", NULL, php_stream_is_persistent(stream));
++ if (header_info->transfer_encoding != NULL) {
++ /* Do not store transfer-encoding header. */
++ store_header = false;
++ }
++ }
++ }
++ }
++
++ if (store_header) {
++ zval http_header;
++ ZVAL_NEW_STR(&http_header, last_header_line_str);
++ zend_hash_next_index_insert(Z_ARRVAL_P(response_header), &http_header);
++ } else {
++ zend_string_efree(last_header_line_str);
++ }
++
++ return NULL;
++}
++
+ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper,
+ const char *path, const char *mode, int options, zend_string **opened_path,
+ php_stream_context *context, int redirect_max, int flags,
+@@ -126,11 +291,12 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper,
+ zend_string *tmp = NULL;
+ char *ua_str = NULL;
+ zval *ua_zval = NULL, *tmpzval = NULL, ssl_proxy_peer_name;
+- char location[HTTP_HEADER_BLOCK_SIZE];
+ int reqok = 0;
+ char *http_header_line = NULL;
++ zend_string *last_header_line_str = NULL;
++ php_stream_http_response_header_info header_info;
+ char tmp_line[128];
+- size_t chunk_size = 0, file_size = 0;
++ size_t chunk_size = 0;
+ int eol_detect = 0;
+ char *transport_string;
+ zend_string *errstr = NULL;
+@@ -141,8 +307,6 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper,
+ char *user_headers = NULL;
+ int header_init = ((flags & HTTP_WRAPPER_HEADER_INIT) != 0);
+ int redirected = ((flags & HTTP_WRAPPER_REDIRECTED) != 0);
+- zend_bool follow_location = 1;
+- php_stream_filter *transfer_encoding = NULL;
+ int response_code;
+ smart_str req_buf = {0};
+ zend_bool custom_request_method;
+@@ -655,8 +819,6 @@ finish:
+ /* send it */
+ php_stream_write(stream, ZSTR_VAL(req_buf.s), ZSTR_LEN(req_buf.s));
+
+- location[0] = '\0';
+-
+ if (Z_ISUNDEF_P(response_header)) {
+ array_init(response_header);
+ }
+@@ -738,130 +900,101 @@ finish:
+ }
+ }
+
+- /* read past HTTP headers */
++ php_stream_http_response_header_info_init(&header_info);
+
++ /* read past HTTP headers */
+ while (!php_stream_eof(stream)) {
+ size_t http_header_line_length;
+
+ if (http_header_line != NULL) {
+ efree(http_header_line);
+ }
+- if ((http_header_line = php_stream_get_line(stream, NULL, 0, &http_header_line_length)) && *http_header_line != '\n' && *http_header_line != '\r') {
+- char *e = http_header_line + http_header_line_length - 1;
+- char *http_header_value;
+-
+- while (e >= http_header_line && (*e == '\n' || *e == '\r')) {
+- e--;
+- }
+-
+- /* The primary definition of an HTTP header in RFC 7230 states:
+- * > Each header field consists of a case-insensitive field name followed
+- * > by a colon (":"), optional leading whitespace, the field value, and
+- * > optional trailing whitespace. */
+-
+- /* Strip trailing whitespace */
+- while (e >= http_header_line && (*e == ' ' || *e == '\t')) {
+- e--;
+- }
+-
+- /* Terminate header line */
+- e++;
+- *e = '\0';
+- http_header_line_length = e - http_header_line;
+-
+- http_header_value = memchr(http_header_line, ':', http_header_line_length);
+- if (http_header_value) {
+- http_header_value++; /* Skip ':' */
+-
+- /* Strip leading whitespace */
+- while (http_header_value < e
+- && (*http_header_value == ' ' || *http_header_value == '\t')) {
+- http_header_value++;
++ if ((http_header_line = php_stream_get_line(stream, NULL, 0, &http_header_line_length))) {
++ bool last_line;
++ if (*http_header_line == '\r') {
++ if (http_header_line[1] != '\n') {
++ php_stream_close(stream);
++ stream = NULL;
++ php_stream_wrapper_log_error(wrapper, options,
++ "HTTP invalid header name (cannot start with CR character)!");
++ goto out;
+ }
++ last_line = true;
++ } else if (*http_header_line == '\n') {
++ last_line = true;
+ } else {
+- /* There is no colon. Set the value to the end of the header line, which is
+- * effectively an empty string. */
+- http_header_value = e;
++ last_line = false;
+ }
+-
+- if (!strncasecmp(http_header_line, "Location:", sizeof("Location:")-1)) {
+- if (context && (tmpzval = php_stream_context_get_option(context, "http", "follow_location")) != NULL) {
+- follow_location = zval_is_true(tmpzval);
+- } else if (!((response_code >= 300 && response_code < 304)
+- || 307 == response_code || 308 == response_code)) {
+- /* we shouldn't redirect automatically
+- if follow_location isn't set and response_code not in (300, 301, 302, 303 and 307)
+- see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.1
+- RFC 7238 defines 308: http://tools.ietf.org/html/rfc7238 */
+- follow_location = 0;
++
++ if (last_header_line_str != NULL) {
++ /* Parse last header line. */
++ last_header_line_str = php_stream_http_response_headers_parse(stream, context,
++ options, last_header_line_str, http_header_line, &http_header_line_length,
++ response_code, response_header, &header_info);
++ if (last_header_line_str != NULL) {
++ /* Folding header present so continue. */
++ continue;
+ }
+- strlcpy(location, http_header_value, sizeof(location));
+- } else if (!strncasecmp(http_header_line, "Content-Type:", sizeof("Content-Type:")-1)) {
+- php_stream_notify_info(context, PHP_STREAM_NOTIFY_MIME_TYPE_IS, http_header_value, 0);
+- } else if (!strncasecmp(http_header_line, "Content-Length:", sizeof("Content-Length:")-1)) {
+- file_size = atoi(http_header_value);
+- php_stream_notify_file_size(context, file_size, http_header_line, 0);
+- } else if (
+- !strncasecmp(http_header_line, "Transfer-Encoding:", sizeof("Transfer-Encoding:")-1)
+- && !strncasecmp(http_header_value, "Chunked", sizeof("Chunked")-1)
+- ) {
+-
+- /* create filter to decode response body */
+- if (!(options & STREAM_ONLY_GET_HEADERS)) {
+- zend_long decode = 1;
+-
+- if (context && (tmpzval = php_stream_context_get_option(context, "http", "auto_decode")) != NULL) {
+- decode = zend_is_true(tmpzval);
+- }
+- if (decode) {
+- transfer_encoding = php_stream_filter_create("dechunk", NULL, php_stream_is_persistent(stream));
+- if (transfer_encoding) {
+- /* don't store transfer-encodeing header */
+- continue;
+- }
+- }
++ } else if (!last_line) {
++ /* The first line cannot start with spaces. */
++ if (*http_header_line == ' ' || *http_header_line == '\t') {
++ php_stream_close(stream);
++ stream = NULL;
++ php_stream_wrapper_log_error(wrapper, options,
++ "HTTP invalid response format (folding header at the start)!");
++ goto out;
+ }
++ /* Trim the first line if it is not the last line. */
++ php_stream_http_response_header_trim(http_header_line, &http_header_line_length);
+ }
+-
+- {
+- zval http_header;
+- ZVAL_STRINGL(&http_header, http_header_line, http_header_line_length);
+- zend_hash_next_index_insert(Z_ARRVAL_P(response_header), &http_header);
++ if (last_line) {
++ /* For the last line the last header line must be NULL. */
++ ZEND_ASSERT(last_header_line_str == NULL);
++ break;
+ }
++ /* Save current line as the last line so it gets parsed in the next round. */
++ last_header_line_str = zend_string_init(http_header_line, http_header_line_length, 0);
+ } else {
+ break;
+ }
+ }
+
+- if (!reqok || (location[0] != '\0' && follow_location)) {
+- if (!follow_location || (((options & STREAM_ONLY_GET_HEADERS) || ignore_errors) && redirect_max <= 1)) {
++ /* If the stream was closed early, we still want to process the last line to keep BC. */
++ if (last_header_line_str != NULL) {
++ php_stream_http_response_headers_parse(stream, context, options, last_header_line_str,
++ NULL, NULL, response_code, response_header, &header_info);
++ }
++
++ if (!reqok || (header_info.location[0] != '\0' && header_info.follow_location)) {
++ if (!header_info.follow_location || (((options & STREAM_ONLY_GET_HEADERS) || ignore_errors) && redirect_max <= 1)) {
+ goto out;
+ }
+
+- if (location[0] != '\0')
+- php_stream_notify_info(context, PHP_STREAM_NOTIFY_REDIRECTED, location, 0);
++ if (header_info.location[0] != '\0')
++ php_stream_notify_info(context, PHP_STREAM_NOTIFY_REDIRECTED, header_info.location, 0);
+
+ php_stream_close(stream);
+ stream = NULL;
+
+- if (transfer_encoding) {
+- php_stream_filter_free(transfer_encoding);
+- transfer_encoding = NULL;
++ if (header_info.transfer_encoding) {
++ php_stream_filter_free(header_info.transfer_encoding);
++ header_info.transfer_encoding = NULL;
+ }
+
+- if (location[0] != '\0') {
++ if (header_info.location[0] != '\0') {
+
+ char new_path[HTTP_HEADER_BLOCK_SIZE];
+ char loc_path[HTTP_HEADER_BLOCK_SIZE];
+
+ *new_path='\0';
+- if (strlen(location)<8 || (strncasecmp(location, "http://", sizeof("http://")-1) &&
+- strncasecmp(location, "https://", sizeof("https://")-1) &&
+- strncasecmp(location, "ftp://", sizeof("ftp://")-1) &&
+- strncasecmp(location, "ftps://", sizeof("ftps://")-1)))
++ if (strlen(header_info.location) < 8 ||
++ (strncasecmp(header_info.location, "http://", sizeof("http://")-1) &&
++ strncasecmp(header_info.location, "https://", sizeof("https://")-1) &&
++ strncasecmp(header_info.location, "ftp://", sizeof("ftp://")-1) &&
++ strncasecmp(header_info.location, "ftps://", sizeof("ftps://")-1)))
+ {
+- if (*location != '/') {
+- if (*(location+1) != '\0' && resource->path) {
++ if (*header_info.location != '/') {
++ if (*(header_info.location+1) != '\0' && resource->path) {
+ char *s = strrchr(ZSTR_VAL(resource->path), '/');
+ if (!s) {
+ s = ZSTR_VAL(resource->path);
+@@ -877,15 +1010,17 @@ finish:
+ if (resource->path &&
+ ZSTR_VAL(resource->path)[0] == '/' &&
+ ZSTR_VAL(resource->path)[1] == '\0') {
+- snprintf(loc_path, sizeof(loc_path) - 1, "%s%s", ZSTR_VAL(resource->path), location);
++ snprintf(loc_path, sizeof(loc_path) - 1, "%s%s",
++ ZSTR_VAL(resource->path), header_info.location);
+ } else {
+- snprintf(loc_path, sizeof(loc_path) - 1, "%s/%s", ZSTR_VAL(resource->path), location);
++ snprintf(loc_path, sizeof(loc_path) - 1, "%s/%s",
++ ZSTR_VAL(resource->path), header_info.location);
+ }
+ } else {
+- snprintf(loc_path, sizeof(loc_path) - 1, "/%s", location);
++ snprintf(loc_path, sizeof(loc_path) - 1, "/%s", header_info.location);
+ }
+ } else {
+- strlcpy(loc_path, location, sizeof(loc_path));
++ strlcpy(loc_path, header_info.location, sizeof(loc_path));
+ }
+ if ((use_ssl && resource->port != 443) || (!use_ssl && resource->port != 80)) {
+ snprintf(new_path, sizeof(new_path) - 1, "%s://%s:%d%s", ZSTR_VAL(resource->scheme), ZSTR_VAL(resource->host), resource->port, loc_path);
+@@ -893,7 +1028,7 @@ finish:
+ snprintf(new_path, sizeof(new_path) - 1, "%s://%s%s", ZSTR_VAL(resource->scheme), ZSTR_VAL(resource->host), loc_path);
+ }
+ } else {
+- strlcpy(new_path, location, sizeof(new_path));
++ strlcpy(new_path, header_info.location, sizeof(new_path));
+ }
+
+ php_url_free(resource);
+@@ -946,7 +1081,7 @@ out:
+ if (header_init) {
+ ZVAL_COPY(&stream->wrapperdata, response_header);
+ }
+- php_stream_notify_progress_init(context, 0, file_size);
++ php_stream_notify_progress_init(context, 0, header_info.file_size);
+
+ /* Restore original chunk size now that we're done with headers */
+ if (options & STREAM_WILL_CAST)
+@@ -962,8 +1097,8 @@ out:
+ /* restore mode */
+ strlcpy(stream->mode, mode, sizeof(stream->mode));
+
+- if (transfer_encoding) {
+- php_stream_filter_append(&stream->readfilters, transfer_encoding);
++ if (header_info.transfer_encoding) {
++ php_stream_filter_append(&stream->readfilters, header_info.transfer_encoding);
+ }
+ }
+
+diff --git a/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-001.phpt b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-001.phpt
+new file mode 100644
+index 00000000000..f935b5a02ca
+--- /dev/null
++++ b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-001.phpt
+@@ -0,0 +1,49 @@
++--TEST--
++GHSA-v8xr-gpvj-cx9g: Header parser of http stream wrapper does not handle folded headers (single)
++--FILE--
++<?php
++$serverCode = <<<'CODE'
++ $ctxt = stream_context_create([
++ "socket" => [
++ "tcp_nodelay" => true
++ ]
++ ]);
++
++ $server = stream_socket_server(
++ "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt);
++ phpt_notify_server_start($server);
++
++ $conn = stream_socket_accept($server);
++
++ phpt_notify(message:"server-accepted");
++
++ fwrite($conn, "HTTP/1.0 200 Ok\r\nContent-Type: text/html;\r\n charset=utf-8\r\n\r\nbody\r\n");
++CODE;
++
++$clientCode = <<<'CODE'
++ function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) {
++ switch($notification_code) {
++ case STREAM_NOTIFY_MIME_TYPE_IS:
++ echo "Found the mime-type: ", $message, PHP_EOL;
++ break;
++ }
++ }
++
++ $ctx = stream_context_create();
++ stream_context_set_params($ctx, array("notification" => "stream_notification_callback"));
++ var_dump(trim(file_get_contents("http://{{ ADDR }}", false, $ctx)));
++ var_dump($http_response_header);
++CODE;
++
++include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
++ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
++?>
++--EXPECTF--
++Found the mime-type: text/html; charset=utf-8
++string(4) "body"
++array(2) {
++ [0]=>
++ string(15) "HTTP/1.0 200 Ok"
++ [1]=>
++ string(38) "Content-Type: text/html; charset=utf-8"
++}
+diff --git a/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-002.phpt b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-002.phpt
+new file mode 100644
+index 00000000000..078d605b671
+--- /dev/null
++++ b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-002.phpt
+@@ -0,0 +1,51 @@
++--TEST--
++GHSA-v8xr-gpvj-cx9g: Header parser of http stream wrapper does not handle folded headers (multiple)
++--FILE--
++<?php
++$serverCode = <<<'CODE'
++ $ctxt = stream_context_create([
++ "socket" => [
++ "tcp_nodelay" => true
++ ]
++ ]);
++
++ $server = stream_socket_server(
++ "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt);
++ phpt_notify_server_start($server);
++
++ $conn = stream_socket_accept($server);
++
++ phpt_notify(message:"server-accepted");
++
++ fwrite($conn, "HTTP/1.0 200 Ok\r\nContent-Type: text/html;\r\nCustom-Header: somevalue;\r\n param1=value1; \r\n param2=value2\r\n\r\nbody\r\n");
++CODE;
++
++$clientCode = <<<'CODE'
++ function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) {
++ switch($notification_code) {
++ case STREAM_NOTIFY_MIME_TYPE_IS:
++ echo "Found the mime-type: ", $message, PHP_EOL;
++ break;
++ }
++ }
++
++ $ctx = stream_context_create();
++ stream_context_set_params($ctx, array("notification" => "stream_notification_callback"));
++ var_dump(trim(file_get_contents("http://{{ ADDR }}", false, $ctx)));
++ var_dump($http_response_header);
++CODE;
++
++include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
++ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
++?>
++--EXPECTF--
++Found the mime-type: text/html;
++string(4) "body"
++array(3) {
++ [0]=>
++ string(15) "HTTP/1.0 200 Ok"
++ [1]=>
++ string(24) "Content-Type: text/html;"
++ [2]=>
++ string(54) "Custom-Header: somevalue; param1=value1; param2=value2"
++}
+diff --git a/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-003.phpt b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-003.phpt
+new file mode 100644
+index 00000000000..ad5ddc879ce
+--- /dev/null
++++ b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-003.phpt
+@@ -0,0 +1,49 @@
++--TEST--
++GHSA-v8xr-gpvj-cx9g: Header parser of http stream wrapper does not handle folded headers (empty)
++--FILE--
++<?php
++$serverCode = <<<'CODE'
++ $ctxt = stream_context_create([
++ "socket" => [
++ "tcp_nodelay" => true
++ ]
++ ]);
++
++ $server = stream_socket_server(
++ "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt);
++ phpt_notify_server_start($server);
++
++ $conn = stream_socket_accept($server);
++
++ phpt_notify(message:"server-accepted");
++
++ fwrite($conn, "HTTP/1.0 200 Ok\r\nContent-Type: text/html;\r\n \r\n charset=utf-8\r\n\r\nbody\r\n");
++CODE;
++
++$clientCode = <<<'CODE'
++ function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) {
++ switch($notification_code) {
++ case STREAM_NOTIFY_MIME_TYPE_IS:
++ echo "Found the mime-type: ", $message, PHP_EOL;
++ break;
++ }
++ }
++
++ $ctx = stream_context_create();
++ stream_context_set_params($ctx, array("notification" => "stream_notification_callback"));
++ var_dump(trim(file_get_contents("http://{{ ADDR }}", false, $ctx)));
++ var_dump($http_response_header);
++CODE;
++
++include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
++ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
++?>
++--EXPECTF--
++Found the mime-type: text/html; charset=utf-8
++string(4) "body"
++array(2) {
++ [0]=>
++ string(15) "HTTP/1.0 200 Ok"
++ [1]=>
++ string(38) "Content-Type: text/html; charset=utf-8"
++}
+diff --git a/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-004.phpt b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-004.phpt
+new file mode 100644
+index 00000000000..d0396e819fb
+--- /dev/null
++++ b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-004.phpt
+@@ -0,0 +1,48 @@
++--TEST--
++GHSA-v8xr-gpvj-cx9g: Header parser of http stream wrapper does not handle folded headers (first line)
++--FILE--
++<?php
++$serverCode = <<<'CODE'
++ $ctxt = stream_context_create([
++ "socket" => [
++ "tcp_nodelay" => true
++ ]
++ ]);
++
++ $server = stream_socket_server(
++ "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt);
++ phpt_notify_server_start($server);
++
++ $conn = stream_socket_accept($server);
++
++ phpt_notify(message:"server-accepted");
++
++ fwrite($conn, "HTTP/1.0 200 Ok\r\n Content-Type: text/html;\r\n \r\n charset=utf-8\r\n\r\nbody\r\n");
++CODE;
++
++$clientCode = <<<'CODE'
++ function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) {
++ switch($notification_code) {
++ case STREAM_NOTIFY_MIME_TYPE_IS:
++ echo "Found the mime-type: ", $message, PHP_EOL;
++ break;
++ }
++ }
++
++ $ctx = stream_context_create();
++ stream_context_set_params($ctx, array("notification" => "stream_notification_callback"));
++ var_dump(file_get_contents("http://{{ ADDR }}", false, $ctx));
++ var_dump($http_response_header);
++CODE;
++
++include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
++ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
++?>
++--EXPECTF--
++
++Warning: file_get_contents(http://127.0.0.1:%d): Failed to open stream: HTTP invalid response format (folding header at the start)! in %s
++bool(false)
++array(1) {
++ [0]=>
++ string(15) "HTTP/1.0 200 Ok"
++}
+diff --git a/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-005.phpt b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-005.phpt
+new file mode 100644
+index 00000000000..037d2002cc5
+--- /dev/null
++++ b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-005.phpt
+@@ -0,0 +1,48 @@
++--TEST--
++GHSA-v8xr-gpvj-cx9g: Header parser of http stream wrapper does not handle folded headers (CR before header name)
++--FILE--
++<?php
++$serverCode = <<<'CODE'
++ $ctxt = stream_context_create([
++ "socket" => [
++ "tcp_nodelay" => true
++ ]
++ ]);
++
++ $server = stream_socket_server(
++ "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt);
++ phpt_notify_server_start($server);
++
++ $conn = stream_socket_accept($server);
++
++ phpt_notify(message:"server-accepted");
++
++ fwrite($conn, "HTTP/1.0 200 Ok\r\n\rIgnored: ignored\r\n\r\nbody\r\n");
++CODE;
++
++$clientCode = <<<'CODE'
++ function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) {
++ switch($notification_code) {
++ case STREAM_NOTIFY_MIME_TYPE_IS:
++ echo "Found the mime-type: ", $message, PHP_EOL;
++ break;
++ }
++ }
++
++ $ctx = stream_context_create();
++ stream_context_set_params($ctx, array("notification" => "stream_notification_callback"));
++ var_dump(file_get_contents("http://{{ ADDR }}", false, $ctx));
++ var_dump($http_response_header);
++CODE;
++
++include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
++ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
++?>
++--EXPECTF--
++
++Warning: file_get_contents(http://127.0.0.1:%d): Failed to open stream: HTTP invalid header name (cannot start with CR character)! in %s
++bool(false)
++array(1) {
++ [0]=>
++ string(15) "HTTP/1.0 200 Ok"
++}
+diff --git a/ext/standard/tests/http/http_response_header_05.phpt b/ext/standard/tests/http/http_response_header_05.phpt
+deleted file mode 100644
+index c5fe60fa612..00000000000
+--- a/ext/standard/tests/http/http_response_header_05.phpt
++++ /dev/null
+@@ -1,30 +0,0 @@
+---TEST--
+-$http_reponse_header (whitespace-only "header")
+---SKIPIF--
+-<?php require 'server.inc'; http_server_skipif(); ?>
+---INI--
+-allow_url_fopen=1
+---FILE--
+-<?php
+-require 'server.inc';
+-
+-$responses = array(
+- "data://text/plain,HTTP/1.0 200 Ok\r\n \r\n\r\nBody",
+-);
+-
+-['pid' => $pid, 'uri' => $uri] = http_server($responses, $output);
+-
+-$f = file_get_contents($uri);
+-var_dump($f);
+-var_dump($http_response_header);
+-
+-http_server_kill($pid);
+-
+---EXPECT--
+-string(4) "Body"
+-array(2) {
+- [0]=>
+- string(15) "HTTP/1.0 200 Ok"
+- [1]=>
+- string(0) ""
+-}
+--
+2.48.1
+
diff --git a/php-cve-2025-1219.patch b/php-cve-2025-1219.patch
new file mode 100644
index 0000000..4f1cd52
--- /dev/null
+++ b/php-cve-2025-1219.patch
@@ -0,0 +1,1779 @@
+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
+
+From adae2b8de8963ac6f92103803bf91a5174172f88 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
+
+---
+ NEWS | 17 +++++++++++++++++
+ 1 file changed, 17 insertions(+)
+
+diff --git a/NEWS b/NEWS
+index e043901ee96..7db6f2660d2 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
+
diff --git a/php-cve-2025-1734.patch b/php-cve-2025-1734.patch
new file mode 100644
index 0000000..b7705f2
--- /dev/null
+++ b/php-cve-2025-1734.patch
@@ -0,0 +1,300 @@
+From e81d0cd14bfeb17e899c73e3aece4991bbda76af Mon Sep 17 00:00:00 2001
+From: Jakub Zelenka <bukka@php.net>
+Date: Sun, 19 Jan 2025 17:49:53 +0100
+Subject: [PATCH 02/11] Fix GHSA-pcmh-g36c-qc44: http headers without colon
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+The header line must contain colon otherwise it is invalid and it needs
+to fail.
+
+Reviewed-by: Tim Düsterhus <tim@tideways-gmbh.com>
+(cherry picked from commit 0548c4c1756724a89ef8310709419b08aadb2b3b)
+---
+ ext/standard/http_fopen_wrapper.c | 51 ++++++++++++++-----
+ ext/standard/tests/http/bug47021.phpt | 22 ++++----
+ ext/standard/tests/http/bug75535.phpt | 4 +-
+ .../tests/http/ghsa-pcmh-g36c-qc44-001.phpt | 51 +++++++++++++++++++
+ .../tests/http/ghsa-pcmh-g36c-qc44-002.phpt | 51 +++++++++++++++++++
+ 5 files changed, 154 insertions(+), 25 deletions(-)
+ create mode 100644 ext/standard/tests/http/ghsa-pcmh-g36c-qc44-001.phpt
+ create mode 100644 ext/standard/tests/http/ghsa-pcmh-g36c-qc44-002.phpt
+
+diff --git a/ext/standard/http_fopen_wrapper.c b/ext/standard/http_fopen_wrapper.c
+index bfc88a74545..7ee22b85f88 100644
+--- a/ext/standard/http_fopen_wrapper.c
++++ b/ext/standard/http_fopen_wrapper.c
+@@ -117,6 +117,7 @@ static zend_bool check_has_header(const char *headers, const char *header) {
+ typedef struct _php_stream_http_response_header_info {
+ php_stream_filter *transfer_encoding;
+ size_t file_size;
++ bool error;
+ bool follow_location;
+ char location[HTTP_HEADER_BLOCK_SIZE];
+ } php_stream_http_response_header_info;
+@@ -126,6 +127,7 @@ static void php_stream_http_response_header_info_init(
+ {
+ header_info->transfer_encoding = NULL;
+ header_info->file_size = 0;
++ header_info->error = false;
+ header_info->follow_location = 1;
+ header_info->location[0] = '\0';
+ }
+@@ -163,10 +165,11 @@ static bool php_stream_http_response_header_trim(char *http_header_line,
+ /* Process folding headers of the current line and if there are none, parse last full response
+ * header line. It returns NULL if the last header is finished, otherwise it returns updated
+ * last header line. */
+-static zend_string *php_stream_http_response_headers_parse(php_stream *stream,
+- php_stream_context *context, int options, zend_string *last_header_line_str,
+- char *header_line, size_t *header_line_length, int response_code,
+- zval *response_header, php_stream_http_response_header_info *header_info)
++static zend_string *php_stream_http_response_headers_parse(php_stream_wrapper *wrapper,
++ php_stream *stream, php_stream_context *context, int options,
++ zend_string *last_header_line_str, char *header_line, size_t *header_line_length,
++ int response_code, zval *response_header,
++ php_stream_http_response_header_info *header_info)
+ {
+ char *last_header_line = ZSTR_VAL(last_header_line_str);
+ size_t last_header_line_length = ZSTR_LEN(last_header_line_str);
+@@ -208,6 +211,19 @@ static zend_string *php_stream_http_response_headers_parse(php_stream *stream,
+ /* Find header separator position. */
+ char *last_header_value = memchr(last_header_line, ':', last_header_line_length);
+ if (last_header_value) {
++ /* Verify there is no space in header name */
++ char *last_header_name = last_header_line + 1;
++ while (last_header_name < last_header_value) {
++ if (*last_header_name == ' ' || *last_header_name == '\t') {
++ header_info->error = true;
++ php_stream_wrapper_log_error(wrapper, options,
++ "HTTP invalid response format (space in header name)!");
++ zend_string_efree(last_header_line_str);
++ return NULL;
++ }
++ ++last_header_name;
++ }
++
+ last_header_value++; /* Skip ':'. */
+
+ /* Strip leading whitespace. */
+@@ -216,9 +232,12 @@ static zend_string *php_stream_http_response_headers_parse(php_stream *stream,
+ last_header_value++;
+ }
+ } else {
+- /* There is no colon. Set the value to the end of the header line, which is effectively
+- * an empty string. */
+- last_header_value = last_header_line_end;
++ /* There is no colon which means invalid response so error. */
++ header_info->error = true;
++ php_stream_wrapper_log_error(wrapper, options,
++ "HTTP invalid response format (no colon in header line)!");
++ zend_string_efree(last_header_line_str);
++ return NULL;
+ }
+
+ bool store_header = true;
+@@ -928,10 +947,16 @@ finish:
+
+ if (last_header_line_str != NULL) {
+ /* Parse last header line. */
+- last_header_line_str = php_stream_http_response_headers_parse(stream, context,
+- options, last_header_line_str, http_header_line, &http_header_line_length,
+- response_code, response_header, &header_info);
+- if (last_header_line_str != NULL) {
++ last_header_line_str = php_stream_http_response_headers_parse(wrapper, stream,
++ context, options, last_header_line_str, http_header_line,
++ &http_header_line_length, response_code, response_header, &header_info);
++ if (EXPECTED(last_header_line_str == NULL)) {
++ if (UNEXPECTED(header_info.error)) {
++ php_stream_close(stream);
++ stream = NULL;
++ goto out;
++ }
++ } else {
+ /* Folding header present so continue. */
+ continue;
+ }
+@@ -961,8 +986,8 @@ finish:
+
+ /* If the stream was closed early, we still want to process the last line to keep BC. */
+ if (last_header_line_str != NULL) {
+- php_stream_http_response_headers_parse(stream, context, options, last_header_line_str,
+- NULL, NULL, response_code, response_header, &header_info);
++ php_stream_http_response_headers_parse(wrapper, stream, context, options,
++ last_header_line_str, NULL, NULL, response_code, response_header, &header_info);
+ }
+
+ if (!reqok || (header_info.location[0] != '\0' && header_info.follow_location)) {
+diff --git a/ext/standard/tests/http/bug47021.phpt b/ext/standard/tests/http/bug47021.phpt
+index 326eceb687a..168721f4ec1 100644
+--- a/ext/standard/tests/http/bug47021.phpt
++++ b/ext/standard/tests/http/bug47021.phpt
+@@ -70,23 +70,27 @@ do_test(1, true);
+ echo "\n";
+
+ ?>
+---EXPECT--
++--EXPECTF--
++
+ Type='text/plain'
+ Hello
+-Size=5
+-World
++
++Warning: file_get_contents(http://%s:%d): Failed to open stream: HTTP invalid response format (no colon in header line)! in %s
++
+
+ Type='text/plain'
+ Hello
+-Size=5
+-World
++
++Warning: file_get_contents(http://%s:%d): Failed to open stream: HTTP invalid response format (no colon in header line)! in %s
++
+
+ Type='text/plain'
+ Hello
+-Size=5
+-World
++
++Warning: file_get_contents(http://%s:%d): Failed to open stream: HTTP invalid response format (no colon in header line)! in %s
++
+
+ Type='text/plain'
+ Hello
+-Size=5
+-World
++
++Warning: file_get_contents(http://%s:%d): Failed to open stream: HTTP invalid response format (no colon in header line)! in %s
+diff --git a/ext/standard/tests/http/bug75535.phpt b/ext/standard/tests/http/bug75535.phpt
+index 7b015890d2f..94348d1a027 100644
+--- a/ext/standard/tests/http/bug75535.phpt
++++ b/ext/standard/tests/http/bug75535.phpt
+@@ -21,9 +21,7 @@ http_server_kill($pid);
+
+ --EXPECT--
+ string(0) ""
+-array(2) {
++array(1) {
+ [0]=>
+ string(15) "HTTP/1.0 200 Ok"
+- [1]=>
+- string(14) "Content-Length"
+ }
+diff --git a/ext/standard/tests/http/ghsa-pcmh-g36c-qc44-001.phpt b/ext/standard/tests/http/ghsa-pcmh-g36c-qc44-001.phpt
+new file mode 100644
+index 00000000000..bb7945ce62d
+--- /dev/null
++++ b/ext/standard/tests/http/ghsa-pcmh-g36c-qc44-001.phpt
+@@ -0,0 +1,51 @@
++--TEST--
++GHSA-pcmh-g36c-qc44: Header parser of http stream wrapper does not verify header name and colon (colon)
++--FILE--
++<?php
++$serverCode = <<<'CODE'
++ $ctxt = stream_context_create([
++ "socket" => [
++ "tcp_nodelay" => true
++ ]
++ ]);
++
++ $server = stream_socket_server(
++ "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt);
++ phpt_notify_server_start($server);
++
++ $conn = stream_socket_accept($server);
++
++ phpt_notify(message:"server-accepted");
++
++ fwrite($conn, "HTTP/1.0 200 Ok\r\nContent-Type: text/html\r\nWrong-Header\r\nGood-Header: test\r\n\r\nbody\r\n");
++CODE;
++
++$clientCode = <<<'CODE'
++ function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) {
++ switch($notification_code) {
++ case STREAM_NOTIFY_MIME_TYPE_IS:
++ echo "Found the mime-type: ", $message, PHP_EOL;
++ break;
++ }
++ }
++
++ $ctx = stream_context_create();
++ stream_context_set_params($ctx, array("notification" => "stream_notification_callback"));
++ var_dump(file_get_contents("http://{{ ADDR }}", false, $ctx));
++ var_dump($http_response_header);
++CODE;
++
++include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
++ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
++?>
++--EXPECTF--
++Found the mime-type: text/html
++
++Warning: file_get_contents(http://127.0.0.1:%d): Failed to open stream: HTTP invalid response format (no colon in header line)! in %s
++bool(false)
++array(2) {
++ [0]=>
++ string(15) "HTTP/1.0 200 Ok"
++ [1]=>
++ string(23) "Content-Type: text/html"
++}
+diff --git a/ext/standard/tests/http/ghsa-pcmh-g36c-qc44-002.phpt b/ext/standard/tests/http/ghsa-pcmh-g36c-qc44-002.phpt
+new file mode 100644
+index 00000000000..1d0e4fa70a2
+--- /dev/null
++++ b/ext/standard/tests/http/ghsa-pcmh-g36c-qc44-002.phpt
+@@ -0,0 +1,51 @@
++--TEST--
++GHSA-pcmh-g36c-qc44: Header parser of http stream wrapper does not verify header name and colon (name)
++--FILE--
++<?php
++$serverCode = <<<'CODE'
++ $ctxt = stream_context_create([
++ "socket" => [
++ "tcp_nodelay" => true
++ ]
++ ]);
++
++ $server = stream_socket_server(
++ "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt);
++ phpt_notify_server_start($server);
++
++ $conn = stream_socket_accept($server);
++
++ phpt_notify(message:"server-accepted");
++
++ fwrite($conn, "HTTP/1.0 200 Ok\r\nContent-Type: text/html\r\nWrong-Header : test\r\nGood-Header: test\r\n\r\nbody\r\n");
++CODE;
++
++$clientCode = <<<'CODE'
++ function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) {
++ switch($notification_code) {
++ case STREAM_NOTIFY_MIME_TYPE_IS:
++ echo "Found the mime-type: ", $message, PHP_EOL;
++ break;
++ }
++ }
++
++ $ctx = stream_context_create();
++ stream_context_set_params($ctx, array("notification" => "stream_notification_callback"));
++ var_dump(file_get_contents("http://{{ ADDR }}", false, $ctx));
++ var_dump($http_response_header);
++CODE;
++
++include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
++ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
++?>
++--EXPECTF--
++Found the mime-type: text/html
++
++Warning: file_get_contents(http://127.0.0.1:%d): Failed to open stream: HTTP invalid response format (space in header name)! in %s
++bool(false)
++array(2) {
++ [0]=>
++ string(15) "HTTP/1.0 200 Ok"
++ [1]=>
++ string(23) "Content-Type: text/html"
++}
+--
+2.48.1
+
diff --git a/php-cve-2025-1736.patch b/php-cve-2025-1736.patch
new file mode 100644
index 0000000..134269d
--- /dev/null
+++ b/php-cve-2025-1736.patch
@@ -0,0 +1,241 @@
+From 8f65ef50929f6781f4973325f9b619f02cce19d8 Mon Sep 17 00:00:00 2001
+From: Jakub Zelenka <bukka@php.net>
+Date: Fri, 14 Feb 2025 19:17:22 +0100
+Subject: [PATCH 04/11] Fix GHSA-hgf5-96fm-v528: http user header check of crlf
+
+(cherry picked from commit 41d49abbd99dab06cdae4834db664435f8177174)
+---
+ ext/standard/http_fopen_wrapper.c | 2 +-
+ .../tests/http/ghsa-hgf5-96fm-v528-001.phpt | 65 +++++++++++++++++++
+ .../tests/http/ghsa-hgf5-96fm-v528-002.phpt | 62 ++++++++++++++++++
+ .../tests/http/ghsa-hgf5-96fm-v528-003.phpt | 64 ++++++++++++++++++
+ 4 files changed, 192 insertions(+), 1 deletion(-)
+ create mode 100644 ext/standard/tests/http/ghsa-hgf5-96fm-v528-001.phpt
+ create mode 100644 ext/standard/tests/http/ghsa-hgf5-96fm-v528-002.phpt
+ create mode 100644 ext/standard/tests/http/ghsa-hgf5-96fm-v528-003.phpt
+
+diff --git a/ext/standard/http_fopen_wrapper.c b/ext/standard/http_fopen_wrapper.c
+index e9b2486a7c9..64703c2f56b 100644
+--- a/ext/standard/http_fopen_wrapper.c
++++ b/ext/standard/http_fopen_wrapper.c
+@@ -107,7 +107,7 @@ static inline void strip_header(char *header_bag, char *lc_header_bag,
+ static zend_bool check_has_header(const char *headers, const char *header) {
+ const char *s = headers;
+ while ((s = strstr(s, header))) {
+- if (s == headers || *(s-1) == '\n') {
++ if (s == headers || (*(s-1) == '\n' && *(s-2) == '\r')) {
+ return 1;
+ }
+ s++;
+diff --git a/ext/standard/tests/http/ghsa-hgf5-96fm-v528-001.phpt b/ext/standard/tests/http/ghsa-hgf5-96fm-v528-001.phpt
+new file mode 100644
+index 00000000000..c40123560ef
+--- /dev/null
++++ b/ext/standard/tests/http/ghsa-hgf5-96fm-v528-001.phpt
+@@ -0,0 +1,65 @@
++--TEST--
++GHSA-hgf5-96fm-v528: Stream HTTP wrapper header check might omit basic auth header (incorrect inside pos)
++--FILE--
++<?php
++$serverCode = <<<'CODE'
++ $ctxt = stream_context_create([
++ "socket" => [
++ "tcp_nodelay" => true
++ ]
++ ]);
++
++ $server = stream_socket_server(
++ "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt);
++ phpt_notify_server_start($server);
++
++ $conn = stream_socket_accept($server);
++
++ phpt_notify(message:"server-accepted");
++
++ $result = fread($conn, 1024);
++ $encoded_result = base64_encode($result);
++
++ fwrite($conn, "HTTP/1.0 200 Ok\r\nContent-Type: text/html; charset=utf-8\r\n\r\n$encoded_result\r\n");
++
++CODE;
++
++$clientCode = <<<'CODE'
++ $opts = [
++ "http" => [
++ "method" => "GET",
++ "header" => "Cookie: foo=bar\nauthorization:x\r\n"
++ ]
++ ];
++ $ctx = stream_context_create($opts);
++ var_dump(explode("\r\n", base64_decode(file_get_contents("http://user:pwd@{{ ADDR }}", false, $ctx))));
++ var_dump($http_response_header);
++CODE;
++
++include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
++ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
++?>
++--EXPECTF--
++array(7) {
++ [0]=>
++ string(14) "GET / HTTP/1.1"
++ [1]=>
++ string(33) "Authorization: Basic dXNlcjpwd2Q="
++ [2]=>
++ string(21) "Host: 127.0.0.1:%d"
++ [3]=>
++ string(17) "Connection: close"
++ [4]=>
++ string(31) "Cookie: foo=bar
++authorization:x"
++ [5]=>
++ string(0) ""
++ [6]=>
++ string(0) ""
++}
++array(2) {
++ [0]=>
++ string(15) "HTTP/1.0 200 Ok"
++ [1]=>
++ string(38) "Content-Type: text/html; charset=utf-8"
++}
+diff --git a/ext/standard/tests/http/ghsa-hgf5-96fm-v528-002.phpt b/ext/standard/tests/http/ghsa-hgf5-96fm-v528-002.phpt
+new file mode 100644
+index 00000000000..37a47df060a
+--- /dev/null
++++ b/ext/standard/tests/http/ghsa-hgf5-96fm-v528-002.phpt
+@@ -0,0 +1,62 @@
++--TEST--
++GHSA-hgf5-96fm-v528: Header parser of http stream wrapper does not handle folded headers (correct start pos)
++--FILE--
++<?php
++$serverCode = <<<'CODE'
++ $ctxt = stream_context_create([
++ "socket" => [
++ "tcp_nodelay" => true
++ ]
++ ]);
++
++ $server = stream_socket_server(
++ "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt);
++ phpt_notify_server_start($server);
++
++ $conn = stream_socket_accept($server);
++
++ phpt_notify(message:"server-accepted");
++
++ $result = fread($conn, 1024);
++ $encoded_result = base64_encode($result);
++
++ fwrite($conn, "HTTP/1.0 200 Ok\r\nContent-Type: text/html; charset=utf-8\r\n\r\n$encoded_result\r\n");
++
++CODE;
++
++$clientCode = <<<'CODE'
++ $opts = [
++ "http" => [
++ "method" => "GET",
++ "header" => "Authorization: Bearer x\r\n"
++ ]
++ ];
++ $ctx = stream_context_create($opts);
++ var_dump(explode("\r\n", base64_decode(file_get_contents("http://user:pwd@{{ ADDR }}", false, $ctx))));
++ var_dump($http_response_header);
++CODE;
++
++include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
++ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
++?>
++--EXPECTF--
++array(6) {
++ [0]=>
++ string(14) "GET / HTTP/1.1"
++ [1]=>
++ string(21) "Host: 127.0.0.1:%d"
++ [2]=>
++ string(17) "Connection: close"
++ [3]=>
++ string(23) "Authorization: Bearer x"
++ [4]=>
++ string(0) ""
++ [5]=>
++ string(0) ""
++}
++array(2) {
++ [0]=>
++ string(15) "HTTP/1.0 200 Ok"
++ [1]=>
++ string(38) "Content-Type: text/html; charset=utf-8"
++}
+diff --git a/ext/standard/tests/http/ghsa-hgf5-96fm-v528-003.phpt b/ext/standard/tests/http/ghsa-hgf5-96fm-v528-003.phpt
+new file mode 100644
+index 00000000000..6c84679ff63
+--- /dev/null
++++ b/ext/standard/tests/http/ghsa-hgf5-96fm-v528-003.phpt
+@@ -0,0 +1,64 @@
++--TEST--
++GHSA-hgf5-96fm-v528: Header parser of http stream wrapper does not handle folded headers (correct middle pos)
++--FILE--
++<?php
++$serverCode = <<<'CODE'
++ $ctxt = stream_context_create([
++ "socket" => [
++ "tcp_nodelay" => true
++ ]
++ ]);
++
++ $server = stream_socket_server(
++ "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt);
++ phpt_notify_server_start($server);
++
++ $conn = stream_socket_accept($server);
++
++ phpt_notify(message:"server-accepted");
++
++ $result = fread($conn, 1024);
++ $encoded_result = base64_encode($result);
++
++ fwrite($conn, "HTTP/1.0 200 Ok\r\nContent-Type: text/html; charset=utf-8\r\n\r\n$encoded_result\r\n");
++
++CODE;
++
++$clientCode = <<<'CODE'
++ $opts = [
++ "http" => [
++ "method" => "GET",
++ "header" => "Cookie: x=y\r\nAuthorization: Bearer x\r\n"
++ ]
++ ];
++ $ctx = stream_context_create($opts);
++ var_dump(explode("\r\n", base64_decode(file_get_contents("http://user:pwd@{{ ADDR }}", false, $ctx))));
++ var_dump($http_response_header);
++CODE;
++
++include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
++ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
++?>
++--EXPECTF--
++array(7) {
++ [0]=>
++ string(14) "GET / HTTP/1.1"
++ [1]=>
++ string(21) "Host: 127.0.0.1:%d"
++ [2]=>
++ string(17) "Connection: close"
++ [3]=>
++ string(11) "Cookie: x=y"
++ [4]=>
++ string(23) "Authorization: Bearer x"
++ [5]=>
++ string(0) ""
++ [6]=>
++ string(0) ""
++}
++array(2) {
++ [0]=>
++ string(15) "HTTP/1.0 200 Ok"
++ [1]=>
++ string(38) "Content-Type: text/html; charset=utf-8"
++}
+--
+2.48.1
+
diff --git a/php-cve-2025-1861.patch b/php-cve-2025-1861.patch
new file mode 100644
index 0000000..856d9be
--- /dev/null
+++ b/php-cve-2025-1861.patch
@@ -0,0 +1,348 @@
+From adc7e9f20c9a9aab9cd23ca47ec3fb96287898ae Mon Sep 17 00:00:00 2001
+From: Jakub Zelenka <bukka@php.net>
+Date: Tue, 4 Mar 2025 09:01:34 +0100
+Subject: [PATCH 03/11] Fix GHSA-52jp-hrpf-2jff: http redirect location
+ truncation
+
+It converts the allocation of location to be on heap instead of stack
+and errors if the location length is greater than 8086 bytes.
+
+(cherry picked from commit ac1a054bb3eb5994a199e8b18cca28cbabf5943e)
+---
+ ext/standard/http_fopen_wrapper.c | 87 ++++++++++++-------
+ .../tests/http/ghsa-52jp-hrpf-2jff-001.phpt | 58 +++++++++++++
+ .../tests/http/ghsa-52jp-hrpf-2jff-002.phpt | 55 ++++++++++++
+ 3 files changed, 168 insertions(+), 32 deletions(-)
+ create mode 100644 ext/standard/tests/http/ghsa-52jp-hrpf-2jff-001.phpt
+ create mode 100644 ext/standard/tests/http/ghsa-52jp-hrpf-2jff-002.phpt
+
+diff --git a/ext/standard/http_fopen_wrapper.c b/ext/standard/http_fopen_wrapper.c
+index 7ee22b85f88..e9b2486a7c9 100644
+--- a/ext/standard/http_fopen_wrapper.c
++++ b/ext/standard/http_fopen_wrapper.c
+@@ -67,15 +67,16 @@
+
+ #include "php_fopen_wrappers.h"
+
+-#define HTTP_HEADER_BLOCK_SIZE 1024
+-#define PHP_URL_REDIRECT_MAX 20
+-#define HTTP_HEADER_USER_AGENT 1
+-#define HTTP_HEADER_HOST 2
+-#define HTTP_HEADER_AUTH 4
+-#define HTTP_HEADER_FROM 8
+-#define HTTP_HEADER_CONTENT_LENGTH 16
+-#define HTTP_HEADER_TYPE 32
+-#define HTTP_HEADER_CONNECTION 64
++#define HTTP_HEADER_BLOCK_SIZE 1024
++#define HTTP_HEADER_MAX_LOCATION_SIZE 8182 /* 8192 - 10 (size of "Location: ") */
++#define PHP_URL_REDIRECT_MAX 20
++#define HTTP_HEADER_USER_AGENT 1
++#define HTTP_HEADER_HOST 2
++#define HTTP_HEADER_AUTH 4
++#define HTTP_HEADER_FROM 8
++#define HTTP_HEADER_CONTENT_LENGTH 16
++#define HTTP_HEADER_TYPE 32
++#define HTTP_HEADER_CONNECTION 64
+
+ #define HTTP_WRAPPER_HEADER_INIT 1
+ #define HTTP_WRAPPER_REDIRECTED 2
+@@ -119,17 +120,15 @@ typedef struct _php_stream_http_response_header_info {
+ size_t file_size;
+ bool error;
+ bool follow_location;
+- char location[HTTP_HEADER_BLOCK_SIZE];
++ char *location;
++ size_t location_len;
+ } php_stream_http_response_header_info;
+
+ static void php_stream_http_response_header_info_init(
+ php_stream_http_response_header_info *header_info)
+ {
+- header_info->transfer_encoding = NULL;
+- header_info->file_size = 0;
+- header_info->error = false;
++ memset(header_info, 0, sizeof(php_stream_http_response_header_info));
+ header_info->follow_location = 1;
+- header_info->location[0] = '\0';
+ }
+
+ /* Trim white spaces from response header line and update its length */
+@@ -255,7 +254,22 @@ static zend_string *php_stream_http_response_headers_parse(php_stream_wrapper *w
+ * RFC 7238 defines 308: http://tools.ietf.org/html/rfc7238 */
+ header_info->follow_location = 0;
+ }
+- strlcpy(header_info->location, last_header_value, sizeof(header_info->location));
++ size_t last_header_value_len = strlen(last_header_value);
++ if (last_header_value_len > HTTP_HEADER_MAX_LOCATION_SIZE) {
++ header_info->error = true;
++ php_stream_wrapper_log_error(wrapper, options,
++ "HTTP Location header size is over the limit of %d bytes",
++ HTTP_HEADER_MAX_LOCATION_SIZE);
++ zend_string_efree(last_header_line_str);
++ return NULL;
++ }
++ if (header_info->location_len == 0) {
++ header_info->location = emalloc(last_header_value_len + 1);
++ } else if (header_info->location_len <= last_header_value_len) {
++ header_info->location = erealloc(header_info->location, last_header_value_len + 1);
++ }
++ header_info->location_len = last_header_value_len;
++ memcpy(header_info->location, last_header_value, last_header_value_len + 1);
+ } else if (!strncasecmp(last_header_line, "Content-Type:", sizeof("Content-Type:")-1)) {
+ php_stream_notify_info(context, PHP_STREAM_NOTIFY_MIME_TYPE_IS, last_header_value, 0);
+ } else if (!strncasecmp(last_header_line, "Content-Length:", sizeof("Content-Length:")-1)) {
+@@ -538,6 +552,8 @@ finish:
+ }
+ }
+
++ php_stream_http_response_header_info_init(&header_info);
++
+ if (stream == NULL)
+ goto out;
+
+@@ -919,8 +935,6 @@ finish:
+ }
+ }
+
+- php_stream_http_response_header_info_init(&header_info);
+-
+ /* read past HTTP headers */
+ while (!php_stream_eof(stream)) {
+ size_t http_header_line_length;
+@@ -990,12 +1004,12 @@ finish:
+ last_header_line_str, NULL, NULL, response_code, response_header, &header_info);
+ }
+
+- if (!reqok || (header_info.location[0] != '\0' && header_info.follow_location)) {
++ if (!reqok || (header_info.location != NULL && header_info.follow_location)) {
+ if (!header_info.follow_location || (((options & STREAM_ONLY_GET_HEADERS) || ignore_errors) && redirect_max <= 1)) {
+ goto out;
+ }
+
+- if (header_info.location[0] != '\0')
++ if (header_info.location != NULL)
+ php_stream_notify_info(context, PHP_STREAM_NOTIFY_REDIRECTED, header_info.location, 0);
+
+ php_stream_close(stream);
+@@ -1006,18 +1020,17 @@ finish:
+ header_info.transfer_encoding = NULL;
+ }
+
+- if (header_info.location[0] != '\0') {
++ if (header_info.location != NULL) {
+
+- char new_path[HTTP_HEADER_BLOCK_SIZE];
+- char loc_path[HTTP_HEADER_BLOCK_SIZE];
++ char *new_path = NULL;
+
+- *new_path='\0';
+ if (strlen(header_info.location) < 8 ||
+ (strncasecmp(header_info.location, "http://", sizeof("http://")-1) &&
+ strncasecmp(header_info.location, "https://", sizeof("https://")-1) &&
+ strncasecmp(header_info.location, "ftp://", sizeof("ftp://")-1) &&
+ strncasecmp(header_info.location, "ftps://", sizeof("ftps://")-1)))
+ {
++ char *loc_path = NULL;
+ if (*header_info.location != '/') {
+ if (*(header_info.location+1) != '\0' && resource->path) {
+ char *s = strrchr(ZSTR_VAL(resource->path), '/');
+@@ -1035,31 +1048,35 @@ finish:
+ if (resource->path &&
+ ZSTR_VAL(resource->path)[0] == '/' &&
+ ZSTR_VAL(resource->path)[1] == '\0') {
+- snprintf(loc_path, sizeof(loc_path) - 1, "%s%s",
+- ZSTR_VAL(resource->path), header_info.location);
++ spprintf(&loc_path, 0, "%s%s", ZSTR_VAL(resource->path), header_info.location);
+ } else {
+- snprintf(loc_path, sizeof(loc_path) - 1, "%s/%s",
+- ZSTR_VAL(resource->path), header_info.location);
++ spprintf(&loc_path, 0, "%s/%s", ZSTR_VAL(resource->path), header_info.location);
+ }
+ } else {
+- snprintf(loc_path, sizeof(loc_path) - 1, "/%s", header_info.location);
++ spprintf(&loc_path, 0, "/%s", header_info.location);
+ }
+ } else {
+- strlcpy(loc_path, header_info.location, sizeof(loc_path));
++ loc_path = header_info.location;
++ header_info.location = NULL;
+ }
+ if ((use_ssl && resource->port != 443) || (!use_ssl && resource->port != 80)) {
+- snprintf(new_path, sizeof(new_path) - 1, "%s://%s:%d%s", ZSTR_VAL(resource->scheme), ZSTR_VAL(resource->host), resource->port, loc_path);
++ spprintf(&new_path, 0, "%s://%s:%d%s", ZSTR_VAL(resource->scheme),
++ ZSTR_VAL(resource->host), resource->port, loc_path);
+ } else {
+- snprintf(new_path, sizeof(new_path) - 1, "%s://%s%s", ZSTR_VAL(resource->scheme), ZSTR_VAL(resource->host), loc_path);
++ spprintf(&new_path, 0, "%s://%s%s", ZSTR_VAL(resource->scheme),
++ ZSTR_VAL(resource->host), loc_path);
+ }
++ efree(loc_path);
+ } else {
+- strlcpy(new_path, header_info.location, sizeof(new_path));
++ new_path = header_info.location;
++ header_info.location = NULL;
+ }
+
+ php_url_free(resource);
+ /* check for invalid redirection URLs */
+ if ((resource = php_url_parse(new_path)) == NULL) {
+ php_stream_wrapper_log_error(wrapper, options, "Invalid redirect URL! %s", new_path);
++ efree(new_path);
+ goto out;
+ }
+
+@@ -1071,6 +1088,7 @@ finish:
+ while (s < e) { \
+ if (iscntrl(*s)) { \
+ php_stream_wrapper_log_error(wrapper, options, "Invalid redirect URL! %s", new_path); \
++ efree(new_path); \
+ goto out; \
+ } \
+ s++; \
+@@ -1086,6 +1104,7 @@ finish:
+ stream = php_stream_url_wrap_http_ex(
+ wrapper, new_path, mode, options, opened_path, context,
+ --redirect_max, HTTP_WRAPPER_REDIRECTED, response_header STREAMS_CC);
++ efree(new_path);
+ } else {
+ php_stream_wrapper_log_error(wrapper, options, "HTTP request failed! %s", tmp_line);
+ }
+@@ -1098,6 +1117,10 @@ out:
+ efree(http_header_line);
+ }
+
++ if (header_info.location != NULL) {
++ efree(header_info.location);
++ }
++
+ if (resource) {
+ php_url_free(resource);
+ }
+diff --git a/ext/standard/tests/http/ghsa-52jp-hrpf-2jff-001.phpt b/ext/standard/tests/http/ghsa-52jp-hrpf-2jff-001.phpt
+new file mode 100644
+index 00000000000..744cff9cc72
+--- /dev/null
++++ b/ext/standard/tests/http/ghsa-52jp-hrpf-2jff-001.phpt
+@@ -0,0 +1,58 @@
++--TEST--
++GHSA-52jp-hrpf-2jff: HTTP stream wrapper truncate redirect location to 1024 bytes (success)
++--FILE--
++<?php
++$serverCode = <<<'CODE'
++$ctxt = stream_context_create([
++ "socket" => [
++ "tcp_nodelay" => true
++ ]
++ ]);
++
++ $server = stream_socket_server(
++ "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt);
++ phpt_notify_server_start($server);
++
++ $conn = stream_socket_accept($server);
++
++ phpt_notify(message:"server-accepted");
++
++ $loc = str_repeat("y", 8000);
++ fwrite($conn, "HTTP/1.0 301 Ok\r\nContent-Type: text/html;\r\nLocation: $loc\r\n\r\nbody\r\n");
++CODE;
++
++$clientCode = <<<'CODE'
++ function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) {
++ switch($notification_code) {
++ case STREAM_NOTIFY_MIME_TYPE_IS:
++ echo "Found the mime-type: ", $message, PHP_EOL;
++ break;
++ case STREAM_NOTIFY_REDIRECTED:
++ echo "Redirected: ";
++ var_dump($message);
++ }
++ }
++
++ $ctx = stream_context_create();
++ stream_context_set_params($ctx, array("notification" => "stream_notification_callback"));
++ var_dump(trim(file_get_contents("http://{{ ADDR }}", false, $ctx)));
++ var_dump($http_response_header);
++CODE;
++
++include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
++ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
++?>
++--EXPECTF--
++Found the mime-type: text/html;
++Redirected: string(8000) "%s"
++
++Warning: file_get_contents(http://127.0.0.1:%d): Failed to open stream: %s
++string(0) ""
++array(3) {
++ [0]=>
++ string(15) "HTTP/1.0 301 Ok"
++ [1]=>
++ string(24) "Content-Type: text/html;"
++ [2]=>
++ string(8010) "Location: %s"
++}
+diff --git a/ext/standard/tests/http/ghsa-52jp-hrpf-2jff-002.phpt b/ext/standard/tests/http/ghsa-52jp-hrpf-2jff-002.phpt
+new file mode 100644
+index 00000000000..bc71fd4e411
+--- /dev/null
++++ b/ext/standard/tests/http/ghsa-52jp-hrpf-2jff-002.phpt
+@@ -0,0 +1,55 @@
++--TEST--
++GHSA-52jp-hrpf-2jff: HTTP stream wrapper truncate redirect location to 1024 bytes (over limit)
++--FILE--
++<?php
++$serverCode = <<<'CODE'
++$ctxt = stream_context_create([
++ "socket" => [
++ "tcp_nodelay" => true
++ ]
++ ]);
++
++ $server = stream_socket_server(
++ "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt);
++ phpt_notify_server_start($server);
++
++ $conn = stream_socket_accept($server);
++
++ phpt_notify(message:"server-accepted");
++
++ $loc = str_repeat("y", 9000);
++ fwrite($conn, "HTTP/1.0 301 Ok\r\nContent-Type: text/html;\r\nLocation: $loc\r\n\r\nbody\r\n");
++CODE;
++
++$clientCode = <<<'CODE'
++ function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) {
++ switch($notification_code) {
++ case STREAM_NOTIFY_MIME_TYPE_IS:
++ echo "Found the mime-type: ", $message, PHP_EOL;
++ break;
++ case STREAM_NOTIFY_REDIRECTED:
++ echo "Redirected: ";
++ var_dump($message);
++ }
++ }
++
++ $ctx = stream_context_create();
++ stream_context_set_params($ctx, array("notification" => "stream_notification_callback"));
++ var_dump(trim(file_get_contents("http://{{ ADDR }}", false, $ctx)));
++ var_dump($http_response_header);
++CODE;
++
++include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
++ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
++?>
++--EXPECTF--
++Found the mime-type: text/html;
++
++Warning: file_get_contents(http://127.0.0.1:%d): Failed to open stream: HTTP Location header size is over the limit of 8182 bytes in %s
++string(0) ""
++array(2) {
++ [0]=>
++ string(15) "HTTP/1.0 301 Ok"
++ [1]=>
++ string(24) "Content-Type: text/html;"
++}
+--
+2.48.1
+
diff --git a/php.spec b/php.spec
index 5c7d361..a41ff6c 100644
--- a/php.spec
+++ b/php.spec
@@ -49,17 +49,10 @@
%global mysql_sock %(mysql_config --socket 2>/dev/null || echo /var/lib/mysql/mysql.sock)
-%ifarch aarch64
-%global oraclever 19.25
-%global oraclemax 20
-%global oraclelib 19.1
-%global oracledir 19.25
-%else
%global oraclever 23.7
%global oraclemax 24
%global oraclelib 23.1
%global oracledir 23
-%endif
# Build for LiteSpeed Web Server (LSAPI), you can disable using --without tests
%bcond_without lsws
@@ -132,7 +125,7 @@
Summary: PHP scripting language for creating dynamic web sites
Name: %{?scl_prefix}php
Version: %{upver}%{?rcver:~%{rcver}}%{?gh_date:.%{gh_date}}
-Release: 12%{?dist}
+Release: 13%{?dist}
# All files licensed under PHP version 3.01, except
# Zend is licensed under Zend
# TSRM is licensed under BSD
@@ -172,8 +165,6 @@ Patch1: php-7.4.0-httpd.patch
Patch5: php-7.2.0-includedir.patch
Patch6: php-8.0.0-embed.patch
Patch7: php-7.4.0-libdb.patch
-# For libxml 2.12 from 8.1
-Patch8: php-8.0.30-libxml212.patch
# get rid of deprecated functions from 8.1
Patch9: php-8.0.6-deprecated.patch
# For recent ICU from 8.2
@@ -226,11 +217,17 @@ Patch209: php-cve-2024-8932.patch
Patch210: php-cve-2024-11233.patch
Patch211: php-ghsa-4w77-75f9-2c8w.patch
Patch212: php-cve-2024-8929.patch
+Patch213: php-cve-2025-1217.patch
+Patch214: php-cve-2025-1734.patch
+Patch215: php-cve-2025-1861.patch
+Patch216: php-cve-2025-1736.patch
+Patch217: php-cve-2025-1219.patch
# Fixes for tests (300+)
# Factory is droped from system tzdata
Patch300: php-7.4.0-datetests.patch
Patch301: php-8.0.30-zlib-tests.patch
+Patch302: php-8.0.30-pcretests.patch
# WIP
@@ -661,14 +658,7 @@ License.
Summary: A module for PHP applications that use OCI8 databases
# All files licensed under PHP version 3.01
License: PHP
-%ifarch aarch64
-BuildRequires: oracle-instantclient%{oraclever}-devel
-# Should requires libclntsh.so.19.1()(aarch-64), but it's not provided by Oracle RPM.
-Requires: libclntsh.so.%{oraclelib}
-AutoReq: 0
-%else
BuildRequires: (oracle-instantclient-devel >= %{oraclever} with oracle-instantclient-devel < %{oraclemax})
-%endif
Requires: %{?scl_prefix}php-pdo%{?_isa} = %{version}-%{release}
Provides: %{?scl_prefix}php_database
Provides: %{?scl_prefix}php-pdo_oci
@@ -986,7 +976,6 @@ in pure PHP.
%patch -P5 -p1 -b .includedir
%patch -P6 -p1 -b .embed
%patch -P7 -p1 -b .libdb
-%patch -P8 -p1 -b .libxml212
%patch -P9 -p1 -b .deprecated
%patch -P11 -p1 -b .icu74
%patch -P12 -p1 -b .proto
@@ -1025,10 +1014,16 @@ rm ext/openssl/tests/p12_with_extra_certs.p12
%patch -P210 -p1 -b .cve11233
%patch -P211 -p1 -b .ghsa4w77
%patch -P212 -p1 -b .cve8929
+%patch -P213 -p1 -b .cve1217
+%patch -P214 -p1 -b .cve1734
+%patch -P215 -p1 -b .cve1861
+%patch -P216 -p1 -b .cve1736
+%patch -P217 -p1 -b .cve1219
# Fixes for tests
%patch -P300 -p1 -b .datetests
%patch -P301 -p1 -b .zlibng
+%patch -P302 -p1 -b .pcretests
# WIP patch
@@ -1051,12 +1046,6 @@ mkdir \
# ----- Manage known as failed test -------
# affected by systzdata patch
rm ext/date/tests/timezone_location_get.phpt
-%if 0%{?fedora} < 36
-# need tzdata 2022b
-rm ext/date/tests/bug33414-1.phpt
-rm ext/date/tests/bug33415-2.phpt
-rm ext/date/tests/date_modify-1.phpt
-%endif
# too fast builder
rm ext/date/tests/bug73837.phpt
# fails sometime
@@ -1073,6 +1062,15 @@ rm ext/zlib/tests/004-mb.phpt
# failed when systemd is enabled
rm sapi/fpm/tests/gh8885-stderr-fd-reload-usr1.phpt
rm sapi/fpm/tests/gh8885-stderr-fd-reload-usr2.phpt
+# Known to fail
+%if 0%{?rhel} == 8
+rm ext/openssl/tests/openssl_error_string_basic.phpt
+rm ext/openssl/tests/openssl_open_basic.phpt
+%else
+rm ext/openssl/tests/openssl_error_string_basic_openssl3.phpt
+%endif
+rm ext/openssl/tests/openssl_private_decrypt_basic.phpt
+rm ext/openssl/tests/openssl_x509_parse_basic.phpt
# Safety check for API version change.
pver=$(sed -n '/#define PHP_VERSION /{s/.* "//;s/".*$//;p}' main/php_version.h)
@@ -1917,6 +1915,19 @@ EOF
%changelog
+* Thu Mar 13 2025 Remi Collet <remi@remirepo.net> - 8.0.30-13
+- 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
+- use oracle client library version 23.7 on x86_64 and aarch64
+
* Thu Feb 13 2025 Remi Collet <remi@remirepo.net> - 8.0.30-12
- backport fix for ICU 74+
- backport fix strict prototypes