Skip to content

Commit f0efdd4

Browse files
committed
ext/curl: Add CURLOPT_PREREQFUNCTION
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
1 parent 765382e commit f0efdd4

File tree

6 files changed

+186
-1
lines changed

6 files changed

+186
-1
lines changed

UPGRADING

+11
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,12 @@ PHP 8.4 UPGRADE NOTES
143143
2. New Features
144144
========================================
145145

146+
- Curl:
147+
. Added CURLOPT_PREREQFUNCTION option which accepts a callable that gets
148+
called after a connection is established (including TLS handshake), but
149+
before the request is made. The function must return CURL_PREREQFUNC_OK
150+
or CURL_PREREQFUNC_ABORT to allow or abort the request.
151+
146152
- Date:
147153
. Added static methods
148154
DateTime[Immutable]::createFromTimestamp(int|float $timestamp): static.
@@ -418,6 +424,11 @@ PDO_SQLITE:
418424
- Core:
419425
. PHP_OUTPUT_HANDLER_PROCESSED.
420426

427+
- Curl:
428+
. CURLOPT_PREREQFUNCTION.
429+
. CURL_PREREQFUNC_OK.
430+
. CURL_PREREQFUNC_ABORT.
431+
421432
- Intl:
422433
. The IntlDateFormatter class exposes now the new PATTERN constant
423434
reflecting udat api's UDAT_PATTERN.

ext/curl/curl.stub.php

+15
Original file line numberDiff line numberDiff line change
@@ -3553,6 +3553,21 @@
35533553
* @cvalue CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256
35543554
*/
35553555
const CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256 = UNKNOWN;
3556+
/**
3557+
* @var int
3558+
* @cvalue CURLOPT_PREREQFUNCTION
3559+
*/
3560+
const CURLOPT_PREREQFUNCTION = UNKNOWN;
3561+
/**
3562+
* @var int
3563+
* @cvalue CURL_PREREQFUNC_OK
3564+
*/
3565+
const CURL_PREREQFUNC_OK = UNKNOWN;
3566+
/**
3567+
* @var int
3568+
* @cvalue CURL_PREREQFUNC_ABORT
3569+
*/
3570+
const CURL_PREREQFUNC_ABORT = UNKNOWN;
35563571
#endif
35573572

35583573
#if LIBCURL_VERSION_NUM >= 0x075100 /* Available since 7.81.0 */

ext/curl/curl_arginfo.h

+10-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ext/curl/curl_private.h

+3
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ typedef struct {
7575
php_curl_callback *progress;
7676
#if LIBCURL_VERSION_NUM >= 0x072000
7777
php_curl_callback *xferinfo;
78+
#endif
79+
#if LIBCURL_VERSION_NUM >= 0x075000
80+
php_curl_callback *prereq;
7881
#endif
7982
php_curl_callback *fnmatch;
8083
#if LIBCURL_VERSION_NUM >= 0x075400

ext/curl/interface.c

+97
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,12 @@ static HashTable *curl_get_gc(zend_object *object, zval **table, int *n)
511511
}
512512
#endif
513513

514+
#if LIBCURL_VERSION_NUM >= 0x075000
515+
if (curl->handlers.prereq) {
516+
zend_get_gc_buffer_add_zval(gc_buffer, &curl->handlers.prereq->func_name);
517+
}
518+
#endif
519+
514520
if (curl->handlers.fnmatch) {
515521
zend_get_gc_buffer_add_zval(gc_buffer, &curl->handlers.fnmatch->func_name);
516522
}
@@ -956,6 +962,66 @@ static size_t curl_write_header(char *data, size_t size, size_t nmemb, void *ctx
956962
}
957963
/* }}} */
958964

