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