-
Notifications
You must be signed in to change notification settings - Fork 7.8k
cURL: make possible to send file from buffer string #6456
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for the delay! This looks like a nice addition to me. I have one safety concern regarding the CURLFORM_BUFFERPTR use. And a more general design concern on whether we need all those methods on CURLStringFile at all, and can't just use typed properties.
Maybe @cmb69 or @kocsismate have thoughts on this addition as well?
ext/curl/interface.c
Outdated
CURLFORM_BUFFER, filename, | ||
CURLFORM_CONTENTTYPE, type ? type : "application/octet-stream", | ||
CURLFORM_BUFFERPTR, ZSTR_VAL(postval), | ||
CURLFORM_BUFFERLENGTH, ZSTR_LEN(postval), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is safe. According to curl docs:
is used in combination with CURLFORM_BUFFER. The parameter is a pointer to the buffer to be uploaded. This buffer must not be freed until after curl_easy_cleanup is called. You must also use CURLFORM_BUFFERLENGTH to set the number of bytes in the buffer.
This means that curl is not making a copy of the buffer. If the CURLStringFile object is destroyed before the request is sent, this will probably result in use-after-free.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @nikic! Great thanks for the code review!
This issue scares me because looks like I need to copy the data somewhere inside CurlHandle. And also copy them when cloning, and destroy them in the curl destructor. Most likely I will not have enough knowledge in the C language.
I can suggest adding CurlStringFile only for curl >= 7.56.0. Or someone will help me with this issue.
Right now, I have code changes based on all your comments, except this one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nevermind. I tried to do it myself. =)
--FILE-- | ||
<?php | ||
|
||
function testcurl($ch, $postname, $data, $mime = '') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should $mime = null
and pass it unconditionally rather than checking empty
ext/curl/curl_string_file.stub.php
Outdated
public function getData() {} | ||
|
||
/** @return string */ | ||
public function getPostFilename() {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CURLFile also has a setPostFilename
method.
Though, taking a step back here, seeing as the class has public properties, these getters and setters are really redundant. I think we'd be better off declaring them as typed properties and just omitting the methods entirely.
It depends on whether we need to have the same interface as CURLFile. It doesn't seem necessary to me, as I would expect the common usage to just construct CURLStringFile and directly pass it to curl.
string(%d) "foo.txt" | ||
string(%d) "62942c05ed0d1b501c4afe6dc1c4db1b" | ||
string(%d) "foo.txt|text/plain|62942c05ed0d1b501c4afe6dc1c4db1b" | ||
string(%d) "foo.txt|text/plain|62942c05ed0d1b501c4afe6dc1c4db1b" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: missing newline
@nikic @cmb69
Not sure if this is correct. |
ext/curl/curl_string_file.c
Outdated
Z_PARAM_STR_OR_NULL(mime) | ||
ZEND_PARSE_PARAMETERS_END(); | ||
|
||
zend_update_property_stringl(curl_CURLStringFile_class, Z_OBJ_P(object), "data", sizeof("data")-1, ZSTR_VAL(data), ZSTR_LEN(data)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will perform a copy of the data. You can use zend_update_property_str to avoid the copy.
ext/curl/curl_string_file.c
Outdated
ZEND_PARSE_PARAMETERS_END(); | ||
|
||
zend_update_property_stringl(curl_CURLStringFile_class, Z_OBJ_P(object), "data", sizeof("data")-1, ZSTR_VAL(data), ZSTR_LEN(data)); | ||
zend_update_property_string(curl_CURLStringFile_class, Z_OBJ_P(object), "postname", sizeof("postname")-1, ZSTR_VAL(postname)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here -- unless this is intentionally written this way to truncate the filename at a null byte?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did it for mime
and postname
the same way as for CURLFile. I think it required to be truncated.
But what the best way? Truncate or, for example, throw an exception if we found null-byte, or do something else?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's okay to stick with the CURLFile behavior here.
ext/curl/curl_string_file.stub.php
Outdated
class CURLStringFile | ||
{ | ||
public string $data = ""; | ||
public string $postname = ""; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can leave off the default values on these properties (and on $mime as well if you assign it unconditionally).
ext/curl/interface.c
Outdated
|
||
if (Z_TYPE_P(prop) == IS_REFERENCE) { | ||
prop = Z_REFVAL_P(prop); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ZVAL_DEREF(prop)
is simpler :)
Directly using
I think you did everything right!
Nice catch! I've backported this fix to the 7.4 branch in 54fa0a6, to make sure older versions get fixed. |
ext/curl/curl_string_file.stub.php
Outdated
public string $postname = ""; | ||
public ?string $mime = null; | ||
|
||
public function __construct(string $data, string $postname, ?string $mime = null) {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not directly related: @kocsismate I just noticed that
php-src/ext/curl/curl_file.stub.php
Line 17 in 8c00c91
public function __construct(string $filename, ?string $mime_type = null, ?string $posted_filename = null) {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Uhh 🤦
@nikic Thanks for the review! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks fine to me. Anyone else have feedback?
Personally I'd probably merge curl_string_file.c into curl_file.c, as the final implementation ended up being rather small.
ext/curl/curl_string_file.c
Outdated
ZEND_PARSE_PARAMETERS_END(); | ||
|
||
zend_update_property_stringl(curl_CURLStringFile_class, Z_OBJ_P(object), "data", sizeof("data")-1, ZSTR_VAL(data), ZSTR_LEN(data)); | ||
zend_update_property_string(curl_CURLStringFile_class, Z_OBJ_P(object), "postname", sizeof("postname")-1, ZSTR_VAL(postname)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's okay to stick with the CURLFile behavior here.
Thank you! I merged files. |
ext/curl/curl_string_file.stub.php
Outdated
<?php | ||
|
||
/** | ||
* @generate-function-entries |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can now get rid of @generate-function-entries
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it, thank you.
ext/curl/interface.c
Outdated
|
||
prop = zend_read_property(curl_CURLStringFile_class, Z_OBJ_P(current), "postname", sizeof("postname")-1, 0, &rv); | ||
ZVAL_DEREF(prop); | ||
if (Z_TYPE_P(prop) != IS_STRING) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As CURLStringFile::$postname
is a property of type string
, is this condition true only if the property is uninitialized, right? If this is the case, shouldn't we throw an exception that we do in other cases? Or am I missing something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, no need to call php_error_docref here. The exception is already being thrown inside zend_read_property. But I'm do not know how to process it correctly. Is it correct to do just this? :
prop = zend_read_property(curl_CURLStringFile_class, Z_OBJ_P(current), "postname", sizeof("postname")-1, 0, &rv);
ZVAL_DEREF(prop);
if (Z_TYPE_P(prop) != IS_STRING) {
zend_string_release_ex(string_key, 0);
return FAILURE;
}
I cannot find a good example in other code =/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would be fine, or possibly something like this?
prop = ...;
if (EG(exception)) {
zend_string_release_ex(string_key, 0);
return FAILURE;
}
ZVAL_DEREF(prop);
ZEND_ASSERT(Z_TYPE_P(prop) == IS_STRING);
ext/curl/curl_file.c
Outdated
ZEND_PARSE_PARAMETERS_END(); | ||
|
||
zend_update_property_str(curl_CURLStringFile_class, Z_OBJ_P(object), "data", sizeof("data") - 1, data); | ||
zend_update_property_string(curl_CURLStringFile_class, Z_OBJ_P(object), "postname", sizeof("postname")-1, ZSTR_VAL(postname)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is there any specific reason you use both zend_update_property_string()
and zend_update_property_str()
, even though the latter would be sufficient (and wouldn't require a ZSTR_VAL()
call)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I mentioned before we need to truncate mime and postname to null-terminated strings. But looks like it will be truncated during reading their values. So we can use zend_update_property_str for all parameters.
But I do not know how to provide a default value for mime property for method zend_update_property_str. I can use "zend_string_init" method, but at least I do not know which value for its third option I must use.
Right now I know only this way:
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");
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You suggested code looks reasonable to me.
For zend_string_init the last parameter should be 0, but in this case it's probably a bit nicer to write it as you did.
@kocsismate @nikic, Thanks a lot for the review! I updated the PR again =) |
> Added `CURLStringFile`, which can be used to post a file from a string rather > than a file: > ```php > $file = new CURLStringFile($data, 'filename.txt', 'text/plain'); > curl_setopt($curl, CURLOPT_POSTFIELDS, ['file' => $file]); > ``` Includes unit test. Refs: * https://github.com/php/php-src/blob/f67986a9218f4889d9352a87c29337a5b6eaa4bd/UPGRADING#L231-L235 * php/php-src#6456 * php/php-src@e727919
> Added `CURLStringFile`, which can be used to post a file from a string rather than a file Refs: * https://github.com/php/php-src/blob/f67986a9218f4889d9352a87c29337a5b6eaa4bd/UPGRADING#L231-L235 * php/php-src#6456 * php/php-src@e727919 As part of the Fibers RFC/PR, the Reflection extension also received a new class. * https://wiki.php.net/rfc/fibers * php/php-src#6875 * php/php-src@c276c16 Note: I've not listed the `Fiber` and `FiberError` classes as those could be considered covered via the mention of Fibers on the "New Features" page, though happy to update the commit if so desired.
> Added `CURLStringFile`, which can be used to post a file from a string rather than a file Refs: * https://github.com/php/php-src/blob/f67986a9218f4889d9352a87c29337a5b6eaa4bd/UPGRADING#L231-L235 * php/php-src#6456 * php/php-src@e727919 As part of the Fibers RFC/PR, the Reflection extension also received a new class. * https://wiki.php.net/rfc/fibers * php/php-src#6875 * php/php-src@c276c16 Note: I've not listed the `Fiber` and `FiberError` classes as those could be considered covered via the mention of Fibers on the "New Features" page, though happy to update the commit if so desired. Co-authored-by: jrfnl <jrfnl@users.noreply.github.com> Closes GH-1447.
> Added `CURLStringFile`, which can be used to post a file from a string rather than a file Refs: * https://github.com/php/php-src/blob/f67986a9218f4889d9352a87c29337a5b6eaa4bd/UPGRADING#L231-L235 * php/php-src#6456 * php/php-src@e727919 As part of the Fibers RFC/PR, the Reflection extension also received a new class. * https://wiki.php.net/rfc/fibers * php/php-src#6875 * php/php-src@c276c16 Note: I've not listed the `Fiber` and `FiberError` classes as those could be considered covered via the mention of Fibers on the "New Features" page, though happy to update the commit if so desired. Co-authored-by: jrfnl <jrfnl@users.noreply.github.com> Closes phpGH-1447.
In my practice, very often I needed to send a file that was created in the current script and stored in memory. Most often these were small generated images, PDF documents, XML files, and etc.
At the current moment in PHP we can send a file from a string variable only through a temporary file on a file system(CURLFile) or with unnecessary base64 converting:
However, libcurl itself has an easy way to send a file from a string.
Since sending a file in this way always requires passing the file name, it will not work to embed this functionality into an existing CurlFile class without losing backward compatibility. Therefore, I decided to create a new class CURLStringFile.
Usage: