Skip to content

Commit fbf8bf8

Browse files
Implement mysqli::quote_string method (#20729)
1 parent 1a9a2c7 commit fbf8bf8

File tree

7 files changed

+135
-2
lines changed

7 files changed

+135
-2
lines changed

NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ PHP NEWS
6464
. Fixed bug GH-21223; mb_guess_encoding no longer crashes when passed huge
6565
list of candidate encodings (with 200,000+ entries). (Jordi Kroon)
6666

67+
- Opcache:
68+
. Added mysqli_quote_string() and mysqli::quote_string(). (Kamil Tekiela)
69+
6770
- Opcache:
6871
. Fixed bug GH-20051 (apache2 shutdowns when restart is requested during
6972
preloading). (Arnaud, welcomycozyhom)

UPGRADING

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,10 @@ PHP 8.6 UPGRADE NOTES
151151
. `grapheme_strrev()` returns strrev for grapheme cluster unit.
152152
RFC: https://wiki.php.net/rfc/grapheme_strrev
153153

154+
- mysqli:
155+
. Added `mysqli::quote_string()` and `mysqli_quote_string()`.
156+
RFC: https://wiki.php.net/rfc/mysqli_quote_string
157+
154158
- Standard:
155159
. `clamp()` returns the given value if in range, else return the nearest
156160
bound.

ext/mysqli/mysqli.stub.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -906,6 +906,11 @@ public function real_connect(
906906
*/
907907
public function real_escape_string(string $string): string {}
908908

909+
/**
910+
* @alias mysqli_quote_string
911+
*/
912+
public function quote_string(string $string): string {}
913+
909914
/**
910915
* @tentative-return-type
911916
* @alias mysqli_reap_async_query
@@ -1547,6 +1552,8 @@ function mysqli_real_escape_string(mysqli $mysql, string $string): string {}
15471552
/** @alias mysqli_real_escape_string */
15481553
function mysqli_escape_string(mysqli $mysql, string $string): string {}
15491554

1555+
function mysqli_quote_string(mysqli $mysql, string $string): string {}
1556+
15501557
function mysqli_real_query(mysqli $mysql, string $query): bool {}
15511558

15521559
/** @refcount 1 */

ext/mysqli/mysqli_api.c

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1198,7 +1198,7 @@ PHP_FUNCTION(mysqli_options)
11981198
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");
11991199
RETURN_THROWS();
12001200
}
1201-
1201+
12021202
if (expected_type != Z_TYPE_P(mysql_value)) {
12031203
switch (expected_type) {
12041204
case IS_STRING:
@@ -1363,6 +1363,29 @@ PHP_FUNCTION(mysqli_real_escape_string) {
13631363
RETURN_NEW_STR(newstr);
13641364
}
13651365

1366+
PHP_FUNCTION(mysqli_quote_string) {
1367+
MY_MYSQL *mysql;
1368+
zval *mysql_link = NULL;
1369+
char *escapestr;
1370+
size_t escapestr_len;
1371+
zend_string *newstr;
1372+
1373+
if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), "Os", &mysql_link, mysqli_link_class_entry, &escapestr, &escapestr_len) == FAILURE) {
1374+
RETURN_THROWS();
1375+
}
1376+
MYSQLI_FETCH_RESOURCE_CONN(mysql, mysql_link, MYSQLI_STATUS_VALID);
1377+
1378+
newstr = zend_string_safe_alloc(2, escapestr_len, 2, 0);
1379+
char *out = ZSTR_VAL(newstr);
1380+
*out++ = '\'';
1381+
out += mysql_real_escape_string(mysql->mysql, out, escapestr, escapestr_len);
1382+
*out++ = '\'';
1383+
*out = '\0';
1384+
newstr = zend_string_truncate(newstr, out - ZSTR_VAL(newstr), 0);
1385+
1386+
RETURN_NEW_STR(newstr);
1387+
}
1388+
13661389
/* {{{ Undo actions from current transaction */
13671390
PHP_FUNCTION(mysqli_rollback)
13681391
{

ext/mysqli/mysqli_arginfo.h

Lines changed: 10 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ext/mysqli/tests/mysqli_class_mysqli_interface.phpt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ require_once 'skipifconnectfailure.inc';
4343
'ping' => true,
4444
'prepare' => true,
4545
'query' => true,
46+
'quote_string' => true,
4647
'real_connect' => true,
4748
'real_escape_string' => true,
4849
'real_query' => true,
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
--TEST--
2+
mysqli_quote_string()
3+
--EXTENSIONS--
4+
mysqli
5+
--SKIPIF--
6+
<?php
7+
require_once 'skipifconnectfailure.inc';
8+
?>
9+
--FILE--
10+
<?php
11+
12+
require_once 'connect.inc';
13+
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
14+
$link = my_mysqli_connect($host, $user, $passwd, $db, $port, $socket);
15+
16+
echo mysqli_quote_string($link, '\\') . "\n";
17+
echo mysqli_quote_string($link, '"') . "\n";
18+
echo mysqli_quote_string($link, "'") . "\n";
19+
20+
$escaped = mysqli_quote_string($link, "\' \ \"");
21+
echo $escaped . "\n";
22+
$result = $link->query("SELECT $escaped AS test");
23+
$value = $result->fetch_column();
24+
echo $value . "\n";
25+
26+
$escaped = mysqli_quote_string($link, '" OR 1=1 -- foo');
27+
echo $escaped . "\n";
28+
$result = $link->query("SELECT $escaped AS test");
29+
$value = $result->fetch_column();
30+
echo $value . "\n";
31+
32+
$escaped = mysqli_quote_string($link, "\n");
33+
if ($escaped !== "'\\n'") {
34+
printf("[001] Expected '\\n', got %s\n", $escaped);
35+
}
36+
37+
$escaped = mysqli_quote_string($link, "\r");
38+
if ($escaped !== "'\\r'") {
39+
printf("[002] Expected '\\r', got %s\n", $escaped);
40+
}
41+
42+
$escaped = mysqli_quote_string($link, "foo" . chr(0) . "bar");
43+
if ($escaped !== "'foo\\0bar'") {
44+
printf("[003] Expected 'foo\\0bar', got %s\n", $escaped);
45+
}
46+
47+
echo "=====================\n";
48+
49+
// Test that the SQL injection is impossible with NO_BACKSLASH_ESCAPES mode
50+
$link->query('SET @@sql_mode="NO_BACKSLASH_ESCAPES"');
51+
52+
echo $link->quote_string('\\') . "\n";
53+
echo $link->quote_string('"') . "\n";
54+
echo $link->quote_string("'") . "\n";
55+
56+
$escaped = $link->quote_string("\' \ \"");
57+
echo $escaped . "\n";
58+
$result = $link->query("SELECT $escaped AS test");
59+
$value = $result->fetch_column();
60+
echo $value . "\n";
61+
62+
$escaped = $link->quote_string('" OR 1=1 -- foo');
63+
echo $escaped . "\n";
64+
$result = $link->query("SELECT $escaped AS test");
65+
$value = $result->fetch_column();
66+
echo $value . "\n";
67+
68+
echo "done!";
69+
?>
70+
--EXPECT--
71+
'\\'
72+
'\"'
73+
'\''
74+
'\\\' \\ \"'
75+
\' \ "
76+
'\" OR 1=1 -- foo'
77+
" OR 1=1 -- foo
78+
=====================
79+
'\'
80+
'"'
81+
''''
82+
'\'' \ "'
83+
\' \ "
84+
'" OR 1=1 -- foo'
85+
" OR 1=1 -- foo
86+
done!

0 commit comments

Comments
 (0)