Merge pull request #532 from Hadis-Fard/AlwaysEncrypted

ODBC 17 support
This commit is contained in:
Hadis Fard 2017-09-15 13:49:20 -07:00 committed by GitHub
commit ffe1a38f64
10 changed files with 521 additions and 268 deletions

View file

@ -43,6 +43,7 @@ const char AttachDBFileName[] = "AttachDbFileName";
const char ConnectionPooling[] = "ConnectionPooling";
const char Authentication[] = "Authentication";
const char ColumnEncryption[] = "ColumnEncryption";
const char Driver[] = "Driver";
const char CEKeystoreProvider[] = "CEKeystoreProvider";
const char CEKeystoreName[] = "CEKeystoreName";
const char CEKeystoreEncryptKey[] = "CEKeystoreEncryptKey";
@ -234,6 +235,15 @@ const connection_option PDO_CONN_OPTS[] = {
CONN_ATTR_STRING,
column_encryption_set_func::func
},
{
PDOConnOptionNames::Driver,
sizeof(PDOConnOptionNames::Driver),
SQLSRV_CONN_OPTION_DRIVER,
ODBCConnOptions::Driver,
sizeof(ODBCConnOptions::Driver),
CONN_ATTR_STRING,
driver_set_func::func
},
{
PDOConnOptionNames::CEKeystoreProvider,
sizeof(PDOConnOptionNames::CEKeystoreProvider),

View file

@ -56,8 +56,8 @@ pdo_error PDO_ERRORS[] = {
{
SQLSRV_ERROR_DRIVER_NOT_INSTALLED,
{ IMSSP, (SQLCHAR*) "This extension requires the Microsoft ODBC Driver 13 for SQL Server to "
"communicate with SQL Server. Access the following URL to download the ODBC Driver 13 for SQL Server "
{ IMSSP, (SQLCHAR*) "This extension requires the Microsoft ODBC Driver for SQL Server to "
"communicate with SQL Server. Access the following URL to download the ODBC Driver for SQL Server "
"for %1!s!: "
"http://go.microsoft.com/fwlink/?LinkId=163712", -1, true }
},
@ -380,7 +380,7 @@ pdo_error PDO_ERRORS[] = {
{
PDO_SQLSRV_ERROR_INVALID_AUTHENTICATION_OPTION,
{ IMSSP, (SQLCHAR*) "Invalid option for the Authentication keyword. Only SqlPassword or ActiveDirectoryPassword is supported.", -73, false }
},
},
{
SQLSRV_ERROR_KEYSTORE_NAME_MISSING,
{ IMSSP, (SQLCHAR*) "The name of the custom keystore provider is missing.", -74, false}
@ -397,6 +397,14 @@ pdo_error PDO_ERRORS[] = {
SQLSRV_ERROR_KEYSTORE_INVALID_VALUE,
{ IMSSP, (SQLCHAR*) "Invalid value for loading a custom keystore provider.", -77, false}
},
{
SQLSRV_ERROR_AE_DRIVER_NOT_INSTALLED,
{ IMSSP, (SQLCHAR*) "The Always Encrypted feature requires Microsoft ODBC Driver 17 for SQL Server.", -78, false }
},
{
SQLSRV_ERROR_CONNECT_INVALID_DRIVER,
{ IMSSP, (SQLCHAR*) "Invalid value %1!s! was specified for Driver option.", -79, true }
},
{ UINT_MAX, {} }
};

View file

@ -26,9 +26,8 @@
#include <windows.h>
#include <winver.h>
#endif // _WIN32
#include <string>
#include <sstream>
#include <vector>
#ifndef _WIN32
#include <sys/utsname.h>
@ -49,11 +48,12 @@ const int INFO_BUFFER_LEN = 256;
// processor architectures
const char* PROCESSOR_ARCH[] = { "x86", "x64", "ia64" };
// ODBC driver name.
const char* CONNECTION_STRING_DRIVER_NAME[] = {"Driver={ODBC Driver 13 for SQL Server};", "Driver={ODBC Driver 11 for SQL Server};"};
// ODBC driver names.
// the order of this list should match the order of DRIVER_VERSION enum
std::vector<std::string> CONNECTION_STRING_DRIVER_NAME{ "Driver={ODBC Driver 13 for SQL Server};", "Driver={ODBC Driver 11 for SQL Server};", "Driver={ODBC Driver 17 for SQL Server};" };
// default options if only the server is specified
const char CONNECTION_STRING_DEFAULT_OPTIONS[] = "Mars_Connection={Yes}";
const char CONNECTION_STRING_DEFAULT_OPTIONS[] = "Mars_Connection={Yes};";
// connection option appended when no user name or password is given
const char CONNECTION_OPTION_NO_CREDENTIALS[] = "Trusted_Connection={Yes};";
@ -64,8 +64,8 @@ const char CONNECTION_OPTION_MARS_ON[] = "MARS_Connection={Yes};";
// *** internal function prototypes ***
void build_connection_string_and_set_conn_attr( _Inout_ sqlsrv_conn* conn, _Inout_z_ const char* server, _Inout_opt_z_ const char* uid, _Inout_opt_z_ const char* pwd,
_Inout_opt_ HashTable* options_ht, _In_ const connection_option valid_conn_opts[],
void* driver,_Inout_ std::string& connection_string TSRMLS_DC );
_Inout_opt_ HashTable* options_ht, _In_ const connection_option valid_conn_opts[],
void* driver,_Inout_ std::string& connection_string TSRMLS_DC );
void determine_server_version( _Inout_ sqlsrv_conn* conn TSRMLS_DC );
const char* get_processor_arch( void );
void get_server_version( _Inout_ sqlsrv_conn* conn, _Outptr_result_buffer_(len) char** server_version, _Out_ SQLSMALLINT& len TSRMLS_DC );
@ -99,13 +99,14 @@ sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_cont
std::string conn_str;
conn_str.reserve( DEFAULT_CONN_STR_LEN );
sqlsrv_malloc_auto_ptr<sqlsrv_conn> conn;
sqlsrv_malloc_auto_ptr<SQLWCHAR> wconn_string;
unsigned int wconn_len = 0;
bool is_pooled = false;
#ifdef _WIN32
sqlsrv_context* henv = &henv_cp; // by default use the connection pooling henv
is_pooled = true;
#else
sqlsrv_context* henv = &henv_ncp; // by default do not use the connection pooling henv
is_pooled = false;
#endif // _WIN32
try {
@ -121,6 +122,7 @@ sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_cont
( toupper( pooling_string[ 0 ] ) == 'O' && toupper( pooling_string[ 1 ] ) == 'N' ))
{
henv = &henv_cp;
is_pooled = true;
}
#else
// check the connection pooling setting to determine which henv to use to allocate the connection handle
@ -130,11 +132,12 @@ sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_cont
if( options_ht && zend_hash_num_elements( options_ht ) > 0 ) {
zval* option_z = NULL;
option_z = zend_hash_index_find(options_ht, SQLSRV_CONN_OPTION_CONN_POOLING);
option_z = zend_hash_index_find( options_ht, SQLSRV_CONN_OPTION_CONN_POOLING );
if ( option_z ) {
// if the option was found and it's not true, then use the non pooled environment handle
if(( Z_TYPE_P( option_z ) == IS_STRING && !core_str_zval_is_true( option_z )) || !zend_is_true( option_z ) ) {
henv = &henv_ncp;
henv = &henv_ncp;
is_pooled = false;
}
}
}
@ -142,75 +145,51 @@ sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_cont
SQLHANDLE temp_conn_h;
core::SQLAllocHandle( SQL_HANDLE_DBC, *henv, &temp_conn_h TSRMLS_CC );
conn = conn_factory( temp_conn_h, err, driver TSRMLS_CC );
conn->set_func( driver_func );
build_connection_string_and_set_conn_attr( conn, server, uid, pwd, options_ht, valid_conn_opts, driver, conn_str TSRMLS_CC );
bool is_missing_driver = false;
for( std::size_t i = DRIVER_VERSION::MIN; i <= DRIVER_VERSION::MAX; ++i ) {
conn_str = CONNECTION_STRING_DRIVER_NAME[i];
build_connection_string_and_set_conn_attr(conn, server, uid, pwd, options_ht, valid_conn_opts, driver, conn_str TSRMLS_CC);
if ( conn->is_driver_set ) {
r = core_odbc_connect( conn, conn_str, is_missing_driver, is_pooled );
}
else if ( conn->ce_option.enabled ) {
conn_str = conn_str + CONNECTION_STRING_DRIVER_NAME[ DRIVER_VERSION::ODBC_DRIVER_17 ];
r = core_odbc_connect( conn, conn_str, is_missing_driver, is_pooled );
// We only support UTF-8 encoding for connection string.
// Convert our UTF-8 connection string to UTF-16 before connecting with SQLDriverConnnectW
wconn_len = static_cast<unsigned int>( conn_str.length() + 1 ) * sizeof( SQLWCHAR );
CHECK_CUSTOM_ERROR( is_missing_driver, conn, SQLSRV_ERROR_AE_DRIVER_NOT_INSTALLED, get_processor_arch()) {
throw core::CoreException();
}
}
else {
wconn_string = utf16_string_from_mbcs_string( SQLSRV_ENCODING_UTF8, conn_str.c_str(), static_cast<unsigned int>(conn_str.length()), &wconn_len );
CHECK_CUSTOM_ERROR( wconn_string == 0, conn, SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE, get_last_error_message())
{
for ( std::size_t i = DRIVER_VERSION::FIRST; i <= DRIVER_VERSION::LAST; ++i ) {
is_missing_driver = false;
std::string conn_str_driver = conn_str + CONNECTION_STRING_DRIVER_NAME[ DRIVER_VERSION(i) ];
r = core_odbc_connect( conn, conn_str_driver, is_missing_driver, is_pooled );
CHECK_CUSTOM_ERROR( is_missing_driver && ( i == DRIVER_VERSION::LAST ), conn, SQLSRV_ERROR_DRIVER_NOT_INSTALLED, get_processor_arch()) {
throw core::CoreException();
}
SQLSMALLINT output_conn_size;
#ifndef _WIN32
// unixODBC 2.3.1 requires a non-wide SQLDriverConnect call while pooling enabled.
// connection handle has been allocated using henv_cp, means pooling enabled in a PHP script
if ( henv == &henv_cp )
{
r = SQLDriverConnect( conn->handle(), NULL, (SQLCHAR*)conn_str.c_str(), SQL_NTS, NULL, 0, &output_conn_size, SQL_DRIVER_NOPROMPT );
}
else
{
r = SQLDriverConnectW( conn->handle(), NULL, reinterpret_cast<SQLWCHAR*>( wconn_string.get() ), static_cast<SQLSMALLINT>( wconn_len ), NULL, 0, &output_conn_size, SQL_DRIVER_NOPROMPT );
}
#else
r = SQLDriverConnectW( conn->handle(), NULL, reinterpret_cast<SQLWCHAR*>( wconn_string.get() ), static_cast<SQLSMALLINT>( wconn_len ), NULL, 0, &output_conn_size, SQL_DRIVER_NOPROMPT );
#endif // !_WIN32
// clear the connection string from memory to remove sensitive data (such as a password).
memset( const_cast<char*>(conn_str.c_str()), 0, conn_str.size());
memset( wconn_string, 0, wconn_len * sizeof( SQLWCHAR )); // wconn_len is the number of characters, not bytes
conn_str.clear();
if ( !SQL_SUCCEEDED( r )) {
SQLCHAR state[SQL_SQLSTATE_BUFSIZE];
SQLSMALLINT len;
SQLRETURN sr = SQLGetDiagField( SQL_HANDLE_DBC, conn->handle(), 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len );
bool missing_driver_error = ( SQL_SUCCEEDED( sr ) && state[0] == 'I' && state[1] == 'M' && state[2] == '0' && state[3] == '0' && state[4] == '2' );
// if it's a IM002, meaning that the correct ODBC driver is not installed
CHECK_CUSTOM_ERROR(missing_driver_error && ( i == DRIVER_VERSION::MAX ), conn, SQLSRV_ERROR_DRIVER_NOT_INSTALLED, get_processor_arch()) {
throw core::CoreException();
}
if ( !missing_driver_error ) {
break;
}
}
else {
conn->driver_version = static_cast<DRIVER_VERSION>( i );
if ( !is_missing_driver) {
break;
}
}
CHECK_SQL_ERROR( r, conn ) {
throw core::CoreException();
}
} // for
} // else ce_option enabled
CHECK_SQL_ERROR( r, conn ) {
throw core::CoreException();
}
CHECK_SQL_WARNING_AS_ERROR( r, conn ) {
throw core::CoreException();
}
CHECK_SQL_WARNING_AS_ERROR( r, conn ) {
throw core::CoreException();
}
load_configure_ksp( conn );
load_configure_ksp( conn );
// determine the version of the server we're connected to. The server version is left in the
// connection upon return.
// determine the version of the server we're connected to. The server version is left in the
// connection upon return.
//
// unixODBC 2.3.1:
// SQLGetInfo works when r = SQL_SUCCESS_WITH_INFO (non-pooled connection)
@ -225,32 +204,29 @@ sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_cont
#endif // !_WIN32
}
catch( std::bad_alloc& ) {
memset( const_cast<char*>( conn_str.c_str()), 0, conn_str.size() );
memset( wconn_string, 0, wconn_len * sizeof( SQLWCHAR )); // wconn_len is the number of characters, not bytes
conn_str.clear();
conn->invalidate();
DIE( "C++ memory allocation failure building the connection string." );
}
catch( std::out_of_range const& ex ) {
memset( const_cast<char*>( conn_str.c_str()), 0, conn_str.size() );
memset( wconn_string, 0, wconn_len * sizeof( SQLWCHAR )); // wconn_len is the number of characters, not bytes
conn_str.clear();
LOG( SEV_ERROR, "C++ exception returned: %1!s!", ex.what() );
conn->invalidate();
throw;
}
catch( std::length_error const& ex ) {
memset( const_cast<char*>( conn_str.c_str()), 0, conn_str.size() );
memset( wconn_string, 0, wconn_len * sizeof( SQLWCHAR )); // wconn_len is the number of characters, not bytes
conn_str.clear();
LOG( SEV_ERROR, "C++ exception returned: %1!s!", ex.what() );
conn->invalidate();
throw;
}
catch( core::CoreException& ) {
memset( const_cast<char*>( conn_str.c_str()), 0, conn_str.size() );
memset( wconn_string, 0, wconn_len * sizeof( SQLWCHAR )); // wconn_len is the number of characters, not bytes
conn_str.clear();
conn->invalidate();
throw;
}
conn_str.clear();
sqlsrv_conn* return_conn = conn;
conn.transferred();
@ -258,6 +234,60 @@ sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_cont
}
// core_odbc_connect
// calls odbc connect API to establish the connection to server
// Parameters:
// conn - The connection structure on which we establish the connection
// conn_str - Connection string
// missing_driver_error - indicates whether odbc driver is installed on client machine
// is_pooled - indicate whether it is a pooled connection
// Return - SQLRETURN status returned by SQLDriverConnect
SQLRETURN core_odbc_connect( _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str, _Inout_ bool& is_missing_driver, _In_ bool is_pooled )
{
SQLRETURN r = SQL_SUCCESS;
sqlsrv_malloc_auto_ptr<SQLWCHAR> wconn_string;
unsigned int wconn_len = static_cast<unsigned int>( conn_str.length() + 1 ) * sizeof( SQLWCHAR );
// We only support UTF-8 encoding for connection string.
// Convert our UTF-8 connection string to UTF-16 before connecting with SQLDriverConnnectW
wconn_string = utf16_string_from_mbcs_string( SQLSRV_ENCODING_UTF8, conn_str.c_str(), static_cast<unsigned int>( conn_str.length() ), &wconn_len );
CHECK_CUSTOM_ERROR( wconn_string == 0, conn, SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE, get_last_error_message())
{
throw core::CoreException();
}
SQLSMALLINT output_conn_size;
#ifndef _WIN32
// unixODBC 2.3.1 requires a non-wide SQLDriverConnect call while pooling enabled.
// connection handle has been allocated using henv_cp, means pooling enabled in a PHP script
if (is_pooled)
{
r = SQLDriverConnect( conn->handle(), NULL, (SQLCHAR*)conn_str.c_str(), SQL_NTS, NULL, 0, &output_conn_size, SQL_DRIVER_NOPROMPT );
}
else
{
r = SQLDriverConnectW( conn->handle(), NULL, wconn_string, static_cast<SQLSMALLINT>( wconn_len ), NULL, 0, &output_conn_size, SQL_DRIVER_NOPROMPT );
}
#else
r = SQLDriverConnectW( conn->handle(), NULL, wconn_string, static_cast<SQLSMALLINT>( wconn_len ), NULL, 0, &output_conn_size, SQL_DRIVER_NOPROMPT );
#endif // !_WIN32
// clear the connection string from memory to remove sensitive data (such as a password).
memset( wconn_string, 0, wconn_len * sizeof( SQLWCHAR )); // wconn_len is the number of characters, not bytes
conn_str.clear();
if (!SQL_SUCCEEDED(r)) {
SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ];
SQLSMALLINT len;
SQLRETURN sr = SQLGetDiagField( SQL_HANDLE_DBC, conn->handle(), 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len );
// sql state IM002/IM003 in ODBC 17, means that the correct ODBC driver is not installed
is_missing_driver = ( SQL_SUCCEEDED(sr) && state[0] == 'I' && state[1] == 'M' && state[2] == '0' && state[3] == '0' && (state[4] == '2' || state[4] == '3'));
}
return r;
}
// core_sqlsrv_begin_transaction
// Begins a transaction on a specified connection. The current transaction
@ -857,8 +887,7 @@ void common_conn_str_append_func( _In_z_ const char* odbc_name, _In_reads_(val_l
} // namespace
// simply add the parsed value to the connection string
void conn_str_append_func::func( _In_ connection_option const* option, _In_ zval* value, sqlsrv_conn* /*conn*/, _Inout_ std::string& conn_str
TSRMLS_DC )
void conn_str_append_func::func( _In_ connection_option const* option, _In_ zval* value, sqlsrv_conn* /*conn*/, _Inout_ std::string& conn_str TSRMLS_DC )
{
const char* val_str = Z_STRVAL_P( value );
size_t val_len = Z_STRLEN_P( value );
@ -867,12 +896,72 @@ void conn_str_append_func::func( _In_ connection_option const* option, _In_ zval
// do nothing for connection pooling since we handled it earlier when
// deciding which environment handle to use.
void conn_null_func::func( connection_option const* /*option*/, zval* /*value*/, sqlsrv_conn* /*conn*/, std::string& /*conn_str*/
TSRMLS_DC )
void conn_null_func::func( connection_option const* /*option*/, zval* /*value*/, sqlsrv_conn* /*conn*/, std::string& /*conn_str*/ TSRMLS_DC )
{
TSRMLS_C;
}
void driver_set_func::func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str TSRMLS_DC )
{
const char* val_str = Z_STRVAL_P( value );
size_t val_len = Z_STRLEN_P( value );
std::string driver_option( "" );
common_conn_str_append_func( option->odbc_name, val_str, val_len, driver_option TSRMLS_CC );
CHECK_CUSTOM_ERROR( std::find( CONNECTION_STRING_DRIVER_NAME.begin(), CONNECTION_STRING_DRIVER_NAME.end(), driver_option) == CONNECTION_STRING_DRIVER_NAME.end(), conn, SQLSRV_ERROR_CONNECT_INVALID_DRIVER, val_str){
throw core::CoreException();
}
conn->is_driver_set = true;
conn_str += driver_option;
}
void column_encryption_set_func::func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str TSRMLS_DC )
{
convert_to_string( value );
const char* value_str = Z_STRVAL_P( value );
// Column Encryption is disabled by default unless it is explicitly 'Enabled'
conn->ce_option.enabled = false;
if ( !stricmp(value_str, "enabled" )) {
conn->ce_option.enabled = true;
}
conn_str += option->odbc_name;
conn_str += "=";
conn_str += value_str;
conn_str += ";";
}
void ce_ksp_provider_set_func::func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str TSRMLS_DC )
{
SQLSRV_ASSERT( Z_TYPE_P( value ) == IS_STRING, "Wrong zval type for this keyword" )
size_t value_len = Z_STRLEN_P( value );
CHECK_CUSTOM_ERROR( value_len == 0, conn, SQLSRV_ERROR_KEYSTORE_INVALID_VALUE ) {
throw core::CoreException();
}
switch ( option->conn_option_key ) {
case SQLSRV_CONN_OPTION_CEKEYSTORE_PROVIDER:
conn->ce_option.ksp_path = value;
conn->ce_option.ksp_required = true;
break;
case SQLSRV_CONN_OPTION_CEKEYSTORE_NAME:
conn->ce_option.ksp_name = value;
conn->ce_option.ksp_required = true;
break;
case SQLSRV_CONN_OPTION_CEKEYSTORE_ENCRYPT_KEY:
conn->ce_option.ksp_encrypt_key = value;
conn->ce_option.key_size = value_len;
conn->ce_option.ksp_required = true;
break;
default:
SQLSRV_ASSERT(false, "ce_ksp_provider_set_func: Invalid KSP option!");
break;
}
}
// helper function to evaluate whether a string value is true or false.
// Values = ("true" or "1") are treated as true values. Everything else is treated as false.
// Returns 1 for true and 0 for false.

View file

@ -151,6 +151,7 @@ OACR_WARNING_POP
#include <deque>
#include <map>
#include <string>
#include <algorithm>
#include <limits>
#include <cassert>
@ -189,6 +190,9 @@ namespace AzureADOptions {
const char AZURE_AUTH_AD_PASSWORD[] = "ActiveDirectoryPassword";
}
// the message returned by ODBC Driver for SQL Server
const char ODBC_CONNECTION_BUSY_ERROR[] = "Connection is busy with results for another command";
// types for conversions on output parameters (though they can be used for input parameters, they are ignored)
enum SQLSRV_PHPTYPE {
MIN_SQLSRV_PHPTYPE = 1, // lowest value for a php type
@ -1021,7 +1025,6 @@ struct sqlsrv_henv {
}
};
//*********************************************************************************************************************************
// Connection
//*********************************************************************************************************************************
@ -1035,11 +1038,13 @@ enum SERVER_VERSION {
};
// supported driver versions.
enum DRIVER_VERSION : size_t {
MIN = 0,
ODBC_DRIVER_13 = MIN,
ODBC_DRIVER_11 = 1,
MAX = ODBC_DRIVER_11,
// the latest RTWed ODBC is the first one
enum DRIVER_VERSION : std::size_t{
FIRST = 0,
ODBC_DRIVER_13 = FIRST,
ODBC_DRIVER_11 = 1,
ODBC_DRIVER_17 = 2,
LAST = ODBC_DRIVER_17
};
// forward decl
@ -1067,16 +1072,15 @@ struct sqlsrv_conn : public sqlsrv_context {
// instance variables
SERVER_VERSION server_version; // version of the server that we're connected to
DRIVER_VERSION driver_version;
col_encryption_option ce_option; // holds the details of what are required to enable column encryption
bool is_driver_set;
// initialize with default values
sqlsrv_conn( _In_ SQLHANDLE h, _In_ error_callback e, _In_opt_ void* drv, _In_ SQLSRV_ENCODING encoding TSRMLS_DC ) :
sqlsrv_context( h, SQL_HANDLE_DBC, e, drv, encoding )
{
server_version = SERVER_VERSION_UNKNOWN;
driver_version = ODBC_DRIVER_13;
is_driver_set = false;
}
// sqlsrv_conn has no destructor since its allocated using placement new, which requires that the destructor be
@ -1104,6 +1108,7 @@ const char ApplicationIntent[] = "ApplicationIntent";
const char AttachDBFileName[] = "AttachDbFileName";
const char Authentication[] = "Authentication";
const char ColumnEncryption[] = "ColumnEncryption";
const char Driver[] = "Driver";
const char CEKeystoreProvider[] = "CEKeystoreProvider";
const char CEKeystoreName[] = "CEKeystoreName";
const char CEKeystoreEncryptKey[] = "CEKeystoreEncryptKey";
@ -1154,6 +1159,7 @@ enum SQLSRV_CONN_OPTIONS {
SQLSRV_CONN_OPTION_MULTI_SUBNET_FAILOVER,
SQLSRV_CONN_OPTION_AUTHENTICATION,
SQLSRV_CONN_OPTION_COLUMNENCRYPTION,
SQLSRV_CONN_OPTION_DRIVER,
SQLSRV_CONN_OPTION_CEKEYSTORE_PROVIDER,
SQLSRV_CONN_OPTION_CEKEYSTORE_NAME,
SQLSRV_CONN_OPTION_CEKEYSTORE_ENCRYPT_KEY,
@ -1197,18 +1203,30 @@ struct connection_option {
void (*func)( connection_option const*, zval* value, sqlsrv_conn* conn, std::string& conn_str TSRMLS_DC );
};
// connection attribute functions
// simply add the parsed value to the connection string
struct conn_str_append_func {
static void func( _In_ connection_option const* option, _In_ zval* value, sqlsrv_conn* /*conn*/, _Inout_ std::string& conn_str TSRMLS_DC );
};
struct conn_null_func {
static void func( connection_option const* /*option*/, zval* /*value*/, sqlsrv_conn* /*conn*/, std::string& /*conn_str*/
TSRMLS_DC );
static void func( connection_option const* /*option*/, zval* /*value*/, sqlsrv_conn* /*conn*/, std::string& /*conn_str*/ TSRMLS_DC );
};
struct column_encryption_set_func {
static void func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str TSRMLS_DC );
};
struct driver_set_func {
static void func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str TSRMLS_DC );
};
struct ce_ksp_provider_set_func {
static void func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str TSRMLS_DC );
};
// factory to create a connection (since they are subclassed to instantiate statements)
typedef sqlsrv_conn* (*driver_conn_factory)( _In_ SQLHANDLE h, _In_ error_callback e, _In_ void* drv TSRMLS_DC );
@ -1217,6 +1235,7 @@ sqlsrv_conn* core_sqlsrv_connect( _In_ sqlsrv_context& henv_cp, _In_ sqlsrv_cont
_Inout_z_ const char* server, _Inout_opt_z_ const char* uid, _Inout_opt_z_ const char* pwd,
_Inout_opt_ HashTable* options_ht, _In_ error_callback err, _In_ const connection_option valid_conn_opts[],
_In_ void* driver, _In_z_ const char* driver_func TSRMLS_DC );
SQLRETURN core_odbc_connect( _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str, _Inout_ bool& is_missing_driver, _In_ bool is_pooled );
void core_sqlsrv_close( _Inout_opt_ sqlsrv_conn* conn TSRMLS_DC );
void core_sqlsrv_prepare( _Inout_ sqlsrv_stmt* stmt, _In_reads_bytes_(sql_len) const char* sql, _In_ SQLLEN sql_len TSRMLS_DC );
void core_sqlsrv_begin_transaction( _Inout_ sqlsrv_conn* conn TSRMLS_DC );
@ -1620,6 +1639,8 @@ enum SQLSRV_ERROR_CODES {
SQLSRV_ERROR_ODBC,
SQLSRV_ERROR_DRIVER_NOT_INSTALLED,
SQLSRV_ERROR_AE_DRIVER_NOT_INSTALLED,
SQLSRV_ERROR_CONNECT_INVALID_DRIVER,
SQLSRV_ERROR_ZEND_HASH,
SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE,
SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE,
@ -1668,10 +1689,6 @@ enum SQLSRV_ERROR_CODES {
};
// the message returned by ODBC Driver 11 for SQL Server
static const char* CONNECTION_BUSY_ODBC_ERROR[] = { "[Microsoft][ODBC Driver 13 for SQL Server]Connection is busy with results for another command",
"[Microsoft][ODBC Driver 11 for SQL Server]Connection is busy with results for another command" };
// SQLSTATE for all internal errors
extern SQLCHAR IMSSP[];
@ -1847,8 +1864,12 @@ namespace core {
throw CoreException();
}
std::size_t driver_version = stmt->conn->driver_version;
if( !strcmp( reinterpret_cast<const char*>( err_msg ), CONNECTION_BUSY_ODBC_ERROR[driver_version] )) {
// the message returned by ODBC Driver for SQL Server
const std::string connection_busy_error( ODBC_CONNECTION_BUSY_ERROR );
const std::string returned_error( reinterpret_cast<char*>( err_msg ));
if(( returned_error.find( connection_busy_error ) != std::string::npos )) {
THROW_CORE_ERROR( stmt, SQLSRV_ERROR_MARS_OFF );
}
@ -2418,73 +2439,18 @@ sqlsrv_conn* allocate_conn( _In_ SQLHANDLE h, _In_ error_callback e, _In_ void*
} // namespace core
// connection attribute functions
template <unsigned int Attr>
struct str_conn_attr_func {
static void func( connection_option const* /*option*/, zval* value, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC )
{
try {
core::SQLSetConnectAttr( conn, Attr, reinterpret_cast<SQLPOINTER>( Z_STRVAL_P( value )),
static_cast<SQLINTEGER>(Z_STRLEN_P( value )) TSRMLS_CC );
core::SQLSetConnectAttr( conn, Attr, reinterpret_cast<SQLPOINTER>( Z_STRVAL_P( value )), static_cast<SQLINTEGER>( Z_STRLEN_P( value )) TSRMLS_CC );
}
catch( core::CoreException& ) {
catch ( core::CoreException& ) {
throw;
}
}
};
struct column_encryption_set_func {
static void func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str TSRMLS_DC)
{
convert_to_string(value);
const char* value_str = Z_STRVAL_P(value);
// Column Encryption is disabled by default unless it is explicitly 'Enabled'
conn->ce_option.enabled = false;
if (!stricmp(value_str, "enabled")) {
conn->ce_option.enabled = true;
}
conn_str += option->odbc_name;
conn_str += "=";
conn_str += value_str;
conn_str += ";";
}
};
struct ce_ksp_provider_set_func {
static void func( _In_ connection_option const* option, _In_ zval* value, _Inout_ sqlsrv_conn* conn, _Inout_ std::string& conn_str TSRMLS_DC)
{
SQLSRV_ASSERT( Z_TYPE_P( value ) == IS_STRING, "Wrong zval type for this keyword" )
size_t value_len = Z_STRLEN_P( value );
CHECK_CUSTOM_ERROR( value_len == 0, conn, SQLSRV_ERROR_KEYSTORE_INVALID_VALUE ) {
throw core::CoreException();
}
switch ( option->conn_option_key ) {
case SQLSRV_CONN_OPTION_CEKEYSTORE_PROVIDER:
conn->ce_option.ksp_path = value;
conn->ce_option.ksp_required = true;
break;
case SQLSRV_CONN_OPTION_CEKEYSTORE_NAME:
conn->ce_option.ksp_name = value;
conn->ce_option.ksp_required = true;
break;
case SQLSRV_CONN_OPTION_CEKEYSTORE_ENCRYPT_KEY:
conn->ce_option.ksp_encrypt_key = value;
conn->ce_option.key_size = value_len;
conn->ce_option.ksp_required = true;
break;
default:
SQLSRV_ASSERT( false, "ce_ksp_provider_set_func: Invalid KSP option!" );
break;
}
}
};
#endif // CORE_SQLSRV_H

View file

@ -134,8 +134,7 @@ struct bool_conn_attr_func {
static void func( connection_option const* /*option*/, _In_ zval* value, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC )
{
try {
core::SQLSetConnectAttr(conn, Attr, reinterpret_cast<SQLPOINTER>((zend_long)zend_is_true(value)),
SQL_IS_UINTEGER TSRMLS_CC);
core::SQLSetConnectAttr(conn, Attr, reinterpret_cast<SQLPOINTER>((zend_long)zend_is_true(value)), SQL_IS_UINTEGER TSRMLS_CC);
}
catch( core::CoreException& ) {
@ -189,6 +188,7 @@ const char CharacterSet[] = "CharacterSet";
const char Authentication[] = "Authentication";
const char ConnectionPooling[] = "ConnectionPooling";
const char ColumnEncryption[] = "ColumnEncryption";
const char Driver[] = "Driver";
const char CEKeystoreProvider[] = "CEKeystoreProvider";
const char CEKeystoreName[] = "CEKeystoreName";
const char CEKeystoreEncryptKey[] = "CEKeystoreEncryptKey";
@ -316,6 +316,15 @@ const connection_option SS_CONN_OPTS[] = {
CONN_ATTR_STRING,
column_encryption_set_func::func
},
{
SSConnOptionNames::Driver,
sizeof(SSConnOptionNames::Driver),
SQLSRV_CONN_OPTION_DRIVER,
ODBCConnOptions::Driver,
sizeof(ODBCConnOptions::Driver),
CONN_ATTR_STRING,
driver_set_func::func
},
{
SSConnOptionNames::CEKeystoreProvider,
sizeof(SSConnOptionNames::CEKeystoreProvider),

View file

@ -300,8 +300,8 @@ ss_error SS_ERRORS[] = {
{
SQLSRV_ERROR_DRIVER_NOT_INSTALLED,
{ IMSSP, (SQLCHAR*) "This extension requires the Microsoft ODBC Driver 11 or 13 for SQL Server. "
"Access the following URL to download the ODBC Driver 11 or 13 for SQL Server for %1!s!: "
{ IMSSP, (SQLCHAR*) "This extension requires the Microsoft ODBC Driver for SQL Server. "
"Access the following URL to download the ODBC Driver for SQL Server for %1!s!: "
"http://go.microsoft.com/fwlink/?LinkId=163712", -49, true }
},
@ -376,8 +376,7 @@ ss_error SS_ERRORS[] = {
SS_SQLSRV_WARNING_FIELD_NAME_EMPTY,
{ SSPWARN, (SQLCHAR*)"An empty field name was skipped by sqlsrv_fetch_object.", -100, false }
},
{
{
SQLSRV_ERROR_KEYSTORE_NAME_MISSING,
{ IMSSP, (SQLCHAR*) "The name of the custom keystore provider is missing.", -101, false}
},
@ -393,7 +392,14 @@ ss_error SS_ERRORS[] = {
SQLSRV_ERROR_KEYSTORE_INVALID_VALUE,
{ IMSSP, (SQLCHAR*) "Invalid value for loading a custom keystore provider.", -104, false}
},
{
SQLSRV_ERROR_AE_DRIVER_NOT_INSTALLED,
{ IMSSP, (SQLCHAR*) "The Always Encrypted feature requires Microsoft ODBC Driver 17 for SQL Server.", -105, false }
},
{
SQLSRV_ERROR_CONNECT_INVALID_DRIVER,
{ IMSSP, (SQLCHAR*) "Invalid value %1!s! was specified for Driver option.", -106, true }
},
// terminate the list of errors/warnings
{ UINT_MAX, {} }
};

View file

@ -4,66 +4,105 @@ Test new connection keyword ColumnEncryption
<?php require('skipif.inc'); ?>
--FILE--
<?php
require_once("MsSetup.inc");
$connectionInfo = "Database = $databaseName; ColumnEncryption = Enabled;";
try
require_once("MsSetup.inc");
$msodbcsql_maj = "";
try
{
$conn = new PDO( "sqlsrv:server = $server", $uid, $pwd );
$msodbcsql_ver = $conn->getAttribute( PDO::ATTR_CLIENT_VERSION )['DriverVer'];
$msodbcsql_maj = explode(".", $msodbcsql_ver)[0];
}
catch( PDOException $e )
{
echo "Failed to connect\n";
print_r( $e->getMessage() );
echo "\n";
}
test_ColumnEncryption( $server, $uid, $pwd, $msodbcsql_maj );
echo "Done";
function verify_output( $PDOerror, $expected )
{
if( strpos( $PDOerror->getMessage(), $expected ) === false )
{
$conn = new PDO( "sqlsrv:server = $server ; $connectionInfo", $uid, $pwd );
echo "Connected successfully with ColumnEncryption enabled.\n";
}
catch( PDOException $e )
{
echo "Failed to connect with ColumnEncryption enabled.\n";
print_r( $e->getMessage() );
print_r( $PDOerror->getMessage() );
echo "\n";
}
$conn = null;
}
function test_ColumnEncryption( $server, $uid, $pwd, $msodbcsql_maj )
{
// Only works for ODBC 17
////////////////////////////////////////
$connectionInfo = "Database = $databaseName; ColumnEncryption = false;";
$connectionInfo = "ColumnEncryption = Enabled;";
try
{
$conn = new PDO( "sqlsrv:server = $server ; $connectionInfo", $uid, $pwd );
}
catch( PDOException $e )
{
echo "Failed to connect.\n";
print_r( $e->getMessage() );
echo "\n";
if($msodbcsql_maj < 17)
{
$expected = "The Always Encrypted feature requires Microsoft ODBC Driver 17 for SQL Server.";
verify_output( $e, $expected );
}
else
{
print_r( $e->getMessage() );
echo "\n";
}
}
// Works for ODBC 17, ODBC 13
////////////////////////////////////////
$connectionInfo = "Database = $databaseName; ColumnEncryption = 1;";
$connectionInfo = "ColumnEncryption = Disabled;";
try
{
$conn = new PDO( "sqlsrv:server = $server ; $connectionInfo", $uid, $pwd );
}
catch( PDOException $e )
{
echo "Failed to connect.\n";
print_r( $e->getMessage() );
echo "\n";
if($msodbcsql_maj < 13)
{
$expected = "Invalid connection string attribute";
verify_output( $e, $expected );
}
else
{
print_r( $e->getMessage() );
echo "\n";
}
}
// should fail for all ODBC drivers
////////////////////////////////////////
$connectionInfo = "Database = $databaseName; ColumnEncryption = Disabled;";
$connectionInfo = "ColumnEncryption = false;";
try
{
$conn = new PDO( "sqlsrv:server = $server ; $connectionInfo", $uid, $pwd );
echo "Connected successfully with ColumnEncryption disabled.\n";
}
catch( PDOException $e )
{
echo "Failed to connect with ColumnEncryption disabled.\n";
print_r( $e->getMessage() );
echo "\n";
$expected = "Invalid value specified for connection string attribute 'ColumnEncryption'";
verify_output( $e, $expected );
}
$conn = null;
echo "Done\n";
// should fail for all ODBC drivers
////////////////////////////////////////
$connectionInfo = "ColumnEncryption = 1;";
try
{
$conn = new PDO( "sqlsrv:server = $server ; $connectionInfo", $uid, $pwd );
}
catch( PDOException $e )
{
$expected = "Invalid value specified for connection string attribute 'ColumnEncryption'";
verify_output( $e, $expected );
}
}
?>
--EXPECTREGEX--
Connected successfully with ColumnEncryption enabled\.
Failed to connect\.
SQLSTATE\[08001\]: .*\[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Invalid value specified for connection string attribute 'ColumnEncryption'
Failed to connect\.
SQLSTATE\[08001\]: .*\[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Invalid value specified for connection string attribute 'ColumnEncryption'
Connected successfully with ColumnEncryption disabled\.
--EXPECT--
Done

View file

@ -0,0 +1,111 @@
--TEST--
Test new connection keyword Driver with valid and invalid values
--SKIPIF--
<?php require('skipif.inc'); ?>
--FILE--
<?php
sqlsrv_configure( 'WarningsReturnAsErrors', 0 );
require( 'MsSetup.inc' );
$connectionOptions = array("Database"=>$database,"UID"=>$userName, "PWD"=>$userPassword);
$conn = sqlsrv_connect($server, $connectionOptions);
if ($conn === false)
{
print_r(sqlsrv_errors());
}
$msodbcsql_ver = sqlsrv_client_info($conn)['DriverVer'];
$msodbcsql_maj = explode(".", $msodbcsql_ver)[0];
sqlsrv_close($conn);
// start test
test_valid_values($msodbcsql_maj,$server ,$connectionOptions);
test_invalid_values($msodbcsql_maj,$server ,$connectionOptions);
echo "Done";
// end test
///////////////////////////
function connect_verify_output( $msodbcsql_maj ,$server ,$connectionOptions ,$expected = '' )
{
$conn = sqlsrv_connect($server, $connectionOptions);
if ($conn === false)
{
if( strpos(sqlsrv_errors($conn)[0]['message'], $expected) === false )
{
print_r(sqlsrv_errors());
}
}
}
function test_valid_values( $msodbcsql_maj ,$server ,$connectionOptions){
$value = "";
// Test with {}
switch ( $msodbcsql_maj )
{
case 17:
$value = "{ODBC Driver 17 for SQL Server}";
break;
case 13:
$value = "{ODBC Driver 13 for SQL Server}";
break;
case 12:
case 11:
$value = "{ODBC Driver 11 for SQL Server}";
break;
default:
$value = "invalid value";
}
$connectionOptions['Driver']=$value;
connect_verify_output( $msodbcsql_maj ,$server ,$connectionOptions );
// Test without {}
switch ( $msodbcsql_maj )
{
case 17:
$value = "ODBC Driver 17 for SQL Server";
break;
case 13:
$value = "ODBC Driver 13 for SQL Server";
break;
case 12:
case 11:
$value = "ODBC Driver 11 for SQL Server";
break;
default:
$value = "invalid value";
}
$connectionOptions['Driver']=$value;
connect_verify_output( $msodbcsql_maj ,$server ,$connectionOptions );
}
function test_invalid_values($msodbcsql_maj ,$server ,$connectionOptions){
// test invalid value
$value = "{SQL Server Native Client 11.0}";
$connectionOptions['Driver']=$value;
$expected = "Invalid value $value was specified for Driver option.";
connect_verify_output( $msodbcsql_maj ,$server ,$connectionOptions ,$expected );
$value = "SQL Server Native Client 11.0";
$connectionOptions['Driver']=$value;
$expected = "Invalid value $value was specified for Driver option.";
connect_verify_output( $msodbcsql_maj ,$server ,$connectionOptions ,$expected );
$value = "ODBC Driver 00 for SQL Server";
$connectionOptions['Driver']=$value;
$expected = "Invalid value $value was specified for Driver option.";
connect_verify_output( $msodbcsql_maj ,$server ,$connectionOptions ,$expected );
$value = 123;
$connectionOptions['Driver']=$value;
$expected = "Invalid value type for option Driver was specified. String type was expected.";
connect_verify_output( $msodbcsql_maj ,$server ,$connectionOptions ,$expected );
$value = false;
$connectionOptions['Driver']=$value;
$expected = "Invalid value type for option Driver was specified. String type was expected.";
connect_verify_output( $msodbcsql_maj ,$server ,$connectionOptions ,$expected );
}
?>
--EXPECT--
Done

View file

@ -4,84 +4,99 @@ Test new connection keyword ColumnEncryption
<?php require('skipif.inc'); ?>
--FILE--
<?php
sqlsrv_configure( 'WarningsReturnAsErrors', 1 );
sqlsrv_configure( 'LogSeverity', SQLSRV_LOG_SEVERITY_ALL );
require( 'MsSetup.inc' );
$connectionInfo = array( "Database"=>$databaseName, "UID"=>$uid, "PWD"=>$pwd,
"ColumnEncryption"=>'Enabled');
$conn = sqlsrv_connect( $server, $connectionInfo );
sqlsrv_configure( 'WarningsReturnAsErrors', 0 );
require( 'MsSetup.inc' );
$connectionOptions = array("Database"=>$database,"UID"=>$userName, "PWD"=>$userPassword);
test_ColumnEncryption($server, $connectionOptions);
echo "Done";
function test_ColumnEncryption($server ,$connectionOptions){
$conn = sqlsrv_connect($server, $connectionOptions);
if ($conn === false)
{
print_r(sqlsrv_errors());
}
$msodbcsql_ver = sqlsrv_client_info($conn)['DriverVer'];
$msodbcsql_maj = explode(".", $msodbcsql_ver)[0];
// Only works for ODBC 17
$connectionOptions['ColumnEncryption']='Enabled';
$conn = sqlsrv_connect( $server, $connectionOptions );
if( $conn === false )
{
echo "Failed to connect.\n";
print_r( sqlsrv_errors() );
if($msodbcsql_maj < 17){
$expected = "The Always Encrypted feature requires Microsoft ODBC Driver 17 for SQL Server.";
if( strcasecmp(sqlsrv_errors($conn)[0]['message'], $expected ) != 0 )
{
print_r(sqlsrv_errors());
}
}
else
{
print_r(sqlsrv_errors());
}
}
// Works for ODBC 17, ODBC 13
$connectionOptions['ColumnEncryption']='Disabled';
$conn = sqlsrv_connect( $server, $connectionOptions );
if( $conn === false )
{
if($msodbcsql_maj < 13)
{
$expected_substr = "Invalid connection string attribute";
if( strpos(sqlsrv_errors($conn)[0]['message'], $expected_substr ) === false )
{
print_r(sqlsrv_errors());
}
}
else
{
print_r(sqlsrv_errors());
}
}
else
{
echo "Connected successfully with ColumnEncryption enabled.\n";
sqlsrv_close( $conn );
sqlsrv_close($conn);
}
////////////////////////////////////////
$connectionInfo['ColumnEncryption']='false';
$conn = sqlsrv_connect( $server, $connectionInfo );
// should fail for all ODBC drivers
$connectionOptions['ColumnEncryption']='false';
$conn = sqlsrv_connect( $server, $connectionOptions );
if( $conn === false )
{
echo "Failed to connect.\n";
print_r( sqlsrv_errors() );
$expected_substr = "Invalid value specified for connection string attribute 'ColumnEncryption'";
if( strpos(sqlsrv_errors($conn)[0]['message'], $expected_substr ) === false )
{
print_r(sqlsrv_errors());
}
}
////////////////////////////////////////
$connectionInfo['ColumnEncryption']=true;
$conn = sqlsrv_connect( $server, $connectionInfo );
// should fail for all ODBC drivers
$connectionOptions['ColumnEncryption']=true;
$conn = sqlsrv_connect( $server, $connectionOptions );
if( $conn === false )
{
echo "Failed to connect.\n";
print_r( sqlsrv_errors() );
$expected_substr = "Invalid value type for option ColumnEncryption was specified. String type was expected.";
if( strpos(sqlsrv_errors($conn)[0]['message'], $expected_substr ) === false )
{
print_r(sqlsrv_errors());
}
}
////////////////////////////////////////
$connectionInfo['ColumnEncryption']='Disabled';
$conn = sqlsrv_connect( $server, $connectionInfo );
// should fail for all ODBC drivers
$connectionOptions['ColumnEncryption']=false;
$conn = sqlsrv_connect( $server, $connectionOptions );
if( $conn === false )
{
echo "Failed to connect.\n";
print_r( sqlsrv_errors() );
$expected_substr = "Invalid value type for option ColumnEncryption was specified. String type was expected.";
if( strpos(sqlsrv_errors($conn)[0]['message'], $expected_substr ) === false )
{
print_r(sqlsrv_errors());
}
}
else
{
echo "Connected successfully with ColumnEncryption disabled.\n";
sqlsrv_close( $conn );
}
echo "Done\n";
}
?>
--EXPECTREGEX--
Connected successfully with ColumnEncryption enabled.
Failed to connect.
Array
\(
\[0\] => Array
\(
\[0\] => 08001
\[SQLSTATE\] => 08001
\[1\] => 0
\[code\] => 0
\[2\] => .*\[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Invalid value specified for connection string attribute 'ColumnEncryption'
\[message\] => .*\[Microsoft\]\[ODBC Driver 1[1-9] for SQL Server\]Invalid value specified for connection string attribute 'ColumnEncryption'
\)
\)
Failed to connect.
Array
\(
\[0\] => Array
\(
\[0\] => IMSSP
\[SQLSTATE\] => IMSSP
\[1\] => -33
\[code\] => -33
\[2\] => Invalid value type for option ColumnEncryption was specified. String type was expected.
\[message\] => Invalid value type for option ColumnEncryption was specified. String type was expected.
\)
\)
Connected successfully with ColumnEncryption disabled.
--EXPECT--
Done

View file

@ -187,13 +187,13 @@ array\(1\) \{
\["SQLSTATE"\]=>
string\(5\) "IMSSP"
\[1\]=>
int\(-1\)
int\(-106\)
\["code"\]=>
int\(-1\)
int\(-106\)
\[2\]=>
string\([0-9]+\) "Invalid option .* was passed to sqlsrv_connect."
string\([0-9]+\) "Invalid value SQL Server Native Client 11.0 was specified for Driver option."
\["message"\]=>
string\([0-9]+\) "Invalid option .* was passed to sqlsrv_connect."
string\([0-9]+\) "Invalid value SQL Server Native Client 11.0 was specified for Driver option."
\}
\}
Test sqlsrv_connect with driver injection