//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// Use of this source code is subject to the terms of the Microsoft shared
// source or premium shared source license agreement under which you licensed
// this source code. If you did not accept the terms of the license agreement,
// you are not authorized to use this source code. For the terms of the license,
// please see the license agreement between you and Microsoft or, if applicable,
// see the SOURCE.RTF on your install media or the root of your tools installation.
// THE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES.
//
/*---------------------------------------------------------------------------*\
 *  module: path.cpp
\*---------------------------------------------------------------------------*/
#include "path.h"


UINT PathGetCharType(TCHAR ch)
/*---------------------------------------------------------------------------*\
 *
\*---------------------------------------------------------------------------*/
{
    switch (ch)
    {
        case TEXT('|'):
        case TEXT('>'):
        case TEXT('<'):
        case TEXT('"'):
#ifdef UNDER_CE
        case TEXT(':'):       // CE doesn't have drives !
#endif
            return GCT_INVALID;

        case TEXT('?'):
        case TEXT('*'):
            return GCT_WILD;

        case TEXT('\\'):      // path separator
        case TEXT('/'):       // path sep
#ifndef UNDER_CE
        case TEXT(':'):       // drive colon
#endif
            return GCT_SEPERATOR;

        case TEXT(';'):
        case TEXT(','):
        case TEXT(' '):
            return GCT_LFNCHAR;     // actually valid in short names
                                // but we want to avoid this
        default:
            if (ch > TEXT(' '))
            {
                return GCT_SHORTCHAR | GCT_LFNCHAR;
            }
            else
            {
                return GCT_INVALID;    // control character
            }
    }

} /* PathGetCharType()
   */

BOOL WINAPI PathIsValidFileName(LPCTSTR pPath)
/*---------------------------------------------------------------------------*\
 *
\*---------------------------------------------------------------------------*/
{
    if (pPath)
    {
        while (*pPath)
        {
            if ( ! (PathGetCharType(*pPath) & (GCT_SHORTCHAR|GCT_LFNCHAR)))
            {
                return FALSE;
            }
            pPath ++;
        }
        return TRUE;
    }

    return FALSE;

} /* PathIsValidFileName()
   */

BOOL WINAPI PathIsValidPath(LPCTSTR pPath)
/*---------------------------------------------------------------------------*\
 *
\*---------------------------------------------------------------------------*/
{
    if (pPath)
    {
        while (*pPath)
        {
            if ( ! (PathGetCharType(*pPath) & (GCT_SHORTCHAR|GCT_LFNCHAR|GCT_SEPERATOR)))
            {
                return FALSE;
            }
            pPath ++;
        }
        return TRUE;
    }

    return FALSE;

} /* PathIsValidPath()
   */

BOOL WINAPI PathIsRemovableDevice(LPCTSTR lpszPath)
/*---------------------------------------------------------------------------*\
 *
\*---------------------------------------------------------------------------*/
{
    BOOL bRet = FALSE;
    TCHAR *psz, szPath[MAX_PATH];

    if (!lpszPath ||
        (lpszPath[0] == L'\0') ||
        FAILED(StringCchCopy(szPath, lengthof(szPath), lpszPath)))
    {
        goto leave;
    }

    psz = szPath;
    // Skip the initial set of backslashes.
    while (*psz == TEXT('\\') || *psz == TEXT('/'))
    {
        psz++;
    }

    // Find the second backslash in the path.
    while (*psz && (*psz != TEXT('\\')) && (*psz != TEXT('/')))
    {
        psz++;
    }

    // Truncate here to leave us with the first component of the path.
    *psz = 0;

    bRet = ((GetFileAttributes(szPath) & FILE_ATTRIBUTE_TEMPORARY) ? TRUE : FALSE);

leave:
    return bRet;
} /* PathIsRemovableDevice()
   */

void WINAPI PathRemoveBlanks(LPTSTR lpszString)
/*---------------------------------------------------------------------------*\
 *
\*---------------------------------------------------------------------------*/
{
    LPTSTR lpszPosn = lpszString;

    /* strip leading blanks */
    while(*lpszPosn == TEXT(' '))
    {
        lpszPosn++;
    }

    if (lpszPosn != lpszString)
    {
        lstrcpy(lpszString, lpszPosn);
    }

    /* strip trailing blanks */
    for (lpszPosn=lpszString; *lpszString; lpszString=CharNext(lpszString))
    {
        if (*lpszString != TEXT(' '))
        {
            lpszPosn = lpszString;
        }
    }

    if (*lpszPosn)
    {
        *CharNext(lpszPosn) = TEXT('\0');
    }

} /* PathRemoveBlanks()
   */

