From ef38d224559ea3fb3eb33216f0b2eceea4758d0c Mon Sep 17 00:00:00 2001 From: Sjoerd Langkemper Date: Wed, 15 Jul 2026 14:32:50 +0000 Subject: [PATCH] Abort curl transfer if callback throws exception Solves bug https://github.com/php/php-src/issues/16513 Also includes https://github.com/php/php-src/pull/16790 --- ext/curl/interface.c | 22 +++++++---- .../curl_headerfunction_throws_abort.phpt | 35 +++++++++++++++++ .../curl_prereqfunction_throws_abort.phpt | 35 +++++++++++++++++ .../curl_progressfunction_throws_abort.phpt | 36 ++++++++++++++++++ .../tests/curl_readfunction_throws_abort.phpt | 38 +++++++++++++++++++ .../curl_writefunction_throws_abort.phpt | 35 +++++++++++++++++ .../curl_xferinfofunction_throws_abort.phpt | 36 ++++++++++++++++++ 7 files changed, 229 insertions(+), 8 deletions(-) create mode 100644 ext/curl/tests/curl_headerfunction_throws_abort.phpt create mode 100644 ext/curl/tests/curl_prereqfunction_throws_abort.phpt create mode 100644 ext/curl/tests/curl_progressfunction_throws_abort.phpt create mode 100644 ext/curl/tests/curl_readfunction_throws_abort.phpt create mode 100644 ext/curl/tests/curl_writefunction_throws_abort.phpt create mode 100644 ext/curl/tests/curl_xferinfofunction_throws_abort.phpt diff --git a/ext/curl/interface.c b/ext/curl/interface.c index c0286069776e..01c104d6a1db 100644 --- a/ext/curl/interface.c +++ b/ext/curl/interface.c @@ -557,6 +557,8 @@ static size_t curl_write(char *data, size_t size, size_t nmemb, void *ctx) _php_curl_verify_handlers(ch, /* reporterror */ true); /* TODO Check callback returns an int or something castable to int */ length = php_curl_get_long(&retval); + } else if (EG(exception)) { + length = -1; } zval_ptr_dtor(&argv[0]); @@ -606,7 +608,7 @@ static int curl_fnmatch(void *ctx, const char *pattern, const char *string) static int curl_progress(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow) { php_curl *ch = (php_curl *)clientp; - int rval = 0; + int rval = 1; #if PHP_CURL_DEBUG fprintf(stderr, "curl_progress() called\n"); @@ -633,8 +635,8 @@ static int curl_progress(void *clientp, double dltotal, double dlnow, double ult if (!Z_ISUNDEF(retval)) { _php_curl_verify_handlers(ch, /* reporterror */ true); /* TODO Check callback returns an int or something castable to int */ - if (0 != php_curl_get_long(&retval)) { - rval = 1; + if (0 == php_curl_get_long(&retval)) { + rval = 0; } } @@ -647,7 +649,7 @@ static int curl_progress(void *clientp, double dltotal, double dlnow, double ult static int curl_xferinfo(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) { php_curl *ch = (php_curl *)clientp; - int rval = 0; + int rval = 1; #if PHP_CURL_DEBUG fprintf(stderr, "curl_xferinfo() called\n"); @@ -674,8 +676,8 @@ static int curl_xferinfo(void *clientp, curl_off_t dltotal, curl_off_t dlnow, cu if (!Z_ISUNDEF(retval)) { _php_curl_verify_handlers(ch, /* reporterror */ true); /* TODO Check callback returns an int or something castable to int */ - if (0 != php_curl_get_long(&retval)) { - rval = 1; + if (0 == php_curl_get_long(&retval)) { + rval = 0; } } @@ -688,13 +690,13 @@ static int curl_xferinfo(void *clientp, curl_off_t dltotal, curl_off_t dlnow, cu static int curl_prereqfunction(void *clientp, char *conn_primary_ip, char *conn_local_ip, int conn_primary_port, int conn_local_port) { php_curl *ch = (php_curl *)clientp; - int rval = CURL_PREREQFUNC_OK; + int rval = CURL_PREREQFUNC_ABORT; // when CURLOPT_PREREQFUNCTION is set to null, curl_prereqfunction still // gets called. Return CURL_PREREQFUNC_OK immediately in this case to avoid // zend_call_known_fcc() with an uninitialized FCC. if (!ZEND_FCC_INITIALIZED(ch->handlers.prereq)) { - return rval; + return CURL_PREREQFUNC_OK; } #if PHP_CURL_DEBUG @@ -823,6 +825,8 @@ static size_t curl_read(char *data, size_t size, size_t nmemb, void *ctx) } // TODO Do type error if invalid type? zval_ptr_dtor(&retval); + } else if (EG(exception)) { + length = CURL_READFUNC_ABORT; } zval_ptr_dtor(&argv[0]); @@ -915,6 +919,8 @@ static size_t curl_write_header(char *data, size_t size, size_t nmemb, void *ctx // TODO: Check for valid int type for return value _php_curl_verify_handlers(ch, /* reporterror */ true); length = php_curl_get_long(&retval); + } else if (EG(exception)) { + length = -1; } zval_ptr_dtor(&argv[0]); zval_ptr_dtor(&argv[1]); diff --git a/ext/curl/tests/curl_headerfunction_throws_abort.phpt b/ext/curl/tests/curl_headerfunction_throws_abort.phpt new file mode 100644 index 000000000000..648784403971 --- /dev/null +++ b/ext/curl/tests/curl_headerfunction_throws_abort.phpt @@ -0,0 +1,35 @@ +--TEST-- +CURLOPT_HEADERFUNCTION aborts transfer when callback throws +--EXTENSIONS-- +curl +--SKIPIF-- + +--FILE-- +getMessage(), "\n"; +} + +var_dump(curl_errno($ch) === CURLE_WRITE_ERROR); + +?> +--EXPECTF-- +header exception +bool(true) diff --git a/ext/curl/tests/curl_prereqfunction_throws_abort.phpt b/ext/curl/tests/curl_prereqfunction_throws_abort.phpt new file mode 100644 index 000000000000..7e8ccbf94f53 --- /dev/null +++ b/ext/curl/tests/curl_prereqfunction_throws_abort.phpt @@ -0,0 +1,35 @@ +--TEST-- +CURLOPT_PREREQFUNCTION aborts transfer when callback throws +--EXTENSIONS-- +curl +--SKIPIF-- + +--FILE-- +getMessage(), "\n"; +} + +var_dump(curl_errno($ch) === CURLE_ABORTED_BY_CALLBACK); + +?> +--EXPECTF-- +prereq exception +bool(true) diff --git a/ext/curl/tests/curl_progressfunction_throws_abort.phpt b/ext/curl/tests/curl_progressfunction_throws_abort.phpt new file mode 100644 index 000000000000..f03d7b96d90a --- /dev/null +++ b/ext/curl/tests/curl_progressfunction_throws_abort.phpt @@ -0,0 +1,36 @@ +--TEST-- +CURLOPT_PROGRESSFUNCTION aborts transfer when callback throws +--EXTENSIONS-- +curl +--SKIPIF-- + +--FILE-- +getMessage(), "\n"; +} + +var_dump(curl_errno($ch) === CURLE_ABORTED_BY_CALLBACK); + +?> +--EXPECTF-- +info exception +bool(true) diff --git a/ext/curl/tests/curl_readfunction_throws_abort.phpt b/ext/curl/tests/curl_readfunction_throws_abort.phpt new file mode 100644 index 000000000000..fdb83dbb421d --- /dev/null +++ b/ext/curl/tests/curl_readfunction_throws_abort.phpt @@ -0,0 +1,38 @@ +--TEST-- +CURLOPT_READFUNCTION aborts transfer when callback throws +--EXTENSIONS-- +curl +--SKIPIF-- + +--FILE-- + $file]); + +curl_setopt($ch, CURLOPT_READFUNCTION, + function (): int { + throw new Exception('read exception'); + } +); + +try { + curl_exec($ch); +} catch (Exception $e) { + echo $e->getMessage(), "\n"; +} + +var_dump(curl_errno($ch) === CURLE_ABORTED_BY_CALLBACK); + +?> +--EXPECTF-- +read exception +bool(true) diff --git a/ext/curl/tests/curl_writefunction_throws_abort.phpt b/ext/curl/tests/curl_writefunction_throws_abort.phpt new file mode 100644 index 000000000000..93c7a9237e95 --- /dev/null +++ b/ext/curl/tests/curl_writefunction_throws_abort.phpt @@ -0,0 +1,35 @@ +--TEST-- +CURLOPT_WRITEFUNCTION aborts transfer when callback throws +--EXTENSIONS-- +curl +--SKIPIF-- + +--FILE-- +getMessage(), "\n"; +} + +var_dump(curl_errno($ch) === CURLE_WRITE_ERROR); + +?> +--EXPECTF-- +write exception +bool(true) diff --git a/ext/curl/tests/curl_xferinfofunction_throws_abort.phpt b/ext/curl/tests/curl_xferinfofunction_throws_abort.phpt new file mode 100644 index 000000000000..2c297d7f816f --- /dev/null +++ b/ext/curl/tests/curl_xferinfofunction_throws_abort.phpt @@ -0,0 +1,36 @@ +--TEST-- +CURLOPT_XFERINFOFUNCTION aborts transfer when callback throws +--EXTENSIONS-- +curl +--SKIPIF-- + +--FILE-- +getMessage(), "\n"; +} + +var_dump(curl_errno($ch) === CURLE_ABORTED_BY_CALLBACK); + +?> +--EXPECTF-- +info exception +bool(true)