Skip to content

Commit aece997

Browse files
committed
Post multiple similarly-named files with curl
Make it possible to post multiple files with the same name using cURL. It was already possible to post files, and it was possible to post multiple strings with the same name. This moves things around to combine these two features. Closes #15859
1 parent 29cec7f commit aece997

3 files changed

Lines changed: 287 additions & 120 deletions

File tree

ext/curl/interface.c

Lines changed: 146 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -1361,167 +1361,195 @@ static inline CURLcode add_simple_field(curl_mime *mime, zend_string *string_key
13611361
return error;
13621362
}
13631363

1364-
static inline zend_result build_mime_structure_from_hash(php_curl *ch, zval *zpostfields) /* {{{ */
1365-
{
1366-
HashTable *postfields = Z_ARRVAL_P(zpostfields);
1364+
static inline CURLcode add_form_field(php_curl *ch, curl_mime *mime, zend_string *string_key, zval *current, zval *zpostfields) {
1365+
zend_string *postval;
13671366
CURLcode error = CURLE_OK;
1368-
zval *current;
1369-
zend_string *string_key;
1370-
zend_ulong num_key;
1371-
curl_mime *mime = NULL;
1372-
curl_mimepart *part;
13731367
CURLcode form_error;
1368+
curl_mimepart *part;
13741369

1375-
if (zend_hash_num_elements(postfields) > 0) {
1376-
mime = curl_mime_init(ch->cp);
1377-
if (mime == NULL) {
1378-
return FAILURE;
1379-
}
1370+
ZVAL_DEREF(current);
1371+
1372+
if (Z_TYPE_P(current) == IS_ARRAY) {
1373+
zend_type_error("%s(): Nested arrays are not supported for key %s", get_active_function_name(), ZSTR_VAL(string_key));
1374+
return CURLE_BAD_FUNCTION_ARGUMENT;
13801375
}
13811376

1382-
ZEND_HASH_FOREACH_KEY_VAL(postfields, num_key, string_key, current) {
1383-
zend_string *postval;
1384-
/* Pretend we have a string_key here */
1385-
if (!string_key) {
1386-
string_key = zend_long_to_str(num_key);
1377+
if (Z_TYPE_P(current) == IS_OBJECT &&
1378+
instanceof_function(Z_OBJCE_P(current), curl_CURLFile_class)) {
1379+
/* new-style file upload */
1380+
zval *prop, rv;
1381+
char *type = NULL, *filename = NULL;
1382+
struct mime_data_cb_arg *cb_arg;
1383+
php_stream_statbuf ssb;
1384+
size_t filesize = -1;
1385+
curl_seek_callback seekfunc = seek_cb;
1386+
1387+
prop = zend_read_property_ex(curl_CURLFile_class, Z_OBJ_P(current), ZSTR_KNOWN(ZEND_STR_NAME), /* silent */ false, &rv);
1388+
ZVAL_DEREF(prop);
1389+
if (Z_TYPE_P(prop) != IS_STRING) {
1390+
php_error_docref(NULL, E_WARNING, "Invalid filename for key %s", ZSTR_VAL(string_key));
13871391
} else {
1388-
zend_string_addref(string_key);
1389-
}
1392+
postval = Z_STR_P(prop);
13901393

1391-
ZVAL_DEREF(current);
1392-
if (Z_TYPE_P(current) == IS_OBJECT &&
1393-
instanceof_function(Z_OBJCE_P(current), curl_CURLFile_class)) {
1394-
/* new-style file upload */
1395-
zval *prop, rv;
1396-
char *type = NULL, *filename = NULL;
1397-
struct mime_data_cb_arg *cb_arg;
1398-
php_stream_statbuf ssb;
1399-
size_t filesize = -1;
1400-
curl_seek_callback seekfunc = seek_cb;
1401-
1402-
prop = zend_read_property_ex(curl_CURLFile_class, Z_OBJ_P(current), ZSTR_KNOWN(ZEND_STR_NAME), /* silent */ false, &rv);
1394+
if (php_check_open_basedir(ZSTR_VAL(postval))) {
1395+
return CURLE_ABORTED_BY_CALLBACK;
1396+
}
1397+
1398+
prop = zend_read_property(curl_CURLFile_class, Z_OBJ_P(current), "mime", sizeof("mime")-1, 0, &rv);
14031399
ZVAL_DEREF(prop);
1404-
if (Z_TYPE_P(prop) != IS_STRING) {
1405-
php_error_docref(NULL, E_WARNING, "Invalid filename for key %s", ZSTR_VAL(string_key));
1406-
} else {
1407-
postval = Z_STR_P(prop);
1400+
if (Z_TYPE_P(prop) == IS_STRING && Z_STRLEN_P(prop) > 0) {
1401+
type = Z_STRVAL_P(prop);
1402+
}
1403+
prop = zend_read_property(curl_CURLFile_class, Z_OBJ_P(current), "postname", sizeof("postname")-1, 0, &rv);
1404+
ZVAL_DEREF(prop);
1405+
if (Z_TYPE_P(prop) == IS_STRING && Z_STRLEN_P(prop) > 0) {
1406+
filename = Z_STRVAL_P(prop);
1407+
}
14081408

1409-
if (php_check_open_basedir(ZSTR_VAL(postval))) {
1410-
goto out_string;
1411-
}
1409+
zval_ptr_dtor(&ch->postfields);
1410+
ZVAL_COPY(&ch->postfields, zpostfields);
14121411

1413-
prop = zend_read_property(curl_CURLFile_class, Z_OBJ_P(current), "mime", sizeof("mime")-1, 0, &rv);
1414-
ZVAL_DEREF(prop);
1415-
if (Z_TYPE_P(prop) == IS_STRING && Z_STRLEN_P(prop) > 0) {
1416-
type = Z_STRVAL_P(prop);
1412+
php_stream *stream;
1413+
if ((stream = php_stream_open_wrapper(ZSTR_VAL(postval), "rb", STREAM_MUST_SEEK, NULL))) {
1414+
if (!stream->readfilters.head && !php_stream_stat(stream, &ssb)) {
1415+
filesize = ssb.sb.st_size;
14171416
}
1418-
prop = zend_read_property(curl_CURLFile_class, Z_OBJ_P(current), "postname", sizeof("postname")-1, 0, &rv);
1419-
ZVAL_DEREF(prop);
1420-
if (Z_TYPE_P(prop) == IS_STRING && Z_STRLEN_P(prop) > 0) {
1421-
filename = Z_STRVAL_P(prop);
1417+
} else {
1418+
seekfunc = NULL;
1419+
}
1420+
1421+
part = curl_mime_addpart(mime);
1422+
if (part == NULL) {
1423+
if (stream) {
1424+
php_stream_close(stream);
14221425
}
1426+
return CURLE_OUT_OF_MEMORY;
1427+
}
14231428

1424-
zval_ptr_dtor(&ch->postfields);
1425-
ZVAL_COPY(&ch->postfields, zpostfields);
1429+
cb_arg = emalloc(sizeof *cb_arg);
1430+
cb_arg->filename = zend_string_copy(postval);
1431+
cb_arg->stream = stream;
14261432

1427-
php_stream *stream;
1428-
if ((stream = php_stream_open_wrapper(ZSTR_VAL(postval), "rb", STREAM_MUST_SEEK, NULL))) {
1429-
if (!stream->readfilters.head && !php_stream_stat(stream, &ssb)) {
1430-
filesize = ssb.sb.st_size;
1431-
}
1432-
} else {
1433-
seekfunc = NULL;
1434-
}
1433+
if ((form_error = curl_mime_name(part, ZSTR_VAL(string_key))) != CURLE_OK
1434+
|| (form_error = curl_mime_data_cb(part, filesize, read_cb, seekfunc, free_cb, cb_arg)) != CURLE_OK
1435+
|| (form_error = curl_mime_filename(part, filename ? filename : ZSTR_VAL(postval))) != CURLE_OK
1436+
|| (form_error = curl_mime_type(part, type ? type : "application/octet-stream")) != CURLE_OK) {
1437+
error = form_error;
1438+
}
1439+
zend_llist_add_element(&ch->to_free->stream, &cb_arg);
1440+
}
14351441

1436-
part = curl_mime_addpart(mime);
1437-
if (part == NULL) {
1438-
if (stream) {
1439-
php_stream_close(stream);
1440-
}
1441-
goto out_string;
1442-
}
1442+
return error;
1443+
}
14431444

1444-
cb_arg = emalloc(sizeof *cb_arg);
1445-
cb_arg->filename = zend_string_copy(postval);
1446-
cb_arg->stream = stream;
1445+
if (Z_TYPE_P(current) == IS_OBJECT && instanceof_function(Z_OBJCE_P(current), curl_CURLStringFile_class)) {
1446+
/* new-style file upload from string */
1447+
zval *prop, rv;
1448+
char *type = NULL, *filename = NULL;
14471449

1448-
if ((form_error = curl_mime_name(part, ZSTR_VAL(string_key))) != CURLE_OK
1449-
|| (form_error = curl_mime_data_cb(part, filesize, read_cb, seekfunc, free_cb, cb_arg)) != CURLE_OK
1450-
|| (form_error = curl_mime_filename(part, filename ? filename : ZSTR_VAL(postval))) != CURLE_OK
1451-
|| (form_error = curl_mime_type(part, type ? type : "application/octet-stream")) != CURLE_OK) {
1452-
error = form_error;
1453-
}
1454-
zend_llist_add_element(&ch->to_free->stream, &cb_arg);
1455-
}
1450+
prop = zend_read_property(curl_CURLStringFile_class, Z_OBJ_P(current), "postname", sizeof("postname")-1, 0, &rv);
1451+
if (EG(exception)) {
1452+
return CURLE_BAD_FUNCTION_ARGUMENT;
1453+
}
1454+
ZVAL_DEREF(prop);
1455+
ZEND_ASSERT(Z_TYPE_P(prop) == IS_STRING);
14561456

1457-
zend_string_release_ex(string_key, 0);
1458-
continue;
1457+
filename = Z_STRVAL_P(prop);
1458+
1459+
prop = zend_read_property(curl_CURLStringFile_class, Z_OBJ_P(current), "mime", sizeof("mime")-1, 0, &rv);
1460+
if (EG(exception)) {
1461+
return CURLE_BAD_FUNCTION_ARGUMENT;
14591462
}
1463+
ZVAL_DEREF(prop);
1464+
ZEND_ASSERT(Z_TYPE_P(prop) == IS_STRING);
14601465

1461-
if (Z_TYPE_P(current) == IS_OBJECT && instanceof_function(Z_OBJCE_P(current), curl_CURLStringFile_class)) {
1462-
/* new-style file upload from string */
1463-
zval *prop, rv;
1464-
char *type = NULL, *filename = NULL;
1466+
type = Z_STRVAL_P(prop);
14651467

1466-
prop = zend_read_property(curl_CURLStringFile_class, Z_OBJ_P(current), "postname", sizeof("postname")-1, 0, &rv);
1467-
if (EG(exception)) {
1468-
goto out_string;
1469-
}
1470-
ZVAL_DEREF(prop);
1471-
ZEND_ASSERT(Z_TYPE_P(prop) == IS_STRING);
1468+
prop = zend_read_property(curl_CURLStringFile_class, Z_OBJ_P(current), "data", sizeof("data")-1, 0, &rv);
1469+
if (EG(exception)) {
1470+
return CURLE_BAD_FUNCTION_ARGUMENT;
1471+
}
1472+
ZVAL_DEREF(prop);
1473+
ZEND_ASSERT(Z_TYPE_P(prop) == IS_STRING);
14721474

1473-
filename = Z_STRVAL_P(prop);
1475+
postval = Z_STR_P(prop);
14741476

1475-
prop = zend_read_property(curl_CURLStringFile_class, Z_OBJ_P(current), "mime", sizeof("mime")-1, 0, &rv);
1476-
if (EG(exception)) {
1477-
goto out_string;
1478-
}
1479-
ZVAL_DEREF(prop);
1480-
ZEND_ASSERT(Z_TYPE_P(prop) == IS_STRING);
1477+
zval_ptr_dtor(&ch->postfields);
1478+
ZVAL_COPY(&ch->postfields, zpostfields);
14811479

1482-
type = Z_STRVAL_P(prop);
1480+
part = curl_mime_addpart(mime);
1481+
if (part == NULL) {
1482+
return CURLE_OUT_OF_MEMORY;
1483+
}
1484+
if ((form_error = curl_mime_name(part, ZSTR_VAL(string_key))) != CURLE_OK
1485+
|| (form_error = curl_mime_data(part, ZSTR_VAL(postval), ZSTR_LEN(postval))) != CURLE_OK
1486+
|| (form_error = curl_mime_filename(part, filename)) != CURLE_OK
1487+
|| (form_error = curl_mime_type(part, type)) != CURLE_OK) {
1488+
error = form_error;
1489+
}
14831490

1484-
prop = zend_read_property(curl_CURLStringFile_class, Z_OBJ_P(current), "data", sizeof("data")-1, 0, &rv);
1485-
if (EG(exception)) {
1486-
goto out_string;
1487-
}
1488-
ZVAL_DEREF(prop);
1489-
ZEND_ASSERT(Z_TYPE_P(prop) == IS_STRING);
1491+
return error;
1492+
}
14901493

1491-
postval = Z_STR_P(prop);
1494+
return add_simple_field(mime, string_key, current);
1495+
}
14921496

1493-
zval_ptr_dtor(&ch->postfields);
1494-
ZVAL_COPY(&ch->postfields, zpostfields);
14951497

1496-
part = curl_mime_addpart(mime);
1497-
if (part == NULL) {
1498-
goto out_string;
1499-
}
1500-
if ((form_error = curl_mime_name(part, ZSTR_VAL(string_key))) != CURLE_OK
1501-
|| (form_error = curl_mime_data(part, ZSTR_VAL(postval), ZSTR_LEN(postval))) != CURLE_OK
1502-
|| (form_error = curl_mime_filename(part, filename)) != CURLE_OK
1503-
|| (form_error = curl_mime_type(part, type)) != CURLE_OK) {
1504-
error = form_error;
1505-
}
1498+
static inline zend_result build_mime_structure_from_hash(php_curl *ch, zval *zpostfields) /* {{{ */
1499+
{
1500+
HashTable *postfields = Z_ARRVAL_P(zpostfields);
1501+
CURLcode error = CURLE_OK;
1502+
zval *current;
1503+
zend_string *string_key;
1504+
zend_ulong num_key;
1505+
curl_mime *mime = NULL;
15061506

1507-
zend_string_release_ex(string_key, 0);
1508-
continue;
1507+
if (zend_hash_num_elements(postfields) > 0) {
1508+
mime = curl_mime_init(ch->cp);
1509+
if (mime == NULL) {
1510+
return FAILURE;
1511+
}
1512+
}
1513+
1514+
ZEND_HASH_FOREACH_KEY_VAL(postfields, num_key, string_key, current) {
1515+
/* Pretend we have a string_key here */
1516+
if (!string_key) {
1517+
string_key = zend_long_to_str(num_key);
1518+
} else {
1519+
zend_string_addref(string_key);
15091520
}
15101521

1522+
ZVAL_DEREF(current);
1523+
15111524
if (Z_TYPE_P(current) == IS_ARRAY) {
15121525
zval *current_element;
15131526

15141527
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(current), current_element) {
1515-
add_simple_field(mime, string_key, current_element);
1528+
ZVAL_DEREF(current_element);
1529+
1530+
error = add_form_field(
1531+
ch, mime, string_key, current_element, zpostfields
1532+
);
1533+
1534+
if (error != CURLE_OK) {
1535+
zend_string_release_ex(string_key, 0);
1536+
goto out_mime;
1537+
}
15161538
} ZEND_HASH_FOREACH_END();
15171539

15181540
zend_string_release_ex(string_key, 0);
15191541
continue;
15201542
}
15211543

1522-
add_simple_field(mime, string_key, current);
1544+
error = add_form_field(
1545+
ch, mime, string_key, current, zpostfields
1546+
);
15231547

15241548
zend_string_release_ex(string_key, 0);
1549+
1550+
if (error != CURLE_OK) {
1551+
goto out_mime;
1552+
}
15251553
} ZEND_HASH_FOREACH_END();
15261554

15271555
SAVE_CURL_ERROR(ch, error);
@@ -1538,8 +1566,6 @@ static inline zend_result build_mime_structure_from_hash(php_curl *ch, zval *zpo
15381566
SAVE_CURL_ERROR(ch, error);
15391567
return error == CURLE_OK ? SUCCESS : FAILURE;
15401568

1541-
out_string:
1542-
zend_string_release_ex(string_key, false);
15431569
out_mime:
15441570
curl_mime_free(mime);
15451571
return FAILURE;
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
--TEST--
2+
CURLOPT_POSTFIELDS with nested arrays
3+
--EXTENSIONS--
4+
curl
5+
sockets
6+
--FILE--
7+
<?php
8+
9+
$url = "http://127.0.0.1:29999/get.inc?test=raw";
10+
11+
$fields = [
12+
'single' => 'SingleValue',
13+
'multi' => [
14+
['array', 'invalid' => 'value'],
15+
'Multi2',
16+
]
17+
];
18+
19+
$options = [
20+
CURLOPT_POST => 1,
21+
CURLOPT_HEADER => 0,
22+
CURLOPT_URL => $url,
23+
CURLOPT_FRESH_CONNECT => 1,
24+
CURLOPT_RETURNTRANSFER => 1,
25+
CURLOPT_FORBID_REUSE => 1,
26+
CURLOPT_TIMEOUT => 1,
27+
CURLOPT_POSTFIELDS => $fields,
28+
CURLOPT_HTTPHEADER => [ 'Expect:' ],
29+
];
30+
31+
try {
32+
$ch = curl_init();
33+
curl_setopt_array($ch, $options);
34+
} catch (TypeError $e) {
35+
echo "TypeError: {$e->getMessage()}\n";
36+
}
37+
?>
38+
--EXPECTF--
39+
TypeError: curl_setopt_array(): Nested arrays are not supported for key multi

0 commit comments

Comments
 (0)