Add new pdo_sqlsrv tests for utf8 encoding errors (#966)

This commit is contained in:
Jenny Tam 2019-04-09 09:34:31 -07:00 committed by GitHub
parent 486ab9fb08
commit 8ba932b1ca
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 672 additions and 0 deletions

View file

@ -0,0 +1,137 @@
--TEST--
Test various connection errors with invalid attributes
--DESCRIPTION--
This is similar to sqlsrv sqlsrv_connStr.phpt such that invalid connection attributes or values used when connecting.
--SKIPIF--
<?php require('skipif_mid-refactor.inc'); ?>
--FILE--
<?php
require_once("MsCommon_mid-refactor.inc");
function invalidEncoding($binary)
{
try {
$options = array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION);
$conn = connect("", $options);
$attr = ($binary) ? PDO::SQLSRV_ENCODING_BINARY : 'gibberish';
$conn->setAttribute(PDO::SQLSRV_ATTR_ENCODING, $attr);
echo "Should have failed about an invalid encoding.\n";
} catch (PDOException $e) {
$error = '*An invalid encoding was specified for SQLSRV_ATTR_ENCODING.';
if (!fnmatch($error, $e->getMessage())) {
echo "invalidEncoding($binary)\n";
var_dump($e->getMessage());
}
}
}
function invalidServer()
{
global $uid, $pwd;
// Test an invalid server name in UTF-8
try {
$options = array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION);
$invalid = pack("H*", "ffc0");
$conn = new PDO("sqlsrv:server = $invalid;", $uid, $pwd, $options);
echo "Should have failed to connect to invalid server.\n";
} catch (PDOException $e) {
$error1 = '*Login timeout expired';
$error2 = '*An error occurred translating the connection string to UTF-16: No mapping for the Unicode character exists in the target multi-byte code page*';
if (fnmatch($error1, $e->getMessage()) || fnmatch($error2, $e->getMessage())) {
; // matched at least one of the expected error messages
} else {
echo "invalidServer\n";
var_dump($e->getMessage());
}
}
}
function utf8APP()
{
global $server, $uid, $pwd;
try {
// Use a UTF-8 name
$app = pack('H*', 'c59ec6a1d0bcc49720c59bc3a4e1839dd180c580e1bb8120ce86c59ac488c4a8c4b02dc5a5e284aec397c5a7');
$dsn = "APP = $app;";
$conn = connect($dsn);
} catch (PDOException $e) {
echo "With APP in UTF8 it should not have failed!\n";
var_dump($e->getMessage());
}
}
function invalidCredentials()
{
global $server, $database;
// Use valid UTF-8
$user = pack('H*', 'c59ec6a1d0bcc49720c59bc3a4e1839dd180c580e1bb8120ce86c59ac488c4a8c4b02dc5a5e284aec397c5a7');
$passwd = pack('H*', 'c59ec6a1d0bcc49720c59bc3a4e1839dd180c580e1bb8120ce86c59ac488c4a8c4b02dc5a5e284aec397c5a7');
$options = array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION);
$error1 = "*Login failed for user \'*\'.";
$error2 = "*Login timeout expired*";
try {
$conn = new PDO("sqlsrv:server = $server; database = $database;", $user, $passwd, $options);
echo "Should have failed to connect\n";
} catch (PDOException $e) {
if (fnmatch($error1, $e->getMessage()) || fnmatch($error2, $e->getMessage())) {
; // matched at least one of the expected error messages
} else {
echo "invalidCredentials()\n";
var_dump($e->getMessage());
}
}
}
function invalidPassword()
{
global $server, $database;
// Use valid UTF-8
$user = pack('H*', 'c59ec6a1d0bcc49720c59bc3a4e1839dd180c580e1bb8120ce86c59ac488c4a8c4b02dc5a5e284aec397c5a7');
// Use invalid UTF-8
$passwd = pack('H*', 'c59ec6c0d0bcc49720c59bc3a4e1839dd180c580e1bb8120ce86c59ac488c4a8c4b02dc5a5e284aec397c5a7');
$options = array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION);
// Possible errors
$error = "*An error occurred translating the connection string to UTF-16: No mapping for the Unicode character exists in the target multi-byte code page.*";
$error1 = "*Login failed for user \'*\'.";
$error2 = "*Login timeout expired*";
try {
$conn = new PDO("sqlsrv:server = $server; database = $database;", $user, $passwd, $options);
echo "Should have failed to connect\n";
} catch (PDOException $e) {
if (!fnmatch($error, $e->getMessage())) {
// Sometimes it might fail with two other possible error messages
if (fnmatch($error1, $e->getMessage()) || fnmatch($error2, $e->getMessage())) {
; // matched at least one of the expected error messages
} else {
echo "invalidPassword()\n";
var_dump($e->getMessage());
}
}
}
}
try {
invalidEncoding(false);
invalidEncoding(true);
invalidServer();
utf8APP();
invalidCredentials();
invalidPassword();
echo "Done\n";
} catch (PDOException $e) {
var_dump($e);
}
?>
--EXPECT--
Done

