ext/standard/stream: Use FCC instead of zval for notification callback (#19024)

Also check that the callable exists while setting the option
This commit is contained in:
Gina Peter Banyard 2025-07-06 01:30:07 +01:00 committed by GitHub
parent c33805791d
commit 677a1f80c8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 76 additions and 26 deletions

View file

@ -856,10 +856,7 @@ PHP_FUNCTION(stream_select)
static void user_space_stream_notifier(php_stream_context *context, int notifycode, int severity,
char *xmsg, int xcode, size_t bytes_sofar, size_t bytes_max, void * ptr)
{
zval *callback = &context->notifier->ptr;
zval retval;
zval zvs[6];
int i;
ZVAL_LONG(&zvs[0], notifycode);
ZVAL_LONG(&zvs[1], severity);
@ -872,21 +869,19 @@ static void user_space_stream_notifier(php_stream_context *context, int notifyco
ZVAL_LONG(&zvs[4], bytes_sofar);
ZVAL_LONG(&zvs[5], bytes_max);
if (FAILURE == call_user_function(NULL, NULL, callback, &retval, 6, zvs)) {
php_error_docref(NULL, E_WARNING, "Failed to call user notifier");
}
for (i = 0; i < 6; i++) {
zval_ptr_dtor(&zvs[i]);
}
zval_ptr_dtor(&retval);
zend_call_known_fcc(context->notifier->fcc, NULL, 6, zvs, NULL);
/* Free refcounted string parameter */
zval_ptr_dtor_str(&zvs[2]);
}
static void user_space_stream_notifier_dtor(php_stream_notifier *notifier)
{
if (notifier && Z_TYPE(notifier->ptr) != IS_UNDEF) {
zval_ptr_dtor(&notifier->ptr);
ZVAL_UNDEF(&notifier->ptr);
}
ZEND_ASSERT(notifier);
ZEND_ASSERT(notifier->fcc);
ZEND_ASSERT(notifier->fcc->function_handler);
zend_fcc_dtor(notifier->fcc);
efree(notifier->fcc);
notifier->fcc = NULL;
}
static zend_result parse_context_options(php_stream_context *context, HashTable *options)
@ -924,9 +919,19 @@ static zend_result parse_context_params(php_stream_context *context, HashTable *
context->notifier = NULL;
}
zend_fcall_info_cache *fcc = emalloc(sizeof(*fcc));
char *error;
if (!zend_is_callable_ex(tmp, NULL, 0, NULL, fcc, &error)) {
zend_argument_type_error(1, "must be an array with valid callbacks as values, %s", error);
efree(fcc);
efree(error);
return FAILURE;
}
zend_fcc_addref(fcc);
context->notifier = php_stream_notification_alloc();
context->notifier->func = user_space_stream_notifier;
ZVAL_COPY(&context->notifier->ptr, tmp);
context->notifier->fcc = fcc;
context->notifier->dtor = user_space_stream_notifier_dtor;
}
if (NULL != (tmp = zend_hash_str_find(params, "options", sizeof("options")-1))) {
@ -1123,9 +1128,11 @@ PHP_FUNCTION(stream_context_get_params)
}
array_init(return_value);
if (context->notifier && Z_TYPE(context->notifier->ptr) != IS_UNDEF && context->notifier->func == user_space_stream_notifier) {
Z_TRY_ADDREF(context->notifier->ptr);
add_assoc_zval_ex(return_value, "notification", sizeof("notification")-1, &context->notifier->ptr);
if (context->notifier && context->notifier->fcc) {
ZEND_ASSERT(context->notifier->func == user_space_stream_notifier);
zval fn;
zend_get_callable_zval_from_fcc(context->notifier->fcc, &fn);
add_assoc_zval_ex(return_value, ZEND_STRL("notification"), &fn);
}
Z_TRY_ADDREF(context->options);
add_assoc_zval_ex(return_value, "options", sizeof("options")-1, &context->options);

View file

@ -4,16 +4,19 @@ stream_context_get_params()
<?php
$ctx = stream_context_create();
var_dump($ctx);
var_dump(stream_context_get_params($ctx));
var_dump(stream_context_set_option($ctx, "foo","bar","baz"));
var_dump(stream_context_get_params($ctx));
function stream_notification_callback() {}
var_dump(stream_context_set_params($ctx, array("notification" => "stream_notification_callback")));
var_dump(stream_context_get_params($ctx));
var_dump(stream_context_set_params($ctx, array("notification" => array("stream","notification_callback"))));
class MyStream {
public static function notification_callback() {}
}
var_dump(stream_context_set_params($ctx, array("notification" => ["MyStream", "notification_callback"])));
var_dump(stream_context_get_params($ctx));
var_dump(stream_context_get_params($ctx));
@ -22,8 +25,7 @@ var_dump(stream_context_get_params($ctx));
var_dump(stream_context_get_options($ctx));
?>
--EXPECTF--
resource(%d) of type (stream-context)
--EXPECT--
array(1) {
["options"]=>
array(0) {
@ -58,7 +60,7 @@ array(2) {
["notification"]=>
array(2) {
[0]=>
string(6) "stream"
string(8) "MyStream"
[1]=>
string(21) "notification_callback"
}
@ -75,7 +77,7 @@ array(2) {
["notification"]=>
array(2) {
[0]=>
string(6) "stream"
string(8) "MyStream"
[1]=>
string(21) "notification_callback"
}
@ -99,7 +101,7 @@ array(2) {
["notification"]=>
array(2) {
[0]=>
string(6) "stream"
string(8) "MyStream"
[1]=>
string(21) "notification_callback"
}

View file

@ -0,0 +1,21 @@
--TEST--
stream_context_set_params() with invalid notification option
--FILE--
<?php
$ctx = stream_context_create();
try {
var_dump(stream_context_set_params($ctx, ["notification" => "fn_not_exist"]));
} catch (\Throwable $e) {
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
}
try {
var_dump(stream_context_set_params($ctx, ["notification" => ["myclass", "fn_not_exist"]]));
} catch (\Throwable $e) {
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
}
?>
--EXPECT--
TypeError: stream_context_set_params(): Argument #1 ($context) must be an array with valid callbacks as values, function "fn_not_exist" not found or invalid function name
TypeError: stream_context_set_params(): Argument #1 ($context) must be an array with valid callbacks as values, class "myclass" not found

View file

@ -0,0 +1,20 @@
--TEST--
stream_context_set_params() with valid, then invalid notification option
--FILE--
<?php
function foo() {}
$ctx = stream_context_create();
var_dump(stream_context_set_params($ctx, ["notification" => "foo"]));
try {
var_dump(stream_context_set_params($ctx, ["notification" => "fn_not_exist"]));
} catch (\Throwable $e) {
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
}
?>
--EXPECT--
bool(true)
TypeError: stream_context_set_params(): Argument #1 ($context) must be an array with valid callbacks as values, function "fn_not_exist" not found or invalid function name

View file

@ -44,7 +44,7 @@ typedef struct _php_stream_notifier php_stream_notifier;
struct _php_stream_notifier {
php_stream_notification_func func;
void (*dtor)(php_stream_notifier *notifier);
zval ptr;
zend_fcall_info_cache *fcc;
int mask;
size_t progress, progress_max; /* position for progress notification */
};