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