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