1
0
mirror of https://github.com/php/php-src.git synced 2026-03-24 00:02:20 +01:00

ext/curl: Add CURLOPT_PREREQFUNCTION (#13255)

Curl >= 7.80.0 supports declaring a function for `CURLOPT_PREREQFUNCTION` option
that gets called after Curl establishes a connection (including the TLS handshake
for HTTPS connections), but before the actual request is made.

The callable must return either `CURL_PREREQFUNC_OK` or `CURL_PREREQFUNC_ABORT` to
allow or abort the request.

This adds support for it to PHP with required ifdef.

 - libc: https://curl.se/libcurl/c/CURLOPT_PREREQFUNCTION.html

Co-authored-by: Gina Peter Bnayard <girgias@php.net>
This commit is contained in:
Ayesh Karunaratne
2024-08-26 19:33:16 +07:00
committed by GitHub
parent 555b603d23
commit a3b7cc2217
6 changed files with 289 additions and 2 deletions

View File

@@ -3497,6 +3497,21 @@ const CURLOPT_MAXLIFETIME_CONN = UNKNOWN;
* @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 */

View File

@@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
* Stub hash: ddfcdd8a0bf0ee6c338ec1689c6de5d7fd87303d */
* Stub hash: 3a5bd4e561f08f0dbd26383132a771acc8192fff */
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_curl_close, 0, 1, IS_VOID, 0)
ZEND_ARG_OBJ_INFO(0, handle, CurlHandle, 0)
@@ -1061,6 +1061,15 @@ static void register_curl_symbols(int module_number)
#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */
REGISTER_LONG_CONSTANT("CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256", CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256, CONST_PERSISTENT);
#endif
#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */
REGISTER_LONG_CONSTANT("CURLOPT_PREREQFUNCTION", CURLOPT_PREREQFUNCTION, CONST_PERSISTENT);
#endif
#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */
REGISTER_LONG_CONSTANT("CURL_PREREQFUNC_OK", CURL_PREREQFUNC_OK, CONST_PERSISTENT);
#endif
#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */
REGISTER_LONG_CONSTANT("CURL_PREREQFUNC_ABORT", CURL_PREREQFUNC_ABORT, CONST_PERSISTENT);
#endif
#if LIBCURL_VERSION_NUM >= 0x075100 /* Available since 7.81.0 */
REGISTER_LONG_CONSTANT("CURLOPT_MIME_OPTIONS", CURLOPT_MIME_OPTIONS, CONST_PERSISTENT);
#endif

View File

@@ -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

View File

@@ -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);
@@ -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)
{
@@ -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
@@ -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
@@ -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
@@ -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);
@@ -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);

View File

@@ -17,6 +17,7 @@ const MIN_SUPPORTED_CURL_VERSION = '7.61.0';
const IGNORED_CURL_CONSTANTS = [
'CURLOPT_PROGRESSDATA',
'CURLOPT_XFERINFODATA',
'CURLOPT_PREREQDATA',
];
const IGNORED_PHP_CONSTANTS = [

View File

@@ -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);
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