775 lines
29 KiB
C++
775 lines
29 KiB
C++
//---------------------------------------------------------------------------------------------------------------------------------
|
|
// File: core_conn.cpp
|
|
//
|
|
// Contents: Core routines that use connection handles shared between sqlsrv and pdo_sqlsrv
|
|
//
|
|
// Microsoft Drivers 4.0 for PHP for SQL Server
|
|
// Copyright(c) Microsoft Corporation
|
|
// All rights reserved.
|
|
// MIT License
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""),
|
|
// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions :
|
|
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
// IN THE SOFTWARE.
|
|
//---------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
#include "core_sqlsrv.h"
|
|
|
|
#include <php.h>
|
|
#include <psapi.h>
|
|
#include <windows.h>
|
|
#include <winver.h>
|
|
|
|
#include <string>
|
|
#include <sstream>
|
|
|
|
// *** internal variables and constants ***
|
|
|
|
namespace {
|
|
|
|
// *** internal constants ***
|
|
// an arbitrary figure that should be large enough for most connection strings.
|
|
const int DEFAULT_CONN_STR_LEN = 2048;
|
|
|
|
// length of buffer used to retrieve information for client and server info buffers
|
|
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};"};
|
|
|
|
// default options if only the server is specified
|
|
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};";
|
|
|
|
// connection option appended for MARS when MARS isn't explicitly mentioned
|
|
const char CONNECTION_OPTION_MARS_ON[] = "MARS_Connection={Yes};";
|
|
|
|
// *** internal function prototypes ***
|
|
|
|
void build_connection_string_and_set_conn_attr( sqlsrv_conn* conn, const char* server, const char* uid, const char* pwd,
|
|
HashTable* options_ht, const connection_option valid_conn_opts[],
|
|
void* driver,__inout std::string& connection_string TSRMLS_DC );
|
|
void determine_server_version( sqlsrv_conn* conn TSRMLS_DC );
|
|
const char* get_processor_arch( void );
|
|
void get_server_version( sqlsrv_conn* conn, char** server_version, SQLSMALLINT& len TSRMLS_DC );
|
|
connection_option const* get_connection_option( sqlsrv_conn* conn, const char* key, SQLULEN key_len TSRMLS_DC );
|
|
void common_conn_str_append_func( const char* odbc_name, const char* val, size_t val_len, std::string& conn_str TSRMLS_DC );
|
|
|
|
}
|
|
|
|
// core_sqlsrv_connect
|
|
// opens a connection and returns a sqlsrv_conn structure.
|
|
// Parameters:
|
|
// henv_cp - connection pooled env context
|
|
// henv_ncp - non connection pooled env context
|
|
// server - name of the server we're connecting to
|
|
// uid - username
|
|
// pwd - password
|
|
// options_ht - zend_hash list of options
|
|
// err - error callback to put into the connection's context
|
|
// valid_conn_opts[] - array of valid driver supported connection options.
|
|
// driver - reference to caller
|
|
// Return
|
|
// A sqlsrv_conn structure. An exception is thrown if an error occurs
|
|
|
|
sqlsrv_conn* core_sqlsrv_connect( sqlsrv_context& henv_cp, sqlsrv_context& henv_ncp, driver_conn_factory conn_factory,
|
|
const char* server, const char* uid, const char* pwd,
|
|
HashTable* options_ht, error_callback err, const connection_option valid_conn_opts[],
|
|
void* driver, const char* driver_func TSRMLS_DC )
|
|
|
|
{
|
|
SQLRETURN r;
|
|
std::string conn_str;
|
|
conn_str.reserve( DEFAULT_CONN_STR_LEN );
|
|
sqlsrv_malloc_auto_ptr<sqlsrv_conn> conn;
|
|
sqlsrv_malloc_auto_ptr<wchar_t> wconn_string;
|
|
unsigned int wconn_len = 0;
|
|
|
|
try {
|
|
|
|
sqlsrv_context* henv = &henv_cp; // by default use the connection pooling henv
|
|
|
|
// check the connection pooling setting to determine which henv to use to allocate the connection handle
|
|
// we do this earlier because we have to allocate the connection handle prior to setting attributes on
|
|
// it in build_connection_string_and_set_conn_attr.
|
|
|
|
if( options_ht && zend_hash_num_elements( options_ht ) > 0 ) {
|
|
|
|
zval* option_z = NULL;
|
|
int zr = SUCCESS;
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
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 );
|
|
|
|
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 );
|
|
|
|
// 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( wchar_t );
|
|
|
|
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 == NULL, conn, SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE, get_last_error_message())
|
|
{
|
|
throw core::CoreException();
|
|
}
|
|
|
|
SQLSMALLINT output_conn_size;
|
|
r = SQLDriverConnectW( conn->handle(), NULL, reinterpret_cast<SQLWCHAR*>( wconn_string.get()),
|
|
static_cast<SQLSMALLINT>( wconn_len ), NULL,
|
|
0, &output_conn_size, SQL_DRIVER_NOPROMPT );
|
|
// 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( wchar_t )); // wconn_len is the number of characters, not bytes
|
|
conn_str.clear();
|
|
|
|
if( !SQL_SUCCEEDED( r )) {
|
|
SQLCHAR state[SQL_SQLSTATE_BUFSIZE];
|
|
SQLSMALLINT len;
|
|
SQLRETURN r = SQLGetDiagField( SQL_HANDLE_DBC, conn->handle(), 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len );
|
|
bool missing_driver_error = ( SQL_SUCCEEDED( r ) && 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 );
|
|
break;
|
|
}
|
|
|
|
}
|
|
CHECK_SQL_ERROR( r, conn ) {
|
|
throw core::CoreException();
|
|
}
|
|
|
|
CHECK_SQL_WARNING_AS_ERROR( r, conn ) {
|
|
throw core::CoreException();
|
|
}
|
|
|
|
// determine the version of the server we're connected to. The server version is left in the
|
|
// connection upon return.
|
|
determine_server_version( conn TSRMLS_CC );
|
|
|
|
}
|
|
catch( std::bad_alloc& ) {
|
|
memset( const_cast<char*>( conn_str.c_str()), 0, conn_str.size() );
|
|
memset( wconn_string, 0, wconn_len * sizeof( wchar_t )); // wconn_len is the number of characters, not bytes
|
|
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( wchar_t )); // wconn_len is the number of characters, not bytes
|
|
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( wchar_t )); // wconn_len is the number of characters, not bytes
|
|
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( wchar_t )); // wconn_len is the number of characters, not bytes
|
|
conn->invalidate();
|
|
throw;
|
|
}
|
|
|
|
sqlsrv_conn* return_conn = conn;
|
|
conn.transferred();
|
|
|
|
return return_conn;
|
|
}
|
|
|
|
|
|
|
|
// core_sqlsrv_begin_transaction
|
|
// Begins a transaction on a specified connection. The current transaction
|
|
// includes all statements on the specified connection that were executed after
|
|
// the call to core_sqlsrv_begin_transaction and before any calls to
|
|
// core_sqlsrv_rollback or core_sqlsrv_commit.
|
|
// The default transaction mode is auto-commit. This means that all queries
|
|
// are automatically committed upon success unless they have been designated
|
|
// as part of an explicit transaction by using core_sqlsrv_begin_transaction.
|
|
// Parameters:
|
|
// sqlsrv_conn*: The connection with which the transaction is associated.
|
|
|
|
void core_sqlsrv_begin_transaction( sqlsrv_conn* conn TSRMLS_DC )
|
|
{
|
|
try {
|
|
|
|
DEBUG_SQLSRV_ASSERT( conn != NULL, "core_sqlsrv_begin_transaction: connection object was null." );
|
|
|
|
core::SQLSetConnectAttr( conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast<SQLPOINTER>( SQL_AUTOCOMMIT_OFF ),
|
|
SQL_IS_UINTEGER TSRMLS_CC );
|
|
}
|
|
catch ( core::CoreException& ) {
|
|
throw;
|
|
}
|
|
}
|
|
|
|
// core_sqlsrv_commit
|
|
// Commits the current transaction on the specified connection and returns the
|
|
// connection to the auto-commit mode. The current transaction includes all
|
|
// statements on the specified connection that were executed after the call to
|
|
// core_sqlsrv_begin_transaction and before any calls to core_sqlsrv_rollback or
|
|
// core_sqlsrv_commit.
|
|
// Parameters:
|
|
// sqlsrv_conn*: The connection on which the transaction is active.
|
|
|
|
void core_sqlsrv_commit( sqlsrv_conn* conn TSRMLS_DC )
|
|
{
|
|
try {
|
|
|
|
DEBUG_SQLSRV_ASSERT( conn != NULL, "core_sqlsrv_commit: connection object was null." );
|
|
|
|
core::SQLEndTran( SQL_HANDLE_DBC, conn, SQL_COMMIT TSRMLS_CC );
|
|
|
|
core::SQLSetConnectAttr( conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast<SQLPOINTER>( SQL_AUTOCOMMIT_ON ),
|
|
SQL_IS_UINTEGER TSRMLS_CC );
|
|
}
|
|
catch ( core::CoreException& ) {
|
|
throw;
|
|
}
|
|
}
|
|
|
|
// core_sqlsrv_rollback
|
|
// Rolls back the current transaction on the specified connection and returns
|
|
// the connection to the auto-commit mode. The current transaction includes all
|
|
// statements on the specified connection that were executed after the call to
|
|
// core_sqlsrv_begin_transaction and before any calls to core_sqlsrv_rollback or
|
|
// core_sqlsrv_commit.
|
|
// Parameters:
|
|
// sqlsrv_conn*: The connection on which the transaction is active.
|
|
|
|
void core_sqlsrv_rollback( sqlsrv_conn* conn TSRMLS_DC )
|
|
{
|
|
try {
|
|
|
|
DEBUG_SQLSRV_ASSERT( conn != NULL, "core_sqlsrv_rollback: connection object was null." );
|
|
|
|
core::SQLEndTran( SQL_HANDLE_DBC, conn, SQL_ROLLBACK TSRMLS_CC );
|
|
|
|
core::SQLSetConnectAttr( conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast<SQLPOINTER>( SQL_AUTOCOMMIT_ON ),
|
|
SQL_IS_UINTEGER TSRMLS_CC );
|
|
|
|
}
|
|
catch ( core::CoreException& ) {
|
|
throw;
|
|
}
|
|
}
|
|
|
|
// core_sqlsrv_close
|
|
// Called when a connection resource is destroyed by the Zend engine.
|
|
// Parameters:
|
|
// conn - The current active connection.
|
|
void core_sqlsrv_close( sqlsrv_conn* conn TSRMLS_DC )
|
|
{
|
|
// if the connection wasn't successful, just return.
|
|
if( conn == NULL )
|
|
return;
|
|
|
|
try {
|
|
|
|
// rollback any transaction in progress (we don't care about the return result)
|
|
core::SQLEndTran( SQL_HANDLE_DBC, conn, SQL_ROLLBACK TSRMLS_CC );
|
|
}
|
|
catch( core::CoreException& ) {
|
|
LOG( SEV_ERROR, "Transaction rollback failed when closing the connection." );
|
|
}
|
|
|
|
// disconnect from the server
|
|
SQLRETURN r = SQLDisconnect( conn->handle() );
|
|
if( !SQL_SUCCEEDED( r )) {
|
|
LOG( SEV_ERROR, "Disconnect failed when closing the connection." );
|
|
}
|
|
|
|
// free the connection handle
|
|
conn->invalidate();
|
|
|
|
sqlsrv_free( conn );
|
|
}
|
|
|
|
// core_sqlsrv_prepare
|
|
// Create a statement object and prepare the SQL query passed in for execution at a later time.
|
|
// Parameters:
|
|
// stmt - statement to be prepared
|
|
// sql - T-SQL command to prepare
|
|
// sql_len - length of the T-SQL string
|
|
|
|
void core_sqlsrv_prepare( sqlsrv_stmt* stmt, const char* sql, SQLLEN sql_len TSRMLS_DC )
|
|
{
|
|
try {
|
|
|
|
// convert the string from its encoding to UTf-16
|
|
// if the string is empty, we initialize the fields and skip since an empty string is a
|
|
// failure case for utf16_string_from_mbcs_string
|
|
sqlsrv_malloc_auto_ptr<wchar_t> wsql_string;
|
|
unsigned int wsql_len = 0;
|
|
if( sql_len == 0 || ( sql[0] == '\0' && sql_len == 1 )) {
|
|
wsql_string = reinterpret_cast<wchar_t*>( sqlsrv_malloc( sizeof( wchar_t )));
|
|
wsql_string[0] = L'\0';
|
|
wsql_len = 0;
|
|
}
|
|
else {
|
|
|
|
if (sql_len > INT_MAX)
|
|
{
|
|
LOG(SEV_ERROR, "Convert input parameter to utf16: buffer length exceeded.");
|
|
throw core::CoreException();
|
|
}
|
|
|
|
SQLSRV_ENCODING encoding = (( stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) ? stmt->conn->encoding() :
|
|
stmt->encoding() );
|
|
wsql_string = utf16_string_from_mbcs_string( encoding, reinterpret_cast<const char*>( sql ),
|
|
static_cast<int>( sql_len ), &wsql_len );
|
|
CHECK_CUSTOM_ERROR( wsql_string == NULL, stmt, SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE,
|
|
get_last_error_message() ) {
|
|
throw core::CoreException();
|
|
}
|
|
}
|
|
|
|
// prepare our wide char query string
|
|
core::SQLPrepareW( stmt, reinterpret_cast<SQLWCHAR*>( wsql_string.get() ), wsql_len TSRMLS_CC );
|
|
}
|
|
catch( core::CoreException& ) {
|
|
|
|
throw;
|
|
}
|
|
}
|
|
|
|
// core_sqlsrv_get_server_version
|
|
// Determines the vesrion of the SQL Server we are connected to. Calls a helper function
|
|
// get_server_version to get the version of SQL Server.
|
|
// Parameters:
|
|
// conn - The connection resource by which the client and server are connected.
|
|
// *server_version - zval for returning results.
|
|
|
|
void core_sqlsrv_get_server_version( sqlsrv_conn* conn, __out zval *server_version TSRMLS_DC )
|
|
{
|
|
try {
|
|
|
|
sqlsrv_malloc_auto_ptr<char> buffer;
|
|
SQLSMALLINT buffer_len = 0;
|
|
|
|
get_server_version( conn, &buffer, buffer_len TSRMLS_CC );
|
|
core::sqlsrv_zval_stringl( server_version, buffer, buffer_len );
|
|
if (NULL != buffer) {
|
|
sqlsrv_free( buffer );
|
|
}
|
|
buffer.transferred();
|
|
}
|
|
|
|
catch( core::CoreException& ) {
|
|
throw;
|
|
}
|
|
}
|
|
|
|
// core_sqlsrv_get_server_info
|
|
// Returns the Database name, the name of the SQL Server we are connected to
|
|
// and the version of the SQL Server.
|
|
// Parameters:
|
|
// conn - The connection resource by which the client and server are connected.
|
|
// *server_info - zval for returning results.
|
|
|
|
void core_sqlsrv_get_server_info( sqlsrv_conn* conn, __out zval *server_info TSRMLS_DC )
|
|
{
|
|
try {
|
|
|
|
sqlsrv_malloc_auto_ptr<char> buffer;
|
|
SQLSMALLINT buffer_len = 0;
|
|
|
|
// initialize the array
|
|
core::sqlsrv_array_init( *conn, server_info TSRMLS_CC );
|
|
|
|
// Get the database name
|
|
buffer = static_cast<char*>( sqlsrv_malloc( INFO_BUFFER_LEN ));
|
|
core::SQLGetInfo( conn, SQL_DATABASE_NAME, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC );
|
|
core::sqlsrv_add_assoc_string( *conn, server_info, "CurrentDatabase", buffer, 0 /*duplicate*/ TSRMLS_CC );
|
|
buffer.transferred();
|
|
|
|
// Get the server version
|
|
get_server_version( conn, &buffer, buffer_len TSRMLS_CC );
|
|
core::sqlsrv_add_assoc_string( *conn, server_info, "SQLServerVersion", buffer, 0 /*duplicate*/ TSRMLS_CC );
|
|
buffer.transferred();
|
|
|
|
// Get the server name
|
|
buffer = static_cast<char*>( sqlsrv_malloc( INFO_BUFFER_LEN ));
|
|
core::SQLGetInfo( conn, SQL_SERVER_NAME, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC );
|
|
core::sqlsrv_add_assoc_string( *conn, server_info, "SQLServerName", buffer, 0 /*duplicate*/ TSRMLS_CC );
|
|
buffer.transferred();
|
|
}
|
|
|
|
catch( core::CoreException& ) {
|
|
throw;
|
|
}
|
|
}
|
|
|
|
// core_sqlsrv_get_client_info
|
|
// Returns the ODBC driver's dll name, version and the ODBC version.
|
|
// Parameters
|
|
// conn - The connection resource by which the client and server are connected.
|
|
// *client_info - zval for returning the results.
|
|
|
|
void core_sqlsrv_get_client_info( sqlsrv_conn* conn, __out zval *client_info TSRMLS_DC )
|
|
{
|
|
try {
|
|
|
|
sqlsrv_malloc_auto_ptr<char> buffer;
|
|
SQLSMALLINT buffer_len = 0;
|
|
|
|
// initialize the array
|
|
core::sqlsrv_array_init( *conn, client_info TSRMLS_CC );
|
|
|
|
// Get the ODBC driver's dll name
|
|
buffer = static_cast<char*>( sqlsrv_malloc( INFO_BUFFER_LEN ));
|
|
core::SQLGetInfo( conn, SQL_DRIVER_NAME, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC );
|
|
core::sqlsrv_add_assoc_string( *conn, client_info, "DriverDllName", buffer, 0 /*duplicate*/ TSRMLS_CC );
|
|
buffer.transferred();
|
|
|
|
// Get the ODBC driver's ODBC version
|
|
buffer = static_cast<char*>( sqlsrv_malloc( INFO_BUFFER_LEN ));
|
|
core::SQLGetInfo( conn, SQL_DRIVER_ODBC_VER, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC );
|
|
core::sqlsrv_add_assoc_string( *conn, client_info, "DriverODBCVer", buffer, 0 /*duplicate*/ TSRMLS_CC );
|
|
buffer.transferred();
|
|
|
|
// Get the OBDC driver's version
|
|
buffer = static_cast<char*>( sqlsrv_malloc( INFO_BUFFER_LEN ));
|
|
core::SQLGetInfo( conn, SQL_DRIVER_VER, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC );
|
|
core::sqlsrv_add_assoc_string( *conn, client_info, "DriverVer", buffer, 0 /*duplicate*/ TSRMLS_CC );
|
|
buffer.transferred();
|
|
|
|
}
|
|
|
|
catch( core::CoreException& ) {
|
|
throw;
|
|
}
|
|
}
|
|
|
|
|
|
// core_is_conn_opt_value_escaped
|
|
// determine if connection string value is properly escaped.
|
|
// Properly escaped means that any '}' should be escaped by a prior '}'. It is assumed that
|
|
// the value will be surrounded by { and } by the caller after it has been validated
|
|
|
|
bool core_is_conn_opt_value_escaped( const char* value, size_t value_len )
|
|
{
|
|
// if the value is already quoted, then only analyse the part inside the quotes and return it as
|
|
// unquoted since we quote it when adding it to the connection string.
|
|
if( value_len > 0 && value[0] == '{' && value[ value_len - 1 ] == '}' ) {
|
|
++value;
|
|
value_len -= 2;
|
|
}
|
|
// check to make sure that all right braces are escaped
|
|
size_t i = 0;
|
|
while( ( value[i] != '}' || ( value[i] == '}' && value[i+1] == '}' )) && i < value_len ) {
|
|
// skip both braces
|
|
if( value[i] == '}' )
|
|
++i;
|
|
++i;
|
|
}
|
|
if( i < value_len && value[i] == '}' ) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// *** internal connection functions and classes ***
|
|
|
|
namespace {
|
|
|
|
connection_option const* get_connection_option( sqlsrv_conn* conn, SQLULEN key,
|
|
const connection_option conn_opts[] TSRMLS_DC )
|
|
{
|
|
for( int opt_idx = 0; conn_opts[ opt_idx ].conn_option_key != SQLSRV_CONN_OPTION_INVALID; ++opt_idx ) {
|
|
|
|
if( key == conn_opts[ opt_idx ].conn_option_key ) {
|
|
|
|
return &conn_opts[ opt_idx ];
|
|
}
|
|
}
|
|
|
|
SQLSRV_ASSERT( false, "Invalid connection option, should have been validated by the driver layer." );
|
|
return NULL; // avoid a compiler warning
|
|
}
|
|
|
|
// says what it does, and does what it says
|
|
// rather than have attributes and connection strings as ODBC does, we unify them into a hash table
|
|
// passed to the connection, and then break them out ourselves and either set attributes or put the
|
|
// option in the connection string.
|
|
|
|
void build_connection_string_and_set_conn_attr( sqlsrv_conn* conn, const char* server, const char* uid, const char* pwd,
|
|
HashTable* options, const connection_option valid_conn_opts[],
|
|
void* driver,__inout std::string& connection_string TSRMLS_DC )
|
|
{
|
|
bool credentials_mentioned = false;
|
|
bool mars_mentioned = false;
|
|
connection_option const* conn_opt;
|
|
int zr = SUCCESS;
|
|
|
|
try {
|
|
|
|
// Add the server name
|
|
common_conn_str_append_func( ODBCConnOptions::SERVER, server, strlen( server ), connection_string TSRMLS_CC );
|
|
|
|
// if uid is not present then we use trusted connection.
|
|
if(uid == NULL || strlen( uid ) == 0 ) {
|
|
|
|
connection_string += "Trusted_Connection={Yes};";
|
|
}
|
|
else {
|
|
|
|
bool escaped = core_is_conn_opt_value_escaped( uid, strlen( uid ));
|
|
CHECK_CUSTOM_ERROR( !escaped, conn, SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED ) {
|
|
throw core::CoreException();
|
|
}
|
|
|
|
common_conn_str_append_func( ODBCConnOptions::UID, uid, strlen( uid ), connection_string TSRMLS_CC );
|
|
|
|
// if no password was given, then don't add a password to the connection string. Perhaps the UID
|
|
// given doesn't have a password?
|
|
if( pwd != NULL ) {
|
|
|
|
escaped = core_is_conn_opt_value_escaped( pwd, strlen( pwd ));
|
|
CHECK_CUSTOM_ERROR( !escaped, conn, SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED ) {
|
|
|
|
throw core::CoreException();
|
|
}
|
|
|
|
common_conn_str_append_func( ODBCConnOptions::PWD, pwd, strlen( pwd ), connection_string TSRMLS_CC );
|
|
}
|
|
}
|
|
|
|
// if no options were given, then we set MARS the defaults and return immediately.
|
|
if( options == NULL || zend_hash_num_elements( options ) == 0 ) {
|
|
connection_string += CONNECTION_STRING_DEFAULT_OPTIONS;
|
|
return;
|
|
}
|
|
|
|
// workaround for a bug in ODBC Driver Manager wherein the Driver Manager creates a 0 KB file
|
|
// if the TraceFile option is set, even if the "TraceOn" is not present or the "TraceOn"
|
|
// flag is set to false.
|
|
if( zend_hash_index_exists( options, SQLSRV_CONN_OPTION_TRACE_FILE )) {
|
|
|
|
zval* trace_value = NULL;
|
|
trace_value = zend_hash_index_find(options, SQLSRV_CONN_OPTION_TRACE_ON);
|
|
|
|
if (trace_value == NULL || !zend_is_true(trace_value)) {
|
|
|
|
zend_hash_index_del( options, SQLSRV_CONN_OPTION_TRACE_FILE );
|
|
}
|
|
}
|
|
|
|
zend_string *key = NULL;
|
|
zend_ulong index = -1;
|
|
zval* data = NULL;
|
|
|
|
ZEND_HASH_FOREACH_KEY_VAL( options, index, key, data ) {
|
|
int type = HASH_KEY_NON_EXISTENT;
|
|
type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG;
|
|
|
|
// The driver layer should ensure a valid key.
|
|
DEBUG_SQLSRV_ASSERT(( type == HASH_KEY_IS_LONG ), "build_connection_string_and_set_conn_attr: invalid connection option key type." );
|
|
|
|
conn_opt = get_connection_option( conn, index, valid_conn_opts TSRMLS_CC );
|
|
|
|
if( index == SQLSRV_CONN_OPTION_MARS ) {
|
|
mars_mentioned = true;
|
|
}
|
|
|
|
conn_opt->func( conn_opt, data, conn, connection_string TSRMLS_CC );
|
|
} ZEND_HASH_FOREACH_END();
|
|
|
|
// MARS on if not explicitly turned off
|
|
if( !mars_mentioned ) {
|
|
connection_string += CONNECTION_OPTION_MARS_ON;
|
|
}
|
|
|
|
}
|
|
catch( core::CoreException& ) {
|
|
throw;
|
|
}
|
|
}
|
|
|
|
|
|
// get_server_version
|
|
// Helper function which returns the version of the SQL Server we are connected to.
|
|
|
|
void get_server_version( sqlsrv_conn* conn, char** server_version, SQLSMALLINT& len TSRMLS_DC )
|
|
{
|
|
try {
|
|
|
|
sqlsrv_malloc_auto_ptr<char> buffer;
|
|
SQLSMALLINT buffer_len = 0;
|
|
|
|
buffer = static_cast<char*>( sqlsrv_malloc( INFO_BUFFER_LEN ));
|
|
core::SQLGetInfo( conn, SQL_DBMS_VER, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC );
|
|
*server_version = buffer;
|
|
len = buffer_len;
|
|
buffer.transferred();
|
|
}
|
|
|
|
catch( core::CoreException& ) {
|
|
throw;
|
|
}
|
|
}
|
|
|
|
|
|
// get_processor_arch
|
|
// Calls GetSystemInfo to verify the what architecture of the processor is supported
|
|
// and return the string of the processor name.
|
|
const char* get_processor_arch( void )
|
|
{
|
|
SYSTEM_INFO sys_info;
|
|
GetSystemInfo( &sys_info);
|
|
switch( sys_info.wProcessorArchitecture ) {
|
|
|
|
case PROCESSOR_ARCHITECTURE_INTEL:
|
|
return PROCESSOR_ARCH[0];
|
|
|
|
case PROCESSOR_ARCHITECTURE_AMD64:
|
|
return PROCESSOR_ARCH[1];
|
|
|
|
case PROCESSOR_ARCHITECTURE_IA64:
|
|
return PROCESSOR_ARCH[2];
|
|
|
|
default:
|
|
DIE( "Unknown Windows processor architecture." );
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
|
|
// some features require a server of a certain version or later
|
|
// this function determines the version of the server we're connected to
|
|
// and stores it in the connection. Any errors are logged before return.
|
|
// Exception is thrown when the server version is either undetermined
|
|
// or is invalid (< 2000).
|
|
|
|
void determine_server_version( sqlsrv_conn* conn TSRMLS_DC )
|
|
{
|
|
SQLSMALLINT info_len;
|
|
char p[ INFO_BUFFER_LEN ];
|
|
core::SQLGetInfo( conn, SQL_DBMS_VER, p, INFO_BUFFER_LEN, &info_len TSRMLS_CC );
|
|
|
|
errno = 0;
|
|
char version_major_str[ 3 ];
|
|
SERVER_VERSION version_major;
|
|
memcpy( version_major_str, p, 2 );
|
|
version_major_str[ 2 ] = '\0';
|
|
version_major = static_cast<SERVER_VERSION>( atoi( version_major_str ));
|
|
|
|
CHECK_CUSTOM_ERROR( version_major == 0 && ( errno == ERANGE || errno == EINVAL ), conn, SQLSRV_ERROR_UNKNOWN_SERVER_VERSION )
|
|
{
|
|
throw core::CoreException();
|
|
}
|
|
|
|
// SNAC won't connect to versions older than SQL Server 2000, so we know that the version is at least
|
|
// that high
|
|
conn->server_version = version_major;
|
|
}
|
|
|
|
void common_conn_str_append_func( const char* odbc_name, const char* val, size_t val_len, std::string& conn_str TSRMLS_DC )
|
|
{
|
|
// wrap a connection option in a quote. It is presumed that any character that need to be escaped will
|
|
// be escaped, such as a closing }.
|
|
TSRMLS_C;
|
|
|
|
if( val_len > 0 && val[0] == '{' && val[ val_len - 1 ] == '}' ) {
|
|
++val;
|
|
val_len -= 2;
|
|
}
|
|
conn_str += odbc_name;
|
|
conn_str += "={";
|
|
conn_str.append( val, val_len );
|
|
conn_str += "};";
|
|
}
|
|
|
|
} // namespace
|
|
|
|
// simply add the parsed value to the connection string
|
|
void conn_str_append_func::func( connection_option const* option, zval* value, sqlsrv_conn* /*conn*/, std::string& conn_str
|
|
TSRMLS_DC )
|
|
{
|
|
const char* val_str = Z_STRVAL_P( value );
|
|
size_t val_len = Z_STRLEN_P( value );
|
|
common_conn_str_append_func( option->odbc_name, val_str, val_len, conn_str TSRMLS_CC );
|
|
}
|
|
|
|
// 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 )
|
|
{
|
|
TSRMLS_C;
|
|
}
|
|
|
|
// 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.
|
|
|
|
size_t core_str_zval_is_true( zval* value_z )
|
|
{
|
|
SQLSRV_ASSERT( Z_TYPE_P( value_z ) == IS_STRING, "core_str_zval_is_true: This function only accepts zval of type string." );
|
|
|
|
char* value_in = Z_STRVAL_P( value_z );
|
|
size_t val_len = Z_STRLEN_P( value_z );
|
|
|
|
// strip any whitespace at the end (whitespace is the same value in ASCII and UTF-8)
|
|
size_t last_char = val_len - 1;
|
|
while( isspace( value_in[ last_char ] )) {
|
|
value_in[ last_char ] = '\0';
|
|
val_len = last_char;
|
|
--last_char;
|
|
}
|
|
|
|
// save adjustments to the value made by stripping whitespace at the end
|
|
Z_STRLEN_P( value_z ) = val_len;
|
|
|
|
const char VALID_TRUE_VALUE_1[] = "true";
|
|
const char VALID_TRUE_VALUE_2[] = "1";
|
|
|
|
if(( val_len == ( sizeof( VALID_TRUE_VALUE_1 ) - 1 ) && !strnicmp( value_in, VALID_TRUE_VALUE_1, val_len )) ||
|
|
( val_len == ( sizeof( VALID_TRUE_VALUE_2 ) - 1 ) && !strnicmp( value_in, VALID_TRUE_VALUE_2, val_len ))
|
|
) {
|
|
|
|
return 1; // true
|
|
}
|
|
|
|
return 0; // false
|
|
}
|