/*
 * UTILITY.C
 *
 * Utility routines for functions inside OLESTD.LIB
 *
 *  General:
 *  ----------------------
 *  HourGlassOn             Displays the hourglass
 *  HourGlassOff            Hides the hourglass
 *
 *  Misc Tools:
 *  ----------------------
 *  Browse                  Displays the "File..." or "Browse..." dialog.
 *  ReplaceCharWithNull     Used to form filter strings for Browse.
 *  ErrorWithFile           Creates an error message with embedded filename
 *  OpenFileError           Give error message for OpenFile error return
 *  ChopText                Chop a file path to fit within a specified width
 *  DoesFileExist           Checks if file is valid
 *
 *  Registration Database:
 *  ----------------------
 *  HIconFromClass          Extracts the first icon in a class's server path
 *  FServerFromClass        Retrieves the server path for a class name (fast)
 *  UClassFromDescription   Finds the classname given a description (slow)
 *  UDescriptionFromClass   Retrieves the description for a class name (fast)
 *  FGetVerb                Retrieves a specific verb for a class (fast)
 *
 *
 * Copyright (c)1992-1996 Microsoft Corporation, All Right Reserved
 */

#define STRICT  1
#include "olestd.h"
#include <stdlib.h>
#include <commdlg.h>
#include <memory.h>
#include <cderr.h>
#include "common.h"
#include "utility.h"

#ifdef WIN32
/* 1/23/94 - define LINK_COMMDLG to avoid LoadLibrary("commdlg.dll") */
#define LINK_COMMDLG
#endif

OLEDBGDATA

/*
 * HourGlassOn
 *
 * Purpose:
 *  Shows the hourglass cursor returning the last cursor in use.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HCURSOR         Cursor in use prior to showing the hourglass.
 */

HCURSOR WINAPI HourGlassOn(void)
   {
   HCURSOR     hCur;

   hCur=SetCursor(LoadCursor(NULL, IDC_WAIT));
   ShowCursor(TRUE);

   return hCur;
   }



/*
 * HourGlassOff
 *
 * Purpose:
 *  Turns off the hourglass restoring it to a previous cursor.
 *
 * Parameters:
 *  hCur            HCURSOR as returned from HourGlassOn
 *
 * Return Value:
 *  None
 */

void WINAPI HourGlassOff(HCURSOR hCur)
   {
   ShowCursor(FALSE);
   SetCursor(hCur);
   return;
   }




/*
 * Browse
 *
 * Purpose:
 *  Displays the standard GetOpenFileName dialog with the title of
 *  "Browse."  The types listed in this dialog are controlled through
 *  iFilterString.  If it's zero, then the types are filled with "*.*"
 *  Otherwise that string is loaded from resources and used.
 *
 * Parameters:
 *  hWndOwner       HWND owning the dialog
 *  lpszFile        LPSTR specifying the initial file and the buffer in
 *                  which to return the selected file.  If there is no
 *                  initial file the first character of this string should
 *                  be NULL.
 *  lpszInitialDir  LPSTR specifying the initial directory.  If none is to
 *                  set (ie, the cwd should be used), then this parameter
 *                  should be NULL.
 *  cchFile         UINT length of pszFile
 *  iFilterString   UINT index into the stringtable for the filter string.
 *  dwOfnFlags      DWORD flags to OR with OFN_HIDEREADONLY
 *
 * Return Value:
 *  BOOL            TRUE if the user selected a file and pressed OK.
 *                  FALSE otherwise, such as on pressing Cancel.
 */

