Commit fbf8bf84dbe for php.net
commit fbf8bf84dbe3bed33a179a668e2c3d5d109d469a
Author: Kamil Tekiela <tekiela246@gmail.com>
Date: Sun Apr 5 18:43:57 2026 +0100
Implement mysqli::quote_string method (#20729)
diff --git a/NEWS b/NEWS
index 1759f6e2ee7..11a0dbb34a0 100644
--- a/NEWS
+++ b/NEWS
@@ -64,6 +64,9 @@ PHP NEWS
. Fixed bug GH-21223; mb_guess_encoding no longer crashes when passed huge
list of candidate encodings (with 200,000+ entries). (Jordi Kroon)
+- Opcache:
+ . Added mysqli_quote_string() and mysqli::quote_string(). (Kamil Tekiela)
+
- Opcache:
. Fixed bug GH-20051 (apache2 shutdowns when restart is requested during
preloading). (Arnaud, welcomycozyhom)
diff --git a/UPGRADING b/UPGRADING
index 467387a9ea3..18f34eca198 100644
--- a/UPGRADING
+++ b/UPGRADING
@@ -151,6 +151,10 @@ PHP 8.6 UPGRADE NOTES
. `grapheme_strrev()` returns strrev for grapheme cluster unit.
RFC: https://wiki.php.net/rfc/grapheme_strrev
+- mysqli:
+ . Added `mysqli::quote_string()` and `mysqli_quote_string()`.
+ RFC: https://wiki.php.net/rfc/mysqli_quote_string
+
- Standard:
. `clamp()` returns the given value if in range, else return the nearest
bound.
diff --git a/ext/mysqli/mysqli.stub.php b/ext/mysqli/mysqli.stub.php
index 7ca2a20ca1b..cc6b8c57404 100644
--- a/ext/mysqli/mysqli.stub.php
+++ b/ext/mysqli/mysqli.stub.php
@@ -906,6 +906,11 @@ public function real_connect(
*/
public function real_escape_string(string $string): string {}
+ /**
+ * @alias mysqli_quote_string
+ */
+ public function quote_string(string $string): string {}
+
/**
* @tentative-return-type
* @alias mysqli_reap_async_query
@@ -1547,6 +1552,8 @@ function mysqli_real_escape_string(mysqli $mysql, string $string): string {}
/** @alias mysqli_real_escape_string */
function mysqli_escape_string(mysqli $mysql, string $string): string {}
+function mysqli_quote_string(mysqli $mysql, string $string): string {}
+
function mysqli_real_query(mysqli $mysql, string $query): bool {}
/** @refcount 1 */
diff --git a/ext/mysqli/mysqli_api.c b/ext/mysqli/mysqli_api.c
index 9473f4a06c1..259fc128237 100644
--- a/ext/mysqli/mysqli_api.c
+++ b/ext/mysqli/mysqli_api.c
@@ -1198,7 +1198,7 @@ PHP_FUNCTION(mysqli_options)
zend_argument_value_error(ERROR_ARG_POS(2), "must be MYSQLI_INIT_COMMAND, MYSQLI_SET_CHARSET_NAME, MYSQLI_SERVER_PUBLIC_KEY, or one of the MYSQLI_OPT_* constants");
RETURN_THROWS();
}
-
+
if (expected_type != Z_TYPE_P(mysql_value)) {
switch (expected_type) {
case IS_STRING:
@@ -1363,6 +1363,29 @@ PHP_FUNCTION(mysqli_real_escape_string) {
RETURN_NEW_STR(newstr);
}
+PHP_FUNCTION(mysqli_quote_string) {
+ MY_MYSQL *mysql;
+ zval *mysql_link = NULL;
+ char *escapestr;
+ size_t escapestr_len;
+ zend_string *newstr;
+
+ if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), "Os", &mysql_link, mysqli_link_class_entry, &escapestr, &escapestr_len) == FAILURE) {
+ RETURN_THROWS();
+ }
+ MYSQLI_FETCH_RESOURCE_CONN(mysql, mysql_link, MYSQLI_STATUS_VALID);
+
+ newstr = zend_string_safe_alloc(2, escapestr_len, 2, 0);
+ char *out = ZSTR_VAL(newstr);
+ *out++ = '\'';
+ out += mysql_real_escape_string(mysql->mysql, out, escapestr, escapestr_len);
+ *out++ = '\'';
+ *out = '\0';
+ newstr = zend_string_truncate(newstr, out - ZSTR_VAL(newstr), 0);
+
+ RETURN_NEW_STR(newstr);
+}
+
/* {{{ Undo actions from current transaction */
PHP_FUNCTION(mysqli_rollback)
{
diff --git a/ext/mysqli/mysqli_arginfo.h b/ext/mysqli/mysqli_arginfo.h
index 4439908e55d..0121f36f3cf 100644
Binary files a/ext/mysqli/mysqli_arginfo.h and b/ext/mysqli/mysqli_arginfo.h differ
diff --git a/ext/mysqli/tests/mysqli_class_mysqli_interface.phpt b/ext/mysqli/tests/mysqli_class_mysqli_interface.phpt
index bada1d85a5c..ccac6710edd 100644
--- a/ext/mysqli/tests/mysqli_class_mysqli_interface.phpt
+++ b/ext/mysqli/tests/mysqli_class_mysqli_interface.phpt
@@ -43,6 +43,7 @@
'ping' => true,
'prepare' => true,
'query' => true,
+ 'quote_string' => true,
'real_connect' => true,
'real_escape_string' => true,
'real_query' => true,
diff --git a/ext/mysqli/tests/mysqli_quote_string.phpt b/ext/mysqli/tests/mysqli_quote_string.phpt
new file mode 100644
index 00000000000..b4a25aa3996
--- /dev/null
+++ b/ext/mysqli/tests/mysqli_quote_string.phpt
@@ -0,0 +1,86 @@
+--TEST--
+mysqli_quote_string()
+--EXTENSIONS--
+mysqli
+--SKIPIF--
+<?php
+require_once 'skipifconnectfailure.inc';
+?>
+--FILE--
+<?php
+
+require_once 'connect.inc';
+mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
+$link = my_mysqli_connect($host, $user, $passwd, $db, $port, $socket);
+
+echo mysqli_quote_string($link, '\\') . "\n";
+echo mysqli_quote_string($link, '"') . "\n";
+echo mysqli_quote_string($link, "'") . "\n";
+
+$escaped = mysqli_quote_string($link, "\' \ \"");
+echo $escaped . "\n";
+$result = $link->query("SELECT $escaped AS test");
+$value = $result->fetch_column();
+echo $value . "\n";
+
+$escaped = mysqli_quote_string($link, '" OR 1=1 -- foo');
+echo $escaped . "\n";
+$result = $link->query("SELECT $escaped AS test");
+$value = $result->fetch_column();
+echo $value . "\n";
+
+$escaped = mysqli_quote_string($link, "\n");
+if ($escaped !== "'\\n'") {
+ printf("[001] Expected '\\n', got %s\n", $escaped);
+}
+
+$escaped = mysqli_quote_string($link, "\r");
+if ($escaped !== "'\\r'") {
+ printf("[002] Expected '\\r', got %s\n", $escaped);
+}
+
+$escaped = mysqli_quote_string($link, "foo" . chr(0) . "bar");
+if ($escaped !== "'foo\\0bar'") {
+ printf("[003] Expected 'foo\\0bar', got %s\n", $escaped);
+}
+
+echo "=====================\n";
+
+// Test that the SQL injection is impossible with NO_BACKSLASH_ESCAPES mode
+$link->query('SET @@sql_mode="NO_BACKSLASH_ESCAPES"');
+
+echo $link->quote_string('\\') . "\n";
+echo $link->quote_string('"') . "\n";
+echo $link->quote_string("'") . "\n";
+
+$escaped = $link->quote_string("\' \ \"");
+echo $escaped . "\n";
+$result = $link->query("SELECT $escaped AS test");
+$value = $result->fetch_column();
+echo $value . "\n";
+
+$escaped = $link->quote_string('" OR 1=1 -- foo');
+echo $escaped . "\n";
+$result = $link->query("SELECT $escaped AS test");
+$value = $result->fetch_column();
+echo $value . "\n";
+
+echo "done!";
+?>
+--EXPECT--
+'\\'
+'\"'
+'\''
+'\\\' \\ \"'
+\' \ "
+'\" OR 1=1 -- foo'
+" OR 1=1 -- foo
+=====================
+'\'
+'"'
+''''
+'\'' \ "'
+\' \ "
+'" OR 1=1 -- foo'
+" OR 1=1 -- foo
+done!