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