diff --git a/ext/standard/streamsfuncs.c b/ext/standard/streamsfuncs.c index 818a645086b0..2e38965232c7 100644 --- a/ext/standard/streamsfuncs.c +++ b/ext/standard/streamsfuncs.c @@ -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(¬ifier->ptr); - ZVAL_UNDEF(¬ifier->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); diff --git a/ext/standard/tests/streams/stream_context_get_params_001.phpt b/ext/standard/tests/streams/stream_context_get_params_001.phpt index e8b107ccfe95..aaed575314e5 100644 --- a/ext/standard/tests/streams/stream_context_get_params_001.phpt +++ b/ext/standard/tests/streams/stream_context_get_params_001.phpt @@ -4,16 +4,19 @@ stream_context_get_params() "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" } diff --git a/ext/standard/tests/streams/stream_context_set_params_notification_error.phpt b/ext/standard/tests/streams/stream_context_set_params_notification_error.phpt new file mode 100644 index 000000000000..32af36d0d477 --- /dev/null +++ b/ext/standard/tests/streams/stream_context_set_params_notification_error.phpt @@ -0,0 +1,21 @@ +--TEST-- +stream_context_set_params() with invalid notification option +--FILE-- + "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 diff --git a/ext/standard/tests/streams/stream_context_set_params_notification_valid_then_invalid.phpt b/ext/standard/tests/streams/stream_context_set_params_notification_valid_then_invalid.phpt new file mode 100644 index 000000000000..9f654d61761e --- /dev/null +++ b/ext/standard/tests/streams/stream_context_set_params_notification_valid_then_invalid.phpt @@ -0,0 +1,20 @@ +--TEST-- +stream_context_set_params() with valid, then invalid notification option +--FILE-- + "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 diff --git a/main/streams/php_stream_context.h b/main/streams/php_stream_context.h index a09b61923ade..f61604c929d7 100644 --- a/main/streams/php_stream_context.h +++ b/main/streams/php_stream_context.h @@ -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 */ };