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

416 lines
12 KiB

  1. /*++
  2. Copyright (c) 2000-2001 Microsoft Corporation
  3. Module Name:
  4. Win9xPath.cpp
  5. Abstract:
  6. Munge a path the same was as Win9x.
  7. Much of this code was copied from Win9x:
  8. \\redrum\slm\proj\win\src\CORE\win32\KERNEL\dirutil.c
  9. \\redrum\slm\proj\win\src\CORE\win32\KERNEL\fileopcc.c
  10. Path changes:
  11. 1. Translate all / to \
  12. 2. Remove all . and .. from the path, also removes some spaces
  13. (This is really bad Win9x code)
  14. 3. Remove all spaces before a \, except spaces following a .
  15. ( "abc \xyz" -> "abc\xyz" or ". \xyz" -> ". \xyz")
  16. Notes:
  17. None
  18. History:
  19. 10/05/2000 robkenny Created
  20. 08/14/2001 robkenny Moved code inside the ShimLib namespace.
  21. --*/
  22. #include "Win9xPath.h"
  23. #include "ShimLib.h"
  24. namespace ShimLib
  25. {
  26. #define WHACK L'\\'
  27. #define SPACE L' '
  28. #define DOT L'.'
  29. #define QUESTION L'.'
  30. #define EOS L'\0'
  31. #define chNetIni L'\\'
  32. #define chDirSep L'\\'
  33. #define chDirSep2 L'/'
  34. #define chRelDir L'.'
  35. #define IsWhackWhack( lpstr ) (lpstr[0] == WHACK && lpstr[1] == WHACK)
  36. #define IsWhackWhackDotWhack( lpstr ) (lpstr[0] == WHACK && lpstr[1] == WHACK && lpstr[2] == DOT && lpstr[3] == WHACK)
  37. #define IsWhackWhackQuestionWhack( lpstr ) (lpstr[0] == WHACK && lpstr[1] == WHACK && lpstr[2] == QUESTION && lpstr[3] == WHACK)
  38. #define CopySz wcscpy // Must be safe for overlapping strings
  39. /*** PchGetNetDir - Validates a net drive spcification and returns
  40. ** a pointer to directory portion.
  41. **
  42. ** Synopsis
  43. ** WCHAR * = PchGetNetDir (pchNetName)
  44. **
  45. ** Input:
  46. ** pchNetName - pointer to a string previously validated as
  47. ** the start of a net name (begins with \\)
  48. **
  49. ** Output:
  50. ** returns pointer to the start of the directory portion of a net path
  51. **
  52. ** Errors:
  53. ** returns NULL if the net name is invalid
  54. **
  55. ** Description:
  56. ** This function takes a name starting with \\ and confirms that
  57. ** it has one following \. It returns the position of the directory
  58. ** portion. For the string
  59. **
  60. ** \\server\share[\path[\]]
  61. **
  62. ** it returns
  63. **
  64. ** [\path[\]]
  65. */
  66. const WCHAR * PchGetNetDir (const WCHAR * pchNetName)
  67. {
  68. register const WCHAR * pch = pchNetName;
  69. // Skip starting slashes
  70. pch +=2;
  71. // Skip to first backslash
  72. for (;*pch != chNetIni; pch++) {
  73. if (*pch == EOS) {
  74. // No code required.
  75. return (NULL);
  76. }
  77. }
  78. pch++; // skip past 1st backslash
  79. // Skip to second backslash
  80. for (;(*pch != chDirSep) && (*pch != chDirSep2); pch++) {
  81. if (*pch == EOS) {
  82. // ok if share with no following \path
  83. return ((*(pch-1)==chNetIni) ? NULL : pch);
  84. }
  85. }
  86. return (pch);
  87. }
  88. /*** DwRemoveDots - Remove any dots from a path name
  89. **
  90. ** Synopsis
  91. ** DWORD DwRemoveDots (pchPath)
  92. **
  93. ** Input:
  94. ** pchPath - A path string
  95. **
  96. **
  97. ** Output:
  98. ** returns the number of double dot levels removed from front
  99. **
  100. ** Errors:
  101. ** returns dwInvalid if invalid path
  102. **
  103. ** Description:
  104. ** Removes ..\ and .\ sequences from a path string. The path
  105. ** string should not include the root drive or net name portion.
  106. ** The return value of is the number of levels removed from the
  107. ** start of the string. Levels removed from inside the string
  108. ** will not be returned. For example:
  109. **
  110. ** String Result Return
  111. **
  112. ** ..\..\dir1 dir1 2
  113. ** dir1\..\dir2 dir2 0
  114. ** dir1\..\..\dir2 dir2 1
  115. ** .\dir1 dir1 0
  116. ** dir1\.\dir2 dir1\dir2 0
  117. **
  118. ** A backslash at the start of the string will be ignored.
  119. */
  120. DWORD DwRemoveDots (WCHAR * pchPath)
  121. {
  122. BOOL fInside = FALSE;
  123. DWORD cLevel = 0;
  124. DWORD cBackup;
  125. register WCHAR * pchR;
  126. register WCHAR * pchL;
  127. // Check for invalid characters
  128. // if (!FFixPathChars(pchPath)) {
  129. // // No code required.
  130. // return dwInvalid;
  131. // }
  132. //
  133. // Skip slashes
  134. for (; *pchPath == chDirSep; pchPath++)
  135. ;
  136. pchL = pchR = pchPath;
  137. // Loop through handling each directory part
  138. while (*pchR) {
  139. // This part starts with dot. Is it one or more?
  140. if (*pchR++ == chRelDir) {
  141. for (cBackup = 0; *pchR == chRelDir; cBackup++, pchR++)
  142. ;
  143. if (cBackup) {
  144. // More than one dot. Back up the left pointer.
  145. if ((*pchR != chDirSep) && (*pchR != EOS)) {
  146. // we got a [.]+X (X != '\') might be an LFN
  147. // process this as a name
  148. goto name_processing;
  149. }
  150. // Doesn't advance for ending ..
  151. for (; *pchR == chDirSep; pchR++)
  152. ;
  153. if (fInside) {
  154. for (; cBackup; cBackup--) {
  155. if (pchL <= pchPath) {
  156. cLevel += cBackup;
  157. fInside = FALSE;
  158. break;
  159. }
  160. // Remove the previous part
  161. for (pchL -= 2; *pchL != chDirSep; pchL--) {
  162. if (pchL <= pchPath) {
  163. fInside = FALSE;
  164. pchL--;
  165. break;
  166. }
  167. }
  168. pchL++;
  169. }
  170. } else {
  171. cLevel += cBackup;
  172. }
  173. // Subtract ending backslash if not root
  174. if ((*pchR == EOS) && (pchL != pchPath))
  175. pchL--;
  176. CopySz(pchL, pchR);
  177. pchR = pchL;
  178. } else {
  179. // This part starts with one dot. Throw it away.
  180. if (*pchR != chDirSep) {
  181. // Special case "\." by converting it to ""
  182. // unless it is a root, when it becomes "\".
  183. if (*pchR == EOS) {
  184. if (pchL == pchPath)
  185. *(pchR-1) = EOS; // root
  186. else
  187. *(pchR-2) = EOS; // not root
  188. return cLevel;
  189. }
  190. // we started with a '.' and then there was no '\'
  191. // might be an LFN name
  192. goto name_processing;
  193. }
  194. pchR++;
  195. CopySz(pchL, pchR);
  196. pchR = pchL;
  197. }
  198. } else {
  199. name_processing:
  200. // This part is a name. Skip it.
  201. fInside = TRUE;
  202. for (; TRUE; pchR++) {
  203. if (*pchR == chDirSep) {
  204. if (*(pchR-1) == chRelDir) {
  205. // This name has one or more dots at the end.
  206. // Remove the last dot (NT3.5 does this).
  207. pchL = pchR-1;
  208. CopySz(pchL, pchR);
  209. pchR = pchL; // point to chDirSep again
  210. }
  211. for (; *pchR == chDirSep; pchR++)
  212. ;
  213. break;
  214. } else if (*pchR == EOS) {
  215. // Remove trailing dots.
  216. // NB Can't fall off the beginning since the first WCHAR
  217. // of the current path element was not chRelDir.
  218. for (; *(pchR-1) == chRelDir; pchR--)
  219. ;
  220. // Overstore the first trailing dot, if there is one.
  221. *pchR = EOS;
  222. break;
  223. }
  224. }
  225. pchL = pchR;
  226. }
  227. }
  228. return cLevel;
  229. }
  230. // Get the Drive portion of this path,
  231. // Either C: or \\server\disk format.
  232. const WCHAR * GetDrivePortion(const WCHAR * uncorrected)
  233. {
  234. if (uncorrected && uncorrected[0])
  235. {
  236. // Look for DOS style
  237. if (uncorrected[1] == ':')
  238. {
  239. uncorrected += 2;
  240. }
  241. // Look for UNC
  242. else if (IsWhackWhack(uncorrected))
  243. {
  244. const WCHAR * pchDir = PchGetNetDir(uncorrected);
  245. if (pchDir == NULL)
  246. {
  247. if (IsWhackWhackDotWhack(uncorrected) || IsWhackWhackQuestionWhack(uncorrected))
  248. {
  249. uncorrected += 4;
  250. }
  251. }
  252. else
  253. {
  254. uncorrected = pchDir;
  255. }
  256. }
  257. }
  258. return uncorrected;
  259. }
  260. // Remove blank directory names "abc\ \def" -> "abc\def"
  261. void RemovePreceedingBlanks(WCHAR * directoryPortion)
  262. {
  263. if (directoryPortion == NULL || directoryPortion[0] == 0)
  264. {
  265. return;
  266. }
  267. WCHAR * blank = wcschr(directoryPortion, SPACE);
  268. while (blank != NULL)
  269. {
  270. // Find the end of the spaces
  271. WCHAR * blankEnd = blank;
  272. while (*blankEnd == SPACE && *blankEnd != WHACK)
  273. {
  274. ++blankEnd;
  275. }
  276. // Do not remove spaces *after* a period
  277. BOOL bPrevCharDot = (blank > directoryPortion) && (blank[-1] == DOT);
  278. if (bPrevCharDot)
  279. {
  280. blank = blankEnd;
  281. continue;
  282. }
  283. // If the the blank is a \ then we simply move the string down
  284. if (*blankEnd == WHACK)
  285. {
  286. BOOL bPrevCharWhack = blank[-1] == WHACK;
  287. // If the previous WCHAR is a \
  288. // we remove the \ at the end of the spaces as well
  289. if (bPrevCharWhack)
  290. blankEnd += 1;
  291. CopySz(blank, blankEnd);
  292. // Note: we don't change the value of blank,
  293. // since we moved all the data to it!
  294. }
  295. else
  296. {
  297. blank = blankEnd + 1;
  298. }
  299. // Keep on truckin'
  300. blank = wcschr(blank, SPACE);
  301. }
  302. }
  303. // Win9x performs some special process on path names,
  304. // particularly they remove spaces before slashes.
  305. WCHAR * W9xPathMassageW(const WCHAR * uncorrect)
  306. {
  307. if (uncorrect == NULL)
  308. return NULL;
  309. // Make a buffer large enough for the resulting string
  310. WCHAR * correctBuffer = StringDuplicateW(uncorrect);
  311. if (!correctBuffer)
  312. return NULL;
  313. // Convert all '/' to '\'
  314. // Win9x allows //robkenny/d as a valid UNC name
  315. for (WCHAR * whack = correctBuffer; *whack; ++whack)
  316. {
  317. if (*whack == chDirSep2)
  318. *whack = chDirSep;
  319. }
  320. // We need to skip past the drive portion of the path
  321. WCHAR * directoryPortion = (WCHAR *)GetDrivePortion(correctBuffer);
  322. // Remove blank directory names "abc\ \def" -> "abc\def"
  323. // These are remove entirely rather than just removing the spaces,
  324. // because we could end up changing "\ \abc" -> "\\abc"
  325. RemovePreceedingBlanks(directoryPortion);
  326. // DwRemoveDots is used to remove all .\ and any ..\ in the middle of a path.
  327. DWORD dwUpDirs = DwRemoveDots(directoryPortion);
  328. if (dwUpDirs > 0)
  329. {
  330. // We need to add some ..\ to the front of the directoryPortion string
  331. // This is sorta wierd, removing the dots and adding them back again.
  332. // But the DwRemoveDots routine was copied strait from Win9x, and I
  333. // didn't want to change it in any way, as to preserve all peculiarities.
  334. // So we have to add back the leading parent directories that were removed.
  335. DWORD dwLen = (dwUpDirs * 3) + wcslen(correctBuffer) + 1;
  336. WCHAR * moreCorrectBuffer = (WCHAR*)malloc(dwLen * sizeof(WCHAR));
  337. if (moreCorrectBuffer)
  338. {
  339. moreCorrectBuffer[0] = 0;
  340. // Copy any drive portion
  341. wcsncpy(moreCorrectBuffer, correctBuffer, directoryPortion - correctBuffer);
  342. // add as many "..\" as were removed by DwRemoveDots
  343. while (dwUpDirs-- > 0)
  344. {
  345. wcscat(moreCorrectBuffer, L"..\\");
  346. }
  347. // finally the remainder of the string
  348. wcscat(moreCorrectBuffer, directoryPortion);
  349. delete correctBuffer;
  350. correctBuffer = moreCorrectBuffer;
  351. }
  352. }
  353. return correctBuffer;
  354. }
  355. }; // end of namespace ShimLib