Skip to content

Commit e727919

Browse files
Irkernikic
authored andcommitted
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.
1 parent d16341d commit e727919

9 files changed

+271
-1
lines changed

UPGRADING

+5
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,11 @@ PHP 8.1 UPGRADE NOTES
100100

101101
- Curl:
102102
. Added CURLOPT_DOH_URL option.
103+
. Added CURLStringFile, which can be used to post a file from a string rather
104+
than a file:
105+
106+
$file = new CURLStringFile($data, 'filename.txt', 'text/plain');
107+
curl_setopt($curl, CURLOPT_POSTFIELDS, ['file' => $file]);
103108

104109
- hash:
105110
. The following functions have changed signatures:

ext/curl/curl_file.c

+26
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "curl_file_arginfo.h"
2626

2727
PHP_CURL_API zend_class_entry *curl_CURLFile_class;
28+
PHP_CURL_API zend_class_entry *curl_CURLStringFile_class;
2829

2930
static void curlfile_ctor(INTERNAL_FUNCTION_PARAMETERS)
3031
{
@@ -120,9 +121,34 @@ ZEND_METHOD(CURLFile, setPostFilename)
120121
}
121122
/* }}} */
122123

124+
ZEND_METHOD(CURLStringFile, __construct)
125+
{
126+
zend_string *data, *postname, *mime = NULL;
127+
zval *object;
128+
129+
object = ZEND_THIS;
130+
131+
ZEND_PARSE_PARAMETERS_START(2,3)
132+
Z_PARAM_STR(data)
133+
Z_PARAM_STR(postname)
134+
Z_PARAM_OPTIONAL
135+
Z_PARAM_STR(mime)
136+
ZEND_PARSE_PARAMETERS_END();
137+
138+
zend_update_property_str(curl_CURLStringFile_class, Z_OBJ_P(object), "data", sizeof("data") - 1, data);
139+
zend_update_property_str(curl_CURLStringFile_class, Z_OBJ_P(object), "postname", sizeof("postname")-1, postname);
140+
if (mime) {
141+
zend_update_property_str(curl_CURLStringFile_class, Z_OBJ_P(object), "mime", sizeof("mime")-1, mime);
142+
} else {
143+
zend_update_property_string(curl_CURLStringFile_class, Z_OBJ_P(object), "mime", sizeof("mime")-1, "application/octet-stream");
144+
}
145+
}
146+
123147
void curlfile_register_class(void)
124148
{
125149
curl_CURLFile_class = register_class_CURLFile();
126150
curl_CURLFile_class->serialize = zend_class_serialize_deny;
127151
curl_CURLFile_class->unserialize = zend_class_unserialize_deny;
152+
153+
curl_CURLStringFile_class = register_class_CURLStringFile();
128154
}

ext/curl/curl_file.stub.php

+9
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,12 @@ public function setMimeType(string $mime_type) {}
2828
/** @return void */
2929
public function setPostFilename(string $posted_filename) {}
3030
}
31+
32+
class CURLStringFile
33+
{
34+
public string $data;
35+
public string $postname;
36+
public string $mime;
37+
38+
public function __construct(string $data, string $postname, string $mime = "application/octet-stream") {}
39+
}

ext/curl/curl_file_arginfo.h

+42-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* This is a generated file, edit the .stub.php file instead.
2-
* Stub hash: 2bd78005380fd7f885618c4cb993bb21abe8cea9 */
2+
* Stub hash: fdeef1c2a9e835b443d6e4cced23656ce21d8a30 */
33

44
ZEND_BEGIN_ARG_INFO_EX(arginfo_class_CURLFile___construct, 0, 0, 1)
55
ZEND_ARG_TYPE_INFO(0, filename, IS_STRING, 0)
@@ -22,13 +22,20 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_CURLFile_setPostFilename, 0, 0, 1)
2222
ZEND_ARG_TYPE_INFO(0, posted_filename, IS_STRING, 0)
2323
ZEND_END_ARG_INFO()
2424

25+
ZEND_BEGIN_ARG_INFO_EX(arginfo_class_CURLStringFile___construct, 0, 0, 2)
26+
ZEND_ARG_TYPE_INFO(0, data, IS_STRING, 0)
27+
ZEND_ARG_TYPE_INFO(0, postname, IS_STRING, 0)
28+
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, mime, IS_STRING, 0, "\"application/octet-stream\"")
29+
ZEND_END_ARG_INFO()
30+
2531

2632
ZEND_METHOD(CURLFile, __construct);
2733
ZEND_METHOD(CURLFile, getFilename);
2834
ZEND_METHOD(CURLFile, getMimeType);
2935
ZEND_METHOD(CURLFile, getPostFilename);
3036
ZEND_METHOD(CURLFile, setMimeType);
3137
ZEND_METHOD(CURLFile, setPostFilename);
38+
ZEND_METHOD(CURLStringFile, __construct);
3239

