Commit b956a6a2 authored by Alexander Barkov's avatar Alexander Barkov

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()
parent 8528eacc
......@@ -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 #
#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
#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);
}
......
......@@ -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
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment