[php-src] master: ext/curl: abort curl transfer if callback throws exception (#22745)

From: Date: Fri, 07 Aug 2026 18:16:11 +0000
Subject: [php-src] master: ext/curl: abort curl transfer if callback throws exception (#22745)
Groups: php.cvs 
Request: Send a blank email to php-cvs+get-140536@lists.php.net to get a copy of this message
Author: Sjoerd Langkemper (Sjord) Committer: GitHub (web-flow) Pusher: Girgias Date: 2026-08-07T19:16:08+01:00 Commit: https://github.com/php/php-src/commit/28febe2a91c5ef7dd5764780729981f912726749 Raw diff: https://github.com/php/php-src/commit/28febe2a91c5ef7dd5764780729981f912726749.diff ext/curl: abort curl transfer if callback throws exception (#22745) Closes GH-16513 Closes GH-16790 Changed paths: A ext/curl/tests/curl_headerfunction_throws_abort.phpt A ext/curl/tests/curl_prereqfunction_throws_abort.phpt A ext/curl/tests/curl_progressfunction_throws_abort.phpt A ext/curl/tests/curl_readfunction_throws_abort.phpt A ext/curl/tests/curl_writefunction_throws_abort.phpt A ext/curl/tests/curl_xferinfofunction_throws_abort.phpt M ext/curl/interface.c Diff: diff --git a/ext/curl/interface.c b/ext/curl/interface.c index 8c6d6a0c1f20..e198b0bb7d77 100644 --- a/ext/curl/interface.c +++ b/ext/curl/interface.c @@ -583,6 +583,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 { + length = -1; } zval_ptr_dtor(&argv[0]); @@ -632,14 +634,14 @@ 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; // error #if PHP_CURL_DEBUG fprintf(stderr, "curl_progress() called\n"); fprintf(stderr, "clientp = %p, dltotal = %f, dlnow = %f, ultotal = %f, ulnow = %f\n", clientp, dltotal, dlnow, ultotal, ulnow); #endif if (!ZEND_FCC_INITIALIZED(ch->handlers.progress)) { - return rval; + return 0; // ok } zval args[5]; @@ -659,8 +661,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; // ok } } @@ -673,14 +675,14 @@ 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; // error #if PHP_CURL_DEBUG fprintf(stderr, "curl_xferinfo() called\n"); fprintf(stderr, "clientp = %p, dltotal = %ld, dlnow = %ld, ultotal = %ld, ulnow = %ld\n", clientp, dltotal, dlnow, ultotal, ulnow); #endif - if (!ZEND_FCC_INITIALIZED(ch->handlers.xferinfo)) { - return rval; + if (UNEXPECTED(!ZEND_FCC_INITIALIZED(ch->handlers.xferinfo))) { + return 0; // ok } zval argv[5]; @@ -700,8 +702,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; // ok } } @@ -714,13 +716,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; + if (UNEXPECTED(!ZEND_FCC_INITIALIZED(ch->handlers.prereq))) { + return CURL_PREREQFUNC_OK; } #if PHP_CURL_DEBUG @@ -858,6 +860,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 { + length = CURL_READFUNC_ABORT; } zval_ptr_dtor(&argv[0]); @@ -952,6 +956,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 { + 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..9a69c966f144 --- /dev/null +++ b/ext/curl/tests/curl_headerfunction_throws_abort.phpt @@ -0,0 +1,45 @@ +--TEST-- +CURLOPT_HEADERFUNCTION aborts transfer when callback throws +--EXTENSIONS-- +curl +--SKIPIF-- +<?php +if (!defined('CURLOPT_HEADERFUNCTION')) { + die('skip CURLOPT_HEADERFUNCTION not available'); +} +?> +--FILE-- +<?php + +include 'server.inc'; +$host = curl_cli_server_start(); +$ch = curl_init("{$host}/get.inc"); + +echo "Test: header function throws exception\n"; +curl_setopt($ch, CURLOPT_HEADERFUNCTION, + function (): int { + throw new Exception('header exception'); + } +); + +try { + curl_exec($ch); +} catch (Exception $e) { + echo $e->getMessage(), "\n"; +} + +var_dump(curl_errno($ch) === CURLE_WRITE_ERROR); + +echo "Test: header function is null\n"; +curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); +curl_setopt($ch, CURLOPT_HEADERFUNCTION, null); +curl_exec($ch); +var_dump(curl_errno($ch) === CURLE_OK); + +?> +--EXPECTF-- +Test: header function throws exception +header exception +bool(true) +Test: header function is null +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-- +<?php +if (!defined('CURLOPT_PREREQFUNCTION')) { + die('skip CURLOPT_PREREQFUNCTION not available'); +} +?> +--FILE-- +<?php + +include 'server.inc'; +$host = curl_cli_server_start(); +$ch = curl_init("{$host}/get.inc"); + +curl_setopt($ch, CURLOPT_PREREQFUNCTION, + function (): int { + throw new Exception('prereq exception'); + } +); + +try { + curl_exec($ch); +} catch (Exception $e) { + echo $e->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..55e0f76cb61f --- /dev/null +++ b/ext/curl/tests/curl_progressfunction_throws_abort.phpt @@ -0,0 +1,46 @@ +--TEST-- +CURLOPT_PROGRESSFUNCTION aborts transfer when callback throws +--EXTENSIONS-- +curl +--SKIPIF-- +<?php +if (!defined('CURLOPT_PROGRESSFUNCTION')) { + die('skip CURLOPT_PROGRESSFUNCTION not available'); +} +?> +--FILE-- +<?php + +include 'server.inc'; +$host = curl_cli_server_start(); +$ch = curl_init("{$host}/get.inc"); + +echo "Test: progress function throws exception\n"; +curl_setopt($ch, CURLOPT_NOPROGRESS, 0); +curl_setopt($ch, CURLOPT_PROGRESSFUNCTION, + function (): int { + throw new Exception('info exception'); + } +); + +try { + curl_exec($ch); +} catch (Exception $e) { + echo $e->getMessage(), "\n"; +} + +var_dump(curl_errno($ch) === CURLE_ABORTED_BY_CALLBACK); + +echo "Test: progress function is null\n"; +curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); +curl_setopt($ch, CURLOPT_PROGRESSFUNCTION, null); +curl_exec($ch); +var_dump(curl_errno($ch) === CURLE_OK); + +?> +--EXPECTF-- +Test: progress function throws exception +info exception +bool(true) +Test: progress function is null +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..a030f8c4f41e --- /dev/null +++ b/ext/curl/tests/curl_readfunction_throws_abort.phpt @@ -0,0 +1,48 @@ +--TEST-- +CURLOPT_READFUNCTION aborts transfer when callback throws +--EXTENSIONS-- +curl +--SKIPIF-- +<?php +if (!defined('CURLOPT_READFUNCTION')) { + die('skip CURLOPT_READFUNCTION not available'); +} +?> +--FILE-- +<?php + +include 'server.inc'; +$host = curl_cli_server_start(); +$ch = curl_init("{$host}/get.inc"); + +$file = new CURLFile(__DIR__ . '/curl_testdata1.txt'); +curl_setopt($ch, CURLOPT_POST, 1); + +echo "Test: read function throws exception\n"; +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); + +echo "Test: read function is null\n"; +curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); +curl_setopt($ch, CURLOPT_READFUNCTION, null); +curl_exec($ch); +var_dump(curl_errno($ch) === CURLE_OK); + +?> +--EXPECTF-- +Test: read function throws exception +read exception +bool(true) +Test: read function is null +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..3da2fe8107b4 --- /dev/null +++ b/ext/curl/tests/curl_writefunction_throws_abort.phpt @@ -0,0 +1,45 @@ +--TEST-- +CURLOPT_WRITEFUNCTION aborts transfer when callback throws +--EXTENSIONS-- +curl +--SKIPIF-- +<?php +if (!defined('CURLOPT_WRITEFUNCTION')) { + die('skip CURLOPT_WRITEFUNCTION not available'); +} +?> +--FILE-- +<?php + +include 'server.inc'; +$host = curl_cli_server_start(); +$ch = curl_init("{$host}/get.inc"); + +echo "Test: write function throws exception\n"; +curl_setopt($ch, CURLOPT_WRITEFUNCTION, + function (): int { + throw new Exception('write exception'); + } +); + +try { + curl_exec($ch); +} catch (Exception $e) { + echo $e->getMessage(), "\n"; +} + +var_dump(curl_errno($ch) === CURLE_WRITE_ERROR); + +echo "Test: write function is null\n"; +curl_setopt($ch, CURLOPT_WRITEFUNCTION, null); +curl_exec($ch); +var_dump(curl_errno($ch) === CURLE_OK); + +?> +--EXPECTF-- +Test: write function throws exception +write exception +bool(true) +Test: write function is null +Hello World! +Hello World!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..fbc28f07ee96 --- /dev/null +++ b/ext/curl/tests/curl_xferinfofunction_throws_abort.phpt @@ -0,0 +1,46 @@ +--TEST-- +CURLOPT_XFERINFOFUNCTION aborts transfer when callback throws +--EXTENSIONS-- +curl +--SKIPIF-- +<?php +if (!defined('CURLOPT_XFERINFOFUNCTION')) { + die('skip CURLOPT_XFERINFOFUNCTION not available'); +} +?> +--FILE-- +<?php + +include 'server.inc'; +$host = curl_cli_server_start(); +$ch = curl_init("{$host}/get.inc"); + +echo "Test: xfer info function throws exception\n"; +curl_setopt($ch, CURLOPT_NOPROGRESS, 0); +curl_setopt($ch, CURLOPT_XFERINFOFUNCTION, + function (): int { + throw new Exception('info exception'); + } +); + +try { + curl_exec($ch); +} catch (Exception $e) { + echo $e->getMessage(), "\n"; +} + +var_dump(curl_errno($ch) === CURLE_ABORTED_BY_CALLBACK); + +echo "Test: xfer info function is null\n"; +curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); +curl_setopt($ch, CURLOPT_XFERINFOFUNCTION, null); +curl_exec($ch); +var_dump(curl_errno($ch) === CURLE_OK); + +?> +--EXPECTF-- +Test: xfer info function throws exception +info exception +bool(true) +Test: xfer info function is null +bool(true)

« previous php.cvs (#140536) next »