BOOL WINAPI Browse(HWND hWndOwner, LPSTR lpszFile, LPSTR lpszInitialDir, UINT cchFile, UINT iFilterString, DWORD dwOfnFlags)
   {
      UINT           cch;
      char           szFilters[256];
      OPENFILENAME   ofn;
      BOOL           fStatus;
      DWORD          dwError;
      char            szDlgTitle[128];  // that should be big enough
#if !defined( LINK_COMMDLG )
      HINSTANCE hinstCommDlg = NULL;
      typedef BOOL (WINAPI* LPFNGETOPENFILENAME) (OPENFILENAME FAR*);
      LPFNGETOPENFILENAME lpfnGetOpenFileName;
      typedef DWORD (WINAPI* LPFNCOMMDLGEXTENDEDERROR) (void);
      LPFNCOMMDLGEXTENDEDERROR lpfnCommDlgExtendedError;
#endif

   if (NULL==lpszFile || 0==cchFile)
      return FALSE;

   /*
    * REVIEW:  Exact contents of the filter combobox is TBD.  One idea
    * is to take all the extensions in the RegDB and place them in here
    * with the descriptive class name associate with them.  This has the
    * extra step of finding all extensions of the same class handler and
    * building one extension string for all of them.  Can get messy quick.
    * UI demo has only *.* which we do for now.
    */

   if (0!=iFilterString)
      cch=LoadString(ghInst, iFilterString, /*(LPSTR)*/szFilters, sizeof(szFilters));
   else
      {
      szFilters[0]=0;
      cch=1;
      }

   if (0==cch)
      return FALSE;

   ReplaceCharWithNull(szFilters, szFilters[cch-1]);

   //Prior string must also be initialized, if there is one.
   _fmemset((LPOPENFILENAME)&ofn, 0, sizeof(ofn));
   ofn.lStructSize =sizeof(ofn);
   ofn.hwndOwner   =hWndOwner;
   ofn.lpstrFile   =lpszFile;
   ofn.nMaxFile    =cchFile;
   ofn.lpstrFilter =/*(LPSTR)*/szFilters;
   ofn.nFilterIndex=1;
   if (LoadString(ghInst, (UINT)IDS_BROWSE, /*(LPSTR)*/szDlgTitle, sizeof(szDlgTitle)))
      ofn.lpstrTitle  =/*(LPSTR)*/szDlgTitle;
   ofn.hInstance = ghInst;
   ofn.lpTemplateName = MAKEINTRESOURCE(IDD_FILEOPEN);
   if (NULL != lpszInitialDir)
     ofn.lpstrInitialDir = lpszInitialDir;

   ofn.Flags= OFN_HIDEREADONLY | OFN_ENABLETEMPLATE | (dwOfnFlags) ;

#if !defined( LINK_COMMDLG )
   // Load the COMMDLG.DLL library module
   hinstCommDlg = LoadLibrary("COMMDLG.DLL");
   if (hinstCommDlg <= HINSTANCE_ERROR)   /* load failed */
      return FALSE;

   // Retrieve the address of required functions
   lpfnGetOpenFileName = (LPFNGETOPENFILENAME)
         GetProcAddress(hinstCommDlg, "GetOpenFileName");
   lpfnCommDlgExtendedError = (LPFNCOMMDLGEXTENDEDERROR)
         GetProcAddress(hinstCommDlg, "CommDlgExtendedError");

   if (lpfnGetOpenFileName == NULL || lpfnCommDlgExtendedError == NULL)
      {
      FreeLibrary(hinstCommDlg);
      return FALSE;
      }

   //On success, copy the chosen filename to the static display
   fStatus = (*lpfnGetOpenFileName) ((LPOPENFILENAME)&ofn);
   dwError = (*lpfnCommDlgExtendedError)();

   FreeLibrary(hinstCommDlg);
   return fStatus;
#else
   //On success, copy the chosen filename to the static display
   fStatus = GetOpenFileName((LPOPENFILENAME)&ofn);
   dwError = CommDlgExtendedError();
   return fStatus;
#endif
   }





/*
 * ReplaceCharWithNull
 *
 * Purpose:
 *  Walks a null-terminated string and replaces a given character
 *  with a zero.  Used to turn a single string for file open/save
 *  filters into the appropriate filter string as required by the
 *  common dialog API.
 *
 * Parameters:
 *  psz             LPSTR to the string to process.
 *  ch              int character to replace.
 *
 * Return Value:
 *  int             Number of characters replaced.  -1 if psz is NULL.
 */

