Skip to content

Commit 512ce49

Browse files
committed
Cater to duplicated handles used by curl_multi_*()
Since `curl_multi_perfom()` processes easy handles asynchronously, we have no control of the operation sequence. Since duplicated cURL handles may be used with multi handles, we cannot use a single arg structure, but actually have to rebuild the whole mime structure on handle duplication and attach this to the new handle. This requires to store the zval that has been originally provided for `CURLOPT_POSTFIELDS`. Ideally, we would store it in an additional member of `php_curl`, but since that would constitute a BC break, we work around by actually storing a union in `to_free->stream`, and we retrieve the appropriate zval by sequentially searching the list.
1 parent c067770 commit 512ce49

File tree

3 files changed

+282
-8
lines changed

3 files changed

+282
-8
lines changed

ext/curl/interface.c

Lines changed: 184 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1797,8 +1797,14 @@ static void curl_free_post(void **post)
17971797
/* }}} */
17981798

17991799
struct mime_file {
1800-
zend_string *name;
1801-
php_stream *stream;
1800+
php_curl *ch;
1801+
union {
1802+
zval postfields;
1803+
struct {
1804+
zend_string *name;
1805+
php_stream *stream;
1806+
};
1807+
};
18021808
};
18031809

18041810
/* {{{ curl_free_stream
@@ -1807,8 +1813,12 @@ static void curl_free_stream(void **post)
18071813
{
18081814
struct mime_file *mime_file = (struct mime_file *) *post;
18091815

1810-
ZEND_ASSERT(mime_file->stream == NULL);
1811-
zend_string_release(mime_file->name);
1816+
if (mime_file->ch) {
1817+
Z_DELREF(mime_file->postfields);
1818+
} else {
1819+
ZEND_ASSERT(mime_file->stream == NULL);
1820+
zend_string_release(mime_file->name);
1821+
}
18121822
efree(mime_file);
18131823
}
18141824
/* }}} */
@@ -2104,13 +2114,148 @@ void _php_setup_easy_copy_handlers(php_curl *ch, php_curl *source)
21042114
(*source->clone)++;
21052115
}
21062116

