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