Skip to content

RFC: Add CurlSharePersistentHandle objects #16937

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Jan 6, 2025
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Zend/Optimizer/zend_func_infos.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ static const func_info_t func_infos[] = {
F1("curl_share_init", MAY_BE_OBJECT),
F1("curl_share_strerror", MAY_BE_STRING|MAY_BE_NULL),
F1("curl_strerror", MAY_BE_STRING|MAY_BE_NULL),
F1("curl_persistent_share_init", MAY_BE_OBJECT),
F1("curl_version", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_LONG|MAY_BE_ARRAY_OF_STRING|MAY_BE_ARRAY_OF_ARRAY|MAY_BE_FALSE),
F1("date", MAY_BE_STRING),
F1("gmdate", MAY_BE_STRING),
Expand Down
12 changes: 12 additions & 0 deletions ext/curl/curl.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -3668,6 +3668,15 @@ final class CurlShareHandle
{
}

/**
* @strict-properties
* @not-serializable
*/
final class CurlPersistentShareHandle
{
public readonly array $options;
}

function curl_close(CurlHandle $handle): void {}

/** @refcount 1 */
Expand Down Expand Up @@ -3753,6 +3762,9 @@ function curl_share_strerror(int $error_code): ?string {}
/** @refcount 1 */
function curl_strerror(int $error_code): ?string {}

/** @refcount 1 */
function curl_persistent_share_init(array $share_options): CurlPersistentShareHandle {}

/**
* @return array<string, int|string|array>|false
* @refcount 1
Expand Down
24 changes: 23 additions & 1 deletion ext/curl/curl_arginfo.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 17 additions & 2 deletions ext/curl/curl_private.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,20 @@
#define SAVE_CURL_ERROR(__handle, __err) \
do { (__handle)->err.no = (int) __err; } while (0)


ZEND_BEGIN_MODULE_GLOBALS(curl)
HashTable persistent_curlsh;
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;
Expand Down Expand Up @@ -125,11 +136,13 @@ typedef struct {
} php_curlm;

typedef struct _php_curlsh {
CURLSH *share;
CURLSH *share;

struct {
int no;
} err;
zend_object std;

zend_object std;
} php_curlsh;

php_curl *init_curl_handle_into_zval(zval *curl);
Expand All @@ -153,6 +166,8 @@ static inline php_curlsh *curl_share_from_obj(zend_object *obj) {

void curl_multi_register_handlers(void);
void curl_share_register_handlers(void);
void curl_share_free_persistent_curlsh(zval *data);
void curl_persistent_share_register_handlers(void);
void curlfile_register_class(void);
zend_result curl_cast_object(zend_object *obj, zval *result, int type);

Expand Down
48 changes: 39 additions & 9 deletions ext/curl/interface.c
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@

#include "curl_arginfo.h"

ZEND_DECLARE_MODULE_GLOBALS(curl)

#ifdef PHP_CURL_NEED_OPENSSL_TSL /* {{{ */
static MUTEX_T *php_curl_openssl_tsl = NULL;

Expand Down Expand Up @@ -215,18 +217,34 @@ 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
};
/* }}} */

#ifdef COMPILE_DL_CURL
ZEND_GET_MODULE (curl)
#endif

PHP_GINIT_FUNCTION(curl)
{
zend_hash_init(&curl_globals->persistent_curlsh, 0, NULL, curl_share_free_persistent_curlsh, true);
GC_MAKE_PERSISTENT_LOCAL(&curl_globals->persistent_curlsh);
}

PHP_GSHUTDOWN_FUNCTION(curl)
{
zend_hash_destroy(&curl_globals->persistent_curlsh);
}

/* CurlHandle class */

zend_class_entry *curl_ce;
zend_class_entry *curl_share_ce;
zend_class_entry *curl_persistent_share_ce;
static zend_object_handlers curl_object_handlers;

static zend_object *curl_create_object(zend_class_entry *class_type);
Expand Down Expand Up @@ -410,6 +428,10 @@ PHP_MINIT_FUNCTION(curl)

curl_share_ce = register_class_CurlShareHandle();
curl_share_register_handlers();

curl_persistent_share_ce = register_class_CurlPersistentShareHandle();
curl_persistent_share_register_handlers();

curlfile_register_class();

return SUCCESS;
Expand Down Expand Up @@ -2276,16 +2298,24 @@ static zend_result _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue

