]> granicus.if.org Git - php/commitdiff
cURL: make possible to send file from buffer string
authorAlexander Moskalev <irker@irker.net>
Thu, 26 Nov 2020 07:30:02 +0000 (10:30 +0300)
committerNikita Popov <nikita.ppv@gmail.com>
Tue, 16 Feb 2021 09:24:22 +0000 (10:24 +0100)
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.

UPGRADING
ext/curl/curl_file.c
ext/curl/curl_file.stub.php
ext/curl/curl_file_arginfo.h
ext/curl/curl_private.h
ext/curl/interface.c
ext/curl/php_curl.h
ext/curl/tests/curl_string_file_upload.phpt [new file with mode: 0644]
ext/curl/tests/responder/get.inc

index 2e4700c1c9592f8d3728ec749da36797b9bf0e59..5d72233e904ea5ce90f82820ef119c1b19ec1b24 100644 (file)
--- a/UPGRADING
+++ b/UPGRADING
@@ -100,6 +100,11 @@ PHP 8.1 UPGRADE NOTES
 
 - Curl:
   . Added CURLOPT_DOH_URL option.
+  . Added CURLStringFile, which can be used to post a file from a string rather
+    than a file:
+
+        $file = new CURLStringFile($data, 'filename.txt', 'text/plain');
+        curl_setopt($curl, CURLOPT_POSTFIELDS, ['file' => $file]);
 
 - hash:
   . The following functions have changed signatures:
index a81239a60aba22eb0317b9bb7aa6cfe00c3ddae4..a8f8a793b202c4cd576ccb8952276ad6ed0360e8 100644 (file)
@@ -25,6 +25,7 @@
 #include "curl_file_arginfo.h"
 
 PHP_CURL_API zend_class_entry *curl_CURLFile_class;
+PHP_CURL_API zend_class_entry *curl_CURLStringFile_class;
 
 static void curlfile_ctor(INTERNAL_FUNCTION_PARAMETERS)
 {
@@ -120,9 +121,34 @@ ZEND_METHOD(CURLFile, setPostFilename)
 }
 /* }}} */
 
+ZEND_METHOD(CURLStringFile, __construct)
+{
+       zend_string *data, *postname, *mime = NULL;
+       zval *object;
+
+       object = ZEND_THIS;
+
+       ZEND_PARSE_PARAMETERS_START(2,3)
+               Z_PARAM_STR(data)
+               Z_PARAM_STR(postname)
+               Z_PARAM_OPTIONAL
+               Z_PARAM_STR(mime)
+       ZEND_PARSE_PARAMETERS_END();
+
+       zend_update_property_str(curl_CURLStringFile_class, Z_OBJ_P(object), "data", sizeof("data") - 1, data);
+       zend_update_property_str(curl_CURLStringFile_class, Z_OBJ_P(object), "postname", sizeof("postname")-1, postname);
+       if (mime) {
+               zend_update_property_str(curl_CURLStringFile_class, Z_OBJ_P(object), "mime", sizeof("mime")-1, mime);
+       } else {
+               zend_update_property_string(curl_CURLStringFile_class, Z_OBJ_P(object), "mime", sizeof("mime")-1, "application/octet-stream");
+       }
+}
+
 void curlfile_register_class(void)
 {
        curl_CURLFile_class = register_class_CURLFile();
        curl_CURLFile_class->serialize = zend_class_serialize_deny;
        curl_CURLFile_class->unserialize = zend_class_unserialize_deny;
+
+       curl_CURLStringFile_class = register_class_CURLStringFile();
 }
index ecc14969149e600d289e20115ccad18b2337ad62..041b780b9a64bccaae8d63d278a565ca015ed5fe 100644 (file)
@@ -28,3 +28,12 @@ class CURLFile
     /** @return void */
     public function setPostFilename(string $posted_filename) {}
 }
+
+class CURLStringFile
+{
+    public string $data;
+    public string $postname;
+    public string $mime;
+
+    public function __construct(string $data, string $postname, string $mime = "application/octet-stream") {}
+}
index 10b7ad04d78d491cc2eeb582008dee62bf0e0c4c..400f2fd5f8c000801bc2772ad706365852d8b1e4 100644 (file)
@@ -1,5 +1,5 @@
 /* This is a generated file, edit the .stub.php file instead.
- * Stub hash: 2bd78005380fd7f885618c4cb993bb21abe8cea9 */
+ * Stub hash: fdeef1c2a9e835b443d6e4cced23656ce21d8a30 */
 
 ZEND_BEGIN_ARG_INFO_EX(arginfo_class_CURLFile___construct, 0, 0, 1)
        ZEND_ARG_TYPE_INFO(0, filename, IS_STRING, 0)
