xref: /PHP-5.5/ext/pdo/pdo_sql_parser.re (revision 73c1be26)
1/*
2  +----------------------------------------------------------------------+
3  | PHP Version 5                                                        |
4  +----------------------------------------------------------------------+
5  | Copyright (c) 1997-2015 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	int len;
72	int bindno;
73	int qlen;		/* quoted length of value */
74	char *quoted;	/* quoted value */
75	int freeq;
76	struct placeholder *next;
77};
78
79PDO_API int pdo_parse_params(pdo_stmt_t *stmt, char *inquery, int inquery_len,
80	char **outquery, int *outquery_len TSRMLS_DC)
81{
82	Scanner s;
83	char *ptr, *newbuffer;
84	int t;
85	int bindno = 0;
86	int ret = 0;
87	int newbuffer_len;
88	HashTable *params;
89	struct pdo_bound_param_data *param;
90	int query_type = PDO_PLACEHOLDER_NONE;
91	struct placeholder *placeholders = NULL, *placetail = NULL, *plc = NULL;
92
93	ptr = *outquery;
94	s.cur = inquery;
95	s.end = inquery + inquery_len + 1;
96
97	/* phase 1: look for args */
98	while((t = scan(&s)) != PDO_PARSER_EOI) {
99		if (t == PDO_PARSER_BIND || t == PDO_PARSER_BIND_POS) {
100			if (t == PDO_PARSER_BIND) {
101				int len = s.cur - s.tok;
102				if ((inquery < (s.cur - len)) && isalnum(*(s.cur - len - 1))) {
103					continue;
104				}
105				query_type |= PDO_PLACEHOLDER_NAMED;
106			} else {
107				query_type |= PDO_PLACEHOLDER_POSITIONAL;
108			}
109
110			plc = emalloc(sizeof(*plc));
111			memset(plc, 0, sizeof(*plc));
112			plc->next = NULL;
113			plc->pos = s.tok;
114			plc->len = s.cur - s.tok;
115			plc->bindno = bindno++;
116
117			if (placetail) {
118				placetail->next = plc;
119			} else {
120				placeholders = plc;
121			}
122			placetail = plc;
123		}
124	}
125
126	if (bindno == 0) {
127		/* nothing to do; good! */
128		return 0;
129	}
130
131	/* did the query make sense to me? */
132	if (query_type == (PDO_PLACEHOLDER_NAMED|PDO_PLACEHOLDER_POSITIONAL)) {
133		/* they mixed both types; punt */
134		pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "mixed named and positional parameters" TSRMLS_CC);
135		ret = -1;
136		goto clean_up;
137	}
138
139	if (stmt->supports_placeholders == query_type && !stmt->named_rewrite_template) {
140		/* query matches native syntax */
141		ret = 0;
142		goto clean_up;
143	}
144
145	if (stmt->named_rewrite_template) {
146		/* magic/hack.
147		 * We we pretend that the query was positional even if
148		 * it was named so that we fall into the
149		 * named rewrite case below.  Not too pretty,
150		 * but it works. */
151		query_type = PDO_PLACEHOLDER_POSITIONAL;
152	}
153
154	params = stmt->bound_params;
155
156	/* Do we have placeholders but no bound params */
157	if (bindno && !params && stmt->supports_placeholders == PDO_PLACEHOLDER_NONE) {
158		pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "no parameters were bound" TSRMLS_CC);
159		ret = -1;
160		goto clean_up;
161	}
162
163	if (params && bindno != zend_hash_num_elements(params) && stmt->supports_placeholders == PDO_PLACEHOLDER_NONE) {
164		/* extra bit of validation for instances when same params are bound more then once */
165		if (query_type != PDO_PLACEHOLDER_POSITIONAL && bindno > zend_hash_num_elements(params)) {
166			int ok = 1;
167			for (plc = placeholders; plc; plc = plc->next) {
168				if (zend_hash_find(params, plc->pos, plc->len, (void**) &param) == FAILURE) {
169					ok = 0;
170					break;
171				}
172			}
173			if (ok) {
174				goto safe;
175			}
176		}
177		pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "number of bound variables does not match number of tokens" TSRMLS_CC);
178		ret = -1;
179		goto clean_up;
180	}
181safe:
182	/* what are we going to do ? */
183	if (stmt->supports_placeholders == PDO_PLACEHOLDER_NONE) {
184		/* query generation */
185
186		newbuffer_len = inquery_len;
187
188		/* let's quote all the values */
189		for (plc = placeholders; plc; plc = plc->next) {
190			if (query_type == PDO_PLACEHOLDER_POSITIONAL) {
191				ret = zend_hash_index_find(params, plc->bindno, (void**) &param);
192			} else {
193				ret = zend_hash_find(params, plc->pos, plc->len, (void**) &param);
194			}
195			if (ret == FAILURE) {
196				/* parameter was not defined */
197				ret = -1;
198				pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "parameter was not defined" TSRMLS_CC);
199				goto clean_up;
200			}
201			if (stmt->dbh->methods->quoter) {
202				if (param->param_type == PDO_PARAM_LOB && Z_TYPE_P(param->parameter) == IS_RESOURCE) {
203					php_stream *stm;
204
205					php_stream_from_zval_no_verify(stm, &param->parameter);
206					if (stm) {
207						size_t len;
208						char *buf = NULL;
209
210						len = php_stream_copy_to_mem(stm, &buf, PHP_STREAM_COPY_ALL, 0);
211						if (!stmt->dbh->methods->quoter(stmt->dbh, buf, len, &plc->quoted, &plc->qlen,
212								param->param_type TSRMLS_CC)) {
213							/* bork */
214							ret = -1;
215							strncpy(stmt->error_code, stmt->dbh->error_code, 6);
216							if (buf) {
217								efree(buf);
218							}
219							goto clean_up;
220						}
221						if (buf) {
222							efree(buf);
223						}
224					} else {
225						pdo_raise_impl_error(stmt->dbh, stmt, "HY105", "Expected a stream resource" TSRMLS_CC);
226						ret = -1;
227						goto clean_up;
228					}
229					plc->freeq = 1;
230				} else {
231					zval tmp_param = *param->parameter;
232					zval_copy_ctor(&tmp_param);
233					switch (Z_TYPE(tmp_param)) {
234						case IS_NULL:
235							plc->quoted = "NULL";
236							plc->qlen = sizeof("NULL")-1;
237							plc->freeq = 0;
238							break;
239
240						case IS_BOOL:
241							convert_to_long(&tmp_param);
242							/* fall through */
243						case IS_LONG:
244						case IS_DOUBLE:
245							convert_to_string(&tmp_param);
246							plc->qlen = Z_STRLEN(tmp_param);
247							plc->quoted = estrdup(Z_STRVAL(tmp_param));
248							plc->freeq = 1;
249							break;
250
251						default:
252							convert_to_string(&tmp_param);
253							if (!stmt->dbh->methods->quoter(stmt->dbh, Z_STRVAL(tmp_param),
254									Z_STRLEN(tmp_param), &plc->quoted, &plc->qlen,
255									param->param_type TSRMLS_CC)) {
256								/* bork */
257								ret = -1;
258								strncpy(stmt->error_code, stmt->dbh->error_code, 6);
259								goto clean_up;
260							}
261							plc->freeq = 1;
262					}
263					zval_dtor(&tmp_param);
264				}
265			} else {
266				plc->quoted = Z_STRVAL_P(param->parameter);
267				plc->qlen = Z_STRLEN_P(param->parameter);
268			}
269			newbuffer_len += plc->qlen;
270		}
271
272rewrite:
273		/* allocate output buffer */
274		newbuffer = emalloc(newbuffer_len + 1);
275		*outquery = newbuffer;
276
277		/* and build the query */
278		plc = placeholders;
279		ptr = inquery;
280
281		do {
282			t = plc->pos - ptr;
283			if (t) {
284				memcpy(newbuffer, ptr, t);
285				newbuffer += t;
286			}
287			memcpy(newbuffer, plc->quoted, plc->qlen);
288			newbuffer += plc->qlen;
289			ptr = plc->pos + plc->len;
290
291			plc = plc->next;
292		} while (plc);
293
294		t = (inquery + inquery_len) - ptr;
295		if (t) {
296			memcpy(newbuffer, ptr, t);
297			newbuffer += t;
298		}
299		*newbuffer = '\0';
300		*outquery_len = newbuffer - *outquery;
301
302		ret = 1;
303		goto clean_up;
304
305	} else if (query_type == PDO_PLACEHOLDER_POSITIONAL) {
306		/* rewrite ? to :pdoX */
307		char *name, *idxbuf;
308		const char *tmpl = stmt->named_rewrite_template ? stmt->named_rewrite_template : ":pdo%d";
309		int bind_no = 1;
310
311		newbuffer_len = inquery_len;
312
313		if (stmt->bound_param_map == NULL) {
314			ALLOC_HASHTABLE(stmt->bound_param_map);
315			zend_hash_init(stmt->bound_param_map, 13, NULL, NULL, 0);
316		}
317
318		for (plc = placeholders; plc; plc = plc->next) {
319			int skip_map = 0;
320			char *p;
321			name = estrndup(plc->pos, plc->len);
322
323			/* check if bound parameter is already available */
324			if (!strcmp(name, "?") || zend_hash_find(stmt->bound_param_map, name, plc->len + 1, (void**) &p) == FAILURE) {
325				spprintf(&idxbuf, 0, tmpl, bind_no++);
326			} else {
327				idxbuf = estrdup(p);
328				skip_map = 1;
329			}
330
331			plc->quoted = idxbuf;
332			plc->qlen = strlen(plc->quoted);
333			plc->freeq = 1;
334			newbuffer_len += plc->qlen;
335
336			if (!skip_map && stmt->named_rewrite_template) {
337				/* create a mapping */
338				zend_hash_update(stmt->bound_param_map, name, plc->len + 1, idxbuf, plc->qlen + 1, NULL);
339			}
340
341			/* map number to name */
342			zend_hash_index_update(stmt->bound_param_map, plc->bindno, idxbuf, plc->qlen + 1, NULL);
343
344			efree(name);
345		}
346
347		goto rewrite;
348
349	} else {
350		/* rewrite :name to ? */
351
352		newbuffer_len = inquery_len;
353
354		if (stmt->bound_param_map == NULL) {
355			ALLOC_HASHTABLE(stmt->bound_param_map);
356			zend_hash_init(stmt->bound_param_map, 13, NULL, NULL, 0);
357		}
358
359		for (plc = placeholders; plc; plc = plc->next) {
360			char *name;
361
362			name = estrndup(plc->pos, plc->len);
363			zend_hash_index_update(stmt->bound_param_map, plc->bindno, name, plc->len + 1, NULL);
364			efree(name);
365			plc->quoted = "?";
366			plc->qlen = 1;
367		}
368
369		goto rewrite;
370	}
371
372clean_up:
373
374	while (placeholders) {
375		plc = placeholders;
376		placeholders = plc->next;
377
378		if (plc->freeq) {
379			efree(plc->quoted);
380		}
381
382		efree(plc);
383	}
384
385	return ret;
386}
387
388#if 0
389int old_pdo_parse_params(pdo_stmt_t *stmt, char *inquery, int inquery_len, char **outquery,
390		int *outquery_len TSRMLS_DC)
391{
392	Scanner s;
393	char *ptr;
394	int t;
395	int bindno = 0;
396	int newbuffer_len;
397	int padding;
398	HashTable *params = stmt->bound_params;
399	struct pdo_bound_param_data *param;
400	/* allocate buffer for query with expanded binds, ptr is our writing pointer */
401	newbuffer_len = inquery_len;
402
403	/* calculate the possible padding factor due to quoting */
404	if(stmt->dbh->max_escaped_char_length) {
405		padding = stmt->dbh->max_escaped_char_length;
406	} else {
407		padding = 3;
408	}
409	if(params) {
410		zend_hash_internal_pointer_reset(params);
411		while (SUCCESS == zend_hash_get_current_data(params, (void**)&param)) {
412			if(param->parameter) {
413				convert_to_string(param->parameter);
414				/* accommodate a string that needs to be fully quoted
415                   bind placeholders are at least 2 characters, so
416                   the accommodate their own "'s
417                */
418				newbuffer_len += padding * Z_STRLEN_P(param->parameter);
419			}
420			zend_hash_move_forward(params);
421		}
422	}
423	*outquery = (char *) emalloc(newbuffer_len + 1);
424	*outquery_len = 0;
425
426	ptr = *outquery;
427	s.cur = inquery;
428	while((t = scan(&s)) != PDO_PARSER_EOI) {
429		if(t == PDO_PARSER_TEXT) {
430			memcpy(ptr, s.tok, s.cur - s.tok);
431			ptr += (s.cur - s.tok);
432			*outquery_len += (s.cur - s.tok);
433		}
434		else if(t == PDO_PARSER_BIND) {
435			if(!params) {
436				/* error */
437				efree(*outquery);
438				*outquery = NULL;
439				return (int) (s.cur - inquery);
440			}
441			/* lookup bind first via hash and then index */
442			/* stupid keys need to be null-terminated, even though we know their length */
443			if((SUCCESS == zend_hash_find(params, s.tok, s.cur-s.tok,(void **)&param))
444			    ||
445			   (SUCCESS == zend_hash_index_find(params, bindno, (void **)&param)))
446			{
447				char *quotedstr;
448				int quotedstrlen;
449				/* restore the in-string key, doesn't need null-termination here */
450				/* currently everything is a string here */
451
452				/* quote the bind value if necessary */
453				if(stmt->dbh->methods->quoter(stmt->dbh, Z_STRVAL_P(param->parameter),
454					Z_STRLEN_P(param->parameter), &quotedstr, &quotedstrlen TSRMLS_CC))
455				{
456					memcpy(ptr, quotedstr, quotedstrlen);
457					ptr += quotedstrlen;
458					*outquery_len += quotedstrlen;
459					efree(quotedstr);
460				} else {
461					memcpy(ptr, Z_STRVAL_P(param->parameter), Z_STRLEN_P(param->parameter));
462					ptr += Z_STRLEN_P(param->parameter);
463					*outquery_len += (Z_STRLEN_P(param->parameter));
464				}
465			}
466			else {
467				/* error and cleanup */
468				efree(*outquery);
469				*outquery = NULL;
470				return (int) (s.cur - inquery);
471			}
472			bindno++;
473		}
474		else if(t == PDO_PARSER_BIND_POS) {
475			if(!params) {
476				/* error */
477				efree(*outquery);
478				*outquery = NULL;
479				return (int) (s.cur - inquery);
480			}
481			/* lookup bind by index */
482			if(SUCCESS == zend_hash_index_find(params, bindno, (void **)&param))
483			{
484				char *quotedstr;
485				int quotedstrlen;
486				/* currently everything is a string here */
487
488				/* quote the bind value if necessary */
489				if(stmt->dbh->methods->quoter(stmt->dbh, Z_STRVAL_P(param->parameter),
490					Z_STRLEN_P(param->parameter), &quotedstr, &quotedstrlen TSRMLS_CC))
491				{
492					memcpy(ptr, quotedstr, quotedstrlen);
493					ptr += quotedstrlen;
494					*outquery_len += quotedstrlen;
495					efree(quotedstr);
496				} else {
497					memcpy(ptr, Z_STRVAL_P(param->parameter), Z_STRLEN_P(param->parameter));
498					ptr += Z_STRLEN_P(param->parameter);
499					*outquery_len += (Z_STRLEN_P(param->parameter));
500				}
501			}
502			else {
503				/* error and cleanup */
504				efree(*outquery);
505				*outquery = NULL;
506				return (int) (s.cur - inquery);
507			}
508			bindno++;
509		}
510	}
511	*ptr = '\0';
512	return 0;
513}
514#endif
515
516/*
517 * Local variables:
518 * tab-width: 4
519 * c-basic-offset: 4
520 * End:
521 * vim600: noet sw=4 ts=4 fdm=marker ft=c
522 * vim<600: noet sw=4 ts=4
523 */
524