Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 47 additions & 1 deletion ext/curl/interface.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand All @@ -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;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be worth to support non seekable stream, like URL, which is a completely legit scenario.

Thanks.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That may not always work (see https://curl.haxx.se/libcurl/c/curl_mime_data_cb.html), but we still could fail in this case.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Passing -1 for data length should be ok in general and would spare the stat call. Not requiring stream to be seekable and setting the seek function to NULL might be still a fallback. Knowing the stream size ahead is good, but doesn't guarantee we still don't end up with a partial data as it's stream dependent.

Thanks.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The curl docs are not particularly clear regarding the datasize, but it seems passing -1 works.

Instead of passing NULL as seekfunc I'd suggest to have a callback which tries to seek, and returns CURL_SEEKFUNC_CANTSEEK otherwise. which indicates that "libcurl is free to work around the problem if possible".

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, could be probably an option, too. Thanks.

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),
Expand Down Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions ext/curl/php_curl.h
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ struct _php_curl_send_headers {
struct _php_curl_free {
zend_llist str;
zend_llist post;
zend_llist stream;
HashTable *slist;
};

Expand Down
32 changes: 32 additions & 0 deletions ext/curl/tests/bug77711.phpt
Original file line number Diff line number Diff line change
@@ -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');
?>
38 changes: 38 additions & 0 deletions ext/curl/tests/curl_copy_handle_variation3.phpt
Original file line number Diff line number Diff line change
@@ -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');
?>
28 changes: 28 additions & 0 deletions ext/curl/tests/curl_file_upload_stream.phpt
Original file line number Diff line number Diff line change
@@ -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===