Skip to content

ext/curl: Add CURLOPT_PREREQFUNCTION #13255

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

Merged
merged 1 commit into from
Aug 26, 2024
Merged
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
15 changes: 15 additions & 0 deletions ext/curl/curl.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -3497,6 +3497,21 @@
* @cvalue CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256
*/
const CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256 = UNKNOWN;
/**
* @var int
* @cvalue CURLOPT_PREREQFUNCTION
*/
const CURLOPT_PREREQFUNCTION = UNKNOWN;
/**
* @var int
* @cvalue CURL_PREREQFUNC_OK
*/
const CURL_PREREQFUNC_OK = UNKNOWN;
/**
* @var int
* @cvalue CURL_PREREQFUNC_ABORT
*/
const CURL_PREREQFUNC_ABORT = UNKNOWN;
#endif

#if LIBCURL_VERSION_NUM >= 0x075100 /* Available since 7.81.0 */
Expand Down
11 changes: 10 additions & 1 deletion ext/curl/curl_arginfo.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions ext/curl/curl_private.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ typedef struct {
zend_fcall_info_cache progress;
zend_fcall_info_cache xferinfo;
zend_fcall_info_cache fnmatch;
#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */
zend_fcall_info_cache prereq;
#endif
#if LIBCURL_VERSION_NUM >= 0x075400 /* Available since 7.84.0 */
zend_fcall_info_cache sshhostkey;
#endif
Expand Down
79 changes: 78 additions & 1 deletion ext/curl/interface.c
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,11 @@ static HashTable *curl_get_gc(zend_object *object, zval **table, int *n)
zend_get_gc_buffer_add_fcc(gc_buffer, &curl->handlers.fnmatch);
}

