Leaked source code of windows server 2003
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.

3767 lines
111 KiB

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