View file

@ -12,6 +12,7 @@ require_once("MsCommon.inc");
// These are the error messages we expect at various points below
$errorNoMoreResults = "There are no more results returned by the query.";
$errorNoFields = "The active result for the query contains no fields.";
$errorNoMoreRows = "There are no more rows in the active result set. Since this result set is not scrollable, no more data may be retrieved.";
// This function compares the expected error message and the error returned by errorInfo().
function CheckError($stmt, $expectedError=NULL)
@ -94,6 +95,7 @@ echo "Empty result set, call fetch first: ##################################\n";
$stmt = $conn->query("TestEmptySetProc @a='a', @b='w'");
Fetch($stmt);
Fetch($stmt, $errorNoMoreRows);
NextResult($stmt);
Fetch($stmt);
NextResult($stmt, $errorNoMoreResults);
@ -158,6 +160,7 @@ Next result...
Next result...
Empty result set, call fetch first: ##################################
Fetch...
Fetch...
Next result...
Fetch...
Next result...

View file

@ -0,0 +1,86 @@
--TEST--
Test fetching invalid UTF-16 from the server
--DESCRIPTION--
This is similar to sqlsrv 0079.phpt with checking for error conditions concerning encoding issues.
--SKIPIF--
<?php require('skipif_mid-refactor.inc'); ?>
--FILE--
<?php
require_once("MsCommon_mid-refactor.inc");
try {
$conn = connect();
// The following is required or else the insertion would have failed because the input
// was invalid
$conn->setAttribute(PDO::SQLSRV_ATTR_ENCODING, PDO::SQLSRV_ENCODING_SYSTEM);
// Create test table
$tableName = 'pdoUTF16invalid';
$columns = array(new ColumnMeta('int', 'id', 'identity'),
new ColumnMeta('nvarchar(100)', 'c1'));
$stmt = createTable($conn, $tableName, $columns);
// 0xdc00,0xdbff is an invalid surrogate pair
$invalidUTF16 = pack("H*", '410042004300440000DCFFDB45004600');
$insertSql = "INSERT INTO $tableName (c1) VALUES (?)";
$stmt = $conn->prepare($insertSql);
$stmt->bindParam(1, $invalidUTF16, PDO::PARAM_STR, null, PDO::SQLSRV_ENCODING_BINARY);
$stmt->execute();
try {
// Now fetch data with UTF-8 encoding
$tsql = "SELECT * FROM $tableName";
$stmt = $conn->prepare($tsql);
$stmt->setAttribute(PDO::SQLSRV_ATTR_ENCODING, PDO::SQLSRV_ENCODING_UTF8);
$stmt->execute();
$utf8 = $stmt->fetchColumn(1); // Ignore the id column
echo "fetchColumn should have failed with an error.\n";
} catch (PDOException $e) {
$error = '*An error occurred translating string for a field to UTF-8:*';
if ($e->getCode() !== "IMSSP" || !fnmatch($error, $e->getMessage())) {
var_dump($e->getMessage());
}
}
dropProc($conn, 'Utf16InvalidOut');
$createProc = <<<PROC
CREATE PROCEDURE Utf16InvalidOut
@param nvarchar(25) OUTPUT
AS
BEGIN
set @param = convert(nvarchar(25), 0x410042004300440000DCFFDB45004600);
END;
PROC;
$conn->query($createProc);
try {
$invalidUTF16Out = '';
$tsql = '{call Utf16InvalidOut(?)}';
$stmt = $conn->prepare($tsql);
$stmt->setAttribute(PDO::SQLSRV_ATTR_ENCODING, PDO::SQLSRV_ENCODING_UTF8);
$stmt->bindParam(1, $invalidUTF16Out, PDO::PARAM_STR, 25);
$stmt->execute();
} catch (PDOException $e) {
$error = '*An error occurred translating string for an output param to UTF-8:*';
if ($e->getCode() !== "IMSSP" || !fnmatch($error, $e->getMessage())) {
var_dump($e->getMessage());
}
}
echo "Done\n";
// Done testing with the stored procedure and test table
dropProc($conn, 'Utf16InvalidOut');
dropTable($conn, $tableName);
unset($stmt);
unset($conn);
} catch (PDOException $e) {
var_dump($e->errorInfo);
}
?>
--EXPECT--
Done

