Skip to content

Commit c067770

Browse files
committed
Fix #79019: Copied cURL handles upload empty file
To cater to `curl_copy_handle()` of cURL handles with attached `CURLFile`s, we must not attach the opened stream, because the stream may not be seekable, so that we could rewind, when the same stream is going to be uploaded multiple times. Instead, we're opening the stream lazily in the read or seek callback, and close it when reading finished (either successfully or not) and when seeking failed, respectively. This behavior is basically the same as for libcurl < 7.56.0, where we attach the file name, and libcurl reads and uploads the file contents. To be able to test this behavior, we extend the test responder to print the size of the upload, and patch the existing tests accordingly.
1 parent 22e9f9f commit c067770

File tree

7 files changed

+56
-36
lines changed

7 files changed

+56
-36
lines changed

ext/curl/interface.c

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1796,11 +1796,20 @@ static void curl_free_post(void **post)
17961796
}
17971797
/* }}} */
17981798

1799+
struct mime_file {
1800+
zend_string *name;
1801+
php_stream *stream;
1802+
};
1803+
17991804
/* {{{ curl_free_stream
18001805
*/
18011806
static void curl_free_stream(void **post)
18021807
{
1803-
php_stream_close((php_stream *)*post);
1808+
struct mime_file *mime_file = (struct mime_file *) *post;
1809+
1810+
ZEND_ASSERT(mime_file->stream == NULL);
1811+
zend_string_release(mime_file->name);
1812+
efree(mime_file);
18041813
}
18051814
/* }}} */
18061815

@@ -1901,7 +1910,7 @@ php_curl *alloc_curl_handle()
19011910

19021911
zend_llist_init(&ch->to_free->str, sizeof(char *), (llist_dtor_func_t)curl_free_string, 0);
19031912
zend_llist_init(&ch->to_free->post, sizeof(struct HttpPost *), (llist_dtor_func_t)curl_free_post, 0);
1904-
zend_llist_init(&ch->to_free->stream, sizeof(php_stream *), (llist_dtor_func_t)curl_free_stream, 0);
1913+
zend_llist_init(&ch->to_free->stream, sizeof(struct mime_file *), (llist_dtor_func_t)curl_free_stream, 0);
19051914

19061915
ch->to_free->slist = emalloc(sizeof(HashTable));
19071916
zend_hash_init(ch->to_free->slist, 4, NULL, curl_free_slist, 0);
@@ -2129,28 +2138,42 @@ PHP_FUNCTION(curl_copy_handle)
21292138
}
21302139
/* }}} */
21312140

2132-
#if LIBCURL_VERSION_NUM >= 0x073800
2141+
#if LIBCURL_VERSION_NUM >= 0x073800 /* 7.56.0 */
21332142
static size_t read_cb(char *buffer, size_t size, size_t nitems, void *arg) /* {{{ */
21342143
{
2135-
php_stream *stream = (php_stream *) arg;
2136-
ssize_t numread = php_stream_read(stream, buffer, nitems * size);
2144+
struct mime_file *mime_file = (struct mime_file *) arg;
2145+
ssize_t numread;
21372146

2138-
if (numread < 0) {
2139-
return CURL_READFUNC_ABORT;
2147+
if (mime_file->stream == NULL) {
2148+
if (!(mime_file->stream = php_stream_open_wrapper(ZSTR_VAL(mime_file->name), "rb", IGNORE_PATH, NULL))) {
2149+
return CURL_READFUNC_ABORT;
2150+
}
21402151
}
2141-
return numread;
2152+
numread = php_stream_read(mime_file->stream, buffer, nitems * size);
2153+
if (numread <= 0) {
2154+
php_stream_close(mime_file->stream);
2155+
mime_file->stream = NULL;
2156+
}
2157+
return (numread >= 0) ? numread : CURL_READFUNC_ABORT;
21422158
}
21432159
/* }}} */
21442160

21452161
static int seek_cb(void *arg, curl_off_t offset, int origin) /* {{{ */
21462162
{
2147-
php_stream *stream = (php_stream *) arg;
2148-
int res = php_stream_seek(stream, offset, origin);
2163+
struct mime_file *mime_file = (struct mime_file *) arg;
2164+
int res;
21492165

2166+
if (mime_file->stream == NULL) {
2167+
if (!(mime_file->stream = php_stream_open_wrapper(ZSTR_VAL(mime_file->name), "rb", IGNORE_PATH, NULL))) {
2168+
return CURL_SEEKFUNC_CANTSEEK;
2169+
}
2170+
}
2171+
res = php_stream_seek(mime_file->stream, offset, origin);
21502172
if (res) {
2151-
return CURL_SEEKFUNC_CANTSEEK;
2173+
php_stream_close(mime_file->stream);
2174+
mime_file->stream = NULL;
21522175
}
2153-
return CURL_SEEKFUNC_OK;
2176+
return !res ? CURL_SEEKFUNC_OK : CURL_SEEKFUNC_CANTSEEK;
21542177
}
21552178
/* }}} */
21562179
#endif
@@ -2793,7 +2816,7 @@ static int _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue) /* {{{
27932816
zval *prop, rv;
27942817
char *type = NULL, *filename = NULL;
27952818
#if LIBCURL_VERSION_NUM >= 0x073800 /* 7.56.0 */
2796-
php_stream *stream;
2819+
struct mime_file *mime_file;
27972820
#endif
27982821

