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 return -1;
981
982 case PDO_ATTR_FETCH_TABLE_NAMES:
983 ZVAL_BOOL(val, H->fetch_table_names);
984 return 1;
985 }
986 return 0;
987 }
988 /* }}} */
989
990 /* 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)991 static void pdo_firebird_fetch_error_func(pdo_dbh_t *dbh, pdo_stmt_t *stmt, zval *info) /* {{{ */
992 {
993 pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data;
994 const ISC_STATUS *s = H->isc_status;
995 char buf[400];
996 zend_long i = 0, l, sqlcode = isc_sqlcode(s);
997
998 if (sqlcode) {
999 add_next_index_long(info, sqlcode);
1000
1001 while ((sizeof(buf)>(i+2))&&(l = fb_interpret(&buf[i],(sizeof(buf)-i-2),&s))) {
1002 i += l;
1003 strcpy(&buf[i++], " ");
1004 }
1005 add_next_index_string(info, buf);
1006 } else if (H->last_app_error) {
1007 add_next_index_long(info, -999);
1008 add_next_index_string(info, const_cast(H->last_app_error));
1009 }
1010 }
1011 /* }}} */
1012
1013 static const struct pdo_dbh_methods firebird_methods = { /* {{{ */
1014 firebird_handle_closer,
1015 firebird_handle_preparer,
1016 firebird_handle_doer,
1017 firebird_handle_quoter,
1018 firebird_handle_begin,
1019 firebird_handle_commit,
1020 firebird_handle_rollback,
1021 firebird_handle_set_attribute,
1022 NULL, /* last_id not supported */
1023 pdo_firebird_fetch_error_func,
1024 firebird_handle_get_attribute,
1025 NULL, /* check_liveness */
1026 NULL, /* get driver methods */
1027 NULL, /* request shutdown */
1028 NULL, /* in transaction, use PDO's internal tracking mechanism */
1029 NULL /* get gc */
1030 };
1031 /* }}} */
1032
1033 /* the driver-specific PDO handle constructor */
pdo_firebird_handle_factory(pdo_dbh_t * dbh,zval * driver_options)1034 static int pdo_firebird_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{{ */
1035 {
1036 struct pdo_data_src_parser vars[] = {
1037 { "dbname", NULL, 0 },
1038 { "charset", NULL, 0 },
1039 { "role", NULL, 0 },
1040 { "dialect", "3", 0 },
1041 { "user", NULL, 0 },
1042 { "password", NULL, 0 }
1043 };
1044 int i, ret = 0;
1045 short buf_len = 256, dpb_len;
1046
1047 pdo_firebird_db_handle *H = dbh->driver_data = pecalloc(1,sizeof(*H),dbh->is_persistent);
1048
1049 php_pdo_parse_data_source(dbh->data_source, dbh->data_source_len, vars, 6);
1050
1051 if (!dbh->username && vars[4].optval) {
1052 dbh->username = pestrdup(vars[4].optval, dbh->is_persistent);
1053 }
1054
1055 if (!dbh->password && vars[5].optval) {
1056 dbh->password = pestrdup(vars[5].optval, dbh->is_persistent);
1057 }
1058
1059 do {
1060 static char const dpb_flags[] = {
1061 isc_dpb_user_name, isc_dpb_password, isc_dpb_lc_ctype, isc_dpb_sql_role_name };
1062 char const *dpb_values[] = { dbh->username, dbh->password, vars[1].optval, vars[2].optval };
1063 char dpb_buffer[256] = { isc_dpb_version1 }, *dpb;
1064
1065 dpb = dpb_buffer + 1;
1066
1067 /* loop through all the provided arguments and set dpb fields accordingly */
1068 for (i = 0; i < sizeof(dpb_flags); ++i) {
1069 if (dpb_values[i] && buf_len > 0) {
1070 dpb_len = slprintf(dpb, buf_len, "%c%c%s", dpb_flags[i], (unsigned char)strlen(dpb_values[i]),
1071 dpb_values[i]);
1072 dpb += dpb_len;
1073 buf_len -= dpb_len;
1074 }
1075 }
1076
1077 H->sql_dialect = PDO_FB_DIALECT;
1078 if (vars[3].optval) {
1079 H->sql_dialect = atoi(vars[3].optval);
1080 }
1081
1082 /* fire it up baby! */
1083 if (isc_attach_database(H->isc_status, 0, vars[0].optval, &H->db,(short)(dpb-dpb_buffer), dpb_buffer)) {
1084 break;
1085 }
1086
1087 dbh->methods = &firebird_methods;
1088 dbh->native_case = PDO_CASE_UPPER;
1089 dbh->alloc_own_columns = 1;
1090
1091 ret = 1;
1092
1093 } while (0);
1094
1095 for (i = 0; i < sizeof(vars)/sizeof(vars[0]); ++i) {
1096 if (vars[i].freeme) {
1097 efree(vars[i].optval);
1098 }
1099 }
1100
1101 if (!dbh->methods) {
1102 char errmsg[512];
1103 const ISC_STATUS *s = H->isc_status;
1104 fb_interpret(errmsg, sizeof(errmsg),&s);
1105 zend_throw_exception_ex(php_pdo_get_exception(), H->isc_status[1], "SQLSTATE[%s] [%ld] %s",
1106 "HY000", H->isc_status[1], errmsg);
1107 }
1108
1109 if (!ret) {
1110 firebird_handle_closer(dbh);
1111 }
1112
1113 return ret;
1114 }
1115 /* }}} */
1116
1117
1118 const pdo_driver_t pdo_firebird_driver = { /* {{{ */
1119 PDO_DRIVER_HEADER(firebird),
1120 pdo_firebird_handle_factory
1121 };
1122 /* }}} */
1123