SQLRETURN sqlsrv_buffered_result_set::system_to_wide_string()

in source/shared/core_results.cpp [1151:1249]


SQLRETURN sqlsrv_buffered_result_set::system_to_wide_string( _In_ SQLSMALLINT field_index, _Out_writes_z_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, 
                                                             _Out_ SQLLEN* out_buffer_length )
{
    SQLSRV_ASSERT( last_error == 0, "Pending error for sqlsrv_buffered_results_set::system_to_wide_string" );
    SQLSRV_ASSERT( buffer_length % 2 == 0, "Odd buffer length passed to sqlsrv_buffered_result_set::system_to_wide_string" );

    SQLRETURN r = SQL_ERROR;
    unsigned char* row = get_row();

    SQLCHAR* field_data = NULL;
    SQLULEN field_len = 0;

    if( meta[field_index].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) {

        field_len = **reinterpret_cast<SQLLEN**>( &row[meta[field_index].offset] );
        field_data = *reinterpret_cast<SQLCHAR**>( &row[meta[field_index].offset] ) + sizeof( SQLULEN ) + read_so_far;
    }
    else {

        field_len = *reinterpret_cast<SQLLEN*>( &row[meta[field_index].offset] );
        field_data = &row[meta[field_index].offset] + sizeof( SQLULEN ) + read_so_far;
    }

    // all fields will be treated as ODBC returns varchar(max) fields:
    // the entire length of the string is returned the first
    // call in out_buffer_len.  Successive calls return how much is
    // left minus how much has already been read by previous reads
    *out_buffer_length = (*reinterpret_cast<SQLLEN*>( field_data - sizeof( SQLULEN )) - read_so_far) * sizeof(WCHAR);

    // to_copy is the number of characters to copy, not including the null terminator
    // supposedly it will never happen that a Windows MBCS will explode to UTF-16 surrogate pair.
    SQLLEN to_copy;

    if( (size_t) buffer_length < (field_len - read_so_far + sizeof(char)) * sizeof(WCHAR)) {

        to_copy = (buffer_length - sizeof(WCHAR)) / sizeof(WCHAR);    // to_copy is the number of characters
        last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) 
            sqlsrv_error( (SQLCHAR*) "01004", (SQLCHAR*) "String data, right truncated", -1 );
        r = SQL_SUCCESS_WITH_INFO;
    }
    else {

        r = SQL_SUCCESS;
        to_copy = field_len - read_so_far;
    }

    if( to_copy > 0 ) {

        bool tried_again = false;
        do {
			if (to_copy > INT_MAX ) {
				LOG(SEV_ERROR, "MultiByteToWideChar: Buffer length exceeded.");
				throw core::CoreException();
			}

#ifndef _WIN32
             int ch_space = SystemLocale::ToUtf16( CP_ACP, (LPCSTR) field_data, static_cast<int>(to_copy), 
                                    static_cast<LPWSTR>(buffer), static_cast<int>(to_copy));
									
#else
            int ch_space = MultiByteToWideChar( CP_ACP, MB_ERR_INVALID_CHARS, (LPCSTR) field_data, static_cast<int>(to_copy), 
                                      static_cast<LPWSTR>(buffer), static_cast<int>(to_copy));
#endif // !_WIN32
			
            if( ch_space == 0 ) {

                switch( GetLastError() ) {

                    case ERROR_NO_UNICODE_TRANSLATION:
                        // the theory here is the conversion failed because the end of the buffer we provided contained only 
                        // half a character at the end
                        if( !tried_again ) {
                            to_copy--;
                            tried_again = true;
                            continue;
                        }
                        last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error )))
                            sqlsrv_error( (SQLCHAR*) "IMSSP", (SQLCHAR*) "Invalid Unicode translation", -1 );
                        break;
                    default:
                        SQLSRV_ASSERT( false, "Severe error translating Unicode" );
                        break;
                }

                return SQL_ERROR;
            }

            ((WCHAR*)buffer)[to_copy] = L'\0';
            read_so_far += to_copy;
            break;

        } while( true );
    }
    else {
        reinterpret_cast<WCHAR*>( buffer )[0] = L'\0';
    }

    return r;
}