Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ PHP NEWS
- Curl:
. curl_multi_exec() now throws a TypeError when still_running is not an int.
(arshidkv12)
. Uploading multiple files with the same field name is now possible by
passing an array of CURLFile or CURLStringFile objects to
CURLOPT_POSTFIELDS. (Sjoerd Langkemper)

- FPM:
. Use recommended TYPE, HELP order for OpenMetrics metadata.
Expand Down
6 changes: 6 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ PHP 8.7 UPGRADE NOTES

- Curl:
. curl_multi_exec() now throws a TypeError when still_running is not an int.
. Nested arrays in CURLOPT_POSTFIELDS now throw a TypeError, instead of being
silently converted to the string "Array".

- PDO:
. PDOStatement::bindColumn() now throws a ValueError when the column name is
Expand All @@ -36,6 +38,10 @@ PHP 8.7 UPGRADE NOTES
2. New Features
========================================

- Curl:
. Multiple files can now be uploaded with the same field name by passing an
array of CURLFile or CURLStringFile objects to CURLOPT_POSTFIELDS.

- Intl:
. Locale::acceptFromHttp() and locale_accept_from_http() now accept an
optional $availableLocales array argument. When given, the
Expand Down
272 changes: 148 additions & 124 deletions ext/curl/interface.c
Original file line number Diff line number Diff line change
Expand Up @@ -1413,173 +1413,198 @@ static inline CURLcode add_simple_field(curl_mime *mime, zend_string *string_key
return error;
}

