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