Skip to content

Commit 962835e

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 602a4ac commit 962835e

7 files changed

+264
-2
lines changed

UPGRADING

+11
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,12 @@ PHP 8.4 UPGRADE NOTES
270270
supported (true) or not (false).
271271
. Added CURL_HTTP_VERSION_3 and CURL_HTTP_VERSION_3ONLY constants (available
272272
since libcurl 7.66 and 7.88) as available options for CURLOPT_HTTP_VERSION.
273+
. Added CURLOPT_PREREQFUNCTION option which accepts a callable that gets
274+
called after a connection is established (including TLS handshake), but
275+
before the request is made. The function is called with the CurlHandle
276+
object, primary IP, local IP, primary port, and the local port, and it
277+
must return CURL_PREREQFUNC_OK or CURL_PREREQFUNC_ABORT to allow or abort
278+
the request.
273279

274280
- Date:
275281
. Added static methods
@@ -935,6 +941,11 @@ PHP 8.4 UPGRADE NOTES
935941
. CURL_HTTP_VERSION_3ONLY.
936942
. CURL_TCP_KEEPCNT
937943

944+
- Curl:
945+
. CURLOPT_PREREQFUNCTION.
946+
. CURL_PREREQFUNC_OK.
947+
. CURL_PREREQFUNC_ABORT.
948+
938949
- Intl:
939950
. The IntlDateFormatter class exposes now the new PATTERN constant
940951
reflecting udat api's UDAT_PATTERN.

ext/curl/curl.stub.php

+15
Original file line numberDiff line numberDiff line change
@@ -3497,6 +3497,21 @@
34973497
* @cvalue CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256
34983498
*/
34993499
const CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256 = UNKNOWN;
3500+
/**
3501+
* @var int
3502+
* @cvalue CURLOPT_PREREQFUNCTION
3503+
*/
3504+
const CURLOPT_PREREQFUNCTION = UNKNOWN;
3505+
/**
3506+
* @var int
3507+
* @cvalue CURL_PREREQFUNC_OK
3508+
*/
3509+
const CURL_PREREQFUNC_OK = UNKNOWN;
3510+
/**
3511+
* @var int
3512+
* @cvalue CURL_PREREQFUNC_ABORT
3513+
*/
3514+
const CURL_PREREQFUNC_ABORT = UNKNOWN;
35003515
#endif
35013516

35023517
#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
@@ -504,6 +504,11 @@ static HashTable *curl_get_gc(zend_object *object, zval **table, int *n)
504504
zend_get_gc_buffer_add_fcc(gc_buffer, &curl->handlers.fnmatch);
505505
}
506506

