Skip to content

Commit dc8d432

Browse files
committed
RFC: Add CurlSharePersistentHandle objects (php#16937)
see https://wiki.php.net/rfc/curl_share_persistence_improvement
1 parent 4f91af3 commit dc8d432

14 files changed

+410
-10
lines changed

Zend/Optimizer/zend_func_infos.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ static const func_info_t func_infos[] = {
4646
F1("curl_multi_strerror", MAY_BE_STRING|MAY_BE_NULL),
4747
F1("curl_share_init", MAY_BE_OBJECT),
4848
F1("curl_share_strerror", MAY_BE_STRING|MAY_BE_NULL),
49+
F1("curl_share_init_persistent", MAY_BE_OBJECT),
4950
F1("curl_strerror", MAY_BE_STRING|MAY_BE_NULL),
5051
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),
5152
F1("date", MAY_BE_STRING),

ext/curl/curl.stub.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3668,6 +3668,15 @@ final class CurlShareHandle
36683668
{
36693669
}
36703670

3671+
/**
3672+
* @strict-properties
3673+
* @not-serializable
3674+
*/
3675+
final class CurlSharePersistentHandle
3676+
{
3677+
public readonly array $options;
3678+
}
3679+
36713680
function curl_close(CurlHandle $handle): void {}
36723681

36733682
/** @refcount 1 */
@@ -3748,6 +3757,9 @@ function curl_share_setopt(CurlShareHandle $share_handle, int $option, mixed $va
37483757
/** @refcount 1 */
37493758
function curl_share_strerror(int $error_code): ?string {}
37503759

3760+
/** @refcount 1 */
3761+
function curl_share_init_persistent(array $share_options): CurlSharePersistentHandle {}
3762+
37513763
/** @refcount 1 */
37523764
function curl_strerror(int $error_code): ?string {}
37533765

ext/curl/curl_arginfo.h

Lines changed: 23 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ext/curl/curl_private.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,20 @@
4040
#define SAVE_CURL_ERROR(__handle, __err) \
4141
do { (__handle)->err.no = (int) __err; } while (0)
4242

43+
44+
ZEND_BEGIN_MODULE_GLOBALS(curl)
45+
HashTable persistent_curlsh;
46+
ZEND_END_MODULE_GLOBALS(curl)
47+
48+
ZEND_EXTERN_MODULE_GLOBALS(curl)
49+
50+
#define CURL_G(v) ZEND_MODULE_GLOBALS_ACCESSOR(curl, v)
51+
4352
PHP_MINIT_FUNCTION(curl);
4453
PHP_MSHUTDOWN_FUNCTION(curl);
4554
PHP_MINFO_FUNCTION(curl);
55+
PHP_GINIT_FUNCTION(curl);
56+
PHP_GSHUTDOWN_FUNCTION(curl);
4657

4758
typedef struct {
4859
zend_fcall_info_cache fcc;
@@ -153,6 +164,8 @@ static inline php_curlsh *curl_share_from_obj(zend_object *obj) {
153164

154165
void curl_multi_register_handlers(void);
155166
void curl_share_register_handlers(void);
167+
void curl_share_persistent_register_handlers(void);
168+
void curl_share_free_persistent_curlsh(zval *data);
156169
void curlfile_register_class(void);
157170
zend_result curl_cast_object(zend_object *obj, zval *result, int type);
158171

ext/curl/interface.c

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@
6767

6868
#include "curl_arginfo.h"
6969

70+
ZEND_DECLARE_MODULE_GLOBALS(curl)
71+
7072
#ifdef PHP_CURL_NEED_OPENSSL_TSL /* {{{ */
7173
static MUTEX_T *php_curl_openssl_tsl = NULL;
7274

@@ -215,18 +217,34 @@ zend_module_entry curl_module_entry = {
215217
NULL,
216218
PHP_MINFO(curl),
217219
PHP_CURL_VERSION,
218-
STANDARD_MODULE_PROPERTIES
220+
PHP_MODULE_GLOBALS(curl),
221+
PHP_GINIT(curl),
222+
PHP_GSHUTDOWN(curl),
223+
NULL,
224+
STANDARD_MODULE_PROPERTIES_EX
219225
};
220226
/* }}} */
221227

222228
#ifdef COMPILE_DL_CURL
223229
ZEND_GET_MODULE (curl)
224230
#endif
225231

232+
PHP_GINIT_FUNCTION(curl)
233+
{
234+
zend_hash_init(&curl_globals->persistent_curlsh, 0, NULL, curl_share_free_persistent_curlsh, true);
235+
GC_MAKE_PERSISTENT_LOCAL(&curl_globals->persistent_curlsh);
236+
}
237+
238+
PHP_GSHUTDOWN_FUNCTION(curl)
239+
{
240+
zend_hash_destroy(&curl_globals->persistent_curlsh);
241+
}
242+
226243
/* CurlHandle class */
227244

228245
zend_class_entry *curl_ce;
229246
zend_class_entry *curl_share_ce;
247+
zend_class_entry *curl_share_persistent_ce;
230248
static zend_object_handlers curl_object_handlers;
231249

232250
static zend_object *curl_create_object(zend_class_entry *class_type);
@@ -410,6 +428,10 @@ PHP_MINIT_FUNCTION(curl)
410428

411429
curl_share_ce = register_class_CurlShareHandle();
412430
curl_share_register_handlers();
431+
432+
curl_share_persistent_ce = register_class_CurlSharePersistentHandle();
433+
curl_share_persistent_register_handlers();
434+
413435
curlfile_register_class();
414436

415437
return SUCCESS;
@@ -2283,16 +2305,24 @@ static zend_result _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue
22832305

22842306
case CURLOPT_SHARE:
22852307
{
2286-
if (Z_TYPE_P(zvalue) == IS_OBJECT && Z_OBJCE_P(zvalue) == curl_share_ce) {
2287-
php_curlsh *sh = Z_CURL_SHARE_P(zvalue);
2288-
curl_easy_setopt(ch->cp, CURLOPT_SHARE, sh->share);
2308+
if (Z_TYPE_P(zvalue) != IS_OBJECT) {
2309+
break;
2310+
}
22892311

2290-
if (ch->share) {
2291-
OBJ_RELEASE(&ch->share->std);
2292-
}
2293-
GC_ADDREF(&sh->std);
2294-
ch->share = sh;
2312+
if (Z_OBJCE_P(zvalue) != curl_share_ce && Z_OBJCE_P(zvalue) != curl_share_persistent_ce) {
2313+
break;
22952314
}
2315+
2316+
php_curlsh *sh = Z_CURL_SHARE_P(zvalue);
2317+
2318+
curl_easy_setopt(ch->cp, CURLOPT_SHARE, sh->share);
2319+
2320+
if (ch->share) {
2321+
OBJ_RELEASE(&ch->share->std);
2322+
}
2323+
2324+
GC_ADDREF(&sh->std);
2325+
ch->share = sh;
22962326
}
22972327
break;
22982328

ext/curl/php_curl.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ extern zend_module_entry curl_module_entry;
3737

3838
PHP_CURL_API extern zend_class_entry *curl_ce;
3939
PHP_CURL_API extern zend_class_entry *curl_share_ce;
40+
PHP_CURL_API extern zend_class_entry *curl_share_persistent_ce;
4041
PHP_CURL_API extern zend_class_entry *curl_multi_ce;
4142
PHP_CURL_API extern zend_class_entry *curl_CURLFile_class;
4243
PHP_CURL_API extern zend_class_entry *curl_CURLStringFile_class;

ext/curl/share.c

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#endif
2222

2323
#include "php.h"
24+
#include "Zend/zend_exceptions.h"
2425

2526
#include "curl_private.h"
2627

@@ -134,6 +135,151 @@ PHP_FUNCTION(curl_share_strerror)
134135
}
135136
/* }}} */
136137

138+
/**
139+
* Initialize a persistent curl share handle with the given share options, reusing an existing one if found.
140+
*
141+
* Throws an exception if the share options are invalid.
142+
*/
143+
PHP_FUNCTION(curl_share_init_persistent)
144+
{
145+
// Options specified by the user.
146+
HashTable *share_opts = NULL;
147+
148+
// De-duplicated and sorted options.
149+
zval share_opts_prop;
150+
ZVAL_UNDEF(&share_opts_prop);
151+
152+
// An ID representing the unique set of options specified by the user.
153+
zend_ulong persistent_id = 0;
154+
155+
php_curlsh *sh = NULL;
156+
157+
ZEND_PARSE_PARAMETERS_START(1, 1)
158+
Z_PARAM_ARRAY_HT(share_opts)
159+
ZEND_PARSE_PARAMETERS_END();
160+
161+
if (zend_hash_num_elements(share_opts) == 0) {
162+
zend_argument_must_not_be_empty_error(1);
163+
goto error;
164+
}
165+
166+
ZEND_HASH_FOREACH_VAL(share_opts, zval *entry) {
167+
ZVAL_DEREF(entry);
168+
169+
bool failed = false;
170+
zend_ulong option = zval_try_get_long(entry, &failed);
171+
172+
if (failed) {
173+
zend_argument_type_error(1, "must contain only int values, %s given", zend_zval_value_name(entry));
174+
goto error;
175+
}
176+
177+
switch (option) {
178+
// Disallowed options
179+
case CURL_LOCK_DATA_COOKIE:
180+
zend_argument_value_error(1, "must not contain CURL_LOCK_DATA_COOKIE because sharing cookies across PHP requests is unsafe");
181+
goto error;
182+
183+
// Allowed options
184+
case CURL_LOCK_DATA_DNS:
185+
persistent_id |= 1 << 0;
186+
break;
187+
case CURL_LOCK_DATA_SSL_SESSION:
188+
persistent_id |= 1 << 1;
189+
break;
190+
case CURL_LOCK_DATA_CONNECT:
191+
persistent_id |= 1 << 2;
192+
break;
193+
case CURL_LOCK_DATA_PSL:
194+
persistent_id |= 1 << 3;
195+
break;
196+
197+
// Unknown options
198+
default:
199+
zend_argument_value_error(1, "must contain only CURL_LOCK_DATA_* constants");
200+
goto error;
201+
}
202+
} ZEND_HASH_FOREACH_END();
203+
204+
// We're now decently confident that we'll be returning a CurlSharePersistentHandle object, so we construct it here.
205+
object_init_ex(return_value, curl_share_persistent_ce);
206+
207+
// Next we initialize a property field for the CurlSharePersistentHandle object with the enabled share options.
208+
array_init(&share_opts_prop);
209+
210+
if (persistent_id & (1 << 0)) {
211+
add_next_index_long(&share_opts_prop, CURL_LOCK_DATA_DNS);
212+
}
213+
214+
if (persistent_id & (1 << 1)) {
215+
add_next_index_long(&share_opts_prop, CURL_LOCK_DATA_SSL_SESSION);
216+
}
217+
218+
if (persistent_id & (1 << 2)) {
219+
add_next_index_long(&share_opts_prop, CURL_LOCK_DATA_CONNECT);
220+
}
221+
222+
if (persistent_id & (1 << 3)) {
223+
add_next_index_long(&share_opts_prop, CURL_LOCK_DATA_PSL);
224+
}
225+
226+
zend_update_property(curl_share_persistent_ce, Z_OBJ_P(return_value), ZEND_STRL("options"), &share_opts_prop);
227+
228+
sh = Z_CURL_SHARE_P(return_value);
229+
230+
// If we are able to find an existing persistent share handle, we can use it and return early.
231+
zval *persisted = zend_hash_index_find(&CURL_G(persistent_curlsh), persistent_id);
232+
233+
if (persisted) {
234+
sh->share = Z_PTR_P(persisted);
235+
236+
goto cleanup;
237+
}
238+
239+
// We could not find an existing share handle, so we'll have to create one.
240+
sh->share = curl_share_init();
241+
242+
// Apply the options property to the handle. We avoid using $share_opts as zval_get_long may not necessarily return
243+
// the same value as it did in the initial ZEND_HASH_FOREACH_VAL above.
244+
ZEND_HASH_PACKED_FOREACH_VAL(Z_ARRVAL_P(&share_opts_prop), zval *entry) {
245+
CURLSHcode curlsh_error = curl_share_setopt(sh->share, CURLSHOPT_SHARE, Z_LVAL_P(entry));
246+
247+
if (curlsh_error != CURLSHE_OK) {
248+
zend_throw_exception_ex(NULL, 0, "Could not construct persistent cURL share handle: %s", curl_share_strerror(curlsh_error));
249+
250+
goto error;
251+
}
252+
} ZEND_HASH_FOREACH_END();
253+
254+
zend_hash_index_add_new_ptr(&CURL_G(persistent_curlsh), persistent_id, sh->share);
255+
256+
cleanup:
257+
zval_ptr_dtor(&share_opts_prop);
258+
259+
return;
260+
261+
error:
262+
if (sh) {
263+
curl_share_cleanup(sh->share);
264+
}
265+
266+
zval_ptr_dtor(&share_opts_prop);
267+
268+
RETURN_THROWS();
269+
}
270+
271+
/**
272+
* Free a persistent curl share handle from the module global HashTable.
273+
*
274+
* See PHP_GINIT_FUNCTION in ext/curl/interface.c.
275+
*/
276+
void curl_share_free_persistent_curlsh(zval *data)
277+
{
278+
CURLSH *handle = Z_PTR_P(data);
279+
280+
curl_share_cleanup(handle);
281+
}
282+
137283
/* CurlShareHandle class */
138284

139285
static zend_object *curl_share_create_object(zend_class_entry *class_type) {
@@ -171,3 +317,23 @@ void curl_share_register_handlers(void) {
171317
curl_share_handlers.clone_obj = NULL;
172318
curl_share_handlers.compare = zend_objects_not_comparable;
173319
}
320+
321+
/* CurlSharePersistentHandle class */
322+
323+
static zend_object_handlers curl_share_persistent_handlers;
324+
325+
static zend_function *curl_share_persistent_get_constructor(zend_object *object) {
326+
zend_throw_error(NULL, "Cannot directly construct CurlSharePersistentHandle, use curl_share_init_persistent() instead");
327+
return NULL;
328+
}
329+
330+
void curl_share_persistent_register_handlers(void) {
331+
curl_share_persistent_ce->create_object = curl_share_create_object;
332+
curl_share_persistent_ce->default_object_handlers = &curl_share_persistent_handlers;
333+
334+
memcpy(&curl_share_persistent_handlers, &std_object_handlers, sizeof(zend_object_handlers));
335+
curl_share_persistent_handlers.offset = XtOffsetOf(php_curlsh, std);
336+
curl_share_persistent_handlers.get_constructor = curl_share_persistent_get_constructor;
337+
curl_share_persistent_handlers.clone_obj = NULL;
338+
curl_share_persistent_handlers.compare = zend_objects_not_comparable;
339+
}

0 commit comments

Comments
 (0)