diff --git a/ext/mysqli/mysqli.stub.php b/ext/mysqli/mysqli.stub.php index 370da77e62a25..27aab4284c6ac 100644 --- a/ext/mysqli/mysqli.stub.php +++ b/ext/mysqli/mysqli.stub.php @@ -901,6 +901,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 @@ -1542,6 +1547,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 1bf74dd77eeab..63f8c47120161 100644 --- a/ext/mysqli/mysqli_api.c +++ b/ext/mysqli/mysqli_api.c @@ -1362,6 +1362,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+1, 0, 0); + ZSTR_VAL(newstr)[0] = '\''; + ZSTR_LEN(newstr) = 1 + mysql_real_escape_string(mysql->mysql, ZSTR_VAL(newstr) + 1, escapestr, escapestr_len); + ZSTR_VAL(newstr)[ZSTR_LEN(newstr)] = '\''; + ZSTR_LEN(newstr) += 1; + ZSTR_VAL(newstr)[ZSTR_LEN(newstr)] = '\0'; + newstr = zend_string_truncate(newstr, ZSTR_LEN(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 789464a762538..09ac13c164f78 100644 --- a/ext/mysqli/mysqli_arginfo.h +++ b/ext/mysqli/mysqli_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: fecde55745fb219cb15fd35a54a71371ef2b8b7d */ + * Stub hash: 1c90e5fcc20b0e810643eb812db01e95bfa928f4 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_mysqli_affected_rows, 0, 1, MAY_BE_LONG|MAY_BE_STRING) ZEND_ARG_OBJ_INFO(0, mysql, mysqli, 0) @@ -252,6 +252,8 @@ ZEND_END_ARG_INFO() #define arginfo_mysqli_escape_string arginfo_mysqli_real_escape_string +#define arginfo_mysqli_quote_string arginfo_mysqli_real_escape_string + #define arginfo_mysqli_real_query arginfo_mysqli_multi_query ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_mysqli_reap_async_query, 0, 1, mysqli_result, MAY_BE_BOOL) @@ -527,6 +529,10 @@ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_mysqli_real_esca ZEND_ARG_TYPE_INFO(0, string, IS_STRING, 0) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_mysqli_quote_string, 0, 1, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, string, IS_STRING, 0) +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_OBJ_TYPE_MASK_EX(arginfo_class_mysqli_reap_async_query, 0, 0, mysqli_result, MAY_BE_BOOL) ZEND_END_ARG_INFO() @@ -767,6 +773,7 @@ ZEND_FUNCTION(mysqli_report); ZEND_FUNCTION(mysqli_query); ZEND_FUNCTION(mysqli_real_connect); ZEND_FUNCTION(mysqli_real_escape_string); +ZEND_FUNCTION(mysqli_quote_string); ZEND_FUNCTION(mysqli_real_query); ZEND_FUNCTION(mysqli_reap_async_query); ZEND_FUNCTION(mysqli_release_savepoint); @@ -883,6 +890,7 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(mysqli_real_connect, arginfo_mysqli_real_connect) ZEND_FE(mysqli_real_escape_string, arginfo_mysqli_real_escape_string) ZEND_RAW_FENTRY("mysqli_escape_string", zif_mysqli_real_escape_string, arginfo_mysqli_escape_string, 0, NULL, NULL) + ZEND_FE(mysqli_quote_string, arginfo_mysqli_quote_string) ZEND_FE(mysqli_real_query, arginfo_mysqli_real_query) ZEND_FE(mysqli_reap_async_query, arginfo_mysqli_reap_async_query) ZEND_FE(mysqli_release_savepoint, arginfo_mysqli_release_savepoint) @@ -957,6 +965,7 @@ static const zend_function_entry class_mysqli_methods[] = { ZEND_RAW_FENTRY("query", zif_mysqli_query, arginfo_class_mysqli_query, ZEND_ACC_PUBLIC, NULL, NULL) ZEND_RAW_FENTRY("real_connect", zif_mysqli_real_connect, arginfo_class_mysqli_real_connect, ZEND_ACC_PUBLIC, NULL, NULL) ZEND_RAW_FENTRY("real_escape_string", zif_mysqli_real_escape_string, arginfo_class_mysqli_real_escape_string, ZEND_ACC_PUBLIC, NULL, NULL) + ZEND_RAW_FENTRY("quote_string", zif_mysqli_quote_string, arginfo_class_mysqli_quote_string, ZEND_ACC_PUBLIC, NULL, NULL) ZEND_RAW_FENTRY("reap_async_query", zif_mysqli_reap_async_query, arginfo_class_mysqli_reap_async_query, ZEND_ACC_PUBLIC, NULL, NULL) ZEND_RAW_FENTRY("escape_string", zif_mysqli_real_escape_string, arginfo_class_mysqli_escape_string, ZEND_ACC_PUBLIC, NULL, NULL) ZEND_RAW_FENTRY("real_query", zif_mysqli_real_query, arginfo_class_mysqli_real_query, ZEND_ACC_PUBLIC, NULL, NULL) diff --git a/ext/mysqli/tests/mysqli_class_mysqli_interface.phpt b/ext/mysqli/tests/mysqli_class_mysqli_interface.phpt index bada1d85a5cef..ccac6710edd1d 100644 --- a/ext/mysqli/tests/mysqli_class_mysqli_interface.phpt +++ b/ext/mysqli/tests/mysqli_class_mysqli_interface.phpt @@ -43,6 +43,7 @@ require_once 'skipifconnectfailure.inc'; '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 0000000000000..93b374b34c4b8 --- /dev/null +++ b/ext/mysqli/tests/mysqli_quote_string.phpt @@ -0,0 +1,83 @@ +--TEST-- +mysqli_quote_string() +--EXTENSIONS-- +mysqli +--SKIPIF-- + +--FILE-- +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); +} + +// 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!