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 = &param->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 = &param->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