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