void WINAPI PathRemoveTrailingSlashes(LPTSTR lpszString)
/*---------------------------------------------------------------------------*\
 *
\*---------------------------------------------------------------------------*/
{
    LPTSTR lpszPosn = lpszString;

    if (lstrcmp(lpszString, TEXT("\\")))
    {
        /* strip trailing slash */
        for (lpszPosn=lpszString; *lpszString; lpszString=CharNext(lpszString))
        {
            if (*lpszString != TEXT('\\'))
            {
                lpszPosn = lpszString;
            }
        }

        if (*lpszPosn)
        {
            *CharNext(lpszPosn) = TEXT('\0');
        }
    }

} /* PathRemoveTrailingSlashes()
   */

LPTSTR WINAPI PathGetArgs(LPCTSTR pszPath)
/*---------------------------------------------------------------------------*\
 * returns a pointer to the arguments in a cmd type path or pointer to
 * NULL if no args exist
 *
 * "foo.exe bar.txt"    -> "bar.txt"
 * "foo.exe"            -> ""
 *
 * Spaces in filenames must be quoted.
 * " "A long name.txt" bar.txt " -> "bar.txt"
 *
\*---------------------------------------------------------------------------*/
{
    BOOL fInQuotes = FALSE;

    if (!pszPath)
    {
        return NULL;
    }

    while (*pszPath)
    {
        if (*pszPath == TEXT('"'))
        {
            fInQuotes = !fInQuotes;
        }
        else if (!fInQuotes && *pszPath == TEXT(' '))
        {
            return (LPTSTR)pszPath+1;
        }
        pszPath = CharNext(pszPath);
    }

    return (LPTSTR)pszPath;
}

void WINAPI PathRemoveArgs(LPTSTR pszPath)
/*---------------------------------------------------------------------------*\
 *
\*---------------------------------------------------------------------------*/
{
    if (pszPath)
    {
        LPTSTR pArgs = PathGetArgs(pszPath);
        if (*pArgs)
        {
            *(pArgs - 1) = TEXT('\0');   // clobber the ' '
        }
        else
        {
            // Handle trailing space.
            pArgs = CharPrev(pszPath, pArgs);
            if (*pArgs == TEXT(' '))
            {
                *pArgs = TEXT('\0');
            }
        }
    }
}

void WINAPI PathRemoveQuotesAndArgs(LPTSTR pszPath)
/*---------------------------------------------------------------------------*\
 *
\*---------------------------------------------------------------------------*/
{
    LPTSTR szOrig = pszPath;

    if (!pszPath)
    {
        return;
    }

    if (*pszPath == TEXT('"'))
    {
        pszPath = CharNext(pszPath);
        while(*pszPath && *pszPath != TEXT('"'))
        {
            pszPath = CharNext(pszPath);
        }
        *pszPath = TEXT('\0');
        lstrcpy(szOrig, CharNext(szOrig));
    }
    else
    {
        PathRemoveArgs(pszPath);
    }
}

void WINAPI PathRemoveQuotes(LPTSTR pszPath)
/*---------------------------------------------------------------------------*\
 *
\*---------------------------------------------------------------------------*/
{
    LPTSTR szOrig = pszPath;
    if (!pszPath)
    {
        return;
    }

    if (*pszPath == TEXT('"'))
    {
        pszPath = CharNext(pszPath);
        while(*pszPath && *pszPath != TEXT('"'))
        {
            pszPath = CharNext(pszPath);
        }
        // That's right: If you put a quote in the middle of your
        // string we truncate at that point.
        if (*pszPath == L'"')
        {
            *pszPath = TEXT('\0');
        }
        lstrcpy(szOrig, CharNext(szOrig));
    }
}

// modify lpszPath in place so it fits within dx space (using the
// current font).  the base (file name) of the path is the minimal
// thing that will be left prepended with ellipses
//
// examples:
//      c:\foo\bar\bletch.txt -> c:\foo...\bletch.txt   -> TRUE
//      c:\foo\bar\bletch.txt -> c:...\bletch.txt       -> TRUE
//      c:\foo\bar\bletch.txt -> ...\bletch.txt         -> FALSE
//      relative-path         -> relative-...           -> TRUE
//
// in:
//      hDC         used to get font metrics
//      lpszPath    path to modify (in place)
//      dx          width in pixels
//
// returns:
//      TRUE    path was compacted to fit in dx
//      FALSE   base part didn't fit, the base part of the path was
//              bigger than dx

BOOL WINAPI PathCompactPath(HDC hDC, LPTSTR lpszPath, UINT dx)
/*---------------------------------------------------------------------------*\
 *
\*---------------------------------------------------------------------------*/
{
    int len;
    UINT dxFixed, dxEllipses;
    LPTSTR lpEnd;          /* end of the unfixed string */
    LPTSTR lpFixed;        /* start of text that we always display */
    BOOL bEllipsesIn;
    SIZE sz;
    TCHAR szTemp[MAX_PATH];
    BOOL bRet = TRUE;
    HDC hdcGet = NULL;

    if (!hDC)
    {
        hDC = hdcGet = GetDC(NULL);
    }

    /* Does it already fit? */

    GetTextExtentPoint(hDC, lpszPath, lstrlen(lpszPath), &sz);
    if ((UINT)sz.cx <= dx)
    {
        goto Exit;
    }

    lpFixed = PathFindFileName(lpszPath);
    if (lpFixed != lpszPath)
    {
        lpFixed = CharPrev(lpszPath, lpFixed);  // point at the slash
    }

    /* Save this guy to prevent overlap. */
    _tcsncpy(szTemp, lpFixed, lengthof(szTemp) - 1);
    szTemp[lengthof(szTemp) - 1] = L'\0';

    lpEnd = lpFixed;
    bEllipsesIn = FALSE;

    GetTextExtentPoint(hDC, lpFixed, lstrlen(lpFixed), &sz);
    dxFixed = sz.cx;

    GetTextExtentPoint(hDC, TEXT("..."), 3, &sz);
    dxEllipses = sz.cx;

    // BUGBUG: GetTextExtentEx() or something should let us do this without looping

    if (lpFixed == lpszPath)
    {
        // if we're just doing a file name, just tack on the ellipses at the end
        lpszPath = lpszPath + lstrlen(lpszPath);
        if ((3 + lpszPath - lpFixed) >= MAX_PATH)
        {
            lpszPath = lpFixed + MAX_PATH - 4;
        }

        for (;;)
        {
            lstrcpy(lpszPath, TEXT("..."));
            GetTextExtentPoint(hDC, lpFixed, 3 + lpszPath - lpFixed, &sz);
            if (sz.cx <= (int)dx)
            {
                break;
            }
            lpszPath--;
        }
    }
    else
    {
        for (;;)
        {
            GetTextExtentPoint(hDC, lpszPath, lpEnd - lpszPath, &sz);

            len = dxFixed + sz.cx;

            if (bEllipsesIn)
            {
                len += dxEllipses;
            }

            if (len <= (int)dx)
            {
                break;
            }

            bEllipsesIn = TRUE;

            if (lpEnd <= lpszPath)
            {
                /* Things didn't fit. */
                lstrcpy(lpszPath, TEXT("..."));
                lstrcat(lpszPath, szTemp);
                bRet = FALSE;
                goto Exit;
            }

            /* Step back a character. */
            lpEnd = CharPrev(lpszPath, lpEnd);
        }

        if (bEllipsesIn)
        {
            lstrcpy(lpEnd, TEXT("..."));
            lstrcat(lpEnd, szTemp);
        }
    }

Exit:
    if (hdcGet)
    {
        ReleaseDC(NULL, hdcGet);
    }

    return bRet;
}

LPTSTR WINAPI PathFindExtension(LPCTSTR pszPath)
/*---------------------------------------------------------------------------*\
 *
\*---------------------------------------------------------------------------*/
{
    LPCTSTR pszDot;

    for (pszDot = NULL; *pszPath; pszPath = CharNext(pszPath))
    {
        switch (*pszPath)
        {
            case TEXT('.'):
                pszDot = pszPath;         // remember the last dot
                break;

            case TEXT('\\'):
            case TEXT(' '):         // extensions can't have spaces
                pszDot = NULL;       // forget last dot, it was in a directory
                break;
        }
    }

    // if we found the extension, return ptr to the dot, else
    // ptr to end of the string (NULL extension) (cast->non const)
    return pszDot ? (LPTSTR)pszDot : (LPTSTR)pszPath;
} /* PathFindExtension()
   */

LPTSTR WINAPI PathFindFileName(LPCTSTR pPath)
/*---------------------------------------------------------------------------*\
 *
\*---------------------------------------------------------------------------*/
{
    LPCTSTR pT;

    for (pT = pPath; *pPath; pPath = CharNext(pPath))
    {
        if ((pPath[0] == TEXT('\\') ||
             pPath[0] == TEXT(':')) && pPath[1] && (pPath[1] != TEXT('\\')))
        {
            pT = pPath + 1;
        }
    }

    return (LPTSTR)pT;   // const -> non const
} /* PathFindFileName()
   */

void WINAPI PathStripPath(LPTSTR pszPath)
/*---------------------------------------------------------------------------*\
 *
\*---------------------------------------------------------------------------*/
{
    LPTSTR pszName = PathFindFileName(pszPath);

    if (pszName != pszPath)
    {
        lstrcpy(pszPath, pszName);
    }

} /* PathStripPath()
   */

