From 7a6a8d5a72108a6f37e1ff890e659d46ae6e5c69 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Tue, 13 Mar 2018 14:04:40 -0700 Subject: [PATCH] PDO tests for retrieving data as output params using various PDO param types --- .../pdo_ae_output_param_binary_size.phpt | 210 +++++++++++++++ .../pdo_ae_output_param_char_size.phpt | 171 +++++++++++++ .../pdo_ae_output_param_datetimes.phpt | 204 +++++++++++++++ .../pdo_ae_output_param_decimals.phpt | 242 ++++++++++++++++++ .../pdo_ae_output_param_floats.phpt | 181 +++++++++++++ .../pdo_ae_output_param_integers.phpt | 193 ++++++++++++++ .../pdo_ae_output_param_nchar_size.phpt | 172 +++++++++++++ 7 files changed, 1373 insertions(+) create mode 100644 test/functional/pdo_sqlsrv/pdo_ae_output_param_binary_size.phpt create mode 100644 test/functional/pdo_sqlsrv/pdo_ae_output_param_char_size.phpt create mode 100644 test/functional/pdo_sqlsrv/pdo_ae_output_param_datetimes.phpt create mode 100644 test/functional/pdo_sqlsrv/pdo_ae_output_param_decimals.phpt create mode 100644 test/functional/pdo_sqlsrv/pdo_ae_output_param_floats.phpt create mode 100644 test/functional/pdo_sqlsrv/pdo_ae_output_param_integers.phpt create mode 100644 test/functional/pdo_sqlsrv/pdo_ae_output_param_nchar_size.phpt diff --git a/test/functional/pdo_sqlsrv/pdo_ae_output_param_binary_size.phpt b/test/functional/pdo_sqlsrv/pdo_ae_output_param_binary_size.phpt new file mode 100644 index 00000000..de999f2d --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_ae_output_param_binary_size.phpt @@ -0,0 +1,210 @@ +--TEST-- +Test for retrieving encrypted data of binary types of various sizes as output parameters +--DESCRIPTION-- +Use PDOstatement::bindParam with all PDO::PARAM_ types +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- + "An invalid PHP type was specified as an output parameter. DateTime objects, NULL values, and streams cannot be specified as output parameters.", "07006" => "Restricted data type attribute violation"); + +$pdoParamTypes = array( + PDO::PARAM_BOOL, // 5 + PDO::PARAM_NULL, // 0 + PDO::PARAM_INT, // 1 + PDO::PARAM_STR, // 2 + PDO::PARAM_LOB // 3 +); + +////////////////////////////////////////////////////////////////////////////////// +function printValues($msg, $det, $rand, $input0, $input1) +{ + echo $msg; + echo "input 0: "; var_dump($input0); + echo "fetched: "; var_dump($det); + echo "input 1: "; var_dump($input1); + echo "fetched: "; var_dump($rand); +} + +function convert2Hex($ch, $length) +{ + // Without AE, the binary values returned as integers will + // have lengths no more than 4 times the original hex string value + // (e.g. string(8) "65656565") - limited by the buffer sizes + if (!isAEConnected()) { + $count = ($length <= 2) ? $length : 4; + } else { + $count = $length; + } + + return str_repeat(bin2hex($ch), $count); +} + +function testOutputBinary($inout) +{ + global $pdoParamTypes, $dataTypes, $lengths, $errors; + + try { + $conn = connect(); + $tbname = "test_binary_types"; + $spname = "test_binary_proc"; + $ch0 = 'd'; + $ch1 = 'e'; + + foreach ($dataTypes as $dataType) { + $maxtype = strpos($dataType, "(max)"); + foreach ($lengths as $length) { + if ($maxtype !== false) { + $type = $dataType; + } else { + $type = "$dataType($length)"; + } + trace("\nTesting $type:\n"); + + //create and populate table + $colMetaArr = array(new ColumnMeta($type, "c_det"), new ColumnMeta($type, "c_rand", null, "randomized")); + createTable($conn, $tbname, $colMetaArr); + $input0 = str_repeat($ch0, $length); + $input1 = str_repeat($ch1, $length); + $ord0 = convert2Hex($ch0, $length); + $ord1 = convert2Hex($ch1, $length); + insertRow($conn, $tbname, array("c_det" => new BindParamOp(1, $input0, "PDO::PARAM_LOB", 0, "PDO::SQLSRV_ENCODING_BINARY"), + "c_rand" => new BindParamOp(2, $input1, "PDO::PARAM_LOB", 0, "PDO::SQLSRV_ENCODING_BINARY")), "prepareBindParam"); + + // fetch with PDO::bindParam using a stored procedure + $procArgs = "@c_det $type OUTPUT, @c_rand $type OUTPUT"; + $procCode = "SELECT @c_det = c_det, @c_rand = c_rand FROM $tbname"; + createProc($conn, $spname, $procArgs, $procCode); + + // call stored procedure + $outSql = getCallProcSqlPlaceholders($spname, 2); + foreach ($pdoParamTypes as $pdoParamType) { + $stmt = $conn->prepare($outSql); + trace("\nParam $pdoParamType with INOUT = $inout\n"); + + if ($inout && $pdoParamType != PDO::PARAM_STR) { + // Currently do not support getting binary as strings + INOUT param + // See VSO 2829 for details + $paramType = $pdoParamType | PDO::PARAM_INPUT_OUTPUT; + } else { + $paramType = $pdoParamType; + } + + $det = ""; + $rand = ""; + if ($pdoParamType == PDO::PARAM_STR || $pdoParamType == PDO::PARAM_LOB) { + $stmt->bindParam(1, $det, $paramType, $length, PDO::SQLSRV_ENCODING_BINARY); + $stmt->bindParam(2, $rand, $paramType, $length, PDO::SQLSRV_ENCODING_BINARY); + } elseif ($pdoParamType == PDO::PARAM_BOOL || $pdoParamType == PDO::PARAM_INT) { + $det = $rand = 0; + $stmt->bindParam(1, $det, $paramType, PDO::SQLSRV_PARAM_OUT_DEFAULT_SIZE); + $stmt->bindParam(2, $rand, $paramType, PDO::SQLSRV_PARAM_OUT_DEFAULT_SIZE); + } else { + $stmt->bindParam(1, $det, $paramType, $length); + $stmt->bindParam(2, $rand, $paramType, $length); + } + + try { + $stmt->execute(); + + $errMsg = "****$dataType as $pdoParamType failed with INOUT = $inout:****\n"; + if ($pdoParamType == PDO::PARAM_STR) { + if ($det !== $input0 || $rand !== $input1) { + printValues($errMsg, $det, $rand, $input0, $input1); + } + } elseif ($pdoParamType == PDO::PARAM_BOOL) { + // for boolean values, they should all be bool(true) + // because all floats are non-zeroes + // This only occurs without AE + // With AE enabled, this would have caused an exception + if (!$det || !$rand) { + printValues($errMsg, $det, $rand, $input0, $input1); + } + } else { + // $pdoParamType is PDO::PARAM_INT + // This only occurs without AE -- likely a rare use case + // With AE enabled, this would have caused an exception + if (strval($det) != $ord0 || strval($rand) != $ord1) { + printValues($errMsg, $det, $rand, $ord0, $ord1); + } + } + } catch (PDOException $e) { + $message = $e->getMessage(); + $errMsg = "EXCEPTION: ****$type as $pdoParamType failed with INOUT = $inout:****\n"; + if ($pdoParamType == PDO::PARAM_NULL || $pdoParamType == PDO::PARAM_LOB) { + // Expected error IMSSP: "An invalid PHP type was specified + // as an output parameter. DateTime objects, NULL values, and + // streams cannot be specified as output parameters." + $found = strpos($message, $errors['IMSSP']); + if ($found === false) { + printValues($errMsg, $det, $rand, $input0, $input1); + } + } elseif ($pdoParamType == PDO::PARAM_BOOL || PDO::PARAM_INT) { + if (isAEConnected()) { + if ($pdoParamType == PDO::PARAM_INT) { + // Expected to fail with this message + $error = "String data, right truncated for output parameter"; + $found = strpos($message, $error); + } else { + // PDO::PARAM_BOOL - + // Expected error 07006 with AE enabled: + // "Restricted data type attribute violation" + // The data value returned for a parameter bound as + // SQL_PARAM_INPUT_OUTPUT or SQL_PARAM_OUTPUT could not + // be converted to the data type identified by the + // ValueType argument in SQLBindParameter. + $found = strpos($message, $errors['07006']); + } + } else { + // When not AE enabled, expected to fail with something like this message + // "Implicit conversion from data type nvarchar(max) to binary is not allowed. Use the CONVERT function to run this query." + // Sometimes it's about nvarchar too + $error = "to $dataType is not allowed. Use the CONVERT function to run this query."; + $found = strpos($message, $error); + } + if ($found === false) { + printValues($errMsg, $det, $rand, $input0, $input1); + } + } else { + // catch all + printValues($errMsg, $det, $rand, $input0, $input1); + } + } + } + dropProc($conn, $spname); + dropTable($conn, $tbname); + } + } + unset($stmt); + unset($conn); + } catch (PDOException $e) { + echo $e->getMessage(); + } +} + +testOutputBinary(false); +testOutputBinary(true); + +echo "Done\n"; + +?> +--CLEAN-- + +--EXPECT-- +Done \ No newline at end of file diff --git a/test/functional/pdo_sqlsrv/pdo_ae_output_param_char_size.phpt b/test/functional/pdo_sqlsrv/pdo_ae_output_param_char_size.phpt new file mode 100644 index 00000000..66a68004 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_ae_output_param_char_size.phpt @@ -0,0 +1,171 @@ +--TEST-- +Test for retrieving encrypted data of char types of various sizes as output parameters +--DESCRIPTION-- +Use PDOstatement::bindParam with all PDO::PARAM_ types +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- + "An invalid PHP type was specified as an output parameter. DateTime objects, NULL values, and streams cannot be specified as output parameters.", +"22003" => "Numeric value out of range"); + +$pdoParamTypes = array( + PDO::PARAM_BOOL, // 5 + PDO::PARAM_NULL, // 0 + PDO::PARAM_INT, // 1 + PDO::PARAM_STR, // 2 + PDO::PARAM_LOB // 3 +); + +////////////////////////////////////////////////////////////////////////////////// +function printValues($msg, $det, $rand, $input0, $input1) +{ + echo $msg; + echo "input 0: "; var_dump($input0); + echo "fetched: "; var_dump($det); + echo "input 1: "; var_dump($input1); + echo "fetched: "; var_dump($rand); +} + +function testOutputChars($inout) +{ + global $pdoParamTypes, $dataTypes, $lengths, $errors; + + try { + + $conn = connect(); + $tbname = "test_char_types"; + $spname = "test_char_proc"; + + foreach ($dataTypes as $dataType) { + $maxtype = strpos($dataType, "(max)"); + foreach ($lengths as $length) { + if ($maxtype !== false) { + $type = $dataType; + } else { + $type = "$dataType($length)"; + } + trace("\nTesting $type:\n"); + + //create and populate table + $colMetaArr = array(new ColumnMeta($type, "c_det"), new ColumnMeta($type, "c_rand", null, "randomized")); + createTable($conn, $tbname, $colMetaArr); + $input0 = str_repeat("1", $length); + $input1 = str_repeat("2", $length); + insertRow($conn, $tbname, array("c_det" => $input0, + "c_rand" => $input1)); + + // fetch with PDO::bindParam using a stored procedure + $procArgs = "@c_det $type OUTPUT, @c_rand $type OUTPUT"; + $procCode = "SELECT @c_det = c_det, @c_rand = c_rand FROM $tbname"; + createProc($conn, $spname, $procArgs, $procCode); + + // call stored procedure + $outSql = getCallProcSqlPlaceholders($spname, 2); + foreach ($pdoParamTypes as $pdoParamType) { + $det = ""; + $rand = ""; + $stmt = $conn->prepare($outSql); + trace("\nParam $pdoParamType with INOUT = $inout\n"); + + if ($inout) { + $paramType = $pdoParamType | PDO::PARAM_INPUT_OUTPUT; + } else { + $paramType = $pdoParamType; + } + + $len = $length; + if ($pdoParamType == PDO::PARAM_BOOL || $pdoParamType == PDO::PARAM_INT) { + $len = PDO::SQLSRV_PARAM_OUT_DEFAULT_SIZE; + $det = $rand = 0; + } + + $stmt->bindParam(1, $det, $paramType, $len); + $stmt->bindParam(2, $rand, $paramType, $len); + + try { + $stmt->execute(); + $errMsg = "****$type as $pdoParamType failed with INOUT = $inout:****\n"; + if ($length < 64 && $pdoParamType != PDO::PARAM_STR) { + if ($pdoParamType == PDO::PARAM_BOOL) { + // For boolean values, they should all be bool(true) + // because all "string literals" are non-zeroes + if (!$det || !$rand) { + printValues($errMsg, $det, $rand, $input0, $input1); + } + } else { + // $pdoParamType = PDO::PARAM_INT + // Expect numeric values + if ($det != intval($input0) || $rand != intval($input1)) { + printValues($errMsg, $det, $rand, $input0, $input1); + } + } + } elseif ($det !== $input0 || $rand !== $input1) { + printValues($errMsg, $det, $rand, $input0, $input1); + } + } catch (PDOException $e) { + $message = $e->getMessage(); + $errMsg = "EXCEPTION: ****$type as $pdoParamType failed with INOUT = $inout:****\n"; + if ($pdoParamType == PDO::PARAM_NULL || $pdoParamType == PDO::PARAM_LOB) { + // Expected error IMSSP: "An invalid PHP type was specified + // as an output parameter. DateTime objects, NULL values, and + // streams cannot be specified as output parameters." + $found = strpos($message, $errors['IMSSP']); + if ($found === false) { + printValues($errMsg, $det, $rand, $input0, $input1); + } + } elseif ($pdoParamType == PDO::PARAM_BOOL) { + if (isAEConnected()) { + // Expected error 22003: "Numeric value out of range" + $found = strpos($message, $errors['22003']); + } else { + // When not AE enabled, expected to fail to convert + // whatever char type to integers + $error = "Error converting data type $dataType to int"; + $found = strpos($message, $error); + } + if ($found === false) { + printValues($errMsg, $det, $rand, $input0, $input1); + } + } else { + printValues($errMsg, $det, $rand, $input0, $input1); + } + } + } + dropProc($conn, $spname); + dropTable($conn, $tbname); + } + } + unset($stmt); + unset($conn); + } catch (PDOException $e) { + echo $e->getMessage(); + } +} + +testOutputChars(false); +testOutputChars(true); + +echo "Done\n"; + +?> +--CLEAN-- + +--EXPECT-- +Done \ No newline at end of file diff --git a/test/functional/pdo_sqlsrv/pdo_ae_output_param_datetimes.phpt b/test/functional/pdo_sqlsrv/pdo_ae_output_param_datetimes.phpt new file mode 100644 index 00000000..b5829191 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_ae_output_param_datetimes.phpt @@ -0,0 +1,204 @@ +--TEST-- +Test for retrieving encrypted data of datetimes as output parameters +--DESCRIPTION-- +Use PDOstatement::bindParam with all PDO::PARAM_ types +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- + array("0001-01-01 00:00:00", "9999-12-31 23:59:59"), + "datetimeoffset" => array("0001-01-01 00:00:00 -14:00", "9999-12-31 23:59:59 +14:00"), + "time" => array("00:00:00", "23:59:59")); + +$errors = array("IMSSP" => "An invalid PHP type was specified as an output parameter. DateTime objects, NULL values, and streams cannot be specified as output parameters.", "07006" => "Restricted data type attribute violation"); + +$pdoParamTypes = array( + PDO::PARAM_BOOL, // 5 + PDO::PARAM_NULL, // 0 + PDO::PARAM_INT, // 1 + PDO::PARAM_STR, // 2 + PDO::PARAM_LOB // 3 +); + +////////////////////////////////////////////////////////////////////////////////// + +// compareDate() returns true when the date/time values are basically the same +// e.g. 00:00:00.000 is the same as 00:00:00 +function compareDate($dtout, $dtin, $dataType) +{ + if ($dataType == "datetimeoffset") { + $dtarr = explode(' ', $dtin); + if (strpos($dtout, $dtarr[0]) !== false && strpos($dtout, $dtarr[1]) !== false && strpos($dtout, $dtarr[2]) !== false) { + return true; + } + } else { + if (strpos($dtout, $dtin) !== false) { + return true; + } + } + return false; +} + +function printValues($msg, $det, $rand, $inputValues) +{ + echo $msg; + echo "input 0: "; var_dump($inputValues[0]); + echo "fetched: "; var_dump($det); + echo "input 1: "; var_dump($inputValues[1]); + echo "fetched: "; var_dump($rand); +} + +function testOutputDatetimes($inout) +{ + global $pdoParamTypes, $dataTypes, $precisions, $inputValuesInit, $errors; + + try { + $conn = connect(); + + $tbname = "test_datetimes_types"; + $spname = "test_datetimes_proc"; + + foreach ($dataTypes as $dataType) { + foreach ($precisions as $precision) { + // change the input values depending on the precision + $inputValues[0] = $inputValuesInit[$dataType][0]; + $inputValues[1] = $inputValuesInit[$dataType][1]; + if ($precision != 0) { + if ($dataType == "datetime2") { + $inputValues[1] .= "." . str_repeat("9", $precision); + } else if ($dataType == "datetimeoffset") { + $inputPieces = explode(" ", $inputValues[1]); + $inputValues[1] = $inputPieces[0] . " " . $inputPieces[1] . "." . str_repeat("9", $precision) . " " . $inputPieces[2]; + } else if ($dataType == "time") { + $inputValues[0] .= "." . str_repeat("0", $precision); + $inputValues[1] .= "." . str_repeat("9", $precision); + } + } + + $type = "$dataType($precision)"; + trace("\nTesting $type:\n"); + + //create and populate table + $colMetaArr = array(new ColumnMeta($type, "c_det"), new ColumnMeta($type, "c_rand", null, "randomized")); + createTable($conn, $tbname, $colMetaArr); + + $stmt = insertRow($conn, $tbname, array("c_det" => $inputValues[0], "c_rand" => $inputValues[1] ), null); + + // fetch with PDO::bindParam using a stored procedure + $procArgs = "@c_det $type OUTPUT, @c_rand $type OUTPUT"; + $procCode = "SELECT @c_det = c_det, @c_rand = c_rand FROM $tbname"; + createProc($conn, $spname, $procArgs, $procCode); + + // call stored procedure + $outSql = getCallProcSqlPlaceholders($spname, 2); + foreach ($pdoParamTypes as $pdoParamType) { + $det = 0; + $rand = 0; + $stmt = $conn->prepare($outSql); + trace("\nParam $pdoParamType with INOUT = $inout\n"); + + if ($inout) { + $paramType = $pdoParamType | PDO::PARAM_INPUT_OUTPUT; + } else { + $paramType = $pdoParamType; + } + + $len = 2048; + if ($pdoParamType == PDO::PARAM_BOOL || $pdoParamType == PDO::PARAM_INT) { + $len = PDO::SQLSRV_PARAM_OUT_DEFAULT_SIZE; + } + + $stmt->bindParam(1, $det, $paramType, $len); + $stmt->bindParam(2, $rand, $paramType, $len); + + try { + $stmt->execute(); + $errMsg = "****$type as $pdoParamType failed with INOUT = $inout:****\n"; + // What follows only happens with OUTPUT parameter + if ($inout) { + echo "Any datetime type as INOUT param should have caused an exception!\n"; + } + if ($pdoParamType == PDO::PARAM_INT) { + // Expect an integer, the first part of the date time string + $ch = ($dataType == "time")? ':' : '-'; + $tmp0 = explode($ch, $inputValues[0]); + $tmp1 = explode($ch, $inputValues[1]); + + if ($det != intval($tmp0[0]) || $rand != intval($tmp1[0])) { + printValues($errMsg, $det, $rand, $inputValues); + } + } elseif (!compareDate($det, $inputValues[0], $dataType) || + !compareDate($rand, $inputValues[1], $dataType)) { + printValues($errMsg, $det, $rand, $inputValues); + } + } catch (PDOException $e) { + $message = $e->getMessage(); + $errMsg = "EXCEPTION: ****$type as $pdoParamType failed with INOUT = $inout:\n$message****\n"; + if ($pdoParamType == PDO::PARAM_NULL || $pdoParamType == PDO::PARAM_LOB) { + // Expected error IMSSP: "An invalid PHP type was specified + // as an output parameter. DateTime objects, NULL values, and + // streams cannot be specified as output parameters." + $found = strpos($message, $errors['IMSSP']); + } elseif (isAEConnected()) { + if ($pdoParamType == PDO::PARAM_BOOL) { + // Expected error 07006: "Restricted data type attribute violation" + // What does this error mean? + // The data value returned for a parameter bound as + // SQL_PARAM_INPUT_OUTPUT or SQL_PARAM_OUTPUT could not + // be converted to the data type identified by the + // ValueType argument in SQLBindParameter. + $found = strpos($message, $errors['07006']); + } else { + $error = "Invalid character value for cast specification"; + $found = strpos($message, $error); + } + } else { + if ($pdoParamType == PDO::PARAM_BOOL) { + $error = "Operand type clash: int is incompatible with $dataType"; + } else { + $error = "Error converting data type nvarchar to $dataType"; + } + $found = strpos($message, $error); + } + if ($found === false) { + printValues($errMsg, $det, $rand, $inputValues); + } + } + } + dropProc($conn, $spname); + dropTable($conn, $tbname); + } + } + unset($stmt); + unset($conn); + } catch (PDOException $e) { + echo $e->getMessage(); + } +} + +testOutputDatetimes(false); +testOutputDatetimes(true); + +echo "Done\n"; + +?> +--CLEAN-- + +--EXPECT-- +Done \ No newline at end of file diff --git a/test/functional/pdo_sqlsrv/pdo_ae_output_param_decimals.phpt b/test/functional/pdo_sqlsrv/pdo_ae_output_param_decimals.phpt new file mode 100644 index 00000000..f54fdaf2 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_ae_output_param_decimals.phpt @@ -0,0 +1,242 @@ +--TEST-- +Test for retrieving encrypted data of decimals/numerics as output parameters +--DESCRIPTION-- +Use PDOstatement::bindParam with all PDO::PARAM_ types +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- + array(0, 1), + 4 => array(0, 1, 4), + 16 => array(0, 1, 4, 16), + 38 => array(0, 1, 4, 16, 38)); +$inputValuesInit = array(92233720368547758089223372036854775808, -92233720368547758089223372036854775808); +$inputPrecision = 38; + +$errors = array("IMSSP" => "An invalid PHP type was specified as an output parameter. DateTime objects, NULL values, and streams cannot be specified as output parameters."); + +$pdoParamTypes = array( + PDO::PARAM_BOOL, // 5 + PDO::PARAM_NULL, // 0 + PDO::PARAM_INT, // 1 + PDO::PARAM_STR, // 2 + PDO::PARAM_LOB // 3 +); + +function printValues($msg, $det, $rand, $inputValues) +{ + echo $msg; + echo "input 0: "; var_dump($inputValues[0]); + echo "fetched: "; var_dump($det); + echo "input 1: "; var_dump($inputValues[1]); + echo "fetched: "; var_dump($rand); +} + +// this function returns true if the floats are more different than expected +function compareFloats($actual, $expected) +{ + $epsilon = 0.00001; + $diff = abs(($actual - $expected) / $expected); + return ($diff > $epsilon); +} + +// function compareIntegers() returns false when the fetched values +// are different from the expected inputs +function compareIntegers($det, $rand, $inputValues, $pdoParamType) +{ + /////////////////////////////////////////////////////////////////////// + // See GitHub issue 707 - Fix this method when the problem is addressed + // + // Assume $pdoParamType is PDO::PARAM_BOOL or PDO::PARAM_INT + if (is_string($det)) { + return (!compareFloats(floatval($det), $inputValues[0]) + && !compareFloats(floatval($rand), $inputValues[1])); + } elseif ($pdoParamType == PDO::PARAM_INT) { + $input0 = floor($inputValues[0]); // the positive float + $input1 = ceil($inputValues[1]); // the negative float + + return ($det == $input0 && $rand == $input1); + } else { + // $pdoParamType == PDO::PARAM_BOOL + // Expect bool(true) or bool(false) depending on the rounded input values + // But with AE enabled (aforementioned GitHub issue), the fetched values + // are floats instead, which should be fixed + $input0 = floor($inputValues[0]); // the positive float + $input1 = ceil($inputValues[1]); // the negative float + if (isAEConnected()) { + $det = boolval(floor($det)); + $rand = boolval(ceil($rand)); + } + + return ($det == boolval($input0) && $rand == boolval($input1)); + } +} + +// function compareDecimals() returns false when the fetched values +// are different from the inputs, based on precision, scale +function compareDecimals($det, $rand, $inputValues, $pdoParamType, $precision, $scale) +{ + // Assume $pdoParamType is PDO::PARAM_STR + for ($i = 0; $i < 2; $i++) { + $inputStr = strval($inputValues[$i]); + $fetchedStr = ($i == 0) ? strval(floatval($det)) : strval(floatval($rand)); + + if ($precision == $scale) { + // compare up to $precision + digits left if radix point ('.') + + // 1 digit ('.') + possibly the negative sign + $len = $precision + 2 + $i; + } elseif ($scale > 0) { + // compare up to $precision + 1 digit ('.') + // + possibly the negative sign + $len = $precision + 1 + $i; + } else { + // in this case, $scale = 0 + // compare up to $precision + possibly the negative sign + $len = $precision + $i; + } + + trace("Comparing $len..."); + $result = substr_compare($inputStr, $fetchedStr, 0, $len); + if ($result != 0) { + return false; + } + } + return true; +} + +function testOutputDecimals($inout) +{ + global $pdoParamTypes, $dataTypes, $inputValuesInit, $precisions, $inputPrecision, $errors; + + try { + $conn = connect(); + + $tbname = "test_decimals_types"; + $spname = "test_decimals_proc"; + + foreach ($dataTypes as $dataType) { + foreach ($precisions as $precision => $scales) { + foreach ($scales as $scale) { + // construct the input values depending on the precision and scale + $precDiff = $inputPrecision - ($precision - $scale); + $inputValues = $inputValuesInit; + foreach ($inputValues as &$inputValue) { + $inputValue = $inputValue / pow(10, $precDiff); + } + + $type = "$dataType($precision, $scale)"; + trace("\nTesting $type:\n"); + + //create and populate table + $colMetaArr = array(new ColumnMeta($type, "c_det"), new ColumnMeta($type, "c_rand", null, "randomized")); + createTable($conn, $tbname, $colMetaArr); + + $stmt = insertRow($conn, $tbname, array("c_det" => $inputValues[0], "c_rand" => $inputValues[1] ), null); + + // fetch with PDO::bindParam using a stored procedure + $procArgs = "@c_det $type OUTPUT, @c_rand $type OUTPUT"; + $procCode = "SELECT @c_det = c_det, @c_rand = c_rand FROM $tbname"; + createProc($conn, $spname, $procArgs, $procCode); + + // call stored procedure + $outSql = getCallProcSqlPlaceholders($spname, 2); + foreach ($pdoParamTypes as $pdoParamType) { + $det = $rand = 0.0; + $stmt = $conn->prepare($outSql); + + $len = 2048; + // Do not initialize $det or $rand as empty strings + // See VSO 2915 for details + if ($pdoParamType == PDO::PARAM_BOOL || $pdoParamType == PDO::PARAM_INT) { + $len = PDO::SQLSRV_PARAM_OUT_DEFAULT_SIZE; + $det = $rand = 0; + } + + trace("\nParam $pdoParamType with INOUT = $inout\n"); + if ($inout) { + $paramType = $pdoParamType | PDO::PARAM_INPUT_OUTPUT; + } else { + $paramType = $pdoParamType; + } + + $stmt->bindParam(1, $det, $paramType, $len); + $stmt->bindParam(2, $rand, $paramType, $len); + + try { + $stmt->execute(); + + $errMsg = "****$type as $pdoParamType failed with INOUT = $inout:****\n"; + if ($pdoParamType == PDO::PARAM_BOOL || $pdoParamType == PDO::PARAM_INT) { + if (!compareIntegers($det, $rand, $inputValues, $pdoParamType)) { + printValues($errMsg, $det, $rand, $inputValues); + } + } else { + // When $pdoParamType is PDO::PARAM_STR, the accuracies + // should have been preserved based on the original + // precision and scale, so compare the retrieved values + // against the input values with more details + if (!compareDecimals($det, $rand, $inputValues, $pdoParamType, $precision, $scale)) { + printValues($errMsg, $det, $rand, $inputValues); + } + } + } catch (PDOException $e) { + $message = $e->getMessage(); + $errMsg = "EXCEPTION: ****$type as $pdoParamType failed with INOUT = $inout:****\n"; + if ($pdoParamType == PDO::PARAM_NULL || $pdoParamType == PDO::PARAM_LOB) { + // Expected error IMSSP: "An invalid PHP type was specified + // as an output parameter. DateTime objects, NULL values, and + // streams cannot be specified as output parameters." + $found = strpos($message, $errors['IMSSP']); + if ($found === false) { + printValues($errMsg, $det, $rand, $inputValues); + } + } elseif (!isAEConnected() && $precision >= 16 && $pdoParamType == PDO::PARAM_BOOL) { + // When not AE enabled, large numbers are expected to + // fail when converting to booleans + $error = "Error converting data type $dataType to int"; + $found = strpos($message, $error); + if ($found === false) { + printValues($errMsg, $det, $rand, $inputValues); + } + } else { + printValues($errMsg, $det, $rand, $inputValues); + } + } + } + dropProc($conn, $spname); + dropTable($conn, $tbname); + } + } + } + unset($stmt); + unset($conn); + } catch (PDOException $e) { + echo $e->getMessage(); + } +} + +testOutputDecimals(false); +testOutputDecimals(true); + +echo "Done\n"; + +?> +--CLEAN-- + +--EXPECT-- +Done \ No newline at end of file diff --git a/test/functional/pdo_sqlsrv/pdo_ae_output_param_floats.phpt b/test/functional/pdo_sqlsrv/pdo_ae_output_param_floats.phpt new file mode 100644 index 00000000..eadec952 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_ae_output_param_floats.phpt @@ -0,0 +1,181 @@ +--TEST-- +Test for retrieving encrypted data of floats as output parameters +--DESCRIPTION-- +Use PDOstatement::bindParam with all PDO::PARAM_ types +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- + "An invalid PHP type was specified as an output parameter. DateTime objects, NULL values, and streams cannot be specified as output parameters."); + +$pdoParamTypes = array( + PDO::PARAM_BOOL, // 5 + PDO::PARAM_NULL, // 0 + PDO::PARAM_INT, // 1 + PDO::PARAM_STR, // 2 + PDO::PARAM_LOB // 3 +); + +////////////////////////////////////////////////////////////////////////////////// + +// this function returns true if the floats are more different than expected +function compareFloats($actual, $expected) +{ + $epsilon = 0.00001; + $diff = abs(($actual - $expected) / $expected); + return ($diff > $epsilon); +} + +function printValues($msg, $det, $rand, $inputValues) +{ + echo $msg; + echo "input 0: "; var_dump($inputValues[0]); + echo "fetched: "; var_dump($det); + echo "input 1: "; var_dump($inputValues[1]); + echo "fetched: "; var_dump($rand); +} + +function testOutputFloats($fetchNumeric, $inout) +{ + global $pdoParamTypes, $inputValues, $errors; + + try { + $conn = connect(); + $conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, $fetchNumeric); + + $tbname = "test_floats_types"; + $spname = "test_floats_proc"; + + $bits = array(1, 12, 24, 36, 53); + + foreach ($bits as $bit) { + $type = "float($bit)"; + trace("\nTesting $type:\n"); + + $inputValues = array(); + // create random input values + for ($i = 0; $i < 2; $i++) { + $mantissa = rand(1, 100000000); + $decimals = rand(1, 100000000); + $floatNum = $mantissa + $decimals / 10000000; + if ($i > 0) { + // make the second input negative + $floatNum *= -1; + } + array_push($inputValues, $floatNum); + } + //create and populate table + $colMetaArr = array(new ColumnMeta($type, "c_det"), new ColumnMeta($type, "c_rand", null, "randomized")); + createTable($conn, $tbname, $colMetaArr); + insertRow($conn, + $tbname, + array("c_det" => new BindParamOp(1, $inputValues[0], 'PDO::PARAM_INT'), + "c_rand" => new BindParamOp(2, $inputValues[1], 'PDO::PARAM_INT')), + "prepareBindParam"); + + // fetch with PDO::bindParam using a stored procedure + $procArgs = "@c_det $type OUTPUT, @c_rand $type OUTPUT"; + $procCode = "SELECT @c_det = c_det, @c_rand = c_rand FROM $tbname"; + createProc($conn, $spname, $procArgs, $procCode); + + // call stored procedure + $outSql = getCallProcSqlPlaceholders($spname, 2); + foreach ($pdoParamTypes as $pdoParamType) { + if ($pdoParamType == PDO::PARAM_INT && strtoupper(substr(PHP_OS, 0, 3)) != 'WIN') { + // Bug 2876 in VSO: Linux - when retrieving a float as OUTPUT + // or INOUT parameter with PDO::PARAM_INT, the returned values + // are always single digits, regardless of the original floats + continue; + } + + $det = 0.0; + $rand = 0.0; + $stmt = $conn->prepare($outSql); + + $len = 2048; + if ($pdoParamType == PDO::PARAM_BOOL || $pdoParamType == PDO::PARAM_INT) { + $len = PDO::SQLSRV_PARAM_OUT_DEFAULT_SIZE; + $det = 0; + $rand = 0; + } + trace("\nParam $pdoParamType with INOUT = $inout\n"); + + if ($inout) { + $paramType = $pdoParamType | PDO::PARAM_INPUT_OUTPUT; + } else { + $paramType = $pdoParamType; + } + $stmt->bindParam(1, $det, $paramType, $len); + $stmt->bindParam(2, $rand, $paramType, $len); + + try { + $stmt->execute(); + + $errMsg = "****$type as $pdoParamType failed with INOUT = $inout:****\n"; + if ($pdoParamType == PDO::PARAM_BOOL) { + // for boolean values, they should all be bool(true) + // because all floats are non-zeroes + if (!$det || !$rand) { + printValues($errMsg, $det, $rand, $inputValues); + } + } else { + // Compare the retrieved values against the input values + // if either of them is very different, print them all + if (compareFloats(floatval($det), $inputValues[0]) || + compareFloats(floatval($rand), $inputValues[1])) { + printValues($errMsg, $det, $rand, $inputValues); + } + } + } catch (PDOException $e) { + $message = $e->getMessage(); + $errMsg = "EXCEPTION: ****$type as $pdoParamType failed with INOUT = $inout:****\n"; + + if ($pdoParamType == PDO::PARAM_NULL || $pdoParamType == PDO::PARAM_LOB) { + // Expected error IMSSP: "An invalid PHP type was specified + // as an output parameter. DateTime objects, NULL values, and + // streams cannot be specified as output parameters." + $found = strpos($message, $errors['IMSSP']); + if ($found === false) { + printValues($errMsg, $det, $rand, $inputValues); + } + } else { + printValues($errMsg, $det, $rand, $inputValues); + } + } + } + dropProc($conn, $spname); + dropTable($conn, $tbname); + } + unset($stmt); + unset($conn); + } catch (PDOException $e) { + echo $e->getMessage(); + } +} + +testOutputFloats(false, false); +testOutputFloats(true, false); +testOutputFloats(false, true); +testOutputFloats(true, true); + +echo "Done\n"; + +?> +--CLEAN-- + +--EXPECT-- +Done \ No newline at end of file diff --git a/test/functional/pdo_sqlsrv/pdo_ae_output_param_integers.phpt b/test/functional/pdo_sqlsrv/pdo_ae_output_param_integers.phpt new file mode 100644 index 00000000..c65e6ff8 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_ae_output_param_integers.phpt @@ -0,0 +1,193 @@ +--TEST-- +Test for retrieving encrypted data of integral types as output parameters +--DESCRIPTION-- +Use PDOstatement::bindParam with all PDO::PARAM_ types +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- + "An invalid PHP type was specified as an output parameter. DateTime objects, NULL values, and streams cannot be specified as output parameters.", "22003" => "Numeric value out of range", "42000" => "Error converting data type bigint to int"); + +$pdoParamTypes = array( + PDO::PARAM_BOOL, // 5 + PDO::PARAM_NULL, // 0 + PDO::PARAM_INT, // 1 + PDO::PARAM_STR, // 2 + PDO::PARAM_LOB // 3 +); + +////////////////////////////////////////////////////////////////////////////////// +function printValues($msg, $det, $rand, $inputValues) +{ + echo $msg; + echo "input 0: "; var_dump($inputValues[0]); + echo "fetched: "; var_dump($det); + echo "input 1: "; var_dump($inputValues[1]); + echo "fetched: "; var_dump($rand); +} + +function generateInputs($dataType) +{ + // create random input values based on data types + // make the second input negative but only for some data types + if ($dataType == "bit") { + $inputValues = array(0, 1); + } elseif ($dataType == "tinyint") { + $inputValues = array(); + for ($i = 0; $i < 2; $i++) { + $randomNum = rand(0, 255); + array_push($inputValues, $randomNum); + } + } else { + switch ($dataType) { + case "smallint": + $max = 32767; + break; + case "int": + $max = 2147483647; + break; + default: + $max = getrandmax(); + } + + $inputValues = array(); + for ($i = 0; $i < 2; $i++) { + $randomNum = rand(0, $max); + if ($i > 0) { + // make the second input negative but only for some data types + $randomNum *= -1; + } + array_push($inputValues, $randomNum); + if (traceMode()) { + echo "input: "; var_dump($inputValues[$i]); + } + } + } + return $inputValues; +} + +function testOutputInts($inout) +{ + global $pdoParamTypes, $dataTypes, $errors; + + try { + $conn = connect(); + $tbname = "test_integers_types"; + $spname = "test_integers_proc"; + + foreach ($dataTypes as $dataType) { + trace("\nTesting $dataType:\n"); + + //create and populate table + $colMetaArr = array(new ColumnMeta($dataType, "c_det"), new ColumnMeta($dataType, "c_rand", null, "randomized")); + createTable($conn, $tbname, $colMetaArr); + $inputValues = generateInputs($dataType); + insertRow($conn, $tbname, array("c_det" => $inputValues[0], + "c_rand" => $inputValues[1])); + + // fetch with PDO::bindParam using a stored procedure + $procArgs = "@c_det $dataType OUTPUT, @c_rand $dataType OUTPUT"; + $procCode = "SELECT @c_det = c_det, @c_rand = c_rand FROM $tbname"; + createProc($conn, $spname, $procArgs, $procCode); + + // call stored procedure + $outSql = getCallProcSqlPlaceholders($spname, 2); + foreach ($pdoParamTypes as $pdoParamType) { + $det = 0; + $rand = 0; + $stmt = $conn->prepare($outSql); + + $len = 2048; + if ($pdoParamType == PDO::PARAM_BOOL || $pdoParamType == PDO::PARAM_INT) { + $len = PDO::SQLSRV_PARAM_OUT_DEFAULT_SIZE; + } + trace("\nParam $pdoParamType with INOUT = $inout\n"); + + if ($inout) { + $paramType = $pdoParamType | PDO::PARAM_INPUT_OUTPUT; + } else { + $paramType = $pdoParamType; + } + $stmt->bindParam(1, $det, $paramType, $len); + $stmt->bindParam(2, $rand, $paramType, $len); + + try { + $stmt->execute(); + $errMsg = "****$dataType as $pdoParamType failed with INOUT = $inout:****\n"; + if ($pdoParamType == PDO::PARAM_STR) { + if ($det !== strval($inputValues[0]) || $rand !== strval($inputValues[1])) { + // comparisons between strings, use '!==' + printValues($errMsg, $det, $rand, $inputValues); + } + } elseif ($pdoParamType == PDO::PARAM_INT || $pdoParamType == PDO::PARAM_BOOL) { + // comparisons between integers and booleans, do not use '!==' + if ($det != $inputValues[0] || $rand != $inputValues[1]) { + printValues($errMsg, $det, $rand, $inputValues); + } + } else { + printValues($errMsg, $det, $rand, $inputValues); + } + } catch (PDOException $e) { + $message = $e->getMessage(); + $errMsg = "EXCEPTION: ****$dataType as $pdoParamType failed with INOUT = $inout:****\n"; + if ($pdoParamType == PDO::PARAM_NULL || $pdoParamType == PDO::PARAM_LOB) { + // Expected error IMSSP: "An invalid PHP type was specified + // as an output parameter. DateTime objects, NULL values, and + // streams cannot be specified as output parameters." + $found = strpos($message, $errors['IMSSP']); + if ($found === false) { + printValues($errMsg, $det, $rand, $inputValues); + } + } elseif ($dataType == "bigint" && ($pdoParamType == PDO::PARAM_INT || $pdoParamType == PDO::PARAM_BOOL)) { + if (isAEConnected()) { + // Expected error 22003: "Numeric value out of range" + // This is expected when converting big integer to integer or bool + $found = strpos($message, $errors['22003']); + } elseif ($pdoParamType == PDO::PARAM_BOOL) { + // Expected error 42000: "Error converting data type bigint to int" + // This is expected when not AE connected and converting big integer to bool + $found = strpos($message, $errors['42000']); + } + if ($found === false) { + printValues($errMsg, $det, $rand, $inputValues); + } + } else { + printValues($errMsg, $det, $rand, $inputValues); + } + } + } + dropProc($conn, $spname); + dropTable($conn, $tbname); + } + unset($stmt); + unset($conn); + } catch (PDOException $e) { + echo $e->getMessage(); + } +} + +testOutputInts(false); +testOutputInts(true); + +echo "Done\n"; + +?> +--CLEAN-- + +--EXPECT-- +Done \ No newline at end of file diff --git a/test/functional/pdo_sqlsrv/pdo_ae_output_param_nchar_size.phpt b/test/functional/pdo_sqlsrv/pdo_ae_output_param_nchar_size.phpt new file mode 100644 index 00000000..e6da0d9c --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_ae_output_param_nchar_size.phpt @@ -0,0 +1,172 @@ +--TEST-- +Test for retrieving encrypted data of nchar types of various sizes as output parameters +--DESCRIPTION-- +Use PDOstatement::bindParam with all PDO::PARAM_ types +Note: Because the maximum allowable table row size is 8060 bytes, 7 bytes of which are reserved for internal overhead. In other words, this allows up to two nvarchar() columns with length slightly +more than 2000 wide characters. Therefore, the max length in this test is 2010. +--ENV-- +PHPT_EXEC=true +--SKIPIF-- + +--FILE-- + "An invalid PHP type was specified as an output parameter. DateTime objects, NULL values, and streams cannot be specified as output parameters.", "22003" => "Numeric value out of range"); + +$pdoParamTypes = array( + PDO::PARAM_BOOL, // 5 + PDO::PARAM_NULL, // 0 + PDO::PARAM_INT, // 1 + PDO::PARAM_STR, // 2 + PDO::PARAM_LOB // 3 +); + +////////////////////////////////////////////////////////////////////////////////// +function printValues($msg, $det, $rand, $input0, $input1) +{ + echo $msg; + echo "input 0: "; var_dump($input0); + echo "fetched: "; var_dump($det); + echo "input 1: "; var_dump($input1); + echo "fetched: "; var_dump($rand); +} + +function testOutputNChars($inout) +{ + global $pdoParamTypes, $dataTypes, $lengths, $errors; + + try { + $conn = connect(); + $tbname = "test_nchar_types"; + $spname = "test_nchar_proc"; + + foreach ($dataTypes as $dataType) { + $maxtype = strpos($dataType, "(max)"); + foreach ($lengths as $length) { + if ($maxtype !== false) { + $type = $dataType; + } else { + $type = "$dataType($length)"; + } + trace("\nTesting $type:\n"); + + //create and populate table + $colMetaArr = array(new ColumnMeta($type, "c_det"), new ColumnMeta($type, "c_rand", null, "randomized")); + createTable($conn, $tbname, $colMetaArr); + $input0 = str_repeat("1", $length); + $input1 = str_repeat("2", $length); + insertRow($conn, $tbname, array("c_det" => $input0, + "c_rand" => $input1)); + + // fetch with PDO::bindParam using a stored procedure + $procArgs = "@c_det $type OUTPUT, @c_rand $type OUTPUT"; + $procCode = "SELECT @c_det = c_det, @c_rand = c_rand FROM $tbname"; + createProc($conn, $spname, $procArgs, $procCode); + + // call stored procedure + $outSql = getCallProcSqlPlaceholders($spname, 2); + foreach ($pdoParamTypes as $pdoParamType) { + $det = ""; + $rand = ""; + $stmt = $conn->prepare($outSql); + trace("\nParam $pdoParamType with INOUT = $inout\n"); + + if ($inout) { + $paramType = $pdoParamType | PDO::PARAM_INPUT_OUTPUT; + } else { + $paramType = $pdoParamType; + } + + $len = $length; + if ($pdoParamType == PDO::PARAM_BOOL || $pdoParamType == PDO::PARAM_INT) { + $len = PDO::SQLSRV_PARAM_OUT_DEFAULT_SIZE; + $det = $rand = 0; + } + + $stmt->bindParam(1, $det, $paramType, $len); + $stmt->bindParam(2, $rand, $paramType, $len); + + try { + $stmt->execute(); + $errMsg = "****$type as $pdoParamType failed with INOUT = $inout:****\n"; + // When $length >= 64, a string is returned regardless of $pdoParamType + if ($length < 64 && $pdoParamType != PDO::PARAM_STR) { + if ($pdoParamType == PDO::PARAM_BOOL) { + // For boolean values, they should all be bool(true) + // because all "string literals" are non-zeroes + if (!$det || !$rand) { + printValues($errMsg, $det, $rand, $input0, $input1); + } + } else { + // $pdoParamType = PDO::PARAM_INT + // Expect numeric values + if ($det != intval($input0) || $rand != intval($input1)) { + printValues($errMsg, $det, $rand, $input0, $input1); + } + } + } elseif ($det !== $input0 || $rand !== $input1) { + printValues($errMsg, $det, $rand, $input0, $input1); + } + } catch (PDOException $e) { + $message = $e->getMessage(); + $errMsg = "EXCEPTION: ****$type as $pdoParamType failed with INOUT = $inout:****\n"; + if ($pdoParamType == PDO::PARAM_NULL || $pdoParamType == PDO::PARAM_LOB) { + // Expected error IMSSP: "An invalid PHP type was specified + // as an output parameter. DateTime objects, NULL values, and + // streams cannot be specified as output parameters." + $found = strpos($message, $errors['IMSSP']); + if ($found === false) { + printValues($errMsg, $det, $rand, $input0, $input1); + } + } elseif ($pdoParamType == PDO::PARAM_BOOL) { + if (isAEConnected()) { + // Expected error 22003: "Numeric value out of range" + $found = strpos($message, $errors['22003']); + } else { + // When not AE enabled, expected to fail to convert + // whatever char type to integers + $error = "Error converting data type $dataType to int"; + $found = strpos($message, $error); + } + if ($found === false) { + printValues($errMsg, $det, $rand, $input0, $input1); + } + } else { + printValues($errMsg, $det, $rand, $input0, $input1); + } + } + } + dropProc($conn, $spname); + dropTable($conn, $tbname); + } + } + unset($stmt); + unset($conn); + } catch (PDOException $e) { + echo $e->getMessage(); + } +} + +testOutputNChars(false); +testOutputNChars(true); + +echo "Done\n"; + +?> +--CLEAN-- + +--EXPECT-- +Done \ No newline at end of file