mirror of
https://github.com/MariaDB/server.git
synced 2025-07-30 16:24:05 +03:00
MDEV-31948 Add class DBNameBuffer, split check_db_name() into stages
- Adding a class Lex_ident_fs, to store identifiers for on-disk database objects, such as databases, tables, triggers. - Moving the validation code from check_db_name() to non-modifying methods in Lex_ident_fs: Lex_ident_fs::check_body() Lex_ident_fs::check_db_name() Adding a new method Lex_ident_fs::check_db_name_with_error(), which performs validation and raises an error on validation failure. Unlike the old function check_db_name(), the new class Lex_ident_fs does not lower-case the identifier during the validation. Lower-casing must be done before calling Lex_ident_fs validation methods. - Adding a low level helper template class CharBuffer which can: * store exact or lower-cased strings with a short fixed maximum length * return the value as a LEX_CSTRING efficiently - Adding a helper template class DBNameBuffer (deriving from CharBuffer), to allocate optionally lower-cased database identifiers on stack when relevant. Useful for temporary values which don't need to be allocated on MEM_ROOT. - Using DBNameBuffer in mysql_change_db() - Using DBNameBuffer in show_create_db()
This commit is contained in:
@ -164,3 +164,30 @@ delete from mysql.proc where name = '';
|
||||
#
|
||||
# End of 10.3 tests
|
||||
#
|
||||
#
|
||||
# Start of 11.3 tests
|
||||
#
|
||||
#
|
||||
# MDEV-31948 Add class DBNameBuffer, split check_db_name() into stages
|
||||
#
|
||||
SET NAMES utf8;
|
||||
SET @mb3char= _utf8 0xEFBFAD;
|
||||
EXECUTE IMMEDIATE CONCAT('use `', REPEAT(@mb3char, 64), '`');
|
||||
ERROR 42000: Unknown database '■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■'
|
||||
EXECUTE IMMEDIATE CONCAT('use `#mysql50#', REPEAT(@mb3char, 64), '`');
|
||||
ERROR 42000: Unknown database '#mysql50#■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■'
|
||||
EXECUTE IMMEDIATE CONCAT('SHOW CREATE DATABASE `', REPEAT(@mb3char, 64), '`');
|
||||
ERROR 42000: Unknown database '■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■'
|
||||
EXECUTE IMMEDIATE CONCAT('SHOW CREATE DATABASE `#mysql50#', REPEAT(@mb3char, 64), '`');
|
||||
ERROR 42000: Unknown database '#mysql50#■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■'
|
||||
EXECUTE IMMEDIATE CONCAT('use `', REPEAT(@mb3char, 65), '`');
|
||||
ERROR 42000: Incorrect database name '■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■...'
|
||||
EXECUTE IMMEDIATE CONCAT('use `#mysql50#', REPEAT(@mb3char, 65), '`');
|
||||
ERROR 42000: Incorrect database name '#mysql50#■■■■■■■■■■■■■■■■■■■■■■■■■■■■■...'
|
||||
EXECUTE IMMEDIATE CONCAT('SHOW CREATE DATABASE `', REPEAT(@mb3char, 65), '`');
|
||||
ERROR 42000: Incorrect database name '■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■...'
|
||||
EXECUTE IMMEDIATE CONCAT('SHOW CREATE DATABASE `#mysql50#', REPEAT(@mb3char, 65), '`');
|
||||
ERROR 42000: Incorrect database name '#mysql50#■■■■■■■■■■■■■■■■■■■■■■■■■■■■■...'
|
||||
#
|
||||
# End of 11.3 tests
|
||||
#
|
||||
|
@ -142,3 +142,43 @@ delete from mysql.proc where name = '';
|
||||
--echo #
|
||||
--echo # End of 10.3 tests
|
||||
--echo #
|
||||
|
||||
--echo #
|
||||
--echo # Start of 11.3 tests
|
||||
--echo #
|
||||
|
||||
--echo #
|
||||
--echo # MDEV-31948 Add class DBNameBuffer, split check_db_name() into stages
|
||||
--echo #
|
||||
|
||||
SET NAMES utf8;
|
||||
|
||||
# U+FFED HALFWIDTH BLACK SQUARE
|
||||
SET @mb3char= _utf8 0xEFBFAD;
|
||||
|
||||
# Database names fitting into the NAME_CHAR_LEN characters limit
|
||||
|
||||
--error ER_BAD_DB_ERROR
|
||||
EXECUTE IMMEDIATE CONCAT('use `', REPEAT(@mb3char, 64), '`');
|
||||
--error ER_BAD_DB_ERROR
|
||||
EXECUTE IMMEDIATE CONCAT('use `#mysql50#', REPEAT(@mb3char, 64), '`');
|
||||
--error ER_BAD_DB_ERROR
|
||||
EXECUTE IMMEDIATE CONCAT('SHOW CREATE DATABASE `', REPEAT(@mb3char, 64), '`');
|
||||
--error ER_BAD_DB_ERROR
|
||||
EXECUTE IMMEDIATE CONCAT('SHOW CREATE DATABASE `#mysql50#', REPEAT(@mb3char, 64), '`');
|
||||
|
||||
|
||||
# Database names longer than NAME_CHAR_LEN characters
|
||||
|
||||
--error ER_WRONG_DB_NAME
|
||||
EXECUTE IMMEDIATE CONCAT('use `', REPEAT(@mb3char, 65), '`');
|
||||
--error ER_WRONG_DB_NAME
|
||||
EXECUTE IMMEDIATE CONCAT('use `#mysql50#', REPEAT(@mb3char, 65), '`');
|
||||
--error ER_WRONG_DB_NAME
|
||||
EXECUTE IMMEDIATE CONCAT('SHOW CREATE DATABASE `', REPEAT(@mb3char, 65), '`');
|
||||
--error ER_WRONG_DB_NAME
|
||||
EXECUTE IMMEDIATE CONCAT('SHOW CREATE DATABASE `#mysql50#', REPEAT(@mb3char, 65), '`');
|
||||
|
||||
--echo #
|
||||
--echo # End of 11.3 tests
|
||||
--echo #
|
||||
|
72
sql/char_buffer.h
Normal file
72
sql/char_buffer.h
Normal file
@ -0,0 +1,72 @@
|
||||
#ifndef CHAR_BUFFER_INCLUDED
|
||||
#define CHAR_BUFFER_INCLUDED
|
||||
/*
|
||||
Copyright (c) 2023, MariaDB
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; version 2 of
|
||||
the License.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
A string buffer with length.
|
||||
This template class is useful to store things like database, table names,
|
||||
whose maximum length a small fixed known value. Mainly to be used as
|
||||
stack variables to store temporary values.
|
||||
|
||||
Can store exact string copies or casefolded string copies.
|
||||
The stored value is returned as a LEX_CSTRING.
|
||||
*/
|
||||
template<size_t buff_sz>
|
||||
class CharBuffer
|
||||
{
|
||||
char m_buff[buff_sz + 1 /* one extra byte for '\0' */];
|
||||
size_t m_length;
|
||||
public:
|
||||
CharBuffer()
|
||||
:m_length(0)
|
||||
{
|
||||
m_buff[0]= '\0';
|
||||
}
|
||||
CharBuffer<buff_sz> & copy_bin(const LEX_CSTRING &str)
|
||||
{
|
||||
m_length= MY_MIN(buff_sz, str.length);
|
||||
memcpy(m_buff, str.str, m_length);
|
||||
m_buff[m_length]= '\0';
|
||||
return *this;
|
||||
}
|
||||
CharBuffer<buff_sz> & copy_casedn(CHARSET_INFO *cs, const LEX_CSTRING &str)
|
||||
{
|
||||
m_length= cs->cset->casedn(cs, str.str, str.length, m_buff, buff_sz);
|
||||
/*
|
||||
casedn() never writes outsize of buff_sz (unless a bug in casedn()),
|
||||
so it's safe to write '\0' at the position m_length:
|
||||
*/
|
||||
DBUG_ASSERT(m_length <= buff_sz);
|
||||
m_buff[m_length]= '\0';
|
||||
return *this;
|
||||
}
|
||||
CharBuffer<buff_sz> & copy_casedn(CHARSET_INFO *cs, const LEX_CSTRING &str,
|
||||
bool casedn)
|
||||
{
|
||||
casedn ? copy_casedn(cs, str) : copy_bin(str);
|
||||
return *this;
|
||||
}
|
||||
LEX_CSTRING to_lex_cstring() const
|
||||
{
|
||||
return LEX_CSTRING{m_buff, m_length};
|
||||
}
|
||||
};
|
||||
|
||||
#endif // CHAR_BUFFER_INCLUDED
|
70
sql/lex_ident.h
Normal file
70
sql/lex_ident.h
Normal file
@ -0,0 +1,70 @@
|
||||
#ifndef LEX_IDENT_INCLUDED
|
||||
#define LEX_IDENT_INCLUDED
|
||||
/*
|
||||
Copyright (c) 2023, MariaDB
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; version 2 of
|
||||
the License.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA
|
||||
*/
|
||||
|
||||
#include "char_buffer.h"
|
||||
|
||||
|
||||
/*
|
||||
Identifiers for the database objects stored on disk,
|
||||
e.g. databases, tables, triggers.
|
||||
*/
|
||||
class Lex_ident_fs: public LEX_CSTRING
|
||||
{
|
||||
public:
|
||||
Lex_ident_fs()
|
||||
:LEX_CSTRING({0,0})
|
||||
{ }
|
||||
Lex_ident_fs(const char *str, size_t length)
|
||||
:LEX_CSTRING({str, length})
|
||||
{ }
|
||||
explicit Lex_ident_fs(const LEX_CSTRING &str)
|
||||
:LEX_CSTRING(str)
|
||||
{ }
|
||||
static bool check_body(const char *name, size_t length,
|
||||
bool disallow_path_chars);
|
||||
bool check_db_name() const;
|
||||
bool check_db_name_with_error() const;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
A helper class to store temporary database names in a buffer.
|
||||
After constructing it's typically should be checked using
|
||||
Lex_ident_fs::check_db_name().
|
||||
|
||||
Note, the database name passed to the constructor can originally
|
||||
come from the parser and can be of an atribtrary long length.
|
||||
Let's reserve additional buffer space for one extra character
|
||||
(SYSTEM_CHARSET_MBMAXLEN bytes), so check_db_name() can still
|
||||
detect too long names even if the constructor cuts the data.
|
||||
*/
|
||||
class DBNameBuffer: public CharBuffer<SAFE_NAME_LEN + SYSTEM_CHARSET_MBMAXLEN>
|
||||
{
|
||||
public:
|
||||
DBNameBuffer()
|
||||
{ }
|
||||
DBNameBuffer(const LEX_CSTRING &db, bool casedn)
|
||||
{
|
||||
copy_casedn(&my_charset_utf8mb3_general_ci, db, casedn);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
#endif // LEX_IDENT_INCLUDED
|
@ -24,6 +24,7 @@
|
||||
#include "dur_prop.h"
|
||||
#include <waiting_threads.h>
|
||||
#include "sql_const.h"
|
||||
#include "lex_ident.h"
|
||||
#include "sql_used.h"
|
||||
#include <mysql/plugin_audit.h>
|
||||
#include "log.h"
|
||||
|
@ -1697,6 +1697,7 @@ static void backup_current_db_name(THD *thd,
|
||||
uint mysql_change_db(THD *thd, const LEX_CSTRING *new_db_name,
|
||||
bool force_switch)
|
||||
{
|
||||
DBNameBuffer new_db_buff;
|
||||
LEX_CSTRING new_db_file_name;
|
||||
|
||||
Security_context *sctx= thd->security_ctx;
|
||||
@ -1739,19 +1740,10 @@ uint mysql_change_db(THD *thd, const LEX_CSTRING *new_db_name,
|
||||
goto done;
|
||||
}
|
||||
|
||||
/*
|
||||
Now we need to make a copy because check_db_name requires a
|
||||
non-constant argument. Actually, it takes database file name.
|
||||
|
||||
TODO: fix check_db_name().
|
||||
*/
|
||||
|
||||
new_db_file_name.str= my_strndup(key_memory_THD_db, new_db_name->str,
|
||||
new_db_name->length, MYF(MY_WME));
|
||||
new_db_file_name.length= new_db_name->length;
|
||||
|
||||
if (new_db_file_name.str == NULL)
|
||||
DBUG_RETURN(ER_OUT_OF_RESOURCES); /* the error is set */
|
||||
new_db_file_name= lower_case_table_names ?
|
||||
new_db_buff.copy_casedn(&my_charset_utf8mb3_general_ci,
|
||||
*new_db_name).to_lex_cstring() :
|
||||
*new_db_name;
|
||||
|
||||
/*
|
||||
NOTE: if check_db_name() fails, we should throw an error in any case,
|
||||
@ -1763,11 +1755,8 @@ uint mysql_change_db(THD *thd, const LEX_CSTRING *new_db_name,
|
||||
The cast below ok here as new_db_file_name was just allocated
|
||||
*/
|
||||
|
||||
if (check_db_name((LEX_STRING*) &new_db_file_name))
|
||||
if (Lex_ident_fs(new_db_file_name).check_db_name_with_error())
|
||||
{
|
||||
my_error(ER_WRONG_DB_NAME, MYF(0), new_db_file_name.str);
|
||||
my_free(const_cast<char*>(new_db_file_name.str));
|
||||
|
||||
if (force_switch)
|
||||
mysql_change_db_impl(thd, NULL, NO_ACL, thd->variables.collation_server);
|
||||
|
||||
@ -1797,7 +1786,6 @@ uint mysql_change_db(THD *thd, const LEX_CSTRING *new_db_name,
|
||||
new_db_file_name.str);
|
||||
general_log_print(thd, COM_INIT_DB, ER_THD(thd, ER_DBACCESS_DENIED_ERROR),
|
||||
sctx->priv_user, sctx->priv_host, new_db_file_name.str);
|
||||
my_free(const_cast<char*>(new_db_file_name.str));
|
||||
DBUG_RETURN(ER_DBACCESS_DENIED_ERROR);
|
||||
}
|
||||
#endif
|
||||
@ -1814,8 +1802,6 @@ uint mysql_change_db(THD *thd, const LEX_CSTRING *new_db_name,
|
||||
ER_BAD_DB_ERROR, ER_THD(thd, ER_BAD_DB_ERROR),
|
||||
new_db_file_name.str);
|
||||
|
||||
my_free(const_cast<char*>(new_db_file_name.str));
|
||||
|
||||
/* Change db to NULL. */
|
||||
|
||||
mysql_change_db_impl(thd, NULL, NO_ACL, thd->variables.collation_server);
|
||||
@ -1828,7 +1814,6 @@ uint mysql_change_db(THD *thd, const LEX_CSTRING *new_db_name,
|
||||
/* Report an error and free new_db_file_name. */
|
||||
|
||||
my_error(ER_BAD_DB_ERROR, MYF(0), new_db_file_name.str);
|
||||
my_free(const_cast<char*>(new_db_file_name.str));
|
||||
|
||||
/* The operation failed. */
|
||||
|
||||
@ -1836,14 +1821,23 @@ uint mysql_change_db(THD *thd, const LEX_CSTRING *new_db_name,
|
||||
}
|
||||
}
|
||||
|
||||
db_default_cl= get_default_db_collation(thd, new_db_file_name.str);
|
||||
|
||||
/*
|
||||
new_db_file_name allocated memory on the stack.
|
||||
mysql_change_db_impl() expects a my_alloc-ed memory.
|
||||
NOTE: in mysql_change_db_impl() new_db_file_name is assigned to THD
|
||||
attributes and will be freed in THD::~THD().
|
||||
*/
|
||||
|
||||
db_default_cl= get_default_db_collation(thd, new_db_file_name.str);
|
||||
|
||||
mysql_change_db_impl(thd, &new_db_file_name, db_access, db_default_cl);
|
||||
if (const char *tmp= my_strndup(key_memory_THD_db,
|
||||
new_db_file_name.str,
|
||||
new_db_file_name.length, MYF(MY_WME)))
|
||||
{
|
||||
LEX_CSTRING new_db_malloced({tmp, new_db_file_name.length});
|
||||
mysql_change_db_impl(thd, &new_db_malloced, db_access, db_default_cl);
|
||||
}
|
||||
else
|
||||
DBUG_RETURN(ER_OUT_OF_RESOURCES); /* the error is set */
|
||||
|
||||
done:
|
||||
thd->session_tracker.current_schema.mark_as_changed(thd);
|
||||
|
@ -6265,21 +6265,14 @@ static bool generate_incident_event(THD *thd)
|
||||
static int __attribute__ ((noinline))
|
||||
show_create_db(THD *thd, LEX *lex)
|
||||
{
|
||||
char db_name_buff[NAME_LEN+1];
|
||||
LEX_CSTRING db_name;
|
||||
DBUG_EXECUTE_IF("4x_server_emul",
|
||||
my_error(ER_UNKNOWN_ERROR, MYF(0)); return 1;);
|
||||
|
||||
db_name.str= db_name_buff;
|
||||
db_name.length= lex->name.length;
|
||||
strmov(db_name_buff, lex->name.str);
|
||||
|
||||
if (check_db_name((LEX_STRING*) &db_name))
|
||||
{
|
||||
my_error(ER_WRONG_DB_NAME, MYF(0), db_name.str);
|
||||
DBNameBuffer dbbuf(lex->name, lower_case_table_names);
|
||||
if (Lex_ident_fs(dbbuf.to_lex_cstring()).check_db_name_with_error())
|
||||
return 1;
|
||||
}
|
||||
return mysqld_show_create_db(thd, &db_name, &lex->name, lex->create_info);
|
||||
LEX_CSTRING db= dbbuf.to_lex_cstring();
|
||||
return mysqld_show_create_db(thd, &db, &lex->name, lex->create_info);
|
||||
}
|
||||
|
||||
|
||||
|
52
sql/table.cc
52
sql/table.cc
@ -5240,10 +5240,6 @@ bool check_db_name(LEX_STRING *org_name)
|
||||
|
||||
bool check_table_name(const char *name, size_t length, bool disallow_path_chars)
|
||||
{
|
||||
// name length in symbols
|
||||
size_t char_length= 0;
|
||||
const char *end= name+length;
|
||||
|
||||
if (!disallow_path_chars &&
|
||||
(disallow_path_chars= check_mysql50_prefix(name)))
|
||||
{
|
||||
@ -5251,8 +5247,20 @@ bool check_table_name(const char *name, size_t length, bool disallow_path_chars)
|
||||
length-= MYSQL50_TABLE_NAME_PREFIX_LENGTH;
|
||||
}
|
||||
|
||||
return Lex_ident_fs::check_body(name, length, disallow_path_chars);
|
||||
}
|
||||
|
||||
|
||||
bool Lex_ident_fs::check_body(const char *name, size_t length,
|
||||
bool disallow_path_chars)
|
||||
{
|
||||
if (!length || length > NAME_LEN)
|
||||
return 1;
|
||||
|
||||
// name length in symbols
|
||||
size_t char_length= 0;
|
||||
const char *end= name + length;
|
||||
|
||||
#if defined(USE_MB) && defined(USE_MB_IDENT)
|
||||
bool last_char_is_space= FALSE;
|
||||
#else
|
||||
@ -5302,6 +5310,42 @@ bool check_table_name(const char *name, size_t length, bool disallow_path_chars)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Check if the name is a valid database name
|
||||
@returns false - on success (valid)
|
||||
@returns true - on error (invalid)
|
||||
*/
|
||||
bool Lex_ident_fs::check_db_name() const
|
||||
{
|
||||
DBUG_ASSERT(str);
|
||||
if (check_mysql50_prefix(str))
|
||||
{
|
||||
Lex_ident_fs name(Lex_cstring(str + MYSQL50_TABLE_NAME_PREFIX_LENGTH,
|
||||
length - MYSQL50_TABLE_NAME_PREFIX_LENGTH));
|
||||
return db_name_is_in_ignore_db_dirs_list(name.str) ||
|
||||
check_body(name.str, name.length, true);
|
||||
}
|
||||
return db_name_is_in_ignore_db_dirs_list(str) ||
|
||||
check_body(str, length, false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Check if the name is a valid database name
|
||||
and raise an error in case of an invalid name.
|
||||
|
||||
@returns false - on success (valid)
|
||||
@returns true - on error (invalid)
|
||||
*/
|
||||
bool Lex_ident_fs::check_db_name_with_error() const
|
||||
{
|
||||
if (!check_db_name())
|
||||
return false;
|
||||
my_error(ER_WRONG_DB_NAME ,MYF(0), safe_str(str));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool check_column_name(const char *name)
|
||||
{
|
||||
// name length in symbols
|
||||
|
Reference in New Issue
Block a user