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