xref: /PHP-7.2/ext/pdo/pdo_sql_parser.re (revision 7a7ec01a)
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 = &param->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 = &param->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