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