int WINAPI ReplaceCharWithNull(LPSTR psz, int ch)
   {
   int             cChanged=-1;

   if (NULL!=psz)
      {
      while (0!=*psz)
         {
         if (ch==*psz)
            {
            *psz=0;
            cChanged++;
            }
         psz++;
         }
      }
   return cChanged;
   }






/*
 * ErrorWithFile
 *
 * Purpose:
 *  Displays a message box built from a stringtable string containing
 *  one %s as a placeholder for a filename and from a string of the
 *  filename to place there.
 *
 * Parameters:
 *  hWnd            HWND owning the message box.  The caption of this
 *                  window is the caption of the message box.
 *  hInst           HINSTANCE from which to draw the idsErr string.
 *  idsErr          UINT identifier of a stringtable string containing
 *                  the error message with a %s.
 *  lpszFile        LPSTR to the filename to include in the message.
 *  uFlags          UINT flags to pass to MessageBox, like MB_OK.
 *
 * Return Value:
 *  int             Return value from MessageBox.
 */

int WINAPI ErrorWithFile(HWND hWnd, HINSTANCE hInst, UINT idsErr
              , LPSTR pszFile, UINT uFlags)
   {
   int             iRet=0;
   HANDLE          hMem;
   const UINT      cb=(2*OLEUI_CCHPATHMAX);
   LPSTR           psz1, psz2, psz3;

   if (NULL==hInst || NULL==pszFile)
      return iRet;

   //Allocate three 2*OLEUI_CCHPATHMAX byte work buffers
   hMem=GlobalAlloc(GHND, (DWORD)(3*cb));

   if (NULL==hMem)
      return iRet;

   psz1=GlobalLock(hMem);
   psz2=psz1+cb;
   psz3=psz2+cb;

   if (0!=LoadString(hInst, idsErr, psz1, cb))
      {
      wsprintf(psz2, psz1, pszFile);

      //Steal the caption of the dialog
      GetWindowText(hWnd, psz3, cb);
      iRet=MessageBox(hWnd, psz2, psz3, uFlags);
      }

   GlobalUnlock(hMem);
   GlobalFree(hMem);
   return iRet;
   }









/*
 * HIconFromClass
 *
 * Purpose:
 *  Given an object class name, finds an associated executable in the
 *  registration database and extracts the first icon from that
 *  executable.  If none is available or the class has no associated
 *  executable, this function returns NULL.
 *
 * Parameters:
 *  pszClass        LPSTR giving the object class to look up.
 *
 * Return Value:
 *  HICON           Handle to the extracted icon if there is a module
 *                  associated to pszClass.  NULL on failure to either
 *                  find the executable or extract and icon.
 */

HICON WINAPI HIconFromClass(LPSTR pszClass)
   {
   HICON           hIcon;
   char            szEXE[OLEUI_CCHPATHMAX];
   UINT            Index;
   CLSID           clsid;
   OLECHAR         szUniStr[256];

   if (NULL==pszClass)
      return NULL;

   A2W (pszClass, szUniStr, 256);
   CLSIDFromString(szUniStr, &clsid);

   if (!FIconFileFromClass((REFCLSID)&clsid, szEXE, sizeof(szEXE), &Index))
      return NULL;

   hIcon=ExtractIcon(ghInst, szEXE, Index);

   if ((HICON)32 > hIcon)
      hIcon=NULL;

   return hIcon;
   }





/*
 * FServerFromClass
 *
 * Purpose:
 *  Looks up the classname in the registration database and retrieves
 *  the name under CLSID\[Local|InProc]Server.
 *
 * Parameters:
 *  pszClass        LPSTR to the classname to look up.
 *  pszEXE          LPSTR at which to store the server name
 *  cch             UINT size of pszEXE
 *
 * Return Value:
 *  BOOL            TRUE if one or more characters were loaded into pszEXE.
 *                  FALSE otherwise.
 */