27992822
prop = zend_read_property(curl_CURLFile_class, current, "name", sizeof("name")-1, 0, &rv);
@@ -2816,24 +2839,21 @@ static int _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue) /* {{{
28162839
}
28172840

28182841
#if LIBCURL_VERSION_NUM >= 0x073800 /* 7.56.0 */
2819-
if (!(stream = php_stream_open_wrapper(ZSTR_VAL(postval), "rb", IGNORE_PATH, NULL))) {
2820-
zend_string_release_ex(string_key, 0);
2821-
return FAILURE;
2822-
}
2842+
mime_file = emalloc(sizeof *mime_file);
2843+
mime_file->name = zend_string_copy(postval);
2844+
mime_file->stream = NULL;
28232845
part = curl_mime_addpart(mime);
28242846
if (part == NULL) {
2825-
php_stream_close(stream);
28262847
zend_string_release_ex(string_key, 0);
28272848
return FAILURE;
28282849
}
28292850
if ((form_error = curl_mime_name(part, ZSTR_VAL(string_key))) != CURLE_OK
2830-
|| (form_error = curl_mime_data_cb(part, -1, read_cb, seek_cb, NULL, stream)) != CURLE_OK
2851+
|| (form_error = curl_mime_data_cb(part, -1, read_cb, seek_cb, NULL, mime_file)) != CURLE_OK
28312852
|| (form_error = curl_mime_filename(part, filename ? filename : ZSTR_VAL(postval))) != CURLE_OK
28322853
|| (form_error = curl_mime_type(part, type ? type : "application/octet-stream")) != CURLE_OK) {
2833-
php_stream_close(stream);
28342854
error = form_error;
28352855
}
2836-
zend_llist_add_element(&ch->to_free->stream, &stream);
2856+
zend_llist_add_element(&ch->to_free->stream, &mime_file);
28372857
#else
28382858
form_error = curl_formadd(&first, &last,
28392859
CURLFORM_COPYNAME, ZSTR_VAL(string_key),

ext/curl/tests/bug27023.phpt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ var_dump(curl_exec($ch));
3838
curl_close($ch);
3939
?>
4040
--EXPECTF--
41-
string(%d) "curl_testdata1.txt|application/octet-stream"
42-
string(%d) "curl_testdata1.txt|text/plain"
43-
string(%d) "foo.txt|application/octet-stream"
44-
string(%d) "foo.txt|text/plain"
41+
string(%d) "curl_testdata1.txt|application/octet-stream|6"
42+
string(%d) "curl_testdata1.txt|text/plain|6"
43+
string(%d) "foo.txt|application/octet-stream|6"
44+
string(%d) "foo.txt|text/plain|6"

ext/curl/tests/bug77711.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ curl_close($ch);
2424
===DONE===
2525
--EXPECTF--
2626
bool(true)
27-
string(%d) "АБВ.txt|application/octet-stream"
27+
string(%d) "АБВ.txt|application/octet-stream|5"
2828
===DONE===
2929
--CLEAN--
3030
<?php

ext/curl/tests/curl_copy_handle_variation3.phpt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ curl_close($ch2);
2929
===DONE===
3030
--EXPECTF--
3131
bool(true)
32-
string(%d) "АБВ.txt|application/octet-stream"
33-
string(%d) "АБВ.txt|application/octet-stream"
32+
string(%d) "АБВ.txt|application/octet-stream|5"
33+
string(%d) "АБВ.txt|application/octet-stream|5"
3434
===DONE===
3535
--CLEAN--
3636
<?php

ext/curl/tests/curl_file_upload.phpt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,15 +60,15 @@ var_dump(curl_exec($ch));
6060
curl_close($ch);
6161
?>
6262
--EXPECTF--
63-
string(%d) "curl_testdata1.txt|application/octet-stream"
64-
string(%d) "curl_testdata1.txt|text/plain"
65-
string(%d) "foo.txt|application/octet-stream"
66-
string(%d) "foo.txt|text/plain"
63+
string(%d) "curl_testdata1.txt|application/octet-stream|6"
64+
string(%d) "curl_testdata1.txt|text/plain|6"
65+
string(%d) "foo.txt|application/octet-stream|6"
66+
string(%d) "foo.txt|text/plain|6"
6767
string(%d) "text/plain"
6868
string(%d) "%s/curl_testdata1.txt"
69-
string(%d) "curl_testdata1.txt|text/plain"
69+
string(%d) "curl_testdata1.txt|text/plain|6"
7070
string(%d) "foo.txt"
71-
string(%d) "foo.txt|application/octet-stream"
71+
string(%d) "foo.txt|application/octet-stream|6"
7272

7373
Warning: curl_setopt(): Disabling safe uploads is no longer supported in %s on line %d
7474
string(0) ""

ext/curl/tests/curl_file_upload_stream.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,5 @@ curl_close($ch);
2424
===DONE===
2525
--EXPECT--
2626
bool(true)
27-
string(21) "i-love-php|text/plain"
27+
string(24) "i-love-php|text/plain|11"
2828
===DONE===

ext/curl/tests/responder/get.inc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
break;
2929
case 'file':
3030
if (isset($_FILES['file'])) {
31-
echo $_FILES['file']['name'] . '|' . $_FILES['file']['type'];
31+
echo $_FILES['file']['name'] . '|' . $_FILES['file']['type'] . '|' . $_FILES['file']['size'];
3232
}
3333
break;
3434
case 'method':

0 commit comments

Comments
 (0)