path.c
Upload User: xhy777
Upload Date: 2007-02-14
Package Size: 24088k
Code Size: 105k
Category:

Windows Kernel

Development Platform:

Visual C++

  1. #ifdef _ALPHA_
  2. #include <nt.h>   // require wx86 definitions
  3. #include <ntrtl.h>
  4. #include <nturtl.h>
  5. #endif
  6. #include "priv.h"
  7. #include "privpath.h"
  8. #ifdef UNIX
  9. #include <mainwin.h>
  10. #include "unixstuff.h"
  11. #endif
  12. #define CH_WHACK TEXT(FILENAME_SEPARATOR)
  13. #include <platform.h>
  14. // Warning this define is in NTDEF.H but not winnt.h...
  15. #ifdef UNICODE
  16. typedef WCHAR TUCHAR;
  17. #else
  18. typedef unsigned char TUCHAR;
  19. #endif
  20. #ifdef UNICODE // {
  21. //***   FAST_CharNext -- fast CharNext for path operations
  22. // DESCRIPTION
  23. //  when we're just stepping thru chars in a path, a simple '++' is fine.
  24. #define FAST_CharNext(p)    (DBNotNULL(p) + 1)
  25. #ifdef DEBUG
  26. LPWSTR WINAPI
  27. DBNotNULL(LPCWSTR lpszCurrent)
  28. {
  29.     ASSERT(*lpszCurrent);
  30.     return (LPWSTR) lpszCurrent;
  31. }
  32. #else
  33. #define DBNotNULL(p)    (p)
  34. #endif
  35. #else // }{
  36. #define FAST_CharNext(p)    CharNext(p)
  37. #endif // }
  38. static const TCHAR c_szPATH[] = TEXT("PATH");
  39. static const TCHAR c_szEllipses[] = TEXT("...");
  40. //
  41. // Inline function to check for a double-backslash at the
  42. // beginning of a string
  43. //
  44. #ifndef UNIX
  45. static __inline BOOL DBL_BSLASH(LPCTSTR psz)
  46. #else
  47. __inline BOOL DBL_BSLASH(LPCTSTR psz)
  48. #endif
  49. {
  50.     return (psz[0] == TEXT('\') && psz[1] == TEXT('\'));
  51. }
  52. #ifdef DBCS
  53. // NOTE:
  54. // LCMAP_IGNOREDBCS is a private bit has been redefined to
  55. // 0x80000000 in NT5 source tree becuase it conflicts with
  56. // another public bit.
  57. // To make this code work with the OLD platforms, namely
  58. // Win95 and OSRs. We have to define this flag.
  59. #define LCMAP_IGNOREDBCS_WIN95 0x01000000
  60. //
  61. // BUGBUG: This is supposed to work only with Path string.
  62. //
  63. int CaseConvertPathExceptDBCS(LPTSTR pszPath, int cch, BOOL fUpper)
  64. {
  65.     TCHAR szTemp[MAX_PATH];
  66.     int   cchUse;
  67.     DWORD fdwMap = (fUpper? LCMAP_UPPERCASE:LCMAP_LOWERCASE);
  68.     // BUGBUG !!! (ccteng)
  69.     // Do we need to check for Memphis? Is Memphis shipping a
  70.     // kernel compiled with new headers?
  71.     // LCMAP_IGNOREDBCS is ignored on NT.
  72.     // And also this flag has been redefined in NT5 headers to
  73.     // resolve a conflict which broke the backward compatibility.
  74.     // So we only set the old flag when it's NOT running on NT.
  75.     if (!g_bRunningOnNT)
  76.     {
  77.         fdwMap |= LCMAP_IGNOREDBCS_WIN95;
  78.     }
  79.     cchUse = (cch == 0)? lstrlen(pszPath): cch;
  80.     // LCMapString cannot deal with src/dst in the same address.
  81.     //
  82.     if (pszPath)
  83.     {
  84.         lstrcpy(szTemp,pszPath);
  85.         return LCMapString(LOCALE_SYSTEM_DEFAULT,fdwMap, szTemp, cchUse, pszPath, cchUse);
  86.     }
  87.     return 0;
  88. }
  89. STDAPI_(LPTSTR) CharLowerNoDBCS(LPTSTR psz)
  90. {
  91.     if(CaseConvertPathExceptDBCS(psz, 0, FALSE))
  92.     {
  93.         return psz;
  94.     }
  95.     return NULL;
  96. }
  97. STDAPI_(LPTSTR) CharUpperNoDBCS(LPTSTR psz)
  98. {
  99.     if(CaseConvertPathExceptDBCS(psz, 0, TRUE))
  100.     {
  101.         return psz;
  102.     }
  103.     return NULL;
  104. }
  105. UINT CharLowerBuffNoDBCS(LPTSTR lpsz, UINT cb)
  106. {
  107.     return (UINT)CaseConvertPathExceptDBCS(lpsz, cb, FALSE);
  108. }
  109. UINT CharUpperBuffNoDBCS(LPTSTR lpsz, UINT cb)
  110. {
  111.     return (UINT)CaseConvertPathExceptDBCS(lpsz, cb, TRUE);
  112. }
  113. #endif // DBCS
  114. // BUGBUG, we should validate the sizes of all path buffers by filling them
  115. // with MAX_PATH fill bytes.
  116. /*----------------------------------------------------------
  117. Purpose: converts a file path to make it look a bit better if
  118.          it is all upper case characters.
  119. Returns:
  120. */
  121. STDAPI_(BOOL)
  122. PathMakePretty(LPTSTR lpPath)
  123. {
  124.     LPTSTR lp;
  125.     RIPMSG(lpPath && IS_VALID_STRING_PTR(lpPath, -1), "PathMakePretty: caller passed bad lpPath");
  126.     if (!lpPath)
  127.     {
  128.         return FALSE;
  129.     }
  130.     // REVIEW: INTL need to deal with lower case chars in (>127) range?
  131.     // check for all uppercase
  132.     for (lp = lpPath; *lp; lp = FAST_CharNext(lp))
  133.     {
  134.         if ((*lp >= TEXT('a')) && (*lp <= TEXT('z')) || IsDBCSLeadByte(*lp))
  135.         {
  136.             // this is a LFN or DBCS, dont mess with it
  137.             return FALSE;
  138.         }
  139.     }
  140. #ifdef DBCS
  141.     // In order to be compatible with the file system, we cannot
  142.     // case convert DBCS Roman characters.
  143.     //
  144.     CharLowerNoDBCS(lpPath);
  145.     CharUpperBuffNoDBCS(lpPath, 1);
  146. #else
  147.     CharLower(lpPath);
  148.     CharUpperBuff(lpPath, 1);
  149. #endif
  150.     return TRUE;        // did the conversion
  151. }
  152. // returns a pointer to the arguments in a cmd type path or pointer to
  153. // NULL if no args exist
  154. //
  155. // "foo.exe bar.txt"    -> "bar.txt"
  156. // "foo.exe"            -> ""
  157. //
  158. // Spaces in filenames must be quoted.
  159. // " "A long name.txt" bar.txt " -> "bar.txt"
  160. STDAPI_(LPTSTR)
  161. PathGetArgs(LPCTSTR pszPath)
  162. {
  163.     RIPMSG(!pszPath || IS_VALID_STRING_PTR(pszPath, -1), "PathGetArgs: caller passed bad pszPath");
  164.     if (pszPath)
  165.     {
  166.         BOOL fInQuotes = FALSE;
  167.         while (*pszPath)
  168.         {
  169.             if (*pszPath == TEXT('"'))
  170.             {
  171.                 fInQuotes = !fInQuotes;
  172.             }
  173.             else if (!fInQuotes && *pszPath == TEXT(' '))
  174.             {
  175.                 return (LPTSTR)pszPath+1;
  176.             }
  177.             pszPath = FAST_CharNext(pszPath);
  178.         }
  179.     }
  180.     return (LPTSTR)pszPath;
  181. }
  182. /*----------------------------------------------------------
  183. Purpose: Remove arguments from pszPath.
  184. Returns: --
  185. Cond:    --
  186. */
  187. STDAPI_(void)
  188. PathRemoveArgs(LPTSTR pszPath)
  189. {
  190.     RIPMSG(pszPath && IS_VALID_STRING_PTR(pszPath, -1), "PathRemoveArgs: caller passed bad pszPath");
  191.     if (pszPath)
  192.     {
  193.         LPTSTR pArgs = PathGetArgs(pszPath);
  194.     
  195.         if (*pArgs)
  196.         {
  197.             // clobber the ' '
  198.             *(pArgs - 1) = TEXT('');
  199.         }
  200.         else
  201.         {
  202.             // Handle trailing space
  203.             pArgs = CharPrev(pszPath, pArgs);
  204.             if (*pArgs == TEXT(' '))
  205.             {
  206.                 *pArgs = TEXT('');
  207.             }
  208.         }
  209.     }
  210. }
  211. /*----------------------------------------------------------
  212. Purpose: Determines if a file exists.  This is fast.
  213. Returns: TRUE if it exists
  214.   ***********************************************************************************************
  215.   !!NOTE!!
  216.   If you want to see if a UNC server, or UNC servershare exists (eg "\pyrex" or "\banyaniptd"),
  217.   then you have to call PathFileExistsAndAttributes, as this function will fail on the UNC server
  218.   and servershare case!
  219.   ***********************************************************************************************
  220. */
  221. STDAPI_(BOOL)
  222. PathFileExists(LPCTSTR pszPath)
  223. {
  224.     BOOL fResult = FALSE;
  225.     RIPMSG(pszPath && IS_VALID_STRING_PTR(pszPath, -1), "PathFileExists: caller passed bad pszPath");
  226. #ifdef DEBUG
  227.     if (PathIsUNCServer(pszPath) || PathIsUNCServerShare(pszPath))
  228.     {
  229.         TraceMsg(TF_WARNING, "PathFileExists: called with a UNC server or server-share, use PathFileExistsAndAttributes for correct results in this case!!");
  230.     }
  231. #endif
  232.     if (pszPath)
  233.     {
  234.         DWORD dwErrMode;
  235.         dwErrMode = SetErrorMode(SEM_FAILCRITICALERRORS);
  236.         fResult = (BOOL)(GetFileAttributes(pszPath) != (DWORD)-1);
  237.         SetErrorMode(dwErrMode);
  238.     }
  239.     return fResult;
  240. }
  241. /*----------------------------------------------------------
  242. Purpose: Determines if a file exists, and returns the attributes
  243.          of the file.
  244. Returns: TRUE if it exists. If the function is able to get the file attributes and the
  245.          caller passed a pdwAttributes, it will fill them in, else it will fill in -1.
  246.   *******************************************************************************************************
  247.   !!NOTE!!
  248.   If you want to fail on UNC servers (eg "\pyrex") or UNC servershares (eg "\banyaniptd") then you
  249.   should call PathFileExists and not this api!
  250.   *******************************************************************************************************
  251. */
  252. STDAPI_(BOOL) PathFileExistsAndAttributes(LPCTSTR pszPath, OPTIONAL DWORD* pdwAttributes)
  253. {
  254.     DWORD dwAttribs;
  255.     BOOL fResult = FALSE;
  256.     RIPMSG(pszPath && IS_VALID_STRING_PTR(pszPath, -1), "PathFileExistsAndAttributes: caller passed bad pszPath");
  257.     if (pdwAttributes)
  258.     {
  259.         *pdwAttributes = (DWORD)-1;
  260.     }
  261.         
  262.     if (pszPath)
  263.     {
  264.         DWORD dwErrMode = SetErrorMode(SEM_FAILCRITICALERRORS);
  265.         dwAttribs = GetFileAttributes(pszPath);
  266.         if (pdwAttributes)
  267.         {
  268.             *pdwAttributes = dwAttribs;
  269.         }
  270.         if (dwAttribs == (DWORD)-1)
  271.         {
  272.             if (PathIsUNCServer(pszPath) || PathIsUNCServerShare(pszPath))
  273.             {
  274.                 NETRESOURCE nr;
  275.                 LPTSTR lpSystem;
  276.                 DWORD dwRet;
  277.                 DWORD dwSize;
  278.                 TCHAR Buffer[256];
  279.                 nr.dwScope = RESOURCE_GLOBALNET;
  280.                 nr.dwType = RESOURCETYPE_ANY;
  281.                 nr.dwDisplayType = 0;
  282.                 nr.lpLocalName = NULL;
  283.                 nr.lpRemoteName = (LPTSTR)pszPath;
  284.                 nr.lpProvider = NULL;
  285.                 nr.lpComment = NULL;
  286.                 dwSize = SIZEOF(Buffer);
  287.     
  288.                 // the net api's might at least tell us if this exists or not in the \server or \servershare cases
  289.                 // even if GetFileAttributes() failed
  290.                 dwRet = WNetGetResourceInformation(&nr, Buffer, &dwSize, &lpSystem);
  291.                 fResult = (dwRet == WN_SUCCESS || dwRet == WN_MORE_DATA);
  292.             }
  293.         }
  294.         else
  295.         {
  296.             // GetFileAttributes succeeded!
  297.             fResult = TRUE;
  298.         }
  299.         SetErrorMode(dwErrMode);
  300.     }
  301.     return fResult;
  302. }
  303. static const TCHAR c_szDotPif[] = TEXT(".pif");
  304. static const TCHAR c_szDotCom[] = TEXT(".com");
  305. static const TCHAR c_szDotBat[] = TEXT(".bat");
  306. static const TCHAR c_szDotCmd[] = TEXT(".cmd");
  307. static const TCHAR c_szDotLnk[] = TEXT(".lnk");
  308. static const TCHAR c_szDotExe[] = TEXT(".exe");
  309. static const TCHAR c_szNone[] = TEXT("");
  310. // NB Look for .pif's first so that bound OS/2 apps (exe's)
  311. // can have their dos stubs run via a pif.
  312. //
  313. // The COMMAND.COM search order is COM then EXE then BAT.  Windows 3.x
  314. // matched this search order.  We need to search in the same order.
  315. //  *** WARNING *** The order of the PFOPEX_ flags must be identical to the order
  316. //  of the c_aDefExtList array.  PathFileExistsDefExt relies on it.
  317. static const LPCTSTR c_aDefExtList[] = {
  318.     c_szDotPif,
  319.     c_szDotCom,
  320.     c_szDotExe,
  321.     c_szDotBat,
  322.     c_szDotLnk,
  323.     c_szDotCmd,
  324.     c_szNone
  325. };
  326. //  *** END OF WARNING ***
  327. //------------------------------------------------------------------
  328. // Return TRUE if a file exists (by attribute check) after
  329. // applying a default extensions (if req).
  330. STDAPI_(BOOL) PathFileExistsDefExt(LPTSTR lpszPath, UINT fExt)
  331. {
  332.     RIPMSG(lpszPath && IS_VALID_STRING_PTR(lpszPath, -1), "PathFileExistsDefExt: caller passed bad lpszPath");
  333.     if (fExt)
  334.     {
  335.         RIPMSG(!lpszPath || !IS_VALID_STRING_PTR(lpszPath, -1) || // avoid RIP when above RIP would have caught it
  336.                IS_VALID_WRITE_BUFFER(lpszPath, TCHAR, MAX_PATH), "PathFileExistsDefExt: caller passed bad lpszPath");
  337.         DEBUGWhackPathString(lpszPath, MAX_PATH);
  338.     }
  339.     if (!lpszPath)
  340.         return FALSE;
  341.     // No sense sticking an extension on a server or share...
  342.     if (PathIsUNCServer(lpszPath) || PathIsUNCServerShare(lpszPath))
  343.     {
  344.         return FALSE;
  345.     }
  346.     // Try default extensions?
  347.     if (fExt && (!*PathFindExtension(lpszPath) || !(PFOPEX_OPTIONAL & fExt)))
  348.     {
  349.         UINT    i;
  350.         UINT    iPathLen = lstrlen(lpszPath);
  351.         LPTSTR  lpszPathEnd = lpszPath + iPathLen;
  352.         //
  353.         //  Bail if not enough space for 4 more chars
  354.         //
  355.         if (MAX_PATH-iPathLen < ARRAYSIZE(c_szDotPif)) {
  356.             return FALSE;
  357.         }
  358.         for (i = 0; i < ARRAYSIZE(c_aDefExtList); i++, fExt = fExt >> 1) {
  359.             if (fExt & 1) {
  360.                 lstrcpy(lpszPathEnd, c_aDefExtList[i]);
  361.                 if (PathFileExistsAndAttributes(lpszPath, NULL))
  362.                     return TRUE;
  363.             }
  364.         }
  365.         *lpszPathEnd = 0;   // Get rid of any extension
  366.     }
  367.     else
  368.     {
  369.         return PathFileExistsAndAttributes(lpszPath, NULL);
  370.     }
  371.     return FALSE;
  372. }
  373. // walk through a path type string (semicolon seperated list of names)
  374. // this deals with spaces and other bad things in the path
  375. //
  376. // call with initial pointer, then continue to call with the
  377. // result pointer until it returns NULL
  378. //
  379. // input: "C:FOO;C:BAR;"
  380. //
  381. // in:
  382. //      lpPath      starting point of path string "C:foo;c:dos;c:bar"
  383. //      cchPath     size of szPath
  384. //
  385. // out:
  386. //      szPath      buffer with path piece
  387. //
  388. // returns:
  389. //      pointer to next piece to be used, NULL if done
  390. //
  391. //
  392. // BUGBUG, we should write some test cases specifically for this code
  393. //
  394. STDAPI_(LPCTSTR) NextPath(LPCTSTR lpPath, LPTSTR szPath, int cchPath)
  395. {
  396.     LPCTSTR lpEnd;
  397.     if (!lpPath)
  398.         return NULL;
  399.     // skip any leading ; in the path...
  400.     while (*lpPath == TEXT(';'))
  401.     {
  402.         lpPath++;
  403.     }
  404.     // See if we got to the end
  405.     if (*lpPath == 0)
  406.     {
  407.         // Yep
  408.         return NULL;
  409.     }
  410.     lpEnd = StrChr(lpPath, TEXT(';'));
  411.     if (!lpEnd)
  412.     {
  413.         lpEnd = lpPath + lstrlen(lpPath);
  414.     }
  415.     lstrcpyn(szPath, lpPath, min((DWORD)cchPath, (DWORD)(lpEnd - lpPath + 1)));
  416.     szPath[lpEnd-lpPath] = TEXT('');
  417.     PathRemoveBlanks(szPath);
  418.     if (szPath[0])
  419.     {
  420.         if (*lpEnd == TEXT(';'))
  421.         {
  422.             // next path string (maybe NULL)
  423.             return lpEnd + 1;
  424.         }
  425.         else
  426.         {
  427.             // pointer to NULL
  428.             return lpEnd;
  429.         }
  430.     }
  431.     else 
  432.     {
  433.         return NULL;
  434.     }
  435. }
  436. // check to see if a dir is on the other dir list
  437. // use this to avoid looking in the same directory twice (don't make the same dos call)
  438. BOOL IsOtherDir(LPCTSTR pszPath, LPCTSTR *ppszOtherDirs)
  439. {
  440.     for (;*ppszOtherDirs; ppszOtherDirs++)
  441.     {
  442.         if (lstrcmpi(pszPath, *ppszOtherDirs) == 0)
  443.         {
  444.             return TRUE;
  445.         }
  446.     }
  447.     return FALSE;
  448. }
  449. //----------------------------------------------------------------------------
  450. // fully qualify a path by walking the path and optionally other dirs
  451. //
  452. // in:
  453. //      ppszOtherDirs a list of LPCTSTRs to other paths to look
  454. //      at first, NULL terminated.
  455. //
  456. //  fExt
  457. //      PFOPEX_ flags specifying what to look for (exe, com, bat, lnk, pif)
  458. //
  459. // in/out
  460. //      pszFile     non qualified path, returned fully qualified
  461. //                      if found (return was TRUE), otherwise unaltered
  462. //                      (return FALSE);
  463. //
  464. // returns:
  465. //      TRUE        the file was found on and qualified
  466. //      FALSE       the file was not found
  467. //
  468. STDAPI_(BOOL) PathFindOnPathEx(LPTSTR pszFile, LPCTSTR* ppszOtherDirs, UINT fExt)
  469. {
  470.     TCHAR szPath[MAX_PATH];
  471.     TCHAR szFullPath[256];       // Default size for buffer
  472.     LPTSTR pszEnv = NULL;        // Use if greater than default
  473.     LPCTSTR lpPath;
  474.     int i;
  475.     RIPMSG(pszFile && IS_VALID_STRING_PTR(pszFile, -1) && IS_VALID_WRITE_BUFFER(pszFile, TCHAR, MAX_PATH), "PathFindOnPathEx: caller passed bad pszFile");
  476.     DEBUGWhackPathString(pszFile, MAX_PATH);
  477.     if (!pszFile) // REVIEW: do we need to check !*pszFile too?
  478.         return FALSE;
  479.     // REVIEW, we may want to just return TRUE here but for
  480.     // now assume only file specs are allowed
  481.     if (!PathIsFileSpec(pszFile))
  482.         return FALSE;
  483.     // first check list of other dirs
  484.     for (i = 0; ppszOtherDirs && ppszOtherDirs[i] && *ppszOtherDirs[i]; i++)
  485.     {
  486.         PathCombine(szPath, ppszOtherDirs[i], pszFile);
  487.         if (PathFileExistsDefExt(szPath, fExt))
  488.         {
  489.             lstrcpy(pszFile, szPath);
  490.             return TRUE;
  491.         }
  492.     }
  493.     // Look in system dir (system for Win95, system32 for NT)
  494.     //  - this should probably be optional.
  495.     GetSystemDirectory(szPath, ARRAYSIZE(szPath));
  496.     if (!PathAppend(szPath, pszFile))
  497.         return FALSE;
  498.     if (PathFileExistsDefExt(szPath, fExt))
  499.     {
  500.         lstrcpy(pszFile, szPath);
  501.         return TRUE;
  502.     }
  503.     if (g_bRunningOnNT)
  504.     {
  505. #ifdef WX86
  506.         // Look in WX86 system  directory (WindDirSys32x86)
  507.         if (g_bRunningOnNT5OrHigher)
  508.         {
  509.             NtCurrentTeb()->Wx86Thread.UseKnownWx86Dll = TRUE;
  510.             GetSystemDirectory(szPath, ARRAYSIZE(szPath));
  511.             NtCurrentTeb()->Wx86Thread.UseKnownWx86Dll = FALSE;
  512.             if (!PathAppend(szPath, pszFile))
  513.                 return FALSE;
  514.             if (PathFileExistsDefExt(szPath, fExt))
  515.             {
  516.                 lstrcpy(pszFile, szPath);
  517.                 return TRUE;
  518.             }
  519.         }
  520. #endif
  521.         // Look in WOW directory (ntsystem instead of ntsystem32)
  522.         GetWindowsDirectory(szPath, ARRAYSIZE(szPath));
  523.         if (!PathAppend(szPath,TEXT("System")))
  524.             return FALSE;
  525.         if (!PathAppend(szPath, pszFile))
  526.             return FALSE;
  527.         if (PathFileExistsDefExt(szPath, fExt))
  528.         {
  529.             lstrcpy(pszFile, szPath);
  530.             return TRUE;
  531.         }
  532.     }
  533. #ifdef UNIX
  534.     // AR: Varma: IEUNIX: Look in user windows dir - this should probably be optional.
  535.     MwGetUserWindowsDirectory(szPath, ARRAYSIZE(szPath));
  536.     if (!PathAppend(szPath, pszFile))
  537.         return FALSE;
  538.     if (PathFileExistsDefExt(szPath, fExt))
  539.     {
  540.         lstrcpy(pszFile, szPath);
  541.         return TRUE;
  542.     }
  543. #endif
  544.     // Look in windows dir - this should probably be optional.
  545.     GetWindowsDirectory(szPath, ARRAYSIZE(szPath));
  546.     if (!PathAppend(szPath, pszFile))
  547.         return FALSE;
  548.     if (PathFileExistsDefExt(szPath, fExt))
  549.     {
  550.         lstrcpy(pszFile, szPath);
  551.         return TRUE;
  552.     }
  553.     // Look along the path.
  554.     i = GetEnvironmentVariable(c_szPATH, szFullPath, ARRAYSIZE(szFullPath));
  555.     if (i >= ARRAYSIZE(szFullPath))
  556.     {
  557.         pszEnv = (LPTSTR)LocalAlloc(LPTR, i*SIZEOF(TCHAR)); // no need for +1, i includes it
  558.         if (pszEnv == NULL)
  559.             return FALSE;
  560.         GetEnvironmentVariable(c_szPATH, pszEnv, i);
  561.         lpPath = pszEnv;
  562.     }
  563.     else
  564.     {
  565.         if (i == 0)
  566.             return(FALSE);
  567.         lpPath = szFullPath;
  568.     }
  569.     while (NULL != (lpPath = NextPath(lpPath, szPath, ARRAYSIZE(szPath))))
  570.     {
  571.         if (!ppszOtherDirs || !IsOtherDir(szPath, ppszOtherDirs))
  572.         {
  573.             PathAppend(szPath, pszFile);
  574.             if (PathFileExistsDefExt(szPath, fExt))
  575.             {
  576.                 lstrcpy(pszFile, szPath);
  577.                 if (pszEnv)
  578.                     LocalFree((HLOCAL)pszEnv);
  579.                 return TRUE;
  580.             }
  581.         }
  582.     }
  583.     if (pszEnv)
  584.         LocalFree((HLOCAL)pszEnv);
  585.     return FALSE;
  586. }
  587. /*----------------------------------------------------------
  588. Purpose: Find the given file on the path.
  589. Returns:
  590. Cond:    --
  591. */
  592. STDAPI_(BOOL) PathFindOnPath(LPTSTR pszFile, LPCTSTR* ppszOtherDirs)
  593. {
  594.     return PathFindOnPathEx(pszFile, ppszOtherDirs, PFOPEX_NONE);
  595. }
  596. // returns a pointer to the extension of a file.
  597. //
  598. // in:
  599. //      qualified or unqualfied file name
  600. //
  601. // returns:
  602. //      pointer to the extension of this file.  if there is no extension
  603. //      as in "foo" we return a pointer to the NULL at the end
  604. //      of the file
  605. //
  606. //      foo.txt     ==> ".txt"
  607. //      foo         ==> ""
  608. //      foo.        ==> "."
  609. //
  610. STDAPI_(LPTSTR) PathFindExtension(LPCTSTR pszPath)
  611. {
  612.     LPCTSTR pszDot = NULL;
  613.     RIPMSG(pszPath && IS_VALID_STRING_PTR(pszPath, -1), "PathFindExtension: caller passed bad pszPath");
  614.     if (pszPath)
  615.     {
  616.         for (; *pszPath; pszPath = FAST_CharNext(pszPath))
  617.         {
  618.             switch (*pszPath)
  619.             {
  620.                 case TEXT('.'):
  621.                     pszDot = pszPath;   // remember the last dot
  622.                     break;
  623.                 case CH_WHACK:
  624.                 case TEXT(' '):         // extensions can't have spaces
  625.                     pszDot = NULL;      // forget last dot, it was in a directory
  626.                     break;
  627.             }
  628.         }
  629.     }
  630.     // if we found the extension, return ptr to the dot, else
  631.     // ptr to end of the string (NULL extension) (cast->non const)
  632.     return pszDot ? (LPTSTR)pszDot : (LPTSTR)pszPath;
  633. }
  634. //
  635. // Find if a given pathname contains any one of the suffixes in a given array of suffixes
  636. //
  637. // in:
  638. //      pszPath     A filename with or without a path.
  639. //
  640. //      apszSuffix   An array of suffixes that we are looking for.
  641. //
  642. // returns:
  643. //      pointer to the suffix in pszPath, if it exists.
  644. //      NULL is returned if the given path does not end with the given suffix.
  645. //
  646. //  NOTE:  This does a CASE SENSITIVE comparison!!! So, the suffix will have to match exactly.
  647. //
  648. STDAPI_(LPCTSTR) PathFindSuffixArray(LPCTSTR pszPath, const LPCTSTR* apszSuffix, int iArraySize)
  649. {
  650.     RIPMSG((iArraySize>=0 && (pszPath && IS_VALID_STRING_PTR(pszPath, -1) && apszSuffix)), "PathFindSuffixArray: caller passed bad parameters");
  651.     if (pszPath && apszSuffix)
  652.     {
  653.         int     iLenSuffix;
  654.         int     iLenPath   = lstrlen(pszPath);
  655.         LPCTSTR pszTail;
  656.         int     i;
  657.         for(i = 0; i< iArraySize; i++)
  658.         {
  659.             iLenSuffix = lstrlen(apszSuffix[i]);
  660.             if(iLenPath < iLenSuffix)
  661.                 continue;
  662.             // Let's get to a pointer to the tail piece which is the same length as the suffix
  663.             // we are looking for.
  664.             pszTail = (LPCTSTR)(pszPath+iLenPath-iLenSuffix);
  665. #ifndef UNICODE
  666.             {
  667.                 LPCSTR  pszTemp = pszTail;
  668.             
  669.                 // In the ANSI world, pszTemp could be in the middle of a DBCS character.
  670.                 // So, move pszTemp such that it points to the begining of a valid character Lead char.
  671.                 while(pszTemp > pszPath)
  672.                 {
  673.                     pszTemp--;
  674.                     if(!IsDBCSLeadByte(*pszTemp))
  675.                     {
  676.                         // Since pszTemp is pointing to the FIRST trail Byte, the next byte must be a
  677.                         // valid character. Move pszTemp to point to a valid character.
  678.                         pszTemp++;
  679.                         break;
  680.                     }
  681.                 }
  682.                 // Everything between pszTemp and pszTail is nothing but lead characters. So, see if they 
  683.                 // are Odd or Even number of them.
  684.                 if(((int)(pszTail - pszTemp)&1) && (pszTail > pszPath))
  685.                 {
  686.                     // There are odd number of lead bytes. That means that pszTail is definitely in the
  687.                     // middle of a DBCS character. Move it to such that it points to a valid char.
  688.                     pszTail--;
  689.                 }
  690.             }
  691. #endif
  692.             if(!lstrcmp(pszTail, apszSuffix[i]))
  693.                 return pszTail;
  694.         }
  695.     }
  696.     //Given suffix is not found in the array!
  697.     return NULL;
  698. }
  699. // add .exe to a file name (if no extension was already there)
  700. //
  701. // in:
  702. //      pszExtension    extension to tag on, if NULL .exe is assumed
  703. //                      (".bat", ".txt", etc)
  704. //
  705. // in/out:
  706. //      pszPath     path string to modify
  707. //
  708. //
  709. // returns:
  710. //      TRUE    added .exe (there was no extension to begin with)
  711. //      FALSE   didn't change the name (it already had an extension)
  712. STDAPI_(BOOL) PathAddExtension(LPTSTR pszPath, LPCTSTR pszExtension)
  713. {
  714.     RIPMSG(pszPath && IS_VALID_STRING_PTR(pszPath, -1) && IS_VALID_WRITE_BUFFER(pszPath, TCHAR, MAX_PATH), "PathAddExtension: caller passed bad pszPath");
  715.     RIPMSG(!pszExtension || IS_VALID_STRING_PTR(pszExtension, -1), "PathAddExtension: caller passed bad pszExtension");
  716.     DEBUGWhackPathString(pszPath, MAX_PATH);
  717.     if (pszPath)
  718.     {
  719.         if (*PathFindExtension(pszPath) == 0 && ((lstrlen(pszPath) + lstrlen(pszExtension ? pszExtension : c_szDotExe)) < MAX_PATH))
  720.         {
  721.             if (pszExtension == NULL)
  722.                 pszExtension = c_szDotExe;
  723.             lstrcat(pszPath, pszExtension);
  724.             return TRUE;
  725.         }
  726.     }
  727.     return FALSE;
  728. }
  729. /*----------------------------------------------------------
  730. Purpose: Remove the extension from pszPath, if one exists.
  731. Returns: --
  732. Cond:    --
  733. */
  734. STDAPI_(void) PathRemoveExtension(LPTSTR pszPath)
  735. {
  736.     RIPMSG(pszPath && IS_VALID_STRING_PTR(pszPath, -1), "PathRemoveExtension: caller passed bad pszPath");
  737.     if (pszPath)
  738.     {
  739.         LPTSTR pExt = PathFindExtension(pszPath);
  740.         if (*pExt)
  741.         {
  742.             ASSERT(*pExt == TEXT('.'));
  743.             *pExt = 0;    // null out the "."
  744.         }
  745.     }
  746. }
  747. /*----------------------------------------------------------
  748. Purpose: Renames the extension
  749. Returns: FALSE if not enough room
  750. Cond:    --
  751. */
  752. STDAPI_(BOOL) PathRenameExtension(LPTSTR  pszPath, LPCTSTR pszExt)
  753. {
  754.     RIPMSG(pszPath && IS_VALID_STRING_PTR(pszPath, -1) && IS_VALID_WRITE_BUFFER(pszPath, TCHAR, MAX_PATH), "PathRenameExtension: caller passed bad pszPath");
  755.     RIPMSG(pszExt && IS_VALID_STRING_PTR(pszExt, -1), "PathRenameExtension: caller passed bad pszExt");
  756.     DEBUGWhackPathString(pszPath, MAX_PATH);
  757.     if (pszPath && pszExt)
  758.     {
  759.         LPTSTR pExt = PathFindExtension(pszPath);  // Rets ptr to end of str if none
  760.         if (pExt - pszPath + lstrlen(pszExt) > MAX_PATH - 1)
  761.         {
  762.             return(FALSE);
  763.         }
  764.         lstrcpy(pExt, pszExt);
  765.         return(TRUE);
  766.     }
  767.     return FALSE;
  768. }
  769. // find the next slash or null terminator
  770. LPCTSTR StrSlash(LPCTSTR psz)
  771. {
  772.     for (; *psz && *psz != CH_WHACK; psz = FAST_CharNext(psz));
  773.     return psz;
  774. }
  775. //
  776. // in:
  777. //      pszFile1 -- fully qualified path name to file #1.
  778. //      pszFile2 -- fully qualified path name to file #2.
  779. //
  780. // out:
  781. //      pszPath  -- pointer to a string buffer (may be NULL)
  782. //
  783. // returns:
  784. //      length of output buffer not including the NULL
  785. //
  786. // examples:
  787. //      c:windesktopfoo.txt
  788. //      c:wintraybar.txt
  789. //      -> c:win
  790. //
  791. //      c:                                ;
  792. //      c:                                ;
  793. //      -> c:  NOTE, includes slash
  794. //
  795. // Returns:
  796. //      Length of the common prefix string usually does NOT include
  797. //      trailing slash, BUT for roots it does.
  798. //
  799. STDAPI_(int) PathCommonPrefix(LPCTSTR pszFile1, LPCTSTR pszFile2, LPTSTR  pszPath)
  800. {
  801.     RIPMSG(pszFile1 && IS_VALID_STRING_PTR(pszFile1, -1), "PathCommonPrefix: caller passed bad pszFile1");
  802.     RIPMSG(pszFile2 && IS_VALID_STRING_PTR(pszFile2, -1), "PathCommonPrefix: caller passed bad pszFile2");
  803.     RIPMSG(!pszPath || IS_VALID_WRITE_BUFFER(pszPath, TCHAR, MAX_PATH), "PathCommonPrefix: caller passed bad pszPath");
  804.     if (pszFile1 && pszFile2)
  805.     {
  806.         LPCTSTR psz1, psz2, pszNext1, pszNext2, pszCommon;
  807.         int cch;
  808.         pszCommon = NULL;
  809.         if (pszPath)
  810.             *pszPath = TEXT('');
  811.         psz1 = pszFile1;
  812.         psz2 = pszFile2;
  813.         // special cases for UNC, don't allow "\" to be a common prefix
  814.         if (DBL_BSLASH(pszFile1))
  815.         {
  816.             if (!DBL_BSLASH(pszFile2))
  817.                 return 0;
  818.             psz1 = pszFile1 + 2;
  819.         }
  820.         if (DBL_BSLASH(pszFile2))
  821.         {
  822.             if (!DBL_BSLASH(pszFile1))
  823.                 return 0;
  824.             psz2 = pszFile2 + 2;
  825.         }
  826.         while (1)
  827.         {
  828.             if (!(*psz1 != CH_WHACK && *psz2 != CH_WHACK))
  829.                 TraceMsg(TF_WARNING, "PathCommonPrefix: caller passed in ill-formed or non-qualified path");
  830.             pszNext1 = StrSlash(psz1);
  831.             pszNext2 = StrSlash(psz2);
  832.             cch = (int) (pszNext1 - psz1);
  833.             if (cch != (pszNext2 - psz2))
  834.                 break;      // lengths of segments not equal
  835.             if (StrIntlEqNI(psz1, psz2, cch))
  836.                 pszCommon = pszNext1;
  837.             else
  838.                 break;
  839.             ASSERT(*pszNext1 == TEXT('') || *pszNext1 == CH_WHACK);
  840.             ASSERT(*pszNext2 == TEXT('') || *pszNext2 == CH_WHACK);
  841.             if (*pszNext1 == TEXT(''))
  842.                 break;
  843.             psz1 = pszNext1 + 1;
  844.             if (*pszNext2 == TEXT(''))
  845.                 break;
  846.             psz2 = pszNext2 + 1;
  847.         }
  848.         if (pszCommon)
  849.         {
  850.             cch = (int) (pszCommon - pszFile1);
  851.             // special case the root to include the slash
  852.             if (cch == 2)
  853.             {
  854.                 ASSERT(pszFile1[1] == TEXT(':'));
  855.                 cch++;
  856.             }
  857.         }
  858.         else
  859.             cch = 0;
  860.         if (pszPath)
  861.         {
  862.             CopyMemory(pszPath, pszFile1, cch * SIZEOF(TCHAR));
  863.             pszPath[cch] = TEXT('');
  864.         }
  865.         return cch;
  866.     }
  867.     return 0;
  868. }
  869. /*----------------------------------------------------------
  870. Purpose: Returns TRUE if pszPrefix is the full prefix of pszPath.
  871. Returns:
  872. Cond:    --
  873. */
  874. STDAPI_(BOOL) PathIsPrefix(IN LPCTSTR  pszPrefix, IN LPCTSTR  pszPath)
  875. {
  876.     RIPMSG(pszPrefix && IS_VALID_STRING_PTR(pszPrefix, -1), "PathIsPrefix: caller passed bad pszPrefix");
  877.     RIPMSG(pszPath && IS_VALID_STRING_PTR(pszPath, -1), "PathIsPrefix: caller passed bad pszPath");
  878.     if (pszPrefix && pszPath)
  879.     {
  880.         int cch = PathCommonPrefix(pszPath, pszPrefix, NULL);
  881.         return (lstrlen(pszPrefix) == cch);
  882.     }
  883.     return FALSE;
  884. }
  885. static const TCHAR c_szDot[] = TEXT(".");
  886. static const TCHAR c_szDotDot[] = TEXT("..");
  887. #ifdef UNIX
  888. static const TCHAR c_szDotDotSlash[] = TEXT("../");
  889. #else
  890. static const TCHAR c_szDotDotSlash[] = TEXT("..\");
  891. #endif
  892. // in:
  893. //      pszFrom         base path, including filespec!
  894. //      pszTo           path to be relative to pszFrom
  895. // out:
  896. //      relative path to construct pszTo from the base path of pszFrom
  897. //
  898. //      c:abFileA
  899. //      c:axyFileB
  900. //      -> ..xyFileB
  901. //
  902. STDAPI_(BOOL) PathRelativePathTo(LPTSTR pszPath, LPCTSTR pszFrom, DWORD dwAttrFrom, LPCTSTR pszTo, DWORD dwAttrTo)
  903. {
  904. #ifdef DEBUG
  905.     TCHAR szFromCopy[MAX_PATH];
  906.     TCHAR szToCopy[MAX_PATH];
  907.     RIPMSG(pszPath && IS_VALID_WRITE_BUFFER(pszPath, TCHAR, MAX_PATH), "PathRelativePathTo: caller passed bad pszPath");
  908.     RIPMSG(pszFrom && IS_VALID_STRING_PTR(pszFrom, -1), "PathRelativePathTo: caller passed bad pszFrom");
  909.     RIPMSG(pszTo && IS_VALID_STRING_PTR(pszTo, -1), "PathRelativePathTo: caller passed bad pszTo");
  910.     // we make copies of the pszFrom and pszTo buffers in case one of the strings they are passing is a pointer
  911.     // inside pszPath buffer. If this were the case, it would be trampled when we call DEBUGWhackPathBuffer().
  912.     if (pszFrom)
  913.     {
  914.         lstrcpyn(szFromCopy, pszFrom, ARRAYSIZE(szFromCopy));
  915.         pszFrom = szFromCopy;
  916.     }
  917.     
  918.     if (pszTo)
  919.     {
  920.         lstrcpyn(szToCopy, pszTo, ARRAYSIZE(szToCopy));
  921.         pszTo = szToCopy;
  922.     }
  923. #endif DEBUG
  924.     if (pszPath && pszFrom && pszTo)
  925.     {
  926.         TCHAR szFrom[MAX_PATH], szTo[MAX_PATH];
  927.         LPTSTR psz;
  928.         UINT cchCommon;
  929.         DEBUGWhackPathBuffer(pszPath, MAX_PATH);
  930.         *pszPath = 0;       // assume none
  931.         lstrcpyn(szFrom, pszFrom, ARRAYSIZE(szFrom));
  932.         lstrcpyn(szTo, pszTo, ARRAYSIZE(szTo));
  933.         if (!(dwAttrFrom & FILE_ATTRIBUTE_DIRECTORY))
  934.             PathRemoveFileSpec(szFrom);
  935.         if (!(dwAttrTo & FILE_ATTRIBUTE_DIRECTORY))
  936.             PathRemoveFileSpec(szTo);
  937.         cchCommon = PathCommonPrefix(szFrom, szTo, NULL);
  938.         if (cchCommon == 0)
  939.             return FALSE;
  940.         psz = szFrom + cchCommon;
  941.         if (*psz)
  942.         {
  943.             // build .... part of the path
  944.             if (*psz == CH_WHACK)
  945.                 psz++;              // skip slash
  946.             while (*psz)
  947.             {
  948.                 psz = PathFindNextComponent(psz);
  949.                 // BUGBUG: in a degenerate case where each path component
  950.                 // is 1 character (less than "..") we can overflow pszPath
  951.                 lstrcat(pszPath, *psz ? c_szDotDotSlash : c_szDotDot);
  952.             }
  953.         }
  954.         else
  955.         {
  956.             lstrcpy(pszPath, c_szDot);
  957.         }
  958.         if (pszTo[cchCommon])
  959.         {
  960.             // deal with root case
  961.             if (pszTo[cchCommon] != CH_WHACK)
  962.                 cchCommon--;
  963.             if ((lstrlen(pszPath) + lstrlen(pszTo + cchCommon)) >= MAX_PATH)
  964.             {
  965.                 TraceMsg(TF_ERROR, "PathRelativePathTo: path won't fit in buffer");
  966.                 *pszPath = 0;
  967.                 return FALSE;
  968.             }
  969.             ASSERT(pszTo[cchCommon] == CH_WHACK);
  970.             lstrcat(pszPath, pszTo + cchCommon);
  971.         }
  972.         ASSERT(PathIsRelative(pszPath));
  973.         ASSERT(lstrlen(pszPath) < MAX_PATH);
  974.         return TRUE;
  975.     }
  976.     return FALSE;
  977. }
  978. /*----------------------------------------------------------
  979. Purpose: Build a root path name given a drive number.
  980. Returns: szRoot
  981. */
  982. STDAPI_(LPTSTR) PathBuildRoot(LPTSTR szRoot, int iDrive)
  983. {
  984.     RIPMSG(szRoot && IS_VALID_WRITE_BUFFER(szRoot, TCHAR, 4), "PathBuildRoot: caller passed bad szRoot");
  985.     RIPMSG(iDrive >= 0 && iDrive < 26, "PathBuildRoot: caller passed bad iDrive");
  986.     if (szRoot && iDrive >= 0 && iDrive < 26)
  987.     {
  988. #ifndef UNIX
  989.         szRoot[0] = (TCHAR)iDrive + (TCHAR)TEXT('A');
  990.         szRoot[1] = TEXT(':');
  991.         szRoot[2] = TEXT('\');
  992.         szRoot[3] = 0;
  993. #else
  994.         szRoot[0] = CH_WHACK;
  995.         szRoot[1] = 0;
  996. #endif
  997.     }
  998.     return szRoot;
  999. }
  1000. // Strips leading and trailing blanks from a string.
  1001. // Alters the memory where the string sits.
  1002. //
  1003. // in:
  1004. //  lpszString  string to strip
  1005. //
  1006. // out:
  1007. //  lpszString  string sans leading/trailing blanks
  1008. //
  1009. STDAPI_(void) PathRemoveBlanks(LPTSTR lpszString)
  1010. {
  1011.     RIPMSG(lpszString && IS_VALID_STRING_PTR(lpszString, -1), "PathRemoveBlanks: caller passed bad lpszString");
  1012.     if (lpszString)
  1013.     {
  1014.         LPTSTR lpszPosn = lpszString;
  1015.         /* strip leading blanks */
  1016.         while (*lpszPosn == TEXT(' '))
  1017.         {
  1018.             lpszPosn++;
  1019.         }
  1020.         if (lpszPosn != lpszString)
  1021.         {
  1022.             lstrcpy(lpszString, lpszPosn);
  1023.         }
  1024.         /* strip trailing blanks */
  1025.         // Find the last non-space
  1026.         // Note that AnsiPrev is cheap is non-DBCS, but very expensive otherwise
  1027.         for (lpszPosn=lpszString; *lpszString; lpszString=FAST_CharNext(lpszString))
  1028.         {
  1029.             if (*lpszString != TEXT(' '))
  1030.             {
  1031.                 lpszPosn = lpszString;
  1032.             }
  1033.         }
  1034.         // Note AnsiNext is a macro for non-DBCS, so it will not stop at NULL
  1035.         if (*lpszPosn)
  1036.         {
  1037.             *FAST_CharNext(lpszPosn) = TEXT('');
  1038.         }
  1039.     }
  1040. }
  1041. // Removes a trailing backslash from a path
  1042. //
  1043. // in:
  1044. //  lpszPath    (A:, C:foo, etc)
  1045. //
  1046. // out:
  1047. //  lpszPath    (A:, C:foo, etc)
  1048. //
  1049. // returns:
  1050. //  ponter to NULL that replaced the backslash
  1051. //  or the pointer to the last character if it isn't a backslash.
  1052. //
  1053. STDAPI_(LPTSTR) PathRemoveBackslash(LPTSTR lpszPath)
  1054. {
  1055.     RIPMSG(lpszPath && IS_VALID_STRING_PTR(lpszPath, -1), "PathRemoveBackslash: caller passed bad lpszPath");
  1056.     if (lpszPath)
  1057.     {
  1058.         int len = lstrlen(lpszPath)-1;
  1059.         if (IsDBCSLeadByte(*CharPrev(lpszPath,lpszPath+len+1)))
  1060.             len--;
  1061.         if (!PathIsRoot(lpszPath) && lpszPath[len] == CH_WHACK)
  1062.             lpszPath[len] = TEXT('');
  1063.         return lpszPath + len;
  1064.     }
  1065.     return NULL;
  1066. }
  1067. //
  1068. // Return a pointer to the end of the next path componenent in the string.
  1069. // ie return a pointer to the next backslash or terminating NULL.
  1070. //
  1071. LPCTSTR GetPCEnd(LPCTSTR lpszStart)
  1072. {
  1073.     LPCTSTR lpszEnd;
  1074.     lpszEnd = StrChr(lpszStart, CH_WHACK);
  1075.     if (!lpszEnd)
  1076.     {
  1077.         lpszEnd = lpszStart + lstrlen(lpszStart);
  1078.     }
  1079.     return lpszEnd;
  1080. }
  1081. //
  1082. // Given a pointer to the end of a path component, return a pointer to
  1083. // its begining.
  1084. // ie return a pointer to the previous backslash (or start of the string).
  1085. //
  1086. LPCTSTR PCStart(LPCTSTR lpszStart, LPCTSTR lpszEnd)
  1087. {
  1088.     LPCTSTR lpszBegin = StrRChr(lpszStart, lpszEnd, CH_WHACK);
  1089.     if (!lpszBegin)
  1090.     {
  1091.         lpszBegin = lpszStart;
  1092.     }
  1093.     return lpszBegin;
  1094. }
  1095. //
  1096. // Fix up a few special cases so that things roughly make sense.
  1097. //
  1098. void NearRootFixups(LPTSTR lpszPath, BOOL fUNC)
  1099. {
  1100.     // Check for empty path.
  1101.     if (lpszPath[0] == TEXT(''))
  1102.     {
  1103.         // Fix up.
  1104.         lpszPath[0] = CH_WHACK;
  1105.         lpszPath[1] = TEXT('');
  1106.     }
  1107.     // Check for missing slash.
  1108.     if (!IsDBCSLeadByte(lpszPath[0]) && lpszPath[1] == TEXT(':') && lpszPath[2] == TEXT(''))
  1109.     {
  1110.         // Fix up.
  1111.         lpszPath[2] = TEXT('\');
  1112.         lpszPath[3] = TEXT('');
  1113.     }
  1114.     // Check for UNC root.
  1115.     if (fUNC && lpszPath[0] == TEXT('\') && lpszPath[1] == TEXT(''))
  1116.     {
  1117.         // Fix up.
  1118.         //lpszPath[0] = TEXT('\'); // already checked in if guard
  1119.         lpszPath[1] = TEXT('\');
  1120.         lpszPath[2] = TEXT('');
  1121.     }
  1122. }
  1123. /*----------------------------------------------------------
  1124. Purpose: Canonicalize a path.
  1125. Returns:
  1126. Cond:    --
  1127. */
  1128. STDAPI_(BOOL) PathCanonicalize(LPTSTR lpszDst, LPCTSTR lpszSrc)
  1129. {
  1130.     LPCTSTR lpchSrc;
  1131.     LPCTSTR lpchPCEnd;      // Pointer to end of path component.
  1132.     LPTSTR lpchDst;
  1133.     BOOL fUNC;
  1134.     int cbPC;
  1135.     RIPMSG(lpszDst && IS_VALID_WRITE_BUFFER(lpszDst, TCHAR, MAX_PATH), "PathCanonicalize: caller passed bad lpszDst");
  1136.     RIPMSG(lpszSrc && IS_VALID_STRING_PTR(lpszSrc, -1), "PathCanonicalize: caller passed bad lpszSrc");
  1137.     if (!lpszDst || !lpszSrc)
  1138.     {
  1139.         SetLastError(ERROR_INVALID_PARAMETER);
  1140.         return FALSE;
  1141.     }
  1142.     DEBUGWhackPathBuffer(lpszDst, MAX_PATH);
  1143.     
  1144.     fUNC = PathIsUNC(lpszSrc);    // Check for UNCness.
  1145.     // Init.
  1146.     lpchSrc = lpszSrc;
  1147.     lpchDst = lpszDst;
  1148.     while (*lpchSrc)
  1149.     {
  1150.         // REVIEW: this should just return the count
  1151.         lpchPCEnd = GetPCEnd(lpchSrc);
  1152.         cbPC = (int) (lpchPCEnd - lpchSrc)+1;
  1153.         if (cbPC == 1 && *lpchSrc == CH_WHACK)                                      // Check for slashes.
  1154.         {
  1155.             // Just copy them.
  1156.             *lpchDst = CH_WHACK;
  1157.             lpchDst++;
  1158.             lpchSrc++;
  1159.         }
  1160.         else if (cbPC == 2 && *lpchSrc == TEXT('.'))                                // Check for dots.
  1161.         {
  1162.             // Skip it...
  1163.             // Are we at the end?
  1164.             if (*(lpchSrc+1) == TEXT(''))
  1165.             {
  1166.                 lpchDst--;
  1167.                 lpchSrc++;
  1168.             }
  1169.             else
  1170.             {
  1171.                 lpchSrc += 2;
  1172.             }
  1173.         }
  1174.         else if (cbPC == 3 && *lpchSrc == TEXT('.') && *(lpchSrc + 1) == TEXT('.')) // Check for dot dot.
  1175.         {
  1176.             // make sure we aren't already at the root
  1177.             if (!PathIsRoot(lpszDst))
  1178.             {
  1179.                 // Go up... Remove the previous path component.
  1180.                 lpchDst = (LPTSTR)PCStart(lpszDst, lpchDst - 1);
  1181.             }
  1182.             else
  1183.             {
  1184.                 // When we can't back up, remove the trailing backslash
  1185.                 // so we don't copy one again. (C:..FOO would otherwise
  1186.                 // turn into C:\FOO).
  1187.                 if (*(lpchSrc + 2) == CH_WHACK)
  1188.                 {
  1189.                     lpchSrc++;
  1190.                 }
  1191.             }
  1192.             // skip ".."
  1193.             lpchSrc += 2;       
  1194.         }
  1195.         else                                                                        // Everything else
  1196.         {
  1197.             // Just copy it.
  1198.             lstrcpyn(lpchDst, lpchSrc, cbPC);
  1199.             lpchDst += cbPC - 1;
  1200.             lpchSrc += cbPC - 1;
  1201.         }
  1202.         // Keep everything nice and tidy.
  1203.         *lpchDst = TEXT('');
  1204.     }
  1205.     // Check for weirdo root directory stuff.
  1206.     NearRootFixups(lpszDst, fUNC);
  1207.     return TRUE;
  1208. }
  1209. // Modifies:
  1210. //      szRoot
  1211. //
  1212. // Returns:
  1213. //      TRUE if a drive root was found
  1214. //      FALSE otherwise
  1215. //
  1216. STDAPI_(BOOL) PathStripToRoot(LPTSTR szRoot)
  1217. {
  1218.     RIPMSG(szRoot && IS_VALID_STRING_PTR(szRoot, -1), "PathStripToRoot: caller passed bad szRoot");
  1219.     if (szRoot)
  1220.     {
  1221.         while(!PathIsRoot(szRoot))
  1222.         {
  1223.             if (!PathRemoveFileSpec(szRoot))
  1224.             {
  1225.                 // If we didn't strip anything off,
  1226.                 // must be current drive
  1227.                 return(FALSE);
  1228.             }
  1229.         }
  1230.         return(TRUE);
  1231.     }
  1232.     return(FALSE);
  1233. }
  1234. /*----------------------------------------------------------
  1235. Purpose: Concatenate lpszDir and lpszFile into a properly formed
  1236.          path and canonicalize any relative path pieces.
  1237.          lpszDest and lpszFile can be the same buffer
  1238.          lpszDest and lpszDir can be the same buffer
  1239. Returns: pointer to lpszDest
  1240. */
  1241. STDAPI_(LPTSTR) PathCombine(LPTSTR lpszDest, LPCTSTR lpszDir, LPCTSTR lpszFile)
  1242. {
  1243. #ifdef DEBUG
  1244.     TCHAR szDirCopy[MAX_PATH];
  1245.     TCHAR szFileCopy[MAX_PATH];
  1246.     RIPMSG(lpszDest && IS_VALID_WRITE_BUFFER(lpszDest, TCHAR, MAX_PATH), "PathCombine: caller passed bad lpszDest");
  1247.     RIPMSG(!lpszDir || IS_VALID_STRING_PTR(lpszDir, -1), "PathCombine: caller passed bad lpszDir");
  1248.     RIPMSG(!lpszFile || IS_VALID_STRING_PTR(lpszFile, -1), "PathCombine: caller passed bad lpszFile");
  1249.     RIPMSG(lpszDir || lpszFile, "PathCombine: caller neglected to pass lpszDir or lpszFile");
  1250.     // we make copies of all the lpszDir and lpszFile buffers in case one of the strings they are passing is a pointer
  1251.     // inside lpszDest buffer. If this were the case, it would be trampled when we call DEBUGWhackPathBuffer().
  1252.     if (lpszDir)
  1253.     {
  1254.         lstrcpyn(szDirCopy, lpszDir, ARRAYSIZE(szDirCopy));
  1255.         lpszDir = szDirCopy;
  1256.     }
  1257.     
  1258.     if (lpszFile)
  1259.     {
  1260.         lstrcpyn(szFileCopy, lpszFile, ARRAYSIZE(szFileCopy));
  1261.         lpszFile = szFileCopy;
  1262.     }
  1263.     // use DEBUGWhackPathString since lpszDest could be the same as plszDir
  1264.     DEBUGWhackPathString(lpszDest, MAX_PATH);
  1265. #endif DEBUG
  1266.     if (lpszDest && (lpszDir || lpszFile))
  1267.     {
  1268.         TCHAR szTemp[MAX_PATH];
  1269.         LPTSTR pszT;
  1270.         if (!lpszFile || *lpszFile==TEXT(''))
  1271.         {
  1272.             lstrcpyn(szTemp, lpszDir, ARRAYSIZE(szTemp));       // lpszFile is empty
  1273.         }
  1274.         else if (lpszDir && *lpszDir && PathIsRelative(lpszFile))
  1275.         {
  1276.             lstrcpyn(szTemp, lpszDir, ARRAYSIZE(szTemp));
  1277.             pszT = PathAddBackslash(szTemp);
  1278.             if (pszT)
  1279.             {
  1280.                 int iLen = lstrlen(szTemp);
  1281.                 if ((iLen + lstrlen(lpszFile)) < ARRAYSIZE(szTemp))
  1282.                     lstrcpy(pszT, lpszFile);
  1283.                 else
  1284.                     return NULL;
  1285.             }
  1286.             else
  1287.             {
  1288.                 return NULL;
  1289.             }
  1290.         }
  1291.         else if (lpszDir && *lpszDir && *lpszFile == CH_WHACK && !PathIsUNC(lpszFile))
  1292.         {
  1293.             lstrcpyn(szTemp, lpszDir, ARRAYSIZE(szTemp));
  1294.             // BUGBUG: Note that we do not check that an actual root is returned;
  1295.             // it is assumed that we are given valid parameters
  1296.             PathStripToRoot(szTemp);
  1297.             pszT = PathAddBackslash(szTemp);
  1298.             if (pszT)
  1299.             {
  1300.                 // Skip the backslash when copying
  1301.                 // Note: We don't support strings longer than 4GB, but that's
  1302.                 // okay because we already barf at MAX_PATH
  1303.                 lstrcpyn(pszT, lpszFile+1, ARRAYSIZE(szTemp) - 1 - (int)(pszT-szTemp));
  1304.             }
  1305.             else
  1306.             {
  1307.                 return NULL;
  1308.             }
  1309.         }
  1310.         else 
  1311.         {
  1312.             lstrcpyn(szTemp, lpszFile, ARRAYSIZE(szTemp));     // already fully qualified file part
  1313.         }
  1314.         PathCanonicalize(lpszDest, szTemp); // this deals with .. and . stuff
  1315.     }
  1316.     return lpszDest;
  1317. }
  1318. /*----------------------------------------------------------
  1319. Purpose: Appends a filename to a path.  Checks the  problem first
  1320.           (which is why one can't just use lstrcat())
  1321.          Also don't append a  to : so we can have drive-relative paths...
  1322.          this last bit is no longer appropriate since we qualify first!
  1323. Returns:
  1324. */
  1325. STDAPI_(BOOL) PathAppend(LPTSTR pszPath, LPCTSTR pszMore)
  1326. {
  1327.     RIPMSG(pszPath && IS_VALID_STRING_PTR(pszPath, -1) && IS_VALID_WRITE_BUFFER(pszPath, TCHAR, MAX_PATH), "PathAppend: caller passed bad pszPath");
  1328.     RIPMSG(pszMore && IS_VALID_STRING_PTR(pszMore, -1), "PathAppend: caller passed bad pszMore");
  1329.     // PathCombine will do this for us: DEBUGWhackPathString(pszPath, MAX_PATH);
  1330.     if (pszPath && pszMore)
  1331.     {
  1332.         // Skip any initial terminators on input, unless it is a UNC path in wich case we will 
  1333.         // treat it as a full path
  1334.         if (!PathIsUNC(pszMore))
  1335.         {
  1336.             while (*pszMore == CH_WHACK)
  1337.             {
  1338. #ifndef UNICODE
  1339.                 pszMore = FAST_CharNext(pszMore);
  1340. #else
  1341.                 pszMore++;
  1342. #endif
  1343.             }
  1344.         }
  1345.         return PathCombine(pszPath, pszPath, pszMore) ? TRUE : FALSE;
  1346.     }
  1347.     
  1348.     return FALSE;
  1349. }
  1350. // rips the last part of the path off including the backslash
  1351. //      C:foo      -> C:
  1352. //      C:foobar  -> C:foo
  1353. //      C:foo     -> C:foo
  1354. //      \xyx     -> \xy
  1355. //      \xy       -> \x
  1356. //      \x         -> \ (Just the double slash!)
  1357. //      foo        ->   (Just the slash!)
  1358. //
  1359. // in/out:
  1360. //      pFile   fully qualified path name
  1361. // returns:
  1362. //      TRUE    we stripped something
  1363. //      FALSE   didn't strip anything (root directory case)
  1364. //
  1365. STDAPI_(BOOL) PathRemoveFileSpec(LPTSTR pFile)
  1366. {
  1367.     RIPMSG(pFile && IS_VALID_STRING_PTR(pFile, -1), "PathRemoveFileSpec: caller passed bad pFile");
  1368.     if (pFile)
  1369.     {
  1370.         LPTSTR pT;
  1371.         LPTSTR pT2 = pFile;
  1372.         for (pT = pT2; *pT2; pT2 = FAST_CharNext(pT2))
  1373.         {
  1374.             if (*pT2 == CH_WHACK)
  1375.             {
  1376.                 pT = pT2;             // last "" found, (we will strip here)
  1377.             }
  1378.             else if (*pT2 == TEXT(':'))     // skip ":" so we don't
  1379.             {
  1380.                 if (pT2[1] ==TEXT('\'))    // strip the "" from "C:"
  1381.                 {
  1382.                     pT2++;
  1383.                 }
  1384.                 pT = pT2 + 1;
  1385.             }
  1386.         }
  1387.         if (*pT == 0)
  1388.         {
  1389.             // didn't strip anything
  1390.             return FALSE;
  1391.         }
  1392.         else if (((pT == pFile) && (*pT == CH_WHACK)) ||                        //  is it the "foo" case?
  1393.                  ((pT == pFile+1) && (*pT == CH_WHACK && *pFile == CH_WHACK)))  //  or the "\bar" case?
  1394.         {
  1395.             // Is it just a ''?
  1396.             if (*(pT+1) != TEXT(''))
  1397.             {
  1398.                 // Nope.
  1399.                 *(pT+1) = TEXT('');
  1400.                 return TRUE;        // stripped something
  1401.             }
  1402.             else
  1403.             {
  1404.                 // Yep.
  1405.                 return FALSE;
  1406.             }
  1407.         }
  1408.         else
  1409.         {
  1410.             *pT = 0;
  1411.             return TRUE;    // stripped something
  1412.         }
  1413.     }
  1414.     return  FALSE;
  1415. }
  1416. // add a backslash to a qualified path
  1417. //
  1418. // in:
  1419. //  lpszPath    path (A:, C:foo, etc)
  1420. //
  1421. // out:
  1422. //  lpszPath    A:, C:foo    ;
  1423. //
  1424. // returns:
  1425. //  pointer to the NULL that terminates the path
  1426. //
  1427. STDAPI_(LPTSTR) PathAddBackslash(LPTSTR lpszPath)
  1428. {
  1429.     RIPMSG(lpszPath && IS_VALID_STRING_PTR(lpszPath, -1), "PathAddBackslash: caller passed bad lpszPath");
  1430.     if (lpszPath)
  1431.     {
  1432.         LPTSTR lpszEnd;
  1433.         // perf: avoid lstrlen call for guys who pass in ptr to end
  1434.         // of buffer (or rather, EOB - 1).
  1435.         // note that such callers need to check for overflow themselves.
  1436.         int ichPath = (*lpszPath && !*(lpszPath + 1)) ? 1 : lstrlen(lpszPath);
  1437.         // try to keep us from tromping over MAX_PATH in size.
  1438.         // if we find these cases, return NULL.  Note: We need to
  1439.         // check those places that call us to handle their GP fault
  1440.         // if they try to use the NULL!
  1441.         if (ichPath >= (MAX_PATH - 1))
  1442.         {
  1443.             RIPMSG(FALSE, "PathAddBackslash: caller passed in lpszPath >= MAX_PATH-1");
  1444.             return(NULL);
  1445.         }
  1446.         lpszEnd = lpszPath + ichPath;
  1447.         // this is really an error, caller shouldn't pass
  1448.         // an empty string
  1449.         if (!*lpszPath)
  1450.             return lpszEnd;
  1451.         // Get the end of the source directory
  1452.         switch(*CharPrev(lpszPath, lpszEnd))
  1453.         {
  1454.             case CH_WHACK:
  1455.                 break;
  1456.             default:
  1457.                 *lpszEnd++ = CH_WHACK;
  1458.                 *lpszEnd = TEXT('');
  1459.         }
  1460.         return lpszEnd;
  1461.     }
  1462.     return NULL;
  1463. }
  1464. // Returns a pointer to the last component of a path string.
  1465. //
  1466. // in:
  1467. //      path name, either fully qualified or not
  1468. //
  1469. // returns:
  1470. //      pointer into the path where the path is.  if none is found
  1471. //      returns a poiter to the start of the path
  1472. //
  1473. //  c:foobar  -> bar
  1474. //  c:foo      -> foo
  1475. //  c:foo     -> c:foo      (REVIEW: is this case busted?)
  1476. //  c:         -> c:          (REVIEW: this case is strange)
  1477. //  c:          -> c:
  1478. //  foo         -> foo
  1479. //
  1480. STDAPI_(LPTSTR) PathFindFileName(LPCTSTR pPath)
  1481. {
  1482.     LPCTSTR pT = pPath;
  1483.     
  1484.     RIPMSG(pPath && IS_VALID_STRING_PTR(pPath, -1), "PathFindFileName: caller passed bad pPath");
  1485.     if (pPath)
  1486.     {
  1487.         for ( ; *pPath; pPath = FAST_CharNext(pPath))
  1488.         {
  1489.             if ((pPath[0] == TEXT('\') || pPath[0] == TEXT(':') || pPath[0] == TEXT('/'))
  1490.                 && pPath[1] &&  pPath[1] != TEXT('\')  &&   pPath[1] != TEXT('/'))
  1491.                 pT = pPath + 1;
  1492.         }
  1493.     }
  1494.     return (LPTSTR)pT;   // const -> non const
  1495. }
  1496. // determine if a path is just a filespec (contains no path parts)
  1497. //
  1498. // REVIEW: we may want to count the # of elements, and make sure
  1499. // there are no illegal chars, but that is probably another routing
  1500. // PathIsValid()
  1501. //
  1502. // in:
  1503. //      lpszPath    path to look at
  1504. // returns:
  1505. //      TRUE        no ":" or "" chars in this path
  1506. //      FALSE       there are path chars in there
  1507. //
  1508. //
  1509. STDAPI_(BOOL) PathIsFileSpec(LPCTSTR lpszPath)
  1510. {
  1511.     RIPMSG(lpszPath && IS_VALID_STRING_PTR(lpszPath, -1), "PathIsFileSpec: caller passed bad lpszPath");
  1512.     if (lpszPath)
  1513.     {
  1514.         for (; *lpszPath; lpszPath = FAST_CharNext(lpszPath))
  1515.         {
  1516.             if (*lpszPath == CH_WHACK || *lpszPath == TEXT(':'))
  1517.                 return FALSE;
  1518.         }
  1519.         return TRUE;
  1520.     }
  1521.     return FALSE;
  1522. }
  1523. //---------------------------------------------------------------------------
  1524. // Returns TRUE if the given string is a UNC path.
  1525. //
  1526. // TRUE
  1527. //      "\foobar"
  1528. //      "\foo"         <- careful
  1529. //      "\"
  1530. // FALSE
  1531. //      "foo"
  1532. //      "foo"
  1533. //      "c:foo"
  1534. //
  1535. //
  1536. STDAPI_(BOOL) PathIsUNC(LPCTSTR pszPath)
  1537. {
  1538.     RIPMSG(pszPath && IS_VALID_STRING_PTR(pszPath, -1), "PathIsUNC: caller passed bad pszPath");
  1539.     if (pszPath)
  1540.     {
  1541.         return DBL_BSLASH(pszPath);
  1542.     }
  1543.     return FALSE;
  1544. }
  1545. //---------------------------------------------------------------------------
  1546. // Returns TRUE if the given string is a path that is on a mounted network drive    
  1547. //
  1548. // Cond:    Calls SHELL32's IsNetDrive function
  1549. //
  1550. STDAPI_(BOOL) PathIsNetworkPath(LPCTSTR pszPath)
  1551. {
  1552.     RIPMSG(pszPath && IS_VALID_STRING_PTR(pszPath, -1), "PathIsNetworkPath: caller passed bad pszPath");
  1553.     if (pszPath)
  1554.     {
  1555.         return DBL_BSLASH(pszPath) || _IsNetDrive(PathGetDriveNumber(pszPath));
  1556.     }
  1557.     return FALSE;
  1558. }
  1559. //---------------------------------------------------------------------------
  1560. // Returns TRUE if the given string is a UNC path to a server only (no share name).
  1561. //
  1562. // TRUE
  1563. //      "\foo"         <- careful
  1564. //      "\"
  1565. // FALSE
  1566. //      "\foobar"
  1567. //      "foo"
  1568. //      "foo"
  1569. //      "c:foo"
  1570. //
  1571. STDAPI_(BOOL) PathIsUNCServer(LPCTSTR pszPath)
  1572. {
  1573.     RIPMSG(pszPath && IS_VALID_STRING_PTR(pszPath, -1), "PathIsUNCServer: caller passed bad pszPath");
  1574.     if (pszPath)
  1575.     {
  1576.         if (DBL_BSLASH(pszPath))
  1577.         {
  1578.             int i = 0;
  1579.             LPTSTR szTmp;
  1580.             for (szTmp = (LPTSTR)pszPath; szTmp && *szTmp; szTmp = FAST_CharNext(szTmp) )
  1581.             {
  1582.                 if (*szTmp==TEXT('\'))
  1583.                 {
  1584.                     i++;
  1585.                 }
  1586.             }
  1587.             return (i == 2);
  1588.         }
  1589.     }
  1590.     return FALSE;
  1591. }
  1592. //---------------------------------------------------------------------------
  1593. // Returns TRUE if the given string is a UNC path to a servershare only.
  1594. //
  1595. // TRUE
  1596. //      "\foobar"         <- careful
  1597. // FALSE
  1598. //      "\foobarbar"
  1599. //      "foo"
  1600. //      "foo"
  1601. //      "c:foo"
  1602. //
  1603. STDAPI_(BOOL) PathIsUNCServerShare(LPCTSTR pszPath)
  1604. {
  1605.     RIPMSG(pszPath && IS_VALID_STRING_PTR(pszPath, -1), "PathIsUNCServerShare: caller passed bad pszPath");
  1606.     if (pszPath)
  1607.     {
  1608.         if (DBL_BSLASH(pszPath))
  1609.         {
  1610.             int i = 0;
  1611.             LPTSTR szTmp;
  1612.             for (szTmp = (LPTSTR)pszPath; szTmp && *szTmp; szTmp = FAST_CharNext(szTmp) )
  1613.             {
  1614.                 if (*szTmp==TEXT('\'))
  1615.                 {
  1616.                     i++;
  1617.                 }
  1618.             }
  1619.             return (i == 3);
  1620.         }
  1621.     }
  1622.     return FALSE;
  1623. }
  1624. //---------------------------------------------------------------------------
  1625. // Returns 0 through 25 (corresponding to 'A' through 'Z') if the path has
  1626. // a drive letter, otherwise returns -1.
  1627. //
  1628. //
  1629. STDAPI_(int) PathGetDriveNumber(LPCTSTR lpsz)
  1630. {
  1631.     RIPMSG(lpsz && IS_VALID_STRING_PTR(lpsz, -1), "PathGetDriveNumber: caller passed bad lpsz");
  1632.     if (lpsz)
  1633.     {
  1634.         if (!IsDBCSLeadByte(lpsz[0]) && lpsz[1] == TEXT(':'))
  1635.         {
  1636.             if (lpsz[0] >= TEXT('a') && lpsz[0] <= TEXT('z'))
  1637.             {
  1638.                 return (lpsz[0] - TEXT('a'));
  1639.             }
  1640.             else if (lpsz[0] >= TEXT('A') && lpsz[0] <= TEXT('Z'))
  1641.             {
  1642.                 return (lpsz[0] - TEXT('A'));
  1643.             }
  1644.         }
  1645.     }
  1646.     return -1;
  1647. }
  1648. //---------------------------------------------------------------------------
  1649. // Return TRUE if the path isn't absoulte.
  1650. //
  1651. // TRUE
  1652. //      "foo.exe"
  1653. //      ".foo.exe"
  1654. //      "..boofoo.exe"
  1655. //
  1656. // FALSE
  1657. //      "foo"
  1658. //      "c:bar"     <- be careful
  1659. //      "c:bar"
  1660. //      "\foobar"
  1661. //
  1662. STDAPI_(BOOL) PathIsRelative(LPCTSTR lpszPath)
  1663. {
  1664.     RIPMSG(lpszPath && IS_VALID_STRING_PTR(lpszPath, -1), "PathIsRelative: caller passed bad lpszPath");
  1665.     if (!lpszPath || *lpszPath == 0)
  1666.     {
  1667.         // The NULL path is assumed relative
  1668.         return TRUE;
  1669.     }
  1670.     if (lpszPath[0] == CH_WHACK)
  1671.     {
  1672.         // Does it begin with a slash ?
  1673.         return FALSE;
  1674.     }
  1675.     else if (!IsDBCSLeadByte(lpszPath[0]) && lpszPath[1] == TEXT(':'))
  1676.     {
  1677.         // Does it begin with a drive and a colon ?
  1678.         return FALSE;
  1679.     }
  1680.     else
  1681.     {
  1682.         // Probably relative.
  1683.         return TRUE;
  1684.     }
  1685. }
  1686. // remove the path part from a fully qualified spec
  1687. //
  1688. // c:foobar   -> bar
  1689. // c:foo       -> foo
  1690. // c:          -> c: and the like
  1691. //
  1692. STDAPI_(void) PathStripPath(LPTSTR pszPath)
  1693. {
  1694.     RIPMSG(pszPath && IS_VALID_STRING_PTR(pszPath, -1), "PathStripPath: caller passed bad pszPath");
  1695.     if (pszPath)
  1696.     {
  1697.         LPTSTR pszName = PathFindFileName(pszPath);
  1698.         if (pszName != pszPath)
  1699.         {
  1700.             lstrcpy(pszPath, pszName);
  1701.         }
  1702.     }
  1703. }
  1704. // replaces forward slashes with backslashes
  1705. // and removes trailing colons from device names
  1706. // excluding drive
  1707. // removes trailing colon if not a drive letter.
  1708. // this is to support DOS character devices (CON:, COM1: LPT1:).  DOS
  1709. // can't deal with these things having a colon on the end (so we strip it).
  1710. //
  1711. STDAPI_(void) FixSlashesAndColon(LPTSTR pszPath)
  1712. {
  1713.     LPTSTR lpLast;
  1714.     int cbPath;
  1715.     // walk the entire path string, keep track of last
  1716.     // char in the path
  1717.     for (cbPath = 0; *pszPath; pszPath = FAST_CharNext(pszPath))
  1718.     {
  1719.         lpLast = pszPath;
  1720. #ifdef UNIX
  1721.         if (*pszPath == TEXT('\'))
  1722. #else
  1723.         if (*pszPath == TEXT('/'))
  1724. #endif
  1725.         {
  1726.             *pszPath = CH_WHACK;
  1727.         }
  1728.     }
  1729.     // if not a drive letter "C:" nuke the colon
  1730.     if (cbPath > 2 && *lpLast == TEXT(':'))
  1731.     {
  1732.         *lpLast = 0;
  1733.     }
  1734. }
  1735. #ifdef DEBUG
  1736. BOOL IsFullPath(LPCTSTR pcszPath)
  1737. {
  1738.     BOOL bResult = FALSE;
  1739.     TCHAR rgchFullPath[MAX_PATH];
  1740.     if (IS_VALID_STRING_PTR(pcszPath, -1) && EVAL(lstrlen(pcszPath) < MAX_PATH))
  1741.     {
  1742.         DWORD dwPathLen;
  1743.         LPTSTR pszFileName;
  1744.         dwPathLen = GetFullPathName(pcszPath, SIZECHARS(rgchFullPath),
  1745.                                     rgchFullPath, &pszFileName);
  1746.         if (EVAL(dwPathLen > 0) &&
  1747.             EVAL(dwPathLen < SIZECHARS(rgchFullPath)))
  1748.             bResult = EVAL(! lstrcmpi(pcszPath, rgchFullPath));
  1749.     }
  1750.     return(bResult);
  1751. }
  1752. #endif // DEBUG
  1753. /*----------------------------------------------------------
  1754. Purpose: Fully qualify a path and search for it.
  1755. Returns: TRUE if the path is qualified
  1756.          FALSE if not
  1757. Cond:    --
  1758. */
  1759. STDAPI_(BOOL) PathSearchAndQualify(LPCTSTR pcszPath, LPTSTR pszFullyQualifiedPath, UINT cchFullyQualifiedPath)
  1760. {
  1761.     BOOL bRet = FALSE;
  1762.     RIPMSG(pcszPath && IS_VALID_STRING_PTR(pcszPath, -1), "PathSearchAndQualify: caller passed bad pcszPath");
  1763.     RIPMSG(IS_VALID_WRITE_BUFFER(pszFullyQualifiedPath, TCHAR, cchFullyQualifiedPath), "PathSearchAndQualify: caller passed bad pszFullyQualifiedPath");
  1764.     DEBUGWhackPathBuffer(pszFullyQualifiedPath, cchFullyQualifiedPath);
  1765.     if (pcszPath && ((cchFullyQualifiedPath == 0) || pszFullyQualifiedPath))
  1766.     {
  1767.         LPTSTR pszFileName;
  1768.         
  1769.         /* Any path separators? */
  1770.         if (!StrPBrk(pcszPath, TEXT(":/\")))
  1771.         {
  1772.             /* No.  Search for file. */
  1773.             bRet = (SearchPath(NULL, pcszPath, NULL, cchFullyQualifiedPath, pszFullyQualifiedPath, &pszFileName) > 0);
  1774.         }
  1775.         if (!bRet && (GetFullPathName(pcszPath, cchFullyQualifiedPath, pszFullyQualifiedPath, &pszFileName) > 0))
  1776.         {
  1777.             bRet = TRUE;
  1778.         }
  1779.         if ( !bRet )
  1780.         {
  1781.             if (cchFullyQualifiedPath > 0)
  1782.             {
  1783.                 *pszFullyQualifiedPath = '';
  1784.             }
  1785.         }
  1786.         
  1787.         ASSERT((bRet && IsFullPath(pszFullyQualifiedPath)) ||
  1788.                (!bRet && (!cchFullyQualifiedPath || !*pszFullyQualifiedPath)));
  1789.     }
  1790.     return bRet;
  1791. }
  1792. // check if a path is a root
  1793. //
  1794. // returns:
  1795. //  TRUE for "" "X:" "\fooasdf" "\foo"
  1796. //  FALSE for others
  1797. //
  1798. STDAPI_(BOOL) PathIsRoot(LPCTSTR pPath)
  1799. {
  1800.     RIPMSG(pPath && IS_VALID_STRING_PTR(pPath, -1), "PathIsRoot: caller passed bad pPath");
  1801.     if (!pPath || !*pPath)
  1802.     {
  1803.         return FALSE;
  1804.     }
  1805.     if (!IsDBCSLeadByte(*pPath))
  1806.     {
  1807.         if (!lstrcmpi(pPath + 1, TEXT(":\")))
  1808.         {
  1809.             // "X:" case
  1810.             return TRUE;
  1811.         }
  1812.     }
  1813.     if ((*pPath == CH_WHACK) && (*(pPath + 1) == 0))
  1814.     {
  1815.         // "/" or "" case
  1816.         return TRUE;
  1817.     }
  1818.     if (DBL_BSLASH(pPath))      // smells like UNC name
  1819.     {
  1820.         LPCTSTR p;
  1821.         int cBackslashes = 0;
  1822.         for (p = pPath + 2; *p; p = FAST_CharNext(p))
  1823.         {
  1824.             if (*p == TEXT('\') && (++cBackslashes > 1))
  1825.             {
  1826.                /* not a bare UNC name, therefore not a root dir */
  1827.                return FALSE;
  1828.             }
  1829.         }
  1830.         return TRUE;    /* end of string with only 1 more backslash */
  1831.                         /* must be a bare UNC, which looks like a root dir */
  1832.     }
  1833.     return FALSE;
  1834. }
  1835. /*----------------------------------------------------------
  1836. Purpose: Determines if pszPath is a directory.  "C:" is
  1837.          considered a directory too.
  1838. Returns: TRUE if it is
  1839. */
  1840. STDAPI_(BOOL) PathIsDirectory(LPCTSTR pszPath)
  1841. {
  1842.     RIPMSG(pszPath && IS_VALID_STRING_PTR(pszPath, -1), "PathIsDirectory: caller passed bad pszPath");
  1843.     if (pszPath)
  1844.     {
  1845.         if (PathIsUNCServer(pszPath))
  1846.         {
  1847.             return FALSE;
  1848.         }
  1849.         else if (PathIsUNCServerShare(pszPath))
  1850.         {
  1851.             union {
  1852.                 NETRESOURCE nr;
  1853.                 TCHAR buf[512];
  1854.             } nrb;
  1855.             LPTSTR lpSystem;
  1856.             DWORD dwRet;
  1857.             DWORD dwSize = SIZEOF(nrb);
  1858.             nrb.nr.dwScope = RESOURCE_GLOBALNET;
  1859.             nrb.nr.dwType = RESOURCETYPE_ANY;
  1860.             nrb.nr.dwDisplayType = 0;
  1861.             nrb.nr.lpLocalName = NULL;
  1862.             nrb.nr.lpRemoteName = (LPTSTR)pszPath;
  1863.             nrb.nr.lpProvider = NULL;
  1864.             nrb.nr.lpComment = NULL;
  1865.             dwRet = WNetGetResourceInformation(&nrb.nr, &nrb, &dwSize, &lpSystem);
  1866.             if (dwRet != WN_SUCCESS)
  1867.                 goto TryGetFileAttrib;
  1868.             if (nrb.nr.dwDisplayType == RESOURCEDISPLAYTYPE_GENERIC)
  1869.                 goto TryGetFileAttrib;
  1870.             if ((nrb.nr.dwDisplayType == RESOURCEDISPLAYTYPE_SHARE) &&
  1871.                 ((nrb.nr.dwType == RESOURCETYPE_ANY) ||
  1872.                  (nrb.nr.dwType == RESOURCETYPE_DISK)))
  1873.             {
  1874.                 return TRUE;
  1875.             }
  1876.         }
  1877.         else
  1878.         {
  1879.             DWORD dwAttribs;
  1880. TryGetFileAttrib:
  1881.             dwAttribs = GetFileAttributes(pszPath);
  1882.             if (dwAttribs != (DWORD)-1)
  1883.                 return (BOOL)(dwAttribs & FILE_ATTRIBUTE_DIRECTORY);
  1884.         }
  1885.     }
  1886.     return FALSE;
  1887. }
  1888. /*----------------------------------------------------------
  1889. Purpose: Determines if pszPath is a directory.  "C:" is
  1890.          considered a directory too.
  1891. Returns: TRUE if it is, FALSE if it is not a directory or there is
  1892.          at least one file other than "." or ".."
  1893. */
  1894. STDAPI_(BOOL) PathIsDirectoryEmpty(LPCTSTR pszPath)
  1895. {
  1896.     RIPMSG(pszPath && IS_VALID_STRING_PTR(pszPath, -1), "PathIsDirectoryEmpty: caller passed bad pszPath");
  1897.     if (pszPath)
  1898.     {
  1899.         TCHAR szDirStarDotStar[MAX_PATH];
  1900.         HANDLE hDir;
  1901.         WIN32_FIND_DATA wfd;
  1902.         if (!PathIsDirectory(pszPath))
  1903.         {
  1904.             // its not even an directory, so it dosent fall into the
  1905.             // category of "empty" directory
  1906.             return FALSE;
  1907.         }
  1908.         lstrcpy(szDirStarDotStar, pszPath);
  1909.         PathAddBackslash(szDirStarDotStar);
  1910.         StrCatBuff(szDirStarDotStar, TEXT("*.*"), ARRAYSIZE(szDirStarDotStar));
  1911.         hDir = FindFirstFile(szDirStarDotStar, &wfd);
  1912.         if (INVALID_HANDLE_VALUE == hDir)
  1913.         {
  1914.             // we cant see into it, so assume some stuff is there
  1915.             return FALSE;
  1916.         }
  1917.         while (PathIsDotOrDotDot(wfd.cFileName))
  1918.         {
  1919.             if (!FindNextFile(hDir, &wfd))
  1920.             {
  1921.                 // failed and all we found was "." and "..", so I guess
  1922.                 // the directory is empty
  1923.                 FindClose(hDir);
  1924.                 return TRUE;
  1925.             }
  1926.         }
  1927.         // If we made it out of the loop, it means we found a file that 
  1928.         // wasen't "." or ".." Therefore, directory is NOT empty
  1929.         FindClose(hDir);
  1930.     }
  1931.     return FALSE;
  1932. }
  1933. #ifndef UNICODE
  1934. // light weight logic for charprev that is not painful for sbcs
  1935. BOOL IsTrailByte(LPCTSTR pszSt, LPCTSTR pszCur)
  1936. {
  1937.     LPCTSTR psz = pszCur;
  1938.     // if the given pointer is at the top of string, at least it's not a trail byte.
  1939.     if (psz <= pszSt) return FALSE;
  1940.     while (psz > pszSt)
  1941.     {
  1942.         psz--;
  1943.         if (!IsDBCSLeadByte(*psz))
  1944.         {
  1945.             // This is either a trail byte of double byte char
  1946.             // or a single byte character we've first seen.
  1947.             // Thus, the next pointer must be at either of a leadbyte
  1948.             // or pszCur itself.
  1949.             psz++;
  1950.             break;
  1951.         }
  1952.     }
  1953.     // Now psz can point to:
  1954.     //     1) a leadbyte of double byte character.
  1955.     //     2) pszSt
  1956.     //     3) pszCur
  1957.     //
  1958.     // if psz == pszSt, psz should point to a valid double byte char.
  1959.     //                  because we didn't hit the above if statement.
  1960.     //
  1961.     // if psz == pszCur, the *(pszCur-1) was non lead byte so pszCur can't
  1962.     //                   be a trail byte.
  1963.     //
  1964.     // Thus, we can see pszCur as trail byte pointer if the distance from
  1965.     // psz is not DBCS boundary that is 2.
  1966.     //
  1967.     return (BOOL) ((pszCur-psz) & 1);
  1968. }
  1969. #endif
  1970. // modify lpszPath in place so it fits within dx space (using the
  1971. // current font).  the base (file name) of the path is the minimal
  1972. // thing that will be left prepended with ellipses
  1973. //
  1974. // examples:
  1975. //      c:foobarbletch.txt -> c:foo...bletch.txt   -> TRUE
  1976. //      c:foobarbletch.txt -> c:...bletch.txt       -> TRUE
  1977. //      c:foobarbletch.txt -> ...bletch.txt         -> FALSE
  1978. //      relative-path         -> relative-...           -> TRUE
  1979. //
  1980. // in:
  1981. //      hDC         used to get font metrics
  1982. //      lpszPath    path to modify (in place)
  1983. //      dx          width in pixels
  1984. //
  1985. // returns:
  1986. //      TRUE    path was compacted to fit in dx
  1987. //      FALSE   base part didn't fit, the base part of the path was
  1988. //              bigger than dx
  1989. //
  1990. STDAPI_(BOOL) PathCompactPath(HDC hDC, LPTSTR lpszPath, UINT dx)
  1991. {
  1992.     BOOL bRet = TRUE;
  1993.     RIPMSG(lpszPath && IS_VALID_STRING_PTR(lpszPath, -1) && IS_VALID_WRITE_BUFFER(lpszPath, TCHAR, MAX_PATH), "PathCompactPath: caller passed bad lpszPath");
  1994.     DEBUGWhackPathString(lpszPath, MAX_PATH);
  1995.     if (lpszPath)
  1996.     {
  1997.         int           len;
  1998.         UINT          dxFixed, dxEllipses;
  1999.         LPTSTR        lpEnd;          /* end of the unfixed string */
  2000.         LPTSTR        lpFixed;        /* start of text that we always display */
  2001.         BOOL          bEllipsesIn;
  2002.         SIZE sz;
  2003.         TCHAR szTemp[MAX_PATH];
  2004.         HDC hdcGet = NULL;
  2005.         if (!hDC)
  2006.             hDC = hdcGet = GetDC(NULL);
  2007.         /* Does it already fit? */
  2008.         GetTextExtentPoint(hDC, lpszPath, lstrlen(lpszPath), &sz);
  2009.         if ((UINT)sz.cx <= dx)
  2010.         {
  2011.             goto Exit;
  2012.         }
  2013.         lpFixed = PathFindFileName(lpszPath);
  2014.         if (lpFixed != lpszPath)
  2015.         {
  2016.             lpFixed = CharPrev(lpszPath, lpFixed);  // point at the slash
  2017.         }
  2018.         /* Save this guy to prevent overlap. */
  2019.         lstrcpyn(szTemp, lpFixed, ARRAYSIZE(szTemp));
  2020.         lpEnd = lpFixed;
  2021.         bEllipsesIn = FALSE;
  2022.         GetTextExtentPoint(hDC, lpFixed, lstrlen(lpFixed), &sz);
  2023.         dxFixed = sz.cx;
  2024.         GetTextExtentPoint(hDC, c_szEllipses, 3, &sz);
  2025.         dxEllipses = sz.cx;
  2026.         // BUGBUG: GetTextExtentEx() or something should let us do this without looping
  2027.         if (lpFixed == lpszPath)
  2028.         {
  2029.             // if we're just doing a file name, just tack on the ellipses at the end
  2030.             lpszPath = lpszPath + lstrlen(lpszPath);
  2031.             if ((3 + lpszPath - lpFixed) >= MAX_PATH)
  2032.             {
  2033.                 lpszPath = lpFixed + MAX_PATH - 4;
  2034.             }
  2035.             while (TRUE) 
  2036.             {
  2037. #ifndef UNICODE
  2038.                 if (IsTrailByte(lpFixed, lpszPath))
  2039.                     lpszPath--;
  2040. #endif
  2041.                 lstrcpy(lpszPath, c_szEllipses);
  2042.                 // Note: We don't support strings longer than 4GB, but that's
  2043.                 // okay because we already barf at MAX_PATH
  2044.                 GetTextExtentPoint(hDC, lpFixed, (int)(3 + lpszPath - lpFixed), &sz);
  2045.                 if (sz.cx <= (int)dx)
  2046.                     break;
  2047.                 
  2048.                 lpszPath--;
  2049.             }
  2050.         }
  2051.         else
  2052.         {
  2053.             // Note that we need to avoid calling GetTextExtentPoint with a
  2054.             // length of zero (because Win95 allegedly crashes under conditions
  2055.             // yet to be determined precisely), but lpEnd is guaranteed
  2056.             // to be greater than lpszPath to start.
  2057.             //
  2058.             // raymondc - I'm guessing that some crappy display driver has
  2059.             // patched GetTextExtent and screwed up their "optimized" version.
  2060.             do
  2061.             {
  2062.                 // Note: We don't support strings longer than 4GB, but that's
  2063.                 // okay because we already barf at MAX_PATH
  2064.                 GetTextExtentPoint(hDC, lpszPath, (int)(lpEnd - lpszPath), &sz);
  2065.                 len = dxFixed + sz.cx;
  2066.                 if (bEllipsesIn)
  2067.                     len += dxEllipses;
  2068.                 if (len <= (int)dx)
  2069.                     break;
  2070.                 // Step back a character.
  2071.                 lpEnd = CharPrev(lpszPath, lpEnd);
  2072.                 
  2073.                 if (!bEllipsesIn)
  2074.                 {
  2075.                     // if this is the first
  2076.                     // truncation, go ahead and truncate by 3 (lstrlen of c_szEllipses);
  2077.                     // so that we don't just go back one, then write 3 and overwrite the buffer
  2078.                     lpEnd = CharPrev(lpszPath, lpEnd);
  2079.                     lpEnd = CharPrev(lpszPath, lpEnd);
  2080.                 }
  2081.                 bEllipsesIn = TRUE;
  2082.             } while (lpEnd > lpszPath);
  2083.             // Things didn't fit. Note that we'll still overflow here because the
  2084.             // filename is larger than the available space. We should probably trim
  2085.             // the file name, but I'm just trying to prevent a crash, not actually
  2086.             // make this work.
  2087.             if (lpEnd <= lpszPath)
  2088.             {
  2089.                 lstrcpy(lpszPath, c_szEllipses);
  2090.                 StrCatBuff(lpszPath, szTemp, MAX_PATH);
  2091.                 bRet = FALSE;
  2092.                 goto Exit;
  2093.             }
  2094.             if (bEllipsesIn)
  2095.             {
  2096.                 lstrcpy(lpEnd, c_szEllipses);
  2097.                 lstrcat(lpEnd, szTemp);
  2098.             }
  2099.         }
  2100.         Exit:
  2101.         if (hdcGet)
  2102.             ReleaseDC(NULL, hdcGet);
  2103.     }
  2104.     
  2105.     return bRet;
  2106. }
  2107. #define LEN_MID_ELLIPSES        4
  2108. #define LEN_END_ELLIPSES        3
  2109. #define MIN_CCHMAX              LEN_MID_ELLIPSES + LEN_END_ELLIPSES
  2110. // PathCompactPathEx
  2111. // Output:
  2112. //          "."
  2113. //          ".."
  2114. //          "..."
  2115. //          "..."
  2116. //          "...."
  2117. //          "....."
  2118. //          "......"
  2119. //          "...Truncated filename..."
  2120. //          "...whole filename"
  2121. //          "Truncated path...whole filename"
  2122. //          "Whole pathwhole filename"
  2123. // The '/' might be used instead of a '' if the original string used it
  2124. // If there is no path, but only a file name that does not fit, the output is:
  2125. //          "truncated filename..."
  2126. //
  2127. STDAPI_(BOOL) PathCompactPathEx(LPTSTR pszOut, LPCTSTR pszSrc, UINT cchMax, DWORD dwFlags)
  2128. {
  2129.     RIPMSG(pszSrc && IS_VALID_STRING_PTR(pszSrc, -1), "PathCompactPathEx: caller passed bad pszSrc");
  2130.     RIPMSG(pszOut && IS_VALID_WRITE_BUFFER(pszOut, TCHAR, cchMax), "PathCompactPathEx: caller passed bad pszOut");
  2131.     RIPMSG(!dwFlags, "PathCompactPathEx: caller passed non-ZERO dwFlags");
  2132.     DEBUGWhackPathBuffer(pszOut, cchMax);
  2133.     if (pszSrc)
  2134.     {
  2135.         TCHAR * pszFileName, *pszWalk;
  2136.         UINT uiFNLen = 0;
  2137.         int cchToCopy = 0, n;
  2138.         TCHAR chSlash = TEXT('0');
  2139.         ZeroMemory(pszOut, cchMax * sizeof(TCHAR));
  2140.         if ((UINT)lstrlen(pszSrc)+1 < cchMax)
  2141.         {
  2142.             lstrcpy(pszOut, pszSrc);
  2143.             ASSERT(pszOut[cchMax-1] == TEXT(''));
  2144.             return TRUE;
  2145.         }
  2146.         // Determine what we use as a slash - a / or a  (default )
  2147.         pszWalk = (TCHAR*)pszSrc;
  2148.         chSlash = TEXT('\');
  2149.         // Scan the entire string as we want the path separator closest to the end
  2150.         // eg. "file://\Themesrvdesktopdesktop.htm"
  2151.         while(*pszWalk)
  2152.         {
  2153.             if ((*pszWalk == TEXT('/')) || (*pszWalk == TEXT('\')))
  2154.                 chSlash = *pszWalk;
  2155.             pszWalk = FAST_CharNext(pszWalk);
  2156.         }
  2157.         pszFileName = PathFindFileName(pszSrc);
  2158.         uiFNLen = lstrlen(pszFileName);
  2159.         // if the whole string is a file name
  2160.         if(pszFileName == pszSrc && cchMax > LEN_END_ELLIPSES)
  2161.         {
  2162.             lstrcpyn(pszOut, pszSrc, cchMax - LEN_END_ELLIPSES);
  2163. #ifndef UNICODE
  2164.             if (IsTrailByte(pszSrc, pszSrc+cchMax-LEN_END_ELLIPSES))
  2165.             {
  2166.                 *(pszOut+cchMax-LEN_END_ELLIPSES-1) = TEXT('');
  2167.             }
  2168. #endif
  2169.             lstrcat(pszOut, TEXT("..."));
  2170.             ASSERT(pszOut[cchMax-1] == TEXT(''));
  2171.             return TRUE;
  2172.         }
  2173.         // Handle all the cases where we just use ellipses ie '.' to '.../...'
  2174.         if ((cchMax < MIN_CCHMAX))
  2175.         {
  2176.             for (n = 0; n < (int)cchMax-1; n++)
  2177.             {
  2178.                 if ((n+1) == LEN_MID_ELLIPSES)
  2179.                 {
  2180.                     pszOut[n] = chSlash;
  2181.                 }
  2182.                 else
  2183.                 {
  2184.                     pszOut[n] = TEXT('.');
  2185.                 }
  2186.             }
  2187.             ASSERT(0==cchMax || pszOut[cchMax-1] == TEXT(''));
  2188.             return TRUE;
  2189.         }
  2190.         // Ok, how much of the path can we copy ? Buffer - (Lenght of MID_ELLIPSES + Len_Filename)
  2191.         cchToCopy = cchMax - (LEN_MID_ELLIPSES + uiFNLen);
  2192.         
  2193.         if (cchToCopy < 0)
  2194.             cchToCopy = 0;
  2195. #ifndef UNICODE
  2196.         if (cchToCopy > 0 && IsTrailByte(pszSrc, pszSrc+cchToCopy))
  2197.             cchToCopy--;
  2198. #endif
  2199.         lstrcpyn(pszOut, pszSrc, cchToCopy);
  2200.         // Now throw in the ".../" or "..."
  2201.         lstrcat(pszOut, TEXT(".../"));
  2202.         pszOut[lstrlen(pszOut) - 1] = chSlash;
  2203.         //Finally the filename and ellipses if necessary
  2204.         if (cchMax > (LEN_MID_ELLIPSES + uiFNLen))
  2205.         {
  2206.             lstrcat(pszOut, pszFileName);
  2207.         }
  2208.         else
  2209.         {
  2210.             cchToCopy = cchMax - LEN_MID_ELLIPSES - LEN_END_ELLIPSES;
  2211. #ifndef UNICODE
  2212.             if (cchToCopy >0 && IsTrailByte(pszFileName, pszFileName+cchToCopy))
  2213.             {
  2214.                 cchToCopy--;
  2215.             }
  2216. #endif
  2217.             lstrcpyn(pszOut + LEN_MID_ELLIPSES, pszFileName, cchToCopy);
  2218.             lstrcat(pszOut, TEXT("..."));
  2219.         }
  2220.         ASSERT(pszOut[cchMax-1] == TEXT(''));
  2221.         return TRUE;
  2222.     }
  2223.     return FALSE;
  2224. }
  2225. // fill a control with a path, using PathCompactPath() to crunch the
  2226. // path to fit.
  2227. //
  2228. // in:
  2229. //      hDlg    dialog box or parent window
  2230. //      id      child id to put the path in
  2231. //      pszPath path to put in
  2232. //
  2233. STDAPI_(void) PathSetDlgItemPath(HWND hDlg, int id, LPCTSTR pszPath)
  2234. {
  2235.     RECT rc;
  2236.     HDC hdc;
  2237.     HFONT hFont;
  2238.     TCHAR szPath[MAX_PATH + 1];  // can have one extra char
  2239.     HWND hwnd;
  2240.     hwnd = GetDlgItem(hDlg, id);
  2241.     
  2242.     if (!hwnd)
  2243.         return;
  2244.     szPath[0] = 0;
  2245.     if (pszPath)
  2246.         lstrcpyn(szPath, pszPath, ARRAYSIZE(szPath));
  2247.     GetClientRect(hwnd, &rc);
  2248.     hdc = GetDC(hDlg);
  2249.     hFont = (HANDLE)SendMessage(hwnd, WM_GETFONT, 0, 0L);
  2250.     
  2251.     if (NULL != (hFont = SelectObject(hdc, hFont)))
  2252.     {
  2253.         PathCompactPath(hdc, szPath, (UINT)rc.right);
  2254.         SelectObject(hdc, hFont);
  2255.     }
  2256.     
  2257.     ReleaseDC(hDlg, hdc);
  2258.     SetWindowText(hwnd, szPath);
  2259. }
  2260. /*----------------------------------------------------------
  2261. Purpose: If a path is contained in quotes then remove them.
  2262. Returns: --
  2263. Cond:    --
  2264. */
  2265. STDAPI_(void) PathUnquoteSpaces(LPTSTR lpsz)
  2266. {
  2267.     RIPMSG(lpsz && IS_VALID_STRING_PTR(lpsz, -1), "PathUnquoteSpaces: caller passed bad lpsz");
  2268.     if (lpsz)
  2269.     {
  2270.         int cch;
  2271.         cch = lstrlen(lpsz);
  2272.         // Are the first and last chars quotes?
  2273.         if (lpsz[0] == TEXT('"') && lpsz[cch-1] == TEXT('"'))
  2274.         {
  2275.             // Yep, remove them.
  2276.             lpsz[cch-1] = TEXT('');
  2277.             hmemcpy(lpsz, lpsz+1, (cch-1) * SIZEOF(TCHAR));
  2278.         }
  2279.     }
  2280. }
  2281. //----------------------------------------------------------------------------
  2282. // If a path contains spaces then put quotes around the whole thing.
  2283. //
  2284. STDAPI_(void)PathQuoteSpaces(LPTSTR lpsz)
  2285. {
  2286.     RIPMSG(lpsz && IS_VALID_STRING_PTR(lpsz, -1) && IS_VALID_WRITE_BUFFER(lpsz, TCHAR, MAX_PATH), "PathQuoteSpaces: caller passed bad lpsz");
  2287.     DEBUGWhackPathString(lpsz, MAX_PATH);
  2288.     if (lpsz)
  2289.     {
  2290.         int cch;
  2291.         if (StrChr(lpsz, TEXT(' ')))
  2292.         {
  2293.             // NB - Use hmemcpy coz it supports overlapps.
  2294.             cch = lstrlen(lpsz)+1;
  2295.             if (cch+1 < MAX_PATH)
  2296.             {
  2297.                 hmemcpy(lpsz+1, lpsz, cch * SIZEOF(TCHAR));
  2298.                 lpsz[0] = TEXT('"');
  2299.                 lpsz[cch] = TEXT('"');
  2300.                 lpsz[cch+1] = TEXT('');
  2301.             }
  2302.         }
  2303.     }
  2304. }
  2305. //---------------------------------------------------------------------------
  2306. // Given a pointer to a point in a path - return a ptr the start of the
  2307. // next path component. Path components are delimted by slashes or the
  2308. // null at the end.
  2309. // There's special handling for UNC names.
  2310. // This returns NULL if you pass in a pointer to a NULL ie if you're about
  2311. // to go off the end of the  path.
  2312. //
  2313. STDAPI_(LPTSTR) PathFindNextComponent(LPCTSTR pszPath)
  2314. {
  2315.     RIPMSG(pszPath && IS_VALID_STRING_PTR(pszPath, -1), "PathFindNextComponent: caller passed bad pszPath");
  2316.     if (pszPath)
  2317.     {
  2318.         LPTSTR pszLastSlash;
  2319.         // Are we at the end of a path.
  2320.         if (!*pszPath)
  2321.         {
  2322.             // Yep, quit.
  2323.             return NULL;
  2324.         }
  2325.         // Find the next slash.
  2326.         // REVIEW UNDONE - can slashes be quoted?
  2327.         pszLastSlash = StrChr(pszPath, TEXT('\'));
  2328.         // Is there a slash?
  2329. #ifdef UNIX
  2330.         if (!pszLastSlash && !(pszLastSlash = StrChr(pszPath, CH_WHACK)))
  2331. #else
  2332.         if (!pszLastSlash)
  2333. #endif
  2334.         {
  2335.             // No - Return a ptr to the NULL.
  2336.             return (LPTSTR)pszPath + lstrlen(pszPath);
  2337.         }
  2338.         else
  2339.         {
  2340.             // Is it a UNC style name?
  2341.             if (*(pszLastSlash + 1) == TEXT('\'))
  2342.             {
  2343.                 // Yep, skip over the second slash.
  2344.                 return pszLastSlash + 2;
  2345.             }
  2346.             else
  2347.             {
  2348.                 // Nope. just skip over one slash.
  2349.                 return pszLastSlash + 1;
  2350.             }
  2351.         }
  2352.     }
  2353.     return NULL;
  2354. }
  2355. //
  2356. // Match a DOS wild card spec against a dos file name
  2357. // Both strings must be ANSI.
  2358. //
  2359. // History:
  2360. //  01-25-94 SatoNa     Moved from search.c
  2361. //
  2362. STDAPI_(BOOL) PathMatchSpec(LPCTSTR pszFileParam, LPCTSTR pszSpec)
  2363. {
  2364.     RIPMSG(pszSpec && IS_VALID_STRING_PTR(pszSpec, -1), "PathMathSpec: caller passed bad pszSpec");
  2365.     RIPMSG(pszFileParam && IS_VALID_STRING_PTR(pszFileParam, -1), "PathMathSpec: caller passed bad pszFileParam");
  2366.     if (pszSpec && pszFileParam)
  2367.     {
  2368.         // Special case empty string, "*", and "*.*"...
  2369.         //
  2370.         if (*pszSpec == 0)
  2371.         {
  2372.             return TRUE;
  2373.         }
  2374. #ifdef UNICODE
  2375.         if (!g_bRunningOnNT)
  2376.         {
  2377.             // To costly to do UpperChar per character...
  2378.             char szFileParam[MAX_PATH];
  2379.             char szSpec[MAX_PATH];
  2380.             WideCharToMultiByte(CP_ACP, 0, pszFileParam, -1, szFileParam, ARRAYSIZE(szFileParam), NULL, NULL);
  2381.             WideCharToMultiByte(CP_ACP, 0, pszSpec, -1, szSpec, ARRAYSIZE(szSpec), NULL, NULL);
  2382.             return PathMatchSpecA(szFileParam, szSpec);
  2383.         }
  2384. #endif
  2385.         do
  2386.         {
  2387.             LPCTSTR pszFile = pszFileParam;
  2388.             // Strip leading spaces from each spec.  This is mainly for commdlg
  2389.             // support;  the standard format that apps pass in for multiple specs
  2390.             // is something like "*.bmp; *.dib; *.pcx" for nicer presentation to
  2391.             // the user.
  2392.             while (*pszSpec == TEXT(' '))
  2393.                 pszSpec++;
  2394.             while (*pszFile && *pszSpec && *pszSpec != TEXT(';'))
  2395.             {
  2396.                 switch (*pszSpec)
  2397.                 {
  2398.                 case TEXT('?'):
  2399.                     pszFile=FAST_CharNext(pszFile);
  2400.                     pszSpec++;      // NLS: We know that this is a SBCS
  2401.                     break;
  2402.                 case TEXT('*'):
  2403.                     // We found a * so see if this is the end of our file spec
  2404.                     // or we have *.* as the end of spec, in which case we
  2405.                     // can return true.
  2406.                     //
  2407.                     if (*(pszSpec + 1) == 0 || *(pszSpec + 1) == TEXT(';'))   // "*" matches everything
  2408.                         return TRUE;
  2409.                     // Increment to the next character in the list
  2410.                     pszSpec = FAST_CharNext(pszSpec);
  2411.                     // If the next character is a . then short circuit the
  2412.                     // recursion for performance reasons
  2413.                     if (*pszSpec == TEXT('.'))
  2414.                     {
  2415.                         pszSpec++;  // Get beyond the .
  2416.                         // Now see if this is the *.* case
  2417.                         if ((*pszSpec == TEXT('*')) &&
  2418.                                 ((*(pszSpec+1) == TEXT('')) || (*(pszSpec+1) == TEXT(';'))))
  2419.                             return TRUE;
  2420.                         // find the extension (or end in the file name)
  2421.                         while (*pszFile)
  2422.                         {
  2423.                             // If the next char is a dot we try to match the
  2424.                             // part on down else we just increment to next item
  2425.                             if (*pszFile == TEXT('.'))
  2426.                             {
  2427.                                 pszFile++;
  2428.                                 if (PathMatchSpec(pszFile, pszSpec))
  2429.                                     return(TRUE);
  2430.                             }
  2431.                             else
  2432.                                 pszFile = FAST_CharNext(pszFile);
  2433.                         }
  2434.                         goto NoMatch;   // No item found so go to next pattern
  2435.                     }
  2436.                     else
  2437.                     {
  2438.                         // Not simply looking for extension, so recurse through
  2439.                         // each of the characters until we find a match or the
  2440.                         // end of the file name
  2441.                         while (*pszFile)
  2442.                         {
  2443.                             // recurse on our self to see if we have a match
  2444.                             if (PathMatchSpec(pszFile, pszSpec))
  2445.                                 return(TRUE);
  2446.                             pszFile = FAST_CharNext(pszFile);
  2447.                         }
  2448.                         goto NoMatch;   // No item found so go to next pattern
  2449.                     }
  2450.                 default:
  2451.                     if (CharUpper((LPTSTR)(ULONG_PTR)(TUCHAR)*pszSpec) ==
  2452.                              CharUpper((LPTSTR)(ULONG_PTR)(TUCHAR)*pszFile))
  2453.                     {
  2454.                         if (IsDBCSLeadByte(*pszSpec))
  2455.                         {
  2456. #ifdef  DBCS
  2457.                             // Because AnsiUpper(CharUpper) just return 0
  2458.                             // for broken DBCS char passing case, above if state
  2459.                             // always true with DBCS char so that we should check
  2460.                             // first byte of DBCS char here again.
  2461.                             if (*pszFile != *pszSpec)
  2462.                                 goto NoMatch;
  2463. #endif
  2464.                             pszFile++;
  2465.                             pszSpec++;
  2466.                             if (*pszFile != *pszSpec)
  2467.                                 goto NoMatch;
  2468.                         }
  2469.                         pszFile++;
  2470.                         pszSpec++;
  2471.                     }
  2472.                     else
  2473.                     {
  2474.                         goto NoMatch;
  2475.                     }
  2476.                 }
  2477.             }
  2478.             // If we made it to the end of both strings, we have a match...
  2479.             //
  2480.             if (!*pszFile)
  2481.             {
  2482.                 if ((!*pszSpec || *pszSpec == TEXT(';')))
  2483.                     return TRUE;
  2484.                 // Also special case if things like foo should match foo*
  2485.                 // as well as foo*; for foo*.* or foo*.*;
  2486.                 if ( (*pszSpec == TEXT('*')) &&
  2487.                     ( (*(pszSpec+1) == TEXT('')) || (*(pszSpec+1) == TEXT(';')) ||
  2488.                         ((*(pszSpec+1) == TEXT('.')) &&  (*(pszSpec+2) == TEXT('*')) &&
  2489.                             ((*(pszSpec+3) == TEXT('')) || (*(pszSpec+3) == TEXT(';'))))))
  2490.                     return TRUE;
  2491.             }
  2492.             // Skip to the end of the path spec...
  2493.             //
  2494. NoMatch:
  2495.             while (*pszSpec && *pszSpec != TEXT(';'))
  2496.                 pszSpec = FAST_CharNext(pszSpec);
  2497.         // If we have more specs, keep looping...
  2498.         //
  2499.         } while (*pszSpec++ == TEXT(';'));
  2500.     }
  2501.     return FALSE;
  2502. }
  2503. /*----------------------------------------------------------
  2504. Purpose: Returns a pointer to the beginning of the subpath
  2505.          that follows the root (drive letter or UNC server/share).
  2506. Returns:
  2507. Cond:    --
  2508. */
  2509. STDAPI_(LPTSTR) PathSkipRoot(LPCTSTR pszPath)
  2510. {
  2511.     RIPMSG(pszPath && IS_VALID_STRING_PTR(pszPath, -1), "PathSkipRoot: caller passed bad pszPath");
  2512.     if (pszPath)
  2513.     {
  2514.         if (DBL_BSLASH(pszPath))
  2515.         {
  2516.             pszPath = StrChr(pszPath+2, TEXT('\'));
  2517.             if (pszPath)
  2518.             {
  2519.                 pszPath = StrChr(pszPath+1, TEXT('\'));
  2520.                 if (pszPath)
  2521.                 {
  2522.                     ++pszPath;
  2523.                 }
  2524.             }
  2525.         }
  2526.         else if (!IsDBCSLeadByte(pszPath[0]) && pszPath[1]==TEXT(':') && pszPath[2]==TEXT('\'))
  2527.         {
  2528.             pszPath += 3;
  2529.         }
  2530. #ifdef UNIX
  2531.         else if (!IsDBCSLeadByte(pszPath[0]) && pszPath[0]==CH_WHACK)
  2532.         {
  2533.             pszPath++;
  2534.         }
  2535. #endif
  2536.         else
  2537.         {
  2538.             pszPath = NULL;
  2539.         }
  2540.     }
  2541.     return (LPTSTR)pszPath;
  2542. }
  2543. // see if two paths have the same root component
  2544. //
  2545. STDAPI_(BOOL) PathIsSameRoot(LPCTSTR pszPath1, LPCTSTR pszPath2)
  2546. {
  2547.     RIPMSG(pszPath1 && IS_VALID_STRING_PTR(pszPath1, -1), "PathIsSameRoot: caller passed bad pszPath1");
  2548.     RIPMSG(pszPath2 && IS_VALID_STRING_PTR(pszPath2, -1), "PathIsSameRoot: caller passed bad pszPath2");
  2549.     if (pszPath1 && pszPath2)
  2550.     {
  2551.         LPTSTR pszAfterRoot = PathSkipRoot(pszPath1);
  2552.         int nLen = PathCommonPrefix(pszPath1, pszPath2, NULL);
  2553.         // Add 1 to account for the '\'
  2554.         return pszAfterRoot && (pszAfterRoot - pszPath1) <= (nLen + 1);
  2555.     }
  2556.     return FALSE;
  2557. }
  2558. #define IsDigit(c) ((c) >= TEXT('0') && c <= TEXT('9'))
  2559. /*----------------------------------------------------------
  2560. Purpose: Takes a location string ("shell32.dll,3") and parses
  2561.          it into a file-component and an icon index.
  2562. Returns: icon index
  2563. Cond:    --
  2564. */
  2565. STDAPI_(int) PathParseIconLocation(IN OUT LPTSTR pszIconFile)
  2566. {
  2567.     int iIndex = 0;
  2568.     RIPMSG(pszIconFile && IS_VALID_STRING_PTR(pszIconFile, -1), "PathParseIconLocation: caller passed bad pszIconFile");
  2569.     
  2570.     if (pszIconFile)
  2571.     {
  2572.         LPTSTR pszComma, pszEnd;
  2573.         // look for the last comma in the string
  2574.         pszEnd = pszIconFile + lstrlen(pszIconFile);
  2575.         pszComma = StrRChr(pszIconFile, pszEnd, TEXT(','));
  2576.         
  2577.         if (pszComma && *pszComma)
  2578.         {
  2579.             LPTSTR pszComma2 = pszComma + 1;
  2580.             BOOL fIsDigit = FALSE;
  2581.             // Sometimes we get something like: "C:path, commapathfile.ico"
  2582.             // where the ',' is in the path and does not indicates that an icon index follows
  2583.             while (*pszComma2)
  2584.             {
  2585.                 if ((TEXT(' ') == *pszComma2) || (TEXT('-') == *pszComma2))
  2586.                 {
  2587.                     ++pszComma2;
  2588.                 }
  2589.                 else
  2590.                 {
  2591.                     if (IsDigit(*pszComma2))
  2592.                     {
  2593.                         fIsDigit = TRUE;
  2594.                     }
  2595.                     break;
  2596.                 }
  2597.             }
  2598.             if (fIsDigit)
  2599.             {
  2600.                 *pszComma++ = 0;            // terminate the icon file name.
  2601.                 iIndex = StrToInt(pszComma);
  2602.             }
  2603.         }
  2604.         PathUnquoteSpaces(pszIconFile);
  2605.         PathRemoveBlanks(pszIconFile);
  2606.     }
  2607.     return iIndex;
  2608. }
  2609. /*----------------------------------------------------------
  2610. Purpose: Returns TRUE if the given path is of a URL format.
  2611.          See http://www.w3.org for a complete description of
  2612.          the URL format.
  2613.          A complete URL looks like:
  2614.             <URL:http://www.microsoft.com/software/index.html>
  2615.          But generally URLs don't have the leading "URL:" and
  2616.          the wrapping angle brackets.  So this function only
  2617.          tests for the following format:
  2618.             http://www.microsoft.com/software
  2619.          It does not check if the path points to an existing
  2620.          site, only if is in a legal URL format.
  2621. Returns: TRUE if URL format
  2622.          FALSE if not
  2623. Cond:    --
  2624. */
  2625. STDAPI_(BOOL) PathIsURL(IN LPCTSTR pszPath)
  2626. {
  2627.     PARSEDURL pu;
  2628.     if (!pszPath)
  2629.         return FALSE;
  2630.     RIPMSG(IS_VALID_STRING_PTR(pszPath, -1), "PathIsURL: caller passed bad pszPath");
  2631.     pu.cbSize = SIZEOF(pu);
  2632.     return SUCCEEDED(ParseURL(pszPath, &pu));
  2633. }
  2634. /****************************************************
  2635.     FUNCTION: PathIsContentType
  2636.     PARAMETERS:
  2637.         pszPath - File Name to check.
  2638.         pszContentType - Content Type to look for.
  2639.     DESCRIPTION:
  2640.         Is the file (pszPath) of the content type
  2641.     specified (pszContentType)?.
  2642. ****************************************************/
  2643. #define SZ_VALUE_CONTENTTYPE      TEXT("Content Type")
  2644. BOOL PathIsContentType(LPCTSTR pszPath, LPCTSTR pszContentType)
  2645. {
  2646.     BOOL fDoesMatch = FALSE;
  2647.     RIPMSG(pszPath && IS_VALID_STRING_PTR(pszPath, -1), "PathIsContentType: caller passed bad pszPath");
  2648.     RIPMSG(pszContentType && IS_VALID_STRING_PTR(pszContentType, -1), "PathIsContentType: caller passed bad pszContentType");
  2649.     if (pszPath)
  2650.     {
  2651.         LPTSTR pszExtension = PathFindExtension(pszPath);
  2652.         if (pszExtension && pszExtension[0])
  2653.         {
  2654.             TCHAR szRegData[MAX_PATH];
  2655.             DWORD dwDataSize = SIZEOF(szRegData);
  2656.             
  2657.             if (ERROR_SUCCESS == SHGetValue(HKEY_CLASSES_ROOT, pszExtension, SZ_VALUE_CONTENTTYPE, NULL,
  2658.                 &szRegData, &dwDataSize))
  2659.             {
  2660.                 fDoesMatch = (0 == lstrcmpi(szRegData, pszContentType));
  2661.             }
  2662.         }
  2663.     }
  2664.     return fDoesMatch;
  2665. }
  2666. /*----------------------------------------------------------
  2667. Purpose: Returns the character type (GCT_)
  2668.   BUGBUG (reinerf) - this API is lame, use PathIsValidChar() instead, its more customizable
  2669.                      and better
  2670. */
  2671. UINT PathGetCharType(TUCHAR ch)
  2672. {
  2673.     switch (ch)
  2674.     {
  2675.         case TEXT('|'):
  2676.         case TEXT('>'):
  2677.         case TEXT('<'):
  2678.         case TEXT('"'):
  2679.         case TEXT('/'):
  2680.             return GCT_INVALID;
  2681.         case TEXT('?'):
  2682.         case TEXT('*'):
  2683.             return GCT_WILD;
  2684.         case TEXT('\'):      // path separator
  2685.         case TEXT(':'):       // drive colon
  2686.             return GCT_SEPARATOR;
  2687.         case TEXT(';'):
  2688.         case TEXT(','):
  2689.         case TEXT(' '):
  2690.             return GCT_LFNCHAR;     // actually valid in short names
  2691.                                     // but we want to avoid this
  2692.         default:
  2693.             if (ch > TEXT(' '))
  2694.             {
  2695.                 return GCT_SHORTCHAR | GCT_LFNCHAR;
  2696.             }
  2697.             else
  2698.             {
  2699.                 // control character
  2700.                 return GCT_INVALID;
  2701.             }
  2702.     }
  2703. }
  2704. /*----------------------------------------------------------
  2705. Purpose: returns if a character is a valid path character given
  2706.          the flags that you pass in (PIVC_XXX). Some basic flags are given below:
  2707.          PIVC_ALLOW_QUESTIONMARK        treat '?' as valid
  2708.          PIVC_ALLOW_STAR                treat '*' as valid
  2709.          PIVC_ALLOW_DOT                 treat '.' as valid
  2710.          PIVC_ALLOW_SLASH               treat '\' as valid
  2711.          PIVC_ALLOW_COLON               treat ':' as valid
  2712.          PIVC_ALLOW_SEMICOLON           treat ';' as valid
  2713.          PIVC_ALLOW_COMMA               treat ',' as valid
  2714.          PIVC_ALLOW_SPACE               treat ' ' as valid
  2715.          PIVC_ALLOW_NONALPAHABETIC      treat non-alphabetic extenede chars as valid
  2716.          PIVC_ALLOW_QUOTE               treat '"' as valid
  2717.          if you pass 0, then only alphabetic characters are valid. there are also basic
  2718.          conglomerations of the above flags:
  2719.          PIVC_ALLOW_FULLPATH, PIVC_ALLOW_WILDCARD, PIVC_ALLOW_LFN, ...
  2720.          
  2721. Returns: TRUE if the character is a valid path character given the dwFlags constraints
  2722.          FALSE if this does not qualify as a valid path character given the dwFlags constraints
  2723. Cond:    --
  2724. */
  2725. STDAPI_(BOOL) PathIsValidChar(TUCHAR ch, DWORD dwFlags)
  2726. {
  2727.     switch (ch)
  2728.     {
  2729.         case TEXT('|'):
  2730.         case TEXT('>'):
  2731.         case TEXT('<'):
  2732.         case TEXT('/'):
  2733.             return FALSE;   // these are allways illegal in a path
  2734.             break;
  2735.         case TEXT('?'):
  2736.             return dwFlags & PIVC_ALLOW_QUESTIONMARK;
  2737.             break;
  2738.         case TEXT('*'):
  2739.             return dwFlags & PIVC_ALLOW_STAR;
  2740.             break;
  2741.         case TEXT('.'):
  2742.             return dwFlags & PIVC_ALLOW_DOT;
  2743.             break;
  2744.         case TEXT('\'):
  2745.             return dwFlags & PIVC_ALLOW_SLASH;
  2746.             break;
  2747.         case TEXT(':'):
  2748.             return dwFlags & PIVC_ALLOW_COLON;
  2749.             break;
  2750.         case TEXT(';'):
  2751.             return dwFlags & PIVC_ALLOW_SEMICOLON;
  2752.             break;
  2753.         case TEXT(','):
  2754.             return dwFlags & PIVC_ALLOW_COMMA;
  2755.             break;
  2756.         case TEXT(' '):
  2757.             return dwFlags & PIVC_ALLOW_SPACE;
  2758.             break;
  2759.         case TEXT('"'):
  2760.             return dwFlags & PIVC_ALLOW_QUOTE;
  2761.             break;
  2762.         default:
  2763.             if ((TEXT('a') <= ch <= TEXT('z')) ||
  2764.                 (TEXT('A') <= ch <= TEXT('Z')))
  2765.             {
  2766.                 // we have an alphabetic character, 
  2767.                 // this is always valid
  2768.                 return TRUE;
  2769.             }
  2770.             else if (ch < TEXT(' '))
  2771.             {
  2772.                 // we have a control sequence, 
  2773.                 // this is allways illegal
  2774.                 return FALSE;
  2775.             }
  2776.             else
  2777.             {
  2778.                 // we have an non-alphabetic extenede character
  2779.                 return dwFlags & PIVC_ALLOW_NONALPAHABETIC;
  2780.             }
  2781.             break;
  2782.     }
  2783. }
  2784. BOOL IsSystemSpecialCase(LPCTSTR pszPath)
  2785. {
  2786.     static TCHAR *g_pszWin = NULL, *g_pszSys = NULL;
  2787.     if (g_pszWin == NULL)
  2788.     {
  2789.         TCHAR szTemp[MAX_PATH];
  2790.         GetWindowsDirectory(szTemp, ARRAYSIZE(szTemp));
  2791.         g_pszWin = StrDup(szTemp);
  2792.     }
  2793.     if (g_pszSys == NULL)
  2794.     {
  2795.         TCHAR szTemp[MAX_PATH];
  2796.         GetSystemDirectory(szTemp, ARRAYSIZE(szTemp));
  2797.         g_pszSys = StrDup(szTemp);
  2798.     }
  2799.     return (g_pszWin && (lstrcmpi(g_pszWin, pszPath) == 0)) ||
  2800.            (g_pszSys && (lstrcmpi(g_pszSys, pszPath) == 0));
  2801. }
  2802. /*----------------------------------------------------------
  2803. Purpose: Mark a folder to be a shell folder by stamping
  2804.          either FILE_ATTRIBUTES_READONLY or FILE_ATTRIBUTE_SYSTEM
  2805.          into it's attributes.  Which flag is used is based
  2806.          on the presence/absense of a registry switch
  2807. */
  2808. BOOL PathMakeSystemFolder(LPCTSTR pszPath)
  2809. {
  2810.     RIPMSG(pszPath && IS_VALID_STRING_PTR(pszPath, -1), "PathMakeSystemFolder: caller passed bad pszPath");
  2811.     if (pszPath && *pszPath)
  2812.     {
  2813.         DWORD dwAttrb, dwAttrbSet = FILE_ATTRIBUTE_READONLY;
  2814.         if (IsSystemSpecialCase(pszPath))
  2815.             return TRUE;
  2816.         if (SHGetValue(HKEY_LOCAL_MACHINE, REGSTR_PATH_EXPLORER, 
  2817.             TEXT("UseSystemForSystemFolders"), NULL, NULL, NULL) == ERROR_SUCCESS)
  2818.         {
  2819.             dwAttrbSet = FILE_ATTRIBUTE_SYSTEM;
  2820.         }
  2821.         dwAttrb = GetFileAttributes(pszPath);
  2822.         if ((dwAttrb != (DWORD)-1) && (dwAttrb & FILE_ATTRIBUTE_DIRECTORY))
  2823.         {
  2824.             dwAttrb &= ~(FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM);
  2825.             dwAttrb |= dwAttrbSet;
  2826.             return SetFileAttributes(pszPath, dwAttrb);
  2827.         }
  2828.     }
  2829.     return FALSE;
  2830. }
  2831. /*----------------------------------------------------------
  2832. Purpose: Unmark a folder so it is no longer a system folder.
  2833.          (remove either FILE_ATTRIBUTES_READONLY or FILE_ATTRIBUTE_SYSTEM
  2834.          attribute).
  2835. */
  2836. BOOL PathUnmakeSystemFolder(LPCTSTR pszPath)
  2837. {
  2838.     RIPMSG(pszPath && IS_VALID_STRING_PTR(pszPath, -1), "PathUnmakeSystemFolder: caller passed bad pszPath");
  2839.     if (pszPath && *pszPath)
  2840.     {
  2841.         DWORD dwAttrb = GetFileAttributes( pszPath );
  2842.         if ((dwAttrb != (DWORD)-1) && (dwAttrb & FILE_ATTRIBUTE_DIRECTORY))
  2843.         {
  2844.             dwAttrb &= ~(FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM);
  2845.             return SetFileAttributes(pszPath, dwAttrb);
  2846.         }
  2847.     }
  2848.     return FALSE;
  2849. }
  2850. /*----------------------------------------------------------
  2851. Purpose: checks whether given path is a system (shell) folder.
  2852.          if path is NULL, then use the attributes passed in
  2853.          instead of reading them off the disk.
  2854. */
  2855. BOOL PathIsSystemFolder(LPCTSTR pszPath, DWORD dwAttrb)
  2856. {
  2857.     if (pszPath && *pszPath)
  2858.         dwAttrb = GetFileAttributes(pszPath);
  2859.     if ((dwAttrb != (DWORD)-1) && (dwAttrb & FILE_ATTRIBUTE_DIRECTORY))
  2860.     {
  2861.         if (dwAttrb & (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM))
  2862.         {
  2863.             return TRUE;
  2864.         }
  2865.     }
  2866.     return FALSE;
  2867. }
  2868. LPCTSTR PathSkipLeadingSlashes(LPCTSTR pszURL)
  2869. {
  2870.     LPCTSTR pszURLStart = pszURL;
  2871.     RIPMSG(pszURL && IS_VALID_STRING_PTR(pszURL, -1), "PathSkipLeadingSlashes: caller passed bad pszURL");
  2872.     if (pszURL)
  2873.     {
  2874.         // Skip two leading slashes.
  2875.         if (pszURL[0] == TEXT('/') && pszURL[1] == TEXT('/'))
  2876.             pszURLStart += 2;
  2877.         ASSERT(IS_VALID_STRING_PTR(pszURL, -1) &&
  2878.                IsStringContained(pszURL, pszURLStart));
  2879.     }
  2880.     
  2881.     return pszURLStart;
  2882. }
  2883. //
  2884. // returns:
  2885. //      TRUE    given filespec is long (> 8.3 form)
  2886. //      FALSE   filespec is short
  2887. //
  2888. STDAPI_(BOOL) PathIsLFNFileSpec(LPCTSTR pszName)
  2889. {
  2890.     RIPMSG(pszName && IS_VALID_STRING_PTR(pszName, -1), "PathIsLFNFileSpec: caller passed bad pszName");
  2891.     if (pszName)
  2892.     {
  2893.         BOOL bSeenDot = FALSE;
  2894.         int iCount = 1; 
  2895.         
  2896.         while (*pszName)
  2897.         {
  2898.             if (bSeenDot)
  2899.             {
  2900.                 if (iCount > 3)
  2901.                 {
  2902.                     // found a long name
  2903.                     return TRUE;
  2904.                 }
  2905.             }
  2906.             if (*pszName == TEXT(' '))
  2907.             {
  2908.                 // Short names dont have blanks in them.
  2909.                 return TRUE;
  2910.             }
  2911.             if (*pszName == TEXT('.'))
  2912.             {
  2913.                 if (bSeenDot)
  2914.                 {
  2915.                     // short names can only have one '.'
  2916.                     return TRUE;
  2917.                 }
  2918.                 bSeenDot = TRUE;
  2919.                 iCount = 0; // don't include the '.'
  2920.             }
  2921.             else if (iCount > 8)
  2922.             {
  2923.                 // long name
  2924.                 return TRUE;
  2925.             }
  2926.             if (IsDBCSLeadByte(*pszName))
  2927.             {
  2928.                 pszName += 2;
  2929.                 iCount += 2;
  2930.             }
  2931.             else
  2932.             {
  2933.                 pszName++;
  2934.                 iCount++;
  2935.             }
  2936.         }
  2937.     }
  2938.     return FALSE;       // short name
  2939. }
  2940. /*----------------------------------------------------------
  2941. Purpose: Removes regexp [[0-9]*] from base name of file  
  2942.          that is typically added by the wininet cache.
  2943. */
  2944. #ifndef UNIX
  2945. #define DECORATION_OPENING_CHAR TEXT('[')
  2946. #define DECORATION_CLOSING_CHAR TEXT(']')
  2947. #else
  2948. #define DECORATION_OPENING_CHAR TEXT('(')
  2949. #define DECORATION_CLOSING_CHAR TEXT(')')
  2950. #endif /* UNIX */
  2951. STDAPI_(void) PathUndecorate(LPTSTR pszPath)
  2952. {
  2953.     RIPMSG(pszPath && IS_VALID_STRING_PTR(pszPath, -1), "PathUndecorate: caller passed bad pszPath");
  2954.     if (pszPath)
  2955.     {
  2956.         LPTSTR pszExt, pszScan;
  2957.         DWORD cchMove;
  2958.         
  2959.         // First, skip over the extension, if any.
  2960.         pszExt = PathFindExtension(pszPath);
  2961.         ASSERT(pszExt >= pszPath); // points to null at end if no ext
  2962.         // Whoa, a completely empty path
  2963.         if (pszExt <= pszPath)
  2964.             return;
  2965.         // Scan backwards from just before the "."
  2966.         pszScan = pszExt - 1;
  2967.         // Check for closing bracket.
  2968.         if (*pszScan-- != DECORATION_CLOSING_CHAR)
  2969.             return;
  2970.         if (pszScan <= pszPath) // it was a 1-char filename ")"
  2971.             return;
  2972.         if (IsDBCSLeadByte(*pszScan)) // Oops, that ")" was the 2nd byte of a DBCS char
  2973.             return;
  2974.         // Skip over digits.
  2975.         while (pszScan > pszPath && IsDigit(*pszScan))
  2976.             pszScan--;
  2977.         if (IsDBCSLeadByte(*pszScan))  // Oops, that last number was the 2nd byte of a DBCS char
  2978.             return;
  2979.         // Check for opening bracket
  2980.         if (*pszScan != DECORATION_OPENING_CHAR)
  2981.             return;
  2982.         if (pszScan <= pszPath) // it was all decoration (we don't want to go to an empty filename)
  2983.             return;
  2984.         if (IsDBCSLeadByte(*(pszScan-1))) // Oops, that "(" was the 2nd byte of a DBCS char
  2985.             return;
  2986.         // Make sure we're not looking at the end of the path (we don't want to go to an empty filename)
  2987.         if (*(pszScan-1) == FILENAME_SEPARATOR
  2988. #ifndef UNICODE
  2989.             // make sure that slash isn't the 2nd byte of a DBCS char
  2990.             && ((pszScan-1) == pszPath || !IsDBCSLeadByte(*(pszScan-2)))
  2991. #endif
  2992.            )
  2993.         {
  2994.             return;
  2995.         }
  2996.         
  2997.         // Got a decoration.  Cut it out of the string.
  2998.         cchMove = lstrlen(pszExt) + 1;
  2999.         memmove(pszScan, pszExt, cchMove * sizeof(TCHAR));
  3000.     }
  3001. }
  3002. //  If the given environment variable exists as the first part of the path,
  3003. //  then the environment variable is inserted into the output buffer.
  3004. //
  3005. //  Returns TRUE if pszResult is filled in.
  3006. //
  3007. //  Example:  Input  -- C:WINNTSYSTEM32FOO.TXT -and- lpEnvVar = %SYSTEMROOT%
  3008. //            Output -- %SYSTEMROOT%SYSTEM32FOO.TXT
  3009. //
  3010. #ifdef  UNICODE
  3011. #define UnExpandEnvironmentStringForUser    UnExpandEnvironmentStringForUserW
  3012. #else
  3013. #define UnExpandEnvironmentStringForUser    UnExpandEnvironmentStringForUserA
  3014. #endif
  3015. BOOL UnExpandEnvironmentStringForUser(HANDLE hToken, LPCTSTR pszPath, LPCTSTR pszEnvVar, LPTSTR pszResult, UINT cbResult)
  3016. {
  3017.     TCHAR szEnvVar[MAX_PATH];
  3018.     DWORD dwEnvVar = SHExpandEnvironmentStringsForUser(hToken, pszEnvVar, szEnvVar, ARRAYSIZE(szEnvVar));
  3019.     if (dwEnvVar)
  3020.     {
  3021.         dwEnvVar--; // don't count the NULL
  3022.         if (CompareString(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE, szEnvVar, dwEnvVar, pszPath, dwEnvVar) == 2)
  3023.         {
  3024.             if (lstrlen(pszPath) - (int)dwEnvVar + lstrlen(pszEnvVar) < (int)cbResult)
  3025.             {
  3026.                 lstrcpy(pszResult, pszEnvVar);
  3027.                 lstrcat(pszResult, pszPath + dwEnvVar);
  3028.                 return TRUE;
  3029.             }
  3030.         }
  3031.     }
  3032.     return FALSE;
  3033. }
  3034. STDAPI_(BOOL) PathUnExpandEnvStringsForUser(HANDLE hToken, LPCTSTR pszPath, LPTSTR pszBuf, UINT cchBuf)
  3035. {
  3036.     RIPMSG(pszPath && IS_VALID_STRING_PTR(pszPath, -1), "PathUnExpandEnvStrings: caller passed bad pszPath");
  3037.     RIPMSG(pszBuf && IS_VALID_WRITE_BUFFER(pszBuf, TCHAR, cchBuf), "PathUnExpandEnvStrings: caller passed bad pszBuf");
  3038.     DEBUGWhackPathBuffer(pszBuf, cchBuf);
  3039.     if (pszPath && pszBuf)
  3040.     {
  3041.         // 99/05/28 #346950 vtan: WARNING!!! Be careful about the order of comparison
  3042.         // here. The longer paths (supersets of other possible paths) MUST be compared
  3043.         // first. For example (default case):
  3044.         //      %APPDATA%       =   x:Documents And SettingsuserApplication Data
  3045.         //      %USERPROFILE%   =   x:Documents And Settingsuser
  3046.         // If %USERPROFILE% is matched first then %APPDATA% will never be matched.
  3047.         // Added %APPDATA% to support Darwin installation into that folder and the
  3048.         // setting of the link icon location.
  3049.         // Also note that %APPDATA% and %USERPROFILE% are user relative and depend on
  3050.         // the context in which this function is invoked. Normally it is within the
  3051.         // currently logged on user's context but Darwin installs from msiexec.exe which
  3052.         // is launched from SYSTEM. Unless the process' environment block is correctly
  3053.         // modified the current user information is incorrect. In this case it is up
  3054.         // to the process to impersonate a user on a thread. We get the impersonated
  3055.         // user information from the hToken passed to us.
  3056.         return (UnExpandEnvironmentStringForUser(hToken, pszPath, TEXT("%APPDATA%"), pszBuf, cchBuf)           ||
  3057.                 UnExpandEnvironmentStringForUser(hToken, pszPath, TEXT("%USERPROFILE%"), pszBuf, cchBuf)       ||
  3058.                 UnExpandEnvironmentStringForUser(hToken, pszPath, TEXT("%ALLUSERSPROFILE%"), pszBuf, cchBuf)   ||
  3059.                 UnExpandEnvironmentStringForUser(hToken, pszPath, TEXT("%ProgramFiles%"), pszBuf, cchBuf)      ||
  3060.                 UnExpandEnvironmentStringForUser(hToken, pszPath, TEXT("%SystemRoot%"), pszBuf, cchBuf)        ||
  3061.                 UnExpandEnvironmentStringForUser(hToken, pszPath, TEXT("%SystemDrive%"), pszBuf, cchBuf));
  3062.     }
  3063.     else
  3064.     {
  3065.         return FALSE;
  3066.     }
  3067. }
  3068. STDAPI_(BOOL) PathUnExpandEnvStrings(LPCTSTR pszPath, LPTSTR pszBuf, UINT cchBuf)
  3069. {
  3070.     return(PathUnExpandEnvStringsForUser(NULL, pszPath, pszBuf, cchBuf));
  3071. }