/*
 * Infparse.c - Setup.inf parsing code.
 * Clark Cyr, Mike Colee, Todd Laney
 * Copyright (C) Microsoft, 1989
 * March 15, 1989
 *
 *  Modification History:
 *
 *  3/15/89  CC  Clark wrote this code for control Panel. This is windows
 *               code.
 *
 *  3/20/89  MC  Decided this code would work for Dos and windows portion
 *               of setup. take out windows specifc stuff like local alloc's
 *               and dialog stuff. Replace it with standard C run time calls.
 *
 *  3/24/89  Toddla TOTAL rewrite! nothing is the same any more.
 *
 *  6/29/89  MC fixed getprofilestring func to not strip quotes if more
 *              than one field exists.
 */

#include <windows.h>
#include <mmsystem.h>
#include <string.h>
#include <stdlib.h>
#include "drivers.h"
#include "sulib.h"

/*** hack.  to avoid realloc problems we make READ_BUFSIZE
            as big as the inf file, thus avoiding any reallocs */

#define READ_BUFSIZE    27000   /* size of inf buffer */
#define TMP_BUFSIZE     1024    /* size of temp reads */

#define EOF        0x1A
#define ISEOL(c)     ((c) == '\n' || (c) == '\r' || (c) == '\0' || (c) == EOF)
#define ISSEP(c)   ((c) == '='  || (c) == ',')
#define ISWHITE(c) ((c) == ' '  || (c) == '\t' || (c) == '\n' || (c) == '\r')
#define ISNOISE(c) ((c) == '"')

#define QUOTE   '"'
#define EQUAL   '='

PINF   pinfDefault = NULL;

static PSTR    pBuf;
static PINF    pInf;
static UINT    iBuf;
static UINT    iInf;

/* Globaly used pointers to non-translatable text strings. */

extern char *pszPATH;

/* Local prototypes */

BOOL multifields(PINF);


static char GETC(int fh)
{
    register UINT n;

    if (!pBuf)
        return EOF;

    n = iBuf % TMP_BUFSIZE;

    if (n == 0)
    {
       _lread(fh,pBuf,TMP_BUFSIZE);
    }
    iBuf++;
    return pBuf[n];
}

static void PUTC(char c)
{
    if (!pInf)
        return;

    pInf[iInf++] = c;
}

static void MODIFYC(char c)
{
    if (!pInf)
        return;

    pInf[iInf++ - 1] = c;
}

static char LASTC(void) {
    if (!pInf) return ' ';

    if (iInf == 0) {
        return ' ';
    }
    return pInf[iInf - 1];
}

/* int infLoadFile()      Load a entire INF file into memory
 *                        comments are removed, each line is terminated
 *                        by a \0 each section is terminated by a \0\0
 *                        ONLY spaces inside of " " are preserved
 *                        the end of file is marked with a ^Z
 *
 *   RETURNS:  A pointer to a block of memory containg file, NULL if failure
 *
 */
