Commit 0d9ff00394d for php.net

commit 0d9ff00394d9447992bb66ccb1cef3edf70576bd
Author: Calvin Buckley <calvinb@php.net>
Date:   Wed Apr 29 10:00:01 2026 -0400

    ext/odbc: Get the exact length for quoted connection string components (#21892)

    * ext/odbc: get exact length for quoted connection strings

    strlen has to scan the string until the end of the null terminator, we
    might as well do the same but scan for end quotes that need escaping.

    * ext/odbc: rename function now that it gets the exact length rather than an estimate

diff --git a/ext/odbc/odbc_utils.c b/ext/odbc/odbc_utils.c
index 742bf54bb29..8468fa9a06c 100644
--- a/ext/odbc/odbc_utils.c
+++ b/ext/odbc/odbc_utils.c
@@ -57,7 +57,7 @@ PHP_FUNCTION(odbc_connection_string_quote)
 		Z_PARAM_STR(str)
 	ZEND_PARSE_PARAMETERS_END();

-	size_t new_size = php_odbc_connstr_estimate_quote_length(ZSTR_VAL(str));
+	size_t new_size = php_odbc_connstr_get_quoted_length(ZSTR_VAL(str));
 	zend_string *new_string = zend_string_alloc(new_size, 0);
 	php_odbc_connstr_quote(ZSTR_VAL(new_string), ZSTR_VAL(str), new_size);
 	/* reset length */
diff --git a/ext/odbc/php_odbc.c b/ext/odbc/php_odbc.c
index 2d6331b0f54..c9f1b3f2e14 100644
--- a/ext/odbc/php_odbc.c
+++ b/ext/odbc/php_odbc.c
@@ -1951,9 +1951,9 @@ bool odbc_sqlconnect(zval *zv, char *db, char *uid, char *pwd, int cur_opt, bool
 				if (use_uid_arg) {
 					should_quote_uid = !php_odbc_connstr_is_quoted(uid) && php_odbc_connstr_should_quote(uid);
 					if (should_quote_uid) {
-						size_t estimated_length = php_odbc_connstr_estimate_quote_length(uid);
-						uid_quoted = emalloc(estimated_length);
-						php_odbc_connstr_quote(uid_quoted, uid, estimated_length);
+						size_t quoted_length = php_odbc_connstr_get_quoted_length(uid);
+						uid_quoted = emalloc(quoted_length);
+						php_odbc_connstr_quote(uid_quoted, uid, quoted_length);
 					} else {
 						uid_quoted = uid;
 					}
@@ -1966,9 +1966,9 @@ bool odbc_sqlconnect(zval *zv, char *db, char *uid, char *pwd, int cur_opt, bool
 				if (use_pwd_arg) {
 					should_quote_pwd = !php_odbc_connstr_is_quoted(pwd) && php_odbc_connstr_should_quote(pwd);
 					if (should_quote_pwd) {
-						size_t estimated_length = php_odbc_connstr_estimate_quote_length(pwd);
-						pwd_quoted = emalloc(estimated_length);
-						php_odbc_connstr_quote(pwd_quoted, pwd, estimated_length);
+						size_t quoted_length = php_odbc_connstr_get_quoted_length(pwd);
+						pwd_quoted = emalloc(quoted_length);
+						php_odbc_connstr_quote(pwd_quoted, pwd, quoted_length);
 					} else {
 						pwd_quoted = pwd;
 					}
diff --git a/ext/pdo_odbc/odbc_driver.c b/ext/pdo_odbc/odbc_driver.c
index 4c627419d18..3c8afe2d421 100644
--- a/ext/pdo_odbc/odbc_driver.c
+++ b/ext/pdo_odbc/odbc_driver.c
@@ -548,9 +548,9 @@ static int pdo_odbc_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{{
 			if (use_uid_arg) {
 				should_quote_uid = !php_odbc_connstr_is_quoted(dbh->username) && php_odbc_connstr_should_quote(dbh->username);
 				if (should_quote_uid) {
-					size_t estimated_length = php_odbc_connstr_estimate_quote_length(dbh->username);
-					uid = emalloc(estimated_length);
-					php_odbc_connstr_quote(uid, dbh->username, estimated_length);
+					size_t quoted_length = php_odbc_connstr_get_quoted_length(dbh->username);
+					uid = emalloc(quoted_length);
+					php_odbc_connstr_quote(uid, dbh->username, quoted_length);
 				} else {
 					uid = dbh->username;
 				}
@@ -565,9 +565,9 @@ static int pdo_odbc_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{{
 			if (use_pwd_arg) {
 				should_quote_pwd = !php_odbc_connstr_is_quoted(dbh->password) && php_odbc_connstr_should_quote(dbh->password);
 				if (should_quote_pwd) {
-					size_t estimated_length = php_odbc_connstr_estimate_quote_length(dbh->password);
-					pwd = emalloc(estimated_length);
-					php_odbc_connstr_quote(pwd, dbh->password, estimated_length);
+					size_t quoted_length = php_odbc_connstr_get_quoted_length(dbh->password);
+					pwd = emalloc(quoted_length);
+					php_odbc_connstr_quote(pwd, dbh->password, quoted_length);
 				} else {
 					pwd = dbh->password;
 				}
diff --git a/main/php_odbc_utils.c b/main/php_odbc_utils.c
index d3b18f2bd64..797a4ac04be 100644
--- a/main/php_odbc_utils.c
+++ b/main/php_odbc_utils.c
@@ -71,12 +71,20 @@ PHPAPI bool php_odbc_connstr_should_quote(const char *str)
 }

 /**
- * Estimates the worst-case scenario for a quoted version of a string's size.
+ * Gets the length of a string after it has been quoted.
  */
-PHPAPI size_t php_odbc_connstr_estimate_quote_length(const char *in_str)
+PHPAPI size_t php_odbc_connstr_get_quoted_length(const char *in_str)
 {
-	/* Assume all '}'. Include '{,' '}', and the null terminator too */
-	return (strlen(in_str) * 2) + 3;
+	/* Start with including the quotes ({}) and the null terminator */
+	size_t size = 3;
+	/* Scan the string like strlen, doubling each } character. */
+	while (*in_str) {
+		size++;
+		if (*in_str++ == '}') {
+			size++;
+		}
+	}
+	return size;
 }

 /**
diff --git a/main/php_odbc_utils.h b/main/php_odbc_utils.h
index 78353b49a81..f2bc46ebc5b 100644
--- a/main/php_odbc_utils.h
+++ b/main/php_odbc_utils.h
@@ -16,5 +16,5 @@

 PHPAPI bool php_odbc_connstr_is_quoted(const char *str);
 PHPAPI bool php_odbc_connstr_should_quote(const char *str);
-PHPAPI size_t php_odbc_connstr_estimate_quote_length(const char *in_str);
+PHPAPI size_t php_odbc_connstr_get_quoted_length(const char *in_str);
 PHPAPI size_t php_odbc_connstr_quote(char *out_str, const char *in_str, size_t out_str_size);