//---------------------------------------------------------------------------------------------------------------------------------- // File: util.cpp // // Copyright (c) Microsoft Corporation. All rights reserved. // // Contents: Utility functions used by both connection or statement functions // // Comments: Mostly error handling and some type handling // // License: This software is released under the Microsoft Public License. A copy of the license agreement // may be found online at http://www.codeplex.com/SQLSRVPHP/license. //---------------------------------------------------------------------------------------------------------------------------------- #include "php_sqlsrv.h" #include namespace { // *** internal constants *** // current subsytem. defined for the CHECK_SQL_{ERROR|WARNING} macros int current_log_subsystem = LOG_UTIL; // SQLSTATE for all internal errors const char IMSSP[] = "IMSSP"; // SQLSTATE for all internal warnings const char SSPWARN[] = "01SSP"; // buffer used to hold a formatted log message prior to actually logging it. const int LOG_MSG_SIZE = 2048; char log_msg[ LOG_MSG_SIZE ]; // buffer use to hold a formatted error message returned by get_last_error_message const int ERR_MSG_SIZE = 2048; char err_msg[ ERR_MSG_SIZE ]; // internal error that says that FormatMessage failed const char internal_format_error[] = "An internal error occurred. FormatMessage failed writing an error message."; // *** internal functions *** bool handle_errors_and_warnings( sqlsrv_context const* ctx, zval** reported_chain, zval** ignored_chain, int log_severity, int log_subsystem, const char* _FN_, sqlsrv_error const* ssphp, va_list args TSRMLS_DC ); bool ignore_warning( char const* sql_state, int native_code TSRMLS_DC ); bool sqlsrv_merge_zend_hash( __inout zval* dest_z, zval const* src_z TSRMLS_DC ); int sqlsrv_merge_zend_hash_dtor( void* dest TSRMLS_DC ); } // internal error defintions. see sqlsrv_error structure definition in php_sqlsrv.h for more information sqlsrv_error SQLSRV_ERROR_INVALID_OPTION[] = { { IMSSP, "Invalid option %1!s! was passed to sqlsrv_connect.", -1, true } }; sqlsrv_error SQLSRV_ERROR_FILE_VERSION[] = { { IMSSP, "An error occurred when retrieving the extension version.", -2, false } }; sqlsrv_error SQLSRV_ERROR_INVALID_PARAM_TYPE[] = { { IMSSP, "An unknown type for a bound parameter was specified.", -3, false } }; sqlsrv_error SQLSRV_ERROR_CONNECT_BRACES_NOT_ESCAPED[] = { { IMSSP, "An unescaped right brace (}) was found in option %1!s!.", -4, true } }; sqlsrv_error SQLSRV_ERROR_NO_DATA[] = { { IMSSP, "Field %1!d! returned no data.", -5, true } }; sqlsrv_error SQLSRV_ERROR_STREAMABLE_TYPES_ONLY[] = { { IMSSP, "Only char, nchar, varchar, nvarchar, binary, varbinary, and large object types can be read by using streams.", -6, false} }; sqlsrv_error SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE[] = { { IMSSP, "An invalid PHP type was specified as an output parameter. DateTime objects, NULL values, and streams cannot be specified as output parameters.", -7, false } }; sqlsrv_error SQLSRV_ERROR_INVALID_CONNECTION_KEY[] = { { IMSSP, "An invalid connection option key type was received. Option key types must be strings.", -8, false } }; sqlsrv_error SQLSRV_ERROR_VAR_REQUIRED[] = { { IMSSP, "Parameter array %1!d! must have at least one value or variable.", -9, true } }; sqlsrv_error SQLSRV_ERROR_INVALID_FETCH_TYPE[] = { { IMSSP, "An invalid fetch type was specified. SQLSRV_FETCH_NUMERIC, SQLSRV_FETCH_ARRAY, and SQLSRV_FETCH_BOTH are acceptable values.", -10, false } }; sqlsrv_error SQLSRV_ERROR_STATEMENT_NOT_EXECUTED[] = { { IMSSP, "The statement must be executed before results can be retrieved.", -11, false } }; sqlsrv_error SQLSRV_ERROR_ALREADY_IN_TXN[] = { { IMSSP, "Cannot begin a transaction until the current transaction has been completed by calling either sqlsrv_commit or sqlsrv_rollback.", -12, false } }; sqlsrv_error SQLSRV_ERROR_NOT_IN_TXN[] = { { IMSSP, "A transaction must be started by calling sqlsrv_begin_transaction before calling sqlsrv_commit or sqlsrv_rollback.", -13, false } }; sqlsrv_error SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER[] = { { IMSSP, "An invalid parameter was passed to %1!s!.", -14, true } }; sqlsrv_error SQLSRV_ERROR_INVALID_PARAMETER_DIRECTION[] = { { IMSSP, "An invalid direction for parameter %1!d! was specified. SQLSRV_PARAM_IN, SQLSRV_PARAM_OUT, and SQLSRV_PARAM_INOUT are valid values.", -15, true } }; sqlsrv_error SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE[] = { { IMSSP, "An invalid PHP type for parameter %1!d! was specified.", -16, true } }; sqlsrv_error SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE[] = { { IMSSP, "An invalid SQL Server type for parameter %1!d! was specified.", -17, true } }; sqlsrv_error SQLSRV_ERROR_FETCH_NOT_CALLED[] = { { IMSSP, "A row must be retrieved with sqlsrv_fetch before retrieving data with sqlsrv_get_field.", -18, false } }; sqlsrv_error SQLSRV_ERROR_FIELD_INDEX_ERROR[] = { { IMSSP, "Fields within a row must be accessed in sequential order. The sqlsrv_get_field function cannot retrieve field %1!d! because its index is less than the index of a field that has already been retrieved (%2!d!).", -19, true } }; sqlsrv_error SQLSRV_ERROR_DATETIME_CONVERSION_FAILED[] = { { IMSSP, "The retrieval of the DateTime object failed.", -20, false } }; sqlsrv_error SQLSRV_ERROR_SERVER_INFO[] = { { IMSSP, "An error occurred while retrieving the server information.", -21, false } }; sqlsrv_error SQLSRV_ERROR_FETCH_PAST_END[] = { { IMSSP, "There are no more rows in the active result set. Since this result set is not scrollable, no more data may be retrieved.", -22, false } }; sqlsrv_error SQLSRV_ERROR_STATEMENT_NOT_PREPARED[] = { { IMSSP, "A statement must be prepared with sqlsrv_prepare before calling sqlsrv_execute.", -23, false } }; sqlsrv_error SQLSRV_ERROR_ZEND_HASH[] = { { IMSSP, "An error occurred while creating or accessing a Zend hash table.", -24, false } }; sqlsrv_error SQLSRV_ERROR_ZEND_STREAM[] = { { IMSSP, "An error occurred while reading from a PHP stream.", -25, false } }; sqlsrv_error SQLSRV_ERROR_NEXT_RESULT_PAST_END[] = { { IMSSP, "There are no more results returned by the query.", -26, false } }; sqlsrv_error SQLSRV_ERROR_STREAM_CREATE[] = { { IMSSP, "An error occurred while retrieving a SQL Server field as a stream.", -27, false } }; sqlsrv_error SQLSRV_ERROR_NO_FIELDS[] = { { IMSSP, "The active result for the query contains no fields.", -28, false } }; sqlsrv_error SQLSRV_ERROR_ZEND_BAD_CLASS[] = { { IMSSP, "Failed to find class %1!s!.", -29, true } }; sqlsrv_error SQLSRV_ERROR_ZEND_OBJECT_FAILED[] = { { IMSSP, "Failed to create an instance of class %1!s!.", -30, true } }; sqlsrv_error SQLSRV_ERROR_INVALID_PARAMETER_PRECISION[] = { { IMSSP, "An invalid size or precision for parameter %1!d! was specified.", -31, true } }; sqlsrv_error SQLSRV_ERROR_INVALID_OPTION_KEY[] = { { IMSSP, "Option %1!s! is invalid.", -32, true } }; sqlsrv_error SQLSRV_ERROR_INVALID_OPTION_VALUE[] = { { IMSSP, "Invalid value %1!s! for option %2!s! was specified.", -33, true } }; sqlsrv_error SQLSRV_ERROR_OUTPUT_PARAM_TYPE_DOESNT_MATCH[] = { { IMSSP, "The type of output parameter %1!d! does not match the type specified by the SQLSRV_PHPTYPE_* constant." " For output parameters, the type of the variable's current value must match the SQLSRV_PHPTYPE_* constant, or be NULL. " "If the type is NULL, the PHP type of the output parameter is inferred from the SQLSRV_SQLTYPE_* constant.", -34, true } }; sqlsrv_error SQLSRV_ERROR_INVALID_TYPE[] = { { IMSSP, "Invalid type", -35, false } }; sqlsrv_error SQLSRV_ERROR_COMMIT_FAILED[] = { { IMSSP, "Transaction commit failed. Auto commit mode is still off.", -36, false } }; sqlsrv_error SQLSRV_ERROR_ROLLBACK_FAILED[] = { { IMSSP, "Transaction rollback failed. Auto commit mode is still off.", -37, false } }; sqlsrv_error SQLSRV_ERROR_AUTO_COMMIT_STILL_OFF[] = { { IMSSP, "The transaction completed (it was either committed or rolled back). Auto commit mode is still off.", -38, false } }; sqlsrv_error SQLSRV_ERROR_REGISTER_RESOURCE[] = { { IMSSP, "Registering the %1!s! resource failed.", -39, true } }; sqlsrv_error SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE[] = { { IMSSP, "An error occurred translating string for input param %1!d! to UCS-2: %2!s!", -40, true } }; sqlsrv_error SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE[] = { { IMSSP, "An error occurred translating string for an output param to UTF-8: %1!s!", -41, true } }; sqlsrv_error SQLSRV_ERROR_FIELD_ENCODING_TRANSLATE[] = { { IMSSP, "An error occurred translating string for a field to UTF-8: %1!s!", -42, true } }; sqlsrv_error SQLSRV_ERROR_INPUT_STREAM_ENCODING_TRANSLATE[] = { { IMSSP, "An error occurred translating a PHP stream from UTF-8 to UTF-16: %1!s!", -43, true } }; sqlsrv_error SQLSRV_ERROR_INVALID_CONN_ENCODING[] = { { IMSSP, "An invalid '%1!s!' encoding was specified in the CharacterSet connection option", -44, true } }; sqlsrv_error SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE[] = { { IMSSP, "An error occurred translating the query string to UTF-16: %1!s!", -46, true } }; sqlsrv_error SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE[] = { { IMSSP, "An error occurred translating the connection string to UTF-16: %1!s!", -47, true } }; sqlsrv_error SQLSRV_ERROR_CONNECT_ILLEGAL_ENCODING[] = { { IMSSP, "The encoding '%1!s!' is not a supported encoding for the CharacterSet connection option.", -48, true } }; sqlsrv_error SQLSRV_ERROR_DRIVER_NOT_INSTALLED[] = { { IMSSP, "The SQL Server Driver for PHP requires the SQL Server 2008 Native Client ODBC Driver (SP1 or later) to communicate with SQL Server. " "That ODBC Driver is not currently installed. Accessing the following URL will download the SQL Server 2008 Native Client ODBC driver for %1!s!: %2!s!", -49, true } }; sqlsrv_error SQLSRV_ERROR_MARS_OFF[] = { { IMSSP, "The connection cannot process this operation because there is a statement with pending results. " "To make the connection available for other queries, either fetch all results or cancel or free the statement. " "For more information, see the product documentation about the MultipleActiveResultSets connection option.", -49, false } }; sqlsrv_error SQLSRV_ERROR_STATEMENT_NOT_SCROLLABLE[] = { { IMSSP, "This function only works with statements that have static or keyset scrollable cursors.", -50, false } }; sqlsrv_error SQLSRV_ERROR_STATEMENT_SCROLLABLE[] = { { IMSSP, "This function only works with statements that are not scrollable.", -51, false } }; sqlsrv_error SQLSRV_ERROR_INVALID_FETCH_STYLE[] = { { IMSSP, "The scroll type passed to sqlsrv_fetch, sqlsrv_fetch_array, or sqlsrv_fetch_object was not valid. Please use one of the SQLSRV_SCROLL constants.", -53, false } }; sqlsrv_error SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE[] = { { IMSSP, "The value passed for the 'Scrollable' statement option is invalid. Please use 'static', 'dynamic', 'keyset', or 'forward'.", -54, false } }; sqlsrv_error SQLSRV_ERROR_INVALID_SERVER_VERSION[] = { { IMSSP, "Attempted to connect to a server of version %1!d!. Only connections to SQL Server 2000 (8) or later are supported.", -55, true } }; // internal warning definitions sqlsrv_error SQLSRV_WARNING_FIELD_NAME_EMPTY[] = { { SSPWARN, "An empty field name was skipped by sqlsrv_fetch_object.", -100, false } }; // This warning is special since it's reported by php_error rather than sqlsrv_errors. That's also why it has // a printf format specification instead of a FormatMessage format specification. sqlsrv_error PHP_WARNING_VAR_NOT_REFERENCE[] = { { SSPWARN, "Variable parameter %d not passed by reference (prefaced with an &). Variable parameters passed to sqlsrv_prepare should be passed by reference, not by value. " "For more information, see sqlsrv_prepare in the API Reference section of the product documentation.", -101, true } }; // sqlsrv_errors( [int $errorsAndOrWarnings] ) // // Returns extended error and/or warning information about the last sqlsrv // operation performed. // // The sqlsrv_errors function can return error and/or warning information by // calling it with one of the following parameter values below. // // Parameters // // $errorsAndOrWarnings[OPTIONAL]: A predefined constant. This parameter can // take one of the values listed: // // SQLSRV_ERR_ALL // Errors and warnings generated on the last sqlsrv function call are returned. // SQLSRV_ERR_ERRORS // Errors generated on the last sqlsrv function call are returned. // SQLSRV_ERR_WARNINGS // Warnings generated on the last sqlsrv function call are returned. // // If no parameter value is supplied, SQLSRV_ERR_ALL is the default // // Return Value // An array of arrays, or null. An example of an error returned: // Array // ( // [0] => Array // ( // [0] => HYT00 // [SQLSTATE] => HYT00 // [1] => 0 // [code] => 0 // [2] => [Microsoft][SQL Native Client]Query timeout expired // [message] => [Microsoft][SQL Native Client]Query timeout expired // ) // ) PHP_FUNCTION( sqlsrv_errors ) { SQLSRV_UNUSED( return_value_used ); SQLSRV_UNUSED( this_ptr ); SQLSRV_UNUSED( return_value_ptr ); long flags = SQLSRV_ERR_ALL; full_mem_check(MEMCHECK_SILENT); DECL_FUNC_NAME( "sqlsrv_errors" ); LOG_FUNCTION; if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "|l", &flags ) == FAILURE ) { RETURN_FALSE; } if( flags == SQLSRV_ERR_ALL ) { int result; zval_auto_ptr both_z; MAKE_STD_ZVAL( both_z ); result = array_init( both_z ); if( result == FAILURE ) { zval_ptr_dtor( &both_z ); RETURN_FALSE; } Z_SET_ISREF_P( both_z ); if( Z_TYPE_P( SQLSRV_G( errors )) == IS_ARRAY && !sqlsrv_merge_zend_hash( both_z, SQLSRV_G( errors ) TSRMLS_CC )) { zend_hash_destroy( Z_ARRVAL_P( both_z )); RETURN_FALSE; } if( Z_TYPE_P( SQLSRV_G( warnings )) == IS_ARRAY && !sqlsrv_merge_zend_hash( both_z, SQLSRV_G( warnings ) TSRMLS_CC )) { zend_hash_destroy( Z_ARRVAL_P( both_z )); RETURN_FALSE; } if( zend_hash_num_elements( Z_ARRVAL_P( both_z )) == 0 ) { RETURN_NULL(); } zval_ptr_dtor( &return_value ); *return_value_ptr = both_z; both_z.transferred(); } else if( flags == SQLSRV_ERR_WARNINGS ) { zval_ptr_dtor( &return_value ); *return_value_ptr = SQLSRV_G( warnings ); zval_add_ref( &SQLSRV_G( warnings )); } else { zval_ptr_dtor( &return_value ); *return_value_ptr = SQLSRV_G( errors ); zval_add_ref( &SQLSRV_G( errors )); } } // sqlsrv_configure( string $setting, mixed $value ) // // Changes the settings for error handling and logging options. // // Parameters // $setting: The name of the setting to be configured. The possible implemented values are // "WarningsReturnAsErrors", "LogSubsystems", and "LogSeverity". // // $value: The value to be applied to the setting specified in the $setting // parameter. See MSDN or the MINIT function for possible values. // // Return Value // If sqlsrv_configure is called with an unsupported setting or value, the // function returns false. Otherwise, the function returns true. PHP_FUNCTION( sqlsrv_configure ) { SQLSRV_UNUSED( return_value_used ); SQLSRV_UNUSED( this_ptr ); SQLSRV_UNUSED( return_value_ptr ); char* option; int option_len; zval* value_z; DECL_FUNC_NAME( "sqlsrv_configure" ); LOG_FUNCTION; RETVAL_FALSE; reset_errors( TSRMLS_C ); if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "sz", &option, &option_len, &value_z ) == FAILURE ) { handle_error( NULL, LOG_UTIL, _FN_, SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER TSRMLS_CC, _FN_ ); RETURN_FALSE; } if( !stricmp( option, INI_WARNINGS_RETURN_AS_ERRORS )) { if( zend_is_true( value_z )) { SQLSRV_G( warnings_return_as_errors ) = true; } else { SQLSRV_G( warnings_return_as_errors ) = false; } LOG( SEV_NOTICE, LOG_UTIL, INI_PREFIX INI_WARNINGS_RETURN_AS_ERRORS " = %1!s!", SQLSRV_G( warnings_return_as_errors ) ? "On" : "Off"); RETURN_TRUE; } else if( !stricmp( option, INI_LOG_SEVERITY )) { if( Z_TYPE_P( value_z ) != IS_LONG ) { handle_error( NULL, LOG_UTIL, _FN_, SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER TSRMLS_CC, _FN_ ); RETURN_FALSE; } long severity_mask = Z_LVAL_P( value_z ); // make sure they can't use 0 to shut off the masking in the severity if( severity_mask < SEV_ALL || severity_mask == 0 || severity_mask > (SEV_NOTICE + SEV_ERROR + SEV_WARNING) ) { RETURN_FALSE; } SQLSRV_G( log_severity ) = severity_mask; LOG( SEV_NOTICE, LOG_UTIL, INI_PREFIX INI_LOG_SEVERITY " = %1!d!", SQLSRV_G( log_severity )); RETURN_TRUE; } else if( !stricmp( option, INI_LOG_SUBSYSTEMS )) { if( Z_TYPE_P( value_z ) != IS_LONG ) { handle_error( NULL, LOG_UTIL, _FN_, SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER TSRMLS_CC, _FN_ ); RETURN_FALSE; } long subsystem_mask = Z_LVAL_P( value_z ); LOG( SEV_NOTICE, LOG_UTIL, "subsystem_mask = %1!d!", subsystem_mask ); if( subsystem_mask < LOG_ALL || subsystem_mask > (LOG_INIT + LOG_CONN + LOG_STMT + LOG_UTIL) ) { RETURN_FALSE; } SQLSRV_G( log_subsystems ) = subsystem_mask; LOG( SEV_NOTICE, LOG_UTIL, INI_PREFIX INI_LOG_SUBSYSTEMS " = %1!d!", SQLSRV_G( log_subsystems )); RETURN_TRUE; } else { LOG( SEV_ERROR, LOG_UTIL, "Invalid option given to sqlsrv_configure" ); handle_error( NULL, LOG_UTIL, _FN_, SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER TSRMLS_CC, _FN_ ); RETURN_FALSE; } } // sqlsrv_get_config( string $setting ) // // Returns the current value of the specified configuration setting. // // Parameters // $setting: The configuration setting for which the value is returned. For a // list of configurable settings, see sqlsrv_configure. // // Return Value // The value of the setting specified by the $setting parameter. If an invalid // setting is specified, false is returned and an error is added to the error // collection. Because false is a valid value for WarningsReturnAsErrors, to // really determine if an error occurred, call sqlsrv_errors. PHP_FUNCTION( sqlsrv_get_config ) { SQLSRV_UNUSED( return_value_used ); SQLSRV_UNUSED( this_ptr ); SQLSRV_UNUSED( return_value_ptr ); char* option; int option_len; DECL_FUNC_NAME( "sqlsrv_get_config" ); LOG_FUNCTION; reset_errors( TSRMLS_C ); if( zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "s", &option, &option_len ) == FAILURE ) { handle_error( NULL, LOG_UTIL, _FN_, SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER TSRMLS_CC, _FN_ ); RETURN_FALSE; } if( !stricmp( option, INI_WARNINGS_RETURN_AS_ERRORS )) { ZVAL_BOOL( return_value, SQLSRV_G( warnings_return_as_errors )); return; } else if( !stricmp( option, INI_LOG_SEVERITY )) { ZVAL_LONG( return_value, SQLSRV_G( log_severity )); return; } else if( !stricmp( option, INI_LOG_SUBSYSTEMS )) { ZVAL_LONG( return_value, SQLSRV_G( log_subsystems )); return; } else { LOG( SEV_ERROR, LOG_UTIL, "Invalid option given to sqlsrv_get_config." ); handle_error( NULL, LOG_UTIL, _FN_, SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER TSRMLS_CC, _FN_ ); RETURN_FALSE; } } // wrapper around handle_error that checks a condition and returns whether or not the error was ignored bool check_sql_error_ex( bool condition, sqlsrv_context const* ctx, int log_subsystem, const char* _FN_, sqlsrv_error const* ssphp TSRMLS_DC, ... ) { if( condition ) { va_list args; #if defined(ZTS) va_start( args, TSRMLS_C ); #else va_start( args, ssphp); #endif bool ignored = handle_error( ctx, log_subsystem, _FN_, ssphp TSRMLS_CC, args ); if( ignored ) { // this will print immediately after the error information in the log LOG( SEV_ERROR, log_subsystem, "error ignored" ); } va_end( args ); return ignored; } return true; } // this is a special function for sqlsrv internal warnings. It emits an internal warning and treats // it as an error if the WarningsReturnAsErrors flag is set. bool check_sqlsrv_warnings( bool condition, sqlsrv_context const* ctx, int log_subsystem, const char* _FN_, sqlsrv_error const* ssphp TSRMLS_DC, ... ) { // we have to have warning as there is no ODBC error or warning assert( ssphp != NULL ); if( condition ) { va_list args; #if defined(ZTS) va_start( args, TSRMLS_C ); #else va_start( args, ssphp); #endif if( SQLSRV_G( warnings_return_as_errors )) { bool ignored = handle_error( ctx, log_subsystem, _FN_, ssphp TSRMLS_CC, args ); if( ignored ) { // this will print immediately after the error information in the log LOG( SEV_ERROR, log_subsystem, "error ignored" ); } va_end( args ); return ignored; } else { handle_warning( ctx, log_subsystem, _FN_, ssphp TSRMLS_CC, args ); } va_end( args ); } return true; } // convert from the default encoding specified by the "CharacterSet" // connection option to UTF-16. mbcs_len and utf16_len are sizes in // bytes. The return is the number of UTF-16 characters in the string // returned in utf16_out_string. An empty string passed in will result as // a failure since MBTWC returns 0 for both an empty string and failure // to convert. unsigned int convert_string_from_default_encoding( unsigned int php_encoding, char const* mbcs_in_string, unsigned int mbcs_len, __out_bcount(utf16_len) wchar_t* utf16_out_string, unsigned int utf16_len ) { unsigned int win_encoding = CP_ACP; switch( php_encoding ) { case SQLSRV_ENCODING_CHAR: win_encoding = CP_ACP; break; // this shouldn't ever be set case SQLSRV_ENCODING_BINARY: DIE( "Invalid encoding" ); break; default: win_encoding = php_encoding; break; } unsigned int required_len = MultiByteToWideChar( win_encoding, MB_ERR_INVALID_CHARS, mbcs_in_string, mbcs_len, utf16_out_string, utf16_len ); if( required_len == 0 ) { return 0; } utf16_out_string[ required_len ] = '\0'; return required_len; } // thin wrapper around convert_string_from_default_encoding that handles // allocation of the destination string. An empty string passed in returns // failure since it's a failure case for convert_string_from_default_encoding. wchar_t* utf16_string_from_mbcs_string( unsigned int php_encoding, const char* mbcs_string, unsigned int mbcs_len, unsigned int* utf16_len ) { *utf16_len = (mbcs_len + 1) * sizeof( wchar_t ); wchar_t* utf16_string = reinterpret_cast( sqlsrv_malloc( *utf16_len )); *utf16_len = convert_string_from_default_encoding( php_encoding, mbcs_string, mbcs_len, utf16_string, *utf16_len ); if( *utf16_len == 0 ) { // we preserve the error and reset it because sqlsrv_free resets the last error DWORD last_error = GetLastError(); sqlsrv_free( utf16_string ); SetLastError( last_error ); return NULL; } return utf16_string; } // wrapper for errors around the common handle_errors_and_warnings bool handle_error( sqlsrv_context const* ctx, int log_subsystem, const char* _FN_, sqlsrv_error const* ssphp TSRMLS_DC, ... ) { va_list args; #if defined(ZTS) va_start( args, TSRMLS_C ); #else va_start( args, ssphp ); #endif LOG( SEV_NOTICE, LOG_UTIL, "handle_error: entered for function %1!s!", _FN_ ); // put errors (including warnings treated as errors) into errors and ignored // warnings into warnings bool ignored = handle_errors_and_warnings( ctx, &SQLSRV_G( errors ), &SQLSRV_G( warnings ), SEV_ERROR, log_subsystem, _FN_, ssphp, args TSRMLS_CC ); va_end( args ); return ignored; } // wrapper for warnings around the common handle_errors_and_warnings void handle_warning( sqlsrv_context const* ctx, int log_subsystem, const char* _FN_, sqlsrv_error const* ssphp TSRMLS_DC, ... ) { va_list args; #if defined(ZTS) va_start( args, TSRMLS_C ); #else va_start( args, ssphp ); #endif LOG( SEV_NOTICE, LOG_UTIL, "handle_warning: entered for function %1!s!", _FN_ ); // put all warnings into the warnings hash table and don't ignore any. No warnings here are treated as errors. handle_errors_and_warnings( ctx, &SQLSRV_G( warnings ), NULL, SEV_WARNING, log_subsystem, _FN_, ssphp, args TSRMLS_CC ); va_end( args ); } // write to the php log if the severity and subsystem match the filters currently set in the INI or // the script (sqlsrv_configure). void write_to_log( unsigned int severity, unsigned int subsystem TSRMLS_DC, const char* msg, ...) { va_list args; va_start( args, msg ); if( (severity & SQLSRV_G( log_severity )) && (subsystem & SQLSRV_G( log_subsystems ))) { DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, msg, 0, 0, log_msg, LOG_MSG_SIZE, &args ); // if an error occurs for FormatMessage, we just output an internal error occurred. if( rc == 0 ) { SQLSRV_STATIC_ASSERT( sizeof( internal_format_error ) < sizeof( log_msg )); std::copy( internal_format_error, internal_format_error + sizeof( internal_format_error ), log_msg ); } php_log_err( log_msg TSRMLS_CC ); } va_end( args ); } // return an error message for GetLastError using FormatMessage. // this function returns the msg pointer so that it may be used within // another function call such as handle_error const char* get_last_error_message( DWORD last_error ) { if( last_error == 0 ) { last_error = GetLastError(); } DWORD r = FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM, NULL, last_error, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), err_msg, sizeof( err_msg ), NULL ); if( r == 0 ) { SQLSRV_STATIC_ASSERT( sizeof( internal_format_error ) < sizeof( err_msg )); std::copy( internal_format_error, internal_format_error + sizeof( internal_format_error ), err_msg ); } return err_msg; } // *** internal function implementations *** namespace { // there are actually two error arrays (potentially) constructed by this function. // The reported chain is that set of diagnostics that is not in the list of warnings to not report as errors. In other words, it's // the list of normally processed diagnostics. // The ignored chain is that set of diagnostics which were specifically ignored and not reported. It is possible for the caller // to specify no ignored chain by setting the parameter to NULL. bool handle_errors_and_warnings( sqlsrv_context const* ctx, zval** reported_chain, zval** ignored_chain, int log_severity, int log_subsystem, const char* _FN_, sqlsrv_error const* ssphp, va_list args TSRMLS_DC ) { zval* ssphp_z = NULL; SQLSMALLINT record_number = 1; SQLCHAR sql_state[6]; SQLCHAR message_text[ SQL_MAX_MESSAGE_LENGTH + 1 ]; SQLINTEGER native_error = 0; SQLSMALLINT message_len = 0; SQLRETURN r = SQL_SUCCESS; zval* temp = NULL; bool reported_chain_was_null = false; bool ignored_chain_was_null = false; int zr = SUCCESS; int reported_before = 0; int ignored_before = 0; LOG( SEV_NOTICE, LOG_UTIL, "handle_errors_and_warnings: entering" ); // create an array of arrays if( Z_TYPE_P( *reported_chain ) == IS_NULL ) { reported_chain_was_null = true; reported_before = 0; zr = array_init( *reported_chain ); if( zr == FAILURE ) { DIE( "Fatal error in handle_errors_and_warnings" ); } } else { reported_before = zend_hash_num_elements( Z_ARRVAL_PP( reported_chain )); } if( ignored_chain != NULL ) { if( Z_TYPE_P( *ignored_chain ) == IS_NULL ) { ignored_chain_was_null = true; ignored_before = 0; zr = array_init( *ignored_chain ); if( zr == FAILURE ) { DIE( "Fatal error in handle_errors_and_warnings" ); } } else { ignored_before = zend_hash_num_elements( Z_ARRVAL_PP( ignored_chain )); } } else { ignored_before = 0; } // add the PHP error first if there is one. // We use a while loop to allow the break to exit the loop and avoid having to use a goto for error handling // the break at the end of the loop assures that we don't get stuck here. while( ssphp ) { sqlsrv_malloc_auto_ptr ssphp_new; sqlsrv_malloc_auto_ptr ssphp_new_message; if( ssphp->format ) { ssphp_new = static_cast( sqlsrv_malloc( sizeof( sqlsrv_error ))); ssphp_new->native_message = ssphp_new_message = static_cast( sqlsrv_malloc( SQL_MAX_MESSAGE_LENGTH )); ssphp_new->sqlstate = ssphp->sqlstate; ssphp_new->native_code = ssphp->native_code; DWORD rc = FormatMessage( FORMAT_MESSAGE_FROM_STRING, const_cast( ssphp->native_message ), 0, 0, const_cast( ssphp_new->native_message ), SQL_MAX_MESSAGE_LENGTH, &args ); if( rc == 0 ) { ssphp_new->native_message = internal_format_error; } ssphp = ssphp_new; } // log the error first in case of failures below LOG( log_severity, log_subsystem, "%1!s!: SQLSTATE = %2!s!", _FN_, ssphp->sqlstate ); LOG( log_severity, log_subsystem, "%1!s!: error code = %2!d!", _FN_, ssphp->native_code ); LOG( log_severity, log_subsystem, "%1!s!: message = %2!s!", _FN_, ssphp->native_message ); MAKE_STD_ZVAL( ssphp_z ); zr = array_init( ssphp_z ); if( zr == FAILURE ) { zval_ptr_dtor( &ssphp_z ); break; } // add the error info to the array MAKE_STD_ZVAL( temp ); ZVAL_STRINGL( temp, const_cast( ssphp->sqlstate ), SQL_SQLSTATE_SIZE, 1 ); zr = add_next_index_zval( ssphp_z, temp ); if( zr == FAILURE ) { zval_ptr_dtor( &ssphp_z ); break; } zr = add_assoc_zval( ssphp_z, "SQLSTATE", temp ); if( zr == FAILURE ) { zval_ptr_dtor( &ssphp_z ); break; } MAKE_STD_ZVAL( temp ); ZVAL_LONG( temp, ssphp->native_code ); zr = add_next_index_zval( ssphp_z, temp ); if( zr == FAILURE ) { zval_ptr_dtor( &ssphp_z ); break; } zr = add_assoc_zval( ssphp_z, "code", temp ); if( zr == FAILURE ) { zval_ptr_dtor( &ssphp_z ); break; } MAKE_STD_ZVAL( temp ); ZVAL_STRING( temp, const_cast( ssphp->native_message ), 1 ); zr = add_next_index_zval( ssphp_z, temp ); if( zr == FAILURE ) { zval_ptr_dtor( &ssphp_z ); break; } zr = add_assoc_zval( ssphp_z, "message", temp ); if( zr == FAILURE ) { zval_ptr_dtor( &ssphp_z ); break; } if( ignore_warning( ssphp->sqlstate, ssphp->native_code TSRMLS_CC ) && ignored_chain != NULL ) { zr = add_next_index_zval( *ignored_chain, ssphp_z ); if( zr == FAILURE ) { zval_ptr_dtor( &ssphp_z ); break; } } else { zr = add_next_index_zval( *reported_chain, ssphp_z ); if( zr == FAILURE ) { zval_ptr_dtor( &ssphp_z ); break; } } break; // exit the "loop" always } if( ctx ) { SQLHANDLE h = ctx->handle; SQLSMALLINT h_type = ctx->handle_type; for( r = SQLGetDiagRec( h_type, h, record_number, sql_state, &native_error, message_text, SQL_MAX_MESSAGE_LENGTH + 1, &message_len ); SQL_SUCCEEDED( r ); ++record_number, r = SQLGetDiagRec( h_type, h, record_number, sql_state, &native_error, message_text, SQL_MAX_MESSAGE_LENGTH + 1, &message_len )) { // log the result first LOG( log_severity, log_subsystem, "%1!s!: SQLSTATE = %2!s!", _FN_, sql_state ); LOG( log_severity, log_subsystem, "%1!s!: error code = %2!d!", _FN_, native_error ); LOG( log_severity, log_subsystem, "%1!s!: message = %2!s!", _FN_, message_text ); MAKE_STD_ZVAL( ssphp_z ); zr = array_init( ssphp_z ); if( zr == FAILURE ) { zval_ptr_dtor( &ssphp_z ); continue; } // add the error info to the array MAKE_STD_ZVAL( temp ); ZVAL_STRINGL( temp, reinterpret_cast( sql_state ), SQL_SQLSTATE_SIZE, 1 ); zr = add_next_index_zval( ssphp_z, temp ); if( zr == FAILURE ) { zval_ptr_dtor( &ssphp_z ); continue; } zr = add_assoc_zval( ssphp_z, "SQLSTATE", temp ); if( zr == FAILURE ) { zval_ptr_dtor( &ssphp_z ); continue; } MAKE_STD_ZVAL( temp ); ZVAL_LONG( temp, native_error ); zr = add_next_index_zval( ssphp_z, temp ); if( zr == FAILURE ) { zval_ptr_dtor( &ssphp_z ); continue; } zr = add_assoc_zval( ssphp_z, "code", temp ); if( zr == FAILURE ) { zval_ptr_dtor( &ssphp_z ); continue; } MAKE_STD_ZVAL( temp ); ZVAL_STRINGL( temp, reinterpret_cast( message_text ), message_len, 1 ); zr = add_next_index_zval( ssphp_z, temp ); if( zr == FAILURE ) { zval_ptr_dtor( &ssphp_z ); continue; } zr = add_assoc_zval( ssphp_z, "message", temp ); if( zr == FAILURE ) { zval_ptr_dtor( &ssphp_z ); continue; } if( ignore_warning( reinterpret_cast( sql_state ), native_error TSRMLS_CC ) && ignored_chain != NULL ) { zr = add_next_index_zval( *ignored_chain, ssphp_z ); if( zr == FAILURE ) { zval_ptr_dtor( &ssphp_z ); continue; } } else { zr = add_next_index_zval( *reported_chain, ssphp_z ); if( zr == FAILURE ) { zval_ptr_dtor( &ssphp_z ); continue; } } } } bool all_errors_ignored = ( zend_hash_num_elements( Z_ARRVAL_PP( reported_chain )) == reported_before ) && ( ignored_chain != NULL && zend_hash_num_elements( Z_ARRVAL_PP( ignored_chain )) > ignored_before ); // if the error array came in as NULL and didn't have anything added to it, return it as NULL if( reported_chain_was_null && zend_hash_num_elements( Z_ARRVAL_PP( reported_chain )) == 0 ) { zend_hash_destroy( Z_ARRVAL_PP( reported_chain )); FREE_HASHTABLE( Z_ARRVAL_PP( reported_chain )); ZVAL_NULL( *reported_chain ); } if( ignored_chain != NULL && ignored_chain_was_null && zend_hash_num_elements( Z_ARRVAL_PP( ignored_chain )) == 0 ) { zend_hash_destroy( Z_ARRVAL_PP( ignored_chain )); FREE_HASHTABLE( Z_ARRVAL_PP( ignored_chain )); ZVAL_NULL( *ignored_chain ); } return all_errors_ignored; } // return whether or not a warning should be ignored or returned as an error if WarningsReturnAsErrors is true // see RINIT in init.cpp for information about which errors are ignored. bool ignore_warning( char const* sql_state, int native_code TSRMLS_DC ) { for( zend_hash_internal_pointer_reset( SQLSRV_G( warnings_to_ignore )); zend_hash_has_more_elements( SQLSRV_G( warnings_to_ignore ) ) == SUCCESS; zend_hash_move_forward( SQLSRV_G( warnings_to_ignore ) ) ) { void* error_v; sqlsrv_error* error; int result; result = zend_hash_get_current_data( SQLSRV_G( warnings_to_ignore ), (void**) &error_v ); if( result == FAILURE ) { return false; } error = static_cast( error_v ); if( !strncmp( error->sqlstate, sql_state, SQL_SQLSTATE_SIZE ) && ( error->native_code == native_code || error->native_code == -1 )) { return true; } } return false; } // used by sqlsrv_merge_zend_hash below int sqlsrv_merge_zend_hash_dtor( void* dest TSRMLS_DC ) { #if defined(ZTS) SQLSRV_UNUSED( tsrm_ls ); #endif zval_ptr_dtor( reinterpret_cast( &dest )); return ZEND_HASH_APPLY_REMOVE; } // sqlsrv_merge_zend_hash // merge a source hash into a dest hash table and return any errors. bool sqlsrv_merge_zend_hash( __inout zval* dest_z, zval const* src_z TSRMLS_DC ) { #if defined(ZTS) SQLSRV_UNUSED( tsrm_ls ); #endif if( Z_TYPE_P( dest_z ) != IS_ARRAY && Z_TYPE_P( dest_z ) != IS_NULL ) DIE( "dest_z must be an array or null" ); if( Z_TYPE_P( src_z ) != IS_ARRAY && Z_TYPE_P( src_z ) != IS_NULL ) DIE( "src_z must be an array or null" ); if( Z_TYPE_P( src_z ) == IS_NULL ) { return true; } HashTable* src_ht = Z_ARRVAL_P( src_z ); int result = SUCCESS; for( zend_hash_internal_pointer_reset( src_ht ); zend_hash_has_more_elements( src_ht ) == SUCCESS; zend_hash_move_forward( src_ht ) ) { void * value_v; zval* value_z; result = zend_hash_get_current_data( src_ht, (void**) &value_v ); if( result == FAILURE ) { zend_hash_apply( Z_ARRVAL_P( dest_z ), sqlsrv_merge_zend_hash_dtor TSRMLS_CC ); return false; } value_z = *(static_cast( value_v )); result = add_next_index_zval( dest_z, value_z ); if( result == FAILURE ) { zend_hash_apply( Z_ARRVAL_P( dest_z ), sqlsrv_merge_zend_hash_dtor TSRMLS_CC ); return false; } zval_add_ref( &value_z ); } return true; } } // namespace