Skip to content

Commit d1ab12f

Browse files
committed
feat: add CurlPersistentShareHandle
This commit introduces a new function, curl_persistent_share_init, that creates a persistent curl share handle by storing it within a hash table in the curl module's global variables. Persisting a curl share handle allows PHP userspace to cache things like DNS lookups or connections between multiple PHP requests, thus reducing work for subsequent requests. The function takes an array of CURL_LOCK_DATA_* constants, which are used to either construct a new curl share handle with the given options, or to find an existing one with the same set of options. CURL_LOCK_DATA_COOKIE is not allowed here, however, as it is considered unsafe; a developer could accidentally share a user's cookies from an earlier request when handling a subsequent from a different user.
1 parent 6bf703a commit d1ab12f

File tree

7 files changed

+210
-12
lines changed

7 files changed

+210
-12
lines changed

Zend/Optimizer/zend_func_infos.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ static const func_info_t func_infos[] = {
4747
F1("curl_share_init", MAY_BE_OBJECT),
4848
F1("curl_share_strerror", MAY_BE_STRING|MAY_BE_NULL),
4949
F1("curl_strerror", MAY_BE_STRING|MAY_BE_NULL),
50+
F1("curl_persistent_share_init", MAY_BE_OBJECT),
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),
5253
F1("gmdate", MAY_BE_STRING),

ext/curl/curl.stub.php

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

3671+
/**
3672+
* @strict-properties
3673+
* @not-serializable
3674+
*/
3675+
final class CurlPersistentShareHandle
3676+
{
3677+
}
3678+
36713679
function curl_close(CurlHandle $handle): void {}
36723680

36733681
/** @refcount 1 */
@@ -3753,6 +3761,9 @@ function curl_share_strerror(int $error_code): ?string {}
37533761
/** @refcount 1 */
37543762
function curl_strerror(int $error_code): ?string {}
37553763