View file

@ -0,0 +1,106 @@
--TEST--
Test inserting UTF-8 stream via PHP including some checking of error conditions
--DESCRIPTION--
This is similar to sqlsrv 0067.phpt with checking for error conditions concerning encoding issues.
--SKIPIF--
<?php require('skipif_mid-refactor.inc'); ?>
--FILE--
<?php
require_once("MsCommon_mid-refactor.inc");
try {
$conn = connect();
// Create test table
$tableName = 'pdoUTF8stream';
$columns = array(new ColumnMeta('tinyint', 'c1'),
new ColumnMeta('char(10)', 'c2'),
new ColumnMeta('float', 'c3'),
new ColumnMeta('varchar(max)', 'c4'));
$stmt = createTable($conn, $tableName, $columns);
$f1 = 1;
$f2 = "testtestte";
$f3 = 12.0;
$f4 = fopen("data://text/plain,This%20is%20some%20text%20meant%20to%20test%20binding%20parameters%20to%20streams", "r");
$insertSql = "INSERT INTO $tableName (c1, c2, c3, c4) VALUES (?, ?, ?, ?)";
$stmt = $conn->prepare($insertSql);
$stmt->bindParam(1, $f1);
$stmt->bindParam(2, $f2);
$stmt->bindParam(3, $f3);
$stmt->bindParam(4, $f4, PDO::PARAM_LOB);
$stmt->execute();
// Next test UTF-8 cutoff in the middle of a valid 3 byte UTF-8 char
$utf8 = str_repeat("41", 8188);
$utf8 = $utf8 . "e38395";
$utf8 = pack("H*", $utf8);
$f4 = fopen("data://text/plain," . $utf8, "r");
$stmt->bindParam(4, $f4, PDO::PARAM_LOB);
$stmt->execute();
// Now test a 2 byte incomplete character
$utf8 = str_repeat("41", 8188);
$utf8 = $utf8 . "dfa0";
$utf8 = pack("H*", $utf8);
$f4 = fopen("data://text/plain," . $utf8, "r");
$stmt->bindParam(4, $f4, PDO::PARAM_LOB);
$stmt->execute();
// Then test a 4 byte incomplete character
$utf8 = str_repeat("41", 8186);
$utf8 = $utf8 . "f1a680bf";
$utf8 = pack("H*", $utf8);
$f4 = fopen("data://text/plain," . $utf8, "r");
$stmt->bindParam(4, $f4, PDO::PARAM_LOB);
$stmt->execute();
// Finally, verify error conditions with invalid inputs
$error = '*An error occurred translating a PHP stream from UTF-8 to UTF-16:*';
// First test UTF-8 cutoff (really cutoff)
$utf8 = str_repeat("41", 8188);
$utf8 = $utf8 . "e383";
$utf8 = pack("H*", $utf8);
$f4 = fopen("data://text/plain," . $utf8, "r");
try {
$stmt->bindParam(4, $f4, PDO::PARAM_LOB);
$stmt->execute();
echo "Should have failed with a cutoff UTF-8 string\n";
} catch (PDOException $e) {
if ($e->getCode() !== "IMSSP" || !fnmatch($error, $e->getMessage())) {
var_dump($e->getMessage());
}
}
// Then test UTF-8 invalid/corrupt stream
$utf8 = str_repeat("41", 8188);
$utf8 = $utf8 . "e38395e38395";
$utf8 = substr_replace($utf8, "fe", 1000, 2);
$utf8 = pack("H*", $utf8);
$f4 = fopen("data://text/plain," . $utf8, "r");
try {
$stmt->bindParam(4, $f4, PDO::PARAM_LOB);
$stmt->execute();
echo "Should have failed with an invalid UTF-8 string\n";
} catch (PDOException $e) {
if ($e->getCode() !== "IMSSP" || !fnmatch($error, $e->getMessage())) {
var_dump($e->getMessage());
}
}
echo "Done\n";
// Done testing with stored procedures and table
dropTable($conn, $tableName);
unset($stmt);
unset($conn);
} catch (PDOException $e) {
var_dump($e->errorInfo);
}
?>
--EXPECT--
Done

