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   | https://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: Ard Biesheuvel <abies@php.net>                               |
14   +----------------------------------------------------------------------+
15 */
16 
17 #ifdef HAVE_CONFIG_H
18 #include "config.h"
19 #endif
20 
21 #ifndef _GNU_SOURCE
22 # define _GNU_SOURCE
23 #endif
24 
25 #include "php.h"
26 #include "zend_exceptions.h"
27 #include "php_ini.h"
28 #include "ext/standard/info.h"
29 #include "pdo/php_pdo.h"
30 #include "pdo/php_pdo_driver.h"
31 #include "php_pdo_firebird.h"
32 #include "php_pdo_firebird_int.h"
33 
34 static int firebird_alloc_prepare_stmt(pdo_dbh_t*, const zend_string*, XSQLDA*, isc_stmt_handle*,
35 	HashTable*);
36 
37 const char CHR_LETTER = 1;
38 const char CHR_DIGIT = 2;
39 const char CHR_IDENT = 4;
40 const char CHR_QUOTE = 8;
41 const char CHR_WHITE = 16;
42 const char CHR_HEX = 32;
43 const char CHR_INTRODUCER = 64;
44 
45 static const char classes_array[] = {
46 	/* 000     */ 0,
47 	/* 001     */ 0,
48 	/* 002     */ 0,
49 	/* 003     */ 0,
50 	/* 004     */ 0,
51 	/* 005     */ 0,
52 	/* 006     */ 0,
53 	/* 007     */ 0,
54 	/* 008     */ 0,
55 	/* 009     */ 16, /* CHR_WHITE */
56 	/* 010     */ 16, /* CHR_WHITE */
57 	/* 011     */ 0,
58 	/* 012     */ 0,
59 	/* 013     */ 16, /* CHR_WHITE */
60 	/* 014     */ 0,
61 	/* 015     */ 0,
62 	/* 016     */ 0,
63 	/* 017     */ 0,
64 	/* 018     */ 0,
65 	/* 019     */ 0,
66 	/* 020     */ 0,
67 	/* 021     */ 0,
68 	/* 022     */ 0,
69 	/* 023     */ 0,
70 	/* 024     */ 0,
71 	/* 025     */ 0,
72 	/* 026     */ 0,
73 	/* 027     */ 0,
74 	/* 028     */ 0,
75 	/* 029     */ 0,
76 	/* 030     */ 0,
77 	/* 031     */ 0,
78 	/* 032     */ 16, /* CHR_WHITE */
79 	/* 033  !  */ 0,
80 	/* 034  "  */ 8, /* CHR_QUOTE */
81 	/* 035  #  */ 0,
82 	/* 036  $  */ 4, /* CHR_IDENT */
83 	/* 037  %  */ 0,
84 	/* 038  &  */ 0,
85 	/* 039  '  */ 8, /* CHR_QUOTE */
86 	/* 040  (  */ 0,
87 	/* 041  )  */ 0,
88 	/* 042  *  */ 0,
89 	/* 043  +  */ 0,
90 	/* 044  ,  */ 0,
91 	/* 045  -  */ 0,
92 	/* 046  .  */ 0,
93 	/* 047  /  */ 0,
94 	/* 048  0  */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */
95 	/* 049  1  */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */
96 	/* 050  2  */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */
97 	/* 051  3  */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */
98 	/* 052  4  */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */
99 	/* 053  5  */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */
100 	/* 054  6  */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */
101 	/* 055  7  */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */
102 	/* 056  8  */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */
103 	/* 057  9  */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */
104 	/* 058  :  */ 0,
105 	/* 059  ;  */ 0,
106 	/* 060  <  */ 0,
107 	/* 061  =  */ 0,
108 	/* 062  >  */ 0,
109 	/* 063  ?  */ 0,
110 	/* 064  @  */ 0,
111 	/* 065  A  */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */
112 	/* 066  B  */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */
113 	/* 067  C  */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */
114 	/* 068  D  */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */
115 	/* 069  E  */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */
116 	/* 070  F  */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */
117 	/* 071  G  */ 5, /* CHR_LETTER | CHR_IDENT */
118 	/* 072  H  */ 5, /* CHR_LETTER | CHR_IDENT */
119 	/* 073  I  */ 5, /* CHR_LETTER | CHR_IDENT */
120 	/* 074  J  */ 5, /* CHR_LETTER | CHR_IDENT */
121 	/* 075  K  */ 5, /* CHR_LETTER | CHR_IDENT */
122 	/* 076  L  */ 5, /* CHR_LETTER | CHR_IDENT */
123 	/* 077  M  */ 5, /* CHR_LETTER | CHR_IDENT */
124 	/* 078  N  */ 5, /* CHR_LETTER | CHR_IDENT */
125 	/* 079  O  */ 5, /* CHR_LETTER | CHR_IDENT */
126 	/* 080  P  */ 5, /* CHR_LETTER | CHR_IDENT */
127 	/* 081  Q  */ 5, /* CHR_LETTER | CHR_IDENT */
128 	/* 082  R  */ 5, /* CHR_LETTER | CHR_IDENT */
129 	/* 083  S  */ 5, /* CHR_LETTER | CHR_IDENT */
130 	/* 084  T  */ 5, /* CHR_LETTER | CHR_IDENT */
131 	/* 085  U  */ 5, /* CHR_LETTER | CHR_IDENT */
132 	/* 086  V  */ 5, /* CHR_LETTER | CHR_IDENT */
133 	/* 087  W  */ 5, /* CHR_LETTER | CHR_IDENT */
134 	/* 088  X  */ 5, /* CHR_LETTER | CHR_IDENT */
135 	/* 089  Y  */ 5, /* CHR_LETTER | CHR_IDENT */
136 	/* 090  Z  */ 5, /* CHR_LETTER | CHR_IDENT */
137 	/* 091  [  */ 0,
138 	/* 092  \  */ 0,
139 	/* 093  ]  */ 0,
140 	/* 094  ^  */ 0,
141 	/* 095  _  */ 68, /* CHR_IDENT | CHR_INTRODUCER */
142 	/* 096  `  */ 0,
143 	/* 097  a  */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */
144 	/* 098  b  */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */
145 	/* 099  c  */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */
146 	/* 100  d  */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */
147 	/* 101  e  */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */
148 	/* 102  f  */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */
149 	/* 103  g  */ 5, /* CHR_LETTER | CHR_IDENT */
150 	/* 104  h  */ 5, /* CHR_LETTER | CHR_IDENT */
151 	/* 105  i  */ 5, /* CHR_LETTER | CHR_IDENT */
152 	/* 106  j  */ 5, /* CHR_LETTER | CHR_IDENT */
153 	/* 107  k  */ 5, /* CHR_LETTER | CHR_IDENT */
154 	/* 108  l  */ 5, /* CHR_LETTER | CHR_IDENT */
155 	/* 109  m  */ 5, /* CHR_LETTER | CHR_IDENT */
156 	/* 110  n  */ 5, /* CHR_LETTER | CHR_IDENT */
157 	/* 111  o  */ 5, /* CHR_LETTER | CHR_IDENT */
158 	/* 112  p  */ 5, /* CHR_LETTER | CHR_IDENT */
159 	/* 113  q  */ 5, /* CHR_LETTER | CHR_IDENT */
160 	/* 114  r  */ 5, /* CHR_LETTER | CHR_IDENT */
161 	/* 115  s  */ 5, /* CHR_LETTER | CHR_IDENT */
162 	/* 116  t  */ 5, /* CHR_LETTER | CHR_IDENT */
163 	/* 117  u  */ 5, /* CHR_LETTER | CHR_IDENT */
164 	/* 118  v  */ 5, /* CHR_LETTER | CHR_IDENT */
165 	/* 119  w  */ 5, /* CHR_LETTER | CHR_IDENT */
166 	/* 120  x  */ 5, /* CHR_LETTER | CHR_IDENT */
167 	/* 121  y  */ 5, /* CHR_LETTER | CHR_IDENT */
168 	/* 122  z  */ 5, /* CHR_LETTER | CHR_IDENT */
169 	/* 123  {  */ 5, /* CHR_LETTER | CHR_IDENT */
170 	/* 124  |  */ 0,
171 	/* 125  }  */ 5, /* CHR_LETTER | CHR_IDENT */
172 	/* 126  ~  */ 0,
173 	/* 127     */ 0
174 };
175 
classes(char idx)176 static inline char classes(char idx)
177 {
178 	unsigned char uidx = (unsigned char) idx;
179 	if (uidx > 127) return 0;
180 	return classes_array[uidx];
181 }
182 
183 typedef enum {
184 	ttNone,
185 	ttWhite,
186 	ttComment,
187 	ttBrokenComment,
188 	ttString,
189 	ttParamMark,
190 	ttIdent,
191 	ttOther
192 } FbTokenType;
193 
getToken(const char ** begin,const char * end)194 static FbTokenType getToken(const char** begin, const char* end)
195 {
196 	FbTokenType ret = ttNone;
197 	const char* p = *begin;
198 
199 	char c = *p++;
200 	switch (c)
201 	{
202 	case ':':
203 	case '?':
204 		ret = ttParamMark;
205 		break;
206 
207 	case '\'':
208 	case '"':
209 		while (p < end)
210 		{
211 			if (*p++ == c)
212 			{
213 				ret = ttString;
214 				break;
215 			}
216 		}
217 		break;
218 
219 	case '/':
220 		if (p < end && *p == '*')
221 		{
222 			ret = ttBrokenComment;
223 			p++;
224 			while (p < end)
225 			{
226 				if (*p++ == '*' && p < end && *p == '/')
227 				{
228 					p++;
229 					ret = ttComment;
230 					break;
231 				}
232 			}
233 		}
234 		else {
235 			ret = ttOther;
236 		}
237 		break;
238 
239 	case '-':
240 		if (p < end && *p == '-')
241 		{
242 			while (++p < end)
243 			{
244 				if (*p == '\r')
245 				{
246 					p++;
247 					if (p < end && *p == '\n')
248 						p++;
249 					break;
250 				}
251 				else if (*p == '\n')
252 					break;
253 			}
254 
255 			ret = ttComment;
256 		}
257 		else
258 			ret = ttOther;
259 		break;
260 
261 	default:
262 		if (classes(c) & CHR_DIGIT)
263 		{
264 			while (p < end && (classes(*p) & CHR_DIGIT))
265 				p++;
266 			ret = ttOther;
267 		}
268 		else if (classes(c) & CHR_IDENT)
269 		{
270 			while (p < end && (classes(*p) & CHR_IDENT))
271 				p++;
272 			ret = ttIdent;
273 		}
274 		else if (classes(c) & CHR_WHITE)
275 		{
276 			while (p < end && (classes(*p) & CHR_WHITE))
277 				p++;
278 			ret = ttWhite;
279 		}
280 		else
281 		{
282 			while (p < end && !(classes(*p) & (CHR_DIGIT | CHR_IDENT | CHR_WHITE)) &&
283 				(*p != '/') && (*p != '-') && (*p != ':') && (*p != '?') &&
284 				(*p != '\'') && (*p != '"'))
285 			{
286 				p++;
287 			}
288 			ret = ttOther;
289 		}
290 	}
291 
292 	*begin = p;
293 	return ret;
294 }
295 
preprocess(const zend_string * sql,char * sql_out,HashTable * named_params)296 int preprocess(const zend_string* sql, char* sql_out, HashTable* named_params)
297 {
298 	bool passAsIs = 1, execBlock = 0;
299 	zend_long pindex = -1;
300 	char pname[254], ident[253], ident2[253];
301 	unsigned int l;
302 	const char* p = ZSTR_VAL(sql), * end = ZSTR_VAL(sql) + ZSTR_LEN(sql);
303 	const char* start = p;
304 	FbTokenType tok = getToken(&p, end);
305 
306 	const char* i = start;
307 	while (p < end && (tok == ttComment || tok == ttWhite))
308 	{
309 		i = p;
310 		tok = getToken(&p, end);
311 	}
312 
313 	if (p >= end || tok != ttIdent)
314 	{
315 		/* Execute statement preprocess SQL error */
316 		/* Statement expected */
317 		return 0;
318 	}
319 	/* skip leading comments ?? */
320 	start = i;
321 	l = p - i;
322 	/* check the length of the identifier */
323 	/* in Firebird 4.0 it is 63 characters, in previous versions 31 bytes */
324 	if (l > 252) {
325 		return 0;
326 	}
327 	strncpy(ident, i, l);
328 	ident[l] = '\0';
329 	if (!strcasecmp(ident, "EXECUTE"))
330 	{
331 		/* For EXECUTE PROCEDURE and EXECUTE BLOCK statements, named parameters must be processed. */
332 		/* However, in EXECUTE BLOCK this is done in a special way. */
333 		const char* i2 = p;
334 		tok = getToken(&p, end);
335 		while (p < end && (tok == ttComment || tok == ttWhite))
336 		{
337 			i2 = p;
338 			tok = getToken(&p, end);
339 		}
340 		if (p >= end || tok != ttIdent)
341 		{
342 			/* Execute statement preprocess SQL error */
343 			/* Statement expected */
344 			return 0;
345 		}
346 		l = p - i2;
347 		/* check the length of the identifier */
348 		/* in Firebird 4.0 it is 63 characters, in previous versions 31 bytes */
349 		if (l > 252) {
350 			return 0;
351 		}
352 		strncpy(ident2, i2, l);
353 		ident2[l] = '\0';
354 		execBlock = !strcasecmp(ident2, "BLOCK");
355 		passAsIs = 0;
356 	}
357 	else
358 	{
359 		/* Named parameters must be processed in the INSERT, UPDATE, DELETE, MERGE statements. */
360 		/* If CTEs are present in the query, they begin with the WITH keyword. */
361 		passAsIs = strcasecmp(ident, "INSERT") && strcasecmp(ident, "UPDATE") &&
362 			strcasecmp(ident, "DELETE") && strcasecmp(ident, "MERGE") &&
363 			strcasecmp(ident, "SELECT") && strcasecmp(ident, "WITH");
364 	}
365 
366 	if (passAsIs)
367 	{
368 		strcpy(sql_out, ZSTR_VAL(sql));
369 		return 1;
370 	}
371 
372 	strncat(sql_out, start, p - start);
373 
374 	while (p < end)
375 	{
376 		start = p;
377 		tok = getToken(&p, end);
378 		switch (tok)
379 		{
380 		case ttParamMark:
381 			tok = getToken(&p, end);
382 			if (tok == ttIdent /*|| tok == ttString*/)
383 			{
384 				++pindex;
385 				l = p - start;
386 				/* check the length of the identifier */
387 				/* in Firebird 4.0 it is 63 characters, in previous versions 31 bytes */
388 				/* + symbol ":" */
389 				if (l > 253) {
390 					return 0;
391 				}
392 				strncpy(pname, start, l);
393 				pname[l] = '\0';
394 
395 				if (named_params) {
396 					zval tmp;
397 					ZVAL_LONG(&tmp, pindex);
398 					zend_hash_str_update(named_params, pname, l, &tmp);
399 				}
400 
401 				strcat(sql_out, "?");
402 			}
403 			else
404 			{
405 				if (strncmp(start, "?", 1)) {
406 					/* Execute statement preprocess SQL error */
407 					/* Parameter name expected */
408 					return 0;
409 				}
410 				++pindex;
411 				strncat(sql_out, start, p - start);
412 			}
413 			break;
414 
415 		case ttIdent:
416 			if (execBlock)
417 			{
418 				/* In the EXECUTE BLOCK statement, processing must be */
419 				/* carried out up to the keyword AS. */
420 				l = p - start;
421 				/* check the length of the identifier */
422 				/* in Firebird 4.0 it is 63 characters, in previous versions 31 bytes */
423 				if (l > 252) {
424 					return 0;
425 				}
426 				strncpy(ident, start, l);
427 				ident[l] = '\0';
428 				if (!strcasecmp(ident, "AS"))
429 				{
430 					strncat(sql_out, start, end - start);
431 					return 1;
432 				}
433 			}
434 			/* TODO Check this is correct? */
435 			ZEND_FALLTHROUGH;
436 
437 		case ttWhite:
438 		case ttComment:
439 		case ttString:
440 		case ttOther:
441 			strncat(sql_out, start, p - start);
442 			break;
443 
444 		case ttBrokenComment:
445 		{
446 			/* Execute statement preprocess SQL error */
447 			/* Unclosed comment found near ''@1'' */
448 			return 0;
449 		}
450 		break;
451 
452 
453 		case ttNone:
454 			/* Execute statement preprocess SQL error */
455 			return 0;
456 			break;
457 		}
458 	}
459 	return 1;
460 }
461 
462 /* map driver specific error message to PDO error */
_firebird_error(pdo_dbh_t * dbh,pdo_stmt_t * stmt,char const * file,zend_long line)463 void _firebird_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, char const *file, zend_long line) /* {{{ */
464 {
465 	pdo_error_type *const error_code = stmt ? &stmt->error_code : &dbh->error_code;
466 
467 	strcpy(*error_code, "HY000");
468 }
469 /* }}} */
470 
471 #define RECORD_ERROR(dbh) _firebird_error(dbh, NULL, __FILE__, __LINE__)
472 
473 /* called by PDO to close a db handle */
firebird_handle_closer(pdo_dbh_t * dbh)474 static void firebird_handle_closer(pdo_dbh_t *dbh) /* {{{ */
475 {
476 	pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data;
477 
478 	if (dbh->in_txn) {
479 		if (dbh->auto_commit) {
480 			if (isc_commit_transaction(H->isc_status, &H->tr)) {
481 				RECORD_ERROR(dbh);
482 			}
483 		} else {
484 			if (isc_rollback_transaction(H->isc_status, &H->tr)) {
485 				RECORD_ERROR(dbh);
486 			}
487 		}
488 	}
489 
490 	if (isc_detach_database(H->isc_status, &H->db)) {
491 		RECORD_ERROR(dbh);
492 	}
493 
494 	if (H->date_format) {
495 		efree(H->date_format);
496 	}
497 	if (H->time_format) {
498 		efree(H->time_format);
499 	}
500 	if (H->timestamp_format) {
501 		efree(H->timestamp_format);
502 	}
503 
504 	pefree(H, dbh->is_persistent);
505 }
506 /* }}} */
507 
508 /* called by PDO to prepare an SQL query */
firebird_handle_preparer(pdo_dbh_t * dbh,zend_string * sql,pdo_stmt_t * stmt,zval * driver_options)509 static bool firebird_handle_preparer(pdo_dbh_t *dbh, zend_string *sql, /* {{{ */
510 	pdo_stmt_t *stmt, zval *driver_options)
511 {
512 	pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data;
513 	pdo_firebird_stmt *S = NULL;
514 	HashTable *np;
515 
516 	do {
517 		isc_stmt_handle s = PDO_FIREBIRD_HANDLE_INITIALIZER;
518 		XSQLDA num_sqlda;
519 		static char const info[] = { isc_info_sql_stmt_type };
520 		char result[8];
521 
522 		num_sqlda.version = PDO_FB_SQLDA_VERSION;
523 		num_sqlda.sqln = 1;
524 
525 		ALLOC_HASHTABLE(np);
526 		zend_hash_init(np, 8, NULL, NULL, 0);
527 
528 		/* allocate and prepare statement */
529 		if (!firebird_alloc_prepare_stmt(dbh, sql, &num_sqlda, &s, np)) {
530 			break;
531 		}
532 
533 		/* allocate a statement handle struct of the right size (struct out_sqlda is inlined) */
534 		S = ecalloc(1, sizeof(*S)-sizeof(XSQLDA) + XSQLDA_LENGTH(num_sqlda.sqld));
535 		S->H = H;
536 		S->stmt = s;
537 		S->out_sqlda.version = PDO_FB_SQLDA_VERSION;
538 		S->out_sqlda.sqln = stmt->column_count = num_sqlda.sqld;
539 		S->named_params = np;
540 
541 		/* determine the statement type */
542 		if (isc_dsql_sql_info(H->isc_status, &s, sizeof(info), const_cast(info), sizeof(result),
543 				result)) {
544 			break;
545 		}
546 		S->statement_type = result[3];
547 
548 		/* fill the output sqlda with information about the prepared query */
549 		if (isc_dsql_describe(H->isc_status, &s, PDO_FB_SQLDA_VERSION, &S->out_sqlda)) {
550 			RECORD_ERROR(dbh);
551 			break;
552 		}
553 
554 		/* allocate the input descriptors */
555 		if (isc_dsql_describe_bind(H->isc_status, &s, PDO_FB_SQLDA_VERSION, &num_sqlda)) {
556 			break;
557 		}
558 
559 		if (num_sqlda.sqld) {
560 			S->in_sqlda = ecalloc(1,XSQLDA_LENGTH(num_sqlda.sqld));
561 			S->in_sqlda->version = PDO_FB_SQLDA_VERSION;
562 			S->in_sqlda->sqln = num_sqlda.sqld;
563 
564 			if (isc_dsql_describe_bind(H->isc_status, &s, PDO_FB_SQLDA_VERSION, S->in_sqlda)) {
565 				break;
566 			}
567 		}
568 
569 		stmt->driver_data = S;
570 		stmt->methods = &firebird_stmt_methods;
571 		stmt->supports_placeholders = PDO_PLACEHOLDER_POSITIONAL;
572 
573 		return true;
574 
575 	} while (0);
576 
577 	RECORD_ERROR(dbh);
578 
579 	zend_hash_destroy(np);
580 	FREE_HASHTABLE(np);
581 
582 	if (S) {
583 		if (S->in_sqlda) {
584 			efree(S->in_sqlda);
585 		}
586 		efree(S);
587 	}
588 
589 	return false;
590 }
591 /* }}} */
592 
593 /* called by PDO to execute a statement that doesn't produce a result set */
firebird_handle_doer(pdo_dbh_t * dbh,const zend_string * sql)594 static zend_long firebird_handle_doer(pdo_dbh_t *dbh, const zend_string *sql) /* {{{ */
595 {
596 	pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data;
597 	isc_stmt_handle stmt = PDO_FIREBIRD_HANDLE_INITIALIZER;
598 	static char const info_count[] = { isc_info_sql_records };
599 	char result[64];
600 	int ret = 0;
601 	XSQLDA in_sqlda, out_sqlda;
602 
603 	/* TODO no placeholders in exec() for now */
604 	in_sqlda.version = out_sqlda.version = PDO_FB_SQLDA_VERSION;
605 	in_sqlda.sqld = out_sqlda.sqld = 0;
606 	out_sqlda.sqln = 1;
607 
608 	/* allocate and prepare statement */
609 	if (!firebird_alloc_prepare_stmt(dbh, sql, &out_sqlda, &stmt, 0)) {
610 		return -1;
611 	}
612 
613 	/* execute the statement */
614 	if (isc_dsql_execute2(H->isc_status, &H->tr, &stmt, PDO_FB_SQLDA_VERSION, &in_sqlda, &out_sqlda)) {
615 		RECORD_ERROR(dbh);
616 		ret = -1;
617 		goto free_statement;
618 	}
619 
620 	/* find out how many rows were affected */
621 	if (isc_dsql_sql_info(H->isc_status, &stmt, sizeof(info_count), const_cast(info_count),
622 			sizeof(result),	result)) {
623 		RECORD_ERROR(dbh);
624 		ret = -1;
625 		goto free_statement;
626 	}
627 
628 	if (result[0] == isc_info_sql_records) {
629 		unsigned i = 3, result_size = isc_vax_integer(&result[1],2);
630 
631 		if (result_size > sizeof(result)) {
632 			ret = -1;
633 			goto free_statement;
634 		}
635 		while (result[i] != isc_info_end && i < result_size) {
636 			short len = (short)isc_vax_integer(&result[i+1],2);
637 			/* bail out on bad len */
638 			if (len != 1 && len != 2 && len != 4) {
639 				ret = -1;
640 				goto free_statement;
641 			}
642 			if (result[i] != isc_info_req_select_count) {
643 				ret += isc_vax_integer(&result[i+3],len);
644 			}
645 			i += len+3;
646 		}
647 	}
648 
649 	/* commit if we're in auto_commit mode */
650 	if (dbh->auto_commit && isc_commit_retaining(H->isc_status, &H->tr)) {
651 		RECORD_ERROR(dbh);
652 	}
653 
654 free_statement:
655 
656 	if (isc_dsql_free_statement(H->isc_status, &stmt, DSQL_drop)) {
657 		RECORD_ERROR(dbh);
658 	}
659 
660 	return ret;
661 }
662 /* }}} */
663 
664 /* called by the PDO SQL parser to add quotes to values that are copied into SQL */
firebird_handle_quoter(pdo_dbh_t * dbh,const zend_string * unquoted,enum pdo_param_type paramtype)665 static zend_string* firebird_handle_quoter(pdo_dbh_t *dbh, const zend_string *unquoted, enum pdo_param_type paramtype)
666 {
667 	int qcount = 0;
668 	char const *co, *l, *r;
669 	char *c;
670 	size_t quotedlen;
671 	zend_string *quoted_str;
672 
673 	if (ZSTR_LEN(unquoted) == 0) {
674 		return ZSTR_INIT_LITERAL("''", 0);
675 	}
676 
677 	/* Firebird only requires single quotes to be doubled if string lengths are used */
678 	/* count the number of ' characters */
679 	for (co = ZSTR_VAL(unquoted); (co = strchr(co,'\'')); qcount++, co++);
680 
681 	quotedlen = ZSTR_LEN(unquoted) + qcount + 2;
682 	quoted_str = zend_string_alloc(quotedlen, 0);
683 	c = ZSTR_VAL(quoted_str);
684 	*c++ = '\'';
685 
686 	/* foreach (chunk that ends in a quote) */
687 	for (l = ZSTR_VAL(unquoted); (r = strchr(l,'\'')); l = r+1) {
688 		strncpy(c, l, r-l+1);
689 		c += (r-l+1);
690 		/* add the second quote */
691 		*c++ = '\'';
692 	}
693 
694 	/* copy the remainder */
695 	strncpy(c, l, quotedlen-(c-ZSTR_VAL(quoted_str))-1);
696 	ZSTR_VAL(quoted_str)[quotedlen-1] = '\'';
697 	ZSTR_VAL(quoted_str)[quotedlen]   = '\0';
698 
699 	return quoted_str;
700 }
701 /* }}} */
702 
703 /* called by PDO to start a transaction */
firebird_handle_begin(pdo_dbh_t * dbh)704 static bool firebird_handle_begin(pdo_dbh_t *dbh) /* {{{ */
705 {
706 	pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data;
707 	char tpb[8] = { isc_tpb_version3 }, *ptpb = tpb+1;
708 #ifdef abies_0
709 	if (dbh->transaction_flags & PDO_TRANS_ISOLATION_LEVEL) {
710 		if (dbh->transaction_flags & PDO_TRANS_READ_UNCOMMITTED) {
711 			/* this is a poor fit, but it's all we have */
712 			*ptpb++ = isc_tpb_read_committed;
713 			*ptpb++ = isc_tpb_rec_version;
714 			dbh->transaction_flags &= ~(PDO_TRANS_ISOLATION_LEVEL^PDO_TRANS_READ_UNCOMMITTED);
715 		} else if (dbh->transaction_flags & PDO_TRANS_READ_COMMITTED) {
716 			*ptpb++ = isc_tpb_read_committed;
717 			*ptpb++ = isc_tpb_no_rec_version;
718 			dbh->transaction_flags &= ~(PDO_TRANS_ISOLATION_LEVEL^PDO_TRANS_READ_COMMITTED);
719 		} else if (dbh->transaction_flags & PDO_TRANS_REPEATABLE_READ) {
720 			*ptpb++ = isc_tpb_concurrency;
721 			dbh->transaction_flags &= ~(PDO_TRANS_ISOLATION_LEVEL^PDO_TRANS_REPEATABLE_READ);
722 		} else {
723 			*ptpb++ = isc_tpb_consistency;
724 			dbh->transaction_flags &= ~(PDO_TRANS_ISOLATION_LEVEL^PDO_TRANS_SERIALIZABLE);
725 		}
726 	}
727 
728 	if (dbh->transaction_flags & PDO_TRANS_ACCESS_MODE) {
729 		if (dbh->transaction_flags & PDO_TRANS_READONLY) {
730 			*ptpb++ = isc_tpb_read;
731 			dbh->transaction_flags &= ~(PDO_TRANS_ACCESS_MODE^PDO_TRANS_READONLY);
732 		} else {
733 			*ptpb++ = isc_tpb_write;
734 			dbh->transaction_flags &= ~(PDO_TRANS_ACCESS_MODE^PDO_TRANS_READWRITE);
735 		}
736 	}
737 
738 	if (dbh->transaction_flags & PDO_TRANS_CONFLICT_RESOLUTION) {
739 		if (dbh->transaction_flags & PDO_TRANS_RETRY) {
740 			*ptpb++ = isc_tpb_wait;
741 			dbh->transaction_flags &= ~(PDO_TRANS_CONFLICT_RESOLUTION^PDO_TRANS_RETRY);
742 		} else {
743 			*ptpb++ = isc_tpb_nowait;
744 			dbh->transaction_flags &= ~(PDO_TRANS_CONFLICT_RESOLUTION^PDO_TRANS_ABORT);
745 		}
746 	}
747 #endif
748 	if (isc_start_transaction(H->isc_status, &H->tr, 1, &H->db, (unsigned short)(ptpb-tpb), tpb)) {
749 		RECORD_ERROR(dbh);
750 		return false;
751 	}
752 	return true;
753 }
754 /* }}} */
755 
756 /* called by PDO to commit a transaction */
firebird_handle_commit(pdo_dbh_t * dbh)757 static bool firebird_handle_commit(pdo_dbh_t *dbh) /* {{{ */
758 {
759 	pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data;
760 
761 	if (isc_commit_transaction(H->isc_status, &H->tr)) {
762 		RECORD_ERROR(dbh);
763 		return false;
764 	}
765 	return true;
766 }
767 /* }}} */
768 
769 /* called by PDO to rollback a transaction */
firebird_handle_rollback(pdo_dbh_t * dbh)770 static bool firebird_handle_rollback(pdo_dbh_t *dbh) /* {{{ */
771 {
772 	pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data;
773 
774 	if (isc_rollback_transaction(H->isc_status, &H->tr)) {
775 		RECORD_ERROR(dbh);
776 		return false;
777 	}
778 	return true;
779 }
780 /* }}} */
781 
782 /* used by prepare and exec to allocate a statement handle and prepare the SQL */
firebird_alloc_prepare_stmt(pdo_dbh_t * dbh,const zend_string * sql,XSQLDA * out_sqlda,isc_stmt_handle * s,HashTable * named_params)783 static int firebird_alloc_prepare_stmt(pdo_dbh_t *dbh, const zend_string *sql,
784 	XSQLDA *out_sqlda, isc_stmt_handle *s, HashTable *named_params)
785 {
786 	pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data;
787 	char *new_sql;
788 
789 	/* Firebird allows SQL statements up to 64k, so bail if it doesn't fit */
790 	if (ZSTR_LEN(sql) > 65536) {
791 		strcpy(dbh->error_code, "01004");
792 		return 0;
793 	}
794 
795 	/* start a new transaction implicitly if auto_commit is enabled and no transaction is open */
796 	if (dbh->auto_commit && !dbh->in_txn) {
797 		/* dbh->transaction_flags = PDO_TRANS_READ_UNCOMMITTED; */
798 
799 		if (!firebird_handle_begin(dbh)) {
800 			return 0;
801 		}
802 		dbh->in_txn = true;
803 	}
804 
805 	/* allocate the statement */
806 	if (isc_dsql_allocate_statement(H->isc_status, &H->db, s)) {
807 		RECORD_ERROR(dbh);
808 		return 0;
809 	}
810 
811 	/* in order to support named params, which Firebird itself doesn't,
812 	   we need to replace :foo by ?, and store the name we just replaced */
813 	new_sql = emalloc(ZSTR_LEN(sql)+1);
814 	new_sql[0] = '\0';
815 	if (!preprocess(sql, new_sql, named_params)) {
816 		strcpy(dbh->error_code, "07000");
817 		efree(new_sql);
818 		return 0;
819 	}
820 
821 	/* prepare the statement */
822 	if (isc_dsql_prepare(H->isc_status, &H->tr, s, 0, new_sql, H->sql_dialect, out_sqlda)) {
823 		RECORD_ERROR(dbh);
824 		efree(new_sql);
825 		return 0;
826 	}
827 
828 	efree(new_sql);
829 	return 1;
830 }
831 
832 /* called by PDO to set a driver-specific dbh attribute */
firebird_handle_set_attribute(pdo_dbh_t * dbh,zend_long attr,zval * val)833 static bool firebird_handle_set_attribute(pdo_dbh_t *dbh, zend_long attr, zval *val) /* {{{ */
834 {
835 	pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data;
836 	bool bval;
837 
838 	switch (attr) {
839 		case PDO_ATTR_AUTOCOMMIT:
840 			{
841 				if (!pdo_get_bool_param(&bval, val)) {
842 					return false;
843 				}
844 
845 				/* ignore if the new value equals the old one */
846 				if (dbh->auto_commit ^ bval) {
847 					if (dbh->in_txn) {
848 						if (bval) {
849 							/* turning on auto_commit with an open transaction is illegal, because
850 							   we won't know what to do with it */
851 							H->last_app_error = "Cannot enable auto-commit while a transaction is already open";
852 							return false;
853 						} else {
854 							/* close the transaction */
855 							if (!firebird_handle_commit(dbh)) {
856 								break;
857 							}
858 							dbh->in_txn = false;
859 						}
860 					}
861 					dbh->auto_commit = bval;
862 				}
863 			}
864 			return true;
865 
866 		case PDO_ATTR_FETCH_TABLE_NAMES:
867 			if (!pdo_get_bool_param(&bval, val)) {
868 				return false;
869 			}
870 			H->fetch_table_names = bval;
871 			return true;
872 
873 		case PDO_FB_ATTR_DATE_FORMAT:
874 			{
875 				zend_string *str = zval_try_get_string(val);
876 				if (UNEXPECTED(!str)) {
877 					return false;
878 				}
879 				if (H->date_format) {
880 					efree(H->date_format);
881 				}
882 				spprintf(&H->date_format, 0, "%s", ZSTR_VAL(str));
883 				zend_string_release_ex(str, 0);
884 			}
885 			return true;
886 
887 		case PDO_FB_ATTR_TIME_FORMAT:
888 			{
889 				zend_string *str = zval_try_get_string(val);
890 				if (UNEXPECTED(!str)) {
891 					return false;
892 				}
893 				if (H->time_format) {
894 					efree(H->time_format);
895 				}
896 				spprintf(&H->time_format, 0, "%s", ZSTR_VAL(str));
897 				zend_string_release_ex(str, 0);
898 			}
899 			return true;
900 
901 		case PDO_FB_ATTR_TIMESTAMP_FORMAT:
902 			{
903 				zend_string *str = zval_try_get_string(val);
904 				if (UNEXPECTED(!str)) {
905 					return false;
906 				}
907 				if (H->timestamp_format) {
908 					efree(H->timestamp_format);
909 				}
910 				spprintf(&H->timestamp_format, 0, "%s", ZSTR_VAL(str));
911 				zend_string_release_ex(str, 0);
912 			}
913 			return true;
914 	}
915 	return false;
916 }
917 /* }}} */
918 
919 #define INFO_BUF_LEN 512
920 
921 /* callback to used to report database server info */
firebird_info_cb(void * arg,char const * s)922 static void firebird_info_cb(void *arg, char const *s) /* {{{ */
923 {
924 	if (arg) {
925 		if (*(char*)arg) { /* second call */
926 			strlcat(arg, " ", INFO_BUF_LEN);
927 		}
928 		strlcat(arg, s, INFO_BUF_LEN);
929 	}
930 }
931 /* }}} */
932 
933 /* called by PDO to get a driver-specific dbh attribute */
firebird_handle_get_attribute(pdo_dbh_t * dbh,zend_long attr,zval * val)934 static int firebird_handle_get_attribute(pdo_dbh_t *dbh, zend_long attr, zval *val) /* {{{ */
935 {
936 	pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data;
937 
938 	switch (attr) {
939 		char tmp[INFO_BUF_LEN];
940 
941 		case PDO_ATTR_AUTOCOMMIT:
942 			ZVAL_LONG(val,dbh->auto_commit);
943 			return 1;
944 
945 		case PDO_ATTR_CONNECTION_STATUS:
946 			ZVAL_BOOL(val, !isc_version(&H->db, firebird_info_cb, NULL));
947 			return 1;
948 
949 		case PDO_ATTR_CLIENT_VERSION: {
950 #if defined(__GNUC__) || defined(PHP_WIN32)
951 			info_func_t info_func = NULL;
952 #ifdef __GNUC__
953 			info_func = (info_func_t)dlsym(RTLD_DEFAULT, "isc_get_client_version");
954 #else
955 			HMODULE l = GetModuleHandle("fbclient");
956 
957 			if (!l) {
958 				break;
959 			}
960 			info_func = (info_func_t)GetProcAddress(l, "isc_get_client_version");
961 #endif
962 			if (info_func) {
963 				info_func(tmp);
964 				ZVAL_STRING(val, tmp);
965 			}
966 #else
967 			ZVAL_NULL(val);
968 #endif
969 			}
970 			return 1;
971 
972 		case PDO_ATTR_SERVER_VERSION:
973 		case PDO_ATTR_SERVER_INFO:
974 			*tmp = 0;
975 
976 			if (!isc_version(&H->db, firebird_info_cb, (void*)tmp)) {
977 				ZVAL_STRING(val, tmp);
978 				return 1;
979 			}
980 			/* TODO Check this is correct? */
981 			ZEND_FALLTHROUGH;
982 
983 		case PDO_ATTR_FETCH_TABLE_NAMES:
984 			ZVAL_BOOL(val, H->fetch_table_names);
985 			return 1;
986 	}
987 	return 0;
988 }
989 /* }}} */
990 
991 /* called by PDO to retrieve driver-specific information about an error that has occurred */
pdo_firebird_fetch_error_func(pdo_dbh_t * dbh,pdo_stmt_t * stmt,zval * info)992 static void pdo_firebird_fetch_error_func(pdo_dbh_t *dbh, pdo_stmt_t *stmt, zval *info) /* {{{ */
993 {
994 	pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data;
995 	const ISC_STATUS *s = H->isc_status;
996 	char buf[400];
997 	zend_long i = 0, l, sqlcode = isc_sqlcode(s);
998 
999 	if (sqlcode) {
1000 		add_next_index_long(info, sqlcode);
1001 
1002 		while ((sizeof(buf)>(i+2))&&(l = fb_interpret(&buf[i],(sizeof(buf)-i-2),&s))) {
1003 			i += l;
1004 			strcpy(&buf[i++], " ");
1005 		}
1006 		add_next_index_string(info, buf);
1007 	} else if (H->last_app_error) {
1008 		add_next_index_long(info, -999);
1009 		add_next_index_string(info, const_cast(H->last_app_error));
1010 	}
1011 }
1012 /* }}} */
1013 
1014 static const struct pdo_dbh_methods firebird_methods = { /* {{{ */
1015 	firebird_handle_closer,
1016 	firebird_handle_preparer,
1017 	firebird_handle_doer,
1018 	firebird_handle_quoter,
1019 	firebird_handle_begin,
1020 	firebird_handle_commit,
1021 	firebird_handle_rollback,
1022 	firebird_handle_set_attribute,
1023 	NULL, /* last_id not supported */
1024 	pdo_firebird_fetch_error_func,
1025 	firebird_handle_get_attribute,
1026 	NULL, /* check_liveness */
1027 	NULL, /* get driver methods */
1028 	NULL, /* request shutdown */
1029 	NULL, /* in transaction, use PDO's internal tracking mechanism */
1030 	NULL /* get gc */
1031 };
1032 /* }}} */
1033 
1034 /* the driver-specific PDO handle constructor */
pdo_firebird_handle_factory(pdo_dbh_t * dbh,zval * driver_options)1035 static int pdo_firebird_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{{ */
1036 {
1037 	struct pdo_data_src_parser vars[] = {
1038 		{ "dbname", NULL, 0 },
1039 		{ "charset",  NULL,	0 },
1040 		{ "role", NULL,	0 },
1041 		{ "dialect", "3", 0 },
1042 		{ "user", NULL, 0 },
1043 		{ "password", NULL, 0 }
1044 	};
1045 	int i, ret = 0;
1046 	short buf_len = 256, dpb_len;
1047 
1048 	pdo_firebird_db_handle *H = dbh->driver_data = pecalloc(1,sizeof(*H),dbh->is_persistent);
1049 
1050 	php_pdo_parse_data_source(dbh->data_source, dbh->data_source_len, vars, 6);
1051 
1052 	if (!dbh->username && vars[4].optval) {
1053 		dbh->username = pestrdup(vars[4].optval, dbh->is_persistent);
1054 	}
1055 
1056 	if (!dbh->password && vars[5].optval) {
1057 		dbh->password = pestrdup(vars[5].optval, dbh->is_persistent);
1058 	}
1059 
1060 	do {
1061 		static char const dpb_flags[] = {
1062 			isc_dpb_user_name, isc_dpb_password, isc_dpb_lc_ctype, isc_dpb_sql_role_name };
1063 		char const *dpb_values[] = { dbh->username, dbh->password, vars[1].optval, vars[2].optval };
1064 		char dpb_buffer[256] = { isc_dpb_version1 }, *dpb;
1065 
1066 		dpb = dpb_buffer + 1;
1067 
1068 		/* loop through all the provided arguments and set dpb fields accordingly */
1069 		for (i = 0; i < sizeof(dpb_flags); ++i) {
1070 			if (dpb_values[i] && buf_len > 0) {
1071 				dpb_len = slprintf(dpb, buf_len, "%c%c%s", dpb_flags[i], (unsigned char)strlen(dpb_values[i]),
1072 					dpb_values[i]);
1073 				dpb += dpb_len;
1074 				buf_len -= dpb_len;
1075 			}
1076 		}
1077 
1078 		H->sql_dialect = PDO_FB_DIALECT;
1079 		if (vars[3].optval) {
1080 			H->sql_dialect = atoi(vars[3].optval);
1081 		}
1082 
1083 		/* fire it up baby! */
1084 		if (isc_attach_database(H->isc_status, 0, vars[0].optval, &H->db,(short)(dpb-dpb_buffer), dpb_buffer)) {
1085 			break;
1086 		}
1087 
1088 		dbh->methods = &firebird_methods;
1089 		dbh->native_case = PDO_CASE_UPPER;
1090 		dbh->alloc_own_columns = 1;
1091 
1092 		ret = 1;
1093 
1094 	} while (0);
1095 
1096 	for (i = 0; i < sizeof(vars)/sizeof(vars[0]); ++i) {
1097 		if (vars[i].freeme) {
1098 			efree(vars[i].optval);
1099 		}
1100 	}
1101 
1102 	if (!dbh->methods) {
1103 		char errmsg[512];
1104 		const ISC_STATUS *s = H->isc_status;
1105 		fb_interpret(errmsg, sizeof(errmsg),&s);
1106 		zend_throw_exception_ex(php_pdo_get_exception(), H->isc_status[1], "SQLSTATE[%s] [%ld] %s",
1107 				"HY000", H->isc_status[1], errmsg);
1108 	}
1109 
1110 	if (!ret) {
1111 		firebird_handle_closer(dbh);
1112 	}
1113 
1114 	return ret;
1115 }
1116 /* }}} */
1117 
1118 
1119 const pdo_driver_t pdo_firebird_driver = { /* {{{ */
1120 	PDO_DRIVER_HEADER(firebird),
1121 	pdo_firebird_handle_factory
1122 };
1123 /* }}} */
1124