void WINAPI PathCompactSlashes(LPTSTR pszPath)
/*---------------------------------------------------------------------------*\
 *
\*---------------------------------------------------------------------------*/
{
    BOOL bSep = FALSE;
    int iSrc;
    int iDest;
    int cch;

    if (!pszPath)
    {
        return;
    }

    cch = wcslen(pszPath);
    for (iSrc = 0, iDest = 0; iSrc < cch; iSrc++)
    {
        if (!((pszPath[iSrc] == L'\\') && bSep))
        {
            pszPath[iDest] = pszPath[iSrc];
            iDest++;
        }
        bSep = (pszPath[iSrc] == L'\\');
    }

    pszPath[iDest] = L'\0';

} /* PathCompactSlashes()
   */

BOOL WINAPI PathIsDatabase(LPCTSTR pszPath)
{
    if (!pszPath)
    {
        return FALSE;
    }

    TCHAR const c_szDataBaseDir[] = TEXT("\\\003Databases"); // This should be somewhere common
    if (!_tcsnicmp(pszPath, c_szDataBaseDir, lstrlen(c_szDataBaseDir)) &&
        !PathFileExists(pszPath))
    {
        return TRUE;
    }
    else
    {
        return FALSE;
    }
}

BOOL WINAPI PathIsDirectory(LPCTSTR pszPath)
/*---------------------------------------------------------------------------*\
 *
\*---------------------------------------------------------------------------*/
{
    if (!pszPath)
    {
        return FALSE;
    }

    DWORD dwAttribs = GetFileAttributes(pszPath);
    if (dwAttribs != (DWORD)-1)
    {
        return (BOOL)(dwAttribs & FILE_ATTRIBUTE_DIRECTORY);
    }
    return FALSE;
} /* PathIsDirectory()
   */

BOOL WINAPI PathGetAssociation(LPCTSTR pszPath, LPTSTR pszAssoc)
{
    HKEY hKey, hkeySub;
    DWORD dw, dwType;
    TCHAR szTemp[MAX_PATH];
    LPTSTR lpszExtension = PathFindExtension(pszPath);

    *pszAssoc = L'\0';

    if (*lpszExtension)
    {
        LONG lRet = RegOpenKeyEx(HKEY_CLASSES_ROOT, lpszExtension, 0, 0, &hKey);
        // Error means no association which is fine.
        if (lRet != ERROR_SUCCESS)
        {
            return FALSE;
        }

        // Fetch the file extension type.
        dw = MAX_PATH;
        lRet = RegQueryValueEx(hKey, NULL, 0, &dwType, (LPBYTE)szTemp, &dw);
        RegCloseKey(hKey);

        // Ensure we are NULL terminated
        szTemp[MAX_PATH-1] = L'\0';

        if (lRet != ERROR_SUCCESS)
        {
            return FALSE;
        }

        // Open the key for this extension type.
        lRet = RegOpenKeyEx(HKEY_CLASSES_ROOT, szTemp, 0, 0, &hKey);

        if (lRet != ERROR_SUCCESS)
        {
            return FALSE;
        }

        *szTemp = 0;
        lRet = RegOpenKeyEx(hKey, L"Shell\\Open\\Command", 0, 0, &hkeySub);

        if (lRet == ERROR_SUCCESS)
        {
            dw = MAX_PATH;
            RegQueryValueEx(hkeySub, NULL, NULL, &dwType, (LPBYTE) szTemp, &dw);
            RegCloseKey(hkeySub);
        }
        RegCloseKey(hKey);

        // We got it, so copy to the output buffer.
        lstrcpy(pszAssoc, szTemp);
    }

    return TRUE;

}

HRESULT WINAPI PathIsGUID(LPCTSTR pszPath)
/*---------------------------------------------------------------------------*\
 *
\*---------------------------------------------------------------------------*/
{
    CLSID clsid;
    return CLSIDFromString((LPOLESTR)pszPath, &clsid);

} /* PathIsGUID()
   */

BOOL WINAPI PathIsURL(LPCTSTR pszPath)
{
    BOOL bRet = FALSE;
    HKEY hKey = NULL;
    LRESULT lRet;
    LPCWSTR pszColon = NULL;
    WCHAR szProtocol[MAX_PATH];
    size_t cch;

    if (!pszPath)
    {
        goto leave;
    }

    pszColon = wcschr(pszPath, L':');
    if (!pszColon)
    {
        goto leave;
    }

    cch = pszColon - pszPath;
    if (FAILED(StringCchCopyN(szProtocol,
                    lengthof(szProtocol),
                    pszPath,
                    cch)))
    {
        goto leave;
    }
    // StringCchCopyN always null-terminates a non-zero-length destination buffer

    lRet = RegOpenKeyEx(HKEY_CLASSES_ROOT, szProtocol, 0, 0, &hKey);
    if (lRet == ERROR_SUCCESS)
    {
        lRet = RegQueryValueEx(hKey, L"URL Protocol", NULL, NULL, NULL, NULL);
        if (lRet == ERROR_SUCCESS)
        {
            bRet = TRUE;
        }
        RegCloseKey(hKey);
    }

leave:
    return bRet;
}