View file

@ -0,0 +1,233 @@
--TEST--
Test inserting and retrieving UTF-8 text
--DESCRIPTION--
This is similar to sqlsrv 0065.phpt with checking for error conditions concerning encoding issues.
--SKIPIF--
<?php require('skipif_mid-refactor.inc'); ?>
--FILE--
<?php
require_once("MsCommon_mid-refactor.inc");
function verifyColumnData($columns, $results, $utf8)
{
for ($i = 0; $i < count($columns); $i++) {
if ($i > 0) {
if ($results[$i] !== $utf8) {
echo $columns[$i]->colName . ' does not match the inserted UTF-8 text';
var_dump($results[$i]);
}
} else {
// The first column, a varchar(100) column, should have question marks,
// like this one:
$expected = "So?e sä???? ?SCII-te×t";
// With AE, the fetched result may be different in Windows and other
// platforms -- the point is to check if there are some '?'
if (!isAEConnected() && $results[$i] !== $expected) {
echo $columns[$i]->colName . " does not match $expected";
var_dump($results[$i]);
} else {
$arr = explode('?', $results[$i]);
if (count($arr) == 1) {
// this means there is no question mark in $t
echo $columns[$i]->colName . " value is unexpected";
var_dump($results[$i]);
}
}
}
}
}
function dropProcedures($conn)
{
// Drop all procedures
dropProc($conn, "pdoIntDoubleProc");
dropProc($conn, "pdoUTF8OutProc");
dropProc($conn, "pdoUTF8OutWithResultsetProc");
dropProc($conn, "pdoUTF8InOutProc");
}
function createProcedures($conn, $tableName)
{
// Drop all procedures first
dropProcedures($conn);
$createProc = <<<PROC
CREATE PROCEDURE pdoUTF8OutProc
@param nvarchar(25) OUTPUT
AS
BEGIN
set @param = convert(nvarchar(25), 0x5E01A1013C04170120005B01E400DD1040044001C11E200086035A010801280130012D0065012E21D7006701);
END;
PROC;
$stmt = $conn->query($createProc);
$createProc = "CREATE PROCEDURE pdoUTF8OutWithResultsetProc @param NVARCHAR(25) OUTPUT AS BEGIN SELECT c1, c2, c3 FROM $tableName SET @param = CONVERT(NVARCHAR(25), 0x5E01A1013C04170120005B01E400DD1040044001C11E200086035A010801280130012D0065012E21D7006701); END";
$stmt = $conn->query($createProc);
$createProc = "CREATE PROCEDURE pdoUTF8InOutProc @param NVARCHAR(25) OUTPUT AS BEGIN SET @param = CONVERT(NVARCHAR(25), 0x6001E11EDD10130120006101E200DD1040043A01BB1E2000C5005A01C700CF0007042D006501BF1E45046301); END";
$stmt = $conn->query($createProc);
$createProc = "CREATE PROCEDURE pdoIntDoubleProc @param INT OUTPUT AS BEGIN SET @param = @param + @param; END;";
$stmt = $conn->query($createProc);
}
function runBaselineProc($conn)
{
$sql = "{call pdoIntDoubleProc(?)}";
$val = 1;
$stmt = $conn->prepare($sql);
$stmt->bindParam(1, $val, PDO::PARAM_INT | PDO::PARAM_INPUT_OUTPUT, 100);
$stmt->execute();
if ($val !== 2) {
echo "Incorrect value $val for pdoIntDoubleProc\n";
}
}
function runImmediateConversion($conn, $utf8)
{
$sql = "{call pdoUTF8OutProc(?)}";
$val = '';
$stmt = $conn->prepare($sql);
$stmt->bindParam(1, $val, PDO::PARAM_STR, 50);
$stmt->execute();
if ($val !== $utf8) {
echo "Incorrect value $val for pdoUTF8OutProc\n";
}
}
function runProcWithResultset($conn, $utf8)
{
$sql = "{call pdoUTF8OutWithResultsetProc(?)}";
$val = '';
$stmt = $conn->prepare($sql);
$stmt->bindParam(1, $val, PDO::PARAM_STR, 50);
$stmt->execute();
// Moves the cursor to the next result set
$stmt->nextRowset();
if ($val !== $utf8) {
echo "Incorrect value $val for pdoUTF8OutWithResultsetProc\n";
}
}
function runInOutProcWithErrors($conn, $utf8_2)
{
// The input string is smaller than the output size for testing
$val = 'This is a test.';
// The following should work
$sql = "{call pdoUTF8InOutProc(?)}";
$stmt = $conn->prepare($sql);
$stmt->bindParam(1, $val, PDO::PARAM_STR | PDO::PARAM_INPUT_OUTPUT, 25);
$stmt->execute();
if ($val !== $utf8_2) {
echo "Incorrect value $val for pdoUTF8InOutProc Part 1\n";
}
// Use a much longer input string
$val = 'This is a longer test that exceeds the returned values buffer size so that we can test an input buffer size larger than the output buffer size.';
try {
$stmt->bindParam(1, $val, PDO::PARAM_STR | PDO::PARAM_INPUT_OUTPUT, 25);
$stmt->execute();
echo "Should have failed since the string is too long!\n";
} catch (PDOException $e) {
$error = '*String data, right truncation';
if ($e->getCode() !== "22001" || !fnmatch($error, $e->getMessage())) {
var_dump($e->getMessage());
}
}
}
function runIntDoubleProcWithErrors($conn)
{
$sql = "{call pdoIntDoubleProc(?)}";
$val = pack('H*', 'ffffffff');
try {
$stmt = $conn->prepare($sql);
$stmt->bindParam(1, $val, PDO::PARAM_STR);
$stmt->execute();
echo "Should have failed because of an invalid utf-8 string!\n";
} catch (PDOException $e) {
$error = '*An error occurred translating string for input param 1 to UCS-2:*';
if ($e->getCode() !== "IMSSP" || !fnmatch($error, $e->getMessage())) {
var_dump($e->getMessage());
}
}
}
try {
$conn = connect();
// Create test table
$tableName = 'pdoUTF8test';
$columns = array(new ColumnMeta('varchar(100)', 'c1'),
new ColumnMeta('nvarchar(100)', 'c2'),
new ColumnMeta('nvarchar(max)', 'c3'));
$stmt = createTable($conn, $tableName, $columns);
$utf8 = "Şơмė śäოрŀề ΆŚĈĨİ-ť℮×ŧ";
$insertSql = "INSERT INTO $tableName (c1, c2, c3) VALUES (?, ?, ?)";
$stmt1 = $conn->prepare($insertSql);
$stmt1->bindParam(1, $utf8);
$stmt1->bindParam(2, $utf8);
$stmt1->bindParam(3, $utf8);
$stmt1->execute();
$stmt2 = $conn->prepare("SELECT c1, c2, c3 FROM $tableName");
$stmt2->execute();
$results = $stmt2->fetch(PDO::FETCH_NUM);
verifyColumnData($columns, $results, $utf8);
// Start creating stored procedures for testing
createProcedures($conn, $tableName);
runBaselineProc($conn);
runImmediateConversion($conn, $utf8);
runProcWithResultset($conn, $utf8);
// Use another set of UTF-8 text to test
$utf8_2 = "Šỡოē šâოрĺẻ ÅŚÇÏЇ-ťếхţ";
runInOutProcWithErrors($conn, $utf8_2);
// Now insert second row
$utf8_3 = pack('H*', '7a61cc86c7bdceb2f18fb3bf');
$stmt1->bindParam(1, $utf8_3);
$stmt1->bindParam(2, $utf8_3);
$stmt1->bindParam(3, $utf8_3);
$stmt1->execute();
// Fetch data, ignoring first row
$stmt2->execute();
$stmt2->fetch(PDO::FETCH_NUM);
// Move to the second row and check second field
$results2 = $stmt2->fetch(PDO::FETCH_NUM);
if ($results2[1] !== $utf8_3) {
echo "Unexpected $results2[1] from field 2 in second row.\n";
}
// Last test with an invalid input
runIntDoubleProcWithErrors($conn);
echo "Done\n";
// Done testing with stored procedures and table
dropProcedures($conn);
dropTable($conn, $tableName);
unset($stmt1);
unset($stmt2);
unset($conn);
} catch (PDOException $e) {
var_dump($e->errorInfo);
}
?>
--EXPECT--
Done

