From: Christoph M. Becker Date: Mon, 29 Apr 2019 08:21:07 +0000 (+0200) Subject: Extend CURLFile to support streams X-Git-Tag: php-7.4.0alpha1~395 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=c68dc6b5e37e74d89e0a387079139c054c8faa81;p=php Extend CURLFile to support streams Due to former restrictions of the libcurl API, curl multipart/formdata file uploads supported only proper files. However, as of curl 7.56.0 the new `curl_mime_*()` API is available (and already supported by PHP[1]), which allows us to support arbitrary *seekable* streams, which is generally desirable, and particularly resolves issues with the transparent Unicode and long part support on Windows (see bug #77711). Note that older curl versions are still supported, but CURLFile is still restricted to proper files in this case. [1] --- diff --git a/NEWS b/NEWS index 34aacf439f..956f93fdbc 100644 --- a/NEWS +++ b/NEWS @@ -20,6 +20,7 @@ PHP NEWS - CURL: . Fixed bug #76480 (Use curl_multi_wait() so that timeouts are respected). (Pierrick) + . Implemented FR #77711 (CURLFile should support UNICODE filenames). (cmb) - Date: . Fixed bug #75232 (print_r of DateTime creating side-effect). (Nikita) diff --git a/UPGRADING b/UPGRADING index 0bcdae1971..8271bd63a3 100644 --- a/UPGRADING +++ b/UPGRADING @@ -129,6 +129,11 @@ PHP 7.4 UPGRADE NOTES . Support for WeakReferences has been added. RFC: https://wiki.php.net/rfc/weakrefs +- CURL: + . CURLFile now supports stream wrappers in addition to plain file names, if + the extension has been built against libcurl >= 7.56.0. The streams may + need to be seekable. + - Filter: . The FILTER_VALIDATE_FLOAT filter now supports the min_range and max_range options, with the same semantics as FILTER_VALIDATE_INT. diff --git a/ext/curl/interface.c b/ext/curl/interface.c index d8b9fcb7ed..d03f81b249 100644 --- a/ext/curl/interface.c +++ b/ext/curl/interface.c @@ -1803,6 +1803,14 @@ static void curl_free_post(void **post) } /* }}} */ +/* {{{ curl_free_stream + */ +static void curl_free_stream(void **post) +{ + php_stream_close((php_stream *)*post); +} +/* }}} */ + /* {{{ curl_free_slist */ static void curl_free_slist(zval *el) @@ -1894,6 +1902,7 @@ php_curl *alloc_curl_handle() zend_llist_init(&ch->to_free->str, sizeof(char *), (llist_dtor_func_t)curl_free_string, 0); 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(php_stream *), (llist_dtor_func_t)curl_free_stream, 0); ch->to_free->slist = emalloc(sizeof(HashTable)); zend_hash_init(ch->to_free->slist, 4, NULL, curl_free_slist, 0); @@ -2121,6 +2130,32 @@ PHP_FUNCTION(curl_copy_handle) } /* }}} */ +#if LIBCURL_VERSION_NUM >= 0x073800 +static size_t read_cb(char *buffer, size_t size, size_t nitems, void *arg) /* {{{ */ +{ + php_stream *stream = (php_stream *) arg; + size_t numread = php_stream_read(stream, buffer, nitems * size); + + if (numread == (size_t)-1) { + return CURL_READFUNC_ABORT; + } + return numread; +} +/* }}} */ + +static int seek_cb(void *arg, curl_off_t offset, int origin) /* {{{ */ +{ + php_stream *stream = (php_stream *) arg; + int res = php_stream_seek(stream, offset, origin); + + if (res) { + return CURL_SEEKFUNC_CANTSEEK; + } + return CURL_SEEKFUNC_OK; +} +/* }}} */ +#endif + static int _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue) /* {{{ */ { CURLcode error = CURLE_OK; @@ -2756,6 +2791,9 @@ static int _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue) /* {{{ /* new-style file upload */ zval *prop, rv; char *type = NULL, *filename = NULL; +#if LIBCURL_VERSION_NUM >= 0x073800 /* 7.56.0 */ + php_stream *stream; +#endif prop = zend_read_property(curl_CURLFile_class, current, "name", sizeof("name")-1, 0, &rv); if (Z_TYPE_P(prop) != IS_STRING) { @@ -2777,17 +2815,24 @@ static int _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue) /* {{{ } #if LIBCURL_VERSION_NUM >= 0x073800 /* 7.56.0 */ + if (!(stream = php_stream_open_wrapper(ZSTR_VAL(postval), "rb", IGNORE_PATH, NULL))) { + zend_string_release_ex(string_key, 0); + return FAILURE; + } part = curl_mime_addpart(mime); if (part == NULL) { + php_stream_close(stream); 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_filedata(part, ZSTR_VAL(postval))) != CURLE_OK + || (form_error = curl_mime_data_cb(part, -1, read_cb, seek_cb, NULL, stream)) != 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) { + php_stream_close(stream); error = form_error; } + zend_llist_add_element(&ch->to_free->stream, &stream); #else form_error = curl_formadd(&first, &last, CURLFORM_COPYNAME, ZSTR_VAL(string_key), @@ -3517,6 +3562,7 @@ static void _php_curl_close_ex(php_curl *ch) if (--(*ch->clone) == 0) { zend_llist_clean(&ch->to_free->str); zend_llist_clean(&ch->to_free->post); + zend_llist_clean(&ch->to_free->stream); zend_hash_destroy(ch->to_free->slist); efree(ch->to_free->slist); efree(ch->to_free); diff --git a/ext/curl/php_curl.h b/ext/curl/php_curl.h index f289c31645..359d6058ef 100644 --- a/ext/curl/php_curl.h +++ b/ext/curl/php_curl.h @@ -167,6 +167,7 @@ struct _php_curl_send_headers { struct _php_curl_free { zend_llist str; zend_llist post; + zend_llist stream; HashTable *slist; }; diff --git a/ext/curl/tests/bug77711.phpt b/ext/curl/tests/bug77711.phpt new file mode 100644 index 0000000000..148c26322a --- /dev/null +++ b/ext/curl/tests/bug77711.phpt @@ -0,0 +1,32 @@ +--TEST-- +FR #77711 (CURLFile should support UNICODE filenames) +--SKIPIF-- + +--FILE-- + $file); +var_dump(curl_setopt($ch, CURLOPT_POSTFIELDS, $params)); + +var_dump(curl_exec($ch)); +curl_close($ch); +?> +===DONE=== +--EXPECTF-- +bool(true) +string(%d) "АБВ.txt|application/octet-stream" +===DONE=== +--CLEAN-- + diff --git a/ext/curl/tests/curl_copy_handle_variation3.phpt b/ext/curl/tests/curl_copy_handle_variation3.phpt new file mode 100644 index 0000000000..18f35f71b1 --- /dev/null +++ b/ext/curl/tests/curl_copy_handle_variation3.phpt @@ -0,0 +1,38 @@ +--TEST-- +curl_copy_handle() allows to post CURLFile multiple times +--SKIPIF-- + +--FILE-- + $file); +var_dump(curl_setopt($ch1, CURLOPT_POSTFIELDS, $params)); + +$ch2 = curl_copy_handle($ch1); + +var_dump(curl_exec($ch1)); +curl_close($ch1); + +var_dump(curl_exec($ch2)); +curl_close($ch2); +?> +===DONE=== +--EXPECTF-- +bool(true) +string(%d) "АБВ.txt|application/octet-stream" +string(%d) "АБВ.txt|application/octet-stream" +===DONE=== +--CLEAN-- + diff --git a/ext/curl/tests/curl_file_upload_stream.phpt b/ext/curl/tests/curl_file_upload_stream.phpt new file mode 100644 index 0000000000..03c85b4b82 --- /dev/null +++ b/ext/curl/tests/curl_file_upload_stream.phpt @@ -0,0 +1,28 @@ +--TEST-- +CURL file uploading from stream +--SKIPIF-- + += 7.56.0'); +--FILE-- + $file); +var_dump(curl_setopt($ch, CURLOPT_POSTFIELDS, $params)); + +var_dump(curl_exec($ch)); +curl_close($ch); +?> +===DONE=== +--EXPECT-- +bool(true) +string(21) "i-love-php|text/plain" +===DONE===