PINF infLoadFile(int fh)
{
    UINT    len;
    char    c;
    BOOL    fQuote = FALSE;
    BOOL    inSectionName = FALSE;

    if (fh == -1)
      return NULL;

    len = (UINT)_llseek(fh,0L,SEEK_END);

    _llseek(fh,0L,SEEK_SET);

    iBuf = 0;
    iInf = 0;
    pBuf = ALLOC(TMP_BUFSIZE);          // temp buffer
    if (!pBuf)
        return NULL;
    pInf = FALLOC(len);                 // destination, at least as big as file
    if (!pInf) {
        FREE((HANDLE)pBuf);
        return NULL;
    }

    while (iBuf < len)
    {
        c = GETC(fh);
loop:
        if (iBuf >= len)
            break;

        switch (c)
        {
            case '[':
                inSectionName = TRUE;
                PUTC(c);
                break;

            case ']':
                if (inSectionName) {
                    if (LASTC() == ' ') {
                        MODIFYC(c);
                    } else {
                        PUTC(c);
                    }
                    inSectionName = FALSE;
                } else {
                    PUTC(c);
                }
                break;

            case '\r':      /* ignore '\r' */
                break;

            case '\n':
                for (; ISWHITE(c); c = GETC(fh))
                    ;
                if (c != ';')
                    PUTC(0);    /* all lines end in a \0 */

                if (c == '[') {
                    PUTC(0);    /* all sections end with \0\0 */
                }

                fQuote = FALSE;
                goto loop;
                break;

            case '\t':
            case ' ':
                if (inSectionName) {
                    if (LASTC() != ' ' && LASTC() != ']')
                        PUTC(' ');
                } else {
                    if (fQuote)
                        PUTC(c);
                }
                break;

            case '"':
                fQuote = !fQuote;
                PUTC(c);
                break;

            case ';':
                for (; !ISEOL(c); c = GETC(fh))
                    ;
                goto loop;
                break;

            default:
                PUTC(c);
                break;
        }
    }

    PUTC(0);
    PUTC(0);
    PUTC(EOF);
    FREE((HANDLE)pBuf);

    // try to shrink this block


    // just leave pInf it's original size.  don't bother shrinking it

    return pInf;
}

/* PINF FAR PASCAL infOpen()
 *   PARAMETERS
 *           szInf - path to inf file to open and load
 *
 *   RETURNS:  A pointer to the parsed inf file if successful,
 *             Null pointer in the case of failure.
 *
 *   ENTER:
 *   EXIT:   To caller
 */

PINF infOpen(LPSTR szInf)
{
    char    szBuf[MAX_PATH];
    int     fh;
    PINF    pinf;

    fh = -1;

    if (szInf == NULL)
        szInf = szSetupInf;

    /*
     * Next try to open passed parameter as is. For Dos half.
     */
    if (fh == -1)
    {
        fh = _lopen(szInf, OF_READ);
    }
    /*
     * Next try destination path\system32. for win half.
     */
    if (fh == -1) {
        lstrcpy(szBuf, szSetupPath);
      catpath(szBuf, "system32");
      catpath(szBuf, szInf);
      fh = _lopen(szBuf, OF_READ);
    }
    /*
     * Next try destination path. for initial setup.
     */
    if (fh == -1) {
        lstrcpy(szBuf, szSetupPath);
      catpath(szBuf, szInf);
      fh = _lopen(szBuf, OF_READ);
    }
    if (fh != -1)
    {
        pinf = infLoadFile(fh);
        _lclose(fh);

        if (pinf && !pinfDefault)
            pinfDefault = pinf;

        return pinf;
    }
    return NULL;
}

/* void FAR PASCAL infClose(PINF pinf)
 *
 *   ENTER:
 *   EXIT:   To caller
 */
void infClose(PINF pinf)
{
    if (pinf == NULL)
        pinf = pinfDefault;

    if (pinf != NULL)
    {
        FFREE(pinf);

        if (pinf == pinfDefault)
            pinfDefault = NULL;
    }
}


/* FindSection  locates a section in Setup.Inf.  Sections are
 *               assumed to be delimited by a '[' as the first
 *               character on a line.
 *
 * Arguments:   pInf     Pointer to SETUP.INF buffer
 *              pszSect  LPSTR to section name
 *
 * Return:      UINT file position of the first line in the section
 *               0 if section not found
 */

UINT FindSection(PINF pInf, LPSTR pszSect)
{
    BOOL        fFound = FALSE;
    int         nLen = lstrlen(pszSect);
    PINF        pch;

    if (!pInf)
        return 0;

    pch = pInf;
    while (!fFound && *pch != EOF)
    {
        if (*pch++ == '[')
        {
            fFound = !strnicmp(pszSect, pch, nLen) && pch[nLen] == ']';
        }

        /*
         * go to the next line, dont forget to skip over \0 and \0\0
         */
        while (*pch != EOF && *pch != '\0')
            pch++;

        while (*pch == 0)
            pch++;
    }
    return((fFound && *pch != '[' && *pch != EOF) ? pch - pInf : 0);
}

