Commit 0ff02e86dc7 for php

commit 0ff02e86dc7db58ada9ef521245025906d24053a
Author: Edmond <1571649+EdmondDantes@users.noreply.github.com>
Date:   Wed Oct 14 20:33:17 2026 +0300

    std: Fix memory leak of the last response headers after a nested HTTP request (#24201)

    * Fix leak of the last response headers after a nested HTTP request

    php_stream_url_wrap_http() releases BG(last_http_headers) before the request
    and copies the new headers over it after. A request made while the first one
    runs, from its notification callback, stores its own headers there in between,
    and the copy overwrote that array without releasing it.

    * Release the last response headers once, after the request

    The release before the request is dropped: a single release after it covers
    both the previous request's headers and those stored by a nested one.

diff --git a/ext/standard/http_fopen_wrapper.c b/ext/standard/http_fopen_wrapper.c
index 2cc9e081ae7..687c2f70d4a 100644
--- a/ext/standard/http_fopen_wrapper.c
+++ b/ext/standard/http_fopen_wrapper.c
@@ -1272,16 +1272,15 @@ php_stream *php_stream_url_wrap_http(php_stream_wrapper *wrapper, const char *pa

 	ZVAL_UNDEF(&headers);

-	zval_ptr_dtor(&BG(last_http_headers));
-	ZVAL_UNDEF(&BG(last_http_headers));
-
 	stream = php_stream_url_wrap_http_ex(
 		wrapper, path, mode, options, opened_path, context,
 		PHP_URL_REDIRECT_MAX, HTTP_WRAPPER_HEADER_INIT, &headers STREAMS_CC);

-	if (!Z_ISUNDEF(headers)) {
-		ZVAL_COPY(&BG(last_http_headers), &headers);
+	/* After the request: one made from its notification callback may have stored its own headers here. */
+	zval_ptr_dtor(&BG(last_http_headers));
+	ZVAL_COPY(&BG(last_http_headers), &headers);

+	if (!Z_ISUNDEF(headers)) {
 		if (FAILURE == zend_set_local_var_str(
 				"http_response_header", sizeof("http_response_header")-1, &headers, 0)) {
 			zval_ptr_dtor(&headers);
diff --git a/ext/standard/tests/http/http_response_header_nested_request.phpt b/ext/standard/tests/http/http_response_header_nested_request.phpt
new file mode 100644
index 00000000000..d562818f74e
--- /dev/null
+++ b/ext/standard/tests/http/http_response_header_nested_request.phpt
@@ -0,0 +1,41 @@
+--TEST--
+http_get_last_response_headers() after a request made from a notification callback
+--SKIPIF--
+<?php require 'server.inc'; http_server_skipif(); ?>
+--INI--
+allow_url_fopen=1
+--FILE--
+<?php
+require 'server.inc';
+
+$responses = [
+    "data://text/plain,HTTP/1.0 200 Ok\r\nContent-Type: text/plain\r\nOuter: 1\r\n\r\nouter",
+    "data://text/plain,HTTP/1.0 200 Ok\r\nInner: 1\r\n\r\ninner",
+];
+
+['pid' => $pid, 'uri' => $uri] = http_server($responses, $output);
+
+$nested = false;
+$ctx = stream_context_create([], ['notification' => function ($code) use (&$nested, $uri) {
+    if ($code === STREAM_NOTIFY_MIME_TYPE_IS && !$nested) {
+        $nested = true;
+        var_dump(file_get_contents($uri));
+    }
+}]);
+
+var_dump(file_get_contents($uri, false, $ctx));
+var_dump(http_get_last_response_headers());
+
+http_server_kill($pid);
+?>
+--EXPECT--
+string(5) "inner"
+string(5) "outer"
+array(3) {
+  [0]=>
+  string(15) "HTTP/1.0 200 Ok"
+  [1]=>
+  string(24) "Content-Type: text/plain"
+  [2]=>
+  string(8) "Outer: 1"
+}