1 /*
2   +----------------------------------------------------------------------+
3   | PHP Version 7                                                        |
4   +----------------------------------------------------------------------+
5   | Copyright (c) 1997-2018 The PHP Group                                |
6   +----------------------------------------------------------------------+
7   | This source file is subject to version 3.01 of the PHP license,      |
8   | that is bundled with this package in the file LICENSE, and is        |
9   | available through the world-wide-web at the following url:           |
10   | http://www.php.net/license/3_01.txt                                  |
11   | If you did not receive a copy of the PHP license and are unable to   |
12   | obtain it through the world-wide-web, please send a note to          |
13   | license@php.net so we can mail you a copy immediately.               |
14   +----------------------------------------------------------------------+
15   | Author: George Schlossnagle <george@omniti.com>                      |
16   |         Wez Furlong <wez@php.net>                                    |
17   |         Johannes Schlueter <johannes@mysql.com>                      |
18   +----------------------------------------------------------------------+
19 */
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 "pdo/php_pdo.h"
29 #include "pdo/php_pdo_driver.h"
30 #include "php_pdo_mysql.h"
31 #include "php_pdo_mysql_int.h"
32 #ifndef PDO_USE_MYSQLND
33 #include <mysqld_error.h>
34 #endif
35 #include "zend_exceptions.h"
36 
37 #if defined(PDO_USE_MYSQLND)
38 #	define pdo_mysql_init(persistent) mysqlnd_init(MYSQLND_CLIENT_NO_FLAG, persistent)
39 #else
40 #	define pdo_mysql_init(persistent) mysql_init(NULL)
41 #endif
42 
43 /* {{{ _pdo_mysql_error */
_pdo_mysql_error(pdo_dbh_t * dbh,pdo_stmt_t * stmt,const char * file,int line)44 int _pdo_mysql_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, const char *file, int line)
45 {
46 	pdo_mysql_db_handle *H = (pdo_mysql_db_handle *)dbh->driver_data;
47 	pdo_error_type *pdo_err;
48 	pdo_mysql_error_info *einfo;
49 	pdo_mysql_stmt *S = NULL;
50 
51 	PDO_DBG_ENTER("_pdo_mysql_error");
52 	PDO_DBG_INF_FMT("file=%s line=%d", file, line);
53 	if (stmt) {
54 		S = (pdo_mysql_stmt*)stmt->driver_data;
55 		pdo_err = &stmt->error_code;
56 		einfo   = &S->einfo;
57 	} else {
58 		pdo_err = &dbh->error_code;
59 		einfo   = &H->einfo;
60 	}
61 
62 	if (S && S->stmt) {
63 		einfo->errcode = mysql_stmt_errno(S->stmt);
64 	} else {
65 		einfo->errcode = mysql_errno(H->server);
66 	}
67 
68 	einfo->file = file;
69 	einfo->line = line;
70 
71 	if (einfo->errmsg) {
72 		pefree(einfo->errmsg, dbh->is_persistent);
73 		einfo->errmsg = NULL;
74 	}
75 
76 	if (einfo->errcode) {
77 		if (einfo->errcode == 2014) {
78 			einfo->errmsg = pestrdup(
79 				"Cannot execute queries while other unbuffered queries are active.  "
80 				"Consider using PDOStatement::fetchAll().  Alternatively, if your code "
81 				"is only ever going to run against mysql, you may enable query "
82 				"buffering by setting the PDO::MYSQL_ATTR_USE_BUFFERED_QUERY attribute.",
83 				dbh->is_persistent);
84 		} else if (einfo->errcode == 2057) {
85 			einfo->errmsg = pestrdup(
86 				"A stored procedure returning result sets of different size was called. "
87 				"This is not supported by libmysql",
88 				dbh->is_persistent);
89 
90 		} else {
91 			einfo->errmsg = pestrdup(mysql_error(H->server), dbh->is_persistent);
92 		}
93 	} else { /* no error */
94 		strcpy(*pdo_err, PDO_ERR_NONE);
95 		PDO_DBG_RETURN(0);
96 	}
97 
98 	if (S && S->stmt) {
99 		strcpy(*pdo_err, mysql_stmt_sqlstate(S->stmt));
100 	} else {
101 		strcpy(*pdo_err, mysql_sqlstate(H->server));
102 	}
103 
104 	if (!dbh->methods) {
105 		PDO_DBG_INF("Throwing exception");
106 		pdo_throw_exception(einfo->errcode, einfo->errmsg, pdo_err);
107 	}
108 
109 	PDO_DBG_RETURN(einfo->errcode);
110 }
111 /* }}} */
112 
113 /* {{{ pdo_mysql_fetch_error_func */
pdo_mysql_fetch_error_func(pdo_dbh_t * dbh,pdo_stmt_t * stmt,zval * info)114 static int pdo_mysql_fetch_error_func(pdo_dbh_t *dbh, pdo_stmt_t *stmt, zval *info)
115 {
116 	pdo_mysql_db_handle *H = (pdo_mysql_db_handle *)dbh->driver_data;
117 	pdo_mysql_error_info *einfo = &H->einfo;
118 
119 	PDO_DBG_ENTER("pdo_mysql_fetch_error_func");
120 	PDO_DBG_INF_FMT("dbh=%p stmt=%p", dbh, stmt);
121 	if (stmt) {
122 		pdo_mysql_stmt *S = (pdo_mysql_stmt*)stmt->driver_data;
123 		einfo = &S->einfo;
124 	} else {
125 		einfo = &H->einfo;
126 	}
127 
128 	if (einfo->errcode) {
129 		add_next_index_long(info, einfo->errcode);
130 		add_next_index_string(info, einfo->errmsg);
131 	}
132 
133 	PDO_DBG_RETURN(1);
134 }
135 /* }}} */
136 
137 /* {{{ mysql_handle_closer */
mysql_handle_closer(pdo_dbh_t * dbh)138 static int mysql_handle_closer(pdo_dbh_t *dbh)
139 {
140 	pdo_mysql_db_handle *H = (pdo_mysql_db_handle *)dbh->driver_data;
141 
142 	PDO_DBG_ENTER("mysql_handle_closer");
143 	PDO_DBG_INF_FMT("dbh=%p", dbh);
144 	if (H) {
145 		if (H->server) {
146 			mysql_close(H->server);
147 		}
148 		if (H->einfo.errmsg) {
149 			pefree(H->einfo.errmsg, dbh->is_persistent);
150 		}
151 		pefree(H, dbh->is_persistent);
152 		dbh->driver_data = NULL;
153 	}
154 	PDO_DBG_RETURN(0);
155 }
156 /* }}} */
157 
158 /* {{{ mysql_handle_preparer */
mysql_handle_preparer(pdo_dbh_t * dbh,const char * sql,size_t sql_len,pdo_stmt_t * stmt,zval * driver_options)159 static int mysql_handle_preparer(pdo_dbh_t *dbh, const char *sql, size_t sql_len, pdo_stmt_t *stmt, zval *driver_options)
160 {
161 	pdo_mysql_db_handle *H = (pdo_mysql_db_handle *)dbh->driver_data;
162 	pdo_mysql_stmt *S = ecalloc(1, sizeof(pdo_mysql_stmt));
163 	char *nsql = NULL;
164 	size_t nsql_len = 0;
165 	int ret;
166 	int server_version;
167 
168 	PDO_DBG_ENTER("mysql_handle_preparer");
169 	PDO_DBG_INF_FMT("dbh=%p", dbh);
170 	PDO_DBG_INF_FMT("sql=%.*s", (int)sql_len, sql);
171 
172 	S->H = H;
173 	stmt->driver_data = S;
174 	stmt->methods = &mysql_stmt_methods;
175 
176 	if (H->emulate_prepare) {
177 		goto end;
178 	}
179 
180 	server_version = mysql_get_server_version(H->server);
181 	if (server_version < 40100) {
182 		goto fallback;
183 	}
184 	stmt->supports_placeholders = PDO_PLACEHOLDER_POSITIONAL;
185 	ret = pdo_parse_params(stmt, (char*)sql, sql_len, &nsql, &nsql_len);
186 
187 	if (ret == 1) {
188 		/* query was rewritten */
189 		sql = nsql;
190 		sql_len = nsql_len;
191 	} else if (ret == -1) {
192 		/* failed to parse */
193 		strcpy(dbh->error_code, stmt->error_code);
194 		PDO_DBG_RETURN(0);
195 	}
196 
197 	if (!(S->stmt = mysql_stmt_init(H->server))) {
198 		pdo_mysql_error(dbh);
199 		if (nsql) {
200 			efree(nsql);
201 		}
202 		PDO_DBG_RETURN(0);
203 	}
204 
205 	if (mysql_stmt_prepare(S->stmt, sql, sql_len)) {
206 		/* TODO: might need to pull statement specific info here? */
207 		/* if the query isn't supported by the protocol, fallback to emulation */
208 		if (mysql_errno(H->server) == 1295) {
209 			if (nsql) {
210 				efree(nsql);
211 			}
212 			goto fallback;
213 		}
214 		pdo_mysql_error(dbh);
215 		if (nsql) {
216 			efree(nsql);
217 		}
218 		PDO_DBG_RETURN(0);
219 	}
220 	if (nsql) {
221 		efree(nsql);
222 	}
223 
224 	S->num_params = mysql_stmt_param_count(S->stmt);
225 
226 	if (S->num_params) {
227 		S->params_given = 0;
228 #if defined(PDO_USE_MYSQLND)
229 		S->params = NULL;
230 #else
231 		S->params = ecalloc(S->num_params, sizeof(MYSQL_BIND));
232 		S->in_null = ecalloc(S->num_params, sizeof(my_bool));
233 		S->in_length = ecalloc(S->num_params, sizeof(zend_ulong));
234 #endif
235 	}
236 	dbh->alloc_own_columns = 1;
237 
238 	S->max_length = pdo_attr_lval(driver_options, PDO_ATTR_MAX_COLUMN_LEN, 0);
239 
240 	PDO_DBG_RETURN(1);
241 
242 fallback:
243 end:
244 	stmt->supports_placeholders = PDO_PLACEHOLDER_NONE;
245 
246 	PDO_DBG_RETURN(1);
247 }
248 /* }}} */
249 
250 /* {{{ mysql_handle_doer */
mysql_handle_doer(pdo_dbh_t * dbh,const char * sql,size_t sql_len)251 static zend_long mysql_handle_doer(pdo_dbh_t *dbh, const char *sql, size_t sql_len)
252 {
253 	pdo_mysql_db_handle *H = (pdo_mysql_db_handle *)dbh->driver_data;
254 	PDO_DBG_ENTER("mysql_handle_doer");
255 	PDO_DBG_INF_FMT("dbh=%p", dbh);
256 	PDO_DBG_INF_FMT("sql=%.*s", (int)sql_len, sql);
257 
258 	if (mysql_real_query(H->server, sql, sql_len)) {
259 		pdo_mysql_error(dbh);
260 		PDO_DBG_RETURN(-1);
261 	} else {
262 		my_ulonglong c = mysql_affected_rows(H->server);
263 		if (c == (my_ulonglong) -1) {
264 			pdo_mysql_error(dbh);
265 			PDO_DBG_RETURN(H->einfo.errcode ? -1 : 0);
266 		} else {
267 
268 			/* MULTI_QUERY support - eat up all unfetched result sets */
269 			MYSQL_RES* result;
270 			while (mysql_more_results(H->server)) {
271 				if (mysql_next_result(H->server)) {
272 					PDO_DBG_RETURN(1);
273 				}
274 				result = mysql_store_result(H->server);
275 				if (result) {
276 					mysql_free_result(result);
277 				}
278 			}
279 			PDO_DBG_RETURN((int)c);
280 		}
281 	}
282 }
283 /* }}} */
284 
285 /* {{{ pdo_mysql_last_insert_id */
pdo_mysql_last_insert_id(pdo_dbh_t * dbh,const char * name,size_t * len)286 static char *pdo_mysql_last_insert_id(pdo_dbh_t *dbh, const char *name, size_t *len)
287 {
288 	pdo_mysql_db_handle *H = (pdo_mysql_db_handle *)dbh->driver_data;
289 	char *id = php_pdo_int64_to_str(mysql_insert_id(H->server));
290 	PDO_DBG_ENTER("pdo_mysql_last_insert_id");
291 	*len = strlen(id);
292 	PDO_DBG_RETURN(id);
293 }
294 /* }}} */
295 
296 #if defined(PDO_USE_MYSQLND) || MYSQL_VERSION_ID < 50707 || defined(MARIADB_BASE_VERSION)
297 # define mysql_real_escape_string_quote(mysql, to, from, length, quote) \
298 	mysql_real_escape_string(mysql, to, from, length)
299 #endif
300 
301 /* {{{ mysql_handle_quoter */
mysql_handle_quoter(pdo_dbh_t * dbh,const char * unquoted,size_t unquotedlen,char ** quoted,size_t * quotedlen,enum pdo_param_type paramtype)302 static int mysql_handle_quoter(pdo_dbh_t *dbh, const char *unquoted, size_t unquotedlen, char **quoted, size_t *quotedlen, enum pdo_param_type paramtype )
303 {
304 	pdo_mysql_db_handle *H = (pdo_mysql_db_handle *)dbh->driver_data;
305 	zend_bool use_national_character_set = 0;
306 
307 	if (H->assume_national_character_set_strings) {
308 		use_national_character_set = 1;
309 	}
310 	if ((paramtype & PDO_PARAM_STR_NATL) == PDO_PARAM_STR_NATL) {
311 		use_national_character_set = 1;
312 	}
313 	if ((paramtype & PDO_PARAM_STR_CHAR) == PDO_PARAM_STR_CHAR) {
314 		use_national_character_set = 0;
315 	}
316 
317 	PDO_DBG_ENTER("mysql_handle_quoter");
318 	PDO_DBG_INF_FMT("dbh=%p", dbh);
319 	PDO_DBG_INF_FMT("unquoted=%.*s", (int)unquotedlen, unquoted);
320 	*quoted = safe_emalloc(2, unquotedlen, 3 + (use_national_character_set ? 1 : 0));
321 
322 	if (use_national_character_set) {
323 		*quotedlen = mysql_real_escape_string_quote(H->server, *quoted + 2, unquoted, unquotedlen, '\'');
324 		(*quoted)[0] = 'N';
325 		(*quoted)[1] = '\'';
326 
327 		++*quotedlen; /* N prefix */
328 	} else {
329 		*quotedlen = mysql_real_escape_string_quote(H->server, *quoted + 1, unquoted, unquotedlen, '\'');
330 		(*quoted)[0] = '\'';
331 	}
332 
333 	(*quoted)[++*quotedlen] = '\'';
334 	(*quoted)[++*quotedlen] = '\0';
335 	PDO_DBG_INF_FMT("quoted=%.*s", (int)*quotedlen, *quoted);
336 	PDO_DBG_RETURN(1);
337 }
338 /* }}} */
339 
340 /* {{{ mysql_handle_begin */
mysql_handle_begin(pdo_dbh_t * dbh)341 static int mysql_handle_begin(pdo_dbh_t *dbh)
342 {
343 	PDO_DBG_ENTER("mysql_handle_quoter");
344 	PDO_DBG_INF_FMT("dbh=%p", dbh);
345 	PDO_DBG_RETURN(0 <= mysql_handle_doer(dbh, ZEND_STRL("START TRANSACTION")));
346 }
347 /* }}} */
348 
349 /* {{{ mysql_handle_commit */
mysql_handle_commit(pdo_dbh_t * dbh)350 static int mysql_handle_commit(pdo_dbh_t *dbh)
351 {
352 	PDO_DBG_ENTER("mysql_handle_commit");
353 	PDO_DBG_INF_FMT("dbh=%p", dbh);
354 	PDO_DBG_RETURN(0 == mysql_commit(((pdo_mysql_db_handle *)dbh->driver_data)->server));
355 }
356 /* }}} */
357 
358 /* {{{ mysql_handle_rollback */
mysql_handle_rollback(pdo_dbh_t * dbh)359 static int mysql_handle_rollback(pdo_dbh_t *dbh)
360 {
361 	PDO_DBG_ENTER("mysql_handle_rollback");
362 	PDO_DBG_INF_FMT("dbh=%p", dbh);
363 	PDO_DBG_RETURN(0 <= mysql_rollback(((pdo_mysql_db_handle *)dbh->driver_data)->server));
364 }
365 /* }}} */
366 
367 /* {{{ mysql_handle_autocommit */
mysql_handle_autocommit(pdo_dbh_t * dbh)368 static inline int mysql_handle_autocommit(pdo_dbh_t *dbh)
369 {
370 	PDO_DBG_ENTER("mysql_handle_autocommit");
371 	PDO_DBG_INF_FMT("dbh=%p", dbh);
372 	PDO_DBG_INF_FMT("dbh->autocommit=%d", dbh->auto_commit);
373 	PDO_DBG_RETURN(0 <= mysql_autocommit(((pdo_mysql_db_handle *)dbh->driver_data)->server, dbh->auto_commit));
374 }
375 /* }}} */
376 
377 /* {{{ pdo_mysql_set_attribute */
pdo_mysql_set_attribute(pdo_dbh_t * dbh,zend_long attr,zval * val)378 static int pdo_mysql_set_attribute(pdo_dbh_t *dbh, zend_long attr, zval *val)
379 {
380 	zend_long lval = zval_get_long(val);
381 	zend_bool bval = lval ? 1 : 0;
382 	PDO_DBG_ENTER("pdo_mysql_set_attribute");
383 	PDO_DBG_INF_FMT("dbh=%p", dbh);
384 	PDO_DBG_INF_FMT("attr=%l", attr);
385 	switch (attr) {
386 		case PDO_ATTR_AUTOCOMMIT:
387 			/* ignore if the new value equals the old one */
388 			if (dbh->auto_commit ^ bval) {
389 				dbh->auto_commit = bval;
390 				mysql_handle_autocommit(dbh);
391 			}
392 			PDO_DBG_RETURN(1);
393 
394 		case PDO_ATTR_DEFAULT_STR_PARAM:
395 			((pdo_mysql_db_handle *)dbh->driver_data)->assume_national_character_set_strings = lval == PDO_PARAM_STR_NATL ? 1 : 0;
396 			PDO_DBG_RETURN(1);
397 
398 		case PDO_MYSQL_ATTR_USE_BUFFERED_QUERY:
399 			/* ignore if the new value equals the old one */
400 			((pdo_mysql_db_handle *)dbh->driver_data)->buffered = bval;
401 			PDO_DBG_RETURN(1);
402 
403 		case PDO_MYSQL_ATTR_DIRECT_QUERY:
404 		case PDO_ATTR_EMULATE_PREPARES:
405 			/* ignore if the new value equals the old one */
406 			((pdo_mysql_db_handle *)dbh->driver_data)->emulate_prepare = bval;
407 			PDO_DBG_RETURN(1);
408 
409 		case PDO_ATTR_FETCH_TABLE_NAMES:
410 			((pdo_mysql_db_handle *)dbh->driver_data)->fetch_table_names = bval;
411 			PDO_DBG_RETURN(1);
412 
413 #ifndef PDO_USE_MYSQLND
414 		case PDO_MYSQL_ATTR_MAX_BUFFER_SIZE:
415 			if (lval < 0) {
416 				/* TODO: Johannes, can we throw a warning here? */
417  				((pdo_mysql_db_handle *)dbh->driver_data)->max_buffer_size = 1024*1024;
418 				PDO_DBG_INF_FMT("Adjusting invalid buffer size to =%l", ((pdo_mysql_db_handle *)dbh->driver_data)->max_buffer_size);
419 			} else {
420 				((pdo_mysql_db_handle *)dbh->driver_data)->max_buffer_size = lval;
421 			}
422 			PDO_DBG_RETURN(1);
423 			break;
424 #endif
425 
426 		default:
427 			PDO_DBG_RETURN(0);
428 	}
429 }
430 /* }}} */
431 
432 /* {{{ pdo_mysql_get_attribute */
pdo_mysql_get_attribute(pdo_dbh_t * dbh,zend_long attr,zval * return_value)433 static int pdo_mysql_get_attribute(pdo_dbh_t *dbh, zend_long attr, zval *return_value)
434 {
435 	pdo_mysql_db_handle *H = (pdo_mysql_db_handle *)dbh->driver_data;
436 
437 	PDO_DBG_ENTER("pdo_mysql_get_attribute");
438 	PDO_DBG_INF_FMT("dbh=%p", dbh);
439 	PDO_DBG_INF_FMT("attr=%l", attr);
440 	switch (attr) {
441 		case PDO_ATTR_CLIENT_VERSION:
442 			ZVAL_STRING(return_value, (char *)mysql_get_client_info());
443 			break;
444 
445 		case PDO_ATTR_SERVER_VERSION:
446 			ZVAL_STRING(return_value, (char *)mysql_get_server_info(H->server));
447 			break;
448 
449 		case PDO_ATTR_CONNECTION_STATUS:
450 			ZVAL_STRING(return_value, (char *)mysql_get_host_info(H->server));
451 			break;
452 		case PDO_ATTR_SERVER_INFO: {
453 #if defined(PDO_USE_MYSQLND)
454 			zend_string *tmp;
455 
456 			if (mysqlnd_stat(H->server, &tmp) == PASS) {
457 				ZVAL_STR(return_value, tmp);
458 #else
459 			char *tmp;
460 			if ((tmp = (char *)mysql_stat(H->server))) {
461 				ZVAL_STRING(return_value, tmp);
462 #endif
463 			} else {
464 				pdo_mysql_error(dbh);
465 				PDO_DBG_RETURN(-1);
466 			}
467 		}
468 			break;
469 
470 		case PDO_ATTR_AUTOCOMMIT:
471 			ZVAL_LONG(return_value, dbh->auto_commit);
472 			break;
473 
474 		case PDO_ATTR_DEFAULT_STR_PARAM:
475 			ZVAL_LONG(return_value, H->assume_national_character_set_strings ? PDO_PARAM_STR_NATL : PDO_PARAM_STR_CHAR);
476 			break;
477 
478 		case PDO_MYSQL_ATTR_USE_BUFFERED_QUERY:
479 			ZVAL_LONG(return_value, H->buffered);
480 			break;
481 
482 		case PDO_ATTR_EMULATE_PREPARES:
483 		case PDO_MYSQL_ATTR_DIRECT_QUERY:
484 			ZVAL_LONG(return_value, H->emulate_prepare);
485 			break;
486 
487 #ifndef PDO_USE_MYSQLND
488 		case PDO_MYSQL_ATTR_MAX_BUFFER_SIZE:
489 			ZVAL_LONG(return_value, H->max_buffer_size);
490 			break;
491 #else
492 		case PDO_MYSQL_ATTR_LOCAL_INFILE:
493 			ZVAL_BOOL(
494 				return_value,
495 				(H->server->data->options->flags & CLIENT_LOCAL_FILES) == CLIENT_LOCAL_FILES);
496 			break;
497 #endif
498 
499 		default:
500 			PDO_DBG_RETURN(0);
501 	}
502 
503 	PDO_DBG_RETURN(1);
504 }
505 /* }}} */
506 
507 /* {{{ pdo_mysql_check_liveness */
508 static int pdo_mysql_check_liveness(pdo_dbh_t *dbh)
509 {
510 	pdo_mysql_db_handle *H = (pdo_mysql_db_handle *)dbh->driver_data;
511 
512 	PDO_DBG_ENTER("pdo_mysql_check_liveness");
513 	PDO_DBG_INF_FMT("dbh=%p", dbh);
514 
515 	if (mysql_ping(H->server)) {
516 		PDO_DBG_RETURN(FAILURE);
517 	}
518 	PDO_DBG_RETURN(SUCCESS);
519 }
520 /* }}} */
521 
522 /* {{{ pdo_mysql_request_shutdown */
523 static void pdo_mysql_request_shutdown(pdo_dbh_t *dbh)
524 {
525 	pdo_mysql_db_handle *H = (pdo_mysql_db_handle *)dbh->driver_data;
526 
527 	PDO_DBG_ENTER("pdo_mysql_request_shutdown");
528 	PDO_DBG_INF_FMT("dbh=%p", dbh);
529 #ifdef PDO_USE_MYSQLND
530 	if (H->server) {
531 		mysqlnd_end_psession(H->server);
532 	}
533 #endif
534 }
535 /* }}} */
536 
537 /* {{{ mysql_methods */
538 static const struct pdo_dbh_methods mysql_methods = {
539 	mysql_handle_closer,
540 	mysql_handle_preparer,
541 	mysql_handle_doer,
542 	mysql_handle_quoter,
543 	mysql_handle_begin,
544 	mysql_handle_commit,
545 	mysql_handle_rollback,
546 	pdo_mysql_set_attribute,
547 	pdo_mysql_last_insert_id,
548 	pdo_mysql_fetch_error_func,
549 	pdo_mysql_get_attribute,
550 	pdo_mysql_check_liveness,
551 	NULL,
552 	pdo_mysql_request_shutdown,
553 	NULL
554 };
555 /* }}} */
556 
557 #ifdef PHP_WIN32
558 # define PDO_DEFAULT_MYSQL_UNIX_ADDR	NULL
559 #else
560 # define PDO_DEFAULT_MYSQL_UNIX_ADDR	PDO_MYSQL_G(default_socket)
561 #endif
562 
563 /* {{{ pdo_mysql_handle_factory */
564 static int pdo_mysql_handle_factory(pdo_dbh_t *dbh, zval *driver_options)
565 {
566 	pdo_mysql_db_handle *H;
567 	size_t i;
568 	int ret = 0;
569 	char *host = NULL, *unix_socket = NULL;
570 	unsigned int port = 3306;
571 	char *dbname;
572 	struct pdo_data_src_parser vars[] = {
573 		{ "charset",  NULL,	0 },
574 		{ "dbname",   "",	0 },
575 		{ "host",   "localhost",	0 },
576 		{ "port",   "3306",	0 },
577 		{ "unix_socket",  PDO_DEFAULT_MYSQL_UNIX_ADDR,	0 },
578 	};
579 	int connect_opts = 0
580 #ifdef CLIENT_MULTI_RESULTS
581 		|CLIENT_MULTI_RESULTS
582 #endif
583 		;
584 #if defined(PDO_USE_MYSQLND)
585 	size_t dbname_len = 0;
586 	size_t password_len = 0;
587 #endif
588 
589 #ifdef CLIENT_MULTI_STATEMENTS
590 	if (!driver_options) {
591 		connect_opts |= CLIENT_MULTI_STATEMENTS;
592 	} else if (pdo_attr_lval(driver_options, PDO_MYSQL_ATTR_MULTI_STATEMENTS, 1)) {
593 		connect_opts |= CLIENT_MULTI_STATEMENTS;
594 	}
595 #endif
596 
597 	PDO_DBG_ENTER("pdo_mysql_handle_factory");
598 	PDO_DBG_INF_FMT("dbh=%p", dbh);
599 #ifdef CLIENT_MULTI_RESULTS
600 	PDO_DBG_INF("multi results");
601 #endif
602 
603 	php_pdo_parse_data_source(dbh->data_source, dbh->data_source_len, vars, 5);
604 
605 	H = pecalloc(1, sizeof(pdo_mysql_db_handle), dbh->is_persistent);
606 
607 	H->einfo.errcode = 0;
608 	H->einfo.errmsg = NULL;
609 
610 	/* allocate an environment */
611 
612 	/* handle for the server */
613 	if (!(H->server = pdo_mysql_init(dbh->is_persistent))) {
614 		pdo_mysql_error(dbh);
615 		goto cleanup;
616 	}
617 #if defined(PDO_USE_MYSQLND)
618 	if (dbh->is_persistent) {
619 		mysqlnd_restart_psession(H->server);
620 	}
621 #endif
622 
623 	dbh->driver_data = H;
624 
625 	dbh->skip_param_evt =
626 		1 << PDO_PARAM_EVT_FREE |
627 		1 << PDO_PARAM_EVT_EXEC_POST |
628 		1 << PDO_PARAM_EVT_FETCH_PRE |
629 		1 << PDO_PARAM_EVT_FETCH_POST |
630 		1 << PDO_PARAM_EVT_NORMALIZE;
631 
632 #ifndef PDO_USE_MYSQLND
633 	H->max_buffer_size = 1024*1024;
634 #endif
635 
636 	H->assume_national_character_set_strings = 0;
637 	H->buffered = H->emulate_prepare = 1;
638 
639 	/* handle MySQL options */
640 	if (driver_options) {
641 		zend_long connect_timeout = pdo_attr_lval(driver_options, PDO_ATTR_TIMEOUT, 30);
642 		unsigned int local_infile = (unsigned int) pdo_attr_lval(driver_options, PDO_MYSQL_ATTR_LOCAL_INFILE, 0);
643 		zend_string *init_cmd = NULL;
644 #ifndef PDO_USE_MYSQLND
645 		zend_string *default_file = NULL, *default_group = NULL;
646 #endif
647 		zend_long compress = 0;
648 		zend_string *ssl_key = NULL, *ssl_cert = NULL, *ssl_ca = NULL, *ssl_capath = NULL, *ssl_cipher = NULL;
649 		H->buffered = pdo_attr_lval(driver_options, PDO_MYSQL_ATTR_USE_BUFFERED_QUERY, 1);
650 
651 		H->emulate_prepare = pdo_attr_lval(driver_options,
652 			PDO_MYSQL_ATTR_DIRECT_QUERY, H->emulate_prepare);
653 		H->emulate_prepare = pdo_attr_lval(driver_options,
654 			PDO_ATTR_EMULATE_PREPARES, H->emulate_prepare);
655 
656 		H->assume_national_character_set_strings = pdo_attr_lval(driver_options,
657 			PDO_ATTR_DEFAULT_STR_PARAM, 0) == PDO_PARAM_STR_NATL ? 1 : 0;
658 
659 #ifndef PDO_USE_MYSQLND
660 		H->max_buffer_size = pdo_attr_lval(driver_options, PDO_MYSQL_ATTR_MAX_BUFFER_SIZE, H->max_buffer_size);
661 #endif
662 
663 		if (pdo_attr_lval(driver_options, PDO_MYSQL_ATTR_FOUND_ROWS, 0)) {
664 			connect_opts |= CLIENT_FOUND_ROWS;
665 		}
666 
667 		if (pdo_attr_lval(driver_options, PDO_MYSQL_ATTR_IGNORE_SPACE, 0)) {
668 			connect_opts |= CLIENT_IGNORE_SPACE;
669 		}
670 
671 		if (mysql_options(H->server, MYSQL_OPT_CONNECT_TIMEOUT, (const char *)&connect_timeout)) {
672 			pdo_mysql_error(dbh);
673 			goto cleanup;
674 		}
675 
676 #ifndef PDO_USE_MYSQLND
677 		if (PG(open_basedir) && PG(open_basedir)[0] != '\0') {
678 			local_infile = 0;
679 		}
680 #endif
681 #if defined(MYSQL_OPT_LOCAL_INFILE) || defined(PDO_USE_MYSQLND)
682 		if (mysql_options(H->server, MYSQL_OPT_LOCAL_INFILE, (const char *)&local_infile)) {
683 			pdo_mysql_error(dbh);
684 			goto cleanup;
685 		}
686 #endif
687 #ifdef MYSQL_OPT_RECONNECT
688 		/* since 5.0.3, the default for this option is 0 if not specified.
689 		 * we want the old behaviour
690 		 * mysqlnd doesn't support reconnect, thus we don't have "|| defined(PDO_USE_MYSQLND)"
691 		*/
692 		{
693 			zend_long reconnect = 1;
694 			mysql_options(H->server, MYSQL_OPT_RECONNECT, (const char*)&reconnect);
695 		}
696 #endif
697 		init_cmd = pdo_attr_strval(driver_options, PDO_MYSQL_ATTR_INIT_COMMAND, NULL);
698 		if (init_cmd) {
699 			if (mysql_options(H->server, MYSQL_INIT_COMMAND, (const char *)ZSTR_VAL(init_cmd))) {
700 				zend_string_release_ex(init_cmd, 0);
701 				pdo_mysql_error(dbh);
702 				goto cleanup;
703 			}
704 			zend_string_release_ex(init_cmd, 0);
705 		}
706 #ifndef PDO_USE_MYSQLND
707 		default_file = pdo_attr_strval(driver_options, PDO_MYSQL_ATTR_READ_DEFAULT_FILE, NULL);
708 		if (default_file) {
709 			if (mysql_options(H->server, MYSQL_READ_DEFAULT_FILE, (const char *)ZSTR_VAL(default_file))) {
710 				zend_string_release_ex(default_file, 0);
711 				pdo_mysql_error(dbh);
712 				goto cleanup;
713 			}
714 			zend_string_release_ex(default_file, 0);
715 		}
716 
717 		default_group = pdo_attr_strval(driver_options, PDO_MYSQL_ATTR_READ_DEFAULT_GROUP, NULL);
718 		if (default_group) {
719 			if (mysql_options(H->server, MYSQL_READ_DEFAULT_GROUP, (const char *)ZSTR_VAL(default_group))) {
720 				zend_string_release_ex(default_group, 0);
721 				pdo_mysql_error(dbh);
722 				goto cleanup;
723 			}
724 			zend_string_release_ex(default_group, 0);
725 		}
726 #endif
727 		compress = pdo_attr_lval(driver_options, PDO_MYSQL_ATTR_COMPRESS, 0);
728 		if (compress) {
729 			if (mysql_options(H->server, MYSQL_OPT_COMPRESS, 0)) {
730 				pdo_mysql_error(dbh);
731 				goto cleanup;
732 			}
733 		}
734 
735 		ssl_key = pdo_attr_strval(driver_options, PDO_MYSQL_ATTR_SSL_KEY, NULL);
736 		ssl_cert = pdo_attr_strval(driver_options, PDO_MYSQL_ATTR_SSL_CERT, NULL);
737 		ssl_ca = pdo_attr_strval(driver_options, PDO_MYSQL_ATTR_SSL_CA, NULL);
738 		ssl_capath = pdo_attr_strval(driver_options, PDO_MYSQL_ATTR_SSL_CAPATH, NULL);
739 		ssl_cipher = pdo_attr_strval(driver_options, PDO_MYSQL_ATTR_SSL_CIPHER, NULL);
740 
741 		if (ssl_key || ssl_cert || ssl_ca || ssl_capath || ssl_cipher) {
742 			mysql_ssl_set(H->server,
743 					ssl_key? ZSTR_VAL(ssl_key) : NULL,
744 					ssl_cert? ZSTR_VAL(ssl_cert) : NULL,
745 					ssl_ca? ZSTR_VAL(ssl_ca) : NULL,
746 					ssl_capath? ZSTR_VAL(ssl_capath) : NULL,
747 					ssl_cipher? ZSTR_VAL(ssl_cipher) : NULL);
748 			if (ssl_key) {
749 				zend_string_release_ex(ssl_key, 0);
750 			}
751 			if (ssl_cert) {
752 				zend_string_release_ex(ssl_cert, 0);
753 			}
754 			if (ssl_ca) {
755 				zend_string_release_ex(ssl_ca, 0);
756 			}
757 			if (ssl_capath) {
758 				zend_string_release_ex(ssl_capath, 0);
759 			}
760 			if (ssl_cipher) {
761 				zend_string_release_ex(ssl_cipher, 0);
762 			}
763 		}
764 
765 #if MYSQL_VERSION_ID > 50605 || defined(PDO_USE_MYSQLND)
766 		{
767 			zend_string *public_key = pdo_attr_strval(driver_options, PDO_MYSQL_ATTR_SERVER_PUBLIC_KEY, NULL);
768 			if (public_key) {
769 				if (mysql_options(H->server, MYSQL_SERVER_PUBLIC_KEY, ZSTR_VAL(public_key))) {
770 					pdo_mysql_error(dbh);
771 					zend_string_release_ex(public_key, 0);
772 					goto cleanup;
773 				}
774 				zend_string_release_ex(public_key, 0);
775 			}
776 		}
777 #endif
778 
779 #ifdef PDO_USE_MYSQLND
780 		{
781 			zend_long ssl_verify_cert = pdo_attr_lval(driver_options,
782 					PDO_MYSQL_ATTR_SSL_VERIFY_SERVER_CERT, -1);
783 			if (ssl_verify_cert != -1) {
784 				connect_opts |= ssl_verify_cert ?
785 					CLIENT_SSL_VERIFY_SERVER_CERT:
786 					CLIENT_SSL_DONT_VERIFY_SERVER_CERT;
787 			}
788 		}
789 #endif
790 	} else {
791 #if defined(MYSQL_OPT_LOCAL_INFILE) || defined(PDO_USE_MYSQLND)
792 		// in case there are no driver options disable 'local infile' explicitly
793 		unsigned int local_infile = 0;
794 		if (mysql_options(H->server, MYSQL_OPT_LOCAL_INFILE, (const char *)&local_infile)) {
795 			pdo_mysql_error(dbh);
796 			goto cleanup;
797 		}
798 #endif
799 	}
800 
801 #ifdef PDO_MYSQL_HAS_CHARSET
802 	if (vars[0].optval && mysql_options(H->server, MYSQL_SET_CHARSET_NAME, vars[0].optval)) {
803 		pdo_mysql_error(dbh);
804 		goto cleanup;
805 	}
806 #endif
807 
808 	dbname = vars[1].optval;
809 	host = vars[2].optval;
810 	if(vars[3].optval) {
811 		port = atoi(vars[3].optval);
812 	}
813 
814 #ifdef PHP_WIN32
815 	if (vars[2].optval && !strcmp(".", vars[2].optval)) {
816 #else
817 	if (vars[2].optval && !strcmp("localhost", vars[2].optval)) {
818 #endif
819 		unix_socket = vars[4].optval;
820 	}
821 
822 	/* TODO: - Check zval cache + ZTS */
823 #ifdef PDO_USE_MYSQLND
824 	if (dbname) {
825 		dbname_len = strlen(dbname);
826 	}
827 
828 	if (dbh->password) {
829 		password_len = strlen(dbh->password);
830 	}
831 
832 	if (mysqlnd_connect(H->server, host, dbh->username, dbh->password, password_len, dbname, dbname_len,
833 						port, unix_socket, connect_opts, MYSQLND_CLIENT_NO_FLAG) == NULL) {
834 #else
835 	if (mysql_real_connect(H->server, host, dbh->username, dbh->password, dbname, port, unix_socket, connect_opts) == NULL) {
836 #endif
837 		pdo_mysql_error(dbh);
838 		goto cleanup;
839 	}
840 
841 	if (!dbh->auto_commit) {
842 		mysql_handle_autocommit(dbh);
843 	}
844 
845 	H->attached = 1;
846 
847 	dbh->alloc_own_columns = 1;
848 	dbh->max_escaped_char_length = 2;
849 	dbh->methods = &mysql_methods;
850 
851 	ret = 1;
852 
853 cleanup:
854 	for (i = 0; i < sizeof(vars)/sizeof(vars[0]); i++) {
855 		if (vars[i].freeme) {
856 			efree(vars[i].optval);
857 		}
858 	}
859 
860 	dbh->methods = &mysql_methods;
861 
862 	PDO_DBG_RETURN(ret);
863 }
864 /* }}} */
865 
866 const pdo_driver_t pdo_mysql_driver = {
867 	PDO_DRIVER_HEADER(mysql),
868 	pdo_mysql_handle_factory
869 };
870 
871 /*
872  * Local variables:
873  * tab-width: 4
874  * c-basic-offset: 4
875  * End:
876  * vim600: noet sw=4 ts=4 fdm=marker
877  * vim<600: noet sw=4 ts=4
878  */
879