Skip to content

Commit 545e929

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 ef8dcbd commit 545e929

File tree

6 files changed

+195
-1
lines changed

6 files changed

+195
-1
lines changed

UPGRADING

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,12 @@ PHP 8.4 UPGRADE NOTES
171171
. curl_version() returns an additional feature_list value, which is an
172172
associative array of all known Curl features, and whether they are
173173
supported (true) or not (false).
174+
. Added CURLOPT_PREREQFUNCTION option which accepts a callable that gets
175+
called after a connection is established (including TLS handshake), but
176+
before the request is made. The function is called with the CurlHandle
177+
object, primary IP, local IP, primary port, and the local port, and it
178+
must return CURL_PREREQFUNC_OK or CURL_PREREQFUNC_ABORT to allow or abort
179+
the request.
174180

175181
- Date:
176182
. Added static methods
@@ -513,6 +519,11 @@ PHP 8.4 UPGRADE NOTES
513519
- Core:
514520
. PHP_OUTPUT_HANDLER_PROCESSED.
515521

522+
- Curl:
523+
. CURLOPT_PREREQFUNCTION.
524+
. CURL_PREREQFUNC_OK.
525+
. CURL_PREREQFUNC_ABORT.
526+
516527
- Intl:
517528
. The IntlDateFormatter class exposes now the new PATTERN constant
518529
reflecting udat api's UDAT_PATTERN.

ext/curl/curl.stub.php

Lines changed: 15 additions & 0 deletions
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

Lines changed: 10 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ext/curl/curl_private.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ typedef struct {
7474
zval std_err;
7575
php_curl_callback *progress;
7676
php_curl_callback *xferinfo;
77+
#if LIBCURL_VERSION_NUM >= 0x075000 /* 7.80.0 */
78+
php_curl_callback *prereq;
79+
#endif
7780
php_curl_callback *fnmatch;
7881
#if LIBCURL_VERSION_NUM >= 0x075400 /* Available since 7.84.0 */
7982
php_curl_callback *sshhostkey;

ext/curl/interface.c

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,12 @@ static HashTable *curl_get_gc(zend_object *object, zval **table, int *n)
496496
zend_get_gc_buffer_add_zval(gc_buffer, &curl->handlers.xferinfo->func_name);
497497
}
498498

499+
#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */
500+
if (curl->handlers.prereq) {
501+
zend_get_gc_buffer_add_zval(gc_buffer, &curl->handlers.prereq->func_name);
502+
}
503+
#endif
504+
499505
if (curl->handlers.fnmatch) {
500506
zend_get_gc_buffer_add_zval(gc_buffer, &curl->handlers.fnmatch->func_name);
501507
}
@@ -939,6 +945,64 @@ static size_t curl_write_header(char *data, size_t size, size_t nmemb, void *ctx
939945
}
940946
/* }}} */
941947

