Skip to content

Commit 0ff02e8

Browse files
authored
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.
1 parent 67decbe commit 0ff02e8

2 files changed

Lines changed: 45 additions & 5 deletions

File tree

‎ext/standard/http_fopen_wrapper.c‎

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1272,16 +1272,15 @@ php_stream *php_stream_url_wrap_http(php_stream_wrapper *wrapper, const char *pa
12721272

12731273
ZVAL_UNDEF(&headers);
12741274

1275-
zval_ptr_dtor(&BG(last_http_headers));
1276-
ZVAL_UNDEF(&BG(last_http_headers));
1277-
12781275
stream = php_stream_url_wrap_http_ex(
12791276
wrapper, path, mode, options, opened_path, context,
12801277
PHP_URL_REDIRECT_MAX, HTTP_WRAPPER_HEADER_INIT, &headers STREAMS_CC);
12811278

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

1283+
if (!Z_ISUNDEF(headers)) {
12851284
if (FAILURE == zend_set_local_var_str(
12861285
"http_response_header", sizeof("http_response_header")-1, &headers, 0)) {
12871286
zval_ptr_dtor(&headers);
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
--TEST--
2+
http_get_last_response_headers() after a request made from a notification callback
3+
--SKIPIF--
4+
<?php require 'server.inc'; http_server_skipif(); ?>
5+
--INI--
6+
allow_url_fopen=1
7+
--FILE--
8+
<?php
9+
require 'server.inc';
10+
11+
$responses = [
12+
"data://text/plain,HTTP/1.0 200 Ok\r\nContent-Type: text/plain\r\nOuter: 1\r\n\r\nouter",
13+
"data://text/plain,HTTP/1.0 200 Ok\r\nInner: 1\r\n\r\ninner",
14+
];
15+
16+
['pid' => $pid, 'uri' => $uri] = http_server($responses, $output);
17+
18+
$nested = false;
19+
$ctx = stream_context_create([], ['notification' => function ($code) use (&$nested, $uri) {
20+
if ($code === STREAM_NOTIFY_MIME_TYPE_IS && !$nested) {
21+
$nested = true;
22+
var_dump(file_get_contents($uri));
23+
}
24+
}]);
25+
26+
var_dump(file_get_contents($uri, false, $ctx));
27+
var_dump(http_get_last_response_headers());
28+
29+
http_server_kill($pid);
30+
?>
31+
--EXPECT--
32+
string(5) "inner"
33+
string(5) "outer"
34+
array(3) {
35+
[0]=>
36+
string(15) "HTTP/1.0 200 Ok"
37+
[1]=>
38+
string(24) "Content-Type: text/plain"
39+
[2]=>
40+
string(8) "Outer: 1"
41+
}

0 commit comments

Comments
 (0)