From 882e64e63eca0036dc0611055e74bfb9f616b87b Mon Sep 17 00:00:00 2001 From: Ayesh Karunaratne Date: Tue, 23 Jan 2024 21:33:27 +0700 Subject: [PATCH] ext/curl: Add `CURLOPT_PREREQFUNCTION` Curl >= 7.80.0 supports declaring a function for `CURLOPT_PREREQFUNCTION` option that gets called after Curl establishes a connection (including the TLS handshake for HTTPS connections), but before the actual request is made. The callable must return either `CURL_PREREQFUNC_OK` or `CURL_PREREQFUNC_ABORT` to allow or abort the request. This adds support for it to PHP with required ifdef. - libc: https://curl.se/libcurl/c/CURLOPT_PREREQFUNCTION.html Co-Authored-By: Gina Peter Bnayard --- ext/curl/curl.stub.php | 15 ++ ext/curl/curl_arginfo.h | 11 +- ext/curl/curl_private.h | 3 + ext/curl/interface.c | 79 +++++++- ext/curl/sync-constants.php | 1 + .../curl_setopt_CURLOPT_PREREQFUNCTION.phpt | 182 ++++++++++++++++++ 6 files changed, 289 insertions(+), 2 deletions(-) create mode 100644 ext/curl/tests/curl_setopt_CURLOPT_PREREQFUNCTION.phpt diff --git a/ext/curl/curl.stub.php b/ext/curl/curl.stub.php index 473e8b6297e98..c7eba7394843c 100644 --- a/ext/curl/curl.stub.php +++ b/ext/curl/curl.stub.php @@ -3497,6 +3497,21 @@ * @cvalue CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256 */ const CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256 = UNKNOWN; +/** + * @var int + * @cvalue CURLOPT_PREREQFUNCTION + */ +const CURLOPT_PREREQFUNCTION = UNKNOWN; +/** + * @var int + * @cvalue CURL_PREREQFUNC_OK + */ +const CURL_PREREQFUNC_OK = UNKNOWN; +/** + * @var int + * @cvalue CURL_PREREQFUNC_ABORT + */ +const CURL_PREREQFUNC_ABORT = UNKNOWN; #endif #if LIBCURL_VERSION_NUM >= 0x075100 /* Available since 7.81.0 */ diff --git a/ext/curl/curl_arginfo.h b/ext/curl/curl_arginfo.h index c1cedaa6acb36..f3f13ae3b4ca0 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: ddfcdd8a0bf0ee6c338ec1689c6de5d7fd87303d */ + * Stub hash: 3a5bd4e561f08f0dbd26383132a771acc8192fff */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_curl_close, 0, 1, IS_VOID, 0) ZEND_ARG_OBJ_INFO(0, handle, CurlHandle, 0) @@ -1061,6 +1061,15 @@ static void register_curl_symbols(int module_number) #if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */ REGISTER_LONG_CONSTANT("CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256", CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256, CONST_PERSISTENT); #endif +#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */ + REGISTER_LONG_CONSTANT("CURLOPT_PREREQFUNCTION", CURLOPT_PREREQFUNCTION, CONST_PERSISTENT); +#endif +#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */ + REGISTER_LONG_CONSTANT("CURL_PREREQFUNC_OK", CURL_PREREQFUNC_OK, CONST_PERSISTENT); +#endif +#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */ + REGISTER_LONG_CONSTANT("CURL_PREREQFUNC_ABORT", CURL_PREREQFUNC_ABORT, CONST_PERSISTENT); +#endif #if LIBCURL_VERSION_NUM >= 0x075100 /* Available since 7.81.0 */ REGISTER_LONG_CONSTANT("CURLOPT_MIME_OPTIONS", CURLOPT_MIME_OPTIONS, CONST_PERSISTENT); #endif diff --git a/ext/curl/curl_private.h b/ext/curl/curl_private.h index c15396f255b17..011c4874e6140 100644 --- a/ext/curl/curl_private.h +++ b/ext/curl/curl_private.h @@ -68,6 +68,9 @@ typedef struct { zend_fcall_info_cache progress; zend_fcall_info_cache xferinfo; zend_fcall_info_cache fnmatch; +#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */ + zend_fcall_info_cache prereq; +#endif #if LIBCURL_VERSION_NUM >= 0x075400 /* Available since 7.84.0 */ zend_fcall_info_cache sshhostkey; #endif diff --git a/ext/curl/interface.c b/ext/curl/interface.c index 8ec2ec33520ec..4697f468098d6 100644 --- a/ext/curl/interface.c +++ b/ext/curl/interface.c @@ -504,6 +504,11 @@ static HashTable *curl_get_gc(zend_object *object, zval **table, int *n) zend_get_gc_buffer_add_fcc(gc_buffer, &curl->handlers.fnmatch); } +#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */ + if (ZEND_FCC_INITIALIZED(curl->handlers.prereq)) { + zend_get_gc_buffer_add_fcc(gc_buffer, &curl->handlers.prereq); + } +#endif #if LIBCURL_VERSION_NUM >= 0x075400 /* Available since 7.84.0 */ if (ZEND_FCC_INITIALIZED(curl->handlers.sshhostkey)) { zend_get_gc_buffer_add_fcc(gc_buffer, &curl->handlers.sshhostkey); @@ -709,6 +714,60 @@ static size_t curl_xferinfo(void *clientp, curl_off_t dltotal, curl_off_t dlnow, } /* }}} */ +#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */ +static int curl_prereqfunction(void *clientp, char *conn_primary_ip, char *conn_local_ip, int conn_primary_port, int conn_local_port) +{ + php_curl *ch = (php_curl *)clientp; + int rval = CURL_PREREQFUNC_OK; + + // when CURLOPT_PREREQFUNCTION is set to null, curl_prereqfunction still + // gets called. Return CURL_PREREQFUNC_OK immediately in this case to avoid + // zend_call_known_fcc() with an uninitialized FCC. + if (!ZEND_FCC_INITIALIZED(ch->handlers.prereq)) { + return rval; + } + +#if PHP_CURL_DEBUG + fprintf(stderr, "curl_prereqfunction() called\n"); + fprintf(stderr, "conn_primary_ip = %s, conn_local_ip = %s, conn_primary_port = %d, conn_local_port = %d\n", conn_primary_ip, conn_local_ip, conn_primary_port, conn_local_port); +#endif + + zval args[5]; + zval retval; + + GC_ADDREF(&ch->std); + ZVAL_OBJ(&args[0], &ch->std); + ZVAL_STRING(&args[1], conn_primary_ip); + ZVAL_STRING(&args[2], conn_local_ip); + ZVAL_LONG(&args[3], conn_primary_port); + ZVAL_LONG(&args[4], conn_local_port); + + ch->in_callback = true; + zend_call_known_fcc(&ch->handlers.prereq, &retval, /* param_count */ 5, args, /* named_params */ NULL); + ch->in_callback = false; + + if (!Z_ISUNDEF(retval)) { + _php_curl_verify_handlers(ch, /* reporterror */ true); + if (Z_TYPE(retval) == IS_LONG) { + zend_long retval_long = Z_LVAL(retval); + if (retval_long == CURL_PREREQFUNC_OK || retval_long == CURL_PREREQFUNC_ABORT) { + rval = retval_long; + } else { + zend_value_error("The CURLOPT_PREREQFUNCTION callback must return either CURL_PREREQFUNC_OK or CURL_PREREQFUNC_ABORT"); + } + } else { + zend_type_error("The CURLOPT_PREREQFUNCTION callback must return either CURL_PREREQFUNC_OK or CURL_PREREQFUNC_ABORT"); + } + } + + zval_ptr_dtor(&args[0]); + zval_ptr_dtor(&args[1]); + zval_ptr_dtor(&args[2]); + + return rval; +} +#endif + #if LIBCURL_VERSION_NUM >= 0x075400 /* Available since 7.84.0 */ static int curl_ssh_hostkeyfunction(void *clientp, int keytype, const char *key, size_t keylen) { @@ -1037,6 +1096,9 @@ void init_curl_handle(php_curl *ch) ch->handlers.progress = empty_fcall_info_cache; ch->handlers.xferinfo = empty_fcall_info_cache; ch->handlers.fnmatch = empty_fcall_info_cache; +#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */ + ch->handlers.prereq = empty_fcall_info_cache; +#endif #if LIBCURL_VERSION_NUM >= 0x075400 /* Available since 7.84.0 */ ch->handlers.sshhostkey = empty_fcall_info_cache; #endif @@ -1210,6 +1272,9 @@ void _php_setup_easy_copy_handlers(php_curl *ch, php_curl *source) php_curl_copy_fcc_with_option(ch, CURLOPT_PROGRESSDATA, &ch->handlers.progress, &source->handlers.progress); php_curl_copy_fcc_with_option(ch, CURLOPT_XFERINFODATA, &ch->handlers.xferinfo, &source->handlers.xferinfo); php_curl_copy_fcc_with_option(ch, CURLOPT_FNMATCH_DATA, &ch->handlers.fnmatch, &source->handlers.fnmatch); +#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */ + php_curl_copy_fcc_with_option(ch, CURLOPT_PREREQDATA, &ch->handlers.prereq, &source->handlers.prereq); +#endif #if LIBCURL_VERSION_NUM >= 0x075400 /* Available since 7.84.0 */ php_curl_copy_fcc_with_option(ch, CURLOPT_SSH_HOSTKEYDATA, &ch->handlers.sshhostkey, &source->handlers.sshhostkey); #endif @@ -1570,6 +1635,9 @@ static zend_result _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue HANDLE_CURL_OPTION_CALLABLE(ch, CURLOPT_PROGRESS, handlers.progress, curl_progress); HANDLE_CURL_OPTION_CALLABLE(ch, CURLOPT_XFERINFO, handlers.xferinfo, curl_xferinfo); HANDLE_CURL_OPTION_CALLABLE(ch, CURLOPT_FNMATCH_, handlers.fnmatch, curl_fnmatch); +#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */ + HANDLE_CURL_OPTION_CALLABLE(ch, CURLOPT_PREREQ, handlers.prereq, curl_prereqfunction); +#endif #if LIBCURL_VERSION_NUM >= 0x075400 /* Available since 7.84.0 */ HANDLE_CURL_OPTION_CALLABLE(ch, CURLOPT_SSH_HOSTKEY, handlers.sshhostkey, curl_ssh_hostkeyfunction); #endif @@ -2736,6 +2804,11 @@ static void curl_free_obj(zend_object *object) if (ZEND_FCC_INITIALIZED(ch->handlers.fnmatch)) { zend_fcc_dtor(&ch->handlers.fnmatch); } +#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */ + if (ZEND_FCC_INITIALIZED(ch->handlers.prereq)) { + zend_fcc_dtor(&ch->handlers.prereq); + } +#endif #if LIBCURL_VERSION_NUM >= 0x075400 /* Available since 7.84.0 */ if (ZEND_FCC_INITIALIZED(ch->handlers.sshhostkey)) { zend_fcc_dtor(&ch->handlers.sshhostkey); @@ -2814,7 +2887,11 @@ static void _php_curl_reset_handlers(php_curl *ch) if (ZEND_FCC_INITIALIZED(ch->handlers.fnmatch)) { zend_fcc_dtor(&ch->handlers.fnmatch); } - +#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */ + if (ZEND_FCC_INITIALIZED(ch->handlers.prereq)) { + zend_fcc_dtor(&ch->handlers.prereq); + } +#endif #if LIBCURL_VERSION_NUM >= 0x075400 /* Available since 7.84.0 */ if (ZEND_FCC_INITIALIZED(ch->handlers.sshhostkey)) { zend_fcc_dtor(&ch->handlers.sshhostkey); diff --git a/ext/curl/sync-constants.php b/ext/curl/sync-constants.php index d827de5fc97e3..8f35f7b05fa38 100755 --- a/ext/curl/sync-constants.php +++ b/ext/curl/sync-constants.php @@ -17,6 +17,7 @@ const IGNORED_CURL_CONSTANTS = [ 'CURLOPT_PROGRESSDATA', 'CURLOPT_XFERINFODATA', + 'CURLOPT_PREREQDATA', ]; const IGNORED_PHP_CONSTANTS = [ diff --git a/ext/curl/tests/curl_setopt_CURLOPT_PREREQFUNCTION.phpt b/ext/curl/tests/curl_setopt_CURLOPT_PREREQFUNCTION.phpt new file mode 100644 index 0000000000000..d11e29f078c09 --- /dev/null +++ b/ext/curl/tests/curl_setopt_CURLOPT_PREREQFUNCTION.phpt @@ -0,0 +1,182 @@ +--TEST-- +Curl option CURLOPT_PREREQFUNCTION +--EXTENSIONS-- +curl +filter +--SKIPIF-- += 7.80.0"); +?> +--FILE-- +getMessage() . \PHP_EOL; +} + +echo "\nTesting with invalid type\n"; +curl_setopt($ch, CURLOPT_PREREQFUNCTION, function() use ($port) { + return 'this should be an integer'; +}); +try { + curl_exec($ch); +} catch (\TypeError $e) { + echo $e->getMessage() . \PHP_EOL; +} + +echo "\nTesting with invalid value\n"; +curl_setopt($ch, CURLOPT_PREREQFUNCTION, function() use ($port) { + return 42; +}); +try { + curl_exec($ch); +} catch (\ValueError $e) { + echo $e->getMessage() . \PHP_EOL; +} + +echo "\nTesting with invalid option value\n"; +try { + curl_setopt($ch, CURLOPT_PREREQFUNCTION, 42); +} catch (\TypeError $e) { + echo $e->getMessage() . \PHP_EOL; +} + +echo "\nTesting with invalid option callback\n"; +try { + curl_setopt($ch, CURLOPT_PREREQFUNCTION, 'function_does_not_exist'); +} catch (\TypeError $e) { + echo $e->getMessage() . \PHP_EOL; +} + +echo "\nTesting with null as the callback\n"; +var_dump(curl_setopt($ch, CURLOPT_PREREQFUNCTION, null)); +var_dump(curl_exec($ch)); +var_dump(curl_error($ch)); +var_dump(curl_errno($ch)); + +echo "\nDone"; +?> +--EXPECT-- +int(20312) +int(0) +int(1) + +Testing with CURL_PREREQFUNC_ABORT +string(8) "callback" +int(5) +string(10) "CurlHandle" +bool(true) +bool(true) +bool(true) +bool(true) +bool(false) +string(41) "operation aborted by pre-request callback" +int(42) + +Testing with CURL_PREREQFUNC_OK +string(8) "callback" +int(5) +string(10) "CurlHandle" +bool(true) +bool(true) +bool(true) +bool(true) +string(0) "" +string(0) "" +int(0) + +Testing with curl_copy_handle +string(8) "callback" +int(5) +string(10) "CurlHandle" +bool(true) +bool(true) +bool(true) +bool(true) +string(0) "" +string(0) "" +int(0) + +Testing with no return type +The CURLOPT_PREREQFUNCTION callback must return either CURL_PREREQFUNC_OK or CURL_PREREQFUNC_ABORT + +Testing with invalid type +The CURLOPT_PREREQFUNCTION callback must return either CURL_PREREQFUNC_OK or CURL_PREREQFUNC_ABORT + +Testing with invalid value +The CURLOPT_PREREQFUNCTION callback must return either CURL_PREREQFUNC_OK or CURL_PREREQFUNC_ABORT + +Testing with invalid option value +curl_setopt(): Argument #3 ($value) must be a valid callback for option CURLOPT_PREREQFUNCTION, no array or string given + +Testing with invalid option callback +curl_setopt(): Argument #3 ($value) must be a valid callback for option CURLOPT_PREREQFUNCTION, function "function_does_not_exist" not found or invalid function name + +Testing with null as the callback +bool(true) +string(0) "" +string(0) "" +int(0) + +Done