diff --git a/ext/curl/curl.stub.php b/ext/curl/curl.stub.php index 8a20231da562b..b0279f4ad80d5 100644 --- a/ext/curl/curl.stub.php +++ b/ext/curl/curl.stub.php @@ -3743,7 +3743,7 @@ function curl_share_close(CurlShareHandle $share_handle): void {} function curl_share_errno(CurlShareHandle $share_handle): int {} /** @refcount 1 */ -function curl_share_init(): CurlShareHandle {} +function curl_share_init(array $share_options = [], ?string $persistent_id = null): CurlShareHandle {} function curl_share_setopt(CurlShareHandle $share_handle, int $option, mixed $value): bool {} diff --git a/ext/curl/curl_arginfo.h b/ext/curl/curl_arginfo.h index 664cda7d32a97..b0afffb4ead60 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: e2800e5ecc33f092576c7afcdb98d89825809669 */ + * Stub hash: 852d02349e947838d46b4a0599c6e64967cdf815 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_curl_close, 0, 1, IS_VOID, 0) ZEND_ARG_OBJ_INFO(0, handle, CurlHandle, 0) @@ -127,6 +127,8 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_curl_share_errno, 0, 1, IS_LONG, ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_curl_share_init, 0, 0, CurlShareHandle, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, share_options, IS_ARRAY, 0, "[]") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, persistent_id, IS_STRING, 1, "null") ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_curl_share_setopt, 0, 3, _IS_BOOL, 0) diff --git a/ext/curl/curl_private.h b/ext/curl/curl_private.h index 8e8e9586db587..987ad971dc2fa 100644 --- a/ext/curl/curl_private.h +++ b/ext/curl/curl_private.h @@ -40,9 +40,19 @@ #define SAVE_CURL_ERROR(__handle, __err) \ do { (__handle)->err.no = (int) __err; } while (0) +ZEND_BEGIN_MODULE_GLOBALS(curl) + HashTable persistent_share_handles; +ZEND_END_MODULE_GLOBALS(curl) + +ZEND_EXTERN_MODULE_GLOBALS(curl) + +#define CURL_G(v) ZEND_MODULE_GLOBALS_ACCESSOR(curl, v) + PHP_MINIT_FUNCTION(curl); PHP_MSHUTDOWN_FUNCTION(curl); PHP_MINFO_FUNCTION(curl); +PHP_GINIT_FUNCTION(curl); +PHP_GSHUTDOWN_FUNCTION(curl); typedef struct { zend_fcall_info_cache fcc; @@ -125,10 +135,13 @@ typedef struct { } php_curlm; typedef struct _php_curlsh { - CURLSH *share; + CURLSH *share; + zend_string *persistent_id; + struct { int no; } err; + zend_object std; } php_curlsh; @@ -152,7 +165,9 @@ static inline php_curlsh *curl_share_from_obj(zend_object *obj) { #define Z_CURL_SHARE_P(zv) curl_share_from_obj(Z_OBJ_P(zv)) void curl_multi_register_handlers(void); +void curl_share_free_persistent(zval *data); void curl_share_register_handlers(void); +void curl_share_free_obj(zend_object *object); void curlfile_register_class(void); zend_result curl_cast_object(zend_object *obj, zval *result, int type); diff --git a/ext/curl/interface.c b/ext/curl/interface.c index 16b7c6618bc6e..b41a459b83088 100644 --- a/ext/curl/interface.c +++ b/ext/curl/interface.c @@ -59,6 +59,8 @@ #include "ext/standard/url.h" #include "curl_private.h" +ZEND_DECLARE_MODULE_GLOBALS(curl) + #ifdef __GNUC__ /* don't complain about deprecated CURLOPT_* we're exposing to PHP; we need to keep using those to avoid breaking PHP API compatibility */ @@ -215,7 +217,11 @@ zend_module_entry curl_module_entry = { NULL, PHP_MINFO(curl), PHP_CURL_VERSION, - STANDARD_MODULE_PROPERTIES + PHP_MODULE_GLOBALS(curl), + PHP_GINIT(curl), + PHP_GSHUTDOWN(curl), + NULL, + STANDARD_MODULE_PROPERTIES_EX }; /* }}} */ @@ -416,6 +422,17 @@ PHP_MINIT_FUNCTION(curl) } /* }}} */ +PHP_GINIT_FUNCTION(curl) +{ + zend_hash_init(&curl_globals->persistent_share_handles, 0, NULL, curl_share_free_persistent, true); + GC_MAKE_PERSISTENT_LOCAL(&curl_globals->persistent_share_handles); +} + +PHP_GSHUTDOWN_FUNCTION(curl) +{ + zend_hash_destroy(&curl_globals->persistent_share_handles); +} + /* CurlHandle class */ static zend_object *curl_create_object(zend_class_entry *class_type) { @@ -728,8 +745,8 @@ static int curl_prereqfunction(void *clientp, char *conn_primary_ip, char *conn_ // 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; - } + return rval; + } #if PHP_CURL_DEBUG fprintf(stderr, "curl_prereqfunction() called\n"); diff --git a/ext/curl/share.c b/ext/curl/share.c index 406982d203275..0a05c44dc4287 100644 --- a/ext/curl/share.c +++ b/ext/curl/share.c @@ -21,6 +21,7 @@ #endif #include "php.h" +#include "Zend/zend_exceptions.h" #include "curl_private.h" @@ -28,30 +29,118 @@ #define SAVE_CURLSH_ERROR(__handle, __err) (__handle)->err.no = (int) __err; -/* {{{ Initialize a share curl handle */ +/** + * Free a persistent curl share handle. + */ +void curl_share_free_persistent(zval *data) +{ + CURLSH *handle = Z_PTR_P(data); + + if (!handle) { + return; + } + + curl_share_cleanup(handle); +} + +/** + * Initialize a share curl handle, optionally with share options and a persistent ID. + */ PHP_FUNCTION(curl_share_init) { + zval *share_opts = NULL, *entry = NULL; + zend_string *persistent_id = NULL; + php_curlsh *sh; - ZEND_PARSE_PARAMETERS_NONE(); + CURLSHcode error; + + ZEND_PARSE_PARAMETERS_START(0, 2) + Z_PARAM_OPTIONAL + Z_PARAM_ARRAY(share_opts) + Z_PARAM_STR_OR_NULL(persistent_id) + ZEND_PARSE_PARAMETERS_END(); object_init_ex(return_value, curl_share_ce); sh = Z_CURL_SHARE_P(return_value); + if (persistent_id) { + zval *persisted = zend_hash_find(&CURL_G(persistent_share_handles), persistent_id); + + if (persisted) { + sh->share = Z_PTR_P(persisted); + sh->persistent_id = zend_string_copy(persistent_id); + + return; + } + } + + // The user did not provide a persistent share ID, or we could not find an existing share handle, so we'll have to + // create one. sh->share = curl_share_init(); + + if (share_opts) { + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(share_opts), entry) { + ZVAL_DEREF(entry); + + error = curl_share_setopt(sh->share, CURLSHOPT_SHARE, zval_get_long_ex(entry, true)); + + if (error != CURLSHE_OK) { + zend_throw_exception_ex( + NULL, + 0, + "Could not construct cURL share handle: %s", + curl_share_strerror(error) + ); + + goto error; + } + } ZEND_HASH_FOREACH_END(); + } + + if (persistent_id) { + // It's important not to mark this as persistent until *after* we've successfully set all of the share options, + // as otherwise the curl_share_free_obj in the error handler won't free the CURLSH. + sh->persistent_id = zend_string_copy(persistent_id); + + // We use the zend_hash_str form here, since we want to ensure it allocates a new persistent string, instead of + // using the request-bound persistent_id. + zend_hash_str_add_new_ptr( + &CURL_G(persistent_share_handles), + ZSTR_VAL(persistent_id), + ZSTR_LEN(persistent_id), + sh->share + ); + } + + return; + + error: + zval_ptr_dtor(return_value); + + RETURN_THROWS(); } -/* }}} */ -/* {{{ Close a set of cURL handles */ +/** + * Close a persistent curl share handle. NOP for non-persistent share handles. + */ PHP_FUNCTION(curl_share_close) { zval *z_sh; + php_curlsh *sh; + ZEND_PARSE_PARAMETERS_START(1,1) Z_PARAM_OBJECT_OF_CLASS(z_sh, curl_share_ce) ZEND_PARSE_PARAMETERS_END(); + + sh = Z_CURL_SHARE_P(z_sh); + + if (sh->persistent_id) { + // Deleting the share from the hash table will trigger curl_share_free_persistent, so no need to call it here. + zend_hash_del(&CURL_G(persistent_share_handles), sh->persistent_id); + } } -/* }}} */ static bool _php_curl_share_setopt(php_curlsh *sh, zend_long option, zval *zvalue, zval *return_value) /* {{{ */ { @@ -73,13 +162,14 @@ static bool _php_curl_share_setopt(php_curlsh *sh, zend_long option, zval *zvalu return error == CURLSHE_OK; } -/* }}} */ -/* {{{ Set an option for a cURL transfer */ +/** + * Set an option for a cURL transfer. + */ PHP_FUNCTION(curl_share_setopt) { zval *z_sh, *zvalue; - zend_long options; + zend_long options; php_curlsh *sh; ZEND_PARSE_PARAMETERS_START(3,3) @@ -96,13 +186,14 @@ PHP_FUNCTION(curl_share_setopt) RETURN_FALSE; } } -/* }}} */ -/* {{{ Return an integer containing the last share curl error number */ +/** + * Return an integer containing the last share curl error number. + */ PHP_FUNCTION(curl_share_errno) { - zval *z_sh; - php_curlsh *sh; + zval *z_sh; + php_curlsh *sh; ZEND_PARSE_PARAMETERS_START(1,1) Z_PARAM_OBJECT_OF_CLASS(z_sh, curl_share_ce) @@ -112,10 +203,11 @@ PHP_FUNCTION(curl_share_errno) RETURN_LONG(sh->err.no); } -/* }}} */ -/* {{{ return string describing error code */ +/** + * Return a string describing the error code. + */ PHP_FUNCTION(curl_share_strerror) { zend_long code; @@ -132,7 +224,6 @@ PHP_FUNCTION(curl_share_strerror) RETURN_NULL(); } } -/* }}} */ /* CurlShareHandle class */ @@ -154,7 +245,12 @@ void curl_share_free_obj(zend_object *object) { php_curlsh *sh = curl_share_from_obj(object); - curl_share_cleanup(sh->share); + if (!sh->persistent_id) { + curl_share_cleanup(sh->share); + } else { + zend_string_release(sh->persistent_id); + } + zend_object_std_dtor(&sh->std); } diff --git a/ext/curl/tests/curl_share_persistent.phpt b/ext/curl/tests/curl_share_persistent.phpt new file mode 100644 index 0000000000000..cdf41fe739d81 --- /dev/null +++ b/ext/curl/tests/curl_share_persistent.phpt @@ -0,0 +1,44 @@ +--TEST-- +Basic curl_share test +--EXTENSIONS-- +curl +--SKIPIF-- + +--FILE-- + +--EXPECT-- +string(23) "Caddy is up and running" +string(23) "Caddy is up and running" +string(22) "second connect_time: 0" diff --git a/ext/curl/tests/skipif-nocaddy.inc b/ext/curl/tests/skipif-nocaddy.inc index 21a06c12af106..ae5442ff28ede 100644 --- a/ext/curl/tests/skipif-nocaddy.inc +++ b/ext/curl/tests/skipif-nocaddy.inc @@ -2,6 +2,7 @@ $ch = curl_init("https://localhost"); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); +curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); $body = curl_exec($ch);