Skip to content

Commit 2c54ff1

Browse files
committed
ext/curl: Add CURLOPT_DEBUGFUNCTION option
This adds support for `CURLOPT_DEBUGFUNCTION`[^1] Curl option to set a custom callback that gets called with debug information during the lifetime of a Curl request. The callback gets called with the `CurlHandle` object, an integer containing the type of the debug message, and a string containing the debug message. The callback may get called multiple times with the same message type during a request. PHP already uses `CURLOPT_DEBUGFUNCTION` functionality to internally to expose a Curl option named `CURLINFO_HEADER_OUT`. However,`CURLINFO_HEADER_OUT` is not a "real" Curl option supported by libcurl. Back in 2006, `CURLINFO_HEADER_OUT` was added[^2] as a Curl option by using the debug-callback feature. Git history does not run that back to show why `CURLINFO_HEADER_OUT` was added as a Curl option, and why the other debug types (such as `CURLINFO_HEADER_IN` were not added as Curl options, but this seems to be a historical artifact when we added features without trying to be close to libcurl options. This approach has a few issues: 1. `CURLINFO_HEADER_OUT` is not an actual Curl option supported by upstream libcurl. 2. All of the Curl options have `CURLOPT_` prefix, and `CURLINFO_HEADER_OUT` is the only Curl "option" that uses the `CURLINFO` prefix. This exception is, however, noted[^3] in docs. 3. When `CURLINFO_HEADER_OUT` is set, the `CURLOPT_VERBOSE` is also implicitly set. This was reported[^4] to bugs.php.net, but the bug is marked as wontfix. This commit adds support for `CURLOPT_DEBUGFUNCTION`. It extends the existing `curl_debug` callback to store the header-in information if it encounters a debug message with `CURLINFO_HEADER_OUT`. In all cases, if a callable is set, it gets called. `CURLOPT_DEBUGFUNCTION` intends to replace `CURLINFO_HEADER_OUT` Curl option as a versatile alternative that can also be used to extract other debug information such as SSL data, text information messages, incoming headers, as well as headers sent out (which `CURLINFO_HEADER_OUT` makes available). The callables are allowed to throw exceptions, but the return values are ignored. `CURLOPT_DEBUGFUNCTION` requires `CURLOPT_VERBOSE` enabled, and setting `CURLOPT_DEBUGFUNCTION` does _not_ implicitly enable `CURLOPT_VERBOSE`. If the `CURLOPT_DEBUGFUNCTION` option is set, setting `CURLINFO_HEADER_OUT` throws a `ValueError` exception. Setting `CURLOPT_DEBUGFUNCTION` _after_ enabling `CURLINFO_HEADER_OUT` is allowed. Technically, it is possible for both functionality (calling user-provided callback _and_ storing header-out data) is possible, setting `CURLINFO_HEADER_OUT` is not allowed to encourage the use of `CURLOPT_DEBUGFUNCTION` function. This commit also adds the rest of the `CURLINFO_` constants used as the `type` integer value in `CURLOPT_DEBUGFUNCTION` callback. --- [^1]: [cur.se - CURLOPT_DEBUGFUNCTION](https://curl.se/libcurl/c/CURLOPT_DEBUGFUNCTION.html) [^2]: [`5f25d80`](php@5f25d80) [^3]: [curl_setopt doc mentioning `CURLINFO_` prefix is intentional](https://www.php.net/manual/en/function.curl-setopt.php#:~:text=prefix%20is%20intentional) [^4]: [bugs.php.net - `CURLOPT_VERBOSE` does not work with `CURLINFO_HEADER_OUT`](https://bugs.php.net/bug.php?id=65348)
1 parent cf0a44a commit 2c54ff1

8 files changed

+362
-10
lines changed

NEWS

+3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ PHP NEWS
77
. ext/bcmath: Check for scale overflow. (SakiTakamachi)
88
. [RFC] ext/bcmath: Added bcdivmod. (SakiTakamachi)
99

10+
- Curl:
11+
. Added CURLOPT_DEBUGFUNCTION as a Curl option. (Ayesh Karunaratne)
12+
1013
- Debugging:
1114
. Fixed bug GH-15923 (GDB: Python Exception <class 'TypeError'>:
1215
exceptions must derive from BaseException). (nielsdos)

UPGRADING

+14
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,13 @@ PHP 8.4 UPGRADE NOTES
302302
to allow or abort the request.
303303
. Added CURLOPT_SERVER_RESPONSE_TIMEOUT, which was formerly known as
304304
CURLOPT_FTP_RESPONSE_TIMEOUT. Both constants hold the same value.
305+
. Added CURLOPT_DEBUGFUNCTION support. This Curl option accepts a callable
306+
that gets called during the request lifetime with the CurlHandle object,
307+
an integer containing the debug message type, and a string containing the
308+
debug message. The debug message type is one of CURLINFO_TEXT, CURLINFO_HEADER_IN,
309+
CURLINFO_HEADER_OUT, CURLINFO_DATA_IN, CURLINFO_DATA_OUT, CURLINFO_SSL_DATA_OUT,
310+
CURLINFO_SSL_DATA_IN constants. Once this option is set, CURLINFO_HEADER_OUT
311+
must not be set because it uses the same libcurl functionality.
305312

306313
- Date:
307314
. Added static methods
@@ -1041,6 +1048,13 @@ PHP 8.4 UPGRADE NOTES
10411048
. CURL_PREREQFUNC_OK.
10421049
. CURL_PREREQFUNC_ABORT.
10431050
. CURLOPT_SERVER_RESPONSE_TIMEOUT.
1051+
. CURLOPT_DEBUGFUNCTION.
1052+
. CURLINFO_TEXT.
1053+
. CURLINFO_HEADER_IN.
1054+
. CURLINFO_DATA_IN.
1055+
. CURLINFO_DATA_OUT.
1056+
. CURLINFO_SSL_DATA_OUT.
1057+
. CURLINFO_SSL_DATA_IN.
10441058

10451059
- Intl:
10461060
. The IntlDateFormatter class exposes now the new PATTERN constant

ext/curl/curl.stub.php

+42
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,48 @@
486486
*/
487487
const CURLOPT_XFERINFOFUNCTION = UNKNOWN;
488488

489+
/**
490+
* @var int
491+
* @cvalue CURLOPT_DEBUGFUNCTION
492+
*/
493+
const CURLOPT_DEBUGFUNCTION = UNKNOWN;
494+
495+
/**
496+
* @var int
497+
* @cvalue CURLINFO_TEXT
498+
*/
499+
const CURLINFO_TEXT = UNKNOWN;
500+
501+
/**
502+
* @var int
503+
* @cvalue CURLINFO_HEADER_IN
504+
*/
505+
const CURLINFO_HEADER_IN = UNKNOWN;
506+
507+
/**
508+
* @var int
509+
* @cvalue CURLINFO_DATA_IN
510+
*/
511+
const CURLINFO_DATA_IN = UNKNOWN;
512+
513+
/**
514+
* @var int
515+
* @cvalue CURLINFO_DATA_OUT
516+
*/
517+
const CURLINFO_DATA_OUT = UNKNOWN;
518+
519+
/**
520+
* @var int
521+
* @cvalue CURLINFO_SSL_DATA_OUT
522+
*/
523+
const CURLINFO_SSL_DATA_OUT = UNKNOWN;
524+
525+
/**
526+
* @var int
527+
* @cvalue CURLINFO_SSL_DATA_IN
528+
*/
529+
const CURLINFO_SSL_DATA_IN = UNKNOWN;
530+
489531
/* */
490532
/**
491533
* @var int

ext/curl/curl_arginfo.h

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

ext/curl/curl_private.h

+1
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ typedef struct {
6868
zend_fcall_info_cache progress;
6969
zend_fcall_info_cache xferinfo;
7070
zend_fcall_info_cache fnmatch;
71+
zend_fcall_info_cache debug;
7172
#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */
7273
zend_fcall_info_cache prereq;
7374
#endif

ext/curl/interface.c

+57-9
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,10 @@ 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 (ZEND_FCC_INITIALIZED(curl->handlers.debug)) {
508+
zend_get_gc_buffer_add_fcc(gc_buffer, &curl->handlers.debug);
509+
}
510+
507511
#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */
508512
if (ZEND_FCC_INITIALIZED(curl->handlers.prereq)) {
509513
zend_get_gc_buffer_add_fcc(gc_buffer, &curl->handlers.prereq);
@@ -915,18 +919,46 @@ static size_t curl_write_header(char *data, size_t size, size_t nmemb, void *ctx
915919
}
916920
/* }}} */
917921

918-
static int curl_debug(CURL *cp, curl_infotype type, char *buf, size_t buf_len, void *ctx) /* {{{ */
922+
static int curl_debug(CURL *handle, curl_infotype type, char *data, size_t size, void *clientp) /* {{{ */
919923
{
920-
php_curl *ch = (php_curl *)ctx;
924+
php_curl *ch = (php_curl *)clientp;
921925

922-
if (type == CURLINFO_HEADER_OUT) {
923-
if (ch->header.str) {
924-
zend_string_release_ex(ch->header.str, 0);
925-
}
926-
ch->header.str = zend_string_init(buf, buf_len, 0);
927-
}
926+
#if PHP_CURL_DEBUG
927+
fprintf(stderr, "curl_debug() called\n");
928+
fprintf(stderr, "type = %d, data = %s\n", type, data);
929+
#endif
930+
931+
// Implicitly store the headers for compatibility with CURLINFO_HEADER_OUT
932+
// used as a Curl option. Previously, setting CURLINFO_HEADER_OUT set curl_debug
933+
// as the CURLOPT_DEBUGFUNCTION and stored the debug data when type is set to
934+
// CURLINFO_HEADER_OUT. For backward compatibility, we now store the headers
935+
// but also call the user-callback function if available.
936+
if (type == CURLINFO_HEADER_OUT) {
937+
if (ch->header.str) {
938+
zend_string_release_ex(ch->header.str, 0);
939+
}
940+
ch->header.str = zend_string_init(data, size, 0);
941+
}
942+
943+
if (!ZEND_FCC_INITIALIZED(ch->handlers.debug)) {
944+
return 0;
945+
}
928946

929-
return 0;
947+
zval args[3];
948+
949+
GC_ADDREF(&ch->std);
950+
ZVAL_OBJ(&args[0], &ch->std);
951+
ZVAL_LONG(&args[1], type);
952+
ZVAL_STRINGL(&args[2], data, size);
953+
954+
ch->in_callback = true;
955+
zend_call_known_fcc(&ch->handlers.debug, NULL, /* param_count */ 3, args, /* named_params */ NULL);
956+
ch->in_callback = false;
957+
958+
zval_ptr_dtor(&args[0]);
959+
zval_ptr_dtor(&args[2]);
960+
961+
return 0;
930962
}
931963
/* }}} */
932964

@@ -1096,6 +1128,7 @@ void init_curl_handle(php_curl *ch)
10961128
ch->handlers.progress = empty_fcall_info_cache;
10971129
ch->handlers.xferinfo = empty_fcall_info_cache;
10981130
ch->handlers.fnmatch = empty_fcall_info_cache;
1131+
ch->handlers.debug = empty_fcall_info_cache;
10991132
#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */
11001133
ch->handlers.prereq = empty_fcall_info_cache;
11011134
#endif
@@ -1269,6 +1302,7 @@ void _php_setup_easy_copy_handlers(php_curl *ch, php_curl *source)
12691302
php_curl_copy_fcc_with_option(ch, CURLOPT_PROGRESSDATA, &ch->handlers.progress, &source->handlers.progress);
12701303
php_curl_copy_fcc_with_option(ch, CURLOPT_XFERINFODATA, &ch->handlers.xferinfo, &source->handlers.xferinfo);
12711304
php_curl_copy_fcc_with_option(ch, CURLOPT_FNMATCH_DATA, &ch->handlers.fnmatch, &source->handlers.fnmatch);
1305+
php_curl_copy_fcc_with_option(ch, CURLOPT_DEBUGDATA, &ch->handlers.debug, &source->handlers.debug);
12721306
#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */
12731307
php_curl_copy_fcc_with_option(ch, CURLOPT_PREREQDATA, &ch->handlers.prereq, &source->handlers.prereq);
12741308
#endif
@@ -1632,6 +1666,8 @@ static zend_result _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue
16321666
HANDLE_CURL_OPTION_CALLABLE(ch, CURLOPT_PROGRESS, handlers.progress, curl_progress);
16331667
HANDLE_CURL_OPTION_CALLABLE(ch, CURLOPT_XFERINFO, handlers.xferinfo, curl_xferinfo);
16341668
HANDLE_CURL_OPTION_CALLABLE(ch, CURLOPT_FNMATCH_, handlers.fnmatch, curl_fnmatch);
1669+
HANDLE_CURL_OPTION_CALLABLE(ch, CURLOPT_DEBUG, handlers.debug, curl_debug);
1670+
16351671
#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */
16361672
HANDLE_CURL_OPTION_CALLABLE(ch, CURLOPT_PREREQ, handlers.prereq, curl_prereqfunction);
16371673
#endif
@@ -2216,6 +2252,11 @@ static zend_result _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue
22162252
}
22172253

22182254
case CURLINFO_HEADER_OUT:
2255+
if (ZEND_FCC_INITIALIZED(ch->handlers.debug)) {
2256+
zend_value_error("CURLINFO_HEADER_OUT option must not be set when the CURLOPT_DEBUGFUNCTION option is set");
2257+
return FAILURE;
2258+
}
2259+
22192260
if (zend_is_true(zvalue)) {
22202261
curl_easy_setopt(ch->cp, CURLOPT_DEBUGFUNCTION, curl_debug);
22212262
curl_easy_setopt(ch->cp, CURLOPT_DEBUGDATA, (void *)ch);
@@ -2795,6 +2836,9 @@ static void curl_free_obj(zend_object *object)
27952836
if (ZEND_FCC_INITIALIZED(ch->handlers.fnmatch)) {
27962837
zend_fcc_dtor(&ch->handlers.fnmatch);
27972838
}
2839+
if (ZEND_FCC_INITIALIZED(ch->handlers.debug)) {
2840+
zend_fcc_dtor(&ch->handlers.debug);
2841+
}
27982842
#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */
27992843
if (ZEND_FCC_INITIALIZED(ch->handlers.prereq)) {
28002844
zend_fcc_dtor(&ch->handlers.prereq);
@@ -2878,6 +2922,10 @@ static void _php_curl_reset_handlers(php_curl *ch)
28782922
if (ZEND_FCC_INITIALIZED(ch->handlers.fnmatch)) {
28792923
zend_fcc_dtor(&ch->handlers.fnmatch);
28802924
}
2925+
2926+
if (ZEND_FCC_INITIALIZED(ch->handlers.debug)) {
2927+
zend_fcc_dtor(&ch->handlers.debug);
2928+
}
28812929
#if LIBCURL_VERSION_NUM >= 0x075000 /* Available since 7.80.0 */
28822930
if (ZEND_FCC_INITIALIZED(ch->handlers.prereq)) {
28832931
zend_fcc_dtor(&ch->handlers.prereq);

ext/curl/sync-constants.php

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
'CURLOPT_PROGRESSDATA',
1919
'CURLOPT_XFERINFODATA',
2020
'CURLOPT_PREREQDATA',
21+
'CURLOPT_DEBUGDATA',
2122
];
2223

2324
const IGNORED_PHP_CONSTANTS = [

0 commit comments

Comments
 (0)