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