BOOL WINAPI FServerFromClass(LPSTR pszClass, LPSTR pszEXE, UINT cch)
{

   DWORD       dw;
   LONG        lRet;
   HKEY        hKey;

   if (NULL==pszClass || NULL==pszEXE || 0==cch)
      return FALSE;

   /*
    * We have to go walking in the registration database under the
    * classname, so we first open the classname key and then check
    * under "\\LocalServer" to get the .EXE.
    */

   //Open up the class key
   lRet=RegOpenKey(HKEY_CLASSES_ROOT, pszClass, &hKey);

   if ((LONG)ERROR_SUCCESS!=lRet)
      return FALSE;

   //Get the executable path.
   dw=(DWORD)cch;
#ifdef WIN32
   lRet=RegQueryValue(hKey, "LocalServer32", pszEXE, &dw);
#else
   lRet=RegQueryValue(hKey, "LocalServer", pszEXE, &dw);
#endif

   if ((LONG)ERROR_SUCCESS!=lRet)
        {
        //Try InprocServer
#ifdef WIN32
        lRet=RegQueryValue(hKey, "InProcServer32", pszEXE, &dw);
#else
        lRet=RegQueryValue(hKey, "InProcServer32", pszEXE, &dw);
#endif
        }

   RegCloseKey(hKey);

   return ((ERROR_SUCCESS == lRet) && (dw > 0));
}



/*
 * UClassFromDescription
 *
 * Purpose:
 *  Looks up the actual OLE class name in the registration database
 *  for the given descriptive name chosen from a listbox.
 *
 * Parameters:
 *  psz             LPSTR to the descriptive name.
 *  pszClass        LPSTR in which to store the class name.
 *  cb              UINT maximum length of pszClass.
 *
 * Return Value:
 *  UINT            Number of characters copied to pszClass.  0 on failure.
 */

UINT WINAPI UClassFromDescription(LPSTR psz, LPSTR pszClass, UINT cb)
   {
   DWORD           dw;
   HKEY            hKey;
   char            szClass[OLEUI_CCHKEYMAX];
   LONG            lRet;
   UINT            i;

   //Open up the root key.
   lRet=RegOpenKey(HKEY_CLASSES_ROOT, NULL, &hKey);

   if ((LONG)ERROR_SUCCESS!=lRet)
      return 0;

   i=0;
   lRet=RegEnumKey(hKey, i++, szClass, OLEUI_CCHKEYMAX);

   //Walk the available keys
   while ((LONG)ERROR_SUCCESS==lRet)
      {
      dw=(DWORD)cb;
      lRet=RegQueryValue(hKey, szClass, pszClass, &dw);

      //Check if the description matches the one just enumerated
      if ((LONG)ERROR_SUCCESS==lRet)
         {
         if (!lstrcmp(pszClass, psz))
            break;
         }

      //Continue with the next key.
      lRet=RegEnumKey(hKey, i++, szClass, OLEUI_CCHKEYMAX);
      }

   //If we found it, copy to the return buffer
   if ((LONG)ERROR_SUCCESS==lRet)
      lstrcpy(pszClass, szClass);
   else
      dw=0L;

   RegCloseKey(hKey);
   return (UINT)dw;
   }








/*
 * UDescriptionFromClass
 *
 * Purpose:
 *  Looks up the actual OLE descriptive name name in the registration
 *  database for the given class name.
 *
 * Parameters:
 *  pszClass        LPSTR to the class name.
 *  psz             LPSTR in which to store the descriptive name.
 *  cb              UINT maximum length of psz.
 *
 * Return Value:
 *  UINT            Number of characters copied to pszClass.  0 on failure.
 */

UINT WINAPI UDescriptionFromClass(LPSTR pszClass, LPSTR psz, UINT cb)
   {
   DWORD           dw;
   HKEY            hKey;
   LONG            lRet;

   if (NULL==pszClass || NULL==psz)
      return 0;

   //Open up the root key.
   lRet=RegOpenKey(HKEY_CLASSES_ROOT, NULL, &hKey);

   if ((LONG)ERROR_SUCCESS!=lRet)
      return 0;

   //Get the descriptive name using the class name.
   dw=(DWORD)cb;
   lRet=RegQueryValue(hKey, pszClass, psz, &dw);

   RegCloseKey(hKey);

   psz+=lstrlen(psz)+1;
   *psz=0;

   if ((LONG)ERROR_SUCCESS!=lRet)
      return 0;

   return (UINT)dw;
   }