948+
#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */
949+
static int curl_prereqfunction(void *clientp, char *conn_primary_ip, char *conn_local_ip, int conn_primary_port, int conn_local_port)
950+
{
951+
php_curl *ch = (php_curl *)clientp;
952+
php_curl_callback *t = ch->handlers.prereq;
953+
954+
int rval = CURL_PREREQFUNC_ABORT; /* Disallow the request by default. */
955+
956+
#if PHP_CURL_DEBUG
957+
fprintf(stderr, "curl_prereqfunction() called\n");
958+
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);
959+
#endif
960+
961+
zval argv[5];
962+
zval retval;
963+
964+
zend_fcall_info fci;
965+
966+
GC_ADDREF(&ch->std);
967+
ZVAL_OBJ(&argv[0], &ch->std);
968+
ZVAL_STRING(&argv[1], conn_primary_ip);
969+
ZVAL_STRING(&argv[2], conn_local_ip);
970+
ZVAL_LONG(&argv[3], conn_primary_port);
971+
ZVAL_LONG(&argv[4], conn_local_port);
972+
973+
fci.size = sizeof(fci);
974+
ZVAL_COPY_VALUE(&fci.function_name, &t->func_name);
975+
fci.object = NULL;
976+
fci.retval = &retval;
977+
fci.param_count = 5;
978+
fci.params = argv;
979+
fci.named_params = NULL;
980+
981+
ch->in_callback = 1;
982+
zend_call_function(&fci, &t->fci_cache);
983+
ch->in_callback = 0;
984+
if (!Z_ISUNDEF(retval)) {
985+
_php_curl_verify_handlers(ch, true);
986+
if (Z_TYPE(retval) == IS_LONG) {
987+
zend_long retval_long = Z_LVAL(retval);
988+
if (retval_long == CURL_PREREQFUNC_OK || retval_long == CURL_PREREQFUNC_ABORT) {
989+
rval = retval_long;
990+
} else {
991+
zend_throw_error(NULL, "The CURLOPT_PREREQFUNCTION callback must return either CURL_PREREQFUNC_OK or CURL_PREREQFUNC_ABORT");
992+
}
993+
} else {
994+
zend_throw_error(NULL, "The CURLOPT_PREREQFUNCTION callback must return either CURL_PREREQFUNC_OK or CURL_PREREQFUNC_ABORT");
995+
}
996+
}
997+
zval_ptr_dtor(&argv[0]);
998+
zval_ptr_dtor(&argv[1]);
999+
zval_ptr_dtor(&argv[2]);
1000+
1001+
return rval;
1002+
}
1003+
1004+
#endif
1005+
9421006
static int curl_debug(CURL *cp, curl_infotype type, char *buf, size_t buf_len, void *ctx) /* {{{ */
9431007
{
9441008
php_curl *ch = (php_curl *)ctx;
@@ -1120,6 +1184,9 @@ void init_curl_handle(php_curl *ch)
11201184
ch->handlers.progress = NULL;
11211185
ch->handlers.xferinfo = NULL;
11221186
ch->handlers.fnmatch = NULL;
1187+
#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */
1188+
ch->handlers.prereq = NULL;
1189+
#endif
11231190
#if LIBCURL_VERSION_NUM >= 0x075400 /* Available since 7.84.0 */
11241191
ch->handlers.sshhostkey = NULL;
11251192
#endif
@@ -1296,6 +1363,9 @@ void _php_setup_easy_copy_handlers(php_curl *ch, php_curl *source)
12961363
_php_copy_callback(ch, &ch->handlers.progress, source->handlers.progress, CURLOPT_PROGRESSDATA);
12971364
_php_copy_callback(ch, &ch->handlers.xferinfo, source->handlers.xferinfo, CURLOPT_XFERINFODATA);
12981365
_php_copy_callback(ch, &ch->handlers.fnmatch, source->handlers.fnmatch, CURLOPT_FNMATCH_DATA);
1366+
#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */
1367+
_php_copy_callback(ch, &ch->handlers.prereq, source->handlers.prereq, CURLOPT_PREREQFUNCTION);
1368+
#endif
12991369
#if LIBCURL_VERSION_NUM >= 0x075400 /* Available since 7.84.0 */
13001370
_php_copy_callback(ch, &ch->handlers.sshhostkey, source->handlers.sshhostkey, CURLOPT_SSH_HOSTKEYDATA);
13011371
#endif
@@ -2206,6 +2276,20 @@ static zend_result _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue
22062276
ZVAL_COPY(&ch->handlers.xferinfo->func_name, zvalue);
22072277
break;
22082278

2279+
#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */
2280+
case CURLOPT_PREREQFUNCTION:
2281+
curl_easy_setopt(ch->cp, CURLOPT_PREREQFUNCTION, curl_prereqfunction);
2282+
curl_easy_setopt(ch->cp, CURLOPT_PREREQDATA, ch);
2283+
if (ch->handlers.prereq == NULL) {
2284+
ch->handlers.prereq = ecalloc(1, sizeof(php_curl_callback));
2285+
} else if (!Z_ISUNDEF(ch->handlers.prereq->func_name)) {
2286+
zval_ptr_dtor(&ch->handlers.prereq->func_name);
2287+
ch->handlers.prereq->fci_cache = empty_fcall_info_cache;
2288+
}
2289+
ZVAL_COPY(&ch->handlers.prereq->func_name, zvalue);
2290+
break;
2291+
#endif
2292+
22092293
/* Curl off_t options */
22102294
case CURLOPT_MAX_RECV_SPEED_LARGE:
22112295
case CURLOPT_MAX_SEND_SPEED_LARGE:
@@ -2849,6 +2933,9 @@ static void curl_free_obj(zend_object *object)
28492933
#if LIBCURL_VERSION_NUM >= 0x075400 /* Available since 7.84.0 */
28502934
_php_curl_free_callback(ch->handlers.sshhostkey);
28512935
#endif
2936+
#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */
2937+
_php_curl_free_callback(ch->handlers.prereq);
2938+
#endif
28522939

28532940
zval_ptr_dtor(&ch->postfields);
28542941
zval_ptr_dtor(&ch->private_data);
@@ -2923,6 +3010,14 @@ static void _php_curl_reset_handlers(php_curl *ch)
29233010
ch->handlers.xferinfo = NULL;
29243011
}
29253012

3013+
#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */
3014+
if (ch->handlers.prereq) {
3015+
zval_ptr_dtor(&ch->handlers.prereq->func_name);
3016+
efree(ch->handlers.prereq);
3017+
ch->handlers.prereq = NULL;
3018+
}
3019+
#endif
3020+
29263021
if (ch->handlers.fnmatch) {
29273022
zval_ptr_dtor(&ch->handlers.fnmatch->func_name);
29283023
efree(ch->handlers.fnmatch);
Lines changed: 61 additions & 0 deletions
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)