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