BOOL WINAPI PathOnExtList(LPCTSTR pszExtList, LPCTSTR pszExt)
/*---------------------------------------------------------------------------*\
 *
\*---------------------------------------------------------------------------*/
{
    BOOL bRet = FALSE;

    if (!pszExtList || !pszExt)
    {
        goto leave;
    }

    for (; *pszExtList; pszExtList += lstrlen(pszExtList) + 1)
    {
        if (0 == wcsicmp(pszExt, pszExtList))
        {
            bRet = TRUE;        // yes
            break;
        }
    }

leave:
    return bRet;
} /* OnExtList()
   */

#ifdef UNDER_CE
const TCHAR achExes[] = TEXT(".exe\0");
#else
const TCHAR achExes[] = TEXT(".bat\0.pif\0.exe\0.com\0");
#endif

BOOL WINAPI PathIsExe(LPCTSTR szFile)
/*---------------------------------------------------------------------------*\
 *
\*---------------------------------------------------------------------------*/
{
    BOOL bRet = FALSE;

    if (!szFile)
    {
        goto leave;
    }

    bRet = PathOnExtList((LPCWSTR) achExes, PathFindExtension(szFile));

leave:
    return bRet;
} /* PathIsExe()
   */

BOOL WINAPI PathIsLink(LPCTSTR szFile)
/*---------------------------------------------------------------------------*\
 *
\*---------------------------------------------------------------------------*/
{
    BOOL bRet = FALSE;

    if (!szFile)
    {
        goto leave;
    }

    bRet = (0 == wcsicmp(L".lnk", PathFindExtension(szFile)));

leave:
    return bRet;
} /* PathIsLink()
   */

BOOL WINAPI PathIsExtension(LPCTSTR szFile, LPCTSTR szExt)
/*---------------------------------------------------------------------------*\
 *
\*---------------------------------------------------------------------------*/
{
    BOOL bRet = FALSE;

    if (!szFile || !szExt)
    {
        goto leave;
    }

    bRet = (0 == wcsicmp(szExt, PathFindExtension(szFile)));

leave:
    return bRet;
} /* PathIsExtension()
   */

BOOL WINAPI PathMakePretty(LPTSTR lpPath)
/*---------------------------------------------------------------------------*\
 *
\*---------------------------------------------------------------------------*/
{
    LPTSTR lp;

    // check for all uppercase
    for (lp = lpPath; *lp; lp = CharNext(lp))
    {
        if ((*lp >= TEXT('a')) && (*lp <= TEXT('z')))
        {
            return FALSE;
        }
    }

    CharLower(lpPath);
    CharUpperBuff(lpPath, 1);

    return TRUE;        // did the conversion
} /* PathMakePretty()
   */

BOOL WINAPI PathFileExists(LPCTSTR lpszPath)
/*---------------------------------------------------------------------------*\
 *
\*---------------------------------------------------------------------------*/
{
    return (UINT)GetFileAttributes(lpszPath) != (UINT)-1;
} /* PathFileExists()
   */

void WINAPI PathRemoveExtension(LPTSTR pszPath)
/*---------------------------------------------------------------------------*\
 *
\*---------------------------------------------------------------------------*/
{
    LPTSTR pExt = PathFindExtension(pszPath);
    if (*pExt)
    {
        *pExt = 0;    // null out the "."
    }

} /* PathRemoveExtension()
   */

BOOL WINAPI PathRemoveFileSpec(LPTSTR pFile)
/*---------------------------------------------------------------------------*\
 *
\*---------------------------------------------------------------------------*/
{
    LPTSTR pSZ = pFile;

    while (*pSZ != 0)
    {
        pSZ ++;
    }

    while (pSZ != pFile && *pSZ != TEXT('\\'))
    {
        pSZ --;
    }
    *pSZ = 0;

    return TRUE;

} /* PathRemoveFileSpec()
   */