#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */
if (ZEND_FCC_INITIALIZED(curl->handlers.prereq)) {
zend_get_gc_buffer_add_fcc(gc_buffer, &curl->handlers.prereq);
}
#endif
#if LIBCURL_VERSION_NUM >= 0x075400 /* Available since 7.84.0 */
if (ZEND_FCC_INITIALIZED(curl->handlers.sshhostkey)) {
zend_get_gc_buffer_add_fcc(gc_buffer, &curl->handlers.sshhostkey);
Expand Down Expand Up @@ -709,6 +714,60 @@ static size_t curl_xferinfo(void *clientp, curl_off_t dltotal, curl_off_t dlnow,
}
/* }}} */

#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */
static int curl_prereqfunction(void *clientp, char *conn_primary_ip, char *conn_local_ip, int conn_primary_port, int conn_local_port)
{
php_curl *ch = (php_curl *)clientp;
int rval = CURL_PREREQFUNC_OK;

// when CURLOPT_PREREQFUNCTION is set to null, curl_prereqfunction still
// gets called. Return CURL_PREREQFUNC_OK immediately in this case to avoid
// zend_call_known_fcc() with an uninitialized FCC.
if (!ZEND_FCC_INITIALIZED(ch->handlers.prereq)) {
return rval;
}

#if PHP_CURL_DEBUG
fprintf(stderr, "curl_prereqfunction() called\n");
fprintf(stderr, "conn_primary_ip = %s, conn_local_ip = %s, conn_primary_port = %d, conn_local_port = %d\n", conn_primary_ip, conn_local_ip, conn_primary_port, conn_local_port);
#endif

zval args[5];
zval retval;

GC_ADDREF(&ch->std);
ZVAL_OBJ(&args[0], &ch->std);
ZVAL_STRING(&args[1], conn_primary_ip);
ZVAL_STRING(&args[2], conn_local_ip);
ZVAL_LONG(&args[3], conn_primary_port);
ZVAL_LONG(&args[4], conn_local_port);

ch->in_callback = true;
zend_call_known_fcc(&ch->handlers.prereq, &retval, /* param_count */ 5, args, /* named_params */ NULL);
ch->in_callback = false;

if (!Z_ISUNDEF(retval)) {
_php_curl_verify_handlers(ch, /* reporterror */ true);
if (Z_TYPE(retval) == IS_LONG) {
zend_long retval_long = Z_LVAL(retval);
if (retval_long == CURL_PREREQFUNC_OK || retval_long == CURL_PREREQFUNC_ABORT) {
rval = retval_long;
} else {
zend_value_error("The CURLOPT_PREREQFUNCTION callback must return either CURL_PREREQFUNC_OK or CURL_PREREQFUNC_ABORT");
}
} else {
zend_type_error("The CURLOPT_PREREQFUNCTION callback must return either CURL_PREREQFUNC_OK or CURL_PREREQFUNC_ABORT");
}
}

zval_ptr_dtor(&args[0]);
zval_ptr_dtor(&args[1]);
zval_ptr_dtor(&args[2]);

return rval;
}
#endif

#if LIBCURL_VERSION_NUM >= 0x075400 /* Available since 7.84.0 */
static int curl_ssh_hostkeyfunction(void *clientp, int keytype, const char *key, size_t keylen)
{
Expand Down Expand Up @@ -1037,6 +1096,9 @@ void init_curl_handle(php_curl *ch)
ch->handlers.progress = empty_fcall_info_cache;
ch->handlers.xferinfo = empty_fcall_info_cache;
ch->handlers.fnmatch = empty_fcall_info_cache;
#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */
ch->handlers.prereq = empty_fcall_info_cache;
#endif
#if LIBCURL_VERSION_NUM >= 0x075400 /* Available since 7.84.0 */
ch->handlers.sshhostkey = empty_fcall_info_cache;
#endif
Expand Down Expand Up @@ -1210,6 +1272,9 @@ void _php_setup_easy_copy_handlers(php_curl *ch, php_curl *source)
php_curl_copy_fcc_with_option(ch, CURLOPT_PROGRESSDATA, &ch->handlers.progress, &source->handlers.progress);
php_curl_copy_fcc_with_option(ch, CURLOPT_XFERINFODATA, &ch->handlers.xferinfo, &source->handlers.xferinfo);
php_curl_copy_fcc_with_option(ch, CURLOPT_FNMATCH_DATA, &ch->handlers.fnmatch, &source->handlers.fnmatch);
#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */
php_curl_copy_fcc_with_option(ch, CURLOPT_PREREQDATA, &ch->handlers.prereq, &source->handlers.prereq);
#endif
#if LIBCURL_VERSION_NUM >= 0x075400 /* Available since 7.84.0 */
php_curl_copy_fcc_with_option(ch, CURLOPT_SSH_HOSTKEYDATA, &ch->handlers.sshhostkey, &source->handlers.sshhostkey);
#endif
Expand Down Expand Up @@ -1570,6 +1635,9 @@ static zend_result _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue
HANDLE_CURL_OPTION_CALLABLE(ch, CURLOPT_PROGRESS, handlers.progress, curl_progress);
HANDLE_CURL_OPTION_CALLABLE(ch, CURLOPT_XFERINFO, handlers.xferinfo, curl_xferinfo);
HANDLE_CURL_OPTION_CALLABLE(ch, CURLOPT_FNMATCH_, handlers.fnmatch, curl_fnmatch);
#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */
HANDLE_CURL_OPTION_CALLABLE(ch, CURLOPT_PREREQ, handlers.prereq, curl_prereqfunction);
#endif
#if LIBCURL_VERSION_NUM >= 0x075400 /* Available since 7.84.0 */
HANDLE_CURL_OPTION_CALLABLE(ch, CURLOPT_SSH_HOSTKEY, handlers.sshhostkey, curl_ssh_hostkeyfunction);
#endif
Expand Down Expand Up @@ -2736,6 +2804,11 @@ static void curl_free_obj(zend_object *object)
if (ZEND_FCC_INITIALIZED(ch->handlers.fnmatch)) {
zend_fcc_dtor(&ch->handlers.fnmatch);
}
#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */
if (ZEND_FCC_INITIALIZED(ch->handlers.prereq)) {
zend_fcc_dtor(&ch->handlers.prereq);
}
#endif
#if LIBCURL_VERSION_NUM >= 0x075400 /* Available since 7.84.0 */
if (ZEND_FCC_INITIALIZED(ch->handlers.sshhostkey)) {
zend_fcc_dtor(&ch->handlers.sshhostkey);
Expand Down Expand Up @@ -2814,7 +2887,11 @@ static void _php_curl_reset_handlers(php_curl *ch)
if (ZEND_FCC_INITIALIZED(ch->handlers.fnmatch)) {
zend_fcc_dtor(&ch->handlers.fnmatch);
}

#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */
if (ZEND_FCC_INITIALIZED(ch->handlers.prereq)) {
zend_fcc_dtor(&ch->handlers.prereq);
}
#endif
#if LIBCURL_VERSION_NUM >= 0x075400 /* Available since 7.84.0 */
if (ZEND_FCC_INITIALIZED(ch->handlers.sshhostkey)) {
zend_fcc_dtor(&ch->handlers.sshhostkey);
Expand Down
1 change: 1 addition & 0 deletions ext/curl/sync-constants.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
const IGNORED_CURL_CONSTANTS = [
'CURLOPT_PROGRESSDATA',
'CURLOPT_XFERINFODATA',
'CURLOPT_PREREQDATA',
];

const IGNORED_PHP_CONSTANTS = [
Expand Down
182 changes: 182 additions & 0 deletions ext/curl/tests/curl_setopt_CURLOPT_PREREQFUNCTION.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
--TEST--
Curl option CURLOPT_PREREQFUNCTION
--EXTENSIONS--
curl
filter
--SKIPIF--
<?php
$curl_version = curl_version();
if ($curl_version['version_number'] < 0x075000) die("skip: test works only with curl >= 7.80.0");
?>
--FILE--
<?php
include 'server.inc';

$host = curl_cli_server_start();
$port = (int) (explode(':', $host))[1];

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "{$host}/get.inc?test=file");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

$result = curl_exec($ch);

var_dump(CURLOPT_PREREQFUNCTION);
var_dump(CURL_PREREQFUNC_OK);
var_dump(CURL_PREREQFUNC_ABORT);

$returnValue = CURL_PREREQFUNC_ABORT;

echo "\nTesting with CURL_PREREQFUNC_ABORT\n";
$callback = function() use ($port, &$returnValue) {
var_dump('callback');
var_dump(func_num_args());
$args = func_get_args();
var_dump(get_class($args[0]));
var_dump(filter_var($args[1], FILTER_VALIDATE_IP) !== false);
var_dump(filter_var($args[2], FILTER_VALIDATE_IP) !== false);
Comment on lines +36 to +37
Copy link
Member

Choose a reason for hiding this comment

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

Would it be possible to have the test not rely on the filter extension? Found this out while trying it locally without ext/filter compiled.

Otherwise it needs to be added to the EXTENSIONS section as I did.

var_dump($port === $args[3]);
var_dump(is_int($args[4]));

return $returnValue;
};

curl_setopt($ch, CURLOPT_PREREQFUNCTION, $callback);

$result = curl_exec($ch);

var_dump($result);
var_dump(curl_error($ch));
var_dump(curl_errno($ch));

$returnValue = CURL_PREREQFUNC_OK;

echo "\nTesting with CURL_PREREQFUNC_OK\n";
$result = curl_exec($ch);

var_dump($result);
var_dump(curl_error($ch));
var_dump(curl_errno($ch));

echo "\nTesting with curl_copy_handle\n";
$ch2 = curl_copy_handle($ch);
$result = curl_exec($ch2);
var_dump($result);
var_dump(curl_error($ch2));
var_dump(curl_errno($ch2));

echo "\nTesting with no return type\n";
curl_setopt($ch, CURLOPT_PREREQFUNCTION, function() use ($port) {
// returns nothing
});
try {
curl_exec($ch);
} catch (\TypeError $e) {
echo $e->getMessage() . \PHP_EOL;
}

echo "\nTesting with invalid type\n";
curl_setopt($ch, CURLOPT_PREREQFUNCTION, function() use ($port) {
return 'this should be an integer';
});
try {
curl_exec($ch);
} catch (\TypeError $e) {
echo $e->getMessage() . \PHP_EOL;
}

echo "\nTesting with invalid value\n";
curl_setopt($ch, CURLOPT_PREREQFUNCTION, function() use ($port) {
return 42;
});
try {
curl_exec($ch);
} catch (\ValueError $e) {
echo $e->getMessage() . \PHP_EOL;
}

echo "\nTesting with invalid option value\n";
try {
curl_setopt($ch, CURLOPT_PREREQFUNCTION, 42);
} catch (\TypeError $e) {
echo $e->getMessage() . \PHP_EOL;
}

echo "\nTesting with invalid option callback\n";
try {
curl_setopt($ch, CURLOPT_PREREQFUNCTION, 'function_does_not_exist');
} catch (\TypeError $e) {
echo $e->getMessage() . \PHP_EOL;
}

echo "\nTesting with null as the callback\n";
var_dump(curl_setopt($ch, CURLOPT_PREREQFUNCTION, null));
var_dump(curl_exec($ch));
var_dump(curl_error($ch));
var_dump(curl_errno($ch));

echo "\nDone";
?>
--EXPECT--
int(20312)
int(0)
int(1)

Testing with CURL_PREREQFUNC_ABORT
string(8) "callback"
int(5)
string(10) "CurlHandle"
bool(true)
bool(true)
bool(true)
bool(true)
bool(false)
string(41) "operation aborted by pre-request callback"
int(42)

Testing with CURL_PREREQFUNC_OK
string(8) "callback"
int(5)
string(10) "CurlHandle"
bool(true)
bool(true)
bool(true)
bool(true)
string(0) ""
string(0) ""
int(0)

Testing with curl_copy_handle
string(8) "callback"
int(5)
string(10) "CurlHandle"
bool(true)
bool(true)
bool(true)
bool(true)
string(0) ""
string(0) ""
int(0)

Testing with no return type
The CURLOPT_PREREQFUNCTION callback must return either CURL_PREREQFUNC_OK or CURL_PREREQFUNC_ABORT

Testing with invalid type
The CURLOPT_PREREQFUNCTION callback must return either CURL_PREREQFUNC_OK or CURL_PREREQFUNC_ABORT

Testing with invalid value
The CURLOPT_PREREQFUNCTION callback must return either CURL_PREREQFUNC_OK or CURL_PREREQFUNC_ABORT

Testing with invalid option value
curl_setopt(): Argument #3 ($value) must be a valid callback for option CURLOPT_PREREQFUNCTION, no array or string given

Testing with invalid option callback
curl_setopt(): Argument #3 ($value) must be a valid callback for option CURLOPT_PREREQFUNCTION, function "function_does_not_exist" not found or invalid function name

Testing with null as the callback
bool(true)
string(0) ""
string(0) ""
int(0)

Done
Loading