// returns width of line of text. this is a support routine for ChopText
static LONG GetTextWSize(HDC hDC, LPSTR lpsz)
{
   SIZE size;

   if (GetTextExtentPoint(hDC, lpsz, lstrlen(lpsz), (LPSIZE)&size))
      return size.cx;
   else {
      return 0;
   }
}


/*
 * ChopText
 *
 * Purpose:
 *  Parse a string (pathname) and convert it to be within a specified
 *  length by chopping the least significant part
 *
 * Parameters:
 *  hWnd            window handle in which the string resides
 *  nWidth          max width of string in pixels
 *                  use width of hWnd if zero
 *  lpch            pointer to beginning of the string
 *
 * Return Value:
 *  pointer to the modified string
 */
LPSTR WINAPI ChopText(HWND hWnd, int nWidth, LPSTR lpch)
{
#define PREFIX_SIZE    7 + 1
#define PREFIX_FORMAT "%c%c%c...\\"

   char    szPrefix[PREFIX_SIZE];
   BOOL    fDone = FALSE;
   int     i;
   RECT    rc;
   HDC     hdc;
   HFONT   hfont;
   HFONT   hfontOld = NULL;

   if (!hWnd || !lpch)
      return NULL;

   /* Get length of static field. */
   if (!nWidth) {
      GetClientRect(hWnd, (LPRECT)&rc);
      nWidth = rc.right - rc.left;
   }

   /* Set up DC appropriately for the static control */
   hdc = GetDC(hWnd);
   hfont = (HFONT)SendMessage(hWnd, WM_GETFONT, 0, 0L);

   if (NULL != hfont)   // WM_GETFONT returns NULL if window uses system font
      hfontOld = SelectObject(hdc, hfont);

   /* check horizontal extent of string */
   if (GetTextWSize(hdc, lpch) > nWidth) {

      /* string is too long to fit in static control; chop it */
      /* set up new prefix & determine remaining space in control */
      wsprintf(/*(LPSTR)*/ szPrefix, PREFIX_FORMAT, lpch[0], lpch[1], lpch[2]);
      nWidth -= (int)GetTextWSize(hdc, /*(LPSTR)*/ szPrefix);

      /*
      ** advance a directory at a time until the remainder of the
      ** string fits into the static control after the "x:\...\" prefix
      */
      while (!fDone) {

#ifdef DBCS
         while (*lpch && (*lpch != '\\'))
            lpch = AnsiNext(lpch);
         if (*lpch)
            lpch = AnsiNext(lpch);
#else
         while (*lpch && (*lpch++ != '\\'));
#endif

         if (!*lpch || GetTextWSize(hdc, lpch) <= nWidth) {
            if (!*lpch)
               /*
               ** Nothing could fit after the prefix; remove the
               ** final "\" from the prefix
               */
               szPrefix[lstrlen(/*(LPSTR)*/ szPrefix) - 1] = 0;

               /* rest or string fits -- stick prefix on front */
               for (i = lstrlen(/*(LPSTR)*/ szPrefix) - 1; i >= 0; --i)
                  *--lpch = szPrefix[i];
               fDone = TRUE;
         }
      }
   }

   if (NULL != hfont)
     SelectObject(hdc, hfontOld);
   ReleaseDC(hWnd, hdc);

   return(lpch);

#undef PREFIX_SIZE
#undef PREFIX_FORMAT
}


/*
 * OpenFileError
 *
 * Purpose:
 *  display message for error returned from OpenFile
 *
 * Parameters:
 *  hDlg            HWND of the dialog.
 *  nErrCode        UINT error code returned in OFSTRUCT passed to OpenFile
 *  lpszFile        LPSTR file name passed to OpenFile
 *
 * Return Value:
 *  None
 */