2117+
#if LIBCURL_VERSION_NUM >= 0x073800 /* 7.56.0 */
2118+
static size_t read_cb(char *buffer, size_t size, size_t nitems, void *arg);
2119+
static int seek_cb(void *arg, curl_off_t offset, int origin);
2120+
static void free_cb(void *arg);
2121+
2122+
static int rebuild_mime_structure(php_curl *ch, zval *zpostfields)
2123+
{
2124+
CURLcode error = CURLE_OK;
2125+
zval *current;
2126+
zend_string *string_key;
2127+
zend_ulong num_key;
2128+
curl_mime *mime = NULL;
2129+
curl_mimepart *part;
2130+
CURLcode form_error;
2131+
HashTable *postfields = Z_ARRVAL_P(zpostfields);
2132+
2133+
if (zend_hash_num_elements(postfields) > 0) {
2134+
mime = curl_mime_init(ch->cp);
2135+
if (mime == NULL) {
2136+
return FAILURE;
2137+
}
2138+
}
2139+
2140+
ZEND_HASH_FOREACH_KEY_VAL(postfields, num_key, string_key, current) {
2141+
zend_string *postval, *tmp_postval;
2142+
/* Pretend we have a string_key here */
2143+
if (!string_key) {
2144+
string_key = zend_long_to_str(num_key);
2145+
} else {
2146+
zend_string_addref(string_key);
2147+
}
2148+
2149+
ZVAL_DEREF(current);
2150+
if (Z_TYPE_P(current) == IS_OBJECT &&
2151+
instanceof_function(Z_OBJCE_P(current), curl_CURLFile_class)) {
2152+
/* new-style file upload */
2153+
zval *prop, rv;
2154+
char *type = NULL, *filename = NULL;
2155+
2156+
struct mime_file *mime_file;
2157+
2158+
2159+
prop = zend_read_property(curl_CURLFile_class, current, "name", sizeof("name")-1, 0, &rv);
2160+
if (Z_TYPE_P(prop) != IS_STRING) {
2161+
php_error_docref(NULL, E_WARNING, "Invalid filename for key %s", ZSTR_VAL(string_key));
2162+
} else {
2163+
postval = Z_STR_P(prop);
2164+
2165+
if (php_check_open_basedir(ZSTR_VAL(postval))) {
2166+
return 1;
2167+
}
2168+
2169+
prop = zend_read_property(curl_CURLFile_class, current, "mime", sizeof("mime")-1, 0, &rv);
2170+
if (Z_TYPE_P(prop) == IS_STRING && Z_STRLEN_P(prop) > 0) {
2171+
type = Z_STRVAL_P(prop);
2172+
}
2173+
prop = zend_read_property(curl_CURLFile_class, current, "postname", sizeof("postname")-1, 0, &rv);
2174+
if (Z_TYPE_P(prop) == IS_STRING && Z_STRLEN_P(prop) > 0) {
2175+
filename = Z_STRVAL_P(prop);
2176+
}
2177+
2178+
mime_file = emalloc(sizeof *mime_file);
2179+
mime_file->ch = ch;
2180+
ZVAL_COPY(&mime_file->postfields, zpostfields);
2181+
zend_llist_add_element(&ch->to_free->stream, &mime_file);
2182+
2183+
mime_file = emalloc(sizeof *mime_file);
2184+
mime_file->ch = NULL;
2185+
mime_file->name = zend_string_copy(postval);
2186+
mime_file->stream = NULL;
2187+
part = curl_mime_addpart(mime);
2188+
if (part == NULL) {
2189+
zend_string_release_ex(string_key, 0);
2190+
return FAILURE;
2191+
}
2192+
if ((form_error = curl_mime_name(part, ZSTR_VAL(string_key))) != CURLE_OK
2193+
|| (form_error = curl_mime_data_cb(part, -1, read_cb, seek_cb, free_cb, mime_file)) != CURLE_OK
2194+
|| (form_error = curl_mime_filename(part, filename ? filename : ZSTR_VAL(postval))) != CURLE_OK
2195+
|| (form_error = curl_mime_type(part, type ? type : "application/octet-stream")) != CURLE_OK) {
2196+
error = form_error;
2197+
}
2198+
zend_llist_add_element(&ch->to_free->stream, &mime_file);
2199+
}
2200+
2201+
zend_string_release_ex(string_key, 0);
2202+
continue;
2203+
}
2204+
2205+
postval = zval_get_tmp_string(current, &tmp_postval);
2206+
2207+
part = curl_mime_addpart(mime);
2208+
if (part == NULL) {
2209+
zend_tmp_string_release(tmp_postval);
2210+
zend_string_release_ex(string_key, 0);
2211+
return FAILURE;
2212+
}
2213+
if ((form_error = curl_mime_name(part, ZSTR_VAL(string_key))) != CURLE_OK
2214+
|| (form_error = curl_mime_data(part, ZSTR_VAL(postval), ZSTR_LEN(postval))) != CURLE_OK) {
2215+
error = form_error;
2216+
}
2217+
zend_tmp_string_release(tmp_postval);
2218+
zend_string_release_ex(string_key, 0);
2219+
} ZEND_HASH_FOREACH_END();
2220+
2221+
SAVE_CURL_ERROR(ch, error);
2222+
if (error != CURLE_OK) {
2223+
return FAILURE;
2224+
}
2225+
2226+
if ((*ch->clone) == 1) {
2227+
zend_llist_clean(&ch->to_free->post);
2228+
}
2229+
zend_llist_add_element(&ch->to_free->post, &mime);
2230+
error = curl_easy_setopt(ch->cp, CURLOPT_MIMEPOST, mime);
2231+
2232+
return SUCCESS;
2233+
}
2234+
2235+
static HashTable *get_postfields_of_handle(php_curl *ch)
2236+
{
2237+
struct mime_file *mime_file, **mfp;
2238+
2239+
mfp = zend_llist_get_last(&ch->to_free->stream);
2240+
while (mfp) {
2241+
mime_file = *mfp;
2242+
if (mime_file->ch == ch) {
2243+
return &mime_file->postfields;
2244+
}
2245+
mfp = zend_llist_get_prev(&ch->to_free->stream);
2246+
}
2247+
return NULL;
2248+
}
2249+
#endif
2250+
21072251
/* {{{ proto resource curl_copy_handle(resource ch)
21082252
Copy a cURL handle along with all of it's preferences */
21092253
PHP_FUNCTION(curl_copy_handle)
21102254
{
21112255
CURL *cp;
21122256
zval *zid;
21132257
php_curl *ch, *dupch;
2258+
zval *postfields;
21142259

21152260
ZEND_PARSE_PARAMETERS_START(1,1)
21162261
Z_PARAM_RESOURCE(zid)
@@ -2131,6 +2276,17 @@ PHP_FUNCTION(curl_copy_handle)
21312276

21322277
_php_setup_easy_copy_handlers(dupch, ch);
21332278

2279+
#if LIBCURL_VERSION_NUM >= 0x073800 /* 7.56.0 */
2280+
postfields = get_postfields_of_handle(ch);
2281+
if (postfields) {
2282+
if (rebuild_mime_structure(dupch, postfields) != SUCCESS) {
2283+
_php_curl_close_ex(dupch);
2284+
php_error_docref(NULL, E_WARNING, "Cannot rebuild mime structure");
2285+
RETURN_FALSE;
2286+
}
2287+
}
2288+
#endif
2289+
21342290
Z_ADDREF_P(zid);
21352291

21362292
ZVAL_RES(return_value, zend_register_resource(dupch, le_curl));
@@ -2144,13 +2300,15 @@ static size_t read_cb(char *buffer, size_t size, size_t nitems, void *arg) /* {{
21442300
struct mime_file *mime_file = (struct mime_file *) arg;
21452301
ssize_t numread;
21462302

2303+
ZEND_ASSERT(!mime_file->ch);
2304+
21472305
if (mime_file->stream == NULL) {
21482306
if (!(mime_file->stream = php_stream_open_wrapper(ZSTR_VAL(mime_file->name), "rb", IGNORE_PATH, NULL))) {
21492307
return CURL_READFUNC_ABORT;
21502308
}
21512309
}
21522310
numread = php_stream_read(mime_file->stream, buffer, nitems * size);
2153-
if (numread <= 0) {
2311+
if (numread < 0) {
21542312
php_stream_close(mime_file->stream);
21552313
mime_file->stream = NULL;
21562314
}
@@ -2163,17 +2321,28 @@ static int seek_cb(void *arg, curl_off_t offset, int origin) /* {{{ */
21632321
struct mime_file *mime_file = (struct mime_file *) arg;
21642322
int res;
21652323

2324+
ZEND_ASSERT(!mime_file->ch);
2325+
21662326
if (mime_file->stream == NULL) {
21672327
if (!(mime_file->stream = php_stream_open_wrapper(ZSTR_VAL(mime_file->name), "rb", IGNORE_PATH, NULL))) {
21682328
return CURL_SEEKFUNC_CANTSEEK;
21692329
}
21702330
}
21712331
res = php_stream_seek(mime_file->stream, offset, origin);
2172-
if (res) {
2332+
return !res ? CURL_SEEKFUNC_OK : CURL_SEEKFUNC_CANTSEEK;
2333+
}
2334+
/* }}} */
2335+
2336+
static void free_cb(void *arg) /* {{{ */
2337+
{
2338+
struct mime_file *mime_file = (struct mime_file *) arg;
2339+
2340+
ZEND_ASSERT(!mime_file->ch);
2341+
2342+
if (mime_file->stream != NULL) {
21732343
php_stream_close(mime_file->stream);
21742344
mime_file->stream = NULL;
21752345
}
2176-
return !res ? CURL_SEEKFUNC_OK : CURL_SEEKFUNC_CANTSEEK;
21772346
}
21782347
/* }}} */
21792348
#endif
@@ -2840,15 +3009,22 @@ static int _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue) /* {{{
28403009

28413010
#if LIBCURL_VERSION_NUM >= 0x073800 /* 7.56.0 */
28423011
mime_file = emalloc(sizeof *mime_file);
3012+
mime_file->ch = ch;
3013+
ZVAL_COPY(&mime_file->postfields, zvalue);
3014+
zend_llist_add_element(&ch->to_free->stream, &mime_file);
3015+
3016+
mime_file = emalloc(sizeof *mime_file);
3017+
mime_file->ch = NULL;
28433018
mime_file->name = zend_string_copy(postval);
28443019
mime_file->stream = NULL;
3020+
28453021
part = curl_mime_addpart(mime);
28463022
if (part == NULL) {
28473023
zend_string_release_ex(string_key, 0);
28483024
return FAILURE;
28493025
}
28503026
if ((form_error = curl_mime_name(part, ZSTR_VAL(string_key))) != CURLE_OK
2851-
|| (form_error = curl_mime_data_cb(part, -1, read_cb, seek_cb, NULL, mime_file)) != CURLE_OK
3027+
|| (form_error = curl_mime_data_cb(part, -1, read_cb, seek_cb, free_cb, mime_file)) != CURLE_OK
28523028
|| (form_error = curl_mime_filename(part, filename ? filename : ZSTR_VAL(postval))) != CURLE_OK
28533029
|| (form_error = curl_mime_type(part, type ? type : "application/octet-stream")) != CURLE_OK) {
28543030
error = form_error;
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
--TEST--
2+
curl_copy_handle() allows to post CURLFile multiple times with curl_multi_exec()
3+
--SKIPIF--
4+
<?php include 'skipif.inc'; ?>
5+
--FILE--
6+
<?php
7+
include 'server.inc';
8+
$host = curl_cli_server_start();
9+
10+
$ch1 = curl_init();
11+
curl_setopt($ch1, CURLOPT_SAFE_UPLOAD, 1);
12+
curl_setopt($ch1, CURLOPT_URL, "{$host}/get.php?test=file");
13+
// curl_setopt($ch1, CURLOPT_RETURNTRANSFER, 1);
14+
15+
$filename = __DIR__ . '/АБВ.txt';
16+
file_put_contents($filename, "Test.");
17+
$file = curl_file_create($filename);
18+
$params = array('file' => $file);
19+
var_dump(curl_setopt($ch1, CURLOPT_POSTFIELDS, $params));
20+
21+
$ch2 = curl_copy_handle($ch1);
22+
$ch3 = curl_copy_handle($ch1);
23+
24+
$mh = curl_multi_init();
25+
curl_multi_add_handle($mh, $ch1);
26+
curl_multi_add_handle($mh, $ch2);
27+
do {
28+
$status = curl_multi_exec($mh, $active);
29+
if ($active) {
30+
curl_multi_select($mh);
31+
}
32+
} while ($active && $status == CURLM_OK);
33+
34+
curl_multi_remove_handle($mh, $ch1);
35+
curl_multi_remove_handle($mh, $ch2);
36+
curl_multi_remove_handle($mh, $ch3);
37+
curl_multi_close($mh);
38+
?>
39+
===DONE===
40+
--EXPECTF--
41+
bool(true)
42+
АБВ.txt|application/octet-stream|5АБВ.txt|application/octet-stream|5===DONE===
43+
--CLEAN--
44+
<?php
45+
@unlink(__DIR__ . '/АБВ.txt');
46+
?>
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
--TEST--
2+
curl_copy_handle() allows to post CURLFile multiple times if postfields change
3+
--SKIPIF--
4+
<?php include 'skipif.inc'; ?>
5+
--FILE--
6+
<?php
7+
include 'server.inc';
8+
$host = curl_cli_server_start();
9+
10+
$ch1 = curl_init();
11+
curl_setopt($ch1, CURLOPT_SAFE_UPLOAD, 1);
12+
curl_setopt($ch1, CURLOPT_URL, "{$host}/get.php?test=file");
13+
curl_setopt($ch1, CURLOPT_RETURNTRANSFER, 1);
14+
15+
$filename = __DIR__ . '/abc.txt';
16+
file_put_contents($filename, "Test.");
17+
$file = curl_file_create($filename);
18+
$params = array('file' => $file);
19+
var_dump(curl_setopt($ch1, CURLOPT_POSTFIELDS, $params));
20+
21+
$ch2 = curl_copy_handle($ch1);
22+
23+
$filename = __DIR__ . '/def.txt';
24+
file_put_contents($filename, "Other test.");
25+
$file = curl_file_create($filename);
26+
$params = array('file' => $file);
27+
var_dump(curl_setopt($ch2, CURLOPT_POSTFIELDS, $params));
28+
29+
$ch3 = curl_copy_handle($ch2);
30+
31+
var_dump(curl_exec($ch1));
32+
curl_close($ch1);
33+
34+
var_dump(curl_exec($ch2));
35+
curl_close($ch2);
36+
37+
var_dump(curl_exec($ch3));
38+
curl_close($ch3);
39+
?>
40+
===DONE===
41+
--EXPECTF--
42+
bool(true)
43+
bool(true)
44+
string(%d) "abc.txt|application/octet-stream|5"
45+
string(%d) "def.txt|application/octet-stream|11"
46+
string(%d) "def.txt|application/octet-stream|11"
47+
===DONE===
48+
--CLEAN--
49+
<?php
50+
@unlink(__DIR__ . '/abc.txt');
51+
@unlink(__DIR__ . '/def.txt');
52+
?>

0 commit comments

Comments
 (0)