1/* 2 +----------------------------------------------------------------------+ 3 | PHP Version 7 | 4 +----------------------------------------------------------------------+ 5 | Copyright (c) 1997-2018 The PHP Group | 6 +----------------------------------------------------------------------+ 7 | This source file is subject to version 3.01 of the PHP license, | 8 | that is bundled with this package in the file LICENSE, and is | 9 | available through the world-wide-web at the following url: | 10 | http://www.php.net/license/3_01.txt | 11 | If you did not receive a copy of the PHP license and are unable to | 12 | obtain it through the world-wide-web, please send a note to | 13 | license@php.net so we can mail you a copy immediately. | 14 +----------------------------------------------------------------------+ 15 | Author: George Schlossnagle <george@omniti.com> | 16 +----------------------------------------------------------------------+ 17*/ 18 19/* $Id$ */ 20 21#include "php.h" 22#include "php_pdo_driver.h" 23#include "php_pdo_int.h" 24 25#define PDO_PARSER_TEXT 1 26#define PDO_PARSER_BIND 2 27#define PDO_PARSER_BIND_POS 3 28#define PDO_PARSER_EOI 4 29 30#define RET(i) {s->cur = cursor; return i; } 31#define SKIP_ONE(i) {s->cur = s->tok + 1; return i; } 32 33#define YYCTYPE unsigned char 34#define YYCURSOR cursor 35#define YYLIMIT s->end 36#define YYMARKER s->ptr 37#define YYFILL(n) { RET(PDO_PARSER_EOI); } 38 39typedef struct Scanner { 40 char *ptr, *cur, *tok, *end; 41} Scanner; 42 43static int scan(Scanner *s) 44{ 45 char *cursor = s->cur; 46 47 s->tok = cursor; 48 /*!re2c 49 BINDCHR = [:][a-zA-Z0-9_]+; 50 QUESTION = [?]; 51 COMMENTS = ("/*"([^*]+|[*]+[^/*])*[*]*"*/"|"--"[^\r\n]*); 52 SPECIALS = [:?"'-/]; 53 MULTICHAR = ([:]{2,}|[?]{2,}); 54 ANYNOEOF = [\001-\377]; 55 */ 56 57 /*!re2c 58 (["](([\\]ANYNOEOF)|ANYNOEOF\["\\])*["]) { RET(PDO_PARSER_TEXT); } 59 (['](([\\]ANYNOEOF)|ANYNOEOF\['\\])*[']) { RET(PDO_PARSER_TEXT); } 60 MULTICHAR { RET(PDO_PARSER_TEXT); } 61 BINDCHR { RET(PDO_PARSER_BIND); } 62 QUESTION { RET(PDO_PARSER_BIND_POS); } 63 SPECIALS { SKIP_ONE(PDO_PARSER_TEXT); } 64 COMMENTS { RET(PDO_PARSER_TEXT); } 65 (ANYNOEOF\SPECIALS)+ { RET(PDO_PARSER_TEXT); } 66 */ 67} 68 69struct placeholder { 70 char *pos; 71 size_t len; 72 int bindno; 73 size_t qlen; /* quoted length of value */ 74 char *quoted; /* quoted value */ 75 int freeq; 76 struct placeholder *next; 77}; 78 79static void free_param_name(zval *el) { 80 efree(Z_PTR_P(el)); 81} 82 83PDO_API int pdo_parse_params(pdo_stmt_t *stmt, char *inquery, size_t inquery_len, 84 char **outquery, size_t *outquery_len) 85{ 86 Scanner s; 87 char *ptr, *newbuffer; 88 int t; 89 uint32_t bindno = 0; 90 int ret = 0; 91 size_t newbuffer_len; 92 HashTable *params; 93 struct pdo_bound_param_data *param; 94 int query_type = PDO_PLACEHOLDER_NONE; 95 struct placeholder *placeholders = NULL, *placetail = NULL, *plc = NULL; 96 97 ptr = *outquery; 98 s.cur = inquery; 99 s.end = inquery + inquery_len + 1; 100 101 /* phase 1: look for args */ 102 while((t = scan(&s)) != PDO_PARSER_EOI) { 103 if (t == PDO_PARSER_BIND || t == PDO_PARSER_BIND_POS) { 104 if (t == PDO_PARSER_BIND) { 105 int len = s.cur - s.tok; 106 if ((inquery < (s.cur - len)) && isalnum(*(s.cur - len - 1))) { 107 continue; 108 } 109 query_type |= PDO_PLACEHOLDER_NAMED; 110 } else { 111 query_type |= PDO_PLACEHOLDER_POSITIONAL; 112 } 113 114 plc = emalloc(sizeof(*plc)); 115 memset(plc, 0, sizeof(*plc)); 116 plc->next = NULL; 117 plc->pos = s.tok; 118 plc->len = s.cur - s.tok; 119 plc->bindno = bindno++; 120 121 if (placetail) { 122 placetail->next = plc; 123 } else { 124 placeholders = plc; 125 } 126 placetail = plc; 127 } 128 } 129 130 if (bindno == 0) { 131 /* nothing to do; good! */ 132 return 0; 133 } 134 135 /* did the query make sense to me? */ 136 if (query_type == (PDO_PLACEHOLDER_NAMED|PDO_PLACEHOLDER_POSITIONAL)) { 137 /* they mixed both types; punt */ 138 pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "mixed named and positional parameters"); 139 ret = -1; 140 goto clean_up; 141 } 142 143 if (stmt->supports_placeholders == query_type && !stmt->named_rewrite_template) { 144 /* query matches native syntax */ 145 ret = 0; 146 goto clean_up; 147 } 148 149 if (stmt->named_rewrite_template) { 150 /* magic/hack. 151 * We we pretend that the query was positional even if 152 * it was named so that we fall into the 153 * named rewrite case below. Not too pretty, 154 * but it works. */ 155 query_type = PDO_PLACEHOLDER_POSITIONAL; 156 } 157 158 params = stmt->bound_params; 159 160 /* Do we have placeholders but no bound params */ 161 if (bindno && !params && stmt->supports_placeholders == PDO_PLACEHOLDER_NONE) { 162 pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "no parameters were bound"); 163 ret = -1; 164 goto clean_up; 165 } 166 167 if (params && bindno != zend_hash_num_elements(params) && stmt->supports_placeholders == PDO_PLACEHOLDER_NONE) { 168 /* extra bit of validation for instances when same params are bound more than once */ 169 if (query_type != PDO_PLACEHOLDER_POSITIONAL && bindno > zend_hash_num_elements(params)) { 170 int ok = 1; 171 for (plc = placeholders; plc; plc = plc->next) { 172 if ((param = zend_hash_str_find_ptr(params, plc->pos, plc->len)) == NULL) { 173 ok = 0; 174 break; 175 } 176 } 177 if (ok) { 178 goto safe; 179 } 180 } 181 pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "number of bound variables does not match number of tokens"); 182 ret = -1; 183 goto clean_up; 184 } 185safe: 186 /* what are we going to do ? */ 187 if (stmt->supports_placeholders == PDO_PLACEHOLDER_NONE) { 188 /* query generation */ 189 190 newbuffer_len = inquery_len; 191 192 /* let's quote all the values */ 193 for (plc = placeholders; plc; plc = plc->next) { 194 if (query_type == PDO_PLACEHOLDER_POSITIONAL) { 195 param = zend_hash_index_find_ptr(params, plc->bindno); 196 } else { 197 param = zend_hash_str_find_ptr(params, plc->pos, plc->len); 198 } 199 if (param == NULL) { 200 /* parameter was not defined */ 201 ret = -1; 202 pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "parameter was not defined"); 203 goto clean_up; 204 } 205 if (stmt->dbh->methods->quoter) { 206 zval *parameter; 207 if (Z_ISREF(param->parameter)) { 208 parameter = Z_REFVAL(param->parameter); 209 } else { 210 parameter = ¶m->parameter; 211 } 212 if (param->param_type == PDO_PARAM_LOB && Z_TYPE_P(parameter) == IS_RESOURCE) { 213 php_stream *stm; 214 215 php_stream_from_zval_no_verify(stm, parameter); 216 if (stm) { 217 zend_string *buf; 218 219 buf = php_stream_copy_to_mem(stm, PHP_STREAM_COPY_ALL, 0); 220 if (!buf) { 221 buf = ZSTR_EMPTY_ALLOC(); 222 } 223 if (!stmt->dbh->methods->quoter(stmt->dbh, ZSTR_VAL(buf), ZSTR_LEN(buf), &plc->quoted, &plc->qlen, 224 param->param_type)) { 225 /* bork */ 226 ret = -1; 227 strncpy(stmt->error_code, stmt->dbh->error_code, 6); 228 if (buf) { 229 zend_string_release(buf); 230 } 231 goto clean_up; 232 } 233 if (buf) { 234 zend_string_release(buf); 235 } 236 } else { 237 pdo_raise_impl_error(stmt->dbh, stmt, "HY105", "Expected a stream resource"); 238 ret = -1; 239 goto clean_up; 240 } 241 plc->freeq = 1; 242 } else { 243 zval tmp_param; 244 ZVAL_DUP(&tmp_param, parameter); 245 switch (Z_TYPE(tmp_param)) { 246 case IS_NULL: 247 plc->quoted = "NULL"; 248 plc->qlen = sizeof("NULL")-1; 249 plc->freeq = 0; 250 break; 251 252 case IS_FALSE: 253 case IS_TRUE: 254 convert_to_long(&tmp_param); 255 /* fall through */ 256 case IS_LONG: 257 case IS_DOUBLE: 258 convert_to_string(&tmp_param); 259 plc->qlen = Z_STRLEN(tmp_param); 260 plc->quoted = estrdup(Z_STRVAL(tmp_param)); 261 plc->freeq = 1; 262 break; 263 264 default: 265 convert_to_string(&tmp_param); 266 if (!stmt->dbh->methods->quoter(stmt->dbh, Z_STRVAL(tmp_param), 267 Z_STRLEN(tmp_param), &plc->quoted, &plc->qlen, 268 param->param_type)) { 269 /* bork */ 270 ret = -1; 271 strncpy(stmt->error_code, stmt->dbh->error_code, 6); 272 goto clean_up; 273 } 274 plc->freeq = 1; 275 } 276 zval_dtor(&tmp_param); 277 } 278 } else { 279 zval *parameter; 280 if (Z_ISREF(param->parameter)) { 281 parameter = Z_REFVAL(param->parameter); 282 } else { 283 parameter = ¶m->parameter; 284 } 285 plc->quoted = Z_STRVAL_P(parameter); 286 plc->qlen = Z_STRLEN_P(parameter); 287 } 288 newbuffer_len += plc->qlen; 289 } 290 291rewrite: 292 /* allocate output buffer */ 293 newbuffer = emalloc(newbuffer_len + 1); 294 *outquery = newbuffer; 295 296 /* and build the query */ 297 plc = placeholders; 298 ptr = inquery; 299 300 do { 301 t = plc->pos - ptr; 302 if (t) { 303 memcpy(newbuffer, ptr, t); 304 newbuffer += t; 305 } 306 memcpy(newbuffer, plc->quoted, plc->qlen); 307 newbuffer += plc->qlen; 308 ptr = plc->pos + plc->len; 309 310 plc = plc->next; 311 } while (plc); 312 313 t = (inquery + inquery_len) - ptr; 314 if (t) { 315 memcpy(newbuffer, ptr, t); 316 newbuffer += t; 317 } 318 *newbuffer = '\0'; 319 *outquery_len = newbuffer - *outquery; 320 321 ret = 1; 322 goto clean_up; 323 324 } else if (query_type == PDO_PLACEHOLDER_POSITIONAL) { 325 /* rewrite ? to :pdoX */ 326 char *name, *idxbuf; 327 const char *tmpl = stmt->named_rewrite_template ? stmt->named_rewrite_template : ":pdo%d"; 328 int bind_no = 1; 329 330 newbuffer_len = inquery_len; 331 332 if (stmt->bound_param_map == NULL) { 333 ALLOC_HASHTABLE(stmt->bound_param_map); 334 zend_hash_init(stmt->bound_param_map, 13, NULL, free_param_name, 0); 335 } 336 337 for (plc = placeholders; plc; plc = plc->next) { 338 int skip_map = 0; 339 char *p; 340 name = estrndup(plc->pos, plc->len); 341 342 /* check if bound parameter is already available */ 343 if (!strcmp(name, "?") || (p = zend_hash_str_find_ptr(stmt->bound_param_map, name, plc->len)) == NULL) { 344 spprintf(&idxbuf, 0, tmpl, bind_no++); 345 } else { 346 idxbuf = estrdup(p); 347 skip_map = 1; 348 } 349 350 plc->quoted = idxbuf; 351 plc->qlen = strlen(plc->quoted); 352 plc->freeq = 1; 353 newbuffer_len += plc->qlen; 354 355 if (!skip_map && stmt->named_rewrite_template) { 356 /* create a mapping */ 357 zend_hash_str_update_mem(stmt->bound_param_map, name, plc->len, idxbuf, plc->qlen + 1); 358 } 359 360 /* map number to name */ 361 zend_hash_index_update_mem(stmt->bound_param_map, plc->bindno, idxbuf, plc->qlen + 1); 362 363 efree(name); 364 } 365 366 goto rewrite; 367 368 } else { 369 /* rewrite :name to ? */ 370 371 newbuffer_len = inquery_len; 372 373 if (stmt->bound_param_map == NULL) { 374 ALLOC_HASHTABLE(stmt->bound_param_map); 375 zend_hash_init(stmt->bound_param_map, 13, NULL, free_param_name, 0); 376 } 377 378 for (plc = placeholders; plc; plc = plc->next) { 379 char *name; 380 name = estrndup(plc->pos, plc->len); 381 zend_hash_index_update_mem(stmt->bound_param_map, plc->bindno, name, plc->len + 1); 382 efree(name); 383 plc->quoted = "?"; 384 plc->qlen = 1; 385 } 386 387 goto rewrite; 388 } 389 390clean_up: 391 392 while (placeholders) { 393 plc = placeholders; 394 placeholders = plc->next; 395 396 if (plc->freeq) { 397 efree(plc->quoted); 398 } 399 400 efree(plc); 401 } 402 403 return ret; 404} 405 406#if 0 407int old_pdo_parse_params(pdo_stmt_t *stmt, char *inquery, int inquery_len, char **outquery, 408 int *outquery_len) 409{ 410 Scanner s; 411 char *ptr; 412 int t; 413 int bindno = 0; 414 int newbuffer_len; 415 int padding; 416 HashTable *params = stmt->bound_params; 417 struct pdo_bound_param_data *param; 418 /* allocate buffer for query with expanded binds, ptr is our writing pointer */ 419 newbuffer_len = inquery_len; 420 421 /* calculate the possible padding factor due to quoting */ 422 if(stmt->dbh->max_escaped_char_length) { 423 padding = stmt->dbh->max_escaped_char_length; 424 } else { 425 padding = 3; 426 } 427 if(params) { 428 ZEND_HASH_FOREACH_PTR(params, param) { 429 if(param->parameter) { 430 convert_to_string(param->parameter); 431 /* accommodate a string that needs to be fully quoted 432 bind placeholders are at least 2 characters, so 433 the accommodate their own "'s 434 */ 435 newbuffer_len += padding * Z_STRLEN_P(param->parameter); 436 } 437 } ZEND_HASH_FOREACH_END(); 438 } 439 *outquery = (char *) emalloc(newbuffer_len + 1); 440 *outquery_len = 0; 441 442 ptr = *outquery; 443 s.cur = inquery; 444 while((t = scan(&s)) != PDO_PARSER_EOI) { 445 if(t == PDO_PARSER_TEXT) { 446 memcpy(ptr, s.tok, s.cur - s.tok); 447 ptr += (s.cur - s.tok); 448 *outquery_len += (s.cur - s.tok); 449 } 450 else if(t == PDO_PARSER_BIND) { 451 if(!params) { 452 /* error */ 453 efree(*outquery); 454 *outquery = NULL; 455 return (int) (s.cur - inquery); 456 } 457 /* lookup bind first via hash and then index */ 458 /* stupid keys need to be null-terminated, even though we know their length */ 459 if((NULL != (param = zend_hash_str_find_ptr(params, s.tok, s.cur-s.tok)) 460 || 461 NULL != (params = zend_hash_index_find_ptr(params, bindno))) 462 { 463 char *quotedstr; 464 int quotedstrlen; 465 /* restore the in-string key, doesn't need null-termination here */ 466 /* currently everything is a string here */ 467 468 /* quote the bind value if necessary */ 469 if(stmt->dbh->methods->quoter(stmt->dbh, Z_STRVAL_P(param->parameter), 470 Z_STRLEN_P(param->parameter), "edstr, "edstrlen)) 471 { 472 memcpy(ptr, quotedstr, quotedstrlen); 473 ptr += quotedstrlen; 474 *outquery_len += quotedstrlen; 475 efree(quotedstr); 476 } else { 477 memcpy(ptr, Z_STRVAL_P(param->parameter), Z_STRLEN_P(param->parameter)); 478 ptr += Z_STRLEN_P(param->parameter); 479 *outquery_len += (Z_STRLEN_P(param->parameter)); 480 } 481 } 482 else { 483 /* error and cleanup */ 484 efree(*outquery); 485 *outquery = NULL; 486 return (int) (s.cur - inquery); 487 } 488 bindno++; 489 } 490 else if(t == PDO_PARSER_BIND_POS) { 491 if(!params) { 492 /* error */ 493 efree(*outquery); 494 *outquery = NULL; 495 return (int) (s.cur - inquery); 496 } 497 /* lookup bind by index */ 498 if(NULL != (params = zend_hash_index_find_ptr(params, bindno))) 499 { 500 char *quotedstr; 501 int quotedstrlen; 502 /* currently everything is a string here */ 503 504 /* quote the bind value if necessary */ 505 if(stmt->dbh->methods->quoter(stmt->dbh, Z_STRVAL_P(param->parameter), 506 Z_STRLEN_P(param->parameter), "edstr, "edstrlen)) 507 { 508 memcpy(ptr, quotedstr, quotedstrlen); 509 ptr += quotedstrlen; 510 *outquery_len += quotedstrlen; 511 efree(quotedstr); 512 } else { 513 memcpy(ptr, Z_STRVAL_P(param->parameter), Z_STRLEN_P(param->parameter)); 514 ptr += Z_STRLEN_P(param->parameter); 515 *outquery_len += (Z_STRLEN_P(param->parameter)); 516 } 517 } 518 else { 519 /* error and cleanup */ 520 efree(*outquery); 521 *outquery = NULL; 522 return (int) (s.cur - inquery); 523 } 524 bindno++; 525 } 526 } 527 *ptr = '\0'; 528 return 0; 529} 530#endif 531 532/* 533 * Local variables: 534 * tab-width: 4 535 * c-basic-offset: 4 536 * End: 537 * vim600: noet sw=4 ts=4 fdm=marker ft=c 538 * vim<600: noet sw=4 ts=4 539 */ 540