void WINAPI OpenFileError(HWND hDlg, UINT nErrCode, LPSTR lpszFile)
{
   switch (nErrCode) {
      case 0x0005:    // Access denied
         ErrorWithFile(hDlg, ghInst, (UINT)IDS_CIFILEACCESS, lpszFile, MB_OK);
         break;

      case 0x0020:    // Sharing violation
         ErrorWithFile(hDlg, ghInst, (UINT)IDS_CIFILESHARE, lpszFile, MB_OK);
         break;

      case 0x0002:    // File not found
      case 0x0003:    // Path not found
         ErrorWithFile(hDlg, ghInst, (UINT)IDS_CIINVALIDFILE, lpszFile, MB_OK);
         break;

      default:
         ErrorWithFile(hDlg, ghInst, (UINT)IDS_CIFILEOPENFAIL, lpszFile, MB_OK);
         break;
   }
}

#define chSpace        ' '
#define chPeriod       '.'
#define PARSE_EMPTYSTRING   -1
#define PARSE_INVALIDDRIVE  -2
#define PARSE_INVALIDPERIOD -3
#define PARSE_INVALIDDIRCHAR    -4
#define PARSE_INVALIDCHAR   -5
#define PARSE_WILDCARDINDIR -6
#define PARSE_INVALIDNETPATH    -7
#define PARSE_INVALIDSPACE  -8
#define PARSE_EXTENTIONTOOLONG  -9
#define PARSE_DIRECTORYNAME -10
#define PARSE_FILETOOLONG   -11

/*---------------------------------------------------------------------------
 * ParseFile
 * Purpose:  Determine if the filename is a legal DOS name
 * Input:    Long pointer to a SINGLE file name
 *           Circumstance checked:
 *           1) Valid as directory name, but not as file name
 *           2) Empty String
 *           3) Illegal Drive label
 *           4) Period in invalid location (in extention, 1st in file name)
 *           5) Missing directory character
 *           6) Illegal character
 *           7) Wildcard in directory name
 *           8) Double slash beyond 1st 2 characters
 *           9) Space character in the middle of the name (trailing spaces OK)
 *          10) Filename greater than 8 characters
 *          11) Extention greater than 3 characters
 * Notes:
 *   Filename length is NOT checked.
 *   Valid filenames will have leading spaces, trailing spaces and
 *     terminating period stripped in place.
 *
 * Returns:  If valid, LOWORD is byte offset to filename
 *                     HIWORD is byte offset to extention
 *                            if string ends with period, 0
 *                            if no extention is given, string length
 *           If invalid, LOWORD is error code suggesting problem (< 0)
 *                       HIWORD is approximate offset where problem found
 *                       Note that this may be beyond the offending character
 *--------------------------------------------------------------------------*/

