xref: /PHP-8.3/ext/pdo/pdo_dbh.c (revision c3f6579f)
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: Wez Furlong <wez@php.net>                                    |
14   |         Marcus Boerger <helly@php.net>                               |
15   |         Sterling Hughes <sterling@php.net>                           |
16   +----------------------------------------------------------------------+
17 */
18 
19 /* The PDO Database Handle Class */
20 
21 #ifdef HAVE_CONFIG_H
22 #include "config.h"
23 #endif
24 
25 #include "php.h"
26 #include "php_ini.h"
27 #include "ext/standard/info.h"
28 #include "php_pdo.h"
29 #include "php_pdo_driver.h"
30 #include "php_pdo_int.h"
31 #include "zend_attributes.h"
32 #include "zend_exceptions.h"
33 #include "zend_object_handlers.h"
34 #include "zend_hash.h"
35 #include "pdo_dbh_arginfo.h"
36 #include "zend_observer.h"
37 #include "zend_extensions.h"
38 
39 static bool pdo_dbh_attribute_set(pdo_dbh_t *dbh, zend_long attr, zval *value);
40 
pdo_throw_exception(unsigned int driver_errcode,char * driver_errmsg,pdo_error_type * pdo_error)41 void pdo_throw_exception(unsigned int driver_errcode, char *driver_errmsg, pdo_error_type *pdo_error)
42 {
43 		zval error_info,pdo_exception;
44 		char *pdo_exception_message;
45 
46 		object_init_ex(&pdo_exception, php_pdo_get_exception());
47 		array_init(&error_info);
48 
49 		add_next_index_string(&error_info, *pdo_error);
50 		add_next_index_long(&error_info, driver_errcode);
51 		add_next_index_string(&error_info, driver_errmsg);
52 
53 		spprintf(&pdo_exception_message, 0,"SQLSTATE[%s] [%d] %s",*pdo_error, driver_errcode, driver_errmsg);
54 		zend_update_property(php_pdo_get_exception(), Z_OBJ(pdo_exception), "errorInfo", sizeof("errorInfo")-1, &error_info);
55 		zend_update_property_long(php_pdo_get_exception(), Z_OBJ(pdo_exception), "code", sizeof("code")-1, driver_errcode);
56 		zend_update_property_string(
57 			php_pdo_get_exception(),
58 			Z_OBJ(pdo_exception),
59 			"message",
60 			sizeof("message")-1,
61 			pdo_exception_message
62 		);
63 		efree(pdo_exception_message);
64 		zval_ptr_dtor(&error_info);
65 		zend_throw_exception_object(&pdo_exception);
66 }
67 
pdo_raise_impl_error(pdo_dbh_t * dbh,pdo_stmt_t * stmt,pdo_error_type sqlstate,const char * supp)68 void pdo_raise_impl_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, pdo_error_type sqlstate, const char *supp) /* {{{ */
69 {
70 	pdo_error_type *pdo_err = &dbh->error_code;
71 	char *message = NULL;
72 	const char *msg;
73 
74 	if (dbh && dbh->error_mode == PDO_ERRMODE_SILENT) {
75 #if 0
76 		/* BUG: if user is running in silent mode and hits an error at the driver level
77 		 * when they use the PDO methods to call up the error information, they may
78 		 * get bogus information */
79 		return;
80 #endif
81 	}
82 
83 	if (stmt) {
84 		pdo_err = &stmt->error_code;
85 	}
86 
87 	memcpy(*pdo_err, sqlstate, sizeof(pdo_error_type));
88 
89 	/* hash sqlstate to error messages */
90 	msg = pdo_sqlstate_state_to_description(*pdo_err);
91 	if (!msg) {
92 		msg = "<<Unknown error>>";
93 	}
94 
95 	if (supp) {
96 		spprintf(&message, 0, "SQLSTATE[%s]: %s: %s", *pdo_err, msg, supp);
97 	} else {
98 		spprintf(&message, 0, "SQLSTATE[%s]: %s", *pdo_err, msg);
99 	}
100 
101 	if (dbh && dbh->error_mode != PDO_ERRMODE_EXCEPTION) {
102 		php_error_docref(NULL, E_WARNING, "%s", message);
103 	} else {
104 		zval ex, info;
105 		zend_class_entry *pdo_ex = php_pdo_get_exception();
106 
107 		object_init_ex(&ex, pdo_ex);
108 
109 		zend_update_property_string(zend_ce_exception, Z_OBJ(ex), "message", sizeof("message")-1, message);
110 		zend_update_property_string(zend_ce_exception, Z_OBJ(ex), "code", sizeof("code")-1, *pdo_err);
111 
112 		array_init(&info);
113 
114 		add_next_index_string(&info, *pdo_err);
115 		add_next_index_long(&info, 0);
116 		zend_update_property(pdo_ex, Z_OBJ(ex), "errorInfo", sizeof("errorInfo")-1, &info);
117 		zval_ptr_dtor(&info);
118 
119 		zend_throw_exception_object(&ex);
120 	}
121 
122 	if (message) {
123 		efree(message);
124 	}
125 }
126 /* }}} */
127 
pdo_handle_error(pdo_dbh_t * dbh,pdo_stmt_t * stmt)128 PDO_API void pdo_handle_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt) /* {{{ */
129 {
130 	pdo_error_type *pdo_err = &dbh->error_code;
131 	const char *msg = "<<Unknown>>";
132 	char *supp = NULL;
133 	zend_long native_code = 0;
134 	zend_string *message = NULL;
135 	zval info;
136 
137 	if (dbh == NULL || dbh->error_mode == PDO_ERRMODE_SILENT) {
138 		return;
139 	}
140 
141 	if (stmt) {
142 		pdo_err = &stmt->error_code;
143 	}
144 
145 	/* hash sqlstate to error messages */
146 	msg = pdo_sqlstate_state_to_description(*pdo_err);
147 	if (!msg) {
148 		msg = "<<Unknown error>>";
149 	}
150 
151 	ZVAL_UNDEF(&info);
152 	if (dbh->methods->fetch_err) {
153 		zval *item;
154 		array_init(&info);
155 
156 		add_next_index_string(&info, *pdo_err);
157 
158 		dbh->methods->fetch_err(dbh, stmt, &info);
159 
160 		if ((item = zend_hash_index_find(Z_ARRVAL(info), 1)) != NULL
161 				&& Z_TYPE_P(item) == IS_LONG) {
162 			native_code = Z_LVAL_P(item);
163 		}
164 
165 		if ((item = zend_hash_index_find(Z_ARRVAL(info), 2)) != NULL) {
166 			supp = estrndup(Z_STRVAL_P(item), Z_STRLEN_P(item));
167 		}
168 	}
169 
170 	if (native_code && supp) {
171 		message = strpprintf(0, "SQLSTATE[%s]: %s: " ZEND_LONG_FMT " %s", *pdo_err, msg, native_code, supp);
172 	} else if (supp) {
173 		message = strpprintf(0, "SQLSTATE[%s]: %s: %s", *pdo_err, msg, supp);
174 	} else {
175 		message = strpprintf(0, "SQLSTATE[%s]: %s", *pdo_err, msg);
176 	}
177 
178 	if (dbh->error_mode == PDO_ERRMODE_WARNING) {
179 		php_error_docref(NULL, E_WARNING, "%s", ZSTR_VAL(message));
180 	} else if (EG(exception) == NULL) {
181 		zval ex;
182 		zend_class_entry *pdo_ex = php_pdo_get_exception();
183 
184 		object_init_ex(&ex, pdo_ex);
185 
186 		zend_update_property_str(zend_ce_exception, Z_OBJ(ex), "message", sizeof("message") - 1, message);
187 		zend_update_property_string(zend_ce_exception, Z_OBJ(ex), "code", sizeof("code") - 1, *pdo_err);
188 
189 		if (!Z_ISUNDEF(info)) {
190 			zend_update_property(pdo_ex, Z_OBJ(ex), "errorInfo", sizeof("errorInfo") - 1, &info);
191 		}
192 
193 		zend_throw_exception_object(&ex);
194 	}
195 
196 	if (!Z_ISUNDEF(info)) {
197 		zval_ptr_dtor(&info);
198 	}
199 
200 	if (message) {
201 		zend_string_release_ex(message, 0);
202 	}
203 
204 	if (supp) {
205 		efree(supp);
206 	}
207 }
208 /* }}} */
209 
dsn_from_uri(char * uri,char * buf,size_t buflen)210 static char *dsn_from_uri(char *uri, char *buf, size_t buflen) /* {{{ */
211 {
212 	php_stream *stream;
213 	char *dsn = NULL;
214 
215 	stream = php_stream_open_wrapper(uri, "rb", REPORT_ERRORS, NULL);
216 	if (stream) {
217 		dsn = php_stream_get_line(stream, buf, buflen, NULL);
218 		php_stream_close(stream);
219 	}
220 	return dsn;
221 }
222 /* }}} */
223 
224 /* {{{ */
PHP_METHOD(PDO,__construct)225 PHP_METHOD(PDO, __construct)
226 {
227 	zval *object = ZEND_THIS;
228 	pdo_dbh_t *dbh = NULL;
229 	bool is_persistent = 0;
230 	char *data_source;
231 	size_t data_source_len;
232 	char *colon;
233 	char *username=NULL, *password=NULL;
234 	size_t usernamelen, passwordlen;
235 	pdo_driver_t *driver = NULL;
236 	zval *options = NULL;
237 	char alt_dsn[512];
238 	int call_factory = 1;
239 	zend_error_handling zeh;
240 
241 	ZEND_PARSE_PARAMETERS_START(1, 4)
242 		Z_PARAM_STRING(data_source, data_source_len)
243 		Z_PARAM_OPTIONAL
244 		Z_PARAM_STRING_OR_NULL(username, usernamelen)
245 		Z_PARAM_STRING_OR_NULL(password, passwordlen)
246 		Z_PARAM_ARRAY_OR_NULL(options)
247 	ZEND_PARSE_PARAMETERS_END();
248 
249 	/* parse the data source name */
250 	colon = strchr(data_source, ':');
251 
252 	if (!colon) {
253 		/* let's see if this string has a matching dsn in the php.ini */
254 		char *ini_dsn = NULL;
255 
256 		snprintf(alt_dsn, sizeof(alt_dsn), "pdo.dsn.%s", data_source);
257 		if (FAILURE == cfg_get_string(alt_dsn, &ini_dsn)) {
258 			zend_argument_error(php_pdo_get_exception(), 1, "must be a valid data source name");
259 			RETURN_THROWS();
260 		}
261 
262 		data_source = ini_dsn;
263 		colon = strchr(data_source, ':');
264 
265 		if (!colon) {
266 			zend_throw_exception_ex(php_pdo_get_exception(), 0, "invalid data source name (via INI: %s)", alt_dsn);
267 			RETURN_THROWS();
268 		}
269 	}
270 
271 	if (!strncmp(data_source, "uri:", sizeof("uri:")-1)) {
272 		/* the specified URI holds connection details */
273 		data_source = dsn_from_uri(data_source + sizeof("uri:")-1, alt_dsn, sizeof(alt_dsn));
274 		if (!data_source) {
275 			zend_argument_error(php_pdo_get_exception(), 1, "must be a valid data source URI");
276 			RETURN_THROWS();
277 		}
278 		colon = strchr(data_source, ':');
279 		if (!colon) {
280 			zend_argument_error(php_pdo_get_exception(), 1, "must be a valid data source name (via URI)");
281 			RETURN_THROWS();
282 		}
283 	}
284 
285 	driver = pdo_find_driver(data_source, colon - data_source);
286 
287 	if (!driver) {
288 		/* NB: don't want to include the data_source in the error message as
289 		 * it might contain a password */
290 		zend_throw_exception_ex(php_pdo_get_exception(), 0, "could not find driver");
291 		RETURN_THROWS();
292 	}
293 
294 	dbh = Z_PDO_DBH_P(object);
295 
296 	/* is this supposed to be a persistent connection ? */
297 	if (options) {
298 		int plen = 0;
299 		char *hashkey = NULL;
300 		zend_resource *le;
301 		pdo_dbh_t *pdbh = NULL;
302 		zval *v;
303 
304 		if ((v = zend_hash_index_find_deref(Z_ARRVAL_P(options), PDO_ATTR_PERSISTENT)) != NULL) {
305 			if (Z_TYPE_P(v) == IS_STRING &&
306 				!is_numeric_string(Z_STRVAL_P(v), Z_STRLEN_P(v), NULL, NULL, 0) && Z_STRLEN_P(v) > 0) {
307 				/* user specified key */
308 				plen = spprintf(&hashkey, 0, "PDO:DBH:DSN=%s:%s:%s:%s", data_source,
309 						username ? username : "",
310 						password ? password : "",
311 						Z_STRVAL_P(v));
312 				is_persistent = 1;
313 			} else {
314 				is_persistent = zval_get_long(v) ? 1 : 0;
315 				plen = spprintf(&hashkey, 0, "PDO:DBH:DSN=%s:%s:%s", data_source,
316 						username ? username : "",
317 						password ? password : "");
318 			}
319 		}
320 
321 		if (is_persistent) {
322 			/* let's see if we have one cached.... */
323 			if ((le = zend_hash_str_find_ptr(&EG(persistent_list), hashkey, plen)) != NULL) {
324 				if (le->type == php_pdo_list_entry()) {
325 					pdbh = (pdo_dbh_t*)le->ptr;
326 
327 					/* is the connection still alive ? */
328 					if (pdbh->methods->check_liveness && FAILURE == (pdbh->methods->check_liveness)(pdbh)) {
329 						/* nope... need to kill it */
330 						pdbh->refcount--;
331 						zend_list_close(le);
332 						pdbh = NULL;
333 					}
334 				}
335 			}
336 
337 			if (pdbh) {
338 				call_factory = 0;
339 			} else {
340 				/* need a brand new pdbh */
341 				pdbh = pecalloc(1, sizeof(*pdbh), 1);
342 
343 				pdbh->refcount = 1;
344 				pdbh->is_persistent = 1;
345 				pdbh->persistent_id = pemalloc(plen + 1, 1);
346 				memcpy((char *)pdbh->persistent_id, hashkey, plen+1);
347 				pdbh->persistent_id_len = plen;
348 				pdbh->def_stmt_ce = dbh->def_stmt_ce;
349 			}
350 		}
351 
352 		if (pdbh) {
353 			efree(dbh);
354 			/* switch over to the persistent one */
355 			Z_PDO_OBJECT_P(object)->inner = pdbh;
356 			pdbh->refcount++;
357 			dbh = pdbh;
358 		}
359 
360 		if (hashkey) {
361 			efree(hashkey);
362 		}
363 	}
364 
365 	if (call_factory) {
366 		dbh->data_source_len = strlen(colon + 1);
367 		dbh->data_source = (const char*)pestrdup(colon + 1, is_persistent);
368 		dbh->username = username ? pestrdup(username, is_persistent) : NULL;
369 		dbh->password = password ? pestrdup(password, is_persistent) : NULL;
370 		dbh->default_fetch_type = PDO_FETCH_BOTH;
371 	}
372 
373 	dbh->auto_commit = pdo_attr_lval(options, PDO_ATTR_AUTOCOMMIT, 1);
374 	dbh->error_mode = pdo_attr_lval(options, PDO_ATTR_ERRMODE, PDO_ERRMODE_EXCEPTION);
375 
376 	if (!dbh->data_source || (username && !dbh->username) || (password && !dbh->password)) {
377 		php_error_docref(NULL, E_ERROR, "Out of memory");
378 	}
379 
380 	/* pdo_dbh_attribute_set() can emit a Warning if the ERR_MODE is set to warning
381 	 * As we are in a constructor we override the behaviour by replacing the error handler */
382 	zend_replace_error_handling(EH_THROW, pdo_exception_ce, &zeh);
383 
384 	if (!call_factory) {
385 		/* we got a persistent guy from our cache */
386 		goto options;
387 	}
388 
389 	if (driver->db_handle_factory(dbh, options)) {
390 		/* all set */
391 
392 		if (is_persistent) {
393 			/* register in the persistent list etc. */
394 			/* we should also need to replace the object store entry,
395 			   since it was created with emalloc */
396 			if ((zend_register_persistent_resource(
397 						(char*)dbh->persistent_id, dbh->persistent_id_len, dbh, php_pdo_list_entry())) == NULL) {
398 				php_error_docref(NULL, E_ERROR, "Failed to register persistent entry");
399 			}
400 		}
401 
402 		dbh->driver = driver;
403 options:
404 		if (options) {
405 			zval *attr_value;
406 			zend_ulong long_key;
407 			zend_string *str_key = NULL;
408 
409 			ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(options), long_key, str_key, attr_value) {
410 				if (str_key) {
411 					continue;
412 				}
413 				ZVAL_DEREF(attr_value);
414 
415 				/* TODO: Should the constructor fail when the attribute cannot be set? */
416 				pdo_dbh_attribute_set(dbh, long_key, attr_value);
417 			} ZEND_HASH_FOREACH_END();
418 		}
419 
420 		zend_restore_error_handling(&zeh);
421 		return;
422 	}
423 
424 	/* the connection failed; things will tidy up in free_storage */
425 	if (is_persistent) {
426 		dbh->refcount--;
427 	}
428 
429 	/* XXX raise exception */
430 	zend_restore_error_handling(&zeh);
431 	if (!EG(exception)) {
432 		zend_throw_exception(pdo_exception_ce, "Constructor failed", 0);
433 	}
434 }
435 /* }}} */
436 
pdo_stmt_instantiate(pdo_dbh_t * dbh,zval * object,zend_class_entry * dbstmt_ce,zval * ctor_args)437 static zval *pdo_stmt_instantiate(pdo_dbh_t *dbh, zval *object, zend_class_entry *dbstmt_ce, zval *ctor_args) /* {{{ */
438 {
439 	if (!Z_ISUNDEF_P(ctor_args)) {
440 		/* This implies an error within PDO if this does not hold */
441 		ZEND_ASSERT(Z_TYPE_P(ctor_args) == IS_ARRAY);
442 		if (!dbstmt_ce->constructor) {
443 			zend_throw_error(NULL, "User-supplied statement does not accept constructor arguments");
444 			return NULL;
445 		}
446 	}
447 
448 	if (UNEXPECTED(object_init_ex(object, dbstmt_ce) != SUCCESS)) {
449 		if (EXPECTED(!EG(exception))) {
450 			zend_throw_error(NULL, "Cannot instantiate user-supplied statement class");
451 		}
452 		return NULL;
453 	}
454 
455 	return object;
456 } /* }}} */
457 
pdo_stmt_construct(zend_execute_data * execute_data,pdo_stmt_t * stmt,zval * object,zend_class_entry * dbstmt_ce,zval * ctor_args)458 static void pdo_stmt_construct(zend_execute_data *execute_data, pdo_stmt_t *stmt, zval *object, zend_class_entry *dbstmt_ce, zval *ctor_args) /* {{{ */
459 {
460 	zval query_string;
461 	zend_string *key;
462 
463 	ZVAL_STR(&query_string, stmt->query_string);
464 	key = ZSTR_INIT_LITERAL("queryString", 0);
465 	zend_std_write_property(Z_OBJ_P(object), key, &query_string, NULL);
466 	zend_string_release_ex(key, 0);
467 
468 	if (dbstmt_ce->constructor) {
469 		zend_fcall_info fci;
470 		zend_fcall_info_cache fcc;
471 		zval retval;
472 
473 		fci.size = sizeof(zend_fcall_info);
474 		ZVAL_UNDEF(&fci.function_name);
475 		fci.object = Z_OBJ_P(object);
476 		fci.retval = &retval;
477 		fci.param_count = 0;
478 		fci.params = NULL;
479 		fci.named_params = NULL;
480 
481 		zend_fcall_info_args(&fci, ctor_args);
482 
483 		fcc.function_handler = dbstmt_ce->constructor;
484 		fcc.called_scope = Z_OBJCE_P(object);
485 		fcc.object = Z_OBJ_P(object);
486 
487 		if (zend_call_function(&fci, &fcc) != FAILURE) {
488 			zval_ptr_dtor(&retval);
489 		}
490 
491 		zend_fcall_info_args_clear(&fci, 1);
492 	}
493 }
494 /* }}} */
495 
496 /* {{{ Prepares a statement for execution and returns a statement object */
PHP_METHOD(PDO,prepare)497 PHP_METHOD(PDO, prepare)
498 {
499 	pdo_stmt_t *stmt;
500 	zend_string *statement;
501 	zval *options = NULL, *value, *item, ctor_args;
502 	zend_class_entry *dbstmt_ce, *pce;
503 	pdo_dbh_object_t *dbh_obj = Z_PDO_OBJECT_P(ZEND_THIS);
504 	pdo_dbh_t *dbh = dbh_obj->inner;
505 
506 	ZEND_PARSE_PARAMETERS_START(1, 2)
507 		Z_PARAM_STR(statement)
508 		Z_PARAM_OPTIONAL
509 		Z_PARAM_ARRAY(options)
510 	ZEND_PARSE_PARAMETERS_END();
511 
512 	PDO_CONSTRUCT_CHECK;
513 
514 	if (ZSTR_LEN(statement) == 0) {
515 		zend_argument_value_error(1, "cannot be empty");
516 		RETURN_THROWS();
517 	}
518 
519 	PDO_DBH_CLEAR_ERR();
520 
521 	if (options && (value = zend_hash_index_find(Z_ARRVAL_P(options), PDO_ATTR_STATEMENT_CLASS)) != NULL) {
522 		if (Z_TYPE_P(value) != IS_ARRAY) {
523 			zend_type_error("PDO::ATTR_STATEMENT_CLASS value must be of type array, %s given",
524 				zend_zval_value_name(value));
525 			RETURN_THROWS();
526 		}
527 		if ((item = zend_hash_index_find(Z_ARRVAL_P(value), 0)) == NULL) {
528 			zend_value_error("PDO::ATTR_STATEMENT_CLASS value must be an array with the format "
529 				"array(classname, constructor_args)");
530 			RETURN_THROWS();
531 		}
532 		if (Z_TYPE_P(item) != IS_STRING || (pce = zend_lookup_class(Z_STR_P(item))) == NULL) {
533 			zend_type_error("PDO::ATTR_STATEMENT_CLASS class must be a valid class");
534 			RETURN_THROWS();
535 		}
536 		dbstmt_ce = pce;
537 		if (!instanceof_function(dbstmt_ce, pdo_dbstmt_ce)) {
538 			zend_type_error("PDO::ATTR_STATEMENT_CLASS class must be derived from PDOStatement");
539 			RETURN_THROWS();
540 		}
541 		if (dbstmt_ce->constructor && !(dbstmt_ce->constructor->common.fn_flags & (ZEND_ACC_PRIVATE|ZEND_ACC_PROTECTED))) {
542 			zend_type_error("User-supplied statement class cannot have a public constructor");
543 			RETURN_THROWS();
544 		}
545 		if ((item = zend_hash_index_find(Z_ARRVAL_P(value), 1)) != NULL) {
546 			if (Z_TYPE_P(item) != IS_ARRAY) {
547 				zend_type_error("PDO::ATTR_STATEMENT_CLASS constructor_args must be of type ?array, %s given",
548 					zend_zval_value_name(value));
549 				RETURN_THROWS();
550 			}
551 			ZVAL_COPY_VALUE(&ctor_args, item);
552 		} else {
553 			ZVAL_UNDEF(&ctor_args);
554 		}
555 	} else {
556 		dbstmt_ce = dbh->def_stmt_ce;
557 		ZVAL_COPY_VALUE(&ctor_args, &dbh->def_stmt_ctor_args);
558 	}
559 
560 	if (!pdo_stmt_instantiate(dbh, return_value, dbstmt_ce, &ctor_args)) {
561 		RETURN_THROWS();
562 	}
563 	stmt = Z_PDO_STMT_P(return_value);
564 
565 	/* unconditionally keep this for later reference */
566 	stmt->query_string = zend_string_copy(statement);
567 	stmt->default_fetch_type = dbh->default_fetch_type;
568 	stmt->dbh = dbh;
569 	/* give it a reference to me */
570 	ZVAL_OBJ_COPY(&stmt->database_object_handle, &dbh_obj->std);
571 	/* we haven't created a lazy object yet */
572 	ZVAL_UNDEF(&stmt->lazy_object_ref);
573 
574 	if (dbh->methods->preparer(dbh, statement, stmt, options)) {
575 		pdo_stmt_construct(execute_data, stmt, return_value, dbstmt_ce, &ctor_args);
576 		return;
577 	}
578 
579 	PDO_HANDLE_DBH_ERR();
580 
581 	/* kill the object handle for the stmt here */
582 	zval_ptr_dtor(return_value);
583 
584 	RETURN_FALSE;
585 }
586 /* }}} */
587 
588 
pdo_is_in_transaction(pdo_dbh_t * dbh)589 static bool pdo_is_in_transaction(pdo_dbh_t *dbh) {
590 	if (dbh->methods->in_transaction) {
591 		return dbh->methods->in_transaction(dbh);
592 	}
593 	return dbh->in_txn;
594 }
595 
596 /* {{{ Initiates a transaction */
PHP_METHOD(PDO,beginTransaction)597 PHP_METHOD(PDO, beginTransaction)
598 {
599 	pdo_dbh_t *dbh = Z_PDO_DBH_P(ZEND_THIS);
600 
601 	ZEND_PARSE_PARAMETERS_NONE();
602 
603 	PDO_CONSTRUCT_CHECK;
604 
605 	if (pdo_is_in_transaction(dbh)) {
606 		zend_throw_exception_ex(php_pdo_get_exception(), 0, "There is already an active transaction");
607 		RETURN_THROWS();
608 	}
609 
610 	if (!dbh->methods->begin) {
611 		/* Throw an exception when the driver does not support transactions */
612 		zend_throw_exception_ex(php_pdo_get_exception(), 0, "This driver doesn't support transactions");
613 		RETURN_THROWS();
614 	}
615 
616 	if (dbh->methods->begin(dbh)) {
617 		dbh->in_txn = true;
618 		RETURN_TRUE;
619 	}
620 
621 	PDO_HANDLE_DBH_ERR();
622 	RETURN_FALSE;
623 }
624 /* }}} */
625 
626 /* {{{ Commit a transaction */
PHP_METHOD(PDO,commit)627 PHP_METHOD(PDO, commit)
628 {
629 	pdo_dbh_t *dbh = Z_PDO_DBH_P(ZEND_THIS);
630 
631 	ZEND_PARSE_PARAMETERS_NONE();
632 
633 	PDO_CONSTRUCT_CHECK;
634 
635 	if (!pdo_is_in_transaction(dbh)) {
636 		zend_throw_exception_ex(php_pdo_get_exception(), 0, "There is no active transaction");
637 		RETURN_THROWS();
638 	}
639 
640 	if (dbh->methods->commit(dbh)) {
641 		dbh->in_txn = false;
642 		RETURN_TRUE;
643 	}
644 
645 	PDO_HANDLE_DBH_ERR();
646 	RETURN_FALSE;
647 }
648 /* }}} */
649 
650 /* {{{ roll back a transaction */
PHP_METHOD(PDO,rollBack)651 PHP_METHOD(PDO, rollBack)
652 {
653 	pdo_dbh_t *dbh = Z_PDO_DBH_P(ZEND_THIS);
654 
655 	ZEND_PARSE_PARAMETERS_NONE();
656 
657 	PDO_CONSTRUCT_CHECK;
658 
659 	if (!pdo_is_in_transaction(dbh)) {
660 		zend_throw_exception_ex(php_pdo_get_exception(), 0, "There is no active transaction");
661 		RETURN_THROWS();
662 	}
663 
664 	if (dbh->methods->rollback(dbh)) {
665 		dbh->in_txn = false;
666 		RETURN_TRUE;
667 	}
668 
669 	PDO_HANDLE_DBH_ERR();
670 	RETURN_FALSE;
671 }
672 /* }}} */
673 
674 /* {{{ determine if inside a transaction */
PHP_METHOD(PDO,inTransaction)675 PHP_METHOD(PDO, inTransaction)
676 {
677 	pdo_dbh_t *dbh = Z_PDO_DBH_P(ZEND_THIS);
678 
679 	ZEND_PARSE_PARAMETERS_NONE();
680 
681 	PDO_CONSTRUCT_CHECK;
682 
683 	RETURN_BOOL(pdo_is_in_transaction(dbh));
684 }
685 /* }}} */
686 
pdo_get_long_param(zend_long * lval,zval * value)687 PDO_API bool pdo_get_long_param(zend_long *lval, zval *value)
688 {
689 	switch (Z_TYPE_P(value)) {
690 		case IS_LONG:
691 		case IS_TRUE:
692 		case IS_FALSE:
693 			*lval = zval_get_long(value);
694 			return true;
695 		case IS_STRING:
696 			if (IS_LONG == is_numeric_str_function(Z_STR_P(value), lval, NULL)) {
697 				return true;
698 			}
699 			ZEND_FALLTHROUGH;
700 		default:
701 			zend_type_error("Attribute value must be of type int for selected attribute, %s given", zend_zval_value_name(value));
702 			return false;
703 	}
704 }
pdo_get_bool_param(bool * bval,zval * value)705 PDO_API bool pdo_get_bool_param(bool *bval, zval *value)
706 {
707 	switch (Z_TYPE_P(value)) {
708 		case IS_TRUE:
709 			*bval = true;
710 			return true;
711 		case IS_FALSE:
712 			*bval = false;
713 			return true;
714 		case IS_LONG:
715 			*bval = zval_is_true(value);
716 			return true;
717 		case IS_STRING: /* TODO Should string be allowed? */
718 		default:
719 			zend_type_error("Attribute value must be of type bool for selected attribute, %s given", zend_zval_value_name(value));
720 			return false;
721 	}
722 }
723 
724 /* Return false on failure, true otherwise */
pdo_dbh_attribute_set(pdo_dbh_t * dbh,zend_long attr,zval * value)725 static bool pdo_dbh_attribute_set(pdo_dbh_t *dbh, zend_long attr, zval *value) /* {{{ */
726 {
727 	zend_long lval;
728 	bool bval;
729 
730 	switch (attr) {
731 		case PDO_ATTR_ERRMODE:
732 			if (!pdo_get_long_param(&lval, value)) {
733 				return false;
734 			}
735 			switch (lval) {
736 				case PDO_ERRMODE_SILENT:
737 				case PDO_ERRMODE_WARNING:
738 				case PDO_ERRMODE_EXCEPTION:
739 					dbh->error_mode = lval;
740 					return true;
741 				default:
742 					zend_value_error("Error mode must be one of the PDO::ERRMODE_* constants");
743 					return false;
744 			}
745 			return false;
746 
747 		case PDO_ATTR_CASE:
748 			if (!pdo_get_long_param(&lval, value)) {
749 				return false;
750 			}
751 			switch (lval) {
752 				case PDO_CASE_NATURAL:
753 				case PDO_CASE_UPPER:
754 				case PDO_CASE_LOWER:
755 					dbh->desired_case = lval;
756 					return true;
757 				default:
758 					zend_value_error("Case folding mode must be one of the PDO::CASE_* constants");
759 					return false;
760 			}
761 			return false;
762 
763 		case PDO_ATTR_ORACLE_NULLS:
764 			if (!pdo_get_long_param(&lval, value)) {
765 				return false;
766 			}
767 			/* TODO Check for valid value (NULL_NATURAL, NULL_EMPTY_STRING, NULL_TO_STRING)? */
768 			dbh->oracle_nulls = lval;
769 			return true;
770 
771 		case PDO_ATTR_DEFAULT_FETCH_MODE:
772 			if (Z_TYPE_P(value) == IS_ARRAY) {
773 				zval *tmp;
774 				if ((tmp = zend_hash_index_find(Z_ARRVAL_P(value), 0)) != NULL && Z_TYPE_P(tmp) == IS_LONG) {
775 					if (Z_LVAL_P(tmp) == PDO_FETCH_INTO || Z_LVAL_P(tmp) == PDO_FETCH_CLASS) {
776 						zend_value_error("PDO::FETCH_INTO and PDO::FETCH_CLASS cannot be set as the default fetch mode");
777 						return false;
778 					}
779 				}
780 				lval = zval_get_long(value);
781 			} else {
782 				if (!pdo_get_long_param(&lval, value)) {
783 					return false;
784 				}
785 			}
786 			if (lval == PDO_FETCH_USE_DEFAULT) {
787 				zend_value_error("Fetch mode must be a bitmask of PDO::FETCH_* constants");
788 				return false;
789 			}
790 			dbh->default_fetch_type = lval;
791 			return true;
792 
793 		case PDO_ATTR_STRINGIFY_FETCHES:
794 			if (!pdo_get_bool_param(&bval, value)) {
795 				return false;
796 			}
797 			dbh->stringify = bval;
798 			if (dbh->methods->set_attribute) {
799 				dbh->methods->set_attribute(dbh, attr, value);
800 			}
801 			return true;
802 
803 		case PDO_ATTR_STATEMENT_CLASS: {
804 			/* array(string classname, array(mixed ctor_args)) */
805 			zend_class_entry *pce;
806 			zval *item;
807 
808 			if (dbh->is_persistent) {
809 				/* TODO: ValueError/ PDOException? */
810 				pdo_raise_impl_error(dbh, NULL, "HY000",
811 					"PDO::ATTR_STATEMENT_CLASS cannot be used with persistent PDO instances"
812 					);
813 				PDO_HANDLE_DBH_ERR();
814 				return false;
815 			}
816 			if (Z_TYPE_P(value) != IS_ARRAY) {
817 				zend_type_error("PDO::ATTR_STATEMENT_CLASS value must be of type array, %s given",
818 					zend_zval_value_name(value));
819 				return false;
820 			}
821 			if ((item = zend_hash_index_find(Z_ARRVAL_P(value), 0)) == NULL) {
822 				zend_value_error("PDO::ATTR_STATEMENT_CLASS value must be an array with the format "
823 					"array(classname, constructor_args)");
824 				return false;
825 			}
826 			if (Z_TYPE_P(item) != IS_STRING || (pce = zend_lookup_class(Z_STR_P(item))) == NULL) {
827 				zend_type_error("PDO::ATTR_STATEMENT_CLASS class must be a valid class");
828 				return false;
829 			}
830 			if (!instanceof_function(pce, pdo_dbstmt_ce)) {
831 				zend_type_error("PDO::ATTR_STATEMENT_CLASS class must be derived from PDOStatement");
832 				return false;
833 			}
834 			if (pce->constructor && !(pce->constructor->common.fn_flags & (ZEND_ACC_PRIVATE|ZEND_ACC_PROTECTED))) {
835 				zend_type_error("User-supplied statement class cannot have a public constructor");
836 				return false;
837 			}
838 			dbh->def_stmt_ce = pce;
839 			if (!Z_ISUNDEF(dbh->def_stmt_ctor_args)) {
840 				zval_ptr_dtor(&dbh->def_stmt_ctor_args);
841 				ZVAL_UNDEF(&dbh->def_stmt_ctor_args);
842 			}
843 			if ((item = zend_hash_index_find(Z_ARRVAL_P(value), 1)) != NULL) {
844 				if (Z_TYPE_P(item) != IS_ARRAY) {
845 					zend_type_error("PDO::ATTR_STATEMENT_CLASS constructor_args must be of type ?array, %s given",
846 						zend_zval_value_name(value));
847 					return false;
848 				}
849 				ZVAL_COPY(&dbh->def_stmt_ctor_args, item);
850 			}
851 			return true;
852 		}
853 		/* Don't throw a ValueError as the attribute might be a driver specific one */
854 		default:;
855 	}
856 
857 	if (!dbh->methods->set_attribute) {
858 		goto fail;
859 	}
860 
861 	PDO_DBH_CLEAR_ERR();
862 	if (dbh->methods->set_attribute(dbh, attr, value)) {
863 		return true;
864 	}
865 
866 fail:
867 	if (!dbh->methods->set_attribute) {
868 		pdo_raise_impl_error(dbh, NULL, "IM001", "driver does not support setting attributes");
869 	} else {
870 		PDO_HANDLE_DBH_ERR();
871 	}
872 	return false;
873 }
874 /* }}} */
875 
876 /* {{{ Set an attribute */
PHP_METHOD(PDO,setAttribute)877 PHP_METHOD(PDO, setAttribute)
878 {
879 	pdo_dbh_t *dbh = Z_PDO_DBH_P(ZEND_THIS);
880 	zend_long attr;
881 	zval *value;
882 
883 	ZEND_PARSE_PARAMETERS_START(2, 2)
884 		Z_PARAM_LONG(attr)
885 		Z_PARAM_ZVAL(value)
886 	ZEND_PARSE_PARAMETERS_END();
887 
888 	PDO_DBH_CLEAR_ERR();
889 	PDO_CONSTRUCT_CHECK;
890 
891 	RETURN_BOOL(pdo_dbh_attribute_set(dbh, attr, value));
892 }
893 /* }}} */
894 
895 /* {{{ Get an attribute */
PHP_METHOD(PDO,getAttribute)896 PHP_METHOD(PDO, getAttribute)
897 {
898 	pdo_dbh_t *dbh = Z_PDO_DBH_P(ZEND_THIS);
899 	zend_long attr;
900 
901 	ZEND_PARSE_PARAMETERS_START(1, 1)
902 		Z_PARAM_LONG(attr)
903 	ZEND_PARSE_PARAMETERS_END();
904 
905 	PDO_DBH_CLEAR_ERR();
906 	PDO_CONSTRUCT_CHECK;
907 
908 	/* handle generic PDO-level attributes */
909 	switch (attr) {
910 		case PDO_ATTR_PERSISTENT:
911 			RETURN_BOOL(dbh->is_persistent);
912 
913 		case PDO_ATTR_CASE:
914 			RETURN_LONG(dbh->desired_case);
915 
916 		case PDO_ATTR_ORACLE_NULLS:
917 			RETURN_LONG(dbh->oracle_nulls);
918 
919 		case PDO_ATTR_ERRMODE:
920 			RETURN_LONG(dbh->error_mode);
921 
922 		case PDO_ATTR_DRIVER_NAME:
923 			RETURN_STRINGL((char*)dbh->driver->driver_name, dbh->driver->driver_name_len);
924 
925 		case PDO_ATTR_STATEMENT_CLASS:
926 			array_init(return_value);
927 			add_next_index_str(return_value, zend_string_copy(dbh->def_stmt_ce->name));
928 			if (!Z_ISUNDEF(dbh->def_stmt_ctor_args)) {
929 				Z_TRY_ADDREF(dbh->def_stmt_ctor_args);
930 				add_next_index_zval(return_value, &dbh->def_stmt_ctor_args);
931 			}
932 			return;
933 
934 		case PDO_ATTR_DEFAULT_FETCH_MODE:
935 			RETURN_LONG(dbh->default_fetch_type);
936 
937 		case PDO_ATTR_STRINGIFY_FETCHES:
938 			RETURN_BOOL(dbh->stringify);
939 
940 		default:
941 			break;
942 	}
943 
944 	if (!dbh->methods->get_attribute) {
945 		pdo_raise_impl_error(dbh, NULL, "IM001", "driver does not support getting attributes");
946 		RETURN_FALSE;
947 	}
948 
949 	switch (dbh->methods->get_attribute(dbh, attr, return_value)) {
950 		case -1:
951 			PDO_HANDLE_DBH_ERR();
952 			RETURN_FALSE;
953 
954 		case 0:
955 			pdo_raise_impl_error(dbh, NULL, "IM001", "driver does not support that attribute");
956 			RETURN_FALSE;
957 
958 		default:
959 			/* No error state, just return as the return_value has been assigned
960 			 * by the get_attribute handler */
961 			return;
962 	}
963 }
964 /* }}} */
965 
966 /* {{{ Execute a statement that does not return a row set, returning the number of affected rows */
PHP_METHOD(PDO,exec)967 PHP_METHOD(PDO, exec)
968 {
969 	pdo_dbh_t *dbh = Z_PDO_DBH_P(ZEND_THIS);
970 	zend_string *statement;
971 	zend_long ret;
972 
973 	ZEND_PARSE_PARAMETERS_START(1, 1)
974 		Z_PARAM_STR(statement)
975 	ZEND_PARSE_PARAMETERS_END();
976 
977 	if (ZSTR_LEN(statement) == 0) {
978 		zend_argument_value_error(1, "cannot be empty");
979 		RETURN_THROWS();
980 	}
981 
982 	PDO_DBH_CLEAR_ERR();
983 	PDO_CONSTRUCT_CHECK;
984 	ret = dbh->methods->doer(dbh, statement);
985 	if (ret == -1) {
986 		PDO_HANDLE_DBH_ERR();
987 		RETURN_FALSE;
988 	} else {
989 		RETURN_LONG(ret);
990 	}
991 }
992 /* }}} */
993 
994 /* {{{ Returns the id of the last row that we affected on this connection. Some databases require a sequence or table name to be passed in. Not always meaningful. */
PHP_METHOD(PDO,lastInsertId)995 PHP_METHOD(PDO, lastInsertId)
996 {
997 	pdo_dbh_t *dbh = Z_PDO_DBH_P(ZEND_THIS);
998 	zend_string *name = NULL;
999 	zend_string *last_id = NULL;
1000 
1001 	ZEND_PARSE_PARAMETERS_START(0, 1)
1002 		Z_PARAM_OPTIONAL
1003 		Z_PARAM_STR_OR_NULL(name)
1004 	ZEND_PARSE_PARAMETERS_END();
1005 
1006 	PDO_CONSTRUCT_CHECK;
1007 
1008 	PDO_DBH_CLEAR_ERR();
1009 
1010 	if (!dbh->methods->last_id) {
1011 		pdo_raise_impl_error(dbh, NULL, "IM001", "driver does not support lastInsertId()");
1012 		RETURN_FALSE;
1013 	}
1014 	last_id = dbh->methods->last_id(dbh, name);
1015 	if (!last_id) {
1016 		PDO_HANDLE_DBH_ERR();
1017 		RETURN_FALSE;
1018 	}
1019 	RETURN_STR(last_id);
1020 }
1021 /* }}} */
1022 
1023 /* {{{ Fetch the error code associated with the last operation on the database handle */
PHP_METHOD(PDO,errorCode)1024 PHP_METHOD(PDO, errorCode)
1025 {
1026 	pdo_dbh_t *dbh = Z_PDO_DBH_P(ZEND_THIS);
1027 
1028 	ZEND_PARSE_PARAMETERS_NONE();
1029 
1030 	PDO_CONSTRUCT_CHECK;
1031 
1032 	if (dbh->query_stmt) {
1033 		RETURN_STRING(dbh->query_stmt->error_code);
1034 	}
1035 
1036 	if (dbh->error_code[0] == '\0') {
1037 		RETURN_NULL();
1038 	}
1039 
1040 	/**
1041 	 * Making sure that we fallback to the default implementation
1042 	 * if the dbh->error_code is not null.
1043 	 */
1044 	RETURN_STRING(dbh->error_code);
1045 }
1046 /* }}} */
1047 
1048 /* {{{ Fetch extended error information associated with the last operation on the database handle */
PHP_METHOD(PDO,errorInfo)1049 PHP_METHOD(PDO, errorInfo)
1050 {
1051 	int error_count;
1052 	int error_count_diff 	 = 0;
1053 	int error_expected_count = 3;
1054 
1055 	pdo_dbh_t *dbh = Z_PDO_DBH_P(ZEND_THIS);
1056 
1057 	ZEND_PARSE_PARAMETERS_NONE();
1058 
1059 	PDO_CONSTRUCT_CHECK;
1060 
1061 	array_init(return_value);
1062 
1063 	if (dbh->query_stmt) {
1064 		add_next_index_string(return_value, dbh->query_stmt->error_code);
1065 		if(!strncmp(dbh->query_stmt->error_code, PDO_ERR_NONE, sizeof(PDO_ERR_NONE))) goto fill_array;
1066 	} else {
1067 		add_next_index_string(return_value, dbh->error_code);
1068 		if(!strncmp(dbh->error_code, PDO_ERR_NONE, sizeof(PDO_ERR_NONE))) goto fill_array;
1069 	}
1070 
1071 	if (dbh->methods->fetch_err) {
1072 		dbh->methods->fetch_err(dbh, dbh->query_stmt, return_value);
1073 	}
1074 
1075 fill_array:
1076 	/**
1077 	 * In order to be consistent, we have to make sure we add the good amount
1078 	 * of nulls depending on the current number of elements. We make a simple
1079 	 * difference and add the needed elements
1080 	 */
1081 	error_count = zend_hash_num_elements(Z_ARRVAL_P(return_value));
1082 
1083 	if (error_expected_count > error_count) {
1084 		int current_index;
1085 
1086 		error_count_diff = error_expected_count - error_count;
1087 		for (current_index = 0; current_index < error_count_diff; current_index++) {
1088 			add_next_index_null(return_value);
1089 		}
1090 	}
1091 }
1092 /* }}} */
1093 
1094 /* {{{ Prepare and execute $sql; returns the statement object for iteration */
PHP_METHOD(PDO,query)1095 PHP_METHOD(PDO, query)
1096 {
1097 	pdo_stmt_t *stmt;
1098 	zend_string *statement;
1099 	zend_long fetch_mode;
1100 	bool fetch_mode_is_null = 1;
1101 	zval *args = NULL;
1102 	uint32_t num_args = 0;
1103 	pdo_dbh_object_t *dbh_obj = Z_PDO_OBJECT_P(ZEND_THIS);
1104 	pdo_dbh_t *dbh = dbh_obj->inner;
1105 
1106 	if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "S|l!*", &statement,
1107 			&fetch_mode, &fetch_mode_is_null, &args, &num_args)) {
1108 		RETURN_THROWS();
1109 	}
1110 
1111 	PDO_CONSTRUCT_CHECK;
1112 
1113 	if (ZSTR_LEN(statement) == 0) {
1114 		zend_argument_value_error(1, "cannot be empty");
1115 		RETURN_THROWS();
1116 	}
1117 
1118 	PDO_DBH_CLEAR_ERR();
1119 
1120 	if (!pdo_stmt_instantiate(dbh, return_value, dbh->def_stmt_ce, &dbh->def_stmt_ctor_args)) {
1121 		RETURN_THROWS();
1122 	}
1123 	stmt = Z_PDO_STMT_P(return_value);
1124 
1125 	/* unconditionally keep this for later reference */
1126 	stmt->query_string = zend_string_copy(statement);
1127 	stmt->active_query_string = zend_string_copy(stmt->query_string);
1128 	stmt->default_fetch_type = dbh->default_fetch_type;
1129 	stmt->dbh = dbh;
1130 	/* give it a reference to me */
1131 	ZVAL_OBJ_COPY(&stmt->database_object_handle, &dbh_obj->std);
1132 	/* we haven't created a lazy object yet */
1133 	ZVAL_UNDEF(&stmt->lazy_object_ref);
1134 
1135 	if (dbh->methods->preparer(dbh, statement, stmt, NULL)) {
1136 		PDO_STMT_CLEAR_ERR();
1137 		if (fetch_mode_is_null || pdo_stmt_setup_fetch_mode(stmt, fetch_mode, 2, args, num_args)) {
1138 			/* now execute the statement */
1139 			PDO_STMT_CLEAR_ERR();
1140 			if (stmt->methods->executer(stmt)) {
1141 				bool ret = true;
1142 				if (!stmt->executed) {
1143 					if (stmt->dbh->alloc_own_columns) {
1144 						ret = pdo_stmt_describe_columns(stmt);
1145 					}
1146 					stmt->executed = 1;
1147 				}
1148 				if (ret) {
1149 					pdo_stmt_construct(execute_data, stmt, return_value, dbh->def_stmt_ce, &dbh->def_stmt_ctor_args);
1150 					return;
1151 				}
1152 			}
1153 		}
1154 		/* something broke */
1155 		dbh->query_stmt = stmt;
1156 		ZVAL_OBJ(&dbh->query_stmt_zval, Z_OBJ_P(return_value));
1157 		Z_DELREF(stmt->database_object_handle);
1158 		ZVAL_UNDEF(&stmt->database_object_handle);
1159 		PDO_HANDLE_STMT_ERR();
1160 	} else {
1161 		PDO_HANDLE_DBH_ERR();
1162 		zval_ptr_dtor(return_value);
1163 	}
1164 
1165 	RETURN_FALSE;
1166 }
1167 /* }}} */
1168 
1169 /* {{{ quotes string for use in a query.
1170  * The optional paramtype acts as a hint for drivers that have alternate quoting styles.
1171  * The default value is PDO_PARAM_STR */
PHP_METHOD(PDO,quote)1172 PHP_METHOD(PDO, quote)
1173 {
1174 	pdo_dbh_t *dbh = Z_PDO_DBH_P(ZEND_THIS);
1175 	zend_string *str, *quoted;
1176 	zend_long paramtype = PDO_PARAM_STR;
1177 
1178 	ZEND_PARSE_PARAMETERS_START(1, 2)
1179 		Z_PARAM_STR(str)
1180 		Z_PARAM_OPTIONAL
1181 		Z_PARAM_LONG(paramtype)
1182 	ZEND_PARSE_PARAMETERS_END();
1183 
1184 	PDO_CONSTRUCT_CHECK;
1185 
1186 	PDO_DBH_CLEAR_ERR();
1187 	if (!dbh->methods->quoter) {
1188 		pdo_raise_impl_error(dbh, NULL, "IM001", "driver does not support quoting");
1189 		RETURN_FALSE;
1190 	}
1191 	quoted = dbh->methods->quoter(dbh, str, paramtype);
1192 
1193 	if (quoted == NULL) {
1194 		PDO_HANDLE_DBH_ERR();
1195 		RETURN_FALSE;
1196 	}
1197 
1198 	RETURN_STR(quoted);
1199 }
1200 /* }}} */
1201 
1202 /* {{{ Return array of available PDO drivers */
PHP_METHOD(PDO,getAvailableDrivers)1203 PHP_METHOD(PDO, getAvailableDrivers)
1204 {
1205 	pdo_driver_t *pdriver;
1206 
1207 	ZEND_PARSE_PARAMETERS_NONE();
1208 
1209 	array_init(return_value);
1210 
1211 	ZEND_HASH_MAP_FOREACH_PTR(&pdo_driver_hash, pdriver) {
1212 		add_next_index_stringl(return_value, (char*)pdriver->driver_name, pdriver->driver_name_len);
1213 	} ZEND_HASH_FOREACH_END();
1214 }
1215 /* }}} */
1216 
cls_method_dtor(zval * el)1217 static void cls_method_dtor(zval *el) /* {{{ */ {
1218 	zend_function *func = (zend_function*)Z_PTR_P(el);
1219 	if (func->common.function_name) {
1220 		zend_string_release_ex(func->common.function_name, 0);
1221 	}
1222 	if (ZEND_MAP_PTR(func->common.run_time_cache)) {
1223 		efree(ZEND_MAP_PTR(func->common.run_time_cache));
1224 	}
1225 	efree(func);
1226 }
1227 /* }}} */
1228 
cls_method_pdtor(zval * el)1229 static void cls_method_pdtor(zval *el) /* {{{ */ {
1230 	zend_function *func = (zend_function*)Z_PTR_P(el);
1231 	if (func->common.function_name) {
1232 		zend_string_release_ex(func->common.function_name, 1);
1233 	}
1234 	if (ZEND_MAP_PTR(func->common.run_time_cache)) {
1235 		pefree(ZEND_MAP_PTR(func->common.run_time_cache), 1);
1236 	}
1237 	pefree(func, 1);
1238 }
1239 /* }}} */
1240 
1241 /* {{{ overloaded object handlers for PDO class */
pdo_hash_methods(pdo_dbh_object_t * dbh_obj,int kind)1242 bool pdo_hash_methods(pdo_dbh_object_t *dbh_obj, int kind)
1243 {
1244 	const zend_function_entry *funcs;
1245 	zend_internal_function func;
1246 	size_t namelen;
1247 	char *lc_name;
1248 	pdo_dbh_t *dbh = dbh_obj->inner;
1249 
1250 	if (!dbh || !dbh->methods || !dbh->methods->get_driver_methods) {
1251 		return false;
1252 	}
1253 	funcs =	dbh->methods->get_driver_methods(dbh, kind);
1254 	if (!funcs) {
1255 		return false;
1256 	}
1257 
1258 	dbh->cls_methods[kind] = pemalloc(sizeof(HashTable), dbh->is_persistent);
1259 	zend_hash_init(dbh->cls_methods[kind], 8, NULL,
1260 			dbh->is_persistent? cls_method_pdtor : cls_method_dtor, dbh->is_persistent);
1261 
1262 	memset(&func, 0, sizeof(func));
1263 
1264 	size_t rt_cache_size = zend_internal_run_time_cache_reserved_size();
1265 	while (funcs->fname) {
1266 		func.type = ZEND_INTERNAL_FUNCTION;
1267 		func.handler = funcs->handler;
1268 		func.function_name = zend_string_init(funcs->fname, strlen(funcs->fname), dbh->is_persistent);
1269 		func.scope = dbh_obj->std.ce;
1270 		func.prototype = NULL;
1271 		ZEND_MAP_PTR(func.run_time_cache) = rt_cache_size ? pecalloc(rt_cache_size, 1, dbh->is_persistent) : NULL;
1272 		func.T = ZEND_OBSERVER_ENABLED;
1273 		if (funcs->flags) {
1274 			func.fn_flags = funcs->flags | ZEND_ACC_NEVER_CACHE;
1275 		} else {
1276 			func.fn_flags = ZEND_ACC_PUBLIC | ZEND_ACC_NEVER_CACHE;
1277 		}
1278 		if (funcs->arg_info) {
1279 			zend_internal_function_info *info = (zend_internal_function_info*)funcs->arg_info;
1280 
1281 			func.arg_info = (zend_internal_arg_info*)funcs->arg_info + 1;
1282 			func.num_args = funcs->num_args;
1283 			if (info->required_num_args == (uint32_t)-1) {
1284 				func.required_num_args = funcs->num_args;
1285 			} else {
1286 				func.required_num_args = info->required_num_args;
1287 			}
1288 			if (ZEND_ARG_SEND_MODE(info)) {
1289 				func.fn_flags |= ZEND_ACC_RETURN_REFERENCE;
1290 			}
1291 			if (ZEND_ARG_IS_VARIADIC(&funcs->arg_info[funcs->num_args])) {
1292 				func.fn_flags |= ZEND_ACC_VARIADIC;
1293 				/* Don't count the variadic argument */
1294 				func.num_args--;
1295 			}
1296 		} else {
1297 			func.arg_info = NULL;
1298 			func.num_args = 0;
1299 			func.required_num_args = 0;
1300 		}
1301 		zend_set_function_arg_flags((zend_function*)&func);
1302 		namelen = strlen(funcs->fname);
1303 		lc_name = emalloc(namelen+1);
1304 		zend_str_tolower_copy(lc_name, funcs->fname, namelen);
1305 		zend_hash_str_add_mem(dbh->cls_methods[kind], lc_name, namelen, &func, sizeof(func));
1306 		efree(lc_name);
1307 		funcs++;
1308 	}
1309 
1310 	return true;
1311 }
1312 
dbh_method_get(zend_object ** object,zend_string * method_name,const zval * key)1313 static zend_function *dbh_method_get(zend_object **object, zend_string *method_name, const zval *key)
1314 {
1315 	zend_function *fbc = NULL;
1316 	pdo_dbh_object_t *dbh_obj = php_pdo_dbh_fetch_object(*object);
1317 	zend_string *lc_method_name;
1318 
1319 	if ((fbc = zend_std_get_method(object, method_name, key)) == NULL) {
1320 		/* not a pre-defined method, nor a user-defined method; check
1321 		 * the driver specific methods */
1322 		if (!dbh_obj->inner->cls_methods[PDO_DBH_DRIVER_METHOD_KIND_DBH]) {
1323 			if (!pdo_hash_methods(dbh_obj,
1324 				PDO_DBH_DRIVER_METHOD_KIND_DBH)
1325 				|| !dbh_obj->inner->cls_methods[PDO_DBH_DRIVER_METHOD_KIND_DBH]) {
1326 				goto out;
1327 			}
1328 		}
1329 
1330 		lc_method_name = zend_string_tolower(method_name);
1331 		fbc = zend_hash_find_ptr(dbh_obj->inner->cls_methods[PDO_DBH_DRIVER_METHOD_KIND_DBH], lc_method_name);
1332 		zend_string_release_ex(lc_method_name, 0);
1333 	}
1334 
1335 out:
1336 	return fbc;
1337 }
1338 
dbh_get_gc(zend_object * object,zval ** gc_data,int * gc_count)1339 static HashTable *dbh_get_gc(zend_object *object, zval **gc_data, int *gc_count)
1340 {
1341 	pdo_dbh_t *dbh = php_pdo_dbh_fetch_inner(object);
1342 	zend_get_gc_buffer *gc_buffer = zend_get_gc_buffer_create();
1343 	zend_get_gc_buffer_add_zval(gc_buffer, &dbh->def_stmt_ctor_args);
1344 	if (dbh->methods && dbh->methods->get_gc) {
1345 		dbh->methods->get_gc(dbh, gc_buffer);
1346 	}
1347 	zend_get_gc_buffer_use(gc_buffer, gc_data, gc_count);
1348 	return zend_std_get_properties(object);
1349 }
1350 
1351 static zend_object_handlers pdo_dbh_object_handlers;
1352 static void pdo_dbh_free_storage(zend_object *std);
1353 
pdo_dbh_init(int module_number)1354 void pdo_dbh_init(int module_number)
1355 {
1356 	pdo_dbh_ce = register_class_PDO();
1357 	pdo_dbh_ce->create_object = pdo_dbh_new;
1358 	pdo_dbh_ce->default_object_handlers = &pdo_dbh_object_handlers;
1359 
1360 	memcpy(&pdo_dbh_object_handlers, &std_object_handlers, sizeof(zend_object_handlers));
1361 	pdo_dbh_object_handlers.offset = XtOffsetOf(pdo_dbh_object_t, std);
1362 	pdo_dbh_object_handlers.free_obj = pdo_dbh_free_storage;
1363 	pdo_dbh_object_handlers.clone_obj = NULL;
1364 	pdo_dbh_object_handlers.get_method = dbh_method_get;
1365 	pdo_dbh_object_handlers.compare = zend_objects_not_comparable;
1366 	pdo_dbh_object_handlers.get_gc = dbh_get_gc;
1367 }
1368 
dbh_free(pdo_dbh_t * dbh,bool free_persistent)1369 static void dbh_free(pdo_dbh_t *dbh, bool free_persistent)
1370 {
1371 	int i;
1372 
1373 	if (dbh->query_stmt) {
1374 		zval_ptr_dtor(&dbh->query_stmt_zval);
1375 		dbh->query_stmt = NULL;
1376 	}
1377 
1378 	if (dbh->is_persistent) {
1379 #if ZEND_DEBUG
1380 		ZEND_ASSERT(!free_persistent || (dbh->refcount == 1));
1381 #endif
1382 		if (!free_persistent && (--dbh->refcount)) {
1383 			return;
1384 		}
1385 	}
1386 
1387 	if (dbh->methods) {
1388 		dbh->methods->closer(dbh);
1389 	}
1390 
1391 	if (dbh->data_source) {
1392 		pefree((char *)dbh->data_source, dbh->is_persistent);
1393 	}
1394 	if (dbh->username) {
1395 		pefree(dbh->username, dbh->is_persistent);
1396 	}
1397 	if (dbh->password) {
1398 		pefree(dbh->password, dbh->is_persistent);
1399 	}
1400 
1401 	if (dbh->persistent_id) {
1402 		pefree((char *)dbh->persistent_id, dbh->is_persistent);
1403 	}
1404 
1405 	if (!Z_ISUNDEF(dbh->def_stmt_ctor_args)) {
1406 		zval_ptr_dtor(&dbh->def_stmt_ctor_args);
1407 	}
1408 
1409 	for (i = 0; i < PDO_DBH_DRIVER_METHOD_KIND__MAX; i++) {
1410 		if (dbh->cls_methods[i]) {
1411 			zend_hash_destroy(dbh->cls_methods[i]);
1412 			pefree(dbh->cls_methods[i], dbh->is_persistent);
1413 		}
1414 	}
1415 
1416 	pefree(dbh, dbh->is_persistent);
1417 }
1418 
pdo_dbh_free_storage(zend_object * std)1419 static void pdo_dbh_free_storage(zend_object *std)
1420 {
1421 	pdo_dbh_t *dbh = php_pdo_dbh_fetch_inner(std);
1422 
1423 	/* dbh might be null if we OOMed during object initialization. */
1424 	if (!dbh) {
1425 		return;
1426 	}
1427 
1428 	if (dbh->driver_data && dbh->methods && dbh->methods->rollback && pdo_is_in_transaction(dbh)) {
1429 		dbh->methods->rollback(dbh);
1430 		dbh->in_txn = false;
1431 	}
1432 
1433 	if (dbh->is_persistent && dbh->methods && dbh->methods->persistent_shutdown) {
1434 		dbh->methods->persistent_shutdown(dbh);
1435 	}
1436 	zend_object_std_dtor(std);
1437 	dbh_free(dbh, 0);
1438 }
1439 
pdo_dbh_new(zend_class_entry * ce)1440 zend_object *pdo_dbh_new(zend_class_entry *ce)
1441 {
1442 	pdo_dbh_object_t *dbh;
1443 
1444 	dbh = zend_object_alloc(sizeof(pdo_dbh_object_t), ce);
1445 	zend_object_std_init(&dbh->std, ce);
1446 	object_properties_init(&dbh->std, ce);
1447 	rebuild_object_properties(&dbh->std);
1448 	dbh->inner = ecalloc(1, sizeof(pdo_dbh_t));
1449 	dbh->inner->def_stmt_ce = pdo_dbstmt_ce;
1450 
1451 	return &dbh->std;
1452 }
1453 
1454 /* }}} */
1455 
ZEND_RSRC_DTOR_FUNC(php_pdo_pdbh_dtor)1456 ZEND_RSRC_DTOR_FUNC(php_pdo_pdbh_dtor) /* {{{ */
1457 {
1458 	if (res->ptr) {
1459 		pdo_dbh_t *dbh = (pdo_dbh_t*)res->ptr;
1460 		dbh_free(dbh, 1);
1461 		res->ptr = NULL;
1462 	}
1463 }
1464 /* }}} */
1465