diff options
author | Remi Collet <remi@remirepo.net> | 2025-03-13 11:10:36 +0100 |
---|---|---|
committer | Remi Collet <remi@php.net> | 2025-03-13 11:10:36 +0100 |
commit | 6e7731500aec13a717379932940501d43e49d1bb (patch) | |
tree | bfc2c8e8781b985207699ef2bb9de7e4acacdf93 | |
parent | e3b1264950afa02a001c9c62eea886838e436c27 (diff) |
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.txt | 35 | ||||
-rw-r--r-- | php-8.0.30-libxml212.patch | 681 | ||||
-rw-r--r-- | php-8.0.30-pcretests.patch | 43 | ||||
-rw-r--r-- | php-cve-2025-1217.patch | 909 | ||||
-rw-r--r-- | php-cve-2025-1219.patch | 1779 | ||||
-rw-r--r-- | php-cve-2025-1734.patch | 300 | ||||
-rw-r--r-- | php-cve-2025-1736.patch | 241 | ||||
-rw-r--r-- | php-cve-2025-1861.patch | 348 | ||||
-rw-r--r-- | php.spec | 59 |
9 files changed, 3668 insertions, 727 deletions
@@ -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 + @@ -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 |