507+
#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */
508+
if (ZEND_FCC_INITIALIZED(curl->handlers.prereq)) {
509+
zend_get_gc_buffer_add_fcc(gc_buffer, &curl->handlers.prereq);
510+
}
511+
#endif
507512
#if LIBCURL_VERSION_NUM >= 0x075400 /* Available since 7.84.0 */
508513
if (ZEND_FCC_INITIALIZED(curl->handlers.sshhostkey)) {
509514
zend_get_gc_buffer_add_fcc(gc_buffer, &curl->handlers.sshhostkey);
@@ -709,6 +714,53 @@ static size_t curl_xferinfo(void *clientp, curl_off_t dltotal, curl_off_t dlnow,
709714
}
710715
/* }}} */
711716

717+
#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */
718+
static int curl_prereqfunction(void *clientp, char *conn_primary_ip, char *conn_local_ip, int conn_primary_port, int conn_local_port)
719+
{
720+
php_curl *ch = (php_curl *)clientp;
721+
int rval = CURL_PREREQFUNC_ABORT;
722+
723+
#if PHP_CURL_DEBUG
724+
fprintf(stderr, "curl_prereqfunction() called\n");
725+
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);
726+
#endif
727+
728+
zval args[5];
729+
zval retval;
730+
731+
GC_ADDREF(&ch->std);
732+
ZVAL_OBJ(&args[0], &ch->std);
733+
ZVAL_STRING(&args[1], conn_primary_ip);
734+
ZVAL_STRING(&args[2], conn_local_ip);
735+
ZVAL_LONG(&args[3], conn_primary_port);
736+
ZVAL_LONG(&args[4], conn_local_port);
737+
738+
ch->in_callback = true;
739+
zend_call_known_fcc(&ch->handlers.prereq, &retval, /* param_count */ 5, args, /* named_params */ NULL);
740+
ch->in_callback = false;
741+
742+
if (!Z_ISUNDEF(retval)) {
743+
_php_curl_verify_handlers(ch, /* reporterror */ true);
744+
if (Z_TYPE(retval) == IS_LONG) {
745+
zend_long retval_long = Z_LVAL(retval);
746+
if (retval_long == CURL_PREREQFUNC_OK || retval_long == CURL_PREREQFUNC_ABORT) {
747+
rval = retval_long;
748+
} else {
749+
zend_value_error("The CURLOPT_PREREQFUNCTION callback must return either CURL_PREREQFUNC_OK or CURL_PREREQFUNC_ABORT");
750+
}
751+
} else {
752+
zend_type_error("The CURLOPT_PREREQFUNCTION callback must return either CURL_PREREQFUNC_OK or CURL_PREREQFUNC_ABORT");
753+
}
754+
}
755+
756+
zval_ptr_dtor(&args[0]);
757+
zval_ptr_dtor(&args[1]);
758+
zval_ptr_dtor(&args[2]);
759+
760+
return rval;
761+
}
762+
#endif
763+
712764
#if LIBCURL_VERSION_NUM >= 0x075400 /* Available since 7.84.0 */
713765
static int curl_ssh_hostkeyfunction(void *clientp, int keytype, const char *key, size_t keylen)
714766
{
@@ -1037,6 +1089,9 @@ void init_curl_handle(php_curl *ch)
10371089
ch->handlers.progress = empty_fcall_info_cache;
10381090
ch->handlers.xferinfo = empty_fcall_info_cache;
10391091
ch->handlers.fnmatch = empty_fcall_info_cache;
1092+
#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */
1093+
ch->handlers.prereq = empty_fcall_info_cache;
1094+
#endif
10401095
#if LIBCURL_VERSION_NUM >= 0x075400 /* Available since 7.84.0 */
10411096
ch->handlers.sshhostkey = empty_fcall_info_cache;
10421097
#endif
@@ -1210,6 +1265,9 @@ void _php_setup_easy_copy_handlers(php_curl *ch, php_curl *source)
12101265
php_curl_copy_fcc_with_option(ch, CURLOPT_PROGRESSDATA, &ch->handlers.progress, &source->handlers.progress);
12111266
php_curl_copy_fcc_with_option(ch, CURLOPT_XFERINFODATA, &ch->handlers.xferinfo, &source->handlers.xferinfo);
12121267
php_curl_copy_fcc_with_option(ch, CURLOPT_FNMATCH_DATA, &ch->handlers.fnmatch, &source->handlers.fnmatch);
1268+
#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */
1269+
php_curl_copy_fcc_with_option(ch, CURLOPT_PREREQDATA, &ch->handlers.prereq, &source->handlers.prereq);
1270+
#endif
12131271
#if LIBCURL_VERSION_NUM >= 0x075400 /* Available since 7.84.0 */
12141272
php_curl_copy_fcc_with_option(ch, CURLOPT_SSH_HOSTKEYDATA, &ch->handlers.sshhostkey, &source->handlers.sshhostkey);
12151273
#endif
@@ -1570,6 +1628,9 @@ static zend_result _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue
15701628
HANDLE_CURL_OPTION_CALLABLE(ch, CURLOPT_PROGRESS, handlers.progress, curl_progress);
15711629
HANDLE_CURL_OPTION_CALLABLE(ch, CURLOPT_XFERINFO, handlers.xferinfo, curl_xferinfo);
15721630
HANDLE_CURL_OPTION_CALLABLE(ch, CURLOPT_FNMATCH_, handlers.fnmatch, curl_fnmatch);
1631+
#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */
1632+
HANDLE_CURL_OPTION_CALLABLE(ch, CURLOPT_PREREQ, handlers.prereq, curl_prereqfunction);
1633+
#endif
15731634
#if LIBCURL_VERSION_NUM >= 0x075400 /* Available since 7.84.0 */
15741635
HANDLE_CURL_OPTION_CALLABLE(ch, CURLOPT_SSH_HOSTKEY, handlers.sshhostkey, curl_ssh_hostkeyfunction);
15751636
#endif
@@ -2736,6 +2797,11 @@ static void curl_free_obj(zend_object *object)
27362797
if (ZEND_FCC_INITIALIZED(ch->handlers.fnmatch)) {
27372798
zend_fcc_dtor(&ch->handlers.fnmatch);
27382799
}
2800+
#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */
2801+
if (ZEND_FCC_INITIALIZED(ch->handlers.prereq)) {
2802+
zend_fcc_dtor(&ch->handlers.prereq);
2803+
}
2804+
#endif
27392805
#if LIBCURL_VERSION_NUM >= 0x075400 /* Available since 7.84.0 */
27402806
if (ZEND_FCC_INITIALIZED(ch->handlers.sshhostkey)) {
27412807
zend_fcc_dtor(&ch->handlers.sshhostkey);
@@ -2814,7 +2880,11 @@ static void _php_curl_reset_handlers(php_curl *ch)
28142880
if (ZEND_FCC_INITIALIZED(ch->handlers.fnmatch)) {
28152881
zend_fcc_dtor(&ch->handlers.fnmatch);
28162882
}
2817-
2883+
#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */
2884+
if (ZEND_FCC_INITIALIZED(ch->handlers.prereq)) {
2885+
zend_fcc_dtor(&ch->handlers.prereq);
2886+
}
2887+
#endif
28182888
#if LIBCURL_VERSION_NUM >= 0x075400 /* Available since 7.84.0 */
28192889
if (ZEND_FCC_INITIALIZED(ch->handlers.sshhostkey)) {
28202890
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,153 @@
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}/get.inc?test=file");
19+
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
20+
21+
$result = curl_exec($ch);
22+
23+
var_dump(CURLOPT_PREREQFUNCTION);
24+
var_dump(CURL_PREREQFUNC_OK);
25+
var_dump(CURL_PREREQFUNC_ABORT);
26+
27+
$returnValue = CURL_PREREQFUNC_ABORT;
28+
29+
echo "Testing with CURL_PREREQFUNC_ABORT\n";
30+
$callback = function() use ($port, &$returnValue) {
31+
var_dump('callback');
32+
var_dump(func_num_args());
33+
$args = func_get_args();
34+
var_dump(get_class($args[0]));
35+
var_dump(filter_var($args[1], FILTER_VALIDATE_IP) !== false);
36+
var_dump(filter_var($args[2], FILTER_VALIDATE_IP) !== false);
37+
var_dump($port === $args[3]);
38+
var_dump(is_int($args[4]));
39+
40+
return $returnValue;
41+
};
42+
43+
curl_setopt($ch, CURLOPT_PREREQFUNCTION, $callback);
44+
45+
$result = curl_exec($ch);
46+
47+
var_dump($result);
48+
var_dump(curl_error($ch));
49+
var_dump(curl_errno($ch));
50+
51+
$returnValue = CURL_PREREQFUNC_OK;
52+
53+
echo "\nTesting with CURL_PREREQFUNC_OK\n";
54+
$result = curl_exec($ch);
55+
56+
var_dump($result);
57+
var_dump(curl_error($ch));
58+
var_dump(curl_errno($ch));
59+
60+
61+
echo "\nTesting with no return type\n";
62+
curl_setopt($ch, CURLOPT_PREREQFUNCTION, function() use ($port) {
63+
// returns nothing
64+
});
65+
try {
66+
curl_exec($ch);
67+
} catch (\TypeError $e) {
68+
echo $e->getMessage() . \PHP_EOL;
69+
}
70+
71+
echo "\nTesting with invalid type\n";
72+
curl_setopt($ch, CURLOPT_PREREQFUNCTION, function() use ($port) {
73+
return 'this should be an integer';
74+
});
75+
try {
76+
curl_exec($ch);
77+
} catch (\TypeError $e) {
78+
echo $e->getMessage() . \PHP_EOL;
79+
}
80+
81+
echo "\nTesting with invalid value\n";
82+
curl_setopt($ch, CURLOPT_PREREQFUNCTION, function() use ($port) {
83+
return 42;
84+
});
85+
try {
86+
curl_exec($ch);
87+
} catch (\ValueError $e) {
88+
echo $e->getMessage() . \PHP_EOL;
89+
}
90+
91+
echo "\nTesting with allowing callback\n";
92+
curl_setopt($ch, CURLOPT_PREREQFUNCTION, function() use ($port) {
93+
var_dump('callback - allow');
94+
var_dump(func_num_args());
95+
$args = func_get_args();
96+
var_dump(get_class($args[0]));
97+
var_dump(filter_var($args[1], FILTER_VALIDATE_IP) !== false);
98+
var_dump(filter_var($args[2], FILTER_VALIDATE_IP) !== false);
99+
var_dump($port === $args[3]);
100+
var_dump(is_int($args[4]));
101+
102+
return CURL_PREREQFUNC_OK;
103+
});
104+
curl_exec($ch);
105+
echo "\nDone";
106+
?>
107+
--EXPECT--
108+
int(20312)
109+
int(0)
110+
int(1)
111+
Testing with CURL_PREREQFUNC_ABORT
112+
string(8) "callback"
113+
int(5)
114+
string(10) "CurlHandle"
115+
bool(true)
116+
bool(true)
117+
bool(true)
118+
bool(true)
119+
bool(false)
120+
string(41) "operation aborted by pre-request callback"
121+
int(42)
122+
123+
Testing with CURL_PREREQFUNC_OK
124+
string(8) "callback"
125+
int(5)
126+
string(10) "CurlHandle"
127+
bool(true)
128+
bool(true)
129+
bool(true)
130+
bool(true)
131+
string(0) ""
132+
string(0) ""
133+
int(0)
134+
135+
Testing with no return type
136+
The CURLOPT_PREREQFUNCTION callback must return either CURL_PREREQFUNC_OK or CURL_PREREQFUNC_ABORT
137+
138+
Testing with invalid type
139+
The CURLOPT_PREREQFUNCTION callback must return either CURL_PREREQFUNC_OK or CURL_PREREQFUNC_ABORT
140+
141+
Testing with invalid value
142+
The CURLOPT_PREREQFUNCTION callback must return either CURL_PREREQFUNC_OK or CURL_PREREQFUNC_ABORT
143+
144+
Testing with allowing callback
145+
string(16) "callback - allow"
146+
int(5)
147+
string(10) "CurlHandle"
148+
bool(true)
149+
bool(true)
150+
bool(true)
151+
bool(true)
152+
153+
Done

0 commit comments

Comments
 (0)