From e053712a9ff4ca085032f695da3b112f7433f625 Mon Sep 17 00:00:00 2001 From: Joe Nahmias Date: Tue, 16 Jul 2024 17:53:47 -0400 Subject: [PATCH 1/2] pdo_odbc: allocate sufficient space for retrieving unicode data Closes phpGH-9498 --- ext/pdo_odbc/odbc_stmt.c | 13 +++++++++ ext/pdo_odbc/tests/gh9498_1.phpt | 46 ++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 ext/pdo_odbc/tests/gh9498_1.phpt diff --git a/ext/pdo_odbc/odbc_stmt.c b/ext/pdo_odbc/odbc_stmt.c index bd4a2f6162d09..af0fc46b0ccdb 100644 --- a/ext/pdo_odbc/odbc_stmt.c +++ b/ext/pdo_odbc/odbc_stmt.c @@ -607,6 +607,19 @@ static int odbc_stmt_describe(pdo_stmt_t *stmt, int colno) } } colsize = displaysize; + if ( S->cols[colno].coltype == SQL_WCHAR +#ifdef SQL_WVARCHAR + || S->cols[colno].coltype == SQL_WVARCHAR +#endif +#ifdef SQL_WLONGVARCHAR + || S->cols[colno].coltype == SQL_WLONGVARCHAR +#endif + ) { + /* displaysize is counted by characters; + for unicode, each could take up to 4 bytes in UTF-8; + see https://www.rfc-editor.org/rfc/rfc3629 */ + colsize = displaysize * 4; + } col->maxlen = S->cols[colno].datalen = colsize; col->name = zend_string_init(S->cols[colno].colname, colnamelen, 0); diff --git a/ext/pdo_odbc/tests/gh9498_1.phpt b/ext/pdo_odbc/tests/gh9498_1.phpt new file mode 100644 index 0000000000000..0e11799cab7ba --- /dev/null +++ b/ext/pdo_odbc/tests/gh9498_1.phpt @@ -0,0 +1,46 @@ +--TEST-- +Bug GH-9498 (unicode string corruption during SELECT) +--EXTENSIONS-- +pdo_odbc +--SKIPIF-- + +--FILE-- +setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + +$select_test = "SELECT + CAST(0x72006f017e016f007600fd00 AS nvarchar(6)) AS pink_in_czech_binary_encoded_utf16le + , CAST(0x6b26 AS nvarchar(1)) AS beamed_eighth_notes_binary_encoded_utf16le + , NCHAR(0x266b) AS beamed_eighth_notes_nchar + , N'♫' AS \"beamed_eighth_notes_u+266b_constant\" + , N'růžový' AS pink_in_czech_unicode_string_constant + , NCHAR(0xfb01) AS ligature_fi_nchar + , N'𝄋' AS \"segno_u+1d10b_constant\" +;"; +var_dump($db->query($select_test, \PDO::FETCH_ASSOC)->fetchAll()); +?> +--EXPECT-- +array(1) { + [0]=> + array(7) { + ["pink_in_czech_binary_encoded_utf16le"]=> + string(9) "růžový" + ["beamed_eighth_notes_binary_encoded_utf16le"]=> + string(3) "♫" + ["beamed_eighth_notes_nchar"]=> + string(3) "♫" + ["beamed_eighth_notes_u+266b_constant"]=> + string(3) "♫" + ["pink_in_czech_unicode_string_constant"]=> + string(9) "růžový" + ["ligature_fi_nchar"]=> + string(3) "fi" + ["segno_u+1d10b_constant"]=> + string(4) "𝄋" + } +} From 788dbef753f20436e6452ee0a54a9c79b9440b94 Mon Sep 17 00:00:00 2001 From: Joe Nahmias Date: Thu, 18 Jul 2024 11:50:01 -0400 Subject: [PATCH 2/2] gh9498.phpt: fix test so that it works on windows too Work around the fact that Windows does not use UTF-8 by doing the comparison within the PHP test code. Also, add a few more tests of plain ASCII strings to make sure those aren't impacted by this change. Fixes: e053712a9ff4ca085032f695da3b112f7433f625 --- ext/pdo_odbc/tests/gh9498_1.phpt | 99 ++++++++++++++++++++++---------- 1 file changed, 70 insertions(+), 29 deletions(-) diff --git a/ext/pdo_odbc/tests/gh9498_1.phpt b/ext/pdo_odbc/tests/gh9498_1.phpt index 0e11799cab7ba..512f114d81d37 100644 --- a/ext/pdo_odbc/tests/gh9498_1.phpt +++ b/ext/pdo_odbc/tests/gh9498_1.phpt @@ -13,34 +13,75 @@ require 'ext/pdo/tests/pdo_test.inc'; $db = PDOTest::test_factory(dirname(__FILE__) . '/common.phpt'); $db->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); -$select_test = "SELECT - CAST(0x72006f017e016f007600fd00 AS nvarchar(6)) AS pink_in_czech_binary_encoded_utf16le - , CAST(0x6b26 AS nvarchar(1)) AS beamed_eighth_notes_binary_encoded_utf16le - , NCHAR(0x266b) AS beamed_eighth_notes_nchar - , N'♫' AS \"beamed_eighth_notes_u+266b_constant\" - , N'růžový' AS pink_in_czech_unicode_string_constant - , NCHAR(0xfb01) AS ligature_fi_nchar - , N'𝄋' AS \"segno_u+1d10b_constant\" -;"; -var_dump($db->query($select_test, \PDO::FETCH_ASSOC)->fetchAll()); +$tests = [ + [ + "name" => "beamed_eighth_notes_binary_encoded_utf16le", + "expr" => "CAST(0x6b26 AS nvarchar(1))", + "expected_answer" => "♫", + ], + [ + "name" => "beamed_eighth_notes_nchar", + "expr" => "NCHAR(0x266b)", + "expected_answer" => "♫", + ], + [ + "name" => "beamed_eighth_notes_u+266b_constant", + "expr" => "N'♫'", + "expected_answer" => "♫", + ], + [ + "name" => "ligature_fi_nchar", + "expr" => "NCHAR(0xfb01)", + "expected_answer" => "fi", + ], + [ + "name" => "php_ascii_string_constant", + "expr" => "'php'", + "expected_answer" => "php", + ], + [ + "name" => "php_unicode_string_constant", + "expr" => "N'php'", + "expected_answer" => "php", + ], + [ + "name" => "pink_in_czech_binary_encoded_utf16le", + "expr" => "CAST(0x72006f017e016f007600fd00 AS nvarchar(6))", + "expected_answer" => "růžový", + ], + [ + "name" => "pink_in_czech_unicode_string_constant", + "expr" => "N'růžový'", + "expected_answer" => "růžový", + ], + [ + "name" => "segno_u+1d10b_constant", + "expr" => "N'𝄋'", + "expected_answer" => "𝄋", + ], +]; + +foreach ($tests as $t) { + print $t["name"] . ": "; + $sql = "SELECT " . $t["expr"] . ' AS "' . $t["name"] . '";'; + $results = $db->query($sql, \PDO::FETCH_NUM)->fetch(); + if ( $results[0] == $t["expected_answer"] ) { + print "PASS"; + } else { + print "FAIL!"; + print "\tresult: '" . $results[0] . "'"; + print "\texpected: '" . $t["expected_answer"] . "'"; + } + print "\n"; +} ?> --EXPECT-- -array(1) { - [0]=> - array(7) { - ["pink_in_czech_binary_encoded_utf16le"]=> - string(9) "růžový" - ["beamed_eighth_notes_binary_encoded_utf16le"]=> - string(3) "♫" - ["beamed_eighth_notes_nchar"]=> - string(3) "♫" - ["beamed_eighth_notes_u+266b_constant"]=> - string(3) "♫" - ["pink_in_czech_unicode_string_constant"]=> - string(9) "růžový" - ["ligature_fi_nchar"]=> - string(3) "fi" - ["segno_u+1d10b_constant"]=> - string(4) "𝄋" - } -} +beamed_eighth_notes_binary_encoded_utf16le: PASS +beamed_eighth_notes_nchar: PASS +beamed_eighth_notes_u+266b_constant: PASS +ligature_fi_nchar: PASS +php_ascii_string_constant: PASS +php_unicode_string_constant: PASS +pink_in_czech_binary_encoded_utf16le: PASS +pink_in_czech_unicode_string_constant: PASS +segno_u+1d10b_constant: PASS