Skip to content
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

RFC: Add CurlSharePersistentHandle objects #16937

Merged
merged 19 commits into from
Jan 6, 2025
Merged
Show file tree
Hide file tree
Changes from 11 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 @@ -46,6 +46,7 @@ static const func_info_t func_infos[] = {
F1("curl_multi_strerror", MAY_BE_STRING|MAY_BE_NULL),
F1("curl_share_init", MAY_BE_OBJECT),
F1("curl_share_strerror", MAY_BE_STRING|MAY_BE_NULL),
F1("curl_share_init_persistent", MAY_BE_OBJECT),
F1("curl_strerror", MAY_BE_STRING|MAY_BE_NULL),
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),
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 CurlSharePersistentHandle
{
public readonly array $options;
}

function curl_close(CurlHandle $handle): void {}

/** @refcount 1 */
Expand Down Expand Up @@ -3750,6 +3759,9 @@ function curl_share_setopt(CurlShareHandle $share_handle, int $option, mixed $va
/** @refcount 1 */
function curl_share_strerror(int $error_code): ?string {}

/** @refcount 1 */
function curl_share_init_persistent(array $share_options): CurlSharePersistentHandle {}

/** @refcount 1 */
function curl_strerror(int $error_code): ?string {}

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.

13 changes: 13 additions & 0 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 @@ -153,6 +164,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_persistent_register_handlers(void);
void curl_share_free_persistent_curlsh(zval *data);
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_share_persistent_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_share_persistent_ce = register_class_CurlSharePersistentHandle();
curl_share_persistent_register_handlers();

curlfile_register_class();

return SUCCESS;
Expand Down Expand Up @@ -2281,16 +2303,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_share_persistent_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_share_persistent_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
159 changes: 159 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"
Girgias marked this conversation as resolved.
Show resolved Hide resolved
#include "Zend/zend_exceptions.h"

#include "curl_private.h"

Expand Down Expand Up @@ -134,6 +136,143 @@ 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_share_init_persistent)
{
zval *share_opts = NULL, *entry = NULL;
zend_ulong persistent_id = 0;

php_curlsh *sh = NULL;

CURLSHcode error = 0;

ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_ARRAY(share_opts)
ericnorris marked this conversation as resolved.
Show resolved Hide resolved
ZEND_PARSE_PARAMETERS_END();

if (zend_hash_num_elements(Z_ARRVAL_P(share_opts)) == 0) {
zend_argument_value_error(1, "must not be empty");
Girgias marked this conversation as resolved.
Show resolved Hide resolved
goto error;
Girgias marked this conversation as resolved.
Show resolved Hide resolved
}

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

bool failed = false;
zend_ulong option = zval_try_get_long(entry, &failed);
Girgias marked this conversation as resolved.
Show resolved Hide resolved

if (failed) {
zend_argument_type_error(1, "must contain only integer values, %s given", zend_zval_value_name(entry));
Girgias marked this conversation as resolved.
Show resolved Hide resolved
goto error;
}

switch (option) {
// Disallowed options
case CURL_LOCK_DATA_COOKIE:
zend_argument_value_error(1, "must not contain CURL_LOCK_DATA_COOKIE because sharing cookies across PHP requests is unsafe");
goto error;

// Allowed options
case CURL_LOCK_DATA_DNS:
persistent_id |= 1 << 0;
break;
case CURL_LOCK_DATA_SSL_SESSION:
persistent_id |= 1 << 1;
break;
case CURL_LOCK_DATA_CONNECT:
persistent_id |= 1 << 2;
break;
case CURL_LOCK_DATA_PSL:
persistent_id |= 1 << 3;
break;

// Unknown options
default:
zend_argument_value_error(1, "must contain only CURL_LOCK_DATA_* constants");
goto error;
}
} ZEND_HASH_FOREACH_END();

// Construct a property field for the CurlSharePersistentHandle object with the enabled share options.
zval share_opts_prop;
array_init(&share_opts_prop);

if (persistent_id & (1 << 0)) {
add_next_index_long(&share_opts_prop, CURL_LOCK_DATA_DNS);
}

if (persistent_id & (1 << 1)) {
add_next_index_long(&share_opts_prop, CURL_LOCK_DATA_SSL_SESSION);
}

if (persistent_id & (1 << 2)) {
add_next_index_long(&share_opts_prop, CURL_LOCK_DATA_CONNECT);
}

if (persistent_id & (1 << 3)) {
add_next_index_long(&share_opts_prop, CURL_LOCK_DATA_PSL);
}

object_init_ex(return_value, curl_share_persistent_ce);

zend_update_property(curl_share_persistent_ce, Z_OBJ_P(return_value), ZEND_STRL("options"), &share_opts_prop);
zval_ptr_dtor(&share_opts_prop);

sh = Z_CURL_SHARE_P(return_value);

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

// If we were able to find an existing persistent share handle, we can use it and return early.
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) {
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);

curl_share_cleanup(handle);
}

/* CurlShareHandle class */

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

/* CurlSharePersistentHandle class */

static zend_object_handlers curl_share_persistent_handlers;

static zend_function *curl_share_persistent_get_constructor(zend_object *object) {
zend_throw_error(NULL, "Cannot directly construct CurlSharePersistentHandle, use curl_share_init_persistent() instead");
return NULL;
}

void curl_share_persistent_register_handlers(void) {
curl_share_persistent_ce->create_object = curl_share_create_object;
curl_share_persistent_ce->default_object_handlers = &curl_share_persistent_handlers;

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