static long ParseFile(LPSTR lpstrFileName)
{
  short nFile, nExt, nFileOffset, nExtOffset;
  BOOL bExt;
  BOOL bWildcard;
  short nNetwork = 0;
  BOOL  bUNCPath = FALSE;
  LPSTR lpstr = lpstrFileName;

/* Strip off initial white space.  Note that TAB is not checked */
/* because it cannot be received out of a standard edit control */
/* 30 January 1991  clarkc                                      */
  while (*lpstr == chSpace)
     lpstr++;

  if (!*lpstr)
   {
     nFileOffset = PARSE_EMPTYSTRING;
     goto FAILURE;
   }

  if (lpstr != lpstrFileName)
   {
     lstrcpy(lpstrFileName, lpstr);
     lpstr = lpstrFileName;
   }

  if (*AnsiNext(lpstr) == ':')
   {
     char cDrive = (*lpstr | (BYTE) 0x20);  /* make lowercase */

/* This does not test if the drive exists, only if it's legal */
     if ((cDrive < 'a') || (cDrive > 'z'))
      {
        nFileOffset = PARSE_INVALIDDRIVE;
        goto FAILURE;
      }
     lpstr = AnsiNext(AnsiNext(lpstr));
   }

  if ((*lpstr == '\\') || (*lpstr == '/'))
   {
     if (*++lpstr == chPeriod)               /* cannot have c:\. */
      {
        if ((*++lpstr != '\\') && (*lpstr != '/'))   /* unless it's stupid */
         {
           if (!*lpstr)        /* it's the root directory */
              goto MustBeDir;

           nFileOffset = PARSE_INVALIDPERIOD;
           goto FAILURE;
         }
        else
           ++lpstr;   /* it's saying top directory (again), thus allowed */
      }
     else if ((*lpstr == '\\') && (*(lpstr-1) == '\\'))
      {
/* It seems that for a full network path, whether a drive is declared or
 * not is insignificant, though if a drive is given, it must be valid
 * (hence the code above should remain there).
 * 13 February 1991           clarkc
 */
        ++lpstr;            /* ...since it's the first slash, 2 are allowed */
        nNetwork = -1;      /* Must receive server and share to be real     */
        bUNCPath = TRUE;    /* No wildcards allowed if UNC name             */
      }
     else if (*lpstr == '/')
      {
        nFileOffset = PARSE_INVALIDDIRCHAR;
        goto FAILURE;
      }
   }
  else if (*lpstr == chPeriod)
   {
     if (*++lpstr == chPeriod)  /* Is this up one directory? */
        ++lpstr;
     if (!*lpstr)
        goto MustBeDir;
     if ((*lpstr != '\\') && (*lpstr != '/'))
      {
        nFileOffset = PARSE_INVALIDPERIOD;
        goto FAILURE;
      }
     else
        ++lpstr;   /* it's saying directory, thus allowed */
   }

  if (!*lpstr)
   {
     goto MustBeDir;
   }

/* Should point to first char in 8.3 filename by now */
  nFileOffset = nExtOffset = nFile = nExt = 0;
  bWildcard = bExt = FALSE;
  while (*lpstr)
   {
/*
 *  The next comparison MUST be unsigned to allow for extended characters!
 *  21 Feb 1991   clarkc
 */
     if (*lpstr < chSpace)
      {
        nFileOffset = PARSE_INVALIDCHAR;
        goto FAILURE;
      }
     switch (*lpstr)
      {
        case '"':             /* All invalid */
        case '+':
        case ',':
        case ':':
        case ';':
        case '<':
        case '=':
        case '>':
        case '[':
        case ']':
        case '|':
         {
           nFileOffset = PARSE_INVALIDCHAR;
           goto FAILURE;
         }

        case '\\':      /* Subdirectory indicators */
        case '/':
         nNetwork++;
         if (bWildcard)
           {
            nFileOffset = PARSE_WILDCARDINDIR;
            goto FAILURE;
           }

         else if (nFile == 0)        /* can't have 2 in a row */
           {
            nFileOffset = PARSE_INVALIDDIRCHAR;
            goto FAILURE;
           }
         else
           {                         /* reset flags */
            ++lpstr;
            if (!nNetwork && !*lpstr)
              {
               nFileOffset = PARSE_INVALIDNETPATH;
               goto FAILURE;
              }
            nFile = nExt = 0;
            bExt = FALSE;
           }
         break;

        case chSpace:
         {
           LPSTR lpSpace = lpstr;

           *lpSpace = '\0';
           while (*++lpSpace)
            {
              if (*lpSpace != chSpace)
               {
                 *lpstr = chSpace;        /* Reset string, abandon ship */
                 nFileOffset = PARSE_INVALIDSPACE;
                 goto FAILURE;
               }
            }
         }
         break;

        case chPeriod:
         if (nFile == 0)
           {
            if (*++lpstr == chPeriod)
               ++lpstr;
            if (!*lpstr)
               goto MustBeDir;

            if ((*lpstr != '\\') && (*lpstr != '/'))
              {
               nFileOffset = PARSE_INVALIDPERIOD;
               goto FAILURE;
              }

            ++lpstr;              /* Flags are already set */
           }
         else if (bExt)
           {
            nFileOffset = PARSE_INVALIDPERIOD;  /* can't have one in ext */
            goto FAILURE;
           }
         else
           {
            nExtOffset = 0;
            ++lpstr;
            bExt = TRUE;
           }
         break;

        case '*':
        case '?':
         if (bUNCPath)
           {
            nFileOffset = PARSE_INVALIDNETPATH;
            goto FAILURE;
           }
         bWildcard = TRUE;
/* Fall through to normal character processing */

        default:
         if (bExt)
           {
            if (++nExt == 1)
               nExtOffset = lpstr - lpstrFileName;
            else if (nExt > 3)
              {
               nFileOffset = PARSE_EXTENTIONTOOLONG;
               goto FAILURE;
              }
            if ((nNetwork == -1) && (nFile + nExt > 11))
              {
               nFileOffset = PARSE_INVALIDNETPATH;
               goto FAILURE;
              }
           }
         else if (++nFile == 1)
            nFileOffset = lpstr - lpstrFileName;
         else if (nFile > 8)
           {
            /* If it's a server name, it can have 11 characters */
            if (nNetwork != -1)
              {
               nFileOffset = PARSE_FILETOOLONG;
               goto FAILURE;
              }
            else if (nFile > 11)
              {
               nFileOffset = PARSE_INVALIDNETPATH;
               goto FAILURE;
              }
           }

         lpstr = AnsiNext(lpstr);
         break;
      }
   }

/* Did we start with a double backslash but not have any more slashes? */
  if (nNetwork == -1)
   {
     nFileOffset = PARSE_INVALIDNETPATH;
     goto FAILURE;
   }

  if (!nFile)
   {
MustBeDir:
     nFileOffset = PARSE_DIRECTORYNAME;
     goto FAILURE;
   }

  if ((*(lpstr - 1) == chPeriod) &&          /* if true, no extention wanted */
           (*AnsiNext(lpstr-2) == chPeriod))
     *(lpstr - 1) = '\0';               /* Remove terminating period   */
  else if (!nExt)
FAILURE:
     nExtOffset = lpstr - lpstrFileName;

  return(MAKELONG(nFileOffset, nExtOffset));
}


