diff --git a/ext/curl/interface.c b/ext/curl/interface.c index c0286069776e..92d9eecfe280 100644 --- a/ext/curl/interface.c +++ b/ext/curl/interface.c @@ -1361,167 +1361,195 @@ 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) /* {{{ */ -{ - HashTable *postfields = Z_ARRVAL_P(zpostfields); +static inline CURLcode add_form_field(php_curl *ch, curl_mime *mime, zend_string *string_key, zval *current, zval *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) { + 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; + } } 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); + + if (error != CURLE_OK) { + goto out_mime; + } } ZEND_HASH_FOREACH_END(); SAVE_CURL_ERROR(ch, error); @@ -1538,8 +1566,6 @@ 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: curl_mime_free(mime); return FAILURE; diff --git a/ext/curl/tests/curl_postfields_error.phpt b/ext/curl/tests/curl_postfields_error.phpt new file mode 100644 index 000000000000..c41bc111b5e7 --- /dev/null +++ b/ext/curl/tests/curl_postfields_error.phpt @@ -0,0 +1,39 @@ +--TEST-- +CURLOPT_POSTFIELDS with nested arrays +--EXTENSIONS-- +curl +sockets +--FILE-- + 'SingleValue', + 'multi' => [ + ['array', 'invalid' => 'value'], + 'Multi2', + ] +]; + +$options = [ + CURLOPT_POST => 1, + CURLOPT_HEADER => 0, + CURLOPT_URL => $url, + 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"; +} +?> +--EXPECTF-- +TypeError: curl_setopt_array(): Nested arrays are not supported for key multi diff --git a/ext/curl/tests/curl_postfiles_array.phpt b/ext/curl/tests/curl_postfiles_array.phpt new file mode 100644 index 000000000000..42eda68afa2c --- /dev/null +++ b/ext/curl/tests/curl_postfiles_array.phpt @@ -0,0 +1,102 @@ +--TEST-- +CURLOPT_POSTFIELDS with multi-value files +--EXTENSIONS-- +curl +sockets +--FILE-- +\n"; + return; +} + +$url = "http://127.0.0.1:29999/get.inc?test=raw"; + +$fields = [ + 'single' => 'SingleValue', + 'multi_string' => [ + new CURLStringFile('Multi1', 'multi1.txt', 'text/plain'), + new CURLStringFile('Multi2', 'multi2.dat'), + ], + 'multi_file' => [ + new CURLFile(__DIR__ . '/curl_testdata1.txt'), + new CURLFile(__DIR__ . '/curl_testdata2.txt'), + ], + 'mixed' => [ + 'string value', + new CURLStringFile('curl string file', 'stringfile.txt', 'text/plain'), + new CURLFile(__DIR__ . '/curl_testdata1.txt', 'text/plain'), + ] +]; + +$options = [ + CURLOPT_POST => 1, + CURLOPT_HEADER => 0, + CURLOPT_URL => $url, + CURLOPT_FRESH_CONNECT => 1, + CURLOPT_RETURNTRANSFER => 1, + CURLOPT_FORBID_REUSE => 1, + CURLOPT_TIMEOUT => 1, + CURLOPT_POSTFIELDS => $fields, + CURLOPT_HTTPHEADER => [ 'Expect:' ], +]; + +$ch = curl_init(); +curl_setopt_array($ch, $options); + +$curl_content = curl_exec($ch); + +$conn = stream_socket_accept($socket); +echo stream_get_contents($conn); +?> +--EXPECTF-- +POST /get.inc?test=raw HTTP/1.1 +Host: %s +Accept: */* +Content-Length: %d +Content-Type: multipart/form-data; boundary=------------------------%s + +--------------------------%s +Content-Disposition: form-data; name="single" + +SingleValue +--------------------------%s +Content-Disposition: form-data; name="multi_string"; filename="multi1.txt" +Content-Type: text/plain + +Multi1 +--------------------------%s +Content-Disposition: form-data; name="multi_string"; filename="multi2.dat" +Content-Type: application/octet-stream + +Multi2 +--------------------------%s +Content-Disposition: form-data; name="multi_file"; filename="%s" +Content-Type: application/octet-stream + +CURL1 + +--------------------------%s +Content-Disposition: form-data; name="multi_file"; filename="%s" +Content-Type: application/octet-stream + +CURL2 + +--------------------------%s +Content-Disposition: form-data; name="mixed" + +string value +--------------------------%s +Content-Disposition: form-data; name="mixed"; filename="stringfile.txt" +Content-Type: text/plain + +curl string file +--------------------------%s +Content-Disposition: form-data; name="mixed"; filename="%s" +Content-Type: text/plain + +CURL1 + +--------------------------%s--