cURL: make possible to send file from buffer string

Add CURLStringFile class which works similarly to CURLFile, but
uploads a file from a string rather than a file. This avoids the
need to create a temporary file, or use of a data:// stream.

Basic usage:

    $file = new CURLStringFile($data, 'filename.txt', 'text/plain');
    curl_setopt($curl, CURLOPT_POSTFIELDS, ['file' => $file]);

Closes GH-6456.
This commit is contained in:
Alexander Moskalev 2020-11-26 10:30:02 +03:00 committed by Nikita Popov
parent d16341dabe
commit e727919b97
9 changed files with 271 additions and 1 deletions

View file

@ -1654,6 +1654,15 @@ static void curl_free_cb_arg(void **cb_arg_p)
}
/* }}} */
#if LIBCURL_VERSION_NUM < 0x073800 /* 7.56.0 */
/* {{{ curl_free_buffers */
static void curl_free_buffers(void **buffer)
{
zend_string_release((zend_string *) *buffer);
}
/* }}} */
#endif
/* {{{ curl_free_slist */
static void curl_free_slist(zval *el)
{
@ -1744,6 +1753,10 @@ void init_curl_handle(php_curl *ch)
zend_llist_init(&ch->to_free->post, sizeof(struct HttpPost *), (llist_dtor_func_t)curl_free_post, 0);
zend_llist_init(&ch->to_free->stream, sizeof(struct mime_data_cb_arg *), (llist_dtor_func_t)curl_free_cb_arg, 0);
#if LIBCURL_VERSION_NUM < 0x073800 /* 7.56.0 */
zend_llist_init(&ch->to_free->buffers, sizeof(zend_string *), (llist_dtor_func_t)curl_free_buffers, 0);
#endif
ch->to_free->slist = emalloc(sizeof(HashTable));
zend_hash_init(ch->to_free->slist, 4, NULL, curl_free_slist, 0);
ZVAL_UNDEF(&ch->postfields);
@ -2086,6 +2099,78 @@ static inline int build_mime_structure_from_hash(php_curl *ch, zval *zpostfields
continue;
}
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;
prop = zend_read_property(curl_CURLStringFile_class, Z_OBJ_P(current), "postname", sizeof("postname")-1, 0, &rv);
if (EG(exception)) {
zend_string_release_ex(string_key, 0);
return FAILURE;
}
ZVAL_DEREF(prop);
ZEND_ASSERT(Z_TYPE_P(prop) == IS_STRING);
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)) {
zend_string_release_ex(string_key, 0);
return FAILURE;
}
ZVAL_DEREF(prop);
ZEND_ASSERT(Z_TYPE_P(prop) == IS_STRING);
type = Z_STRVAL_P(prop);
prop = zend_read_property(curl_CURLStringFile_class, Z_OBJ_P(current), "data", sizeof("data")-1, 0, &rv);
if (EG(exception)) {
zend_string_release_ex(string_key, 0);
return FAILURE;
}
ZVAL_DEREF(prop);
ZEND_ASSERT(Z_TYPE_P(prop) == IS_STRING);
postval = Z_STR_P(prop);
#if LIBCURL_VERSION_NUM >= 0x073800 /* 7.56.0 */
zval_ptr_dtor(&ch->postfields);
ZVAL_COPY(&ch->postfields, zpostfields);
part = curl_mime_addpart(mime);
if (part == NULL) {
zend_string_release_ex(string_key, 0);
return FAILURE;
}
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;
}
#else
postval = zend_string_copy(postval);
zend_llist_add_element(&ch->to_free->buffers, &postval);
form_error = curl_formadd(&first, &last,
CURLFORM_COPYNAME, ZSTR_VAL(string_key),
CURLFORM_NAMELENGTH, ZSTR_LEN(string_key),
CURLFORM_BUFFER, filename,
CURLFORM_CONTENTTYPE, type,
CURLFORM_BUFFERPTR, ZSTR_VAL(postval),
CURLFORM_BUFFERLENGTH, ZSTR_LEN(postval),
CURLFORM_END);
if (form_error != CURL_FORMADD_OK) {
/* Not nice to convert between enums but we only have place for one error type */
error = (CURLcode)form_error;
}
#endif
zend_string_release_ex(string_key, 0);
continue;
}
postval = zval_get_tmp_string(current, &tmp_postval);
#if LIBCURL_VERSION_NUM >= 0x073800 /* 7.56.0 */
@ -3330,6 +3415,11 @@ static void curl_free_obj(zend_object *object)
if (--(*ch->clone) == 0) {
zend_llist_clean(&ch->to_free->post);
zend_llist_clean(&ch->to_free->stream);
#if LIBCURL_VERSION_NUM < 0x073800 /* 7.56.0 */
zend_llist_clean(&ch->to_free->buffers);
#endif
zend_hash_destroy(ch->to_free->slist);
efree(ch->to_free->slist);
efree(ch->to_free);