@@ -22,6 +22,12 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_CURLFile_setPostFilename, 0, 0, 1)
        ZEND_ARG_TYPE_INFO(0, posted_filename, IS_STRING, 0)
 ZEND_END_ARG_INFO()
 
+ZEND_BEGIN_ARG_INFO_EX(arginfo_class_CURLStringFile___construct, 0, 0, 2)
+       ZEND_ARG_TYPE_INFO(0, data, IS_STRING, 0)
+       ZEND_ARG_TYPE_INFO(0, postname, IS_STRING, 0)
+       ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, mime, IS_STRING, 0, "\"application/octet-stream\"")
+ZEND_END_ARG_INFO()
+
 
 ZEND_METHOD(CURLFile, __construct);
 ZEND_METHOD(CURLFile, getFilename);
@@ -29,6 +35,7 @@ ZEND_METHOD(CURLFile, getMimeType);
 ZEND_METHOD(CURLFile, getPostFilename);
 ZEND_METHOD(CURLFile, setMimeType);
 ZEND_METHOD(CURLFile, setPostFilename);
+ZEND_METHOD(CURLStringFile, __construct);
 
 
 static const zend_function_entry class_CURLFile_methods[] = {
@@ -41,6 +48,12 @@ static const zend_function_entry class_CURLFile_methods[] = {
        ZEND_FE_END
 };
 
+
+static const zend_function_entry class_CURLStringFile_methods[] = {
+       ZEND_ME(CURLStringFile, __construct, arginfo_class_CURLStringFile___construct, ZEND_ACC_PUBLIC)
+       ZEND_FE_END
+};
+
 zend_class_entry *register_class_CURLFile()
 {
        zend_class_entry ce, *class_entry;
@@ -69,3 +82,31 @@ zend_class_entry *register_class_CURLFile()
        return class_entry;
 }
 
+zend_class_entry *register_class_CURLStringFile()
+{
+       zend_class_entry ce, *class_entry;
+
+       INIT_CLASS_ENTRY(ce, "CURLStringFile", class_CURLStringFile_methods);
+       class_entry = zend_register_internal_class_ex(&ce, NULL);
+
+       zval property_data_default_value;
+       ZVAL_UNDEF(&property_data_default_value);
+       zend_string *property_data_name = zend_string_init("data", sizeof("data") - 1, 1);
+       zend_declare_typed_property(class_entry, property_data_name, &property_data_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING));
+       zend_string_release(property_data_name);
+
+       zval property_postname_default_value;
+       ZVAL_UNDEF(&property_postname_default_value);
+       zend_string *property_postname_name = zend_string_init("postname", sizeof("postname") - 1, 1);
+       zend_declare_typed_property(class_entry, property_postname_name, &property_postname_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING));
+       zend_string_release(property_postname_name);
+
+       zval property_mime_default_value;
+       ZVAL_UNDEF(&property_mime_default_value);
+       zend_string *property_mime_name = zend_string_init("mime", sizeof("mime") - 1, 1);
+       zend_declare_typed_property(class_entry, property_mime_name, &property_mime_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING));
+       zend_string_release(property_mime_name);
+
+       return class_entry;
+}
+
index 49201334b96117d4a4445dbf28a966f817572da5..3c219d70144fda90517284d48fc7b7d9a0b6191b 100644 (file)
@@ -90,6 +90,9 @@ struct _php_curl_send_headers {
 struct _php_curl_free {
        zend_llist post;
        zend_llist stream;
+#if LIBCURL_VERSION_NUM < 0x073800 /* 7.56.0 */
+       zend_llist buffers;
+#endif
        HashTable *slist;
 };
 
index d96334cd07b7410ffc1ace1b5c75c3902955eda3..0827bdfd8b37fd0e43a49e886c22abc504a19088 100644 (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);
index 88806262eceb90aa8504abcdeedf89c226d81193..09e20602f413c5f6ae79103d347650c0238572f0 100644 (file)
@@ -39,5 +39,6 @@ PHP_CURL_API extern zend_class_entry *curl_ce;
 PHP_CURL_API extern zend_class_entry *curl_share_ce;
 PHP_CURL_API extern zend_class_entry *curl_multi_ce;
 PHP_CURL_API extern zend_class_entry *curl_CURLFile_class;
+PHP_CURL_API extern zend_class_entry *curl_CURLStringFile_class;
 
 #endif  /* _PHP_CURL_H */
diff --git a/ext/curl/tests/curl_string_file_upload.phpt b/ext/curl/tests/curl_string_file_upload.phpt
new file mode 100644 (file)
index 0000000..65a041e
--- /dev/null
@@ -0,0 +1,86 @@
+--TEST--
+CURL file uploading from string
+--SKIPIF--
+<?php include 'skipif.inc'; ?>
+--FILE--
+<?php
+
+function testcurl($ch, $postname, $data, $mime = null)
+{
+       if (is_null($mime)) {
+               // for default mime value
+               $file = new CURLStringFile($data, $postname);
+       } else {
+               $file = new CURLStringFile($data, $postname, $mime);
+       }
+       curl_setopt($ch, CURLOPT_POSTFIELDS, array("file" => $file));
+       var_dump(curl_exec($ch));
+}
+
+include 'server.inc';
+$host = curl_cli_server_start();
+$ch = curl_init();
+curl_setopt($ch, CURLOPT_URL, "{$host}/get.php?test=string_file");
+curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+
+$data = "test\0test";
+var_dump(md5($data));
+testcurl($ch, 'foo.txt', $data);
+testcurl($ch, 'foo.txt', $data, 'text/plain');
+testcurl($ch, '', $data);
+testcurl($ch, 'foo.txt', '');
+testcurl($ch, "foo.txt\0broken_string", $data, "text/plain\0broken_string");
+
+// properties
+$file = new CURLStringFile($data, 'foo.txt');
+$file->mime = 'text/plain';
+var_dump($file->mime);
+var_dump($file->postname);
+var_dump(md5($file->data));
+curl_setopt($ch, CURLOPT_POSTFIELDS, array("file" => $file));
+var_dump(curl_exec($ch));
+
+// serialization / deserialization
+$old = new CURLStringFile($data, 'foo.txt', 'text/plain');
+$serialized = serialize($old);
+$new = unserialize($serialized);
+curl_setopt($ch, CURLOPT_POSTFIELDS, array("file" => $new));
+var_dump(curl_exec($ch));
+
+// destroy object before send request
+$file = new CURLStringFile($data, 'foo.txt', 'text/plain');
+curl_setopt($ch, CURLOPT_POSTFIELDS, array("file" => $file));
+unset($file);
+var_dump(curl_exec($ch));
+
+// clone curl handler
+$file = new CURLStringFile($data, 'foo.txt', 'text/plain');
+curl_setopt($ch, CURLOPT_POSTFIELDS, array("file" => $file));
+$ch2 = clone $ch;
+var_dump(curl_exec($ch2));
+
+// properties are references
+
+$file = new CURLStringFile($data, 'foo.txt', 'text/plain');
+$data =& $file->data;
+$postname =& $file->postname;
+$mime =& $file->mime;
+curl_setopt($ch, CURLOPT_POSTFIELDS, array("file" => $file));
+var_dump(curl_exec($ch));
+
+?>
+--EXPECTF--
+string(%d) "62942c05ed0d1b501c4afe6dc1c4db1b"
+string(%d) "foo.txt|application/octet-stream|62942c05ed0d1b501c4afe6dc1c4db1b"
+string(%d) "foo.txt|text/plain|62942c05ed0d1b501c4afe6dc1c4db1b"
+string(%d) "error:4"
+string(%d) "foo.txt|application/octet-stream|d41d8cd98f00b204e9800998ecf8427e"
+string(%d) "foo.txt|text/plain|62942c05ed0d1b501c4afe6dc1c4db1b"
+string(%d) "text/plain"
+string(%d) "foo.txt"
+string(%d) "62942c05ed0d1b501c4afe6dc1c4db1b"
+string(%d) "foo.txt|text/plain|62942c05ed0d1b501c4afe6dc1c4db1b"
+string(%d) "foo.txt|text/plain|62942c05ed0d1b501c4afe6dc1c4db1b"
+string(%d) "foo.txt|text/plain|62942c05ed0d1b501c4afe6dc1c4db1b"
+string(%d) "foo.txt|text/plain|62942c05ed0d1b501c4afe6dc1c4db1b"
+string(%d) "foo.txt|text/plain|62942c05ed0d1b501c4afe6dc1c4db1b"
index 64ab267d50365c11448195e6ab7b33180a730ddc..4ed9ae0282346dbbcaed792e634b1b7012abddbc 100644 (file)
           echo $_FILES['file']['name'] . '|' . $_FILES['file']['type'] . '|' . $_FILES['file']['size'];
       }
       break;
+    case 'string_file':
+      if (isset($_FILES['file'])) {
+          if ($_FILES['file']['error'] === UPLOAD_ERR_OK) {
+              echo $_FILES['file']['name'] . '|' . $_FILES['file']['type'] . '|' . md5_file($_FILES['file']['tmp_name']);
+          } else {
+              echo 'error:' . $_FILES['file']['error'];
+          }
+      }
+      break;
     case 'method':
       echo $_SERVER['REQUEST_METHOD'];
       break;