965+
#if LIBCURL_VERSION_NUM >= 0x075000
966+
static int curl_prereqfunction(void *clientp, char *conn_primary_ip, char *conn_local_ip, int conn_primary_port, int conn_local_port)
967+
{
968+
php_curl *ch = (php_curl *)clientp;
969+
php_curl_callback *t = ch->handlers.prereq;
970+
971+
int rval = CURL_PREREQFUNC_ABORT; /* Disallow the request by default. */
972+
973+
#if PHP_CURL_DEBUG
974+
fprintf(stderr, "curl_prereqfunction() called\n");
975+
fprintf(stderr, "clientp = %x, conn_primary_ip = %s, conn_local_ip = %s, conn_primary_port = %d, conn_local_port = %d\n", clientp, conn_primary_ip, conn_local_ip, c, onn_primary_port, conn_local_port);
976+
#endif
977+
978+
zval argv[5];
979+
zval retval;
980+
zend_result error;
981+
zend_fcall_info fci;
982+
983+
GC_ADDREF(&ch->std);
984+
ZVAL_OBJ(&argv[0], &ch->std);
985+
ZVAL_STRING(&argv[1], conn_primary_ip);
986+
ZVAL_STRING(&argv[2], conn_local_ip);
987+
ZVAL_LONG(&argv[3], conn_primary_port);
988+
ZVAL_LONG(&argv[4], conn_local_port);
989+
990+
fci.size = sizeof(fci);
991+
ZVAL_COPY_VALUE(&fci.function_name, &t->func_name);
992+
fci.object = NULL;
993+
fci.retval = &retval;
994+
fci.param_count = 5;
995+
fci.params = argv;
996+
fci.named_params = NULL;
997+
998+
ch->in_callback = 1;
999+
error = zend_call_function(&fci, &t->fci_cache);
1000+
ch->in_callback = 0;
1001+
if (error == FAILURE) {
1002+
php_error_docref(NULL, E_WARNING, "Cannot call the CURLOPT_PREREQFUNCTION");
1003+
} else if (!Z_ISUNDEF(retval)) {
1004+
_php_curl_verify_handlers(ch, true);
1005+
if (Z_TYPE(retval) == IS_LONG) {
1006+
zend_long retval_long = Z_LVAL(retval);
1007+
if (retval_long == CURL_PREREQFUNC_OK || retval_long == CURL_PREREQFUNC_ABORT) {
1008+
rval = retval_long;
1009+
} else {
1010+
zend_throw_error(NULL, "The CURLOPT_PREREQFUNCTION callback must return either CURL_PREREQFUNC_OK or CURL_PREREQFUNC_ABORT");
1011+
}
1012+
} else {
1013+
zend_throw_error(NULL, "The CURLOPT_PREREQFUNCTION callback must return either CURL_PREREQFUNC_OK or CURL_PREREQFUNC_ABORT");
1014+
}
1015+
}
1016+
zval_ptr_dtor(&argv[0]);
1017+
zval_ptr_dtor(&argv[1]);
1018+
zval_ptr_dtor(&argv[2]);
1019+
1020+
return rval;
1021+
}
1022+
1023+
#endif
1024+
9591025
static int curl_debug(CURL *cp, curl_infotype type, char *buf, size_t buf_len, void *ctx) /* {{{ */
9601026
{
9611027
php_curl *ch = (php_curl *)ctx;
@@ -1090,6 +1156,9 @@ void init_curl_handle(php_curl *ch)
10901156
ch->handlers.progress = NULL;
10911157
#if LIBCURL_VERSION_NUM >= 0x072000
10921158
ch->handlers.xferinfo = NULL;
1159+
#endif
1160+
#if LIBCURL_VERSION_NUM >= 0x075000
1161+
ch->handlers.prereq = NULL;
10931162
#endif
10941163
ch->handlers.fnmatch = NULL;
10951164
#if LIBCURL_VERSION_NUM >= 0x075400
@@ -1272,6 +1341,9 @@ void _php_setup_easy_copy_handlers(php_curl *ch, php_curl *source)
12721341
_php_copy_callback(ch, &ch->handlers.progress, source->handlers.progress, CURLOPT_PROGRESSDATA);
12731342
#if LIBCURL_VERSION_NUM >= 0x072000
12741343
_php_copy_callback(ch, &ch->handlers.xferinfo, source->handlers.xferinfo, CURLOPT_XFERINFODATA);
1344+
#endif
1345+
#if LIBCURL_VERSION_NUM >= 0x075000
1346+
_php_copy_callback(ch, &ch->handlers.prereq, source->handlers.prereq, CURLOPT_PREREQFUNCTION);
12751347
#endif
12761348
_php_copy_callback(ch, &ch->handlers.fnmatch, source->handlers.fnmatch, CURLOPT_FNMATCH_DATA);
12771349
#if LIBCURL_VERSION_NUM >= 0x075400
@@ -2330,6 +2402,20 @@ static zend_result _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue
23302402
break;
23312403
#endif
23322404

2405+
#if LIBCURL_VERSION_NUM >= 0x075000
2406+
case CURLOPT_PREREQFUNCTION:
2407+
curl_easy_setopt(ch->cp, CURLOPT_PREREQFUNCTION, curl_prereqfunction);
2408+
curl_easy_setopt(ch->cp, CURLOPT_PREREQDATA, ch);
2409+
if (ch->handlers.prereq == NULL) {
2410+
ch->handlers.prereq = ecalloc(1, sizeof(php_curl_callback));
2411+
} else if (!Z_ISUNDEF(ch->handlers.prereq->func_name)) {
2412+
zval_ptr_dtor(&ch->handlers.prereq->func_name);
2413+
ch->handlers.prereq->fci_cache = empty_fcall_info_cache;
2414+
}
2415+
ZVAL_COPY(&ch->handlers.prereq->func_name, zvalue);
2416+
break;
2417+
#endif
2418+
23332419
/* Curl off_t options */
23342420
case CURLOPT_MAX_RECV_SPEED_LARGE:
23352421
case CURLOPT_MAX_SEND_SPEED_LARGE:
@@ -2991,6 +3077,9 @@ static void curl_free_obj(zend_object *object)
29913077
#if LIBCURL_VERSION_NUM >= 0x075400
29923078
_php_curl_free_callback(ch->handlers.sshhostkey);
29933079
#endif
3080+
#if LIBCURL_VERSION_NUM >= 0x075000
3081+
_php_curl_free_callback(ch->handlers.prereq);
3082+
#endif
29943083

29953084
zval_ptr_dtor(&ch->postfields);
29963085
zval_ptr_dtor(&ch->private_data);
@@ -3067,6 +3156,14 @@ static void _php_curl_reset_handlers(php_curl *ch)
30673156
}
30683157
#endif
30693158