View file

@ -0,0 +1,107 @@
--TEST--
Test various scenarios which all return the same error about statement not executed
--DESCRIPTION--
This is similar to sqlsrv test_warning_errors2.phpt with checking for error conditions concerning fetching and metadata.
--SKIPIF--
<?php require('skipif_mid-refactor.inc'); ?>
--FILE--
<?php
require_once("MsCommon_mid-refactor.inc");
$execError = '*The statement must be executed before results can be retrieved.';
$noMoreResult = '*There are no more results returned by the query.';
function getNextResult($stmt, $error = null)
{
try {
$result = $stmt->nextRowset();
if (!is_null($error)) {
echo "getNextResult: expect this to fail with an error from the driver\n";
} elseif ($result !== false) {
echo "getNextResult: expect this to simply return false\n";
}
} catch (PDOException $e) {
if ($e->getCode() !== "IMSSP" || !fnmatch($error, $e->getMessage())) {
var_dump($e->getMessage());
}
}
}
try {
$conn = connect();
$tsql = 'SELECT name FROM sys.objects';
$stmt = $conn->prepare($tsql);
$colCount = $stmt->columnCount();
if ($colCount != 0) {
echo "Before execute(), result set should only have 0 columns\n";
}
$metadata = $stmt->getColumnMeta(0);
if ($metadata !== false) {
echo "Before execute(), result set is empty so getColumnMeta should have failed\n";
}
// When fetching, PDO checks if statement is executed before passing the
// control to the driver, so it simply fails without error message
$result = $stmt->fetch(PDO::FETCH_ASSOC);
if ($result !== false) {
echo "Before execute(), fetch should have failed\n";
}
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
var_dump($result);
$result = $stmt->fetchAll(PDO::FETCH_COLUMN, 0);
var_dump($result);
getNextResult($stmt, $execError);
// Now, call execute()
$stmt->execute();
$colCount = $stmt->columnCount();
if ($colCount != 1) {
echo "Expected only one column\n";
}
$metadata = $stmt->getColumnMeta(0);
if ($metadata['native_type'] !== 'string') {
echo "The metadata returned is unexpected: \n";
var_dump($metadata);
}
$result = $stmt->fetch(PDO::FETCH_ASSOC);
if (!is_array($result) && count($result) == 0) {
echo "After execute(), fetch should have returned an array with results\n";
}
$result = $stmt->fetchAll(PDO::FETCH_COLUMN, 0);
if (!is_array($result) && count($result) == 0) {
echo "After execute(), fetchAll should have returned an array with results\n";
}
getNextResult($stmt);
getNextResult($stmt, $noMoreResult);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
if ($result !== false) {
// When nextRowset() fails, it resets the execute flag to false
echo "After nextRowset failed, fetch should have failed\n";
}
echo "Done\n";
unset($stmt);
unset($conn);
} catch (PDOException $e) {
var_dump($e->errorInfo);
}
?>
--EXPECT--
array(0) {
}
array(0) {
}
Done