3764+
/** @refcount 1 */
3765+
function curl_persistent_share_init(array $share_options): CurlPersistentShareHandle {}
3766+
37563767
/**
37573768
* @return array<string, int|string|array>|false
37583769
* @refcount 1

ext/curl/curl_arginfo.h

Lines changed: 17 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: 17 additions & 2 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;
@@ -125,11 +136,13 @@ typedef struct {
125136
} php_curlm;
126137

127138
typedef struct _php_curlsh {
128-
CURLSH *share;
139+
CURLSH *share;
140+
129141
struct {
130142
int no;
131143
} err;
132-
zend_object std;
144+
145+
zend_object std;
133146
} php_curlsh;
134147

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

154167
void curl_multi_register_handlers(void);
155168
void curl_share_register_handlers(void);
169+
void curl_share_free_persistent_curlsh(zval *data);
170+
void curl_persistent_share_register_handlers(void);
156171
void curlfile_register_class(void);
157172
zend_result curl_cast_object(zend_object *obj, zval *result, int type);
158173

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_persistent_share_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_persistent_share_ce = register_class_CurlPersistentShareHandle();
433+
curl_persistent_share_register_handlers();
434+
413435
curlfile_register_class();
414436

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

22772299
case CURLOPT_SHARE:
22782300
{
2279-
if (Z_TYPE_P(zvalue) == IS_OBJECT && Z_OBJCE_P(zvalue) == curl_share_ce) {
2280-
php_curlsh *sh = Z_CURL_SHARE_P(zvalue);
2281-
curl_easy_setopt(ch->cp, CURLOPT_SHARE, sh->share);
2301+
if (Z_TYPE_P(zvalue) != IS_OBJECT) {
2302+
break;
2303+
}
22822304

2283-
if (ch->share) {
2284-
OBJ_RELEASE(&ch->share->std);
2285-
}
2286-
GC_ADDREF(&sh->std);
2287-
ch->share = sh;
2305+
if (Z_OBJCE_P(zvalue) != curl_share_ce && Z_OBJCE_P(zvalue) != curl_persistent_share_ce) {
2306+
break;
22882307
}
2308+
2309+
php_curlsh *sh = Z_CURL_SHARE_P(zvalue);
2310+
2311+
curl_easy_setopt(ch->cp, CURLOPT_SHARE, sh->share);
2312+
2313+
if (ch->share) {
2314+
OBJ_RELEASE(&ch->share->std);
2315+
}
2316+
2317+
GC_ADDREF(&sh->std);
2318+
ch->share = sh;
22892319
}
22902320
break;
22912321

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_persistent_share_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: 124 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,109 @@ 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_persistent_share_init)
144+
{
145+
zval *share_opts = NULL, *entry = NULL;
146+
zend_ulong persistent_id = 0;
147+
148+
php_curlsh *sh;
149+
150+
CURLSHcode error;
151+
152+
ZEND_PARSE_PARAMETERS_START(1, 1)
153+
Z_PARAM_ARRAY(share_opts)
154+
ZEND_PARSE_PARAMETERS_END();
155+
156+
object_init_ex(return_value, curl_persistent_share_ce);
157+
sh = Z_CURL_SHARE_P(return_value);
158+
159+
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(share_opts), entry) {
160+
ZVAL_DEREF(entry);
161+
162+
zend_ulong option = zval_get_long_ex(entry, true);
163+
164+
if (option == CURL_LOCK_DATA_COOKIE) {
165+
zend_throw_exception_ex(
166+
NULL,
167+
0,
168+
"CURL_LOCK_DATA_COOKIE is not allowed with persistent curl share handles"
169+
);
170+
171+
goto error;
172+
}
173+
174+
// Ensure that each additional option results in a unique persistent ID.
175+
persistent_id += 1 << option;
176+
} ZEND_HASH_FOREACH_END();
177+
178+
if (persistent_id) {
179+
zval *persisted = zend_hash_index_find(&CURL_G(persistent_curlsh), persistent_id);
180+
181+
if (persisted) {
182+
sh->share = Z_PTR_P(persisted);
183+
184+
return;
185+
}
186+
}
187+
188+
// We could not find an existing share handle, so we'll have to create one.
189+
sh->share = curl_share_init();
190+
191+
// Apply $share_options to the handle.
192+
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(share_opts), entry) {
193+
ZVAL_DEREF(entry);
194+
195+
error = curl_share_setopt(sh->share, CURLSHOPT_SHARE, zval_get_long(entry));
196+
197+
if (error != CURLSHE_OK) {
198+
zend_throw_exception_ex(
199+
NULL,
200+
0,
201+
"Could not construct persistent cURL share handle: %s",
202+
curl_share_strerror(error)
203+
);
204+
205+
goto error;
206+
}
207+
} ZEND_HASH_FOREACH_END();
208+
209+
zend_hash_index_add_new_ptr(
210+
&CURL_G(persistent_curlsh),
211+
persistent_id,
212+
sh->share
213+
);
214+
215+
return;
216+
217+
error:
218+
if (sh->share) {
219+
curl_share_cleanup(sh->share);
220+
}
221+
222+
RETURN_THROWS();
223+
}
224+
225+
/**
226+
* Free a persistent curl share handle from the module global HashTable.
227+
*
228+
* See PHP_GINIT_FUNCTION in ext/curl/interface.c.
229+
*/
230+
void curl_share_free_persistent_curlsh(zval *data)
231+
{
232+
CURLSH *handle = Z_PTR_P(data);
233+
234+
if (!handle) {
235+
return;
236+
}
237+
238+
curl_share_cleanup(handle);
239+
}
240+
137241
/* CurlShareHandle class */
138242

139243
static zend_object *curl_share_create_object(zend_class_entry *class_type) {
@@ -171,3 +275,23 @@ void curl_share_register_handlers(void) {
171275
curl_share_handlers.clone_obj = NULL;
172276
curl_share_handlers.compare = zend_objects_not_comparable;
173277
}
278+
279+
/* CurlPersistentShareHandle class */
280+
281+
static zend_function *curl_persistent_share_get_constructor(zend_object *object) {
282+
zend_throw_error(NULL, "Cannot directly construct CurlPersistentShareHandle, use curl_persistent_share_init() instead");
283+
return NULL;
284+
}
285+
286+
static zend_object_handlers curl_persistent_share_handlers;
287+
288+
void curl_persistent_share_register_handlers(void) {
289+
curl_persistent_share_ce->create_object = curl_share_create_object;
290+
curl_persistent_share_ce->default_object_handlers = &curl_persistent_share_handlers;
291+
292+
memcpy(&curl_persistent_share_handlers, &std_object_handlers, sizeof(zend_object_handlers));
293+
curl_persistent_share_handlers.offset = XtOffsetOf(php_curlsh, std);
294+
curl_persistent_share_handlers.get_constructor = curl_persistent_share_get_constructor;
295+
curl_persistent_share_handlers.clone_obj = NULL;
296+
curl_persistent_share_handlers.compare = zend_objects_not_comparable;
297+
}

0 commit comments

Comments
 (0)