Merge pull request #621 from v-kaywon/AEStreamError
add error handling for fetching stream with always encrypted
This commit is contained in:
commit
54ca7ffed6
|
@ -421,6 +421,10 @@ pdo_error PDO_ERRORS[] = {
|
|||
SQLSRV_ERROR_OUTPUT_PARAM_TYPES_NOT_SUPPORTED,
|
||||
{ IMSSP, (SQLCHAR*) "Stored Procedures do not support text, ntext or image as OUTPUT parameters.", -83, false }
|
||||
},
|
||||
{
|
||||
SQLSRV_ERROR_ENCRYPTED_STREAM_FETCH,
|
||||
{ IMSSP, (SQLCHAR*) "Connection with Column Encryption enabled does not support fetching stream. Please fetch the data as a string.", -84, false }
|
||||
},
|
||||
{ UINT_MAX, {} }
|
||||
};
|
||||
|
||||
|
|
|
@ -1716,6 +1716,7 @@ enum SQLSRV_ERROR_CODES {
|
|||
SQLSRV_ERROR_KEYSTORE_KEY_MISSING,
|
||||
SQLSRV_ERROR_KEYSTORE_INVALID_VALUE,
|
||||
SQLSRV_ERROR_OUTPUT_PARAM_TYPES_NOT_SUPPORTED,
|
||||
SQLSRV_ERROR_ENCRYPTED_STREAM_FETCH,
|
||||
|
||||
// Driver specific error codes starts from here.
|
||||
SQLSRV_ERROR_DRIVER_SPECIFIC = 1000,
|
||||
|
|
|
@ -1580,193 +1580,196 @@ size_t calc_utf8_missing( _Inout_ sqlsrv_stmt* stmt, _In_reads_(buffer_end) cons
|
|||
// the driver layer would have to calculate size of the field_value
|
||||
// to decide the amount of memory allocation.
|
||||
void core_get_field_common( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _Inout_ sqlsrv_phptype
|
||||
sqlsrv_php_type, _Inout_updates_bytes_(*field_len) void*& field_value, _Inout_ SQLLEN* field_len TSRMLS_DC )
|
||||
sqlsrv_php_type, _Inout_updates_bytes_(*field_len) void*& field_value, _Inout_ SQLLEN* field_len TSRMLS_DC )
|
||||
{
|
||||
try {
|
||||
try {
|
||||
|
||||
close_active_stream( stmt TSRMLS_CC );
|
||||
close_active_stream( stmt TSRMLS_CC );
|
||||
|
||||
// make sure that fetch is called before trying to retrieve.
|
||||
CHECK_CUSTOM_ERROR( !stmt->fetch_called, stmt, SQLSRV_ERROR_FETCH_NOT_CALLED ) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
// make sure that fetch is called before trying to retrieve.
|
||||
CHECK_CUSTOM_ERROR( !stmt->fetch_called, stmt, SQLSRV_ERROR_FETCH_NOT_CALLED ) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
|
||||
// make sure that fields are not retrieved incorrectly.
|
||||
CHECK_CUSTOM_ERROR( stmt->last_field_index > field_index, stmt, SQLSRV_ERROR_FIELD_INDEX_ERROR, field_index,
|
||||
stmt->last_field_index ) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
// make sure that fields are not retrieved incorrectly.
|
||||
CHECK_CUSTOM_ERROR( stmt->last_field_index > field_index, stmt, SQLSRV_ERROR_FIELD_INDEX_ERROR, field_index,
|
||||
stmt->last_field_index ) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
|
||||
switch( sqlsrv_php_type.typeinfo.type ) {
|
||||
switch( sqlsrv_php_type.typeinfo.type ) {
|
||||
|
||||
case SQLSRV_PHPTYPE_INT:
|
||||
{
|
||||
sqlsrv_malloc_auto_ptr<long> field_value_temp;
|
||||
field_value_temp = static_cast<long*>( sqlsrv_malloc( sizeof( long )));
|
||||
*field_value_temp = 0;
|
||||
case SQLSRV_PHPTYPE_INT:
|
||||
{
|
||||
sqlsrv_malloc_auto_ptr<long> field_value_temp;
|
||||
field_value_temp = static_cast<long*>( sqlsrv_malloc( sizeof( long )));
|
||||
*field_value_temp = 0;
|
||||
|
||||
SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_LONG, field_value_temp, sizeof( long ),
|
||||
field_len, true /*handle_warning*/ TSRMLS_CC );
|
||||
SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_LONG, field_value_temp, sizeof( long ),
|
||||
field_len, true /*handle_warning*/ TSRMLS_CC );
|
||||
|
||||
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
|
||||
CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
|
||||
if( *field_len == SQL_NULL_DATA ) {
|
||||
field_value = NULL;
|
||||
break;
|
||||
}
|
||||
if( *field_len == SQL_NULL_DATA ) {
|
||||
field_value = NULL;
|
||||
break;
|
||||
}
|
||||
|
||||
field_value = field_value_temp;
|
||||
field_value_temp.transferred();
|
||||
break;
|
||||
}
|
||||
field_value = field_value_temp;
|
||||
field_value_temp.transferred();
|
||||
break;
|
||||
}
|
||||
|
||||
case SQLSRV_PHPTYPE_FLOAT:
|
||||
{
|
||||
sqlsrv_malloc_auto_ptr<double> field_value_temp;
|
||||
field_value_temp = static_cast<double*>( sqlsrv_malloc( sizeof( double )));
|
||||
case SQLSRV_PHPTYPE_FLOAT:
|
||||
{
|
||||
sqlsrv_malloc_auto_ptr<double> field_value_temp;
|
||||
field_value_temp = static_cast<double*>( sqlsrv_malloc( sizeof( double )));
|
||||
|
||||
SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_DOUBLE, field_value_temp, sizeof( double ),
|
||||
field_len, true /*handle_warning*/ TSRMLS_CC );
|
||||
SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_DOUBLE, field_value_temp, sizeof( double ),
|
||||
field_len, true /*handle_warning*/ TSRMLS_CC );
|
||||
|
||||
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
|
||||
CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
|
||||
if( *field_len == SQL_NULL_DATA ) {
|
||||
field_value = NULL;
|
||||
break;
|
||||
}
|
||||
if( *field_len == SQL_NULL_DATA ) {
|
||||
field_value = NULL;
|
||||
break;
|
||||
}
|
||||
|
||||
field_value = field_value_temp;
|
||||
field_value_temp.transferred();
|
||||
break;
|
||||
}
|
||||
field_value = field_value_temp;
|
||||
field_value_temp.transferred();
|
||||
break;
|
||||
}
|
||||
|
||||
case SQLSRV_PHPTYPE_STRING:
|
||||
{
|
||||
get_field_as_string( stmt, field_index, sqlsrv_php_type, field_value, field_len TSRMLS_CC );
|
||||
break;
|
||||
}
|
||||
case SQLSRV_PHPTYPE_STRING:
|
||||
{
|
||||
get_field_as_string( stmt, field_index, sqlsrv_php_type, field_value, field_len TSRMLS_CC );
|
||||
break;
|
||||
}
|
||||
|
||||
// get the date as a string (http://msdn2.microsoft.com/en-us/library/ms712387(VS.85).aspx) and
|
||||
// convert it to a DateTime object and return the created object
|
||||
case SQLSRV_PHPTYPE_DATETIME:
|
||||
{
|
||||
char field_value_temp[ MAX_DATETIME_STRING_LEN ];
|
||||
zval params[1];
|
||||
zval field_value_temp_z;
|
||||
zval function_z;
|
||||
// get the date as a string (http://msdn2.microsoft.com/en-us/library/ms712387(VS.85).aspx) and
|
||||
// convert it to a DateTime object and return the created object
|
||||
case SQLSRV_PHPTYPE_DATETIME:
|
||||
{
|
||||
char field_value_temp[ MAX_DATETIME_STRING_LEN ];
|
||||
zval params[1];
|
||||
zval field_value_temp_z;
|
||||
zval function_z;
|
||||
|
||||
ZVAL_UNDEF( &field_value_temp_z );
|
||||
ZVAL_UNDEF( &function_z );
|
||||
ZVAL_UNDEF( params );
|
||||
ZVAL_UNDEF( &field_value_temp_z );
|
||||
ZVAL_UNDEF( &function_z );
|
||||
ZVAL_UNDEF( params );
|
||||
|
||||
SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_CHAR, field_value_temp,
|
||||
MAX_DATETIME_STRING_LEN, field_len, true TSRMLS_CC );
|
||||
SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_CHAR, field_value_temp,
|
||||
MAX_DATETIME_STRING_LEN, field_len, true TSRMLS_CC );
|
||||
|
||||
CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index ) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
|
||||
zval_auto_ptr return_value_z;
|
||||
return_value_z = ( zval * )sqlsrv_malloc( sizeof( zval ));
|
||||
ZVAL_UNDEF( return_value_z );
|
||||
zval_auto_ptr return_value_z;
|
||||
return_value_z = ( zval * )sqlsrv_malloc( sizeof( zval ));
|
||||
ZVAL_UNDEF( return_value_z );
|
||||
|
||||
if( *field_len == SQL_NULL_DATA ) {
|
||||
ZVAL_NULL( return_value_z );
|
||||
field_value = reinterpret_cast<void*>( return_value_z.get());
|
||||
return_value_z.transferred();
|
||||
break;
|
||||
}
|
||||
if( *field_len == SQL_NULL_DATA ) {
|
||||
ZVAL_NULL( return_value_z );
|
||||
field_value = reinterpret_cast<void*>( return_value_z.get());
|
||||
return_value_z.transferred();
|
||||
break;
|
||||
}
|
||||
|
||||
// Convert the string date to a DateTime object
|
||||
core::sqlsrv_zval_stringl( &field_value_temp_z, field_value_temp, *field_len );
|
||||
core::sqlsrv_zval_stringl( &function_z, "date_create", sizeof("date_create") - 1 );
|
||||
params[0] = field_value_temp_z;
|
||||
// Convert the string date to a DateTime object
|
||||
core::sqlsrv_zval_stringl( &field_value_temp_z, field_value_temp, *field_len );
|
||||
core::sqlsrv_zval_stringl( &function_z, "date_create", sizeof("date_create") - 1 );
|
||||
params[0] = field_value_temp_z;
|
||||
|
||||
if( call_user_function( EG( function_table ), NULL, &function_z, return_value_z, 1,
|
||||
params TSRMLS_CC ) == FAILURE) {
|
||||
THROW_CORE_ERROR(stmt, SQLSRV_ERROR_DATETIME_CONVERSION_FAILED);
|
||||
}
|
||||
if( call_user_function( EG( function_table ), NULL, &function_z, return_value_z, 1,
|
||||
params TSRMLS_CC ) == FAILURE) {
|
||||
THROW_CORE_ERROR(stmt, SQLSRV_ERROR_DATETIME_CONVERSION_FAILED);
|
||||
}
|
||||
|
||||
field_value = reinterpret_cast<void*>( return_value_z.get());
|
||||
return_value_z.transferred();
|
||||
zend_string_free( Z_STR( field_value_temp_z ));
|
||||
zend_string_free( Z_STR( function_z ));
|
||||
break;
|
||||
}
|
||||
field_value = reinterpret_cast<void*>( return_value_z.get());
|
||||
return_value_z.transferred();
|
||||
zend_string_free( Z_STR( field_value_temp_z ));
|
||||
zend_string_free( Z_STR( function_z ));
|
||||
break;
|
||||
}
|
||||
|
||||
// create a stream wrapper around the field and return that object to the PHP script. calls to fread
|
||||
// on the stream will result in calls to SQLGetData. This is handled in stream.cpp. See that file
|
||||
// for how these fields are used.
|
||||
case SQLSRV_PHPTYPE_STREAM:
|
||||
{
|
||||
// create a stream wrapper around the field and return that object to the PHP script. calls to fread
|
||||
// on the stream will result in calls to SQLGetData. This is handled in stream.cpp. See that file
|
||||
// for how these fields are used.
|
||||
case SQLSRV_PHPTYPE_STREAM:
|
||||
{
|
||||
CHECK_CUSTOM_ERROR(stmt->conn->ce_option.enabled, stmt, SQLSRV_ERROR_ENCRYPTED_STREAM_FETCH) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
|
||||
php_stream* stream = NULL;
|
||||
sqlsrv_stream* ss = NULL;
|
||||
SQLLEN sql_type;
|
||||
php_stream* stream = NULL;
|
||||
sqlsrv_stream* ss = NULL;
|
||||
SQLLEN sql_type;
|
||||
|
||||
SQLRETURN r = SQLColAttributeW( stmt->handle(), field_index + 1, SQL_DESC_TYPE, NULL, 0, NULL, &sql_type );
|
||||
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
SQLRETURN r = SQLColAttributeW( stmt->handle(), field_index + 1, SQL_DESC_TYPE, NULL, 0, NULL, &sql_type );
|
||||
CHECK_SQL_ERROR_OR_WARNING( r, stmt ) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
|
||||
CHECK_CUSTOM_ERROR( !is_streamable_type( sql_type ), stmt, SQLSRV_ERROR_STREAMABLE_TYPES_ONLY ) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
CHECK_CUSTOM_ERROR( !is_streamable_type( sql_type ), stmt, SQLSRV_ERROR_STREAMABLE_TYPES_ONLY ) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
|
||||
stream = php_stream_open_wrapper( "sqlsrv://sqlncli10", "r", 0, NULL );
|
||||
stream = php_stream_open_wrapper( "sqlsrv://sqlncli10", "r", 0, NULL );
|
||||
|
||||
CHECK_CUSTOM_ERROR( !stream, stmt, SQLSRV_ERROR_STREAM_CREATE ) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
CHECK_CUSTOM_ERROR( !stream, stmt, SQLSRV_ERROR_STREAM_CREATE ) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
|
||||
ss = static_cast<sqlsrv_stream*>( stream->abstract );
|
||||
ss->stmt = stmt;
|
||||
ss->field_index = field_index;
|
||||
ss->sql_type = static_cast<SQLUSMALLINT>( sql_type );
|
||||
ss->encoding = static_cast<SQLSRV_ENCODING>( sqlsrv_php_type.typeinfo.encoding );
|
||||
ss = static_cast<sqlsrv_stream*>( stream->abstract );
|
||||
ss->stmt = stmt;
|
||||
ss->field_index = field_index;
|
||||
ss->sql_type = static_cast<SQLUSMALLINT>( sql_type );
|
||||
ss->encoding = static_cast<SQLSRV_ENCODING>( sqlsrv_php_type.typeinfo.encoding );
|
||||
|
||||
zval_auto_ptr return_value_z;
|
||||
return_value_z = ( zval * )sqlsrv_malloc( sizeof( zval ));
|
||||
ZVAL_UNDEF( return_value_z );
|
||||
zval_auto_ptr return_value_z;
|
||||
return_value_z = ( zval * )sqlsrv_malloc( sizeof( zval ));
|
||||
ZVAL_UNDEF( return_value_z );
|
||||
|
||||
// turn our stream into a zval to be returned
|
||||
php_stream_to_zval( stream, return_value_z );
|
||||
// turn our stream into a zval to be returned
|
||||
php_stream_to_zval( stream, return_value_z );
|
||||
|
||||
field_value = reinterpret_cast<void*>( return_value_z.get());
|
||||
return_value_z.transferred();
|
||||
break;
|
||||
}
|
||||
field_value = reinterpret_cast<void*>( return_value_z.get());
|
||||
return_value_z.transferred();
|
||||
break;
|
||||
}
|
||||
|
||||
case SQLSRV_PHPTYPE_NULL:
|
||||
field_value = NULL;
|
||||
*field_len = 0;
|
||||
break;
|
||||
case SQLSRV_PHPTYPE_NULL:
|
||||
field_value = NULL;
|
||||
*field_len = 0;
|
||||
break;
|
||||
|
||||
default:
|
||||
DIE( "core_get_field_common: Unexpected sqlsrv_phptype provided" );
|
||||
break;
|
||||
}
|
||||
default:
|
||||
DIE( "core_get_field_common: Unexpected sqlsrv_phptype provided" );
|
||||
break;
|
||||
}
|
||||
|
||||
// sucessfully retrieved the field, so update our last retrieved field
|
||||
if( stmt->last_field_index < field_index ) {
|
||||
stmt->last_field_index = field_index;
|
||||
}
|
||||
}
|
||||
catch( core::CoreException& e ) {
|
||||
throw e;
|
||||
}
|
||||
// sucessfully retrieved the field, so update our last retrieved field
|
||||
if( stmt->last_field_index < field_index ) {
|
||||
stmt->last_field_index = field_index;
|
||||
}
|
||||
}
|
||||
catch( core::CoreException& e ) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -412,6 +412,10 @@ ss_error SS_ERRORS[] = {
|
|||
SQLSRV_ERROR_OUTPUT_PARAM_TYPES_NOT_SUPPORTED,
|
||||
{ IMSSP, (SQLCHAR*) "Stored Procedures do not support text, ntext or image as OUTPUT parameters.", -108, false }
|
||||
},
|
||||
{
|
||||
SQLSRV_ERROR_ENCRYPTED_STREAM_FETCH,
|
||||
{ IMSSP, (SQLCHAR*) "Connection with Column Encryption enabled does not support fetching stream. Please fetch the data as a string.", -109, false }
|
||||
},
|
||||
|
||||
// terminate the list of errors/warnings
|
||||
{ UINT_MAX, {} }
|
||||
|
|
80
test/functional/sqlsrv/sqlsrv_fetch_large_stream.phpt
Normal file
80
test/functional/sqlsrv/sqlsrv_fetch_large_stream.phpt
Normal file
|
@ -0,0 +1,80 @@
|
|||
--TEST--
|
||||
Streaming Field Test
|
||||
--DESCRIPTION--
|
||||
Verifies the streaming behavior and proper error handling with Always Encrypted
|
||||
--SKIPIF--
|
||||
<?php require('skipif_versions_old.inc'); ?>
|
||||
--FILE--
|
||||
<?php
|
||||
require_once('MsCommon.inc');
|
||||
|
||||
$conn = AE\connect();
|
||||
|
||||
$tableName = "test_max_fields";
|
||||
AE\createTable($conn, $tableName, array(new AE\ColumnMeta("varchar(max)", "varchar_max_col")));
|
||||
|
||||
$inValue = str_repeat("ÃÜðßZZýA©", 600);
|
||||
$insertSql = "INSERT INTO $tableName (varchar_max_col) VALUES (?)";
|
||||
$params = array($inValue);
|
||||
|
||||
$stmt = sqlsrv_prepare($conn, $insertSql, $params);
|
||||
if ($stmt) {
|
||||
sqlsrv_execute($stmt);
|
||||
}
|
||||
|
||||
$query = "SELECT * FROM $tableName";
|
||||
$stmt = sqlsrv_prepare($conn, $query);
|
||||
if ($stmt) {
|
||||
sqlsrv_execute($stmt);
|
||||
}
|
||||
|
||||
if (!sqlsrv_fetch($stmt)) {
|
||||
fatalError("Failed to fetch row ");
|
||||
}
|
||||
|
||||
$stream = sqlsrv_get_field($stmt, 0, SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_CHAR));
|
||||
|
||||
$success = false;
|
||||
if ($stream === false) {
|
||||
$error = sqlsrv_errors()[0];
|
||||
if (AE\isColEncrypted() && $error['SQLSTATE'] === "IMSSP" && $error['code'] === -109 &&
|
||||
$error['message'] === "Connection with Column Encryption enabled does not support fetching stream. Please fetch the data as a string.") {
|
||||
$success = true;
|
||||
}
|
||||
} else {
|
||||
$value = '';
|
||||
if (!AE\isColEncrypted()) {
|
||||
$num = 0;
|
||||
while (!feof($stream)) {
|
||||
$value .= fread($stream, 8192);
|
||||
}
|
||||
fclose($stream);
|
||||
if (checkData($value, $inValue)) { // compare the data to see if they match!
|
||||
$success = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($success) {
|
||||
echo "Done.\n";
|
||||
} else {
|
||||
fatalError("Failed to fetch stream ");
|
||||
}
|
||||
|
||||
function checkData($actual, $expected)
|
||||
{
|
||||
$success = true;
|
||||
|
||||
$pos = strpos($actual, $expected);
|
||||
if (($pos === false) || ($pos > 1)) {
|
||||
$success = false;
|
||||
}
|
||||
|
||||
if (!$success) {
|
||||
trace("\nData error\nExpected:\n$expected\nActual:\n$actual\n");
|
||||
}
|
||||
|
||||
return ($success);
|
||||
}
|
||||
?>
|
||||
--EXPECT--
|
||||
Done.
|
Loading…
Reference in a new issue