3340

3441
static const zend_function_entry class_CURLFile_methods[] = {
@@ -41,6 +48,12 @@ static const zend_function_entry class_CURLFile_methods[] = {
4148
ZEND_FE_END
4249
};
4350

51+
52+
static const zend_function_entry class_CURLStringFile_methods[] = {
53+
ZEND_ME(CURLStringFile, __construct, arginfo_class_CURLStringFile___construct, ZEND_ACC_PUBLIC)
54+
ZEND_FE_END
55+
};
56+
4457
zend_class_entry *register_class_CURLFile()
4558
{
4659
zend_class_entry ce, *class_entry;
@@ -69,3 +82,31 @@ zend_class_entry *register_class_CURLFile()
6982
return class_entry;
7083
}
7184

85+
zend_class_entry *register_class_CURLStringFile()
86+
{
87+
zend_class_entry ce, *class_entry;
88+
89+
INIT_CLASS_ENTRY(ce, "CURLStringFile", class_CURLStringFile_methods);
90+
class_entry = zend_register_internal_class_ex(&ce, NULL);
91+
92+
zval property_data_default_value;
93+
ZVAL_UNDEF(&property_data_default_value);
94+
zend_string *property_data_name = zend_string_init("data", sizeof("data") - 1, 1);
95+
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));
96+
zend_string_release(property_data_name);
97+
98+
zval property_postname_default_value;
99+
ZVAL_UNDEF(&property_postname_default_value);
100+
zend_string *property_postname_name = zend_string_init("postname", sizeof("postname") - 1, 1);
101+
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));
102+
zend_string_release(property_postname_name);
103+
104+
zval property_mime_default_value;
105+
ZVAL_UNDEF(&property_mime_default_value);
106+
zend_string *property_mime_name = zend_string_init("mime", sizeof("mime") - 1, 1);
107+
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));
108+
zend_string_release(property_mime_name);
109+
110+
return class_entry;
111+
}
112+

ext/curl/curl_private.h

+3
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ struct _php_curl_send_headers {
9090
struct _php_curl_free {
9191
zend_llist post;
9292
zend_llist stream;
93+
#if LIBCURL_VERSION_NUM < 0x073800 /* 7.56.0 */
94+
zend_llist buffers;
95+
#endif
9396
HashTable *slist;
9497
};
9598

ext/curl/interface.c

+90
Original file line numberDiff line numberDiff line change
@@ -1654,6 +1654,15 @@ static void curl_free_cb_arg(void **cb_arg_p)
16541654
}
16551655
/* }}} */
16561656