// helper for PathMatchSpec.
// originally PathMatchSpec had this logic embedded in it and it recursively called itself.
// only problem is the recursion picked up all the extra specs, so for example
// PathMatchSpec("foo....txt", "*.txt;*.a;*.b;*.c;*.d;*.e;*.f;*.g") called itself too much
// and wound up being O(N^3).
// in fact this logic doesnt match strings efficiently, but we shipped it so leave it be.
// just test one spec at a time and its all good.
// pszSpec is a pointer within the pszSpec passed to PathMatchSpec so we terminate at ';' in addition to '\0'.
BOOL PathMatchSingleSpec(LPCTSTR pszFileParam, LPCTSTR pszSpec)
{
    LPCTSTR pszFile = pszFileParam;

    // Strip leading spaces from each spec.  This is mainly for commdlg
    // support;  the standard format that apps pass in for multiple specs
    // is something like "*.bmp; *.dib; *.pcx" for nicer presentation to
    // the user.
    while (*pszSpec == TEXT(' '))
        pszSpec++;

    while (*pszFile && *pszSpec && *pszSpec != TEXT(';'))
    {
        switch (*pszSpec)
        {
        case TEXT('?'):
            pszFile = pszFile + 1;
            pszSpec++;      // NLS: We know that this is a SBCS
            break;

        case TEXT('*'):

            // We found a * so see if this is the end of our file spec
            // or we have *.* as the end of spec, in which case we
            // can return true.
            //
            if (*(pszSpec + 1) == 0 || *(pszSpec + 1) == TEXT(';'))   // "*" matches everything
                return TRUE;


            // Increment to the next character in the list
            pszSpec = pszSpec + 1;

            // If the next character is a . then short circuit the
            // recursion for performance reasons
            if (*pszSpec == TEXT('.'))
            {
                pszSpec++;  // Get beyond the .

                // Now see if this is the *.* case
                if ((*pszSpec == TEXT('*')) &&
                        ((*(pszSpec+1) == TEXT('\0')) || (*(pszSpec+1) == TEXT(';'))))
                    return TRUE;

                // find the extension (or end in the file name)
                while (*pszFile)
                {
                    // If the next char is a dot we try to match the
                    // part on down else we just increment to next item
                    if (*pszFile == TEXT('.'))
                    {
                        pszFile++;

                        if (PathMatchSingleSpec(pszFile, pszSpec))
                            return TRUE;

                    }
                    else
                        pszFile = pszFile + 1;
                }

                return FALSE;   // No item found so go to next pattern
            }
            else
            {
                // Not simply looking for extension, so recurse through
                // each of the characters until we find a match or the
                // end of the file name
                while (*pszFile)
                {
                    // recurse on our self to see if we have a match
                    if (PathMatchSingleSpec(pszFile, pszSpec))
                        return TRUE;
                    pszFile = pszFile + 1;
                }

                return FALSE;   // No item found so go to next pattern
            }

        default:
            if (CharUpper((TCHAR*)*pszSpec) == CharUpper((TCHAR*)*pszFile))
            {
                pszFile++;
                pszSpec++;
            }
            else
            {
                return FALSE;
            }
        }
    }

    // If we made it to the end of both strings, we have a match...
    //
    if (!*pszFile)
    {
        if ((!*pszSpec || *pszSpec == TEXT(';')))
            return TRUE;

        // Also special case if things like foo should match foo*
        // as well as foo*; for foo*.* or foo*.*;
        if ( (*pszSpec == TEXT('*')) &&
            ( (*(pszSpec+1) == TEXT('\0')) || (*(pszSpec+1) == TEXT(';')) ||
                ((*(pszSpec+1) == TEXT('.')) &&  (*(pszSpec+2) == TEXT('*')) &&
                    ((*(pszSpec+3) == TEXT('\0')) || (*(pszSpec+3) == TEXT(';'))))))

                return TRUE;
    }
    return FALSE;
}


BOOL WINAPI PathMatchSpec(LPCTSTR pszFileParam, LPCTSTR pszSpec)
{
    if (pszSpec && pszFileParam)
    {
        // Special case empty string, "*", and "*.*"...
        //
        if (*pszSpec == 0)
        {
            return TRUE;
        }

        // loop over the spec, break off at ';', and call our helper for each.
        do
        {
            if (PathMatchSingleSpec(pszFileParam, pszSpec))
                return TRUE;

            // Skip to the end of the path spec...
            while (*pszSpec && *pszSpec != TEXT(';'))
                pszSpec = pszSpec + 1;

        // If we have more specs, keep looping...
        } while (*pszSpec++ == TEXT(';'));
    }

    return FALSE;
}


