/* Copyright (c) 2004, 2012, Oracle and/or its affiliates. All rights reserved.

   The MySQL Connector/ODBC is licensed under the terms of the GPLv2
   <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
   MySQL Connectors. There are special exceptions to the terms and
   conditions of the GPLv2 as it is applied to this software, see the
   FLOSS License Exception
   <http://www.mysql.com/about/legal/licensing/foss-exception.html>.
   
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published
   by the Free Software Foundation; version 2 of the License.
   
   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
   or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
   for more details.
   
   You should have received a copy of the GNU General Public License along
   with this program; if not, write to the Free Software Foundation, Inc.,
   51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA */

#ifdef RISCOS
#include "MYODBC_MYSQL.h"
#else
#include "../MYODBC_MYSQL.h"
#endif
#include "MYODBCUtil.h"

/*!
    \note   - unixODBC does not have a header section in ini files.
            - On XP, the "32 bit" parts are secretly stripped away
            inside SQLGetPrivateProfileString() somewhere.
*/            
#define MYODBCUTIL_ODBCINI_HEADER_SECTION "ODBC Data Sources"

ulong NamedAttributeToFlag(const char* name);

/*!
    \brief  Read data source from system information.

    \param  pDataSource Data Source struct.
    \param  pszDSN The data source name to lookup.

    \note   This will not replace existing values in pDataSource.

            On XP we use hard-coded driver name and driver
            file name (if not given) as it seems MS is in the middle
            of changing odbc system info (probably to better support
            64 bit).
*/            
BOOL MYODBCUtilReadDataSource( MYODBCUTIL_DATASOURCE *pDataSource, LPCSTR pszDSN )
{
    char    szEntryNames[SQL_MAX_DSN_LENGTH * MYODBCUTIL_MAX_DSN_NAMES];
    char *  pszEntryName;
    char    szValue[4096];
    int     nChars;
    ulong   nOption= 0, nPositives=0, nNegatives= 0, flag= 0;

#ifdef _WIN32
    UWORD   nMode   = ODBC_BOTH_DSN;
#endif
    SAVE_MODE();

#ifdef _WIN32
    if ( !SQLGetConfigMode( &nMode ) )
    {
        fprintf( stderr, "[%s][%d][ERROR] SQLGetConfigMode failed!\n", __FILE__, __LINE__ );
        return FALSE;
    }
#endif

    if ( !pszDSN || !(*pszDSN) )
        return TRUE;

    *szEntryNames = '\0';

    /* Get the list of key names for the DSN's section. */
    nChars= SQLGetPrivateProfileString(pszDSN, NULL, "",
                                       szEntryNames, sizeof(szEntryNames) - 1,
                                       MYODBCUTIL_ODBC_INI);
    if (nChars < 1)
      return FALSE;

    RESTORE_MODE();

#if defined(WIN32)
    {
        /*
          There is a bug in SQLGetPrivateProfileString when mode is
          ODBC_BOTH_DSN and we are looking for a system DSN. In this case
          SQLGetPrivateProfileString will find the system DSN but return
          a corrupt list of attributes.

          A corrupt list of attributes can be identified because the first
          attribute (if any) will be followed by more than one '\0'.

          The solution is to detect this condition and set mode to
          ODBC_SYSTEM_DSN and try again. We also ensure we reset the mode
          when done - regardless of outcome.

          This may be the issue explained here:
            http://support.microsoft.com/kb/909122/

          And this work-around may not be correct:
            http://bugs.mysql.com/27599
        */
        int     nLen    = strlen( szEntryNames );

        if ( nMode == ODBC_BOTH_DSN && nLen < nChars && szEntryNames[nLen + 1 ] == '\0' )
        {
            *szEntryNames = '\0';
            if ( !SQLSetConfigMode( ODBC_SYSTEM_DSN ) )
                fprintf( stderr, "[%s][%d][ERROR] SQLSetConfigMode failed!\n", __FILE__, __LINE__ );

            if ( ( nChars = SQLGetPrivateProfileString( pszDSN, NULL, NULL, szEntryNames, sizeof( szEntryNames ) - 1, MYODBCUTIL_ODBC_INI ) ) < 1 )
                return FALSE;
        }
    }
#endif

    /* Looks like things are going to work. Lets set DSN.  */
    if ( !pDataSource->pszDSN )
        pDataSource->pszDSN = _global_strdup( pszDSN ) ;

    /* Handle each key we found for this DSN */
    pszEntryName = szEntryNames;
    while ( *pszEntryName )
    {
        *szValue = '\0';
        if ( SQLGetPrivateProfileString( pszDSN, pszEntryName, "", szValue, sizeof( szValue ) - 1, MYODBCUTIL_ODBC_INI ) > 0 )
        {
            /*!
                ODBC RULE
                (SQLDriverConnect)

                The driver uses any information it retrieves from the system information 
                to augment the information passed to it in the connection string. If the 
                information in the system information duplicates information in the 
                connection string, the driver uses the information in the connection 
                string.
            */    
            if ( strcasecmp( pszEntryName, "DATABASE" ) == 0 || strcasecmp( pszEntryName, "DB" ) == 0 )
            {
                if ( !pDataSource->pszDATABASE )
                    pDataSource->pszDATABASE = _global_strdup( szValue ) ;
            }
            else if ( strcasecmp( pszEntryName, "DESCRIPTION" ) == 0 || strcasecmp( pszEntryName, "DESC" ) == 0 )
            {
                if ( !pDataSource->pszDESCRIPTION )
                    pDataSource->pszDESCRIPTION = _global_strdup( szValue );
            }
            else if ( strcasecmp( pszEntryName, "DRIVER" ) == 0 )
            {
#if defined(WIN32)
                if ( !pDataSource->pszDriverFileName )
                    pDataSource->pszDriverFileName = _global_strdup( szValue );
#else
                if ( *szValue == '/' )
                {
                    if ( !pDataSource->pszDriverFileName )
                        pDataSource->pszDriverFileName = _global_strdup( szValue );
                }
                else
                {
                    if ( !pDataSource->pszDRIVER )
                        pDataSource->pszDRIVER = _global_strdup( szValue );
                }
#endif
            }
            else if ( strcasecmp( pszEntryName, "OPTION" ) == 0 )
            {
              /* I think since option is not a single attribute by rather
                 a collection of attributes, I think an OPTION from the dsn should
                 be added to an option in the connection string.
                 But let's better leave it as is for now. */
                if ( !pDataSource->pszOPTION )
                {
                  nOption= strtoul(szValue, NULL, 10);
                  if (nOption > 0)
                  {
                    pDataSource->pszOPTION = _global_strdup(szValue);
                  }
                    
                }
            }
            else if ( strcasecmp( pszEntryName, "INTERACTIVE" ) == 0 )
            {
              // If some connection option is set in a connection string, it has
              // priority over value set in DSN for it.
              // TODO: Following is not absolutely correct - if str contains
              // INTERACTIVE=0, while DSN - INTERACTIVE=1, driver will use DSN's value
              pDataSource->bINTERACTIVE= pDataSource->bINTERACTIVE || (atol(szValue) != 0);
            }
            else if ( strcasecmp( pszEntryName, "PWD" ) == 0 || strcasecmp( pszEntryName, "PASSWORD" ) == 0 )
            {    
                if ( !pDataSource->pszPASSWORD )
                    pDataSource->pszPASSWORD = _global_strdup( szValue );
            }
            else if ( strcasecmp( pszEntryName, "PORT" ) == 0 )
            {    
                if ( !pDataSource->pszPORT )
                    pDataSource->pszPORT = _global_strdup( szValue );
            }
            else if ( strcasecmp( pszEntryName, "SERVER" ) == 0 )
            {    
                if ( !pDataSource->pszSERVER )
                    pDataSource->pszSERVER = _global_strdup( szValue );
            }
            else if ( strcasecmp( pszEntryName, "SOCKET" ) == 0 )
            {    
                if ( !pDataSource->pszSOCKET )
                    pDataSource->pszSOCKET = _global_strdup( szValue );
            }
            else if ( strcasecmp( pszEntryName, "STMT" ) == 0 || strcasecmp( pszEntryName, "INITSTMT" ) == 0 )
            {    
                if ( !pDataSource->pszSTMT )
                    pDataSource->pszSTMT = _global_strdup( szValue );
            }
            else if ( strcasecmp( pszEntryName, "UID" ) == 0 || strcasecmp( pszEntryName, "USER" ) == 0 )
            {    
                if ( !pDataSource->pszUSER )
                    pDataSource->pszUSER = _global_strdup( szValue );
            }
            else if ( strcasecmp( pszEntryName, "SSLCA" ) == 0 )
            {    
                if ( !pDataSource->pszSSLCA )
                    pDataSource->pszSSLCA = _global_strdup( szValue );
            }
            else if ( strcasecmp( pszEntryName, "SSLCAPATH" ) == 0 )
            {    
                if ( !pDataSource->pszSSLCAPATH )
                    pDataSource->pszSSLCAPATH = _global_strdup( szValue );
            }
            else if ( strcasecmp( pszEntryName, "SSLCERT" ) == 0 )
            {    
                if ( !pDataSource->pszSSLCERT )
                    pDataSource->pszSSLCERT = _global_strdup( szValue );
            }
            else if ( strcasecmp( pszEntryName, "SSLCIPHER" ) == 0 )
            {    
                if ( !pDataSource->pszSSLCIPHER )
                    pDataSource->pszSSLCIPHER = _global_strdup( szValue );
            }
            else if ( strcasecmp( pszEntryName, "SSLKEY" ) == 0 )
            {    
                if ( !pDataSource->pszSSLKEY )
                    pDataSource->pszSSLKEY = _global_strdup( szValue );
            }
            else if ( strcasecmp( pszEntryName, "SSLVERIFY" ) == 0 )
            {    
                if ( !pDataSource->pszSSLVERIFY )
                    pDataSource->pszSSLVERIFY = _global_strdup( szValue );
            }
            else if (strcasecmp(pszEntryName, "CHARSET") == 0)
            {
              if (!pDataSource->pszCHARSET)
                pDataSource->pszCHARSET= _global_strdup(szValue);
            }
            else if (strcasecmp(pszEntryName, "READTIMEOUT") == 0)
            {
              if (!pDataSource->pszREADTIMEOUT)
                pDataSource->pszREADTIMEOUT= _global_strdup(szValue);
            }
            else if (strcasecmp(pszEntryName, "WRITETIMEOUT") == 0)
            {
              if (!pDataSource->pszWRITETIMEOUT)
                pDataSource->pszWRITETIMEOUT= _global_strdup(szValue);
            }
            else if ((flag= NamedAttributeToFlag(pszEntryName)) != 0)
            {
              /* Sticking to same principle here: it OPTION is already set, and
                 that was done in the connection string we do not update it
                 here */
              if (!pDataSource->pszOPTION || nOption > 0
                && (flag & (nPositives | nNegatives)) == 0)
              {
                if (atol(szValue) != 0)
                {
                  nPositives |= flag;
                }
                else
                {
                  nNegatives |= flag;
                }
              }
            }
#ifdef RISCOS
            else if ( strcasecmp( pszEntryName, "NAME" ) == 0 ||
                      strcasecmp( pszEntryName, "SERVERTYPE" ) == 0 ||
                      strcasecmp( pszEntryName, "THREADMANAGER" ) == 0 ||
                      strcasecmp( pszEntryName, "TRACE" ) == 0 ||
                      strcasecmp( pszEntryName, "TRACEFILE" ) == 0 ||
                      strcasecmp( pszEntryName, "UNLOADSAFE" ) == 0 )
            {
                /* ODBCMan and iODBC keys, not the driver's */
            }
#endif
            else
            {
                /* What the ? */
                fprintf( stderr, "[%s][%d][ERROR] Unknown attribute (%s).\n", __FILE__, __LINE__, pszEntryName );
            }
        }
        else
        {
            /*  
                At least two diff Users stated they did not want this message and I think we can do with out
                this one so lets silently move on.

                see; CSC 3985, email 52189

                fprintf( stderr, "[%s][%d][WARNING] Failed to get value for attribute (%s).\n", __FILE__, __LINE__, pszEntryName );
            */    
        }

        RESTORE_MODE();

        pszEntryName += strlen( pszEntryName ) + 1;
    } /* while */

    if ((nNegatives|nPositives) != 0)
    {
        char tmp[24];

        nOption= nOption & ~nNegatives;
        nOption= nOption | nPositives;

        sprintf(tmp, "%lu", nOption);

        /* Better safe */
        if (pDataSource->pszOPTION != NULL)
        {
          _global_free(pDataSource->pszOPTION);
        }

        pDataSource->pszOPTION= (char *)_global_strdup(tmp);
    }

    /* try harder to get the friendly driver name */
    if ( !pDataSource->pszDRIVER )
    {
        if ( SQLGetPrivateProfileString( MYODBCUTIL_ODBCINI_HEADER_SECTION, "", "", szEntryNames, sizeof( szEntryNames ) - 1, MYODBCUTIL_ODBC_INI ) < 1 )
        {
          return FALSE;
        }

        RESTORE_MODE();

        pszEntryName = szEntryNames;
        while ( *pszEntryName )
        {
            *szValue = '\0';
            if ( SQLGetPrivateProfileString( MYODBCUTIL_ODBCINI_HEADER_SECTION, pszEntryName, "", szValue, sizeof( szValue ) - 1, MYODBCUTIL_ODBC_INI ) > 0 )
            {
                if ( strcasecmp( pszEntryName, pszDSN ) == 0 )
                {
                    pDataSource->pszDRIVER = _global_strdup( szValue );
                }
            }
            RESTORE_MODE();
            pszEntryName += strlen( pszEntryName ) + 1;
        }
    }

#if defined(WIN32)
    /*!
        XP

        Some people have reported that the friendly driver name is never found so...
    */
    if ( !pDataSource->pszDRIVER )
        pDataSource->pszDRIVER = _global_strdup(MYODBCINST_DRIVER_NAME);
#endif

#if defined(WIN32)
    /* ALWAYS restore mode */
    SQLSetConfigMode( nMode );
#endif

    return TRUE;
}
