//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// This source code is licensed under Microsoft Shared Source License
// Version 1.0 for Windows CE.
// For a copy of the license visit http://go.microsoft.com/fwlink/?LinkId=3223.
//
/*---------------------------------------------------------------------------*\
 *  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;

    

    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;
    LPWSTR 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()
   */

BOOL WINAPI PathMatchSpec(LPCTSTR pszFileParam, LPCTSTR pszSpec)
/*---------------------------------------------------------------------------*\
 * Match a DOS wild card spec against a dos file name
\*---------------------------------------------------------------------------*/
{
    // Special case empty string, "*", and "*.*"...
    //
    if (*pszSpec == 0)
    {
        return TRUE;
    }

    do
    {
        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=CharNext(pszFile);
                    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 = CharNext(pszSpec);

                    // 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 (PathMatchSpec(pszFile, pszSpec))
                                {
                                    return(TRUE);
                                }
                            }
                            else
                            {
                                pszFile = CharNext(pszFile);
                            }
                        }

                        goto NoMatch;   // 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 (PathMatchSpec(pszFile, pszSpec))
                            {
                                return(TRUE);
                            }
                            pszFile = CharNext(pszFile);
                        }

                        goto NoMatch;   // No item found so go to next pattern
                    }

            default:
                if (CharUpper((LPTSTR)(DWORD)*pszSpec) ==
                    CharUpper((LPTSTR)(DWORD)*pszFile))
                {
                    pszFile++;
                    pszSpec++;
                }
                else
                {
                    goto NoMatch;
                }
            }
        }

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

        // Skip to the end of the path spec...

NoMatch:
        while (*pszSpec && *pszSpec != TEXT(';'))
        {
            pszSpec = CharNext(pszSpec);
        }

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

    return FALSE;
} /* PathMatchSpec()
   */

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

}

BOOL
WINAPI
PathMakeUniqueNameEx(LPCTSTR lpszPath,
                   LPCTSTR lpszT1,
                   LPCTSTR lpszT2,
                   BOOL bLink,
                   BOOL bSourceIsDirectory,
                   LPTSTR  lpszUniqueName)
{
    BOOL bRet = FALSE;
    int nItems = 1, nT1, nT2, nPath, nName, nCounter = 0;
    LPTSTR szPath = lpszUniqueName; // NOTE: This had better be MAX_PATH
    TCHAR szFile[MAX_PATH];

    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;

    lstrcpy(szPath, 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)
    {
        lstrcpy(szPath, 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;
        }

        lstrcpy(szPath, lpszPath);
        PathRemoveFileSpec(szPath);

        lstrcat(szPath, 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);
            }

            wsprintf(szPath+lstrlen(szPath), lpszT1, szFile);

            if (nItems > 1)
            {
                lstrcat(szPath, TEXT(" "));
                wsprintf(&szPath[lstrlen(szPath)], TEXT("(%d)"), nItems);
            }

            if (lpszT2)
            {
                lstrcat(szPath, TEXT(" "));
                lstrcat(szPath, lpszT2);
                lstrcat(szPath, TEXT(" "));
            }

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

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

            if (nItems > 1)
            {
                wsprintf(&szPath[lstrlen(szPath)], TEXT(" (%d)"), nItems);
            }

            if (bLink)
            {
                lstrcat(szPath, 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()
   */