BOOL
WINAPI
PathRemoveStem(LPTSTR pszPath, BOOL bIsDirectory)
/*---------------------------------------------------------------------------*\
 *
\*---------------------------------------------------------------------------*/
{
    LPTSTR pszRest, pszEndUniq;

    // NOTE: This function will remove the following "(number)", so the first
    //       thing this function will do is trim off the extension, if there
    //       is one. The next step is to remove a number enclosed in parenthesis.
    //       and then remove ther extra space, if there is one.
    //
    //        "\foo\bar (3).lnk" -->   "\foo\bar"

    if (!bIsDirectory)
    {
        PathRemoveExtension(pszPath);
    }

    pszRest = _tcschr(pszPath, TEXT('('));
    while (pszRest)
    {
        // First validate that this is the right one
        pszEndUniq = CharNext(pszRest);
        while (*pszEndUniq && *pszEndUniq >= TEXT('0') && *pszEndUniq <= TEXT('9'))
        {
            pszEndUniq++;
        }

        if (*pszEndUniq == TEXT(')'))
        {
            break;  // We have the right one!
        }
        pszRest = _tcschr(CharNext(pszRest), TEXT('('));
    }

    if (pszRest)
    {
        if (pszRest != pszPath)
        {
            pszRest--;
            if (*pszRest != TEXT(' '))
            {
                pszRest++;
            }
        }
        *pszRest = 0;
    }

    return TRUE;
} /* PathRemoveStem()
   */

BOOL
WINAPI
PathMakeUniqueName(LPCTSTR lpszPath,
                   LPCTSTR lpszT1,
                   LPCTSTR lpszT2,
                   BOOL    bLink,
                   LPTSTR  lpszUniqueName,
                   UINT    cchUniqueName)
/*---------------------------------------------------------------------------*\
 *
 * NOTE: Win95 has a better implementation, but for the lack of a brain,
 *       I am just trying to implement this to solve our needs a simple as
 *       as possible. All this will do is make sure the path won't be bigger
 *       than MAX_PATH and also make sure that one doesn't already exist.
 *
\*---------------------------------------------------------------------------*/
{
    return PathMakeUniqueNameEx(lpszPath,
                lpszT1,
                lpszT2,
                bLink,
                FALSE,
                lpszUniqueName,
                cchUniqueName);

}

BOOL
WINAPI
PathMakeUniqueNameEx(LPCTSTR lpszPath,
                     LPCTSTR lpszT1,
                     LPCTSTR lpszT2,
                     BOOL    bLink,
                     BOOL    bSourceIsDirectory,
                     LPTSTR  lpszUniqueName,
                     UINT    cchUniqueName)
{
    BOOL bRet = FALSE;
    int nItems = 1, nT1, nT2, nPath, nName, nCounter = 0;
    LPTSTR szPath = lpszUniqueName;
    UINT cchPath = cchUniqueName;

    TCHAR szFile[MAX_PATH];
    TCHAR szBuf[MAX_PATH];

    // Path shouldn't be bigger than MAX_PATH
    ASSERT(MAX_PATH >= cchPath);

    if (!lpszPath || !lpszUniqueName)
    {
        goto leave;
    }

    // NOTE: If either of these strings exist then there will also be a space
    //       involved.
    nT1 = (lpszT1) ? lstrlen(lpszT1) + 1 : 0;
    nT2 = (lpszT2) ? lstrlen(lpszT2) + 1 : 0;

    StringCchCopy(szPath, cchPath, lpszPath);
    PathRemoveFileSpec(szPath);

    // NOTE: Adding one to this to account for the slash after the path
    nPath = lstrlen(szPath) + 1;

    // NOTE: So as not to go over MAX_PATH, I am imcluding the NULL terminator
    //       in this string count.
    if (bLink)
    {
        StringCchCopy(szPath, cchPath, PathFindFileName(lpszPath));
        PathRemoveStem(szPath, bSourceIsDirectory);
        nName = lstrlen(szPath) + 1;
        nName += 4;  // extension ".lnk"
    }
    else
    {
        nName  = lstrlen(PathFindFileName(lpszPath)) + 1;
    }

    do
    {
        if (nItems == 1)
        {
            nCounter = 0;
        }
        else if (nItems < 10)
        {
            nCounter = 4;
        }
        else if (nItems < 100)
        {
            nCounter = 5;
        }
        else
        {
            nCounter = 6;
        }

        if ((nPath + nT1 + nT2 + nName + nCounter) > MAX_PATH)
        {
            SetLastError(ERROR_FILENAME_EXCED_RANGE);
            goto leave;
        }

        StringCchCopy(szPath, cchPath, lpszPath);
        PathRemoveFileSpec(szPath);

        StringCchCat(szPath, cchPath, TEXT("\\"));

        if (lpszT1)
        {
            // This processing is added for compatible of Win95.
            //
            // [English]
            //    in. lpszT1 = "Shortcut to %s"
            //        lpszT1 = "Copy", lpszT2 = "of"
            //      out.  "Shortcut to file (n)"
            //          "Copy (n) of file"
            //
            // [Japanese]
            //    in. lpszT1 = "%s no Shortcut"
            //        lpszT1 = "Copy", lpszT2 = "~"
            //      out.  "file no Shortcut (n)"
            //          "Copy (n) ~ file"

            if (FAILED(StringCchCopy(szFile,
                            lengthof(szFile),
                            PathFindFileName(lpszPath))))
            {
                goto leave;
            }

            if (bLink && !bSourceIsDirectory)
            {
                PathRemoveExtension(szFile);
            }

            StringCchPrintf(szBuf, lengthof(szBuf), lpszT1, szFile);
            StringCchCat(szPath, cchPath, szBuf);

            if (nItems > 1)
            {
                StringCchCat(szPath, cchPath, TEXT(" "));
                StringCchPrintf(szBuf, lengthof(szBuf), TEXT("(%d)"), nItems);
                StringCchCat(szPath, cchPath, szBuf);
            }

            if (lpszT2)
            {
                StringCchCat(szPath, cchPath, TEXT(" "));
                StringCchCat(szPath, cchPath, lpszT2);
                StringCchCat(szPath, cchPath, TEXT(" "));
            }

            if (bLink)
            {
                StringCchCat(szPath, cchPath, TEXT(".lnk"));
            }
            else
            {
                StringCchCat(szPath, cchPath, szFile);
            }

        }
        else
        {
            StringCchCat(szPath, cchPath, PathFindFileName(lpszPath));
            if (bLink)
            {
                PathRemoveStem(szPath, bSourceIsDirectory);
            }

            if (nItems > 1)
            {
                StringCchPrintf(szBuf, lengthof(szBuf), TEXT(" (%d)"), nItems);
                StringCchCat(szPath, cchPath, szBuf);
            }

            if (bLink)
            {
                StringCchCat(szPath, cchPath, TEXT(".lnk"));
            }
        }

        // NOTE: Sanity Check
        if (++nItems >= 1000)
        {
            goto leave;
        }

    } while (GetFileAttributes(szPath) != (UINT)-1);

    RETAILMSG(0, (TEXT("PathMakeUniqueName: '%s'\r\n"), szPath));
    bRet = TRUE;

leave:
    return bRet;
} /* PathMakeUniqueName()
   */