/* char* fnGetDataString(npszData,szDataStr)
 *
 * Called by functions that read sections of information from setup.inf
 * to obtain strings that are set equal to keywords. Example:
 *
 * welcome=("Hello There")
 *
 * This function will return a pointer to the null terminated string
 * "Hello There".
 *
 * ENTRY:
 *
 * npszData    : pointer to entire section taken from setup.inf
 * npszDataStr : pointer to key word to look for (welcome in example above.)
 *
 * EXIT: returns pointer to string if successful, NULL if failure.
 *
 */
BOOL fnGetDataString(PINF npszData, LPSTR szDataStr, LPSTR szBuf)
{
    int len = lstrlen(szDataStr);

    while (npszData)
    {
            if (!strnicmp(npszData,szDataStr,len))  // looking for correct prof.
            {
               npszData += len;            // found !, look past prof str.
               while (ISWHITE(*npszData))  // pull out the stuff.
                       npszData++;
          if (*npszData == EQUAL)     // Now we have what were looking for !
               {
                       npszData++;

             if (!multifields(npszData) )
             {
                while (ISWHITE(*npszData) || ISNOISE(*npszData))
                             npszData++;

                          while (*npszData)
                             *szBuf++ = *npszData++;

                       /*
                        * remove trailing spaces, and those pesky ()'s
                           */

                while (ISWHITE(szBuf[-1]) || ISNOISE(szBuf[-1]))
                             szBuf--;

                          *szBuf = 0;
                          return TRUE;
             }
             else
             {
                while (*npszData)
                   *szBuf++ = *npszData++;
                *szBuf = '\0';
                return TRUE;
             }
               }
       }
       npszData = infNextLine(npszData);
    }
    *szBuf = 0;
    return FALSE;
}

/*  PINF FAR PASCAL infSetDefault(pinf)
 *
 *  Sets the default INF file
 *
 * ENTRY:
 *      pinf            : inf file to be new default
 *
 * EXIT: returns old default
 *
 */
PINF infSetDefault(PINF pinf)
{
    PINF pinfT;

    pinfT = pinfDefault;
    pinfDefault = pinf;
    return pinfT;
}

/*  PINF FAR PASCAL infFindSection(pinf,szSection)
 *
 *  Reads a entire section into memory and returns a pointer to it
 *
 * ENTRY:
 *      pinf            : inf file to search for section
 *      szSection       : section name to read
 *
 * EXIT: returns pointer to section, NULL if error
 *
 */
PINF infFindSection(PINF pinf, LPSTR szSection)
{
    UINT   pos;

    if (pinf == NULL)
        pinf = pinfDefault;

    pos = FindSection(pinf, szSection);
    return pos ? pinf + pos : NULL;
}

/*  BOOL FAR PASCAL infGetProfileString(szSection,szItem,szBuf)
 *
 *  Reads a single string from a section in SETUP.INF
 *
 *  [section]
 *      item = string
 *
 * ENTRY:
 *      szSection       : pointer to section name to read.
 *      szItem          : pointer to item name to read
 *      szBuf           : pointer to a buffer to hold result
 *
 * EXIT: returns TRUE if successful, FALSE if failure.
 *
 */
BOOL infGetProfileString(PINF pinf, LPSTR szSection,LPSTR szItem,LPSTR szBuf)
{
    PINF    pSection;

    pSection = infFindSection(pinf,szSection);
    if (pSection )
        return fnGetDataString(pSection,szItem,szBuf);
    else
        *szBuf = 0;
    return FALSE;
}

