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