xref: /PHP-8.0/ext/pdo/pdo_sql_parser.re (revision 9e3ba775)
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  | http://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	size_t qlen;	/* quoted length of value */
74	char *quoted;	/* quoted value */
75	int freeq;
76	int bindno;
77	struct placeholder *next;
78};
79
80static void free_param_name(zval *el) {
81	efree(Z_PTR_P(el));
82}
83
84PDO_API int pdo_parse_params(pdo_stmt_t *stmt, const char *inquery, size_t inquery_len,
85	char **outquery, size_t *outquery_len)
86{
87	Scanner s;
88	const char *ptr;
89	char *newbuffer;
90	ptrdiff_t t;
91	uint32_t bindno = 0;
92	int ret = 0, escapes = 0;
93	size_t newbuffer_len;
94	HashTable *params;
95	struct pdo_bound_param_data *param;
96	int query_type = PDO_PLACEHOLDER_NONE;
97	struct placeholder *placeholders = NULL, *placetail = NULL, *plc = NULL;
98
99	ptr = *outquery;
100	s.cur = inquery;
101	s.end = inquery + inquery_len + 1;
102
103	/* phase 1: look for args */
104	while((t = scan(&s)) != PDO_PARSER_EOI) {
105		if (t == PDO_PARSER_BIND || t == PDO_PARSER_BIND_POS || t == PDO_PARSER_ESCAPED_QUESTION) {
106			if (t == PDO_PARSER_ESCAPED_QUESTION && stmt->supports_placeholders == PDO_PLACEHOLDER_POSITIONAL) {
107				/* escaped question marks unsupported, treat as text */
108				continue;
109			}
110
111			if (t == PDO_PARSER_BIND) {
112				ptrdiff_t len = s.cur - s.tok;
113				if ((inquery < (s.cur - len)) && isalnum(*(s.cur - len - 1))) {
114					continue;
115				}
116				query_type |= PDO_PLACEHOLDER_NAMED;
117			} else if (t == PDO_PARSER_BIND_POS) {
118				query_type |= PDO_PLACEHOLDER_POSITIONAL;
119			}
120
121			plc = emalloc(sizeof(*plc));
122			memset(plc, 0, sizeof(*plc));
123			plc->next = NULL;
124			plc->pos = s.tok;
125			plc->len = s.cur - s.tok;
126
127			if (t == PDO_PARSER_ESCAPED_QUESTION) {
128				plc->bindno = PDO_PARSER_BINDNO_ESCAPED_CHAR;
129				plc->quoted = "?";
130				plc->qlen = 1;
131				plc->freeq = 0;
132				escapes++;
133			} else {
134				plc->bindno = bindno++;
135			}
136
137			if (placetail) {
138				placetail->next = plc;
139			} else {
140				placeholders = plc;
141			}
142			placetail = plc;
143		}
144	}
145
146	/* did the query make sense to me? */
147	if (query_type == (PDO_PLACEHOLDER_NAMED|PDO_PLACEHOLDER_POSITIONAL)) {
148		/* they mixed both types; punt */
149		pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "mixed named and positional parameters");
150		ret = -1;
151		goto clean_up;
152	}
153
154	params = stmt->bound_params;
155	if (stmt->supports_placeholders == PDO_PLACEHOLDER_NONE && params && bindno != zend_hash_num_elements(params)) {
156		/* extra bit of validation for instances when same params are bound more than once */
157		if (query_type != PDO_PLACEHOLDER_POSITIONAL && bindno > zend_hash_num_elements(params)) {
158			int ok = 1;
159			for (plc = placeholders; plc; plc = plc->next) {
160				if ((param = zend_hash_str_find_ptr(params, plc->pos, plc->len)) == NULL) {
161					ok = 0;
162					break;
163				}
164			}
165			if (ok) {
166				goto safe;
167			}
168		}
169		pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "number of bound variables does not match number of tokens");
170		ret = -1;
171		goto clean_up;
172	}
173
174	if (!placeholders) {
175		/* nothing to do; good! */
176		return 0;
177	}
178
179	if (stmt->supports_placeholders == query_type && !stmt->named_rewrite_template) {
180		/* query matches native syntax */
181		if (escapes) {
182			newbuffer_len = inquery_len;
183			goto rewrite;
184		}
185
186		ret = 0;
187		goto clean_up;
188	}
189
190	if (query_type == PDO_PLACEHOLDER_NAMED && stmt->named_rewrite_template) {
191		/* magic/hack.
192		 * We we pretend that the query was positional even if
193		 * it was named so that we fall into the
194		 * named rewrite case below.  Not too pretty,
195		 * but it works. */
196		query_type = PDO_PLACEHOLDER_POSITIONAL;
197	}
198
199safe:
200	/* what are we going to do ? */
201	if (stmt->supports_placeholders == PDO_PLACEHOLDER_NONE) {
202		/* query generation */
203
204		newbuffer_len = inquery_len;
205
206		/* let's quote all the values */
207		for (plc = placeholders; plc && params; plc = plc->next) {
208			if (plc->bindno == PDO_PARSER_BINDNO_ESCAPED_CHAR) {
209				/* escaped character */
210				continue;
211			}
212
213			if (query_type == PDO_PLACEHOLDER_NONE) {
214				continue;
215			}
216
217			if (query_type == PDO_PLACEHOLDER_POSITIONAL) {
218				param = zend_hash_index_find_ptr(params, plc->bindno);
219			} else {
220				param = zend_hash_str_find_ptr(params, plc->pos, plc->len);
221			}
222			if (param == NULL) {
223				/* parameter was not defined */
224				ret = -1;
225				pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "parameter was not defined");
226				goto clean_up;
227			}
228			if (stmt->dbh->methods->quoter) {
229				zval *parameter;
230				if (Z_ISREF(param->parameter)) {
231					parameter = Z_REFVAL(param->parameter);
232				} else {
233					parameter = &param->parameter;
234				}
235				if (param->param_type == PDO_PARAM_LOB && Z_TYPE_P(parameter) == IS_RESOURCE) {
236					php_stream *stm;
237
238					php_stream_from_zval_no_verify(stm, parameter);
239					if (stm) {
240						zend_string *buf;
241
242						buf = php_stream_copy_to_mem(stm, PHP_STREAM_COPY_ALL, 0);
243						if (!buf) {
244							buf = ZSTR_EMPTY_ALLOC();
245						}
246						if (!stmt->dbh->methods->quoter(stmt->dbh, ZSTR_VAL(buf), ZSTR_LEN(buf), &plc->quoted, &plc->qlen,
247								param->param_type)) {
248							/* bork */
249							ret = -1;
250							strncpy(stmt->error_code, stmt->dbh->error_code, 6);
251							if (buf) {
252								zend_string_release_ex(buf, 0);
253							}
254							goto clean_up;
255						}
256						if (buf) {
257							zend_string_release_ex(buf, 0);
258						}
259					} else {
260						pdo_raise_impl_error(stmt->dbh, stmt, "HY105", "Expected a stream resource");
261						ret = -1;
262						goto clean_up;
263					}
264					plc->freeq = 1;
265				} else {
266					enum pdo_param_type param_type = param->param_type;
267					zend_string *buf = NULL;
268
269					/* assume all types are nullable */
270					if (Z_TYPE_P(parameter) == IS_NULL) {
271						param_type = PDO_PARAM_NULL;
272					}
273
274					switch (param_type) {
275						case PDO_PARAM_BOOL:
276							plc->quoted = zend_is_true(parameter) ? "1" : "0";
277							plc->qlen = sizeof("1")-1;
278							plc->freeq = 0;
279							break;
280
281						case PDO_PARAM_INT:
282							buf = zend_long_to_str(zval_get_long(parameter));
283
284							plc->qlen = ZSTR_LEN(buf);
285							plc->quoted = estrdup(ZSTR_VAL(buf));
286							plc->freeq = 1;
287							break;
288
289						case PDO_PARAM_NULL:
290							plc->quoted = "NULL";
291							plc->qlen = sizeof("NULL")-1;
292							plc->freeq = 0;
293							break;
294
295						default:
296							buf = zval_get_string(parameter);
297							if (EG(exception) ||
298								!stmt->dbh->methods->quoter(stmt->dbh, ZSTR_VAL(buf),
299									ZSTR_LEN(buf), &plc->quoted, &plc->qlen,
300									param_type)) {
301								/* bork */
302								ret = -1;
303								strncpy(stmt->error_code, stmt->dbh->error_code, 6);
304								if (buf) {
305									zend_string_release_ex(buf, 0);
306								}
307								goto clean_up;
308							}
309							plc->freeq = 1;
310					}
311
312					if (buf) {
313						zend_string_release_ex(buf, 0);
314					}
315				}
316			} else {
317				zval *parameter;
318				if (Z_ISREF(param->parameter)) {
319					parameter = Z_REFVAL(param->parameter);
320				} else {
321					parameter = &param->parameter;
322				}
323				plc->quoted = Z_STRVAL_P(parameter);
324				plc->qlen = Z_STRLEN_P(parameter);
325			}
326			newbuffer_len += plc->qlen;
327		}
328
329rewrite:
330		/* allocate output buffer */
331		newbuffer = emalloc(newbuffer_len + 1);
332		*outquery = newbuffer;
333
334		/* and build the query */
335		plc = placeholders;
336		ptr = inquery;
337
338		do {
339			t = plc->pos - ptr;
340			if (t) {
341				memcpy(newbuffer, ptr, t);
342				newbuffer += t;
343			}
344			if (plc->quoted) {
345				memcpy(newbuffer, plc->quoted, plc->qlen);
346				newbuffer += plc->qlen;
347			} else {
348				memcpy(newbuffer, plc->pos, plc->len);
349				newbuffer += plc->len;
350			}
351			ptr = plc->pos + plc->len;
352
353			plc = plc->next;
354		} while (plc);
355
356		t = (inquery + inquery_len) - ptr;
357		if (t) {
358			memcpy(newbuffer, ptr, t);
359			newbuffer += t;
360		}
361		*newbuffer = '\0';
362		*outquery_len = newbuffer - *outquery;
363
364		ret = 1;
365		goto clean_up;
366
367	} else if (query_type == PDO_PLACEHOLDER_POSITIONAL) {
368		/* rewrite ? to :pdoX */
369		char *name, *idxbuf;
370		const char *tmpl = stmt->named_rewrite_template ? stmt->named_rewrite_template : ":pdo%d";
371		int bind_no = 1;
372
373		newbuffer_len = inquery_len;
374
375		if (stmt->bound_param_map == NULL) {
376			ALLOC_HASHTABLE(stmt->bound_param_map);
377			zend_hash_init(stmt->bound_param_map, 13, NULL, free_param_name, 0);
378		}
379
380		for (plc = placeholders; plc; plc = plc->next) {
381			int skip_map = 0;
382			char *p;
383
384			if (plc->bindno == PDO_PARSER_BINDNO_ESCAPED_CHAR) {
385				continue;
386			}
387
388			name = estrndup(plc->pos, plc->len);
389
390			/* check if bound parameter is already available */
391			if (!strcmp(name, "?") || (p = zend_hash_str_find_ptr(stmt->bound_param_map, name, plc->len)) == NULL) {
392				spprintf(&idxbuf, 0, tmpl, bind_no++);
393			} else {
394				idxbuf = estrdup(p);
395				skip_map = 1;
396			}
397
398			plc->quoted = idxbuf;
399			plc->qlen = strlen(plc->quoted);
400			plc->freeq = 1;
401			newbuffer_len += plc->qlen;
402
403			if (!skip_map && stmt->named_rewrite_template) {
404				/* create a mapping */
405				zend_hash_str_update_mem(stmt->bound_param_map, name, plc->len, idxbuf, plc->qlen + 1);
406			}
407
408			/* map number to name */
409			zend_hash_index_update_mem(stmt->bound_param_map, plc->bindno, idxbuf, plc->qlen + 1);
410
411			efree(name);
412		}
413
414		goto rewrite;
415
416	} else {
417		/* rewrite :name to ? */
418
419		newbuffer_len = inquery_len;
420
421		if (stmt->bound_param_map == NULL) {
422			ALLOC_HASHTABLE(stmt->bound_param_map);
423			zend_hash_init(stmt->bound_param_map, 13, NULL, free_param_name, 0);
424		}
425
426		for (plc = placeholders; plc; plc = plc->next) {
427			char *name;
428			name = estrndup(plc->pos, plc->len);
429			zend_hash_index_update_mem(stmt->bound_param_map, plc->bindno, name, plc->len + 1);
430			efree(name);
431			plc->quoted = "?";
432			plc->qlen = 1;
433			newbuffer_len -= plc->len - 1;
434		}
435
436		goto rewrite;
437	}
438
439clean_up:
440
441	while (placeholders) {
442		plc = placeholders;
443		placeholders = plc->next;
444
445		if (plc->freeq) {
446			efree(plc->quoted);
447		}
448
449		efree(plc);
450	}
451
452	return ret;
453}
454