3159+
#if LIBCURL_VERSION_NUM >= 0x075000
3160+
if (ch->handlers.prereq) {
3161+
zval_ptr_dtor(&ch->handlers.prereq->func_name);
3162+
efree(ch->handlers.prereq);
3163+
ch->handlers.prereq = NULL;
3164+
}
3165+
#endif
3166+
30703167
if (ch->handlers.fnmatch) {
30713168
zval_ptr_dtor(&ch->handlers.fnmatch->func_name);
30723169
efree(ch->handlers.fnmatch);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
--TEST--
2+
Curl option CURLOPT_PREREQFUNCTION
3+
--EXTENSIONS--
4+
curl
5+
--SKIPIF--
6+
<?php
7+
$curl_version = curl_version();
8+
if ($curl_version['version_number'] < 0x075000) die("skip: test works only with curl >= 7.80.0");
9+
?>
10+
--FILE--
11+
<?php
12+
include 'server.inc';
13+
14+
$ch = curl_init();
15+
16+
$host = curl_cli_server_start();
17+
$url = "{$host}/get.inc?test=";
18+
curl_setopt($ch, CURLOPT_URL, $url);
19+
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
20+
21+
var_dump(CURLOPT_PREREQFUNCTION);
22+
var_dump(CURL_PREREQFUNC_OK);
23+
var_dump(CURL_PREREQFUNC_ABORT);
24+
25+
curl_setopt($ch, CURLOPT_PREREQFUNCTION, function() {
26+
var_dump('callback');
27+
var_dump(func_num_args());
28+
$args = func_get_args();
29+
var_dump(get_class($args[0]));
30+
var_dump($args[1]);
31+
var_dump($args[2]);
32+
var_dump(is_int($args[3]));
33+
var_dump(is_int($args[4]));
34+
35+
return CURL_PREREQFUNC_ABORT;
36+
});
37+
38+
curl_exec($ch);
39+
?>
40+
--EXPECT--
41+
int(20312)
42+
int(0)
43+
int(1)
44+
string(8) "callback"
45+
int(5)
46+
string(10) "CurlHandle"
47+
string(9) "127.0.0.1"
48+
string(9) "127.0.0.1"
49+
bool(true)
50+
bool(true)

0 commit comments

Comments
 (0)