Improvements and fixes to strchr_esc_mysql

The C style comments were not ignored and the -- style comments
did not check for the trailing whitespace and made return values char*.

The creation of a stored procedure would prevent sessions from using
any of the slave servers because readwritesplit would interpret
the creation statement as a multi-statement query.

Parts of modutil and readwritesplit now compare pointers to pointers instead of
converting pointers to integers.
This commit is contained in:
Markus Makela
2016-03-02 16:06:19 +02:00
parent 2589630b8e
commit 8b6595aa68
3 changed files with 68 additions and 24 deletions

View File

@ -34,6 +34,7 @@
#include <mysql_client_server_protocol.h> #include <mysql_client_server_protocol.h>
#include <maxscale/poll.h> #include <maxscale/poll.h>
#include <modutil.h> #include <modutil.h>
#include <strings.h>
/** These are used when converting MySQL wildcards to regular expressions */ /** These are used when converting MySQL wildcards to regular expressions */
static SPINLOCK re_lock = SPINLOCK_INIT; static SPINLOCK re_lock = SPINLOCK_INIT;
@ -765,7 +766,7 @@ static void modutil_reply_routing_error(DCB* backend_dcb,
* @return Pointer to the first non-escaped, non-quoted occurrence of the character. * @return Pointer to the first non-escaped, non-quoted occurrence of the character.
* If the character is not found, NULL is returned. * If the character is not found, NULL is returned.
*/ */
void* strnchr_esc(char* ptr, char c, int len) char* strnchr_esc(char* ptr, char c, int len)
{ {
char* p = (char*)ptr; char* p = (char*)ptr;
char* start = p; char* start = p;
@ -804,27 +805,28 @@ void* strnchr_esc(char* ptr, char c, int len)
/** /**
* Find the first occurrence of a character in a string. This function ignores * Find the first occurrence of a character in a string. This function ignores
* escaped characters and all characters that are enclosed in single or double quotes. * escaped characters and all characters that are enclosed in single or double quotes.
* Also MySQL style comment blocks are ignored. * MySQL style comment blocks and identifiers in backticks are also ignored.
* @param ptr Pointer to area of memory to inspect * @param ptr Pointer to area of memory to inspect
* @param c Character to search for * @param c Character to search for
* @param len Size of the memory area * @param len Size of the memory area
* @return Pointer to the first non-escaped, non-quoted occurrence of the character. * @return Pointer to the first non-escaped, non-quoted occurrence of the character.
* If the character is not found, NULL is returned. * If the character is not found, NULL is returned.
*/ */
void* strnchr_esc_mysql(char* ptr, char c, int len) char* strnchr_esc_mysql(char* ptr, char c, int len)
{ {
char* p = (char*) ptr; char* p = (char*) ptr;
char* start = p; char* start = p, *end = start + len;
bool quoted = false, escaped = false, backtick = false; bool quoted = false, escaped = false, backtick = false, comment = false;
char qc; char qc;
while (p < start + len) while (p < end)
{ {
if (escaped) if (escaped)
{ {
escaped = false; escaped = false;
} }
else else if ((!comment && !quoted && !backtick) || (comment && *p == '*') ||
(!comment && quoted && *p == qc) || (!comment && backtick && *p == '`'))
{ {
switch (*p) switch (*p)
{ {
@ -832,7 +834,7 @@ void* strnchr_esc_mysql(char* ptr, char c, int len)
escaped = true; escaped = true;
break; break;
case'\'': case '\'':
case '"': case '"':
if (!quoted) if (!quoted)
{ {
@ -845,31 +847,47 @@ void* strnchr_esc_mysql(char* ptr, char c, int len)
} }
break; break;
case '/':
if (p + 1 < end && *(p + 1) == '*')
{
comment = true;
p += 1;
}
break;
case '*':
if (comment && p + 1 < end && *(p + 1) == '/')
{
comment = false;
p += 1;
}
break;
case '`': case '`':
backtick = !backtick; backtick = !backtick;
break; break;
case '#': case '#':
if (!backtick) return NULL;
case '-':
if (p + 2 < end && *(p + 1) == '-' &&
isspace(*(p + 2)))
{ {
return NULL; return NULL;
} }
break; break;
case '-': default:
if (!backtick && p + 1 < start + len && *(p + 1) == '-')
{
return NULL;
}
break; break;
} }
if (*p == c && !escaped && !quoted) if (*p == c && !escaped && !quoted && !comment && !backtick)
{ {
return p; return p;
} }
p++;
} }
p++;
} }
return NULL; return NULL;
} }
@ -883,7 +901,7 @@ bool is_mysql_comment_start(const char* start, int len)
{ {
const char *ptr = start; const char *ptr = start;
while (ptr - start < len && isspace(*ptr)) while (ptr < start + len && (isspace(*ptr) || *ptr == ';'))
{ {
ptr++; ptr++;
} }
@ -891,7 +909,7 @@ bool is_mysql_comment_start(const char* start, int len)
switch (*ptr) switch (*ptr)
{ {
case '-': case '-':
if (*(ptr + 1) == '-') if (*(ptr + 1) == '-' && isspace(*(ptr + 2)))
{ {
return true; return true;
} }
@ -910,6 +928,23 @@ bool is_mysql_comment_start(const char* start, int len)
return false; return false;
} }
/**
* @brief Check if the token is the END part of a BEGIN ... END block.
* @param ptr String with at least three non-whitespace characters in it
* @return True if the token is the final part of a BEGIN .. END block
*/
bool is_mysql_sp_end(const char* start, int len)
{
const char *ptr = start;
while (ptr < start + len && (isspace(*ptr) || *ptr == ';'))
{
ptr++;
}
return ptr < start + len - 3 && strncasecmp(ptr, "end", 3) == 0;
}
/** /**
* Create a COM_QUERY packet from a string. * Create a COM_QUERY packet from a string.
* @param query Query to create. * @param query Query to create.

View File

@ -69,8 +69,9 @@ int modutil_count_signal_packets(GWBUF*,int,int,int*);
mxs_pcre2_result_t modutil_mysql_wildcard_match(const char* pattern, const char* string); mxs_pcre2_result_t modutil_mysql_wildcard_match(const char* pattern, const char* string);
/** Character and token searching functions */ /** Character and token searching functions */
void* strnchr_esc(char* ptr, char c, int len); char* strnchr_esc(char* ptr, char c, int len);
void* strnchr_esc_mysql(char* ptr, char c, int len); char* strnchr_esc_mysql(char* ptr, char c, int len);
bool is_mysql_comment_start(const char* start, int len); bool is_mysql_comment_start(const char* start, int len);
bool is_mysql_sp_end(const char* start, int len);
#endif #endif

View File

@ -5366,11 +5366,19 @@ static void check_for_multi_stmt(ROUTER_CLIENT_SES* rses, GWBUF *buf,
if ((ptr = strnchr_esc_mysql(data, ';', buflen))) if ((ptr = strnchr_esc_mysql(data, ';', buflen)))
{ {
ptr++; /** Skip stored procedures etc. */
if (ptr - data < buflen && !is_mysql_comment_start(ptr, ptr - data)) while (ptr && is_mysql_sp_end(ptr, ptr - data))
{ {
rses->forced_node = rses->rses_master_ref; ptr = strnchr_esc_mysql(ptr + 1, ';', ptr - data);
MXS_INFO("Multi-statement query, routing all future queries to master."); }
if (ptr)
{
if (ptr < data + buflen && !is_mysql_comment_start(ptr, ptr - data))
{
rses->forced_node = rses->rses_master_ref;
MXS_INFO("Multi-statement query, routing all future queries to master.");
}
} }
} }
} }