case CURLOPT_SHARE:
{
if (Z_TYPE_P(zvalue) == IS_OBJECT && Z_OBJCE_P(zvalue) == curl_share_ce) {
php_curlsh *sh = Z_CURL_SHARE_P(zvalue);
curl_easy_setopt(ch->cp, CURLOPT_SHARE, sh->share);
if (Z_TYPE_P(zvalue) != IS_OBJECT) {
break;
}

if (ch->share) {
OBJ_RELEASE(&ch->share->std);
}
GC_ADDREF(&sh->std);
ch->share = sh;
if (Z_OBJCE_P(zvalue) != curl_share_ce && Z_OBJCE_P(zvalue) != curl_persistent_share_ce) {
break;
}

php_curlsh *sh = Z_CURL_SHARE_P(zvalue);

curl_easy_setopt(ch->cp, CURLOPT_SHARE, sh->share);

if (ch->share) {
OBJ_RELEASE(&ch->share->std);
}

GC_ADDREF(&sh->std);
ch->share = sh;
}
break;

Expand Down
1 change: 1 addition & 0 deletions ext/curl/php_curl.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ extern zend_module_entry curl_module_entry;

PHP_CURL_API extern zend_class_entry *curl_ce;
PHP_CURL_API extern zend_class_entry *curl_share_ce;
PHP_CURL_API extern zend_class_entry *curl_persistent_share_ce;
PHP_CURL_API extern zend_class_entry *curl_multi_ce;
PHP_CURL_API extern zend_class_entry *curl_CURLFile_class;
PHP_CURL_API extern zend_class_entry *curl_CURLStringFile_class;
Expand Down
128 changes: 128 additions & 0 deletions ext/curl/share.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
#endif

#include "php.h"
#include "ext/standard/php_array.h"
#include "Zend/zend_exceptions.h"

#include "curl_private.h"

Expand Down Expand Up @@ -134,6 +136,112 @@ PHP_FUNCTION(curl_share_strerror)
}
/* }}} */

/**
* Initialize a persistent curl share handle with the given share options, reusing an existing one if found.
*
* Throws an exception if the share options are invalid.
*/
PHP_FUNCTION(curl_persistent_share_init)
{
zval *share_opts = NULL, *entry = NULL;
zend_ulong persistent_id = 0;

php_curlsh *sh;

CURLSHcode error;

ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_ARRAY_EX(share_opts, 0, 1)
ZEND_PARSE_PARAMETERS_END();

object_init_ex(return_value, curl_persistent_share_ce);
sh = Z_CURL_SHARE_P(return_value);

ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(share_opts), entry) {
ZVAL_DEREF(entry);

zend_ulong option = zval_get_long_ex(entry, true);

if (option == CURL_LOCK_DATA_COOKIE) {
zend_throw_exception_ex(
NULL,
0,
"CURL_LOCK_DATA_COOKIE is not allowed with persistent curl share handles"
);

goto error;
}

// Ensure that each additional option results in a unique persistent ID.
persistent_id += 1 << option;
} ZEND_HASH_FOREACH_END();

zend_array_sort(Z_ARRVAL_P(share_opts), php_array_data_compare_unstable_i, 1);
zend_update_property(curl_persistent_share_ce, Z_OBJ_P(return_value), "options", sizeof("options") - 1, share_opts);

if (persistent_id) {
zval *persisted = zend_hash_index_find(&CURL_G(persistent_curlsh), persistent_id);

if (persisted) {
sh->share = Z_PTR_P(persisted);

return;
}
}

// We could not find an existing share handle, so we'll have to create one.
sh->share = curl_share_init();

// Apply $share_options to the handle.
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(share_opts), entry) {
ZVAL_DEREF(entry);

error = curl_share_setopt(sh->share, CURLSHOPT_SHARE, zval_get_long(entry));

if (error != CURLSHE_OK) {
zend_throw_exception_ex(
NULL,
0,
"Could not construct persistent cURL share handle: %s",
curl_share_strerror(error)
);

goto error;
}
} ZEND_HASH_FOREACH_END();

zend_hash_index_add_new_ptr(
&CURL_G(persistent_curlsh),
persistent_id,
sh->share
);

return;

error:
if (sh->share) {
curl_share_cleanup(sh->share);
}

RETURN_THROWS();
}

/**
* Free a persistent curl share handle from the module global HashTable.
*
* See PHP_GINIT_FUNCTION in ext/curl/interface.c.
*/
void curl_share_free_persistent_curlsh(zval *data)
{
CURLSH *handle = Z_PTR_P(data);

if (!handle) {
return;
}

curl_share_cleanup(handle);
}

/* CurlShareHandle class */

static zend_object *curl_share_create_object(zend_class_entry *class_type) {
Expand Down Expand Up @@ -171,3 +279,23 @@ void curl_share_register_handlers(void) {
curl_share_handlers.clone_obj = NULL;
curl_share_handlers.compare = zend_objects_not_comparable;
}

/* CurlPersistentShareHandle class */

static zend_function *curl_persistent_share_get_constructor(zend_object *object) {
zend_throw_error(NULL, "Cannot directly construct CurlPersistentShareHandle, use curl_persistent_share_init() instead");
return NULL;
}

static zend_object_handlers curl_persistent_share_handlers;

void curl_persistent_share_register_handlers(void) {
curl_persistent_share_ce->create_object = curl_share_create_object;
curl_persistent_share_ce->default_object_handlers = &curl_persistent_share_handlers;

memcpy(&curl_persistent_share_handlers, &std_object_handlers, sizeof(zend_object_handlers));
curl_persistent_share_handlers.offset = XtOffsetOf(php_curlsh, std);
curl_persistent_share_handlers.get_constructor = curl_persistent_share_get_constructor;
curl_persistent_share_handlers.clone_obj = NULL;
curl_persistent_share_handlers.compare = zend_objects_not_comparable;
}
Loading