Index: driver/utility.c =================================================================== --- driver/utility.c (revision 416) +++ driver/utility.c (working copy) @@ -1188,3 +1188,111 @@ return TRUE; return FALSE; } + + +/** + Escapes a string that may contain wildcard characters (%, _) and other + problematic characters (", ', \n, etc). Like mysql_real_escape_string() but + also including % and _. + + @param[in] mysql Pointer to MYSQL structure + @param[out] to Buffer for escaped string + @param[in] to_length Length of destination buffer, or 0 for "big enough" + @param[in] from The string to escape + @param[in] length The length of the string to escape + +*/ +ulong mysql_escape_wildcard(MYSQL *mysql, char *to, ulong to_length, const + char *from, ulong length) +{ + CHARSET_INFO *charset_info= mysql->charset; + const char *to_start= to; + const char *end, *to_end=to_start + (to_length ? to_length-1 : 2*length); + my_bool overflow= FALSE; +#ifdef USE_MB + my_bool use_mb_flag= use_mb(charset_info); +#endif + for (end= from + length; from < end; from++) + { + char escape= 0; +#ifdef USE_MB + int tmp_length; + if (use_mb_flag && (tmp_length= my_ismbchar(charset_info, from, end))) + { + if (to + tmp_length > to_end) + { + overflow= TRUE; + break; + } + while (tmp_length--) + *to++= *from++; + from--; + continue; + } + /* + If the next character appears to begin a multi-byte character, we + escape that first byte of that apparent multi-byte character. (The + character just looks like a multi-byte character -- if it were actually + a multi-byte character, it would have been passed through in the test + above.) + + Without this check, we can create a problem by converting an invalid + multi-byte character into a valid one. For example, 0xbf27 is not + a valid GBK character, but 0xbf5c is. (0x27 = ', 0x5c = \) + */ + if (use_mb_flag && (tmp_length= my_mbcharlen(charset_info, *from)) > 1) + escape= *from; + else +#endif + switch (*from) { + case 0: /* Must be escaped for 'mysql' */ + escape= '0'; + break; + case '\n': /* Must be escaped for logs */ + escape= 'n'; + break; + case '\r': + escape= 'r'; + break; + case '\\': + escape= '\\'; + break; + case '\'': + escape= '\''; + break; + case '"': /* Better safe than sorry */ + escape= '"'; + break; + case '_': + escape= '_'; + break; + case '%': + escape= '%'; + break; + case '\032': /* This gives problems on Win32 */ + escape= 'Z'; + break; + } + if (escape) + { + if (to + 2 > to_end) + { + overflow= TRUE; + break; + } + *to++= '\\'; + *to++= escape; + } + else + { + if (to + 1 > to_end) + { + overflow= TRUE; + break; + } + *to++= *from; + } + } + *to= 0; + return overflow ? (ulong)~0 : (ulong) (to - to_start); +} Index: driver/catalog.c =================================================================== --- driver/catalog.c (revision 416) +++ driver/catalog.c (working copy) @@ -1849,8 +1849,8 @@ if (table && *table) { to= strmov(to, "LIKE '"); - /** @todo this is *wrong* -- we need to escape % and _ */ - to+= mysql_real_escape_string(mysql, to, (char *)table, table_length); + to+= mysql_escape_wildcard(mysql, to, sizeof(buff) - (to - buff), + (char *)table, table_length); to= strmov(to, "'"); } Index: driver/myutil.h =================================================================== --- driver/myutil.h (revision 416) +++ driver/myutil.h (working copy) @@ -162,6 +162,9 @@ int myodbc_casecmp(const char *s, const char *t, uint len); my_bool reget_current_catalog(DBC FAR *dbc); +ulong mysql_escape_wildcard(MYSQL *mysql, char *to, ulong to_length, const + char *from, ulong length); + /* Functions used when debugging */ #ifdef MYODBC_DBG void query_print(FILE *log_file,char *query); Index: test/my_catalog.c =================================================================== --- test/my_catalog.c (revision 416) +++ test/my_catalog.c (working copy) @@ -1121,11 +1121,16 @@ /** Bug #4518: SQLForeignKeys returns too many foreign key + Bug #27723: SQLForeignKeys does not escape _ and % in the table name arguments + + The original test case was extended to have a table that would inadvertantly + get included because of the poor escaping. */ DECLARE_TEST(t_bug4518) { + SQLCHAR buff[255]; - ok_sql(hstmt, "DROP TABLE IF EXISTS t_bug4518_c, t_bug4518_c2, " + ok_sql(hstmt, "DROP TABLE IF EXISTS t_bug4518_c, t_bug4518_c2, t_bug4518ac, " " t_bug4518_p"); ok_sql(hstmt, "CREATE TABLE t_bug4518_p (id INT PRIMARY KEY) ENGINE=InnoDB"); ok_sql(hstmt, "CREATE TABLE t_bug4518_c (id INT, parent_id INT," @@ -1140,15 +1145,28 @@ " t_bug4518_p(id)" " ON DELETE SET NULL)" " ENGINE=InnoDB"); + ok_sql(hstmt, "CREATE TABLE t_bug4518ac (id INT, parent_id INT," + " FOREIGN KEY (parent_id)" + " REFERENCES" + " t_bug4518_p(id)" + " ON DELETE SET NULL)" + " ENGINE=InnoDB"); ok_stmt(hstmt, SQLForeignKeys(hstmt, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, (SQLCHAR *)"t_bug4518_c", SQL_NTS)); - my_assert(1 == my_print_non_format_result(hstmt)); + ok_stmt(hstmt, SQLFetch(hstmt)); + is_str(my_fetch_str(hstmt, buff, 3), "t_bug4518_p", 11); + is_str(my_fetch_str(hstmt, buff, 4), "id", 2); + is_str(my_fetch_str(hstmt, buff, 7), "t_bug4518_c", 11); + is_str(my_fetch_str(hstmt, buff, 8), "parent_id", 9); + expect_stmt(hstmt, SQLFetch(hstmt), SQL_NO_DATA_FOUND); + ok_stmt(hstmt, SQLFreeStmt(hstmt,SQL_CLOSE)); - ok_sql(hstmt, "DROP TABLE IF EXISTS t_bug4518_c, t_bug4518_c2, t_bug4518_p"); + ok_sql(hstmt, + "DROP TABLE t_bug4518_c, t_bug4518_c2, t_bug4518ac, t_bug4518_p"); return OK; } Index: ChangeLog =================================================================== --- ChangeLog (revision 416) +++ ChangeLog (working copy) @@ -1,6 +1,8 @@ 3.51.16 Functionality added or changed: + * SQLForeignKeys() did not properly escape wildcard characters in its + table name parameters when retrieving information. (Bug #27723) Bugs fixed: