1/* 2 +----------------------------------------------------------------------+ 3 | Copyright (c) The PHP Group | 4 +----------------------------------------------------------------------+ 5 | This source file is subject to version 3.01 of the PHP license, | 6 | that is bundled with this package in the file LICENSE, and is | 7 | available through the world-wide-web at the following url: | 8 | https://www.php.net/license/3_01.txt | 9 | If you did not receive a copy of the PHP license and are unable to | 10 | obtain it through the world-wide-web, please send a note to | 11 | license@php.net so we can mail you a copy immediately. | 12 +----------------------------------------------------------------------+ 13 | Author: George Schlossnagle <george@omniti.com> | 14 +----------------------------------------------------------------------+ 15*/ 16 17#include "php.h" 18#include "php_pdo_driver.h" 19#include "pdo_sql_parser.h" 20 21static int default_scanner(pdo_scanner_t *s) 22{ 23 const char *cursor = s->cur; 24 25 s->tok = cursor; 26 /*!re2c 27 BINDCHR = [:][a-zA-Z0-9_]+; 28 QUESTION = [?]; 29 COMMENTS = ("/*"([^*]+|[*]+[^/*])*[*]*"*/"|"--".*); 30 SPECIALS = [:?"'/-]; 31 MULTICHAR = ([:]{2,}|[?]{2,}); 32 ANYNOEOF = [\001-\377]; 33 */ 34 35 /*!re2c 36 (["]((["]["])|ANYNOEOF\["])*["]) { RET(PDO_PARSER_TEXT); } 37 (['](([']['])|ANYNOEOF\['])*[']) { RET(PDO_PARSER_TEXT); } 38 MULTICHAR { RET(PDO_PARSER_TEXT); } 39 BINDCHR { RET(PDO_PARSER_BIND); } 40 QUESTION { RET(PDO_PARSER_BIND_POS); } 41 SPECIALS { SKIP_ONE(PDO_PARSER_TEXT); } 42 COMMENTS { RET(PDO_PARSER_TEXT); } 43 (ANYNOEOF\SPECIALS)+ { RET(PDO_PARSER_TEXT); } 44 */ 45} 46 47struct placeholder { 48 const char *pos; 49 size_t len; 50 zend_string *quoted; /* quoted value */ 51 int bindno; 52 struct placeholder *next; 53}; 54 55struct custom_quote { 56 const char *pos; 57 size_t len; 58}; 59 60static void free_param_name(zval *el) { 61 zend_string_release(Z_PTR_P(el)); 62} 63 64PDO_API int pdo_parse_params(pdo_stmt_t *stmt, zend_string *inquery, zend_string **outquery) 65{ 66 pdo_scanner_t s; 67 char *newbuffer; 68 ptrdiff_t t; 69 uint32_t bindno = 0; 70 int ret = 0, escapes = 0; 71 size_t newbuffer_len; 72 HashTable *params; 73 struct pdo_bound_param_data *param; 74 int query_type = PDO_PLACEHOLDER_NONE; 75 struct placeholder *placeholders = NULL, *placetail = NULL, *plc = NULL; 76 int (*scan)(pdo_scanner_t *s); 77 struct custom_quote custom_quote = {NULL, 0}; 78 79 scan = stmt->dbh->methods->scanner ? stmt->dbh->methods->scanner : default_scanner; 80 81 s.cur = ZSTR_VAL(inquery); 82 s.end = s.cur + ZSTR_LEN(inquery) + 1; 83 84 /* phase 1: look for args */ 85 while((t = scan(&s)) != PDO_PARSER_EOI) { 86 if (custom_quote.pos) { 87 /* Inside a custom quote */ 88 if (t == PDO_PARSER_CUSTOM_QUOTE && custom_quote.len == s.cur - s.tok && !strncmp(s.tok, custom_quote.pos, custom_quote.len)) { 89 /* Matching closing quote found, end custom quoting */ 90 custom_quote.pos = NULL; 91 custom_quote.len = 0; 92 } else if (t == PDO_PARSER_ESCAPED_QUESTION) { 93 /* An escaped question mark has been used inside a dollar quoted string, most likely as a workaround 94 * as a single "?" would have been parsed as placeholder, due to the lack of support for dollar quoted 95 * strings. For now, we emit a deprecation notice, but still process it */ 96 php_error_docref(NULL, E_DEPRECATED, "Escaping question marks inside dollar quoted strings is not required anymore and is deprecated"); 97 98 goto placeholder; 99 } 100 101 continue; 102 } 103 104 if (t == PDO_PARSER_CUSTOM_QUOTE) { 105 /* Start of a custom quote, keep a reference to search for the matching closing quote */ 106 custom_quote.pos = s.tok; 107 custom_quote.len = s.cur - s.tok; 108 109 continue; 110 } 111 112 if (t == PDO_PARSER_BIND || t == PDO_PARSER_BIND_POS || t == PDO_PARSER_ESCAPED_QUESTION) { 113 if (t == PDO_PARSER_ESCAPED_QUESTION && stmt->supports_placeholders == PDO_PLACEHOLDER_POSITIONAL) { 114 /* escaped question marks unsupported, treat as text */ 115 continue; 116 } 117 118 if (t == PDO_PARSER_BIND) { 119 ptrdiff_t len = s.cur - s.tok; 120 if ((ZSTR_VAL(inquery) < (s.cur - len)) && isalnum(*(s.cur - len - 1))) { 121 continue; 122 } 123 query_type |= PDO_PLACEHOLDER_NAMED; 124 } else if (t == PDO_PARSER_BIND_POS) { 125 query_type |= PDO_PLACEHOLDER_POSITIONAL; 126 } 127 128placeholder: 129 plc = emalloc(sizeof(*plc)); 130 memset(plc, 0, sizeof(*plc)); 131 plc->next = NULL; 132 plc->pos = s.tok; 133 plc->len = s.cur - s.tok; 134 135 if (t == PDO_PARSER_ESCAPED_QUESTION) { 136 plc->bindno = PDO_PARSER_BINDNO_ESCAPED_CHAR; 137 plc->quoted = ZSTR_CHAR('?'); 138 escapes++; 139 } else { 140 plc->bindno = bindno++; 141 } 142 143 if (placetail) { 144 placetail->next = plc; 145 } else { 146 placeholders = plc; 147 } 148 placetail = plc; 149 } 150 } 151 152 /* did the query make sense to me? */ 153 if (query_type == (PDO_PLACEHOLDER_NAMED|PDO_PLACEHOLDER_POSITIONAL)) { 154 /* they mixed both types; punt */ 155 pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "mixed named and positional parameters"); 156 ret = -1; 157 goto clean_up; 158 } 159 160 params = stmt->bound_params; 161 if (stmt->supports_placeholders == PDO_PLACEHOLDER_NONE && params && bindno != zend_hash_num_elements(params)) { 162 /* extra bit of validation for instances when same params are bound more than once */ 163 if (query_type != PDO_PLACEHOLDER_POSITIONAL && bindno > zend_hash_num_elements(params)) { 164 int ok = 1; 165 for (plc = placeholders; plc; plc = plc->next) { 166 if ((param = zend_hash_str_find_ptr(params, plc->pos, plc->len)) == NULL) { 167 ok = 0; 168 break; 169 } 170 } 171 if (ok) { 172 goto safe; 173 } 174 } 175 pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "number of bound variables does not match number of tokens"); 176 ret = -1; 177 goto clean_up; 178 } 179 180 if (!placeholders) { 181 /* nothing to do; good! */ 182 return 0; 183 } 184 185 if (stmt->supports_placeholders == query_type && !stmt->named_rewrite_template) { 186 /* query matches native syntax */ 187 if (escapes) { 188 newbuffer_len = ZSTR_LEN(inquery); 189 goto rewrite; 190 } 191 192 ret = 0; 193 goto clean_up; 194 } 195 196 if (query_type == PDO_PLACEHOLDER_NAMED && stmt->named_rewrite_template) { 197 /* magic/hack. 198 * We we pretend that the query was positional even if 199 * it was named so that we fall into the 200 * named rewrite case below. Not too pretty, 201 * but it works. */ 202 query_type = PDO_PLACEHOLDER_POSITIONAL; 203 } 204 205safe: 206 /* what are we going to do ? */ 207 if (stmt->supports_placeholders == PDO_PLACEHOLDER_NONE) { 208 /* query generation */ 209 210 newbuffer_len = ZSTR_LEN(inquery); 211 212 /* let's quote all the values */ 213 for (plc = placeholders; plc && params; plc = plc->next) { 214 if (plc->bindno == PDO_PARSER_BINDNO_ESCAPED_CHAR) { 215 /* escaped character */ 216 continue; 217 } 218 219 if (query_type == PDO_PLACEHOLDER_NONE) { 220 continue; 221 } 222 223 if (query_type == PDO_PLACEHOLDER_POSITIONAL) { 224 param = zend_hash_index_find_ptr(params, plc->bindno); 225 } else { 226 param = zend_hash_str_find_ptr(params, plc->pos, plc->len); 227 } 228 if (param == NULL) { 229 /* parameter was not defined */ 230 ret = -1; 231 pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "parameter was not defined"); 232 goto clean_up; 233 } 234 if (stmt->dbh->methods->quoter) { 235 zval *parameter; 236 if (Z_ISREF(param->parameter)) { 237 parameter = Z_REFVAL(param->parameter); 238 } else { 239 parameter = ¶m->parameter; 240 } 241 if (param->param_type == PDO_PARAM_LOB && Z_TYPE_P(parameter) == IS_RESOURCE) { 242 php_stream *stm; 243 244 php_stream_from_zval_no_verify(stm, parameter); 245 if (stm) { 246 zend_string *buf; 247 248 buf = php_stream_copy_to_mem(stm, PHP_STREAM_COPY_ALL, 0); 249 if (!buf) { 250 buf = ZSTR_EMPTY_ALLOC(); 251 } 252 253 plc->quoted = stmt->dbh->methods->quoter(stmt->dbh, buf, param->param_type); 254 255 if (buf) { 256 zend_string_release_ex(buf, 0); 257 } 258 if (plc->quoted == NULL) { 259 /* bork */ 260 ret = -1; 261 strncpy(stmt->error_code, stmt->dbh->error_code, 6); 262 goto clean_up; 263 } 264 265 } else { 266 pdo_raise_impl_error(stmt->dbh, stmt, "HY105", "Expected a stream resource"); 267 ret = -1; 268 goto clean_up; 269 } 270 } else { 271 enum pdo_param_type param_type = param->param_type; 272 zend_string *buf = NULL; 273 274 /* assume all types are nullable */ 275 if (Z_TYPE_P(parameter) == IS_NULL) { 276 param_type = PDO_PARAM_NULL; 277 } 278 279 switch (param_type) { 280 case PDO_PARAM_BOOL: 281 plc->quoted = zend_is_true(parameter) ? ZSTR_CHAR('1') : ZSTR_CHAR('0'); 282 break; 283 284 case PDO_PARAM_INT: 285 plc->quoted = zend_long_to_str(zval_get_long(parameter)); 286 break; 287 288 case PDO_PARAM_NULL: 289 plc->quoted = ZSTR_KNOWN(ZEND_STR_NULL); 290 break; 291 292 default: { 293 buf = zval_try_get_string(parameter); 294 /* parameter does not have a string representation, buf == NULL */ 295 if (EG(exception)) { 296 /* bork */ 297 ret = -1; 298 strncpy(stmt->error_code, stmt->dbh->error_code, 6); 299 goto clean_up; 300 } 301 302 plc->quoted = stmt->dbh->methods->quoter(stmt->dbh, buf, param_type); 303 } 304 } 305 306 if (buf) { 307 zend_string_release_ex(buf, 0); 308 } 309 } 310 } else { 311 zval *parameter; 312 if (Z_ISREF(param->parameter)) { 313 parameter = Z_REFVAL(param->parameter); 314 } else { 315 parameter = ¶m->parameter; 316 } 317 plc->quoted = zend_string_copy(Z_STR_P(parameter)); 318 } 319 newbuffer_len += ZSTR_LEN(plc->quoted); 320 } 321 322rewrite: 323 /* allocate output buffer */ 324 *outquery = zend_string_alloc(newbuffer_len, 0); 325 newbuffer = ZSTR_VAL(*outquery); 326 327 /* and build the query */ 328 const char *ptr = ZSTR_VAL(inquery); 329 plc = placeholders; 330 331 do { 332 t = plc->pos - ptr; 333 if (t) { 334 memcpy(newbuffer, ptr, t); 335 newbuffer += t; 336 } 337 if (plc->quoted) { 338 memcpy(newbuffer, ZSTR_VAL(plc->quoted), ZSTR_LEN(plc->quoted)); 339 newbuffer += ZSTR_LEN(plc->quoted); 340 } else { 341 memcpy(newbuffer, plc->pos, plc->len); 342 newbuffer += plc->len; 343 } 344 ptr = plc->pos + plc->len; 345 346 plc = plc->next; 347 } while (plc); 348 349 t = ZSTR_VAL(inquery) + ZSTR_LEN(inquery) - ptr; 350 if (t) { 351 memcpy(newbuffer, ptr, t); 352 newbuffer += t; 353 } 354 *newbuffer = '\0'; 355 ZSTR_LEN(*outquery) = newbuffer - ZSTR_VAL(*outquery); 356 357 ret = 1; 358 goto clean_up; 359 360 } else if (query_type == PDO_PLACEHOLDER_POSITIONAL) { 361 /* rewrite ? to :pdoX */ 362 const char *tmpl = stmt->named_rewrite_template ? stmt->named_rewrite_template : ":pdo%d"; 363 int bind_no = 1; 364 365 newbuffer_len = ZSTR_LEN(inquery); 366 367 if (stmt->bound_param_map == NULL) { 368 ALLOC_HASHTABLE(stmt->bound_param_map); 369 zend_hash_init(stmt->bound_param_map, 13, NULL, free_param_name, 0); 370 } 371 372 for (plc = placeholders; plc; plc = plc->next) { 373 int skip_map = 0; 374 zend_string *p; 375 zend_string *idxbuf; 376 377 if (plc->bindno == PDO_PARSER_BINDNO_ESCAPED_CHAR) { 378 continue; 379 } 380 381 zend_string *name = zend_string_init(plc->pos, plc->len, 0); 382 383 /* check if bound parameter is already available */ 384 if (zend_string_equals_literal(name, "?") || (p = zend_hash_find_ptr(stmt->bound_param_map, name)) == NULL) { 385 idxbuf = zend_strpprintf(0, tmpl, bind_no++); 386 } else { 387 idxbuf = zend_string_copy(p); 388 skip_map = 1; 389 } 390 391 plc->quoted = idxbuf; 392 newbuffer_len += ZSTR_LEN(plc->quoted); 393 394 if (!skip_map && stmt->named_rewrite_template) { 395 /* create a mapping */ 396 zend_hash_update_ptr(stmt->bound_param_map, name, zend_string_copy(plc->quoted)); 397 } 398 399 /* map number to name */ 400 zend_hash_index_update_ptr(stmt->bound_param_map, plc->bindno, zend_string_copy(plc->quoted)); 401 402 zend_string_release(name); 403 } 404 405 goto rewrite; 406 407 } else { 408 /* rewrite :name to ? */ 409 410 newbuffer_len = ZSTR_LEN(inquery); 411 412 if (stmt->bound_param_map == NULL) { 413 ALLOC_HASHTABLE(stmt->bound_param_map); 414 zend_hash_init(stmt->bound_param_map, 13, NULL, free_param_name, 0); 415 } 416 417 for (plc = placeholders; plc; plc = plc->next) { 418 zend_string *name = zend_string_init(plc->pos, plc->len, 0); 419 zend_hash_index_update_ptr(stmt->bound_param_map, plc->bindno, name); 420 plc->quoted = ZSTR_CHAR('?'); 421 newbuffer_len -= plc->len - 1; 422 } 423 424 goto rewrite; 425 } 426 427clean_up: 428 429 while (placeholders) { 430 plc = placeholders; 431 placeholders = plc->next; 432 if (plc->quoted) { 433 zend_string_release_ex(plc->quoted, 0); 434 } 435 efree(plc); 436 } 437 438 return ret; 439} 440