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 php_firebird_alloc_prepare_stmt(pdo_dbh_t*, const zend_string*, XSQLDA*, isc_stmt_handle*,
35 	HashTable*);
36 static bool php_firebird_rollback_transaction(pdo_dbh_t *dbh);
37 
38 const char CHR_LETTER = 1;
39 const char CHR_DIGIT = 2;
40 const char CHR_IDENT = 4;
41 const char CHR_QUOTE = 8;
42 const char CHR_WHITE = 16;
43 const char CHR_HEX = 32;
44 const char CHR_INTRODUCER = 64;
45 
46 static const char classes_array[] = {
47 	/* 000     */ 0,
48 	/* 001     */ 0,
49 	/* 002     */ 0,
50 	/* 003     */ 0,
51 	/* 004     */ 0,
52 	/* 005     */ 0,
53 	/* 006     */ 0,
54 	/* 007     */ 0,
55 	/* 008     */ 0,
56 	/* 009     */ 16, /* CHR_WHITE */
57 	/* 010     */ 16, /* CHR_WHITE */
58 	/* 011     */ 0,
59 	/* 012     */ 0,
60 	/* 013     */ 16, /* CHR_WHITE */
61 	/* 014     */ 0,
62 	/* 015     */ 0,
63 	/* 016     */ 0,
64 	/* 017     */ 0,
65 	/* 018     */ 0,
66 	/* 019     */ 0,
67 	/* 020     */ 0,
68 	/* 021     */ 0,
69 	/* 022     */ 0,
70 	/* 023     */ 0,
71 	/* 024     */ 0,
72 	/* 025     */ 0,
73 	/* 026     */ 0,
74 	/* 027     */ 0,
75 	/* 028     */ 0,
76 	/* 029     */ 0,
77 	/* 030     */ 0,
78 	/* 031     */ 0,
79 	/* 032     */ 16, /* CHR_WHITE */
80 	/* 033  !  */ 0,
81 	/* 034  "  */ 8, /* CHR_QUOTE */
82 	/* 035  #  */ 0,
83 	/* 036  $  */ 4, /* CHR_IDENT */
84 	/* 037  %  */ 0,
85 	/* 038  &  */ 0,
86 	/* 039  '  */ 8, /* CHR_QUOTE */
87 	/* 040  (  */ 0,
88 	/* 041  )  */ 0,
89 	/* 042  *  */ 0,
90 	/* 043  +  */ 0,
91 	/* 044  ,  */ 0,
92 	/* 045  -  */ 0,
93 	/* 046  .  */ 0,
94 	/* 047  /  */ 0,
95 	/* 048  0  */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */
96 	/* 049  1  */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */
97 	/* 050  2  */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */
98 	/* 051  3  */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */
99 	/* 052  4  */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */
100 	/* 053  5  */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */
101 	/* 054  6  */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */
102 	/* 055  7  */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */
103 	/* 056  8  */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */
104 	/* 057  9  */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */
105 	/* 058  :  */ 0,
106 	/* 059  ;  */ 0,
107 	/* 060  <  */ 0,
108 	/* 061  =  */ 0,
109 	/* 062  >  */ 0,
110 	/* 063  ?  */ 0,
111 	/* 064  @  */ 0,
112 	/* 065  A  */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */
113 	/* 066  B  */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */
114 	/* 067  C  */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */
115 	/* 068  D  */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */
116 	/* 069  E  */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */
117 	/* 070  F  */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */
118 	/* 071  G  */ 5, /* CHR_LETTER | CHR_IDENT */
119 	/* 072  H  */ 5, /* CHR_LETTER | CHR_IDENT */
120 	/* 073  I  */ 5, /* CHR_LETTER | CHR_IDENT */
121 	/* 074  J  */ 5, /* CHR_LETTER | CHR_IDENT */
122 	/* 075  K  */ 5, /* CHR_LETTER | CHR_IDENT */
123 	/* 076  L  */ 5, /* CHR_LETTER | CHR_IDENT */
124 	/* 077  M  */ 5, /* CHR_LETTER | CHR_IDENT */
125 	/* 078  N  */ 5, /* CHR_LETTER | CHR_IDENT */
126 	/* 079  O  */ 5, /* CHR_LETTER | CHR_IDENT */
127 	/* 080  P  */ 5, /* CHR_LETTER | CHR_IDENT */
128 	/* 081  Q  */ 5, /* CHR_LETTER | CHR_IDENT */
129 	/* 082  R  */ 5, /* CHR_LETTER | CHR_IDENT */
130 	/* 083  S  */ 5, /* CHR_LETTER | CHR_IDENT */
131 	/* 084  T  */ 5, /* CHR_LETTER | CHR_IDENT */
132 	/* 085  U  */ 5, /* CHR_LETTER | CHR_IDENT */
133 	/* 086  V  */ 5, /* CHR_LETTER | CHR_IDENT */
134 	/* 087  W  */ 5, /* CHR_LETTER | CHR_IDENT */
135 	/* 088  X  */ 5, /* CHR_LETTER | CHR_IDENT */
136 	/* 089  Y  */ 5, /* CHR_LETTER | CHR_IDENT */
137 	/* 090  Z  */ 5, /* CHR_LETTER | CHR_IDENT */
138 	/* 091  [  */ 0,
139 	/* 092  \  */ 0,
140 	/* 093  ]  */ 0,
141 	/* 094  ^  */ 0,
142 	/* 095  _  */ 68, /* CHR_IDENT | CHR_INTRODUCER */
143 	/* 096  `  */ 0,
144 	/* 097  a  */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */
145 	/* 098  b  */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */
146 	/* 099  c  */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */
147 	/* 100  d  */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */
148 	/* 101  e  */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */
149 	/* 102  f  */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */
150 	/* 103  g  */ 5, /* CHR_LETTER | CHR_IDENT */
151 	/* 104  h  */ 5, /* CHR_LETTER | CHR_IDENT */
152 	/* 105  i  */ 5, /* CHR_LETTER | CHR_IDENT */
153 	/* 106  j  */ 5, /* CHR_LETTER | CHR_IDENT */
154 	/* 107  k  */ 5, /* CHR_LETTER | CHR_IDENT */
155 	/* 108  l  */ 5, /* CHR_LETTER | CHR_IDENT */
156 	/* 109  m  */ 5, /* CHR_LETTER | CHR_IDENT */
157 	/* 110  n  */ 5, /* CHR_LETTER | CHR_IDENT */
158 	/* 111  o  */ 5, /* CHR_LETTER | CHR_IDENT */
159 	/* 112  p  */ 5, /* CHR_LETTER | CHR_IDENT */
160 	/* 113  q  */ 5, /* CHR_LETTER | CHR_IDENT */
161 	/* 114  r  */ 5, /* CHR_LETTER | CHR_IDENT */
162 	/* 115  s  */ 5, /* CHR_LETTER | CHR_IDENT */
163 	/* 116  t  */ 5, /* CHR_LETTER | CHR_IDENT */
164 	/* 117  u  */ 5, /* CHR_LETTER | CHR_IDENT */
165 	/* 118  v  */ 5, /* CHR_LETTER | CHR_IDENT */
166 	/* 119  w  */ 5, /* CHR_LETTER | CHR_IDENT */
167 	/* 120  x  */ 5, /* CHR_LETTER | CHR_IDENT */
168 	/* 121  y  */ 5, /* CHR_LETTER | CHR_IDENT */
169 	/* 122  z  */ 5, /* CHR_LETTER | CHR_IDENT */
170 	/* 123  {  */ 5, /* CHR_LETTER | CHR_IDENT */
171 	/* 124  |  */ 0,
172 	/* 125  }  */ 5, /* CHR_LETTER | CHR_IDENT */
173 	/* 126  ~  */ 0,
174 	/* 127     */ 0
175 };
176 
php_firebird_classes(char idx)177 static inline char php_firebird_classes(char idx)
178 {
179 	unsigned char uidx = (unsigned char) idx;
180 	if (uidx > 127) return 0;
181 	return classes_array[uidx];
182 }
183 
184 typedef enum {
185 	ttNone,
186 	ttWhite,
187 	ttComment,
188 	ttBrokenComment,
189 	ttString,
190 	ttParamMark,
191 	ttIdent,
192 	ttOther
193 } FbTokenType;
194 
php_firebird_get_token(const char ** begin,const char * end)195 static FbTokenType php_firebird_get_token(const char** begin, const char* end)
196 {
197 	FbTokenType ret = ttNone;
198 	const char* p = *begin;
199 
200 	char c = *p++;
201 	switch (c)
202 	{
203 	case ':':
204 	case '?':
205 		ret = ttParamMark;
206 		break;
207 
208 	case '\'':
209 	case '"':
210 		while (p < end)
211 		{
212 			if (*p++ == c)
213 			{
214 				ret = ttString;
215 				break;
216 			}
217 		}
218 		break;
219 
220 	case '/':
221 		if (p < end && *p == '*')
222 		{
223 			ret = ttBrokenComment;
224 			p++;
225 			while (p < end)
226 			{
227 				if (*p++ == '*' && p < end && *p == '/')
228 				{
229 					p++;
230 					ret = ttComment;
231 					break;
232 				}
233 			}
234 		}
235 		else {
236 			ret = ttOther;
237 		}
238 		break;
239 
240 	case '-':
241 		if (p < end && *p == '-')
242 		{
243 			while (++p < end)
244 			{
245 				if (*p == '\r')
246 				{
247 					p++;
248 					if (p < end && *p == '\n')
249 						p++;
250 					break;
251 				}
252 				else if (*p == '\n')
253 					break;
254 			}
255 
256 			ret = ttComment;
257 		}
258 		else
259 			ret = ttOther;
260 		break;
261 
262 	default:
263 		if (php_firebird_classes(c) & CHR_DIGIT)
264 		{
265 			while (p < end && (php_firebird_classes(*p) & CHR_DIGIT))
266 				p++;
267 			ret = ttOther;
268 		}
269 		else if (php_firebird_classes(c) & CHR_IDENT)
270 		{
271 			while (p < end && (php_firebird_classes(*p) & CHR_IDENT))
272 				p++;
273 			ret = ttIdent;
274 		}
275 		else if (php_firebird_classes(c) & CHR_WHITE)
276 		{
277 			while (p < end && (php_firebird_classes(*p) & CHR_WHITE))
278 				p++;
279 			ret = ttWhite;
280 		}
281 		else
282 		{
283 			while (p < end && !(php_firebird_classes(*p) & (CHR_DIGIT | CHR_IDENT | CHR_WHITE)) &&
284 				(*p != '/') && (*p != '-') && (*p != ':') && (*p != '?') &&
285 				(*p != '\'') && (*p != '"'))
286 			{
287 				p++;
288 			}
289 			ret = ttOther;
290 		}
291 	}
292 
293 	*begin = p;
294 	return ret;
295 }
296 
php_firebird_preprocess(const zend_string * sql,char * sql_out,HashTable * named_params)297 static int php_firebird_preprocess(const zend_string* sql, char* sql_out, HashTable* named_params)
298 {
299 	bool passAsIs = 1, execBlock = 0;
300 	zend_long pindex = -1;
301 	char pname[254], ident[253], ident2[253];
302 	unsigned int l;
303 	const char* p = ZSTR_VAL(sql), * end = ZSTR_VAL(sql) + ZSTR_LEN(sql);
304 	const char* start = p;
305 	FbTokenType tok = php_firebird_get_token(&p, end);
306 
307 	const char* i = start;
308 	while (p < end && (tok == ttComment || tok == ttWhite))
309 	{
310 		i = p;
311 		tok = php_firebird_get_token(&p, end);
312 	}
313 
314 	if (p >= end || tok != ttIdent)
315 	{
316 		/* Execute statement preprocess SQL error */
317 		/* Statement expected */
318 		return 0;
319 	}
320 	/* skip leading comments ?? */
321 	start = i;
322 	l = p - i;
323 	/* check the length of the identifier */
324 	/* in Firebird 4.0 it is 63 characters, in previous versions 31 bytes */
325 	if (l > 252) {
326 		return 0;
327 	}
328 	strncpy(ident, i, l);
329 	ident[l] = '\0';
330 	if (!strcasecmp(ident, "EXECUTE"))
331 	{
332 		/* For EXECUTE PROCEDURE and EXECUTE BLOCK statements, named parameters must be processed. */
333 		/* However, in EXECUTE BLOCK this is done in a special way. */
334 		const char* i2 = p;
335 		tok = php_firebird_get_token(&p, end);
336 		while (p < end && (tok == ttComment || tok == ttWhite))
337 		{
338 			i2 = p;
339 			tok = php_firebird_get_token(&p, end);
340 		}
341 		if (p >= end || tok != ttIdent)
342 		{
343 			/* Execute statement preprocess SQL error */
344 			/* Statement expected */
345 			return 0;
346 		}
347 		l = p - i2;
348 		/* check the length of the identifier */
349 		/* in Firebird 4.0 it is 63 characters, in previous versions 31 bytes */
350 		if (l > 252) {
351 			return 0;
352 		}
353 		strncpy(ident2, i2, l);
354 		ident2[l] = '\0';
355 		execBlock = !strcasecmp(ident2, "BLOCK");
356 		passAsIs = 0;
357 	}
358 	else
359 	{
360 		/* Named parameters must be processed in the INSERT, UPDATE, DELETE, MERGE statements. */
361 		/* If CTEs are present in the query, they begin with the WITH keyword. */
362 		passAsIs = strcasecmp(ident, "INSERT") && strcasecmp(ident, "UPDATE") &&
363 			strcasecmp(ident, "DELETE") && strcasecmp(ident, "MERGE") &&
364 			strcasecmp(ident, "SELECT") && strcasecmp(ident, "WITH");
365 	}
366 
367 	if (passAsIs)
368 	{
369 		strcpy(sql_out, ZSTR_VAL(sql));
370 		return 1;
371 	}
372 
373 	strncat(sql_out, start, p - start);
374 
375 	while (p < end)
376 	{
377 		start = p;
378 		tok = php_firebird_get_token(&p, end);
379 		switch (tok)
380 		{
381 		case ttParamMark:
382 			tok = php_firebird_get_token(&p, end);
383 			if (tok == ttIdent /*|| tok == ttString*/)
384 			{
385 				++pindex;
386 				l = p - start;
387 				/* check the length of the identifier */
388 				/* in Firebird 4.0 it is 63 characters, in previous versions 31 bytes */
389 				/* + symbol ":" */
390 				if (l > 253) {
391 					return 0;
392 				}
393 				strncpy(pname, start, l);
394 				pname[l] = '\0';
395 
396 				if (named_params) {
397 					zval tmp;
398 					ZVAL_LONG(&tmp, pindex);
399 					zend_hash_str_update(named_params, pname, l, &tmp);
400 				}
401 
402 				strcat(sql_out, "?");
403 			}
404 			else
405 			{
406 				if (strncmp(start, "?", 1)) {
407 					/* Execute statement preprocess SQL error */
408 					/* Parameter name expected */
409 					return 0;
410 				}
411 				++pindex;
412 				strncat(sql_out, start, p - start);
413 			}
414 			break;
415 
416 		case ttIdent:
417 			if (execBlock)
418 			{
419 				/* In the EXECUTE BLOCK statement, processing must be */
420 				/* carried out up to the keyword AS. */
421 				l = p - start;
422 				/* check the length of the identifier */
423 				/* in Firebird 4.0 it is 63 characters, in previous versions 31 bytes */
424 				if (l > 252) {
425 					return 0;
426 				}
427 				strncpy(ident, start, l);
428 				ident[l] = '\0';
429 				if (!strcasecmp(ident, "AS"))
430 				{
431 					strncat(sql_out, start, end - start);
432 					return 1;
433 				}
434 			}
435 			/* TODO Check this is correct? */
436 			ZEND_FALLTHROUGH;
437 
438 		case ttWhite:
439 		case ttComment:
440 		case ttString:
441 		case ttOther:
442 			strncat(sql_out, start, p - start);
443 			break;
444 
445 		case ttBrokenComment:
446 		{
447 			/* Execute statement preprocess SQL error */
448 			/* Unclosed comment found near ''@1'' */
449 			return 0;
450 		}
451 		break;
452 
453 
454 		case ttNone:
455 			/* Execute statement preprocess SQL error */
456 			return 0;
457 			break;
458 		}
459 	}
460 	return 1;
461 }
462 
463 /* map driver specific error message to PDO error */
php_firebird_set_error(pdo_dbh_t * dbh,pdo_stmt_t * stmt,const char * state,const size_t state_len,const char * msg,const size_t msg_len)464 void php_firebird_set_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, const char *state, const size_t state_len,
465 	const char *msg, const size_t msg_len) /* {{{ */
466 {
467 	pdo_error_type *const error_code = stmt ? &stmt->error_code : &dbh->error_code;
468 	pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data;
469 	pdo_firebird_error_info *einfo = &H->einfo;
470 	int sqlcode = -999;
471 
472 	if (einfo->errmsg) {
473 		pefree(einfo->errmsg, dbh->is_persistent);
474 		einfo->errmsg = NULL;
475 		einfo->errmsg_length = 0;
476 	}
477 
478 	if (H->isc_status && (H->isc_status[0] == 1 && H->isc_status[1] > 0)) {
479 		char buf[512];
480 		size_t buf_size = sizeof(buf), read_len = 0;
481 		ssize_t tmp_len;
482 		const ISC_STATUS *s = H->isc_status;
483 		sqlcode = isc_sqlcode(s);
484 
485 		while ((buf_size > (read_len + 1)) && (tmp_len = fb_interpret(&buf[read_len], (buf_size - read_len - 1), &s)) && tmp_len > 0) {
486 			read_len += tmp_len;
487 			buf[read_len++] = ' ';
488 		}
489 
490 		/* remove last space */
491 		if (read_len) {
492 			buf[read_len--] = '\0';
493 		}
494 
495 		einfo->errmsg_length = read_len;
496 		einfo->errmsg = pestrndup(buf, read_len, dbh->is_persistent);
497 
498 #if FB_API_VER >= 25
499 		char sqlstate[sizeof(pdo_error_type)];
500 		fb_sqlstate(sqlstate, H->isc_status);
501 		if (sqlstate != NULL && strlen(sqlstate) < sizeof(pdo_error_type)) {
502 			strcpy(*error_code, sqlstate);
503 			goto end;
504 		}
505 #endif
506 	} else if (msg && msg_len) {
507 		einfo->errmsg_length = msg_len;
508 		einfo->errmsg = pestrndup(msg, einfo->errmsg_length, dbh->is_persistent);
509 	}
510 
511 	if (state && state_len && state_len < sizeof(pdo_error_type)) {
512 		memcpy(*error_code, state, state_len + 1);
513 	} else {
514 		memcpy(*error_code, "HY000", sizeof("HY000"));
515 	}
516 
517 end:
518 	einfo->sqlcode = sqlcode;
519 	if (!dbh->methods) {
520 		pdo_throw_exception(0, einfo->errmsg, error_code);
521 	}
522 }
523 /* }}} */
524 
525 /* called by PDO to close a db handle */
firebird_handle_closer(pdo_dbh_t * dbh)526 static void firebird_handle_closer(pdo_dbh_t *dbh) /* {{{ */
527 {
528 	pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data;
529 
530 	if (H->tr) {
531 		if (dbh->auto_commit) {
532 			php_firebird_commit_transaction(dbh, /* retain */ false);
533 		} else {
534 			php_firebird_rollback_transaction(dbh);
535 		}
536 	}
537 	H->in_manually_txn = 0;
538 
539 	if (isc_detach_database(H->isc_status, &H->db)) {
540 		php_firebird_error(dbh);
541 	}
542 
543 	if (H->date_format) {
544 		efree(H->date_format);
545 	}
546 	if (H->time_format) {
547 		efree(H->time_format);
548 	}
549 	if (H->timestamp_format) {
550 		efree(H->timestamp_format);
551 	}
552 
553 	if (H->einfo.errmsg) {
554 		pefree(H->einfo.errmsg, dbh->is_persistent);
555 		H->einfo.errmsg = NULL;
556 	}
557 
558 	pefree(H, dbh->is_persistent);
559 }
560 /* }}} */
561 
562 /* 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)563 static bool firebird_handle_preparer(pdo_dbh_t *dbh, zend_string *sql, /* {{{ */
564 	pdo_stmt_t *stmt, zval *driver_options)
565 {
566 	pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data;
567 	pdo_firebird_stmt *S = NULL;
568 	HashTable *np;
569 
570 	do {
571 		isc_stmt_handle s = PDO_FIREBIRD_HANDLE_INITIALIZER;
572 		XSQLDA num_sqlda;
573 		static char const info[] = { isc_info_sql_stmt_type };
574 		char result[8];
575 
576 		num_sqlda.version = PDO_FB_SQLDA_VERSION;
577 		num_sqlda.sqln = 1;
578 
579 		ALLOC_HASHTABLE(np);
580 		zend_hash_init(np, 8, NULL, NULL, 0);
581 
582 		/* allocate and prepare statement */
583 		if (!php_firebird_alloc_prepare_stmt(dbh, sql, &num_sqlda, &s, np)) {
584 			break;
585 		}
586 
587 		/* allocate a statement handle struct of the right size (struct out_sqlda is inlined) */
588 		S = ecalloc(1, sizeof(*S)-sizeof(XSQLDA) + XSQLDA_LENGTH(num_sqlda.sqld));
589 		S->H = H;
590 		S->stmt = s;
591 		S->out_sqlda.version = PDO_FB_SQLDA_VERSION;
592 		S->out_sqlda.sqln = stmt->column_count = num_sqlda.sqld;
593 		S->named_params = np;
594 
595 		/* determine the statement type */
596 		if (isc_dsql_sql_info(H->isc_status, &s, sizeof(info), const_cast(info), sizeof(result),
597 				result)) {
598 			break;
599 		}
600 		S->statement_type = result[3];
601 
602 		/* fill the output sqlda with information about the prepared query */
603 		if (isc_dsql_describe(H->isc_status, &s, PDO_FB_SQLDA_VERSION, &S->out_sqlda)) {
604 			php_firebird_error(dbh);
605 			break;
606 		}
607 
608 		/* allocate the input descriptors */
609 		if (isc_dsql_describe_bind(H->isc_status, &s, PDO_FB_SQLDA_VERSION, &num_sqlda)) {
610 			break;
611 		}
612 
613 		if (num_sqlda.sqld) {
614 			S->in_sqlda = ecalloc(1,XSQLDA_LENGTH(num_sqlda.sqld));
615 			S->in_sqlda->version = PDO_FB_SQLDA_VERSION;
616 			S->in_sqlda->sqln = num_sqlda.sqld;
617 
618 			if (isc_dsql_describe_bind(H->isc_status, &s, PDO_FB_SQLDA_VERSION, S->in_sqlda)) {
619 				break;
620 			}
621 		}
622 
623 		stmt->driver_data = S;
624 		stmt->methods = &firebird_stmt_methods;
625 		stmt->supports_placeholders = PDO_PLACEHOLDER_POSITIONAL;
626 
627 		return true;
628 
629 	} while (0);
630 
631 	php_firebird_error(dbh);
632 
633 	zend_hash_destroy(np);
634 	FREE_HASHTABLE(np);
635 
636 	if (S) {
637 		if (S->in_sqlda) {
638 			efree(S->in_sqlda);
639 		}
640 		efree(S);
641 	}
642 
643 	return false;
644 }
645 /* }}} */
646 
647 /* 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)648 static zend_long firebird_handle_doer(pdo_dbh_t *dbh, const zend_string *sql) /* {{{ */
649 {
650 	pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data;
651 	isc_stmt_handle stmt = PDO_FIREBIRD_HANDLE_INITIALIZER;
652 	static char const info_count[] = { isc_info_sql_records };
653 	char result[64];
654 	int ret = 0;
655 	XSQLDA in_sqlda, out_sqlda;
656 
657 	/* TODO no placeholders in exec() for now */
658 	in_sqlda.version = out_sqlda.version = PDO_FB_SQLDA_VERSION;
659 	in_sqlda.sqld = out_sqlda.sqld = 0;
660 	out_sqlda.sqln = 1;
661 
662 	/* allocate and prepare statement */
663 	if (!php_firebird_alloc_prepare_stmt(dbh, sql, &out_sqlda, &stmt, 0)) {
664 		return -1;
665 	}
666 
667 	/* execute the statement */
668 	if (isc_dsql_execute2(H->isc_status, &H->tr, &stmt, PDO_FB_SQLDA_VERSION, &in_sqlda, &out_sqlda)) {
669 		php_firebird_error(dbh);
670 		ret = -1;
671 		goto free_statement;
672 	}
673 
674 	/* find out how many rows were affected */
675 	if (isc_dsql_sql_info(H->isc_status, &stmt, sizeof(info_count), const_cast(info_count),
676 			sizeof(result),	result)) {
677 		php_firebird_error(dbh);
678 		ret = -1;
679 		goto free_statement;
680 	}
681 
682 	if (result[0] == isc_info_sql_records) {
683 		unsigned i = 3, result_size = isc_vax_integer(&result[1],2);
684 
685 		if (result_size > sizeof(result)) {
686 			ret = -1;
687 			goto free_statement;
688 		}
689 		while (result[i] != isc_info_end && i < result_size) {
690 			short len = (short)isc_vax_integer(&result[i+1],2);
691 			/* bail out on bad len */
692 			if (len != 1 && len != 2 && len != 4) {
693 				ret = -1;
694 				goto free_statement;
695 			}
696 			if (result[i] != isc_info_req_select_count) {
697 				ret += isc_vax_integer(&result[i+3],len);
698 			}
699 			i += len+3;
700 		}
701 	}
702 
703 	if (dbh->auto_commit && !H->in_manually_txn) {
704 		if (!php_firebird_commit_transaction(dbh, /* retain */ true)) {
705 			ret = -1;
706 		}
707 	}
708 
709 free_statement:
710 
711 	if (isc_dsql_free_statement(H->isc_status, &stmt, DSQL_drop)) {
712 		php_firebird_error(dbh);
713 	}
714 
715 	return ret;
716 }
717 /* }}} */
718 
719 /* 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)720 static zend_string* firebird_handle_quoter(pdo_dbh_t *dbh, const zend_string *unquoted, enum pdo_param_type paramtype)
721 {
722 	int qcount = 0;
723 	char const *co, *l, *r;
724 	char *c;
725 	size_t quotedlen;
726 	zend_string *quoted_str;
727 
728 	if (ZSTR_LEN(unquoted) == 0) {
729 		return ZSTR_INIT_LITERAL("''", 0);
730 	}
731 
732 	/* Firebird only requires single quotes to be doubled if string lengths are used */
733 	/* count the number of ' characters */
734 	for (co = ZSTR_VAL(unquoted); (co = strchr(co,'\'')); qcount++, co++);
735 
736 	quotedlen = ZSTR_LEN(unquoted) + qcount + 2;
737 	quoted_str = zend_string_alloc(quotedlen, 0);
738 	c = ZSTR_VAL(quoted_str);
739 	*c++ = '\'';
740 
741 	/* foreach (chunk that ends in a quote) */
742 	for (l = ZSTR_VAL(unquoted); (r = strchr(l,'\'')); l = r+1) {
743 		strncpy(c, l, r-l+1);
744 		c += (r-l+1);
745 		/* add the second quote */
746 		*c++ = '\'';
747 	}
748 
749 	/* copy the remainder */
750 	strncpy(c, l, quotedlen-(c-ZSTR_VAL(quoted_str))-1);
751 	ZSTR_VAL(quoted_str)[quotedlen-1] = '\'';
752 	ZSTR_VAL(quoted_str)[quotedlen]   = '\0';
753 
754 	return quoted_str;
755 }
756 /* }}} */
757 
758 /* php_firebird_begin_transaction */
php_firebird_begin_transaction(pdo_dbh_t * dbh,bool is_auto_commit_txn)759 static bool php_firebird_begin_transaction(pdo_dbh_t *dbh, bool is_auto_commit_txn) /* {{{ */
760 {
761 	pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data;
762 
763 	/* isc_xxx are all 1 byte. */
764 	char tpb[4] = { isc_tpb_version3 };
765 	size_t tpb_size;
766 
767 	/* access mode. writable or readonly */
768 	tpb[1] = H->is_writable_txn ? isc_tpb_write : isc_tpb_read;
769 
770 	if (is_auto_commit_txn) {
771 		/*
772 		 * In autocommit mode, we need to always read the latest information, so we set `read committed`.
773 		 */
774 		tpb[2] = isc_tpb_read_committed;
775 		/* Ignore indeterminate data from other transactions. This option only required with `read committed`. */
776 		tpb[3] = isc_tpb_rec_version;
777 		tpb_size = 4;
778 	} else {
779 		switch (H->txn_isolation_level) {
780 			/*
781 			* firebird's `read committed` has the option to wait until other transactions
782 			* commit or rollback if there is indeterminate data.
783 			* Introducing too many configuration values at once can cause confusion, so
784 			* we don't support in PDO that feature yet.
785 			*/
786 			case PDO_FB_READ_COMMITTED:
787 				tpb[2] = isc_tpb_read_committed;
788 				/* Ignore indeterminate data from other transactions. This option only required with `read committed`. */
789 				tpb[3] = isc_tpb_rec_version;
790 				tpb_size = 4;
791 				break;
792 
793 			case PDO_FB_SERIALIZABLE:
794 				tpb[2] = isc_tpb_consistency;
795 				tpb_size = 3;
796 				break;
797 
798 			case PDO_FB_REPEATABLE_READ:
799 			default:
800 				tpb[2] = isc_tpb_concurrency;
801 				tpb_size = 3;
802 				break;
803 		}
804 	}
805 
806 	if (isc_start_transaction(H->isc_status, &H->tr, 1, &H->db, tpb_size, tpb)) {
807 		php_firebird_error(dbh);
808 		return false;
809 	}
810 	return true;
811 }
812 /* }}} */
813 
814 /* called by PDO to start a transaction */
firebird_handle_manually_begin(pdo_dbh_t * dbh)815 static bool firebird_handle_manually_begin(pdo_dbh_t *dbh) /* {{{ */
816 {
817 	pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data;
818 
819 	/**
820 	 * If in autocommit mode and in transaction, we will need to close the transaction once.
821 	 */
822 	if (dbh->auto_commit && H->tr) {
823 		if (!php_firebird_commit_transaction(dbh, /* retain */ false)) {
824 			return false;
825 		}
826 	}
827 
828 	if (!php_firebird_begin_transaction(dbh, /* auto commit mode */ false)) {
829 		return false;
830 	}
831 	H->in_manually_txn = 1;
832 	return true;
833 }
834 /* }}} */
835 
836 /* php_firebird_commit_transaction */
php_firebird_commit_transaction(pdo_dbh_t * dbh,bool retain)837 bool php_firebird_commit_transaction(pdo_dbh_t *dbh, bool retain) /* {{{ */
838 {
839 	pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data;
840 
841 	/**
842 	 * `retaining` keeps the transaction open without closing it.
843 	 *
844 	 * firebird needs to always have a transaction open to emulate autocommit mode,
845 	 * and in autocommit mode it keeps the transaction open.
846 	 *
847 	 * Same as close and then begin again, but use retain to save overhead.
848 	 */
849 	if (retain) {
850 		if (isc_commit_retaining(H->isc_status, &H->tr)) {
851 			php_firebird_error(dbh);
852 			return false;
853 		}
854 	} else {
855 		if (isc_commit_transaction(H->isc_status, &H->tr)) {
856 			php_firebird_error(dbh);
857 			return false;
858 		}
859 	}
860 	return true;
861 }
862 /* }}} */
863 
864 /* called by PDO to commit a transaction */
firebird_handle_manually_commit(pdo_dbh_t * dbh)865 static bool firebird_handle_manually_commit(pdo_dbh_t *dbh) /* {{{ */
866 {
867 	pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data;
868 	if (!php_firebird_commit_transaction(dbh, /*release*/ false)) {
869 		return false;
870 	}
871 
872 	/**
873 	 * If in autocommit mode, begin the transaction again
874 	 * Reopen instead of retain because isolation level may change
875 	 */
876 	if (dbh->auto_commit) {
877 		if (!php_firebird_begin_transaction(dbh, /* auto commit mode */ true)) {
878 			return false;
879 		}
880 	}
881 	H->in_manually_txn = 0;
882 	return true;
883 }
884 /* }}} */
885 
886 /* php_firebird_rollback_transaction */
php_firebird_rollback_transaction(pdo_dbh_t * dbh)887 static bool php_firebird_rollback_transaction(pdo_dbh_t *dbh) /* {{{ */
888 {
889 	pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data;
890 
891 	if (isc_rollback_transaction(H->isc_status, &H->tr)) {
892 		php_firebird_error(dbh);
893 		return false;
894 	}
895 	return true;
896 }
897 /* }}} */
898 
899 /* called by PDO to rollback a transaction */
firebird_handle_manually_rollback(pdo_dbh_t * dbh)900 static bool firebird_handle_manually_rollback(pdo_dbh_t *dbh) /* {{{ */
901 {
902 	pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data;
903 
904 	if (!php_firebird_rollback_transaction(dbh)) {
905 		return false;
906 	}
907 
908 	/**
909 	 * If in autocommit mode, begin the transaction again
910 	 * Reopen instead of retain because isolation level may change
911 	 */
912 	if (dbh->auto_commit) {
913 		if (!php_firebird_begin_transaction(dbh, /* auto commit mode */ true)) {
914 			return false;
915 		}
916 	}
917 	H->in_manually_txn = 0;
918 	return true;
919 }
920 /* }}} */
921 
922 /* used by prepare and exec to allocate a statement handle and prepare the SQL */
php_firebird_alloc_prepare_stmt(pdo_dbh_t * dbh,const zend_string * sql,XSQLDA * out_sqlda,isc_stmt_handle * s,HashTable * named_params)923 static int php_firebird_alloc_prepare_stmt(pdo_dbh_t *dbh, const zend_string *sql,
924 	XSQLDA *out_sqlda, isc_stmt_handle *s, HashTable *named_params)
925 {
926 	pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data;
927 	char *new_sql;
928 
929 	/* Firebird allows SQL statements up to 64k, so bail if it doesn't fit */
930 	if (ZSTR_LEN(sql) > 65536) {
931 		php_firebird_error_with_info(dbh, "01004", strlen("01004"), NULL, 0);
932 		return 0;
933 	}
934 
935 	/* allocate the statement */
936 	if (isc_dsql_allocate_statement(H->isc_status, &H->db, s)) {
937 		php_firebird_error(dbh);
938 		return 0;
939 	}
940 
941 	/* in order to support named params, which Firebird itself doesn't,
942 	   we need to replace :foo by ?, and store the name we just replaced */
943 	new_sql = emalloc(ZSTR_LEN(sql)+1);
944 	new_sql[0] = '\0';
945 	if (!php_firebird_preprocess(sql, new_sql, named_params)) {
946 		php_firebird_error_with_info(dbh, "07000", strlen("07000"), NULL, 0);
947 		efree(new_sql);
948 		return 0;
949 	}
950 
951 	/* prepare the statement */
952 	if (isc_dsql_prepare(H->isc_status, &H->tr, s, 0, new_sql, H->sql_dialect, out_sqlda)) {
953 		php_firebird_error(dbh);
954 		efree(new_sql);
955 		return 0;
956 	}
957 
958 	efree(new_sql);
959 	return 1;
960 }
961 
962 /* called by PDO to set a driver-specific dbh attribute */
pdo_firebird_set_attribute(pdo_dbh_t * dbh,zend_long attr,zval * val)963 static bool pdo_firebird_set_attribute(pdo_dbh_t *dbh, zend_long attr, zval *val) /* {{{ */
964 {
965 	pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data;
966 	bool bval;
967 	zend_long lval;
968 
969 	switch (attr) {
970 		case PDO_ATTR_AUTOCOMMIT:
971 			{
972 				if (!pdo_get_bool_param(&bval, val)) {
973 					return false;
974 				}
975 
976 				if (H->in_manually_txn) {
977 					/* change auto commit mode with an open transaction is illegal, because
978 						we won't know what to do with it */
979 					pdo_raise_impl_error(dbh, NULL, "HY000", "Cannot change autocommit mode while a transaction is already open");
980 					return false;
981 				}
982 
983 				/* ignore if the new value equals the old one */
984 				if (dbh->auto_commit ^ bval) {
985 					if (bval) {
986 						/*
987 						 * change to auto commit mode.
988 						 * If the transaction is not started, start it.
989 						 */
990 						if (!H->tr) {
991 							if (!php_firebird_begin_transaction(dbh, /* auto commit mode */ true)) {
992 								return false;
993 							}
994 						}
995 					} else {
996 						/*
997 						 * change to not auto commit mode.
998 						 * close the transaction if exists.
999 						 */
1000 						if (H->tr) {
1001 							if (!php_firebird_commit_transaction(dbh, /* retain */ false)) {
1002 								return false;
1003 							}
1004 						}
1005 					}
1006 					dbh->auto_commit = bval;
1007 				}
1008 			}
1009 			return true;
1010 
1011 		case PDO_ATTR_FETCH_TABLE_NAMES:
1012 			if (!pdo_get_bool_param(&bval, val)) {
1013 				return false;
1014 			}
1015 			H->fetch_table_names = bval;
1016 			return true;
1017 
1018 		case PDO_FB_ATTR_DATE_FORMAT:
1019 			{
1020 				zend_string *str = zval_try_get_string(val);
1021 				if (UNEXPECTED(!str)) {
1022 					return false;
1023 				}
1024 				if (H->date_format) {
1025 					efree(H->date_format);
1026 				}
1027 				spprintf(&H->date_format, 0, "%s", ZSTR_VAL(str));
1028 				zend_string_release_ex(str, 0);
1029 			}
1030 			return true;
1031 
1032 		case PDO_FB_ATTR_TIME_FORMAT:
1033 			{
1034 				zend_string *str = zval_try_get_string(val);
1035 				if (UNEXPECTED(!str)) {
1036 					return false;
1037 				}
1038 				if (H->time_format) {
1039 					efree(H->time_format);
1040 				}
1041 				spprintf(&H->time_format, 0, "%s", ZSTR_VAL(str));
1042 				zend_string_release_ex(str, 0);
1043 			}
1044 			return true;
1045 
1046 		case PDO_FB_ATTR_TIMESTAMP_FORMAT:
1047 			{
1048 				zend_string *str = zval_try_get_string(val);
1049 				if (UNEXPECTED(!str)) {
1050 					return false;
1051 				}
1052 				if (H->timestamp_format) {
1053 					efree(H->timestamp_format);
1054 				}
1055 				spprintf(&H->timestamp_format, 0, "%s", ZSTR_VAL(str));
1056 				zend_string_release_ex(str, 0);
1057 			}
1058 			return true;
1059 
1060 		case PDO_FB_TRANSACTION_ISOLATION_LEVEL:
1061 			{
1062 				if (!pdo_get_long_param(&lval, val)) {
1063 					return false;
1064 				}
1065 
1066 				if (H->in_manually_txn) {
1067 					pdo_raise_impl_error(dbh, NULL, "HY000", "Cannot change transaction isolation level while a transaction is already open");
1068 					return false;
1069 				}
1070 
1071 				/* ignore if the new value equals the old one */
1072 				if (H->txn_isolation_level != lval) {
1073 					if (lval == PDO_FB_READ_COMMITTED ||
1074 						lval == PDO_FB_REPEATABLE_READ ||
1075 						lval == PDO_FB_SERIALIZABLE
1076 					) {
1077 						/*
1078 						 * Autocommit mode is always read-committed, so this setting is used the next time
1079 						 * a manual transaction starts. Therefore, there is no need to immediately reopen the transaction.
1080 						 */
1081 						H->txn_isolation_level = lval;
1082 					} else {
1083 						zend_value_error("PDO::FB_TRANSACTION_ISOLATION_LEVEL must be a valid transaction isolation level "
1084 							"(PDO::FB_READ_COMMITTED, PDO::FB_REPEATABLE_READ, or PDO::FB_SERIALIZABLE)");
1085 						return false;
1086 					}
1087 				}
1088 			}
1089 			return true;
1090 
1091 		case PDO_FB_WRITABLE_TRANSACTION:
1092 			{
1093 				if (!pdo_get_bool_param(&bval, val)) {
1094 					return false;
1095 				}
1096 
1097 				if (H->in_manually_txn) {
1098 					pdo_raise_impl_error(dbh, NULL, "HY000", "Cannot change access mode while a transaction is already open");
1099 					return false;
1100 				}
1101 
1102 				/* ignore if the new value equals the old one */
1103 				if (H->is_writable_txn != bval) {
1104 					H->is_writable_txn = bval;
1105 					if (dbh->auto_commit) {
1106 						if (H->tr) {
1107 							if (!php_firebird_commit_transaction(dbh, /* retain */ false)) {
1108 								/* In case of error, revert the setting */
1109 								H->is_writable_txn = !bval;
1110 								return false;
1111 							}
1112 						}
1113 						if (!php_firebird_begin_transaction(dbh, /* auto commit mode */ true)) {
1114 							/* In case of error, revert the setting */
1115 							H->is_writable_txn = !bval;
1116 							return false;
1117 						}
1118 					}
1119 				}
1120 			}
1121 			return true;
1122 	}
1123 	return false;
1124 }
1125 /* }}} */
1126 
1127 #define INFO_BUF_LEN 512
1128 
1129 /* callback to used to report database server info */
php_firebird_info_cb(void * arg,char const * s)1130 static void php_firebird_info_cb(void *arg, char const *s) /* {{{ */
1131 {
1132 	if (arg) {
1133 		if (*(char*)arg) { /* second call */
1134 			strlcat(arg, " ", INFO_BUF_LEN);
1135 		}
1136 		strlcat(arg, s, INFO_BUF_LEN);
1137 	}
1138 }
1139 /* }}} */
1140 
1141 /* called by PDO to get a driver-specific dbh attribute */
pdo_firebird_get_attribute(pdo_dbh_t * dbh,zend_long attr,zval * val)1142 static int pdo_firebird_get_attribute(pdo_dbh_t *dbh, zend_long attr, zval *val) /* {{{ */
1143 {
1144 	pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data;
1145 
1146 	switch (attr) {
1147 		char tmp[INFO_BUF_LEN];
1148 
1149 		case PDO_ATTR_AUTOCOMMIT:
1150 			ZVAL_BOOL(val,dbh->auto_commit);
1151 			return 1;
1152 
1153 		case PDO_ATTR_CONNECTION_STATUS:
1154 			ZVAL_BOOL(val, !isc_version(&H->db, php_firebird_info_cb, NULL));
1155 			return 1;
1156 
1157 		case PDO_ATTR_CLIENT_VERSION: {
1158 #if defined(__GNUC__) || defined(PHP_WIN32)
1159 			info_func_t info_func = NULL;
1160 #ifdef __GNUC__
1161 			info_func = (info_func_t)dlsym(RTLD_DEFAULT, "isc_get_client_version");
1162 #else
1163 			HMODULE l = GetModuleHandle("fbclient");
1164 
1165 			if (!l) {
1166 				break;
1167 			}
1168 			info_func = (info_func_t)GetProcAddress(l, "isc_get_client_version");
1169 #endif
1170 			if (info_func) {
1171 				info_func(tmp);
1172 				ZVAL_STRING(val, tmp);
1173 			}
1174 #else
1175 			ZVAL_NULL(val);
1176 #endif
1177 			}
1178 			return 1;
1179 
1180 		case PDO_ATTR_SERVER_VERSION:
1181 		case PDO_ATTR_SERVER_INFO:
1182 			*tmp = 0;
1183 
1184 			if (!isc_version(&H->db, php_firebird_info_cb, (void*)tmp)) {
1185 				ZVAL_STRING(val, tmp);
1186 				return 1;
1187 			}
1188 			/* TODO Check this is correct? */
1189 			ZEND_FALLTHROUGH;
1190 
1191 		case PDO_ATTR_FETCH_TABLE_NAMES:
1192 			ZVAL_BOOL(val, H->fetch_table_names);
1193 			return 1;
1194 
1195 		case PDO_FB_ATTR_DATE_FORMAT:
1196 			ZVAL_STRING(val, H->date_format);
1197 			return 1;
1198 
1199 		case PDO_FB_ATTR_TIME_FORMAT:
1200 			ZVAL_STRING(val, H->time_format);
1201 			return 1;
1202 
1203 		case PDO_FB_ATTR_TIMESTAMP_FORMAT:
1204 			ZVAL_STRING(val, H->timestamp_format);
1205 			return 1;
1206 
1207 		case PDO_FB_TRANSACTION_ISOLATION_LEVEL:
1208 			ZVAL_LONG(val, H->txn_isolation_level);
1209 			return 1;
1210 
1211 		case PDO_FB_WRITABLE_TRANSACTION:
1212 			ZVAL_BOOL(val, H->is_writable_txn);
1213 			return 1;
1214 	}
1215 	return 0;
1216 }
1217 /* }}} */
1218 
1219 #if FB_API_VER >= 30
1220 /* called by PDO to check liveness */
pdo_firebird_check_liveness(pdo_dbh_t * dbh)1221 static zend_result pdo_firebird_check_liveness(pdo_dbh_t *dbh) /* {{{ */
1222 {
1223 	pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data;
1224 
1225 	/* fb_ping return 0 if the connection is alive */
1226 	return fb_ping(H->isc_status, &H->db) ? FAILURE : SUCCESS;
1227 }
1228 /* }}} */
1229 #endif
1230 
1231 /* 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)1232 static void pdo_firebird_fetch_error_func(pdo_dbh_t *dbh, pdo_stmt_t *stmt, zval *info) /* {{{ */
1233 {
1234 	pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data;
1235 	if (H->einfo.sqlcode != IS_NULL) {
1236 		add_next_index_long(info, H->einfo.sqlcode);
1237 	}
1238 	if (H->einfo.errmsg && H->einfo.errmsg_length) {
1239 		add_next_index_stringl(info, H->einfo.errmsg, H->einfo.errmsg_length);
1240 	}
1241 }
1242 /* }}} */
1243 
1244 /* {{{ firebird_in_manually_transaction */
pdo_firebird_in_manually_transaction(pdo_dbh_t * dbh)1245 static bool pdo_firebird_in_manually_transaction(pdo_dbh_t *dbh)
1246 {
1247 	/**
1248 	 * we can tell if a transaction exists now by checking H->tr,
1249 	 * but which will always be true in autocommit mode.
1250 	 * So this function checks if there is currently a "manually begun transaction".
1251 	 */
1252 	pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data;
1253 	return H->in_manually_txn;
1254 }
1255 /* }}} */
1256 
1257 static const struct pdo_dbh_methods firebird_methods = { /* {{{ */
1258 	firebird_handle_closer,
1259 	firebird_handle_preparer,
1260 	firebird_handle_doer,
1261 	firebird_handle_quoter,
1262 	firebird_handle_manually_begin,
1263 	firebird_handle_manually_commit,
1264 	firebird_handle_manually_rollback,
1265 	pdo_firebird_set_attribute,
1266 	NULL, /* last_id not supported */
1267 	pdo_firebird_fetch_error_func,
1268 	pdo_firebird_get_attribute,
1269 #if FB_API_VER >= 30
1270 	pdo_firebird_check_liveness,
1271 #else
1272 	NULL,
1273 #endif
1274 	NULL, /* get driver methods */
1275 	NULL, /* request shutdown */
1276 	pdo_firebird_in_manually_transaction,
1277 	NULL /* get gc */
1278 };
1279 /* }}} */
1280 
1281 /* the driver-specific PDO handle constructor */
pdo_firebird_handle_factory(pdo_dbh_t * dbh,zval * driver_options)1282 static int pdo_firebird_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{{ */
1283 {
1284 	struct pdo_data_src_parser vars[] = {
1285 		{ "dbname", NULL, 0 },
1286 		{ "charset",  NULL,	0 },
1287 		{ "role", NULL,	0 },
1288 		{ "dialect", "3", 0 },
1289 		{ "user", NULL, 0 },
1290 		{ "password", NULL, 0 }
1291 	};
1292 	int i, ret = 0;
1293 	short buf_len = 256, dpb_len;
1294 
1295 	pdo_firebird_db_handle *H = dbh->driver_data = pecalloc(1,sizeof(*H),dbh->is_persistent);
1296 
1297 	php_pdo_parse_data_source(dbh->data_source, dbh->data_source_len, vars, 6);
1298 
1299 	if (!dbh->username && vars[4].optval) {
1300 		dbh->username = pestrdup(vars[4].optval, dbh->is_persistent);
1301 	}
1302 
1303 	if (!dbh->password && vars[5].optval) {
1304 		dbh->password = pestrdup(vars[5].optval, dbh->is_persistent);
1305 	}
1306 
1307 	H->in_manually_txn = 0;
1308 	H->is_writable_txn = pdo_attr_lval(driver_options, PDO_FB_WRITABLE_TRANSACTION, 1);
1309 	zend_long txn_isolation_level = pdo_attr_lval(driver_options, PDO_FB_TRANSACTION_ISOLATION_LEVEL, PDO_FB_REPEATABLE_READ);
1310 	if (txn_isolation_level == PDO_FB_READ_COMMITTED ||
1311 		txn_isolation_level == PDO_FB_REPEATABLE_READ ||
1312 		txn_isolation_level == PDO_FB_SERIALIZABLE
1313 	) {
1314 		H->txn_isolation_level = txn_isolation_level;
1315 	} else {
1316 		zend_value_error("PDO::FB_TRANSACTION_ISOLATION_LEVEL must be a valid transaction isolation level "
1317 			"(PDO::FB_READ_COMMITTED, PDO::FB_REPEATABLE_READ, or PDO::FB_SERIALIZABLE)");
1318 		ret = 0;
1319 	}
1320 
1321 	do {
1322 		static char const dpb_flags[] = {
1323 			isc_dpb_user_name, isc_dpb_password, isc_dpb_lc_ctype, isc_dpb_sql_role_name };
1324 		char const *dpb_values[] = { dbh->username, dbh->password, vars[1].optval, vars[2].optval };
1325 		char dpb_buffer[256] = { isc_dpb_version1 }, *dpb;
1326 
1327 		dpb = dpb_buffer + 1;
1328 
1329 		/* loop through all the provided arguments and set dpb fields accordingly */
1330 		for (i = 0; i < sizeof(dpb_flags); ++i) {
1331 			if (dpb_values[i] && buf_len > 0) {
1332 				dpb_len = slprintf(dpb, buf_len, "%c%c%s", dpb_flags[i], (unsigned char)strlen(dpb_values[i]),
1333 					dpb_values[i]);
1334 				dpb += dpb_len;
1335 				buf_len -= dpb_len;
1336 			}
1337 		}
1338 
1339 		H->sql_dialect = PDO_FB_DIALECT;
1340 		if (vars[3].optval) {
1341 			H->sql_dialect = atoi(vars[3].optval);
1342 		}
1343 
1344 		/* fire it up baby! */
1345 		if (isc_attach_database(H->isc_status, 0, vars[0].optval, &H->db,(short)(dpb-dpb_buffer), dpb_buffer)) {
1346 			break;
1347 		}
1348 
1349 		dbh->methods = &firebird_methods;
1350 		dbh->native_case = PDO_CASE_UPPER;
1351 		dbh->alloc_own_columns = 1;
1352 
1353 		ret = 1;
1354 
1355 	} while (0);
1356 
1357 	for (i = 0; i < sizeof(vars)/sizeof(vars[0]); ++i) {
1358 		if (vars[i].freeme) {
1359 			efree(vars[i].optval);
1360 		}
1361 	}
1362 
1363 	if (!dbh->methods) {
1364 		char errmsg[512];
1365 		const ISC_STATUS *s = H->isc_status;
1366 		fb_interpret(errmsg, sizeof(errmsg),&s);
1367 		zend_throw_exception_ex(php_pdo_get_exception(), H->isc_status[1], "SQLSTATE[%s] [%ld] %s",
1368 				"HY000", H->isc_status[1], errmsg);
1369 	}
1370 
1371 	if (dbh->auto_commit && !H->tr) {
1372 		ret = php_firebird_begin_transaction(dbh, /* auto commit mode */ true);
1373 	}
1374 
1375 	if (!ret) {
1376 		firebird_handle_closer(dbh);
1377 	}
1378 
1379 	return ret;
1380 }
1381 /* }}} */
1382 
1383 
1384 const pdo_driver_t pdo_firebird_driver = { /* {{{ */
1385 	PDO_DRIVER_HEADER(firebird),
1386 	pdo_firebird_handle_factory
1387 };
1388 /* }}} */
1389