Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

3807 lines
108 KiB

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