/* BOOL FAR PASCAL infParseField(szData,n,szBuf)
 *
 * Given a line from SETUP.INF, will extract the nth field from the string
 * fields are assumed separated by comma's.  Leading and trailing spaces
 * are removed.
 *
 * ENTRY:
 *
 * szData    : pointer to line from SETUP.INF
 * n         : field to extract. ( 1 based )
 *             0 is field before a '=' sign
 * szBuf     : pointer to buffer to hold extracted field
 *
 * EXIT: returns TRUE if successful, FALSE if failure.
 *
 */
BOOL infParseField(PINF szData, int n, LPSTR szBuf)
{
    BOOL    fQuote = FALSE;
    PINF    pch;
    LPSTR   ptr;

    if (!szData || !szBuf)
        return FALSE;

    /*
     * find the first separator
     */
    for (pch=szData; *pch && !ISSEP(*pch); pch++) {
      if ( *pch == QUOTE )
         fQuote = !fQuote;
    }

    if (n == 0 && *pch != '=')
        return FALSE;

    if (n > 0 && *pch == '=' && !fQuote)
        szData = ++pch;

    /*
     *  locate the nth comma, that is not inside of quotes
     */
    fQuote = FALSE;
    while (n > 1)
    {
            while (*szData)
            {
          if (!fQuote && ISSEP(*szData))
                   break;

          if (*szData == QUOTE)
                   fQuote = !fQuote;

               szData++;
            }

            if (!*szData) {
               szBuf[0] = 0;            // make szBuf empty
               return FALSE;
            }

            szData++;
            n--;
    }
    /*
     * now copy the field to szBuf
     */
    while (ISWHITE(*szData))
            szData++;

    fQuote = FALSE;
    ptr = szBuf;                // fill output buffer with this
    while (*szData)
    {
       if (*szData == QUOTE)
               fQuote = !fQuote;
       else if (!fQuote && ISSEP(*szData))
               break;
            else
               *ptr++ = *szData;
            szData++;
    }
    /*
     * remove trailing spaces, and those pesky ()'s
     */
    while ((ptr > szBuf) && (ISWHITE(ptr[-1]) || ISNOISE(ptr[-1])))
            ptr--;

    *ptr = 0;
    return TRUE;
}

/* BOOL multifields(LPSTR npszData);
 *
 * Given a line line from mmdriver.inf that was after a profile
 * string this function will determine if that line has more than one
 * field. ie. Fields are seperated by commas that are NOT contained between
 * quotes.
 *
 * ENYRY:
 *
 * npszData : a line from setup.inf Example "xyz adapter",1:foobar.drv
 *
 * EXIT: This function will return TRUE if the line containes more than
 *       one field, ie the function would return TRUE for the example line
 *       shown above.
 *
 */
BOOL multifields(PINF npszData)
{
   BOOL    fQuote = FALSE;

        while (*npszData)
        {
      if (!fQuote && ISSEP(*npszData))
                   return TRUE;

      if (*npszData == QUOTE)
                   fQuote = !fQuote;

           npszData++;
        }
   return FALSE;
}

/* LPSTR FAR PASCAL infNextLine(sz)
 *
 * Given a line from SETUP.INF, advance to the next line.  will skip past the
 * ending NULL character checking for end of buffer \0\0
 *
 * ENTRY:
 *
 * sz        : pointer to line from a SETUP.INF section
 *
 * EXIT: returns pointer to next line if successful, NULL if failure.
 *
 */
PINF infNextLine(PINF pinf)
{
    if (!pinf)
        return NULL;

    while (*pinf != 0 || *(pinf + 1) == ' ')
        pinf++;

    return *++pinf ? pinf : NULL;
}

/* int FAR PASCAL infLineCount(pinf)
 *
 * Given a section from SETUP.INF, returns the number of lines in the section
 *
 * ENTRY:
 *
 * pinf      : pointer to a section from SETUP.INF
 *
 * EXIT: returns line count
 *
 */
int infLineCount(PINF pinf)
{
    int n = 0;

    for (n=0; pinf; pinf = infNextLine(pinf))
        n++;

    return n;
}