From 5842e34e4d95b1e777786dde2761cd9a8a80e690 Mon Sep 17 00:00:00 2001 From: Joe Watkins Date: Mon, 1 Sep 2025 06:01:53 +0200 Subject: [PATCH] socket functions initial import --- ext/curl/curl.stub.php | 53 +++++++++++++ ext/curl/curl_arginfo.h | 21 +++++- ext/curl/curl_private.h | 2 + ext/curl/multi.c | 162 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 237 insertions(+), 1 deletion(-) diff --git a/ext/curl/curl.stub.php b/ext/curl/curl.stub.php index c9cda9c0d4b0c..efd444f1309e9 100644 --- a/ext/curl/curl.stub.php +++ b/ext/curl/curl.stub.php @@ -2713,6 +2713,56 @@ * @cvalue CURLMOPT_PUSHFUNCTION */ const CURLMOPT_PUSHFUNCTION = UNKNOWN; +/** + * @var int + * @cvalue CURLMOPT_SOCKETFUNCTION + */ +const CURLMOPT_SOCKETFUNCTION = UNKNOWN; +/** + * @var int + * @cvalue CURLMOPT_TIMERFUNCTION + */ +const CURLMOPT_TIMERFUNCTION = UNKNOWN; +/** + * @var int + * @cvalue CURL_POLL_IN + */ +const CURL_POLL_IN = UNKNOWN; +/** + * @var int + * @cvalue CURL_POLL_OUT + */ +const CURL_POLL_OUT = UNKNOWN; +/** + * @var int + * @cvalue CURL_POLL_INOUT + */ +const CURL_POLL_INOUT = UNKNOWN; +/** + * @var int + * @cvalue CURL_POLL_REMOVE + */ +const CURL_POLL_REMOVE = UNKNOWN; +/** + * @var int + * @cvalue CURL_CSELECT_IN + */ +const CURL_CSELECT_IN = UNKNOWN; +/** + * @var int + * @cvalue CURL_CSELECT_OUT + */ +const CURL_CSELECT_OUT = UNKNOWN; +/** + * @var int + * @cvalue CURL_CSELECT_ERR + */ +const CURL_CSELECT_ERR = UNKNOWN; +/** + * @var int + * @cvalue CURL_SOCKET_TIMEOUT + */ +const CURL_SOCKET_TIMEOUT = UNKNOWN; /** * @var int * @cvalue CURL_PUSH_OK @@ -3759,6 +3809,9 @@ function curl_unescape(CurlHandle $handle, string $string): string|false {} function curl_multi_setopt(CurlMultiHandle $multi_handle, int $option, mixed $value): bool {} +/** @param mixed $socket */ +function curl_multi_socket_action(CurlMultiHandle $multi_handle, $socket, int $what, int &$still_running): int {}; + function curl_exec(CurlHandle $handle): string|bool {} /** @refcount 1 */ diff --git a/ext/curl/curl_arginfo.h b/ext/curl/curl_arginfo.h index b511ff077de9d..6b0d992fad11a 100644 --- a/ext/curl/curl_arginfo.h +++ b/ext/curl/curl_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 2a2772e99deea07c0bc148e9715e6a960230cf4d */ + * Stub hash: 5b43d8d0d2e80b34bfcde8cdda8db300d8b9ac90 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_curl_close, 0, 1, IS_VOID, 0) ZEND_ARG_OBJ_INFO(0, handle, CurlHandle, 0) @@ -30,6 +30,13 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_curl_multi_setopt, 0, 3, _IS_BOO ZEND_ARG_TYPE_INFO(0, value, IS_MIXED, 0) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_curl_multi_socket_action, 0, 4, IS_LONG, 0) + ZEND_ARG_OBJ_INFO(0, multi_handle, CurlMultiHandle, 0) + ZEND_ARG_INFO(0, socket) + ZEND_ARG_TYPE_INFO(0, what, IS_LONG, 0) + ZEND_ARG_TYPE_INFO(1, still_running, IS_LONG, 0) +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_curl_exec, 0, 1, MAY_BE_STRING|MAY_BE_BOOL) ZEND_ARG_OBJ_INFO(0, handle, CurlHandle, 0) ZEND_END_ARG_INFO() @@ -153,6 +160,7 @@ ZEND_FUNCTION(curl_error); ZEND_FUNCTION(curl_escape); ZEND_FUNCTION(curl_unescape); ZEND_FUNCTION(curl_multi_setopt); +ZEND_FUNCTION(curl_multi_socket_action); ZEND_FUNCTION(curl_exec); ZEND_FUNCTION(curl_file_create); ZEND_FUNCTION(curl_getinfo); @@ -192,6 +200,7 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(curl_escape, arginfo_curl_escape) ZEND_FE(curl_unescape, arginfo_curl_unescape) ZEND_FE(curl_multi_setopt, arginfo_curl_multi_setopt) + ZEND_FE(curl_multi_socket_action, arginfo_curl_multi_socket_action) ZEND_FE(curl_exec, arginfo_curl_exec) ZEND_FE(curl_file_create, arginfo_curl_file_create) ZEND_FE(curl_getinfo, arginfo_curl_getinfo) @@ -747,6 +756,16 @@ static void register_curl_symbols(int module_number) REGISTER_LONG_CONSTANT("CURLOPT_DEFAULT_PROTOCOL", CURLOPT_DEFAULT_PROTOCOL, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("CURLOPT_STREAM_WEIGHT", CURLOPT_STREAM_WEIGHT, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("CURLMOPT_PUSHFUNCTION", CURLMOPT_PUSHFUNCTION, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("CURLMOPT_SOCKETFUNCTION", CURLMOPT_SOCKETFUNCTION, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("CURLMOPT_TIMERFUNCTION", CURLMOPT_TIMERFUNCTION, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("CURL_POLL_IN", CURL_POLL_IN, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("CURL_POLL_OUT", CURL_POLL_OUT, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("CURL_POLL_INOUT", CURL_POLL_INOUT, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("CURL_POLL_REMOVE", CURL_POLL_REMOVE, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("CURL_CSELECT_IN", CURL_CSELECT_IN, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("CURL_CSELECT_OUT", CURL_CSELECT_OUT, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("CURL_CSELECT_ERR", CURL_CSELECT_ERR, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("CURL_SOCKET_TIMEOUT", CURL_SOCKET_TIMEOUT, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("CURL_PUSH_OK", CURL_PUSH_OK, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("CURL_PUSH_DENY", CURL_PUSH_DENY, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("CURL_HTTP_VERSION_2TLS", CURL_HTTP_VERSION_2TLS, CONST_PERSISTENT); diff --git a/ext/curl/curl_private.h b/ext/curl/curl_private.h index df6fd691a2a75..e860863b06b5c 100644 --- a/ext/curl/curl_private.h +++ b/ext/curl/curl_private.h @@ -123,6 +123,8 @@ typedef struct { typedef struct { zend_fcall_info_cache server_push; + zend_fcall_info_cache socket_function; + zend_fcall_info_cache timer_function; } php_curlm_handlers; typedef struct { diff --git a/ext/curl/multi.c b/ext/curl/multi.c index a16155759aed4..75244057f1545 100644 --- a/ext/curl/multi.c +++ b/ext/curl/multi.c @@ -448,6 +448,109 @@ static int _php_server_push_callback(CURL *parent_ch, CURL *easy, size_t num_hea } /* }}} */ +/* {{{ */ +PHP_FUNCTION(curl_multi_socket_action) +{ + zval *z_mh; + zval *z_socket; + zend_long ev_bitmask; + zval *z_still_running; + + ZEND_PARSE_PARAMETERS_START(4,4) + Z_PARAM_OBJECT_OF_CLASS(z_mh, curl_multi_ce) + Z_PARAM_ZVAL(z_socket) + Z_PARAM_LONG(ev_bitmask) + Z_PARAM_ZVAL(z_still_running) + ZEND_PARSE_PARAMETERS_END(); + + php_curlm *mh = Z_CURL_MULTI_P(z_mh); + + curl_socket_t socket; + + if (Z_TYPE_P(z_socket) == IS_LONG) { + socket = (curl_socket_t) Z_LVAL_P(z_socket); + } else { + php_stream *p_stream; + php_stream_from_zval(p_stream, z_socket); + if (php_stream_cast( + p_stream, PHP_STREAM_AS_FD, + (void**) &socket, REPORT_ERRORS) != SUCCESS) { + return; + } + } + + int still_running = zval_get_long(z_still_running); + CURLMcode error = curl_multi_socket_action( + mh->multi, socket, ev_bitmask, &still_running); + ZEND_TRY_ASSIGN_REF_LONG(z_still_running, still_running); + + SAVE_CURLM_ERROR(mh, error); + RETURN_LONG((zend_long) error); +} + +static int _php_curl_multi_timerfunction(CURLM *multi, zend_long timeout, void *userp) { + php_curlm *mh = (php_curlm *) userp; + + zval z_object; + ZVAL_OBJ_COPY(&z_object, &mh->std); + + zval z_timeout; + ZVAL_LONG(&z_timeout, timeout); + + zval call_args[2] = {z_object, z_timeout}; + zval retval; + zend_call_known_fcc(&mh->handlers.timer_function, &retval, /* param_count */ 2, call_args, /* named_params */ NULL); + + if (!Z_ISUNDEF(retval)) { + zval_ptr_dtor(&retval); + } + + zval_ptr_dtor(&z_object); + return SUCCESS; +} + +static int _php_curl_multi_socketfunction(CURL *easy, curl_socket_t socket, int what, void *userp, void *stream) { + php_curlm *mh = (php_curlm *) userp; + php_stream *p_stream; + + if (stream == NULL && what != CURL_POLL_REMOVE) { + p_stream = (void*) php_stream_fopen_from_fd(socket, "rw", NULL); + if (!p_stream) { + return FAILURE; + } + + if (curl_multi_assign(mh->multi, socket, p_stream) != CURLM_OK) { + php_stream_close(p_stream); + return FAILURE; + } + } else { + p_stream = (php_stream*) stream; + } + + zval z_stream; + php_stream_to_zval(p_stream, &z_stream); + + zval *z_easy = _php_curl_multi_find_easy_handle(mh, easy); + + zval z_what; + ZVAL_LONG(&z_what, what); + zval call_args[3] = {*z_easy, z_stream, z_what}; + zval retval; + zend_call_known_fcc(&mh->handlers.socket_function, &retval, /* param_count */ 3, call_args, /* named_params */ NULL); + + if (!Z_ISUNDEF(retval)) { + zval_ptr_dtor(&retval); + } + + if (what == CURL_POLL_REMOVE && p_stream != NULL) { + curl_multi_assign(mh->multi, socket, NULL); + php_stream_close(p_stream); + } + + return SUCCESS; +} +/* }}} */ + static bool _php_curl_multi_setopt(php_curlm *mh, zend_long option, zval *zvalue, zval *return_value) /* {{{ */ { CURLMcode error = CURLM_OK; @@ -499,6 +602,49 @@ static bool _php_curl_multi_setopt(php_curlm *mh, zend_long option, zval *zvalue error = curl_multi_setopt(mh->multi, CURLMOPT_PUSHDATA, mh); break; } + case CURLMOPT_SOCKETFUNCTION: { + if (ZEND_FCC_INITIALIZED(mh->handlers.socket_function)) { + zend_fcc_dtor(&mh->handlers.socket_function); + } + + char *error_str = NULL; + if (UNEXPECTED(!zend_is_callable_ex(zvalue, /* object */ NULL, /* check_flags */ 0, /* callable_name */ NULL, &mh->handlers.socket_function, /* error */ &error_str))) { + if (!EG(exception)) { + zend_argument_type_error(2, "must be a valid callback for option CURLMOPT_SOCKETFUNCTION, %s", error_str); + } + efree(error_str); + return false; + } + zend_fcc_addref(&mh->handlers.socket_function); + + error = curl_multi_setopt(mh->multi, CURLMOPT_SOCKETFUNCTION, _php_curl_multi_socketfunction); + if (error != CURLM_OK) { + return false; + } + error = curl_multi_setopt(mh->multi, CURLMOPT_SOCKETDATA, mh); + break; + } + case CURLMOPT_TIMERFUNCTION: { + if (ZEND_FCC_INITIALIZED(mh->handlers.timer_function)) { + zend_fcc_dtor(&mh->handlers.timer_function); + } + + char *error_str = NULL; + if (UNEXPECTED(!zend_is_callable_ex(zvalue, /* object */ NULL, /* check_flags */ 0, /* callable_name */ NULL, &mh->handlers.timer_function, /* error */ &error_str))) { + if (!EG(exception)) { + zend_argument_type_error(2, "must be a valid callback for option CURLMOPT_TIMERFUNCTION, %s", error_str); + } + efree(error_str); + return false; + } + zend_fcc_addref(&mh->handlers.timer_function); + error = curl_multi_setopt(mh->multi, CURLMOPT_TIMERFUNCTION, _php_curl_multi_timerfunction); + if (error != CURLM_OK) { + return false; + } + error = curl_multi_setopt(mh->multi, CURLMOPT_TIMERDATA, mh); + break; + } default: zend_argument_value_error(2, "is not a valid cURL multi option"); error = CURLM_UNKNOWN_OPTION; @@ -575,6 +721,14 @@ static void curl_multi_free_obj(zend_object *object) zend_fcc_dtor(&mh->handlers.server_push); } + if (ZEND_FCC_INITIALIZED(mh->handlers.timer_function)) { + zend_fcc_dtor(&mh->handlers.timer_function); + } + + if (ZEND_FCC_INITIALIZED(mh->handlers.socket_function)) { + zend_fcc_dtor(&mh->handlers.socket_function); + } + zend_object_std_dtor(&mh->std); } @@ -588,6 +742,14 @@ static HashTable *curl_multi_get_gc(zend_object *object, zval **table, int *n) zend_get_gc_buffer_add_fcc(gc_buffer, &curl_multi->handlers.server_push); } + if (ZEND_FCC_INITIALIZED(curl_multi->handlers.timer_function)) { + zend_get_gc_buffer_add_fcc(gc_buffer, &curl_multi->handlers.timer_function); + } + + if (ZEND_FCC_INITIALIZED(curl_multi->handlers.socket_function)) { + zend_get_gc_buffer_add_fcc(gc_buffer, &curl_multi->handlers.socket_function); + } + zend_llist_position pos; for (zval *pz_ch = (zval *) zend_llist_get_first_ex(&curl_multi->easyh, &pos); pz_ch; pz_ch = (zval *) zend_llist_get_next_ex(&curl_multi->easyh, &pos)) {