Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions ext/standard/tests/streams/gh22841.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
--TEST--
GH-22841 (php_stream_copy_to_stream_ex() drops progress notifications)
--EXTENSIONS--
zlib
--SKIPIF--
<?php
if (!function_exists("proc_open")) die("skip proc_open() is not available");
?>
--INI--
allow_url_fopen=1
--CONFLICTS--
server
--FILE--
<?php

/* A 43 byte gzip stream decompressing to "Hello World!\n". Kept as a literal so
* that the server, which is started with -n, does not need zlib itself. */
$serverCode = <<<'CODE'
$data = base64_decode("H4sICPQfX2oAA2luZGV4LnBocADzSM3JyVcIzy/KSVHkAgDd3RR9DQAAAA==");
header("Content-Type: application/gzip");
header("Content-Length: " . strlen($data));
echo $data;
CODE;

include __DIR__ . "/../../../../sapi/cli/tests/php_cli_server.inc";
php_cli_server_start($serverCode, null, []);

$names = [
STREAM_NOTIFY_CONNECT => "CONNECT",
STREAM_NOTIFY_MIME_TYPE_IS => "MIME_TYPE_IS",
STREAM_NOTIFY_FILE_SIZE_IS => "FILE_SIZE_IS",
STREAM_NOTIFY_PROGRESS => "PROGRESS",
STREAM_NOTIFY_COMPLETED => "COMPLETED",
];

$events = [];
$context = stream_context_create();
stream_context_set_params($context, ["notification" =>
function ($code, $severity, $message, $message_code, $bytes_transferred, $bytes_max)
use (&$events, $names) {
$name = $names[$code] ?? $code;
$event = $name . " " . ($code === STREAM_NOTIFY_FILE_SIZE_IS ? $bytes_max : $bytes_transferred);
/* The body may arrive in more than one read, so collapse consecutive
* progress events into the latest one to keep the output stable. */
if ($name === "PROGRESS" && str_starts_with(end($events) ?: "", "PROGRESS ")) {
array_pop($events);
}
$events[] = $event;
}
]);

/* compress.zlib:// opens the inner stream with STREAM_MUST_SEEK, which copies
* the HTTP stream into a temporary file through php_stream_copy_to_stream_ex(). */
$fp = fopen("compress.zlib://http://" . PHP_CLI_SERVER_ADDRESS . "/", "r", false, $context);
var_dump(stream_get_contents($fp));
fclose($fp);

echo implode(PHP_EOL, $events), PHP_EOL;

?>
--EXPECT--
string(13) "Hello World!
"
CONNECT 0
MIME_TYPE_IS 0
FILE_SIZE_IS 43
PROGRESS 43
COMPLETED 43
10 changes: 8 additions & 2 deletions main/streams/streams.c
Original file line number Diff line number Diff line change
Expand Up @@ -1302,7 +1302,12 @@ static zend_result php_stream_filters_seek_all(php_stream *stream, bool is_start
return SUCCESS;
}


static bool php_stream_copy_context(php_stream *stream)
{
php_stream_context *context = PHP_STREAM_CONTEXT(stream);
/* The fd-level copy cannot emit progress notifications. */
return context && context->notifier;
}

PHPAPI int _php_stream_seek(php_stream *stream, zend_off_t offset, int whence)
{
Expand Down Expand Up @@ -1655,7 +1660,8 @@ PHPAPI zend_result _php_stream_copy_to_stream_ex(php_stream *src, php_stream *de
* are empty, so the fd offsets match the logical stream positions */
if (!php_stream_is(src, PHP_STREAM_IS_USERSPACE) && !php_stream_is(dest, PHP_STREAM_IS_USERSPACE) &&
src->writepos == src->readpos && dest->writepos == dest->readpos &&
!php_stream_is_filtered(src) && !php_stream_is_filtered(dest)) {
!php_stream_is_filtered(src) && !php_stream_is_filtered(dest) &&
!php_stream_copy_context(src) && !php_stream_copy_context(dest)) {
php_io_fd src_copy_fd, dest_copy_fd;

if (php_stream_cast(src, PHP_STREAM_AS_FD_FOR_COPY, (void *) &src_copy_fd, 0) == SUCCESS &&
Expand Down
Loading