diff --git a/NEWS b/NEWS index cf28751877fa..a56c03bb9e48 100644 --- a/NEWS +++ b/NEWS @@ -54,6 +54,10 @@ PHP NEWS . Fixed a leak when a persistent connection failed a liveness check with no other live PDO handle. (iliaal) +- PDO_ODBC: + . Fixed bug GH-23444 (ODBC_ATTR_ASSUME_UTF8 corrupts Unicode data outside + Windows). (Calvin Buckley, Lazizbek Ergashev) + - Phar: . Fixed bug GH-23418 (Use-after-free when looking up mounted directories). (Weilin Du) diff --git a/ext/pdo_odbc/odbc_stmt.c b/ext/pdo_odbc/odbc_stmt.c index 8786f2563e52..042c4684a887 100644 --- a/ext/pdo_odbc/odbc_stmt.c +++ b/ext/pdo_odbc/odbc_stmt.c @@ -34,6 +34,7 @@ enum pdo_odbc_conv_result { static int pdo_odbc_sqltype_is_unicode(pdo_odbc_stmt *S, SQLSMALLINT sqltype) { +#ifdef PHP_WIN32 if (!S->assume_utf8) return 0; switch (sqltype) { #ifdef SQL_WCHAR @@ -51,6 +52,9 @@ static int pdo_odbc_sqltype_is_unicode(pdo_odbc_stmt *S, SQLSMALLINT sqltype) default: return 0; } +#else + return 0; +#endif } static int pdo_odbc_utf82ucs2(pdo_stmt_t *stmt, int is_unicode, const char *buf, @@ -548,7 +552,15 @@ static int odbc_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data *p break; } } else { - P->len = SQL_LEN_DATA_AT_EXEC(Z_STRLEN_P(parameter)); + zend_ulong ulen; + if (pdo_odbc_utf82ucs2(stmt, P->is_unicode, + Z_STRVAL_P(parameter), + Z_STRLEN_P(parameter), + &ulen) == PDO_ODBC_CONV_OK) { + P->len = SQL_LEN_DATA_AT_EXEC(ulen); + } else { + P->len = SQL_LEN_DATA_AT_EXEC(Z_STRLEN_P(parameter)); + } } } return 1; diff --git a/ext/pdo_odbc/tests/gh23444.phpt b/ext/pdo_odbc/tests/gh23444.phpt new file mode 100644 index 000000000000..f71fc733839f --- /dev/null +++ b/ext/pdo_odbc/tests/gh23444.phpt @@ -0,0 +1,34 @@ +--TEST-- +GH-23444 (Unicode data is corrupted with ODBC_ATTR_ASSUME_UTF8) +--EXTENSIONS-- +pdo_odbc +--SKIPIF-- + +--FILE-- +exec("CREATE TABLE gh23444 (v NVARCHAR(100))"); + +// 13 bytes as UTF-8, so an unconverted parameter is an odd number of bytes +$string = "\u{6e2c}\u{8a66}\u{4e2d}\u{1f418}"; + +$db->setAttribute(PDO::ODBC_ATTR_ASSUME_UTF8, true); +$stmt = $db->prepare("INSERT INTO gh23444 VALUES(?)"); +$stmt->execute([$string]); + +$stmt = $db->prepare("SELECT v FROM gh23444 WHERE v = ?"); +$stmt->execute([$string]); +var_dump($stmt->fetchColumn() === $string); +?> +--CLEAN-- +exec("DROP TABLE IF EXISTS gh23444"); +?> +--EXPECT-- +bool(true)