Commit 6a4ab2ed305 for php.net

commit 6a4ab2ed305aaba30f76bcd3a531c33972546304
Author: Sjoerd Langkemper <sjoerd-github@linuxonly.nl>
Date:   Thu Aug 6 13:10:54 2026 +0200

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

diff --git a/NEWS b/NEWS
index 96895dc4a4b..fd4d58dd3c2 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 6df7cf66fbe..8c6d6a0c1f2 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 086f949ff63..6487076e9ee 100644
--- a/ext/curl/tests/bug48207.phpt
+++ b/ext/curl/tests/bug48207.phpt
@@ -36,10 +36,17 @@
 $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 @@
 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 b8733c5066b..c175df5248f 100644
--- a/ext/curl/tests/bug68089.phpt
+++ b/ext/curl/tests/bug68089.phpt
@@ -16,5 +16,5 @@
 ?>
 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