/*
 * DoesFileExist
 *
 * Purpose:
 *  Determines if a file path exists
 *
 * Parameters:
 *  lpszFile        LPSTR - file name
 *  lpOpenBuf       OFSTRUCT FAR* - points to the OFSTRUCT structure that
 *                      will receive information about the file when the
 *                      file is first opened. this field is filled by the
 *                      Windows OpenFile API.
 *
 * Return Value:
 *  HFILE   HFILE_ERROR - file does NOT exist
 *          file handle (as returned from OpenFile) - file exists
 */
HFILE WINAPI DoesFileExist(LPSTR lpszFile, OFSTRUCT FAR* lpOpenBuf)
{
   long        nRet;
   int         i;
   static char *arrIllegalNames[] = {
      "LPT1",
      "LPT2",
      "LPT3",
      "COM1",
      "COM2",
      "COM3",
      "COM4",
      "CON",
      "AUX",
      "PRN"
   };

   // Check if file name is syntactically correct.
   //   (OpenFile sometimes crashes if path is not syntactically correct)
   nRet = ParseFile(lpszFile);
   if (LOWORD(nRet) < 0)
      goto error;

   // Check is the name is an illegal name (eg. the name of a device)
   for (i=0; i < (sizeof(arrIllegalNames)/sizeof(arrIllegalNames[0])); i++) {
      if (lstrcmpi(lpszFile, arrIllegalNames[i])==0)
         goto error; // illegal name FOUND
   }
   return OpenFile(lpszFile, lpOpenBuf, OF_EXIST);

error:
   _fmemset(lpOpenBuf, 0, sizeof(OFSTRUCT));
   lpOpenBuf->nErrCode = 0x0002;   // File not found
   return HFILE_ERROR;
}
