[PHP-CVS] [php-src] master: ext/curl: show curl option name in error message (#22908)

[email protected] (Sjoerd Langkemper via GitHub)
Newsgroups php.cvs
Message-ID <[email protected]>
Author: Sjoerd Langkemper (Sjord)
Committer: GitHub (web-flow)
Pusher: arnaud-lb
Date: 2026-08-06T13:10:54+02:00

Commit: https://github.com/php/php-src/commit/6a4ab2ed305aaba30f76bcd3a531c33972546304
Raw diff: https://github.com/php/php-src/commit/6a4ab2ed305aaba30f76bcd3a531c33972546304.diff

ext/curl: show curl option name in error message (#22908)

Changed paths:
  M  NEWS
  M  ext/curl/interface.c
  M  ext/curl/tests/bug48207.phpt
  M  ext/curl/tests/bug68089.phpt


Diff:

diff --git a/NEWS b/NEWS
index 96895dc4a4bc..fd4d58dd3c20 100644
--- a/NEWS
+++ b/NEWS
@@ -7,6 +7,8 @@ PHP                                                                        NEWS
     possible. (NickSdot)
 
 - Curl:
+  . Improved cURL option validation errors to include the option name.
+    (Sjoerd Langkemper)
   . Raise a value error when the callback registered with CURLOPT_READFUNCTION
     returns an unexpected long. (Sjoerd Langkemper)
 
diff --git a/ext/curl/interface.c b/ext/curl/interface.c
index 6df7cf66fbe7..8c6d6a0c1f20 100644
--- a/ext/curl/interface.c
+++ b/ext/curl/interface.c
@@ -62,10 +62,39 @@ ZEND_DECLARE_MODULE_GLOBALS(curl)
 # define php_curl_ret(__ret) RETVAL_FALSE; return;
 #endif
 
+// php_curl_option_get_name(CURLOPT_HTTPHEADER) -> "HTTPHEADER"
+static const char * php_curl_option_get_name(zend_long option) {
+
+#if LIBCURL_VERSION_NUM >= 0x074900
+	const struct curl_easyoption * opt = curl_easy_option_by_id(option);
+	if (EXPECTED(opt != NULL)) {
+		return opt->name;
+	}
+#endif
+
+	const char prefix[] = "CURLOPT_";
+	const size_t prefix_len = sizeof(prefix) - 1;
+	zend_string *key;
+	zend_constant *constant;
+
+	ZEND_HASH_FOREACH_STR_KEY_PTR(EG(zend_constants), key, constant) {
+		if (!key
+			|| Z_TYPE(constant->value) != IS_LONG
+			|| strncmp(ZSTR_VAL(key), prefix, prefix_len) != 0) {
+			continue;
+		}
+
+		if (Z_LVAL(constant->value) == option) {
+			return ZSTR_VAL(key) + prefix_len;
+		}
+	} ZEND_HASH_FOREACH_END();
+	return "UNKNOWN_OPTION";
+}
+
 static zend_result php_curl_option_str(php_curl *ch, zend_long option, const char *str, const size_t len)
 {
 	if (zend_char_has_nul_byte(str, len)) {
-		zend_value_error("%s(): cURL option must not contain any null bytes", get_active_function_name());
+		zend_value_error("%s(): cURL option CURLOPT_%s must not contain any null bytes", get_active_function_name(), php_curl_option_get_name(option));
 		return FAILURE;
 	}
 
@@ -2017,7 +2046,7 @@ static zend_result _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue
 						ch->handlers.write->method = PHP_CURL_FILE;
 						ZVAL_COPY(&ch->handlers.write->stream, zvalue);
 					} else {
-						zend_value_error("%s(): The provided file handle must be writable", get_active_function_name());
+						zend_value_error("%s(): The file handle provided for CURLOPT_FILE must be writable", get_active_function_name());
 						return FAILURE;
 					}
 					break;
@@ -2035,7 +2064,7 @@ static zend_result _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue
 						ch->handlers.write_header->method = PHP_CURL_FILE;
 						ZVAL_COPY(&ch->handlers.write_header->stream, zvalue);
 					} else {
-						zend_value_error("%s(): The provided file handle must be writable", get_active_function_name());
+						zend_value_error("%s(): The file handle provided for CURLOPT_WRITEHEADER must be writable", get_active_function_name());
 						return FAILURE;
 					}
 					break;
@@ -2064,7 +2093,7 @@ static zend_result _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue
 						zval_ptr_dtor(&ch->handlers.std_err);
 						ZVAL_COPY(&ch->handlers.std_err, zvalue);
 					} else {
-						zend_value_error("%s(): The provided file handle must be writable", get_active_function_name());
+						zend_value_error("%s(): The file handle provided for CURLOPT_STDERR must be writable", get_active_function_name());
 						return FAILURE;
 					}
 					ZEND_FALLTHROUGH;
