]> granicus.if.org Git - php/commitdiff
Extend CURLFile to support streams
authorChristoph M. Becker <cmbecker69@gmx.de>
Mon, 29 Apr 2019 08:21:07 +0000 (10:21 +0200)
committerChristoph M. Becker <cmbecker69@gmx.de>
Mon, 29 Apr 2019 08:21:07 +0000 (10:21 +0200)
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] <http://git.php.net/?p=php-src.git;a=commit;h=a83b68ba56714bfa06737a61af795460caa4a105>

NEWS
UPGRADING
ext/curl/interface.c
ext/curl/php_curl.h
ext/curl/tests/bug77711.phpt [new file with mode: 0644]
ext/curl/tests/curl_copy_handle_variation3.phpt [new file with mode: 0644]
ext/curl/tests/curl_file_upload_stream.phpt [new file with mode: 0644]

diff --git a/NEWS b/NEWS
index 34aacf439f714051d394e1bc99ddbd4b782e605c..956f93fdbc5f6c2e5ed0ad63337998e504c709c1 100644 (file)
--- 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)
index 0bcdae197143563be5a09bfbd9abe380971f68df..8271bd63a3b35ddfb06c513a90d730a82a263919 100644 (file)
--- 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.
index d8b9fcb7ed3b7b43de31b9405dcb768ec1a97f5a..d03f81b249b83989aff2bb1d280a5dabe154f5f5 100644 (file)
@@ -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);
index f289c316450246a789919e2beb0c39f93ecf801f..359d6058efde85b146e99d5d9f68b9b1f628cd98 100644 (file)
@@ -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 (file)
index 0000000..148c263
--- /dev/null
@@ -0,0 +1,32 @@
+--TEST--
+FR #77711 (CURLFile should support UNICODE filenames)
+--SKIPIF--
+<?php include 'skipif.inc'; ?>
+--FILE--
+<?php
+include 'server.inc';
+$host = curl_cli_server_start();
+
+$ch = curl_init();
+curl_setopt($ch, CURLOPT_SAFE_UPLOAD, 1);
+curl_setopt($ch, CURLOPT_URL, "{$host}/get.php?test=file");
+curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+
+$filename = __DIR__ . '/АБВ.txt';
+file_put_contents($filename, "Test.");
+$file = curl_file_create($filename);
+$params = array('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--
+<?php
+@unlink(__DIR__ . '/АБВ.txt');
+?>
diff --git a/ext/curl/tests/curl_copy_handle_variation3.phpt b/ext/curl/tests/curl_copy_handle_variation3.phpt
new file mode 100644 (file)
index 0000000..18f35f7
--- /dev/null
@@ -0,0 +1,38 @@
+--TEST--
+curl_copy_handle() allows to post CURLFile multiple times
+--SKIPIF--
+<?php include 'skipif.inc'; ?>
+--FILE--
+<?php
+include 'server.inc';
+$host = curl_cli_server_start();
+
+$ch1 = curl_init();
+curl_setopt($ch1, CURLOPT_SAFE_UPLOAD, 1);
+curl_setopt($ch1, CURLOPT_URL, "{$host}/get.php?test=file");
+curl_setopt($ch1, CURLOPT_RETURNTRANSFER, 1);
+
+$filename = __DIR__ . '/АБВ.txt';
+file_put_contents($filename, "Test.");
+$file = curl_file_create($filename);
+$params = array('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--
+<?php
+@unlink(__DIR__ . '/АБВ.txt');
+?>
diff --git a/ext/curl/tests/curl_file_upload_stream.phpt b/ext/curl/tests/curl_file_upload_stream.phpt
new file mode 100644 (file)
index 0000000..03c85b4
--- /dev/null
@@ -0,0 +1,28 @@
+--TEST--
+CURL file uploading from stream
+--SKIPIF--
+<?php include 'skipif.inc'; ?>
+<?php
+if (curl_version()['version_number'] < 0x73800) die('skip requires curl >= 7.56.0');
+--FILE--
+<?php
+include 'server.inc';
+$host = curl_cli_server_start();
+
+$ch = curl_init();
+curl_setopt($ch, CURLOPT_SAFE_UPLOAD, 1);
+curl_setopt($ch, CURLOPT_URL, "{$host}/get.inc?test=file");
+curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+
+$file = curl_file_create('data://text/plain;base64,SSBsb3ZlIFBIUAo=', 'text/plain', 'i-love-php');
+$params = array('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===