void build_connection_string_and_set_conn_attr()

in source/shared/core_conn.cpp [731:858]


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, _In_ const connection_option valid_conn_opts[],
                                                void* driver, _Inout_ std::string& connection_string )
{
    bool mars_mentioned = false;
    connection_option const* conn_opt;
    bool access_token_used = false;
    bool authentication_option_used = zend_hash_index_exists(options, SQLSRV_CONN_OPTION_AUTHENTICATION);

    try {
        // Since connection options access token and authentication cannot coexist, check if both of them are used.
        // If access token is specified, check UID and�PWD as well.
        // No need to check the keyword Trusted_Connection�because it is not among the acceptable options for SQLSRV drivers
        if (zend_hash_index_exists(options, SQLSRV_CONN_OPTION_ACCESS_TOKEN)) {
            bool invalidOptions = false;

            // UID and PWD have to be NULLs... throw an exception as long as the user has specified any of them in the connection string,
            // even if they may be empty strings. Likewise if the keyword Authentication exists
            if (uid != NULL || pwd != NULL || authentication_option_used) {
                invalidOptions = true;
            }

            CHECK_CUSTOM_ERROR(invalidOptions, conn, SQLSRV_ERROR_INVALID_OPTION_WITH_ACCESS_TOKEN ) {
                throw core::CoreException();
            }

            access_token_used = true;
        }

        // Check if Authentication is ActiveDirectoryMSI because we have to handle this case differently
        // https://docs.microsoft.com/en-ca/azure/active-directory/managed-identities-azure-resources/overview
        bool activeDirectoryMSI = false;
        if (authentication_option_used) {
            const char aadMSIoption[] = "ActiveDirectoryMSI";
            zval* auth_option = NULL;
            auth_option = zend_hash_index_find(options, SQLSRV_CONN_OPTION_AUTHENTICATION);

            char* option = NULL;
            if (auth_option != NULL) {
                option = Z_STRVAL_P(auth_option);
            }

            if (option != NULL && !stricmp(option, aadMSIoption)) {
                activeDirectoryMSI = true;
            }
        }

        // Add the server name
        common_conn_str_append_func( ODBCConnOptions::SERVER, server, strnlen_s( server ), connection_string );

        // If uid is not present then we use trusted connection -- but not when connecting
        // using the access token or Authentication is ActiveDirectoryMSI
        if (!access_token_used && !activeDirectoryMSI) {
            if (uid == NULL || strnlen_s(uid) == 0) {
                connection_string += CONNECTION_OPTION_NO_CREDENTIALS;  //  "Trusted_Connection={Yes};"
            }
            else {
                bool escaped = core_is_conn_opt_value_escaped(uid, strnlen_s(uid));
                CHECK_CUSTOM_ERROR(!escaped, conn, SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED) {
                    throw core::CoreException();
                }

                common_conn_str_append_func(ODBCConnOptions::UID, uid, strnlen_s(uid), connection_string);

                // 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, strnlen_s(pwd));
                    CHECK_CUSTOM_ERROR(!escaped, conn, SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED) {
                        throw core::CoreException();
                    }

                    common_conn_str_append_func(ODBCConnOptions::PWD, pwd, strnlen_s(pwd), connection_string);
                }
            }
        }

        // 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 );

            if( index == SQLSRV_CONN_OPTION_MARS ) {
                mars_mentioned = true;
            }

            conn_opt->func( conn_opt, data, conn, connection_string );
        } ZEND_HASH_FOREACH_END();

        // MARS on if not explicitly turned off
        if( !mars_mentioned ) {
            connection_string += CONNECTION_OPTION_MARS_ON;
        }

    }
    catch( core::CoreException& ) {
        conn->ce_option.akv_reset();
        throw;
    }
}