static inline zend_result build_mime_structure_from_hash(php_curl *ch, zval *zpostfields) /* {{{ */
static inline CURLcode add_form_field(php_curl *ch, curl_mime *mime, zend_string *string_key, zval *current, zval *zpostfields)
{
HashTable *postfields = Z_ARRVAL_P(zpostfields);
zend_string *postval;
CURLcode error = CURLE_OK;
zval *current;
zend_string *string_key;
zend_ulong num_key;
curl_mime *mime = NULL;
curl_mimepart *part;
CURLcode form_error;
curl_mimepart *part;

if (zend_hash_num_elements(postfields) > 0) {
mime = curl_mime_init(ch->cp);
if (mime == NULL) {
return FAILURE;
}
ZVAL_DEREF(current);

if (Z_TYPE_P(current) == IS_ARRAY) {
zend_type_error("%s(): Nested arrays are not supported for key %s", get_active_function_name(), ZSTR_VAL(string_key));
return CURLE_BAD_FUNCTION_ARGUMENT;
}

ZEND_HASH_FOREACH_KEY_VAL(postfields, num_key, string_key, current) {
zend_string *postval;
/* Pretend we have a string_key here */
if (!string_key) {
string_key = zend_long_to_str(num_key);
if (Z_TYPE_P(current) == IS_OBJECT &&
instanceof_function(Z_OBJCE_P(current), curl_CURLFile_class)) {
/* new-style file upload */
zval *prop, rv;
char *type = NULL, *filename = NULL;
struct mime_data_cb_arg *cb_arg;
php_stream_statbuf ssb;
size_t filesize = -1;
curl_seek_callback seekfunc = seek_cb;

prop = zend_read_property_ex(curl_CURLFile_class, Z_OBJ_P(current), ZSTR_KNOWN(ZEND_STR_NAME), /* silent */ false, &rv);
ZVAL_DEREF(prop);
if (Z_TYPE_P(prop) != IS_STRING) {
php_error_docref(NULL, E_WARNING, "Invalid filename for key %s", ZSTR_VAL(string_key));
} else {
zend_string_addref(string_key);
}
postval = Z_STR_P(prop);

ZVAL_DEREF(current);
if (Z_TYPE_P(current) == IS_OBJECT &&
instanceof_function(Z_OBJCE_P(current), curl_CURLFile_class)) {
/* new-style file upload */
zval *prop, rv;
char *type = NULL, *filename = NULL;
struct mime_data_cb_arg *cb_arg;
php_stream_statbuf ssb;
size_t filesize = -1;
curl_seek_callback seekfunc = seek_cb;

prop = zend_read_property_ex(curl_CURLFile_class, Z_OBJ_P(current), ZSTR_KNOWN(ZEND_STR_NAME), /* silent */ false, &rv);
if (php_check_open_basedir(ZSTR_VAL(postval))) {
return CURLE_ABORTED_BY_CALLBACK;
}

prop = zend_read_property(curl_CURLFile_class, Z_OBJ_P(current), "mime", sizeof("mime")-1, 0, &rv);
ZVAL_DEREF(prop);
if (Z_TYPE_P(prop) != IS_STRING) {
php_error_docref(NULL, E_WARNING, "Invalid filename for key %s", ZSTR_VAL(string_key));
} else {
postval = Z_STR_P(prop);
if (Z_TYPE_P(prop) == IS_STRING && Z_STRLEN_P(prop) > 0) {
type = Z_STRVAL_P(prop);
}
prop = zend_read_property(curl_CURLFile_class, Z_OBJ_P(current), "postname", sizeof("postname")-1, 0, &rv);
ZVAL_DEREF(prop);
if (Z_TYPE_P(prop) == IS_STRING && Z_STRLEN_P(prop) > 0) {
filename = Z_STRVAL_P(prop);
}

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

prop = zend_read_property(curl_CURLFile_class, Z_OBJ_P(current), "mime", sizeof("mime")-1, 0, &rv);
ZVAL_DEREF(prop);
if (Z_TYPE_P(prop) == IS_STRING && Z_STRLEN_P(prop) > 0) {
type = Z_STRVAL_P(prop);
php_stream *stream;
if ((stream = php_stream_open_wrapper(ZSTR_VAL(postval), "rb", STREAM_MUST_SEEK, NULL))) {
if (!stream->readfilters.head && !php_stream_stat(stream, &ssb)) {
filesize = ssb.sb.st_size;
}
prop = zend_read_property(curl_CURLFile_class, Z_OBJ_P(current), "postname", sizeof("postname")-1, 0, &rv);
ZVAL_DEREF(prop);
if (Z_TYPE_P(prop) == IS_STRING && Z_STRLEN_P(prop) > 0) {
filename = Z_STRVAL_P(prop);
} else {
seekfunc = NULL;
}

part = curl_mime_addpart(mime);
if (part == NULL) {
if (stream) {
php_stream_close(stream);
}
return CURLE_OUT_OF_MEMORY;
}

zval_ptr_dtor(&ch->postfields);
ZVAL_COPY(&ch->postfields, zpostfields);
cb_arg = emalloc(sizeof *cb_arg);
cb_arg->filename = zend_string_copy(postval);
cb_arg->stream = stream;

php_stream *stream;
if ((stream = php_stream_open_wrapper(ZSTR_VAL(postval), "rb", STREAM_MUST_SEEK, NULL))) {
if (!stream->readfilters.head && !php_stream_stat(stream, &ssb)) {
filesize = ssb.sb.st_size;
}
} else {
seekfunc = NULL;
}
if ((form_error = curl_mime_name(part, ZSTR_VAL(string_key))) != CURLE_OK
|| (form_error = curl_mime_data_cb(part, filesize, read_cb, seekfunc, free_cb, cb_arg)) != CURLE_OK
|| (form_error = curl_mime_filename(part, filename ? filename : ZSTR_VAL(postval))) != CURLE_OK
|| (form_error = curl_mime_type(part, type ? type : "application/octet-stream")) != CURLE_OK) {
error = form_error;
}
zend_llist_add_element(&ch->to_free->stream, &cb_arg);
}

part = curl_mime_addpart(mime);
if (part == NULL) {
if (stream) {
php_stream_close(stream);
}
goto out_string;
}
return error;
}

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

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

zend_string_release_ex(string_key, 0);
continue;
filename = Z_STRVAL_P(prop);

prop = zend_read_property(curl_CURLStringFile_class, Z_OBJ_P(current), "mime", sizeof("mime")-1, 0, &rv);
if (EG(exception)) {
return CURLE_BAD_FUNCTION_ARGUMENT;
}
ZVAL_DEREF(prop);
ZEND_ASSERT(Z_TYPE_P(prop) == IS_STRING);

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

prop = zend_read_property(curl_CURLStringFile_class, Z_OBJ_P(current), "postname", sizeof("postname")-1, 0, &rv);
if (EG(exception)) {
goto out_string;
}
ZVAL_DEREF(prop);
ZEND_ASSERT(Z_TYPE_P(prop) == IS_STRING);
prop = zend_read_property(curl_CURLStringFile_class, Z_OBJ_P(current), "data", sizeof("data")-1, 0, &rv);
if (EG(exception)) {
return CURLE_BAD_FUNCTION_ARGUMENT;
}
ZVAL_DEREF(prop);
ZEND_ASSERT(Z_TYPE_P(prop) == IS_STRING);

filename = Z_STRVAL_P(prop);
postval = Z_STR_P(prop);

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

type = Z_STRVAL_P(prop);
part = curl_mime_addpart(mime);
if (part == NULL) {
return CURLE_OUT_OF_MEMORY;
}
if ((form_error = curl_mime_name(part, ZSTR_VAL(string_key))) != CURLE_OK
|| (form_error = curl_mime_data(part, ZSTR_VAL(postval), ZSTR_LEN(postval))) != CURLE_OK
|| (form_error = curl_mime_filename(part, filename)) != CURLE_OK
|| (form_error = curl_mime_type(part, type)) != CURLE_OK) {
error = form_error;
}

prop = zend_read_property(curl_CURLStringFile_class, Z_OBJ_P(current), "data", sizeof("data")-1, 0, &rv);
if (EG(exception)) {
goto out_string;
}
ZVAL_DEREF(prop);
ZEND_ASSERT(Z_TYPE_P(prop) == IS_STRING);
return error;
}

postval = Z_STR_P(prop);
return add_simple_field(mime, string_key, current);
}

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

part = curl_mime_addpart(mime);
if (part == NULL) {
goto out_string;
}
if ((form_error = curl_mime_name(part, ZSTR_VAL(string_key))) != CURLE_OK
|| (form_error = curl_mime_data(part, ZSTR_VAL(postval), ZSTR_LEN(postval))) != CURLE_OK
|| (form_error = curl_mime_filename(part, filename)) != CURLE_OK
|| (form_error = curl_mime_type(part, type)) != CURLE_OK) {
error = form_error;
}
static inline zend_result build_mime_structure_from_hash(php_curl *ch, zval *zpostfields) /* {{{ */
{
HashTable *postfields = Z_ARRVAL_P(zpostfields);
CURLcode error = CURLE_OK;
zval *current;
zend_string *string_key;
zend_ulong num_key;
curl_mime *mime = NULL;

zend_string_release_ex(string_key, 0);
continue;
if (zend_hash_num_elements(postfields) > 0) {
mime = curl_mime_init(ch->cp);
if (mime == NULL) {
SAVE_CURL_ERROR(ch, CURLE_OUT_OF_MEMORY);
return FAILURE;
}
}

ZEND_HASH_FOREACH_KEY_VAL(postfields, num_key, string_key, current) {
/* Pretend we have a string_key here */
if (!string_key) {
string_key = zend_long_to_str(num_key);
} else {
zend_string_addref(string_key);
}

ZVAL_DEREF(current);

if (Z_TYPE_P(current) == IS_ARRAY) {
zval *current_element;

ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(current), current_element) {
add_simple_field(mime, string_key, current_element);
ZVAL_DEREF(current_element);

error = add_form_field(
ch, mime, string_key, current_element, zpostfields
);

if (error != CURLE_OK) {
zend_string_release_ex(string_key, 0);
goto out_mime;
Comment thread
arnaud-lb marked this conversation as resolved.
}
} ZEND_HASH_FOREACH_END();

zend_string_release_ex(string_key, 0);
continue;
}

add_simple_field(mime, string_key, current);
error = add_form_field(
ch, mime, string_key, current, zpostfields
);

zend_string_release_ex(string_key, 0);
} ZEND_HASH_FOREACH_END();

SAVE_CURL_ERROR(ch, error);
if (error != CURLE_OK) {
goto out_mime;
}
if (error != CURLE_OK) {
goto out_mime;
}
} ZEND_HASH_FOREACH_END();

if ((*ch->clone) == 1) {
zend_llist_clean(&ch->to_free->post);
Expand All @@ -1590,9 +1615,8 @@ static inline zend_result build_mime_structure_from_hash(php_curl *ch, zval *zpo
SAVE_CURL_ERROR(ch, error);
return error == CURLE_OK ? SUCCESS : FAILURE;

out_string:
zend_string_release_ex(string_key, false);
out_mime:
SAVE_CURL_ERROR(ch, error);
curl_mime_free(mime);
return FAILURE;
}
Expand Down
37 changes: 37 additions & 0 deletions ext/curl/tests/curl_postfields_error.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
--TEST--
CURLOPT_POSTFIELDS with nested arrays
--EXTENSIONS--
curl
--FILE--
<?php

$fields = [
'single' => 'SingleValue',
'multi' => [
['array', 'invalid' => 'value'],
'Multi2',
]
];

$options = [
CURLOPT_POST => 1,
CURLOPT_HEADER => 0,
CURLOPT_FRESH_CONNECT => 1,
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_FORBID_REUSE => 1,
CURLOPT_TIMEOUT => 1,
CURLOPT_POSTFIELDS => $fields,
CURLOPT_HTTPHEADER => [ 'Expect:' ],
];

try {
$ch = curl_init();
curl_setopt_array($ch, $options);
} catch (TypeError $e) {
echo "TypeError: {$e->getMessage()}\n";
}
var_dump(curl_errno($ch) === CURLE_BAD_FUNCTION_ARGUMENT);
?>
--EXPECT--
TypeError: curl_setopt_array(): Nested arrays are not supported for key multi
bool(true)
Loading
Loading