Skip to content

Commit 3350b4a

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 bb21d19 commit 3350b4a

7 files changed

+172
-2
lines changed

UPGRADING

+11
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,12 @@ PHP 8.4 UPGRADE NOTES
186186
. curl_version() returns an additional feature_list value, which is an
187187
associative array of all known Curl features, and whether they are
188188
supported (true) or not (false).
189+
. Added CURLOPT_PREREQFUNCTION option which accepts a callable that gets
190+
called after a connection is established (including TLS handshake), but
191+
before the request is made. The function is called with the CurlHandle
192+
object, primary IP, local IP, primary port, and the local port, and it
193+
must return CURL_PREREQFUNC_OK or CURL_PREREQFUNC_ABORT to allow or abort
194+
the request.
189195

190196
- Date:
191197
. Added static methods
@@ -588,6 +594,11 @@ PHP 8.4 UPGRADE NOTES
588594
- Core:
589595
. PHP_OUTPUT_HANDLER_PROCESSED.
590596

597+
- Curl:
598+
. CURLOPT_PREREQFUNCTION.
599+
. CURL_PREREQFUNC_OK.
600+
. CURL_PREREQFUNC_ABORT.
601+
591602
- Intl:
592603
. The IntlDateFormatter class exposes now the new PATTERN constant
593604
reflecting udat api's UDAT_PATTERN.

ext/curl/curl.stub.php

+15
Original file line numberDiff line numberDiff line change
@@ -3485,6 +3485,21 @@
34853485
* @cvalue CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256
34863486
*/
34873487
const CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256 = UNKNOWN;
3488+
/**
3489+
* @var int
3490+
* @cvalue CURLOPT_PREREQFUNCTION
3491+
*/
3492+
const CURLOPT_PREREQFUNCTION = UNKNOWN;
3493+
/**
3494+
* @var int
3495+
* @cvalue CURL_PREREQFUNC_OK
3496+
*/
3497+
const CURL_PREREQFUNC_OK = UNKNOWN;
3498+
/**
3499+
* @var int
3500+
* @cvalue CURL_PREREQFUNC_ABORT
3501+
*/
3502+
const CURL_PREREQFUNC_ABORT = UNKNOWN;
34883503
#endif
34893504

34903505
#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
@@ -68,6 +68,9 @@ typedef struct {
6868
zend_fcall_info_cache progress;
6969
zend_fcall_info_cache xferinfo;
7070
zend_fcall_info_cache fnmatch;
71+
#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */
72+
zend_fcall_info_cache prereq;
73+
#endif
7174
#if LIBCURL_VERSION_NUM >= 0x075400 /* Available since 7.84.0 */
7275
zend_fcall_info_cache sshhostkey;
7376
#endif

ext/curl/interface.c

+71-1
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,11 @@ static HashTable *curl_get_gc(zend_object *object, zval **table, int *n)
506506
zend_get_gc_buffer_add_fcc(gc_buffer, &curl->handlers.fnmatch);
507507
}
508508

