xref: /PHP-7.1/ext/pdo/pdo_sql_parser.re (revision ccd4716e)
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					zval tmp_param;
244				   	ZVAL_DUP(&tmp_param, parameter);
245					switch (Z_TYPE(tmp_param)) {
246						case IS_NULL:
247							plc->quoted = "NULL";
248							plc->qlen = sizeof("NULL")-1;
249							plc->freeq = 0;
250							break;
251
252						case IS_FALSE:
253						case IS_TRUE:
254							convert_to_long(&tmp_param);
255							/* fall through */
256						case IS_LONG:
257						case IS_DOUBLE:
258							convert_to_string(&tmp_param);
259							plc->qlen = Z_STRLEN(tmp_param);
260							plc->quoted = estrdup(Z_STRVAL(tmp_param));
261							plc->freeq = 1;
262							break;
263
264						default:
265							convert_to_string(&tmp_param);
266							if (!stmt->dbh->methods->quoter(stmt->dbh, Z_STRVAL(tmp_param),
267									Z_STRLEN(tmp_param), &plc->quoted, &plc->qlen,
268									param->param_type)) {
269								/* bork */
270								ret = -1;
271								strncpy(stmt->error_code, stmt->dbh->error_code, 6);
272								goto clean_up;
273							}
274							plc->freeq = 1;
275					}
276					zval_dtor(&tmp_param);
277				}
278			} else {
279				zval *parameter;
280				if (Z_ISREF(param->parameter)) {
281					parameter = Z_REFVAL(param->parameter);
282				} else {
283					parameter = &param->parameter;
284				}
285				plc->quoted = Z_STRVAL_P(parameter);
286				plc->qlen = Z_STRLEN_P(parameter);
287			}
288			newbuffer_len += plc->qlen;
289		}
290
291rewrite:
292		/* allocate output buffer */
293		newbuffer = emalloc(newbuffer_len + 1);
294		*outquery = newbuffer;
295
296		/* and build the query */
297		plc = placeholders;
298		ptr = inquery;
299
300		do {
301			t = plc->pos - ptr;
302			if (t) {
303				memcpy(newbuffer, ptr, t);
304				newbuffer += t;
305			}
306			memcpy(newbuffer, plc->quoted, plc->qlen);
307			newbuffer += plc->qlen;
308			ptr = plc->pos + plc->len;
309
310			plc = plc->next;
311		} while (plc);
312
313		t = (inquery + inquery_len) - ptr;
314		if (t) {
315			memcpy(newbuffer, ptr, t);
316			newbuffer += t;
317		}
318		*newbuffer = '\0';
319		*outquery_len = newbuffer - *outquery;
320
321		ret = 1;
322		goto clean_up;
323
324	} else if (query_type == PDO_PLACEHOLDER_POSITIONAL) {
325		/* rewrite ? to :pdoX */
326		char *name, *idxbuf;
327		const char *tmpl = stmt->named_rewrite_template ? stmt->named_rewrite_template : ":pdo%d";
328		int bind_no = 1;
329
330		newbuffer_len = inquery_len;
331
332		if (stmt->bound_param_map == NULL) {
333			ALLOC_HASHTABLE(stmt->bound_param_map);
334			zend_hash_init(stmt->bound_param_map, 13, NULL, free_param_name, 0);
335		}
336
337		for (plc = placeholders; plc; plc = plc->next) {
338			int skip_map = 0;
339			char *p;
340			name = estrndup(plc->pos, plc->len);
341
342			/* check if bound parameter is already available */
343			if (!strcmp(name, "?") || (p = zend_hash_str_find_ptr(stmt->bound_param_map, name, plc->len)) == NULL) {
344				spprintf(&idxbuf, 0, tmpl, bind_no++);
345			} else {
346				idxbuf = estrdup(p);
347				skip_map = 1;
348			}
349
350			plc->quoted = idxbuf;
351			plc->qlen = strlen(plc->quoted);
352			plc->freeq = 1;
353			newbuffer_len += plc->qlen;
354
355			if (!skip_map && stmt->named_rewrite_template) {
356				/* create a mapping */
357				zend_hash_str_update_mem(stmt->bound_param_map, name, plc->len, idxbuf, plc->qlen + 1);
358			}
359
360			/* map number to name */
361			zend_hash_index_update_mem(stmt->bound_param_map, plc->bindno, idxbuf, plc->qlen + 1);
362
363			efree(name);
364		}
365
366		goto rewrite;
367
368	} else {
369		/* rewrite :name to ? */
370
371		newbuffer_len = inquery_len;
372
373		if (stmt->bound_param_map == NULL) {
374			ALLOC_HASHTABLE(stmt->bound_param_map);
375			zend_hash_init(stmt->bound_param_map, 13, NULL, free_param_name, 0);
376		}
377
378		for (plc = placeholders; plc; plc = plc->next) {
379			char *name;
380			name = estrndup(plc->pos, plc->len);
381			zend_hash_index_update_mem(stmt->bound_param_map, plc->bindno, name, plc->len + 1);
382			efree(name);
383			plc->quoted = "?";
384			plc->qlen = 1;
385		}
386
387		goto rewrite;
388	}
389
390clean_up:
391
392	while (placeholders) {
393		plc = placeholders;
394		placeholders = plc->next;
395
396		if (plc->freeq) {
397			efree(plc->quoted);
398		}
399
400		efree(plc);
401	}
402
403	return ret;
404}
405
406#if 0
407int old_pdo_parse_params(pdo_stmt_t *stmt, char *inquery, int inquery_len, char **outquery,
408		int *outquery_len)
409{
410	Scanner s;
411	char *ptr;
412	int t;
413	int bindno = 0;
414	int newbuffer_len;
415	int padding;
416	HashTable *params = stmt->bound_params;
417	struct pdo_bound_param_data *param;
418	/* allocate buffer for query with expanded binds, ptr is our writing pointer */
419	newbuffer_len = inquery_len;
420
421	/* calculate the possible padding factor due to quoting */
422	if(stmt->dbh->max_escaped_char_length) {
423		padding = stmt->dbh->max_escaped_char_length;
424	} else {
425		padding = 3;
426	}
427	if(params) {
428		ZEND_HASH_FOREACH_PTR(params, param) {
429			if(param->parameter) {
430				convert_to_string(param->parameter);
431				/* accommodate a string that needs to be fully quoted
432                   bind placeholders are at least 2 characters, so
433                   the accommodate their own "'s
434                */
435				newbuffer_len += padding * Z_STRLEN_P(param->parameter);
436			}
437		} ZEND_HASH_FOREACH_END();
438	}
439	*outquery = (char *) emalloc(newbuffer_len + 1);
440	*outquery_len = 0;
441
442	ptr = *outquery;
443	s.cur = inquery;
444	while((t = scan(&s)) != PDO_PARSER_EOI) {
445		if(t == PDO_PARSER_TEXT) {
446			memcpy(ptr, s.tok, s.cur - s.tok);
447			ptr += (s.cur - s.tok);
448			*outquery_len += (s.cur - s.tok);
449		}
450		else if(t == PDO_PARSER_BIND) {
451			if(!params) {
452				/* error */
453				efree(*outquery);
454				*outquery = NULL;
455				return (int) (s.cur - inquery);
456			}
457			/* lookup bind first via hash and then index */
458			/* stupid keys need to be null-terminated, even though we know their length */
459			if((NULL != (param = zend_hash_str_find_ptr(params, s.tok, s.cur-s.tok))
460			    ||
461			   NULL != (params = zend_hash_index_find_ptr(params, bindno)))
462			{
463				char *quotedstr;
464				int quotedstrlen;
465				/* restore the in-string key, doesn't need null-termination here */
466				/* currently everything is a string here */
467
468				/* quote the bind value if necessary */
469				if(stmt->dbh->methods->quoter(stmt->dbh, Z_STRVAL_P(param->parameter),
470					Z_STRLEN_P(param->parameter), &quotedstr, &quotedstrlen))
471				{
472					memcpy(ptr, quotedstr, quotedstrlen);
473					ptr += quotedstrlen;
474					*outquery_len += quotedstrlen;
475					efree(quotedstr);
476				} else {
477					memcpy(ptr, Z_STRVAL_P(param->parameter), Z_STRLEN_P(param->parameter));
478					ptr += Z_STRLEN_P(param->parameter);
479					*outquery_len += (Z_STRLEN_P(param->parameter));
480				}
481			}
482			else {
483				/* error and cleanup */
484				efree(*outquery);
485				*outquery = NULL;
486				return (int) (s.cur - inquery);
487			}
488			bindno++;
489		}
490		else if(t == PDO_PARSER_BIND_POS) {
491			if(!params) {
492				/* error */
493				efree(*outquery);
494				*outquery = NULL;
495				return (int) (s.cur - inquery);
496			}
497			/* lookup bind by index */
498			if(NULL != (params = zend_hash_index_find_ptr(params, bindno)))
499			{
500				char *quotedstr;
501				int quotedstrlen;
502				/* currently everything is a string here */
503
504				/* quote the bind value if necessary */
505				if(stmt->dbh->methods->quoter(stmt->dbh, Z_STRVAL_P(param->parameter),
506					Z_STRLEN_P(param->parameter), &quotedstr, &quotedstrlen))
507				{
508					memcpy(ptr, quotedstr, quotedstrlen);
509					ptr += quotedstrlen;
510					*outquery_len += quotedstrlen;
511					efree(quotedstr);
512				} else {
513					memcpy(ptr, Z_STRVAL_P(param->parameter), Z_STRLEN_P(param->parameter));
514					ptr += Z_STRLEN_P(param->parameter);
515					*outquery_len += (Z_STRLEN_P(param->parameter));
516				}
517			}
518			else {
519				/* error and cleanup */
520				efree(*outquery);
521				*outquery = NULL;
522				return (int) (s.cur - inquery);
523			}
524			bindno++;
525		}
526	}
527	*ptr = '\0';
528	return 0;
529}
530#endif
531
532/*
533 * Local variables:
534 * tab-width: 4
535 * c-basic-offset: 4
536 * End:
537 * vim600: noet sw=4 ts=4 fdm=marker ft=c
538 * vim<600: noet sw=4 ts=4
539 */
540