@@ -2091,43 +2120,9 @@ static zend_result _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue
 			HashTable *ph;
 			zend_string *val, *tmp_val;
 			struct curl_slist *slist = NULL;
-			const char *name = NULL;
-
-			switch (option) {
-				case CURLOPT_HTTPHEADER:
-					name = "CURLOPT_HTTPHEADER";
-					break;
-				case CURLOPT_QUOTE:
-					name = "CURLOPT_QUOTE";
-					break;
-				case CURLOPT_HTTP200ALIASES:
-					name = "CURLOPT_HTTP200ALIASES";
-					break;
-				case CURLOPT_POSTQUOTE:
-					name = "CURLOPT_POSTQUOTE";
-					break;
-				case CURLOPT_PREQUOTE:
-					name = "CURLOPT_PREQUOTE";
-					break;
-				case CURLOPT_TELNETOPTIONS:
-					name = "CURLOPT_TELNETOPTIONS";
-					break;
-				case CURLOPT_MAIL_RCPT:
-					name = "CURLOPT_MAIL_RCPT";
-					break;
-				case CURLOPT_RESOLVE:
-					name = "CURLOPT_RESOLVE";
-					break;
-				case CURLOPT_PROXYHEADER:
-					name = "CURLOPT_PROXYHEADER";
-					break;
-				case CURLOPT_CONNECT_TO:
-					name = "CURLOPT_CONNECT_TO";
-					break;
-			}
 
 			if (Z_TYPE_P(zvalue) != IS_ARRAY) {
-				zend_type_error("%s(): The %s option must have an array value", get_active_function_name(), name);
+				zend_type_error("%s(): The CURLOPT_%s option must have an array value", get_active_function_name(), php_curl_option_get_name(option));
 				return FAILURE;
 			}
 
@@ -2139,7 +2134,7 @@ static zend_result _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue
 				if (zend_str_has_nul_byte(val)) {
 					curl_slist_free_all(slist);
 					zend_tmp_string_release(tmp_val);
-					zend_value_error("%s(): cURL option %s must not contain any null bytes", get_active_function_name(), name);
+					zend_value_error("%s(): cURL option CURLOPT_%s must not contain any null bytes", get_active_function_name(), php_curl_option_get_name(option));
 					return FAILURE;
 				}
 
diff --git a/ext/curl/tests/bug48207.phpt b/ext/curl/tests/bug48207.phpt
index 086f949ff63d..6487076e9ee3 100644
--- a/ext/curl/tests/bug48207.phpt
+++ b/ext/curl/tests/bug48207.phpt
@@ -36,10 +36,17 @@ $tempfile	= tempnam(sys_get_temp_dir(), 'CURL_FILE_HANDLE');
 $fp = fopen($tempfile, "r"); // Opening 'fubar' with the incorrect readonly flag
 
 $ch = curl_init($url);
-try {
-    curl_setopt($ch, CURLOPT_FILE, $fp);
-} catch (ValueError $exception) {
-    echo $exception->getMessage() . "\n";
+
+foreach ([
+    CURLOPT_FILE,
+    CURLOPT_WRITEHEADER,
+    CURLOPT_STDERR,
+] as $option) {
+    try {
+        curl_setopt($ch, $option, $fp);
+    } catch (ValueError $exception) {
+        echo $exception->getMessage(), "\n";
+    }
 }
 
 curl_exec($ch);
@@ -47,6 +54,8 @@ is_file($tempfile) and @unlink($tempfile);
 isset($tempname) and is_file($tempname) and @unlink($tempname);
 ?>
 --EXPECT--
-curl_setopt(): The provided file handle must be writable
+curl_setopt(): The file handle provided for CURLOPT_FILE must be writable
+curl_setopt(): The file handle provided for CURLOPT_WRITEHEADER must be writable
+curl_setopt(): The file handle provided for CURLOPT_STDERR must be writable
 Hello World!
 Hello World!
diff --git a/ext/curl/tests/bug68089.phpt b/ext/curl/tests/bug68089.phpt
index b8733c5066b8..c175df5248fe 100644
--- a/ext/curl/tests/bug68089.phpt
+++ b/ext/curl/tests/bug68089.phpt
@@ -16,5 +16,5 @@ try {
 ?>
 Done
 --EXPECT--
-curl_setopt(): cURL option must not contain any null bytes
+curl_setopt(): cURL option CURLOPT_URL must not contain any null bytes
 Done
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.