509+
#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */
510+
if (ZEND_FCC_INITIALIZED(curl->handlers.prereq)) {
511+
zend_get_gc_buffer_add_fcc(gc_buffer, &curl->handlers.prereq);
512+
}
513+
#endif
509514
#if LIBCURL_VERSION_NUM >= 0x075400 /* Available since 7.84.0 */
510515
if (ZEND_FCC_INITIALIZED(curl->handlers.sshhostkey)) {
511516
zend_get_gc_buffer_add_fcc(gc_buffer, &curl->handlers.sshhostkey);
@@ -711,6 +716,53 @@ static size_t curl_xferinfo(void *clientp, curl_off_t dltotal, curl_off_t dlnow,
711716
}
712717
/* }}} */
713718

719+
#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */
720+
static int curl_prereqfunction(void *clientp, char *conn_primary_ip, char *conn_local_ip, int conn_primary_port, int conn_local_port)
721+
{
722+
php_curl *ch = (php_curl *)clientp;
723+
int rval = CURL_PREREQFUNC_ABORT;
724+
725+
#if PHP_CURL_DEBUG
726+
fprintf(stderr, "curl_prereqfunction() called\n");
727+
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, conn_primary_port, conn_local_port);
728+
#endif
729+
730+
zval args[5];
731+
zval retval;
732+
733+
GC_ADDREF(&ch->std);
734+
ZVAL_OBJ(&args[0], &ch->std);
735+
ZVAL_STRING(&args[1], conn_primary_ip);
736+
ZVAL_STRING(&args[2], conn_local_ip);
737+
ZVAL_LONG(&args[3], conn_primary_port);
738+
ZVAL_LONG(&args[4], conn_local_port);
739+
740+
ch->in_callback = true;
741+
zend_call_known_fcc(&ch->handlers.prereq, &retval, /* param_count */ 5, args, /* named_params */ NULL);
742+
ch->in_callback = false;
743+
744+
if (!Z_ISUNDEF(retval)) {
745+
_php_curl_verify_handlers(ch, /* reporterror */ true);
746+
if (Z_TYPE(retval) == IS_LONG) {
747+
zend_long retval_long = Z_LVAL(retval);
748+
if (retval_long == CURL_PREREQFUNC_OK || retval_long == CURL_PREREQFUNC_ABORT) {
749+
rval = retval_long;
750+
} else {
751+
zend_throw_error(NULL, "The CURLOPT_PREREQFUNCTION callback must return either CURL_PREREQFUNC_OK or CURL_PREREQFUNC_ABORT");
752+
}
753+
} else {
754+
zend_throw_error(NULL, "The CURLOPT_PREREQFUNCTION callback must return either CURL_PREREQFUNC_OK or CURL_PREREQFUNC_ABORT");
755+
}
756+
}
757+
758+
zval_ptr_dtor(&args[0]);
759+
zval_ptr_dtor(&args[1]);
760+
zval_ptr_dtor(&args[2]);
761+
762+
return rval;
763+
}
764+
#endif
765+
714766
#if LIBCURL_VERSION_NUM >= 0x075400 /* Available since 7.84.0 */
715767
static int curl_ssh_hostkeyfunction(void *clientp, int keytype, const char *key, size_t keylen)
716768
{
@@ -1039,6 +1091,9 @@ void init_curl_handle(php_curl *ch)
10391091
ch->handlers.progress = empty_fcall_info_cache;
10401092
ch->handlers.xferinfo = empty_fcall_info_cache;
10411093
ch->handlers.fnmatch = empty_fcall_info_cache;
1094+
#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */
1095+
ch->handlers.prereq = empty_fcall_info_cache;
1096+
#endif
10421097
#if LIBCURL_VERSION_NUM >= 0x075400 /* Available since 7.84.0 */
10431098
ch->handlers.sshhostkey = empty_fcall_info_cache;
10441099
#endif
@@ -1212,6 +1267,9 @@ void _php_setup_easy_copy_handlers(php_curl *ch, php_curl *source)
12121267
php_curl_copy_fcc_with_option(ch, CURLOPT_PROGRESSDATA, &ch->handlers.progress, &source->handlers.progress);
12131268
php_curl_copy_fcc_with_option(ch, CURLOPT_XFERINFODATA, &ch->handlers.xferinfo, &source->handlers.xferinfo);
12141269
php_curl_copy_fcc_with_option(ch, CURLOPT_FNMATCH_DATA, &ch->handlers.fnmatch, &source->handlers.fnmatch);
1270+
#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */
1271+
php_curl_copy_fcc_with_option(ch, CURLOPT_PREREQDATA, &ch->handlers.prereq, &source->handlers.prereq);
1272+
#endif
12151273
#if LIBCURL_VERSION_NUM >= 0x075400 /* Available since 7.84.0 */
12161274
php_curl_copy_fcc_with_option(ch, CURLOPT_SSH_HOSTKEYDATA, &ch->handlers.sshhostkey, &source->handlers.sshhostkey);
12171275
#endif
@@ -1568,6 +1626,9 @@ static zend_result _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue
15681626
HANDLE_CURL_OPTION_CALLABLE(ch, CURLOPT_PROGRESS, handlers.progress, curl_progress);
15691627
HANDLE_CURL_OPTION_CALLABLE(ch, CURLOPT_XFERINFO, handlers.xferinfo, curl_xferinfo);
15701628
HANDLE_CURL_OPTION_CALLABLE(ch, CURLOPT_FNMATCH_, handlers.fnmatch, curl_fnmatch);
1629+
#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */
1630+
HANDLE_CURL_OPTION_CALLABLE(ch, CURLOPT_PREREQ, handlers.prereq, curl_prereqfunction);
1631+
#endif
15711632
#if LIBCURL_VERSION_NUM >= 0x075400 /* Available since 7.84.0 */
15721633
HANDLE_CURL_OPTION_CALLABLE(ch, CURLOPT_SSH_HOSTKEY, handlers.sshhostkey, curl_ssh_hostkeyfunction);
15731634
#endif
@@ -2727,6 +2788,11 @@ static void curl_free_obj(zend_object *object)
27272788
if (ZEND_FCC_INITIALIZED(ch->handlers.fnmatch)) {
27282789
zend_fcc_dtor(&ch->handlers.fnmatch);
27292790
}
2791+
#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */
2792+
if (ZEND_FCC_INITIALIZED(ch->handlers.prereq)) {
2793+
zend_fcc_dtor(&ch->handlers.prereq);
2794+
}
2795+
#endif
27302796
#if LIBCURL_VERSION_NUM >= 0x075400 /* Available since 7.84.0 */
27312797
if (ZEND_FCC_INITIALIZED(ch->handlers.sshhostkey)) {
27322798
zend_fcc_dtor(&ch->handlers.sshhostkey);
@@ -2805,7 +2871,11 @@ static void _php_curl_reset_handlers(php_curl *ch)
28052871
if (ZEND_FCC_INITIALIZED(ch->handlers.fnmatch)) {
28062872
zend_fcc_dtor(&ch->handlers.fnmatch);
28072873
}
2808-
2874+
#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */
2875+
if (ZEND_FCC_INITIALIZED(ch->handlers.prereq)) {
2876+
zend_fcc_dtor(&ch->handlers.prereq);
2877+
}
2878+
#endif
28092879
#if LIBCURL_VERSION_NUM >= 0x075400 /* Available since 7.84.0 */
28102880
if (ZEND_FCC_INITIALIZED(ch->handlers.sshhostkey)) {
28112881
zend_fcc_dtor(&ch->handlers.sshhostkey);

ext/curl/sync-constants.php

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
const IGNORED_CURL_CONSTANTS = [
1818
'CURLOPT_PROGRESSDATA',
1919
'CURLOPT_XFERINFODATA',
20+
'CURLOPT_PREREQDATA',
2021
];
2122

2223
const IGNORED_PHP_CONSTANTS = [
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
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+
$host = curl_cli_server_start();
15+
$port = (int) (explode(':', $host))[1];
16+
17+
$ch = curl_init();
18+
curl_setopt($ch, CURLOPT_URL, $host);
19+
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
20+
//curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
21+
22+
var_dump(CURLOPT_PREREQFUNCTION);
23+
var_dump(CURL_PREREQFUNC_OK);
24+
var_dump(CURL_PREREQFUNC_ABORT);
25+
26+
curl_setopt($ch, CURLOPT_PREREQFUNCTION, function() use ($port) {
27+
var_dump('callback');
28+
var_dump(func_num_args());
29+
$args = func_get_args();
30+
var_dump(get_class($args[0]));
31+
var_dump(filter_var($args[1], FILTER_VALIDATE_IP) !== false);
32+
var_dump(filter_var($args[2], FILTER_VALIDATE_IP) !== false);
33+
var_dump($port === $args[3]);
34+
var_dump(is_int($args[4]));
35+
36+
return CURL_PREREQFUNC_ABORT;
37+
});
38+
39+
$result = curl_exec($ch);
40+
41+
var_dump($result);
42+
var_dump(curl_error($ch));
43+
var_dump(curl_errno($ch));
44+
45+
var_dump('finished');
46+
?>
47+
--EXPECT--
48+
int(20312)
49+
int(0)
50+
int(1)
51+
string(8) "callback"
52+
int(5)
53+
string(10) "CurlHandle"
54+
bool(true)
55+
bool(true)
56+
bool(true)
57+
bool(true)
58+
bool(false)
59+
string(41) "operation aborted by pre-request callback"
60+
int(42)
61+
string(8) "finished"

0 commit comments

Comments
 (0)