From: Adam Baratz Date: Mon, 20 Mar 2017 22:25:50 +0000 (-0400) Subject: Add PDO parameter types for national character set strings X-Git-Tag: php-7.2.0alpha1~234 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=4afce8ec8c6660ebd9f9eb174d2614361d1c6129;p=php Add PDO parameter types for national character set strings --- diff --git a/NEWS b/NEWS index bd66e01817..7a28686f07 100644 --- a/NEWS +++ b/NEWS @@ -112,6 +112,7 @@ PHP NEWS - PDO: . Add "Sent SQL" to debug dump for emulated prepares. (Adam Baratz) + . Add parameter types for national character set strings. (Adam Baratz) - PDO_DBlib: . Fixed bug #73234 (Emulated statements let value dictate parameter type). diff --git a/ext/pdo/pdo_dbh.c b/ext/pdo/pdo_dbh.c index 4cfd01020f..cb5f1e1bc3 100644 --- a/ext/pdo/pdo_dbh.c +++ b/ext/pdo/pdo_dbh.c @@ -1413,6 +1413,9 @@ void pdo_dbh_init(void) REGISTER_PDO_CLASS_CONST_LONG("PARAM_LOB", (zend_long)PDO_PARAM_LOB); REGISTER_PDO_CLASS_CONST_LONG("PARAM_STMT", (zend_long)PDO_PARAM_STMT); REGISTER_PDO_CLASS_CONST_LONG("PARAM_INPUT_OUTPUT", (zend_long)PDO_PARAM_INPUT_OUTPUT); + REGISTER_PDO_CLASS_CONST_LONG("PARAM_STR_NATL", (zend_long)PDO_PARAM_STR_NATL); + REGISTER_PDO_CLASS_CONST_LONG("PARAM_STR_CHAR", (zend_long)PDO_PARAM_STR_CHAR); + REGISTER_PDO_CLASS_CONST_LONG("PARAM_EVT_ALLOC", (zend_long)PDO_PARAM_EVT_ALLOC); REGISTER_PDO_CLASS_CONST_LONG("PARAM_EVT_FREE", (zend_long)PDO_PARAM_EVT_FREE); @@ -1462,6 +1465,7 @@ void pdo_dbh_init(void) REGISTER_PDO_CLASS_CONST_LONG("ATTR_MAX_COLUMN_LEN", (zend_long)PDO_ATTR_MAX_COLUMN_LEN); REGISTER_PDO_CLASS_CONST_LONG("ATTR_EMULATE_PREPARES", (zend_long)PDO_ATTR_EMULATE_PREPARES); REGISTER_PDO_CLASS_CONST_LONG("ATTR_DEFAULT_FETCH_MODE", (zend_long)PDO_ATTR_DEFAULT_FETCH_MODE); + REGISTER_PDO_CLASS_CONST_LONG("ATTR_DEFAULT_STR_PARAM", (zend_long)PDO_ATTR_DEFAULT_STR_PARAM); REGISTER_PDO_CLASS_CONST_LONG("ERRMODE_SILENT", (zend_long)PDO_ERRMODE_SILENT); REGISTER_PDO_CLASS_CONST_LONG("ERRMODE_WARNING", (zend_long)PDO_ERRMODE_WARNING); diff --git a/ext/pdo/php_pdo_driver.h b/ext/pdo/php_pdo_driver.h index 9d5753ef9e..bcbe5b3672 100644 --- a/ext/pdo/php_pdo_driver.h +++ b/ext/pdo/php_pdo_driver.h @@ -46,7 +46,7 @@ PDO_API char *php_pdo_int64_to_str(pdo_int64_t i64); # define FALSE 0 #endif -#define PDO_DRIVER_API 20161020 +#define PDO_DRIVER_API 20170320 enum pdo_param_type { PDO_PARAM_NULL, @@ -77,7 +77,15 @@ enum pdo_param_type { PDO_PARAM_ZVAL, /* magic flag to denote a parameter as being input/output */ - PDO_PARAM_INPUT_OUTPUT = 0x80000000 + PDO_PARAM_INPUT_OUTPUT = 0x80000000, + + /* magic flag to denote a string that uses the national character set + see section 4.2.1 of SQL-92: http://www.contrib.andrew.cmu.edu/~shadow/sql/sql1992.txt + */ + PDO_PARAM_STR_NATL = 0x40000000, + + /* magic flag to denote a string that uses the regular character set */ + PDO_PARAM_STR_CHAR = 0x20000000, }; #define PDO_PARAM_FLAGS 0xFFFF0000 @@ -140,6 +148,7 @@ enum pdo_attribute_type { PDO_ATTR_MAX_COLUMN_LEN, /* make database calculate maximum length of data found in a column */ PDO_ATTR_DEFAULT_FETCH_MODE, /* Set the default fetch mode */ PDO_ATTR_EMULATE_PREPARES, /* use query emulation rather than native */ + PDO_ATTR_DEFAULT_STR_PARAM, /* set the default string parameter type (see the PDO::PARAM_STR_* magic flags) */ /* this defines the start of the range for driver specific options. * Drivers should define their own attribute constants beginning with this diff --git a/ext/pdo_dblib/dblib_driver.c b/ext/pdo_dblib/dblib_driver.c index bb37ace47a..efc8dc1197 100644 --- a/ext/pdo_dblib/dblib_driver.c +++ b/ext/pdo_dblib/dblib_driver.c @@ -151,10 +151,23 @@ static zend_long dblib_handle_doer(pdo_dbh_t *dbh, const char *sql, size_t sql_l static int dblib_handle_quoter(pdo_dbh_t *dbh, const char *unquoted, size_t unquotedlen, char **quoted, size_t *quotedlen, enum pdo_param_type paramtype) { + pdo_dblib_db_handle *H = (pdo_dblib_db_handle *)dbh->driver_data; + zend_bool use_national_character_set = 0; + size_t i; char * q; *quotedlen = 0; + if (H->assume_national_character_set_strings) { + use_national_character_set = 1; + } + if ((paramtype & PDO_PARAM_STR_NATL) == PDO_PARAM_STR_NATL) { + use_national_character_set = 1; + } + if ((paramtype & PDO_PARAM_STR_CHAR) == PDO_PARAM_STR_CHAR) { + use_national_character_set = 0; + } + /* Detect quoted length, adding extra char for doubled single quotes */ for (i = 0; i < unquotedlen; i++) { if (unquoted[i] == '\'') ++*quotedlen; @@ -162,7 +175,13 @@ static int dblib_handle_quoter(pdo_dbh_t *dbh, const char *unquoted, size_t unqu } *quotedlen += 2; /* +2 for opening, closing quotes */ + if (use_national_character_set) { + ++*quotedlen; /* N prefix */ + } q = *quoted = emalloc(*quotedlen + 1); /* Add byte for terminal null */ + if (use_national_character_set) { + *q++ = 'N'; + } *q++ = '\''; for (i = 0; i < unquotedlen; i++) { @@ -256,12 +275,17 @@ char *dblib_handle_last_id(pdo_dbh_t *dbh, const char *name, size_t *len) static int dblib_set_attr(pdo_dbh_t *dbh, zend_long attr, zval *val) { + pdo_dblib_db_handle *H = (pdo_dblib_db_handle *)dbh->driver_data; + switch(attr) { + case PDO_ATTR_DEFAULT_STR_PARAM: + H->assume_national_character_set_strings = zval_get_long(val) == PDO_PARAM_STR_NATL ? 1 : 0; + return 1; case PDO_ATTR_TIMEOUT: case PDO_DBLIB_ATTR_QUERY_TIMEOUT: return SUCCEED == dbsettime(zval_get_long(val)) ? 1 : 0; case PDO_DBLIB_ATTR_STRINGIFY_UNIQUEIDENTIFIER: - ((pdo_dblib_db_handle *)dbh->driver_data)->stringify_uniqueidentifier = zval_get_long(val); + H->stringify_uniqueidentifier = zval_get_long(val); return 1; default: return 0; @@ -270,14 +294,20 @@ static int dblib_set_attr(pdo_dbh_t *dbh, zend_long attr, zval *val) static int dblib_get_attribute(pdo_dbh_t *dbh, zend_long attr, zval *return_value) { + pdo_dblib_db_handle *H = (pdo_dblib_db_handle *)dbh->driver_data; + switch (attr) { + case PDO_ATTR_DEFAULT_STR_PARAM: + ZVAL_LONG(return_value, H->assume_national_character_set_strings ? PDO_PARAM_STR_NATL : PDO_PARAM_STR_CHAR); + break; + case PDO_ATTR_EMULATE_PREPARES: /* this is the only option available, but expose it so common tests and whatever else can introspect */ ZVAL_TRUE(return_value); break; case PDO_DBLIB_ATTR_STRINGIFY_UNIQUEIDENTIFIER: - ZVAL_BOOL(return_value, ((pdo_dblib_db_handle *)dbh->driver_data)->stringify_uniqueidentifier); + ZVAL_BOOL(return_value, H->stringify_uniqueidentifier); break; case PDO_DBLIB_ATTR_VERSION: @@ -355,6 +385,7 @@ static int pdo_dblib_handle_factory(pdo_dbh_t *dbh, zval *driver_options) H = pecalloc(1, sizeof(*H), dbh->is_persistent); H->login = dblogin(); H->err.sqlstate = dbh->error_code; + H->assume_national_character_set_strings = 0; H->stringify_uniqueidentifier = 0; if (!H->login) { @@ -376,6 +407,7 @@ static int pdo_dblib_handle_factory(pdo_dbh_t *dbh, zval *driver_options) dbsetlogintime(connect_timeout); /* Connection/Login Timeout */ dbsettime(query_timeout); /* Statement Timeout */ + H->assume_national_character_set_strings = pdo_attr_lval(driver_options, PDO_ATTR_DEFAULT_STR_PARAM, 0) == PDO_PARAM_STR_NATL ? 1 : 0; H->stringify_uniqueidentifier = pdo_attr_lval(driver_options, PDO_DBLIB_ATTR_STRINGIFY_UNIQUEIDENTIFIER, 0); } diff --git a/ext/pdo_dblib/php_pdo_dblib_int.h b/ext/pdo_dblib/php_pdo_dblib_int.h index 7eb6b1d9a1..c40697680f 100644 --- a/ext/pdo_dblib/php_pdo_dblib_int.h +++ b/ext/pdo_dblib/php_pdo_dblib_int.h @@ -116,6 +116,7 @@ typedef struct { DBPROCESS *link; pdo_dblib_err err; + unsigned assume_national_character_set_strings:1; unsigned stringify_uniqueidentifier:1; } pdo_dblib_db_handle; diff --git a/ext/pdo_dblib/tests/pdo_dblib_param_str_natl.phpt b/ext/pdo_dblib/tests/pdo_dblib_param_str_natl.phpt new file mode 100644 index 0000000000..4753a91a51 --- /dev/null +++ b/ext/pdo_dblib/tests/pdo_dblib_param_str_natl.phpt @@ -0,0 +1,27 @@ +--TEST-- +PDO_DBLIB: national character set values are quoted correctly in queries +--SKIPIF-- + +--FILE-- +prepare('SELECT :value'); +$stmt->bindValue(':value', 'foo', PDO::PARAM_STR | PDO::PARAM_STR_NATL); +$stmt->execute(); + +var_dump($stmt->debugDumpParams()); +?> +--EXPECT-- +SQL: [13] SELECT :value +Sent SQL: [13] SELECT N'foo' +Params: 1 +Key: Name: [6] :value +paramno=-1 +name=[6] ":value" +is_param=1 +param_type=1073741826 +NULL diff --git a/ext/pdo_dblib/tests/pdo_dblib_quote.phpt b/ext/pdo_dblib/tests/pdo_dblib_quote.phpt index 543093d6ce..854e14cb9d 100644 --- a/ext/pdo_dblib/tests/pdo_dblib_quote.phpt +++ b/ext/pdo_dblib/tests/pdo_dblib_quote.phpt @@ -14,7 +14,20 @@ var_dump($db->quote(42, PDO::PARAM_INT)); var_dump($db->quote(null, PDO::PARAM_NULL)); var_dump($db->quote('\'', PDO::PARAM_STR)); var_dump($db->quote('foo', PDO::PARAM_STR)); +var_dump($db->quote('foo', PDO::PARAM_STR | PDO::PARAM_STR_CHAR)); +var_dump($db->quote('über', PDO::PARAM_STR | PDO::PARAM_STR_NATL)); + +var_dump($db->getAttribute(PDO::ATTR_DEFAULT_STR_PARAM) === PDO::PARAM_STR_CHAR); +$db->setAttribute(PDO::ATTR_DEFAULT_STR_PARAM, PDO::PARAM_STR_NATL); +var_dump($db->getAttribute(PDO::ATTR_DEFAULT_STR_PARAM) === PDO::PARAM_STR_NATL); + +var_dump($db->quote('foo', PDO::PARAM_STR | PDO::PARAM_STR_CHAR)); var_dump($db->quote('über', PDO::PARAM_STR)); +var_dump($db->quote('über', PDO::PARAM_STR | PDO::PARAM_STR_NATL)); + +$db = new PDO($dsn, $user, $pass, [PDO::ATTR_DEFAULT_STR_PARAM => PDO::PARAM_STR_NATL]); +var_dump($db->getAttribute(PDO::ATTR_DEFAULT_STR_PARAM) === PDO::PARAM_STR_NATL); + ?> --EXPECT-- string(3) "'1'" @@ -23,4 +36,11 @@ string(4) "'42'" string(2) "''" string(4) "''''" string(5) "'foo'" -string(7) "'über'" +string(5) "'foo'" +string(8) "N'über'" +bool(true) +bool(true) +string(5) "'foo'" +string(8) "N'über'" +string(8) "N'über'" +bool(true) diff --git a/ext/pdo_mysql/mysql_driver.c b/ext/pdo_mysql/mysql_driver.c index a716c7b1e7..08b546ce10 100644 --- a/ext/pdo_mysql/mysql_driver.c +++ b/ext/pdo_mysql/mysql_driver.c @@ -300,12 +300,35 @@ static char *pdo_mysql_last_insert_id(pdo_dbh_t *dbh, const char *name, size_t * 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 ) { pdo_mysql_db_handle *H = (pdo_mysql_db_handle *)dbh->driver_data; + zend_bool use_national_character_set = 0; + + if (H->assume_national_character_set_strings) { + use_national_character_set = 1; + } + if ((paramtype & PDO_PARAM_STR_NATL) == PDO_PARAM_STR_NATL) { + use_national_character_set = 1; + } + if ((paramtype & PDO_PARAM_STR_CHAR) == PDO_PARAM_STR_CHAR) { + use_national_character_set = 0; + } + PDO_DBG_ENTER("mysql_handle_quoter"); PDO_DBG_INF_FMT("dbh=%p", dbh); PDO_DBG_INF_FMT("unquoted=%.*s", (int)unquotedlen, unquoted); - *quoted = safe_emalloc(2, unquotedlen, 3); - *quotedlen = mysql_real_escape_string(H->server, *quoted + 1, unquoted, unquotedlen); - (*quoted)[0] =(*quoted)[++*quotedlen] = '\''; + *quoted = safe_emalloc(2, unquotedlen, 3 + (use_national_character_set ? 1 : 0)); + + if (use_national_character_set) { + *quotedlen = mysql_real_escape_string(H->server, *quoted + 2, unquoted, unquotedlen); + (*quoted)[0] = 'N'; + (*quoted)[1] = '\''; + + ++*quotedlen; /* N prefix */ + } else { + *quotedlen = mysql_real_escape_string(H->server, *quoted + 1, unquoted, unquotedlen); + (*quoted)[0] = '\''; + } + + (*quoted)[++*quotedlen] = '\''; (*quoted)[++*quotedlen] = '\0'; PDO_DBG_INF_FMT("quoted=%.*s", (int)*quotedlen, *quoted); PDO_DBG_RETURN(1); @@ -369,7 +392,7 @@ static inline int mysql_handle_autocommit(pdo_dbh_t *dbh) static int pdo_mysql_set_attribute(pdo_dbh_t *dbh, zend_long attr, zval *val) { zend_long lval = zval_get_long(val); - zend_bool bval = lval? 1 : 0; + zend_bool bval = lval ? 1 : 0; PDO_DBG_ENTER("pdo_mysql_set_attribute"); PDO_DBG_INF_FMT("dbh=%p", dbh); PDO_DBG_INF_FMT("attr=%l", attr); @@ -382,18 +405,25 @@ static int pdo_mysql_set_attribute(pdo_dbh_t *dbh, zend_long attr, zval *val) } PDO_DBG_RETURN(1); + case PDO_ATTR_DEFAULT_STR_PARAM: + ((pdo_mysql_db_handle *)dbh->driver_data)->assume_national_character_set_strings = lval == PDO_PARAM_STR_NATL ? 1 : 0; + PDO_DBG_RETURN(1); + case PDO_MYSQL_ATTR_USE_BUFFERED_QUERY: /* ignore if the new value equals the old one */ ((pdo_mysql_db_handle *)dbh->driver_data)->buffered = bval; PDO_DBG_RETURN(1); + case PDO_MYSQL_ATTR_DIRECT_QUERY: case PDO_ATTR_EMULATE_PREPARES: /* ignore if the new value equals the old one */ ((pdo_mysql_db_handle *)dbh->driver_data)->emulate_prepare = bval; PDO_DBG_RETURN(1); + case PDO_ATTR_FETCH_TABLE_NAMES: ((pdo_mysql_db_handle *)dbh->driver_data)->fetch_table_names = bval; PDO_DBG_RETURN(1); + #ifndef PDO_USE_MYSQLND case PDO_MYSQL_ATTR_MAX_BUFFER_SIZE: if (lval < 0) { @@ -450,10 +480,15 @@ static int pdo_mysql_get_attribute(pdo_dbh_t *dbh, zend_long attr, zval *return_ } } break; + case PDO_ATTR_AUTOCOMMIT: ZVAL_LONG(return_value, dbh->auto_commit); break; + case PDO_ATTR_DEFAULT_STR_PARAM: + ZVAL_LONG(return_value, H->assume_national_character_set_strings ? PDO_PARAM_STR_NATL : PDO_PARAM_STR_CHAR); + break; + case PDO_MYSQL_ATTR_USE_BUFFERED_QUERY: ZVAL_LONG(return_value, H->buffered); break; @@ -597,6 +632,7 @@ static int pdo_mysql_handle_factory(pdo_dbh_t *dbh, zval *driver_options) H->max_buffer_size = 1024*1024; #endif + H->assume_national_character_set_strings = 0; H->buffered = H->emulate_prepare = 1; /* handle MySQL options */ @@ -616,6 +652,9 @@ static int pdo_mysql_handle_factory(pdo_dbh_t *dbh, zval *driver_options) H->emulate_prepare = pdo_attr_lval(driver_options, PDO_ATTR_EMULATE_PREPARES, H->emulate_prepare); + H->assume_national_character_set_strings = pdo_attr_lval(driver_options, + PDO_ATTR_DEFAULT_STR_PARAM, 0) == PDO_PARAM_STR_NATL ? 1 : 0; + #ifndef PDO_USE_MYSQLND H->max_buffer_size = pdo_attr_lval(driver_options, PDO_MYSQL_ATTR_MAX_BUFFER_SIZE, H->max_buffer_size); #endif diff --git a/ext/pdo_mysql/php_pdo_mysql_int.h b/ext/pdo_mysql/php_pdo_mysql_int.h index cf3ad9deb8..32e79e111c 100644 --- a/ext/pdo_mysql/php_pdo_mysql_int.h +++ b/ext/pdo_mysql/php_pdo_mysql_int.h @@ -104,6 +104,7 @@ typedef struct { typedef struct { MYSQL *server; + unsigned assume_national_character_set_strings:1; unsigned attached:1; unsigned buffered:1; unsigned emulate_prepare:1; diff --git a/ext/pdo_mysql/tests/pdo_mysql_param_str_natl.phpt b/ext/pdo_mysql/tests/pdo_mysql_param_str_natl.phpt new file mode 100644 index 0000000000..56b94483f3 --- /dev/null +++ b/ext/pdo_mysql/tests/pdo_mysql_param_str_natl.phpt @@ -0,0 +1,44 @@ +--TEST-- +PDO MySQL national character set parameters don't affect true prepared statements +--SKIPIF-- + +--FILE-- +setAttribute(PDO::ATTR_EMULATE_PREPARES, 0); + +$db->exec('CREATE TABLE test (bar NCHAR(4) NOT NULL)'); + +$stmt = $db->prepare('INSERT INTO test VALUES(?)'); +$stmt->bindValue(1, 'foo', PDO::PARAM_STR | PDO::PARAM_STR_NATL); +$stmt->execute(); + +var_dump($db->query('SELECT * from test')); +foreach ($db->query('SELECT * from test') as $row) { + print_r($row); +} + +?> +--CLEAN-- + +--EXPECTF-- +object(PDOStatement)#%d (1) { + ["queryString"]=> + string(18) "SELECT * from test" +} +Array +( + [bar] => foo + [0] => foo +) diff --git a/ext/pdo_mysql/tests/pdo_mysql_quote.phpt b/ext/pdo_mysql/tests/pdo_mysql_quote.phpt new file mode 100644 index 0000000000..3d094a4136 --- /dev/null +++ b/ext/pdo_mysql/tests/pdo_mysql_quote.phpt @@ -0,0 +1,34 @@ +--TEST-- +MySQL ensure quote function returns expected results +--SKIPIF-- + +--FILE-- +quote('foo', PDO::PARAM_STR)); +var_dump($db->quote('foo', PDO::PARAM_STR | PDO::PARAM_STR_CHAR)); +var_dump($db->quote('über', PDO::PARAM_STR | PDO::PARAM_STR_NATL)); + +var_dump($db->getAttribute(PDO::ATTR_DEFAULT_STR_PARAM) === PDO::PARAM_STR_CHAR); +$db->setAttribute(PDO::ATTR_DEFAULT_STR_PARAM, PDO::PARAM_STR_NATL); +var_dump($db->getAttribute(PDO::ATTR_DEFAULT_STR_PARAM) === PDO::PARAM_STR_NATL); + +var_dump($db->quote('foo', PDO::PARAM_STR | PDO::PARAM_STR_CHAR)); +var_dump($db->quote('über', PDO::PARAM_STR)); +var_dump($db->quote('über', PDO::PARAM_STR | PDO::PARAM_STR_NATL)); +?> +--EXPECT-- +string(5) "'foo'" +string(5) "'foo'" +string(8) "N'über'" +bool(true) +bool(true) +string(5) "'foo'" +string(8) "N'über'" +string(8) "N'über'"