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 } else { 246 pdo_raise_impl_error(stmt->dbh, stmt, "HY105", "Expected a stream resource"); 247 ret = -1; 248 goto clean_up; 249 } 250 } else { 251 enum pdo_param_type param_type = param->param_type; 252 zend_string *buf = NULL; 253 254 /* assume all types are nullable */ 255 if (Z_TYPE_P(parameter) == IS_NULL) { 256 param_type = PDO_PARAM_NULL; 257 } 258 259 switch (param_type) { 260 case PDO_PARAM_BOOL: 261 plc->quoted = zend_is_true(parameter) ? ZSTR_CHAR('1') : ZSTR_CHAR('0'); 262 break; 263 264 case PDO_PARAM_INT: 265 plc->quoted = zend_long_to_str(zval_get_long(parameter)); 266 break; 267 268 case PDO_PARAM_NULL: 269 plc->quoted = ZSTR_KNOWN(ZEND_STR_NULL); 270 break; 271 272 default: { 273 buf = zval_try_get_string(parameter); 274 /* parameter does not have a string representation, buf == NULL */ 275 if (EG(exception)) { 276 /* bork */ 277 ret = -1; 278 strncpy(stmt->error_code, stmt->dbh->error_code, 6); 279 goto clean_up; 280 } 281 282 plc->quoted = stmt->dbh->methods->quoter(stmt->dbh, buf, param_type); 283 } 284 } 285 286 if (buf) { 287 zend_string_release_ex(buf, 0); 288 } 289 } 290 } else { 291 zval *parameter; 292 if (Z_ISREF(param->parameter)) { 293 parameter = Z_REFVAL(param->parameter); 294 } else { 295 parameter = ¶m->parameter; 296 } 297 plc->quoted = zend_string_copy(Z_STR_P(parameter)); 298 } 299 newbuffer_len += ZSTR_LEN(plc->quoted); 300 } 301 302rewrite: 303 /* allocate output buffer */ 304 *outquery = zend_string_alloc(newbuffer_len, 0); 305 newbuffer = ZSTR_VAL(*outquery); 306 307 /* and build the query */ 308 const char *ptr = ZSTR_VAL(inquery); 309 plc = placeholders; 310 311 do { 312 t = plc->pos - ptr; 313 if (t) { 314 memcpy(newbuffer, ptr, t); 315 newbuffer += t; 316 } 317 if (plc->quoted) { 318 memcpy(newbuffer, ZSTR_VAL(plc->quoted), ZSTR_LEN(plc->quoted)); 319 newbuffer += ZSTR_LEN(plc->quoted); 320 } else { 321 memcpy(newbuffer, plc->pos, plc->len); 322 newbuffer += plc->len; 323 } 324 ptr = plc->pos + plc->len; 325 326 plc = plc->next; 327 } while (plc); 328 329 t = ZSTR_VAL(inquery) + ZSTR_LEN(inquery) - ptr; 330 if (t) { 331 memcpy(newbuffer, ptr, t); 332 newbuffer += t; 333 } 334 *newbuffer = '\0'; 335 ZSTR_LEN(*outquery) = newbuffer - ZSTR_VAL(*outquery); 336 337 ret = 1; 338 goto clean_up; 339 340 } else if (query_type == PDO_PLACEHOLDER_POSITIONAL) { 341 /* rewrite ? to :pdoX */ 342 const char *tmpl = stmt->named_rewrite_template ? stmt->named_rewrite_template : ":pdo%d"; 343 int bind_no = 1; 344 345 newbuffer_len = ZSTR_LEN(inquery); 346 347 if (stmt->bound_param_map == NULL) { 348 ALLOC_HASHTABLE(stmt->bound_param_map); 349 zend_hash_init(stmt->bound_param_map, 13, NULL, free_param_name, 0); 350 } 351 352 for (plc = placeholders; plc; plc = plc->next) { 353 int skip_map = 0; 354 zend_string *p; 355 zend_string *idxbuf; 356 357 if (plc->bindno == PDO_PARSER_BINDNO_ESCAPED_CHAR) { 358 continue; 359 } 360 361 zend_string *name = zend_string_init(plc->pos, plc->len, 0); 362 363 /* check if bound parameter is already available */ 364 if (zend_string_equals_literal(name, "?") || (p = zend_hash_find_ptr(stmt->bound_param_map, name)) == NULL) { 365 idxbuf = zend_strpprintf(0, tmpl, bind_no++); 366 } else { 367 idxbuf = zend_string_copy(p); 368 skip_map = 1; 369 } 370 371 plc->quoted = idxbuf; 372 newbuffer_len += ZSTR_LEN(plc->quoted); 373 374 if (!skip_map && stmt->named_rewrite_template) { 375 /* create a mapping */ 376 zend_hash_update_ptr(stmt->bound_param_map, name, zend_string_copy(plc->quoted)); 377 } 378 379 /* map number to name */ 380 zend_hash_index_update_ptr(stmt->bound_param_map, plc->bindno, zend_string_copy(plc->quoted)); 381 382 zend_string_release(name); 383 } 384 385 goto rewrite; 386 387 } else { 388 /* rewrite :name to ? */ 389 390 newbuffer_len = ZSTR_LEN(inquery); 391 392 if (stmt->bound_param_map == NULL) { 393 ALLOC_HASHTABLE(stmt->bound_param_map); 394 zend_hash_init(stmt->bound_param_map, 13, NULL, free_param_name, 0); 395 } 396 397 for (plc = placeholders; plc; plc = plc->next) { 398 zend_string *name = zend_string_init(plc->pos, plc->len, 0); 399 zend_hash_index_update_ptr(stmt->bound_param_map, plc->bindno, name); 400 plc->quoted = ZSTR_CHAR('?'); 401 newbuffer_len -= plc->len - 1; 402 } 403 404 goto rewrite; 405 } 406 407clean_up: 408 409 while (placeholders) { 410 plc = placeholders; 411 placeholders = plc->next; 412 if (plc->quoted) { 413 zend_string_release_ex(plc->quoted, 0); 414 } 415 efree(plc); 416 } 417 418 return ret; 419} 420