1 /*
2  * Copyright (c) 2015, 2021, Oracle and/or its affiliates.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License, version 2.0,
6  * as published by the Free Software Foundation.
7  *
8  * This program is also distributed with certain software (including
9  * but not limited to OpenSSL) that is licensed under separate terms,
10  * as designated in a particular file or component or in included license
11  * documentation.  The authors of MySQL hereby grant you an additional
12  * permission to link the program and your derivative works with the
13  * separately licensed software that they have included with MySQL.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License, version 2.0, for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
23  * 02110-1301  USA
24  */
25 
26 #include <algorithm>
27 
28 #include "xpl_log.h"
29 #include "query_formatter.h"
30 #include "my_sys.h" // escape_string_for_mysql
31 #include "xpl_error.h"
32 #include "ngs/error_code.h"
33 
34 using namespace xpl;
35 
36 enum Block {Block_none, Block_string_quoted, Block_string_double_quoted, Block_identifier, Block_comment, Block_line_comment};
37 
38 class Sql_search_tags
39 {
40 public:
Sql_search_tags()41   Sql_search_tags()
42   : m_state(Block_none),
43     m_matching_chars_comment(0),
44     m_matching_chars_line_comment1(0),
45     m_matching_chars_line_comment2(0),
46     m_escape_chars(0)
47   {
48   }
49 
should_ignore_block(const char character,const Block try_block,const char character_begin,const char character_end,bool escape=false)50   bool should_ignore_block(const char character, const Block try_block, const char character_begin, const char character_end, bool escape = false)
51   {
52     if (m_state != try_block && m_state != Block_none)
53       return false;
54 
55     if (m_state == Block_none)
56     {
57       if (character_begin == character)
58       {
59         m_escape_chars = 0;
60         m_state = try_block;
61 
62         return true;
63       }
64     }
65     else
66     {
67       if (escape)
68       {
69         if (0 != m_escape_chars)
70         {
71           --m_escape_chars;
72           return true;
73         }
74         else if ('\\' == character)
75         {
76           ++m_escape_chars;
77           return true;
78         }
79       }
80 
81       if (character_end == character)
82       {
83         m_state = Block_none;
84       }
85 
86       return true;
87     }
88 
89     return false;
90   }
91 
if_matching_switch_state(const char character,const Block try_block,uint8_t & matching_chars,const char * match,const std::size_t match_length)92   bool if_matching_switch_state(const char character, const Block try_block, uint8_t &matching_chars, const char *match, const std::size_t match_length)
93   {
94     bool repeat = true;
95 
96     while (repeat)
97     {
98       if (character == match[matching_chars])
99       {
100         ++matching_chars;
101         break;
102       }
103 
104       repeat = matching_chars != 0;
105 
106       matching_chars = 0;
107     }
108 
109     if (matching_chars == match_length - 1)
110     {
111       m_state = try_block;
112       matching_chars = 0;
113 
114       return true;
115     }
116 
117     return false;
118   }
119 
120   template <std::size_t block_begin_length, std::size_t block_end_length>
should_ignore_block_multichar(const char character,const Block try_block_state,uint8_t & matching_chars,const char (& block_begin)[block_begin_length],const char (& block_end)[block_end_length])121   bool should_ignore_block_multichar(const char character, const Block try_block_state, uint8_t &matching_chars, const char (&block_begin)[block_begin_length], const char (&block_end)[block_end_length])
122   {
123     if (m_state != try_block_state && m_state != Block_none)
124       return false;
125 
126     if (m_state == Block_none)
127     {
128       return if_matching_switch_state(character, try_block_state, matching_chars, block_begin, block_begin_length);
129     }
130     else
131     {
132       if_matching_switch_state(character, Block_none, matching_chars, block_end, block_end_length);
133 
134       return true;
135     }
136   }
137 
should_be_ignored(const char character)138   bool should_be_ignored(const char character)
139   {
140     const bool escape_sequence = true;
141 
142     if (should_ignore_block(character, Block_string_quoted, '\'', '\'', escape_sequence))
143       return true;
144 
145     if (should_ignore_block(character, Block_string_double_quoted, '"', '"', escape_sequence))
146       return true;
147 
148     if (should_ignore_block(character, Block_identifier, '`', '`'))
149       return true;
150 
151     if (should_ignore_block_multichar(character, Block_comment, m_matching_chars_comment, "/*", "*/"))
152       return true;
153 
154     if (should_ignore_block_multichar(character, Block_line_comment, m_matching_chars_line_comment1, "#", "\n"))
155       return true;
156 
157     if (should_ignore_block_multichar(character, Block_line_comment, m_matching_chars_line_comment2, "-- ", "\n"))
158       return true;
159 
160     return false;
161   }
162 
operator ()(const char query_character)163   bool operator() (const char query_character)
164   {
165     if (should_be_ignored(query_character))
166       return false;
167 
168     return query_character == '?';
169   }
170 
171 private:
172   Block  m_state;
173   uint8_t m_matching_chars_comment;
174   uint8_t m_matching_chars_line_comment1;
175   uint8_t m_matching_chars_line_comment2;
176   uint8_t m_escape_chars;
177 };
178 
179 
Query_formatter(ngs::PFS_string & query,charset_info_st & charset)180 Query_formatter::Query_formatter(ngs::PFS_string &query, charset_info_st &charset)
181 : m_query(query), m_charset(charset), m_last_tag_position(0)
182 {
183 }
184 
operator %(const char * value)185 Query_formatter &Query_formatter::operator % (const char *value)
186 {
187   validate_next_tag();
188 
189   put_value_and_escape(value, strlen(value));
190 
191   return *this;
192 }
193 
operator %(const No_escape<const char * > & value)194 Query_formatter &Query_formatter::operator % (const No_escape<const char *> &value)
195 {
196   validate_next_tag();
197 
198   put_value(value.m_value, strlen(value.m_value));
199 
200   return *this;
201 }
202 
operator %(const std::string & value)203 Query_formatter &Query_formatter::operator % (const std::string &value)
204 {
205   validate_next_tag();
206 
207   put_value_and_escape(value.c_str(), value.length());
208 
209   return *this;
210 }
211 
operator %(const No_escape<std::string> & value)212 Query_formatter &Query_formatter::operator % (const No_escape<std::string> &value)
213 {
214   validate_next_tag();
215 
216   put_value(value.m_value.c_str(), value.m_value.length());
217 
218   return *this;
219 }
220 
validate_next_tag()221 void Query_formatter::validate_next_tag()
222 {
223   ngs::PFS_string::iterator i = std::find_if(m_query.begin() + m_last_tag_position, m_query.end(), Sql_search_tags());
224 
225   if (m_query.end() == i)
226   {
227     throw ngs::Error_code(ER_X_CMD_NUM_ARGUMENTS, "Too many arguments");
228   }
229 
230   m_last_tag_position = std::distance(m_query.begin(), i);
231 }
232 
put_value_and_escape(const char * value,const std::size_t length)233 void Query_formatter::put_value_and_escape(const char *value, const std::size_t length)
234 {
235   const std::size_t length_maximum = 2 * length + 1 + 2;
236   std::string       value_escaped(length_maximum, '\0');
237 
238   std::size_t length_escaped = escape_string_for_mysql(&m_charset, &value_escaped[1], length_maximum, value, length);
239   value_escaped[0] = value_escaped[1 + length_escaped] = '\'';
240 
241   value_escaped.resize(length_escaped + 2);
242 
243   put_value(value_escaped.c_str(), value_escaped.length());
244 }
245 
put_value(const char * value,const std::size_t length)246 void Query_formatter::put_value(const char *value, const std::size_t length)
247 {
248   const uint8_t     tag_size = 1;
249   const std::size_t length_source = m_query.length();
250   const std::size_t length_target = m_query.length() +  length - tag_size;
251 
252   if (length_source < length_target)
253   {
254     m_query.resize(length_target, '\0');
255   }
256 
257   ngs::PFS_string::iterator tag_position = m_query.begin() + m_last_tag_position;
258   ngs::PFS_string::iterator move_to = tag_position + length;
259   ngs::PFS_string::iterator move_from = tag_position + tag_size;
260 
261   std::copy(move_from, m_query.begin() + length_source, move_to);
262   std::copy(value, value + length,  tag_position);
263 
264   m_last_tag_position += length;
265 
266   if (m_query.length() != length_target)
267   {
268     m_query.resize(length_target);
269   }
270 }
271