1657+
#if LIBCURL_VERSION_NUM < 0x073800 /* 7.56.0 */
1658+
/* {{{ curl_free_buffers */
1659+
static void curl_free_buffers(void **buffer)
1660+
{
1661+
zend_string_release((zend_string *) *buffer);
1662+
}
1663+
/* }}} */
1664+
#endif
1665+
16571666
/* {{{ curl_free_slist */
16581667
static void curl_free_slist(zval *el)
16591668
{
@@ -1744,6 +1753,10 @@ void init_curl_handle(php_curl *ch)
17441753
zend_llist_init(&ch->to_free->post, sizeof(struct HttpPost *), (llist_dtor_func_t)curl_free_post, 0);
17451754
zend_llist_init(&ch->to_free->stream, sizeof(struct mime_data_cb_arg *), (llist_dtor_func_t)curl_free_cb_arg, 0);
17461755

1756+
#if LIBCURL_VERSION_NUM < 0x073800 /* 7.56.0 */
1757+
zend_llist_init(&ch->to_free->buffers, sizeof(zend_string *), (llist_dtor_func_t)curl_free_buffers, 0);
1758+
#endif
1759+
17471760
ch->to_free->slist = emalloc(sizeof(HashTable));
17481761
zend_hash_init(ch->to_free->slist, 4, NULL, curl_free_slist, 0);
17491762
ZVAL_UNDEF(&ch->postfields);
@@ -2086,6 +2099,78 @@ static inline int build_mime_structure_from_hash(php_curl *ch, zval *zpostfields
20862099
continue;
20872100
}
20882101

2102+
if (Z_TYPE_P(current) == IS_OBJECT && instanceof_function(Z_OBJCE_P(current), curl_CURLStringFile_class)) {
2103+
/* new-style file upload from string */
2104+
zval *prop, rv;
2105+
char *type = NULL, *filename = NULL;
2106+
2107+
prop = zend_read_property(curl_CURLStringFile_class, Z_OBJ_P(current), "postname", sizeof("postname")-1, 0, &rv);
2108+
if (EG(exception)) {
2109+
zend_string_release_ex(string_key, 0);
2110+
return FAILURE;
2111+
}
2112+
ZVAL_DEREF(prop);
2113+
ZEND_ASSERT(Z_TYPE_P(prop) == IS_STRING);
2114+
2115+
filename = Z_STRVAL_P(prop);
2116+
2117+
prop = zend_read_property(curl_CURLStringFile_class, Z_OBJ_P(current), "mime", sizeof("mime")-1, 0, &rv);
2118+
if (EG(exception)) {
2119+
zend_string_release_ex(string_key, 0);
2120+
return FAILURE;
2121+
}
2122+
ZVAL_DEREF(prop);
2123+
ZEND_ASSERT(Z_TYPE_P(prop) == IS_STRING);
2124+
2125+
type = Z_STRVAL_P(prop);
2126+
2127+
prop = zend_read_property(curl_CURLStringFile_class, Z_OBJ_P(current), "data", sizeof("data")-1, 0, &rv);
2128+
if (EG(exception)) {
2129+
zend_string_release_ex(string_key, 0);
2130+
return FAILURE;
2131+
}
2132+
ZVAL_DEREF(prop);
2133+
ZEND_ASSERT(Z_TYPE_P(prop) == IS_STRING);
2134+
2135+
postval = Z_STR_P(prop);
2136+
2137+
#if LIBCURL_VERSION_NUM >= 0x073800 /* 7.56.0 */
2138+
zval_ptr_dtor(&ch->postfields);
2139+
ZVAL_COPY(&ch->postfields, zpostfields);
2140+
2141+
part = curl_mime_addpart(mime);
2142+
if (part == NULL) {
2143+
zend_string_release_ex(string_key, 0);
2144+
return FAILURE;
2145+
}
2146+
if ((form_error = curl_mime_name(part, ZSTR_VAL(string_key))) != CURLE_OK
2147+
|| (form_error = curl_mime_data(part, ZSTR_VAL(postval), ZSTR_LEN(postval))) != CURLE_OK
2148+
|| (form_error = curl_mime_filename(part, filename)) != CURLE_OK
2149+
|| (form_error = curl_mime_type(part, type)) != CURLE_OK) {
2150+
error = form_error;
2151+
}
2152+
#else
2153+
postval = zend_string_copy(postval);
2154+
zend_llist_add_element(&ch->to_free->buffers, &postval);
2155+
2156+
form_error = curl_formadd(&first, &last,
2157+
CURLFORM_COPYNAME, ZSTR_VAL(string_key),
2158+
CURLFORM_NAMELENGTH, ZSTR_LEN(string_key),
2159+
CURLFORM_BUFFER, filename,
2160+
CURLFORM_CONTENTTYPE, type,
2161+
CURLFORM_BUFFERPTR, ZSTR_VAL(postval),
2162+
CURLFORM_BUFFERLENGTH, ZSTR_LEN(postval),
2163+
CURLFORM_END);
2164+
if (form_error != CURL_FORMADD_OK) {
2165+
/* Not nice to convert between enums but we only have place for one error type */
2166+
error = (CURLcode)form_error;
2167+
}
2168+
#endif
2169+
2170+
zend_string_release_ex(string_key, 0);
2171+
continue;
2172+
}
2173+
20892174
postval = zval_get_tmp_string(current, &tmp_postval);
20902175

20912176
#if LIBCURL_VERSION_NUM >= 0x073800 /* 7.56.0 */
@@ -3330,6 +3415,11 @@ static void curl_free_obj(zend_object *object)
33303415
if (--(*ch->clone) == 0) {
33313416
zend_llist_clean(&ch->to_free->post);
33323417
zend_llist_clean(&ch->to_free->stream);
3418+
3419+
#if LIBCURL_VERSION_NUM < 0x073800 /* 7.56.0 */
3420+
zend_llist_clean(&ch->to_free->buffers);
3421+
#endif
3422+
33333423
zend_hash_destroy(ch->to_free->slist);
33343424
efree(ch->to_free->slist);
33353425
efree(ch->to_free);

ext/curl/php_curl.h

+1
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,6 @@ PHP_CURL_API extern zend_class_entry *curl_ce;
3939
PHP_CURL_API extern zend_class_entry *curl_share_ce;
4040
PHP_CURL_API extern zend_class_entry *curl_multi_ce;
4141
PHP_CURL_API extern zend_class_entry *curl_CURLFile_class;
42+
PHP_CURL_API extern zend_class_entry *curl_CURLStringFile_class;
4243

4344
#endif /* _PHP_CURL_H */
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
--TEST--
2+
CURL file uploading from string
3+
--SKIPIF--
4+
<?php include 'skipif.inc'; ?>
5+
--FILE--
6+
<?php
7+
8+
function testcurl($ch, $postname, $data, $mime = null)
9+
{
10+
if (is_null($mime)) {
11+
// for default mime value
12+
$file = new CURLStringFile($data, $postname);
13+
} else {
14+
$file = new CURLStringFile($data, $postname, $mime);
15+
}
16+
curl_setopt($ch, CURLOPT_POSTFIELDS, array("file" => $file));
17+
var_dump(curl_exec($ch));
18+
}
19+
20+
include 'server.inc';
21+
$host = curl_cli_server_start();
22+
$ch = curl_init();
23+
curl_setopt($ch, CURLOPT_URL, "{$host}/get.php?test=string_file");
24+
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
25+
26+
$data = "test\0test";
27+
var_dump(md5($data));
28+
testcurl($ch, 'foo.txt', $data);
29+
testcurl($ch, 'foo.txt', $data, 'text/plain');
30+
testcurl($ch, '', $data);
31+
testcurl($ch, 'foo.txt', '');
32+
testcurl($ch, "foo.txt\0broken_string", $data, "text/plain\0broken_string");
33+
34+
// properties
35+
$file = new CURLStringFile($data, 'foo.txt');
36+
$file->mime = 'text/plain';
37+
var_dump($file->mime);
38+
var_dump($file->postname);
39+
var_dump(md5($file->data));
40+
curl_setopt($ch, CURLOPT_POSTFIELDS, array("file" => $file));
41+
var_dump(curl_exec($ch));
42+
43+
// serialization / deserialization
44+
$old = new CURLStringFile($data, 'foo.txt', 'text/plain');
45+
$serialized = serialize($old);
46+
$new = unserialize($serialized);
47+
curl_setopt($ch, CURLOPT_POSTFIELDS, array("file" => $new));
48+
var_dump(curl_exec($ch));
49+
50+
// destroy object before send request
51+
$file = new CURLStringFile($data, 'foo.txt', 'text/plain');
52+
curl_setopt($ch, CURLOPT_POSTFIELDS, array("file" => $file));
53+
unset($file);
54+
var_dump(curl_exec($ch));
55+
56+
// clone curl handler
57+
$file = new CURLStringFile($data, 'foo.txt', 'text/plain');
58+
curl_setopt($ch, CURLOPT_POSTFIELDS, array("file" => $file));
59+
$ch2 = clone $ch;
60+
var_dump(curl_exec($ch2));
61+
62+
// properties are references
63+
64+
$file = new CURLStringFile($data, 'foo.txt', 'text/plain');
65+
$data =& $file->data;
66+
$postname =& $file->postname;
67+
$mime =& $file->mime;
68+
curl_setopt($ch, CURLOPT_POSTFIELDS, array("file" => $file));
69+
var_dump(curl_exec($ch));
70+
71+
?>
72+
--EXPECTF--
73+
string(%d) "62942c05ed0d1b501c4afe6dc1c4db1b"
74+
string(%d) "foo.txt|application/octet-stream|62942c05ed0d1b501c4afe6dc1c4db1b"
75+
string(%d) "foo.txt|text/plain|62942c05ed0d1b501c4afe6dc1c4db1b"
76+
string(%d) "error:4"
77+
string(%d) "foo.txt|application/octet-stream|d41d8cd98f00b204e9800998ecf8427e"
78+
string(%d) "foo.txt|text/plain|62942c05ed0d1b501c4afe6dc1c4db1b"
79+
string(%d) "text/plain"
80+
string(%d) "foo.txt"
81+
string(%d) "62942c05ed0d1b501c4afe6dc1c4db1b"
82+
string(%d) "foo.txt|text/plain|62942c05ed0d1b501c4afe6dc1c4db1b"
83+
string(%d) "foo.txt|text/plain|62942c05ed0d1b501c4afe6dc1c4db1b"
84+
string(%d) "foo.txt|text/plain|62942c05ed0d1b501c4afe6dc1c4db1b"
85+
string(%d) "foo.txt|text/plain|62942c05ed0d1b501c4afe6dc1c4db1b"
86+
string(%d) "foo.txt|text/plain|62942c05ed0d1b501c4afe6dc1c4db1b"

ext/curl/tests/responder/get.inc

+9
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,15 @@
3131
echo $_FILES['file']['name'] . '|' . $_FILES['file']['type'] . '|' . $_FILES['file']['size'];
3232
}
3333
break;
34+
case 'string_file':
35+
if (isset($_FILES['file'])) {
36+
if ($_FILES['file']['error'] === UPLOAD_ERR_OK) {
37+
echo $_FILES['file']['name'] . '|' . $_FILES['file']['type'] . '|' . md5_file($_FILES['file']['tmp_name']);
38+
} else {
39+
echo 'error:' . $_FILES['file']['error'];
40+
}
41+
}
42+
break;
3443
case 'method':
3544
echo $_SERVER['REQUEST_METHOD'];
3645
break;

0 commit comments

Comments
 (0)