LPCTSTR PathFindRootDevice(LPTSTR szPath)
/*---------------------------------------------------------------------------*\
 *
\*---------------------------------------------------------------------------*/
{
    TCHAR *psz;
    int cLeadingSlashes = 0;

    psz = szPath;

    // Skip all initial slashes.
    while (*psz == TEXT('\\') || *psz == TEXT('/'))
    {
        cLeadingSlashes++;
        psz++;
    }

    // Find the next slash in the path.
    while (*psz && (*psz != TEXT('\\')) && (*psz != TEXT('/')))
    {
        psz++;
    }

    // Now we're at the end of the first component.  If this is a network
    // path we need to continue skipping ahead to the next slash.
    if (cLeadingSlashes > 1 && psz[0] && psz[1] != '\0')
    {
        psz++;
        while (*psz && (*psz != TEXT('\\')) && (*psz != TEXT('/')))
        {
            psz++;
        }
    }

    // Finally terminate the path.
    *psz = 0;

    return szPath;

} /* PathFindRootDevice()
   */

BOOL PathIsSameDevice(LPCTSTR szPath1, LPCTSTR szPath2)
/*---------------------------------------------------------------------------*\
 *
\*---------------------------------------------------------------------------*/
{
    TCHAR sz1[MAX_PATH], sz2[MAX_PATH];
    DWORD dw;

    _tcsncpy(sz1, szPath1, MAX_PATH - 1);
    sz1[MAX_PATH - 1] = 0;
    PathFindRootDevice(sz1);

    _tcsncpy(sz2, szPath2, MAX_PATH - 1);
    sz2[MAX_PATH - 1] = 0;
    PathFindRootDevice(sz2);

    // If either path name begins with slashes, then we have at least
    // one network path and can do a direct comparision.
    if (((sz1[0] == TEXT('\\') || sz1[0] == TEXT('/')) &&
         (sz1[1] == TEXT('\\') || sz1[1] == TEXT('/'))) ||
        ((sz2[0] == TEXT('\\') || sz2[0] == TEXT('/')) &&
         (sz2[1] == TEXT('\\') || sz2[1] == TEXT('/'))))
    {
        return (lstrcmpi(sz1, sz2) == 0);
    }

    //NOTE: If the root is the same, then the device is the same!
    if (!lstrcmpi(sz1, sz2))
    {
        return TRUE;
    }

    dw = GetFileAttributes(sz1);
    if ((dw != (UINT)-1) && (dw & FILE_ATTRIBUTE_TEMPORARY))
    {
        return FALSE;
    }

    dw = GetFileAttributes(sz2);
    if ((dw != (UINT)-1) && (dw & FILE_ATTRIBUTE_TEMPORARY))
    {
        return FALSE;
    }

    return TRUE;

} /* PathIsSameDevice()
   */

