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.

560 lines
15 KiB

  1. /*++
  2. Copyright (c) 1991-92 Microsoft Corporation
  3. Module Name:
  4. canon.c
  5. Abstract:
  6. Code to 'canonicalize' a path name. This code may be replaced by OS or FS
  7. code sometime in the future, so keep it separate from the rest of the net
  8. canonicalization code.
  9. We do not canonicalize a path with reference to a specific drive. Therefore,
  10. we can't use rules about the number of characters or format of a path
  11. component (eg. FAT filename rules). We leave this up to the file system. The
  12. CanonicalizePathName function in this module will make a path name look
  13. presentable, nothing more
  14. Contents:
  15. CanonicalizePathName
  16. (ConvertPathCharacters)
  17. (ParseLocalDevicePrefix)
  18. (ConvertPathMacros)
  19. (BackUpPath)
  20. Author:
  21. Richard L Firth (rfirth) 02-Jan-1992
  22. Revision History:
  23. --*/
  24. #include "nticanon.h"
  25. #include <tstring.h> // NetpInitOemString().
  26. const TCHAR text_AUX[] = TEXT("AUX");
  27. const TCHAR text_COM[] = TEXT("COM");
  28. const TCHAR text_DEV[] = TEXT("DEV");
  29. const TCHAR text_LPT[] = TEXT("LPT");
  30. const TCHAR text_PRN[] = TEXT("PRN");
  31. //
  32. // prototypes
  33. //
  34. STATIC
  35. VOID
  36. ConvertPathCharacters(
  37. IN LPTSTR Path
  38. );
  39. STATIC
  40. BOOL
  41. ConvertDeviceName(
  42. IN OUT LPTSTR PathName
  43. );
  44. STATIC
  45. BOOL
  46. ParseLocalDevicePrefix(
  47. IN OUT LPTSTR* DeviceName
  48. );
  49. STATIC
  50. BOOL
  51. ConvertPathMacros(
  52. IN OUT LPTSTR Path
  53. );
  54. STATIC
  55. LPTSTR
  56. BackUpPath(
  57. IN LPTSTR Stopper,
  58. IN LPTSTR Path
  59. );
  60. //
  61. // routines
  62. //
  63. NET_API_STATUS
  64. CanonicalizePathName(
  65. IN LPTSTR PathPrefix OPTIONAL,
  66. IN LPTSTR PathName,
  67. OUT LPTSTR Buffer,
  68. IN DWORD BufferSize,
  69. OUT LPDWORD RequiredSize OPTIONAL
  70. )
  71. /*++
  72. Routine Description:
  73. Given a path name, this function will 'canonicalize' it - that is, convert
  74. it to a standard form. We attempt to accomplish here what path canonicalization
  75. accomplished in LANMAN. The following is done:
  76. * all macros in the input filename (\., .\, \.., ..\) are removed and
  77. replaced by path components
  78. * any required translations are performed on the path specification:
  79. * unix-style / converted to dos-style \
  80. * specific transliteration. NOTE: the input case is NOT converted.
  81. The underlying file system may be case insensitive. Just pass
  82. through the path as presented by the caller
  83. * device names (ie name space controlled by us) is canonicalized by
  84. converting device names to UPPER CASE and removing trailing colon
  85. in all but disk devices
  86. What is NOT done:
  87. * starts with a drive specifier (eg. D:) or a sharepoint specifier
  88. (eg \\computername\sharename)
  89. * all path components required to fully specify the required path or
  90. file are included in the output path specification
  91. NOTES: 1. This function only uses local naming rules. It does not gurantee
  92. to 'correctly' canonicalize a remote path name
  93. 2. Character validation is not done - this is left to the underlying
  94. file system
  95. Arguments:
  96. PathPrefix - an OPTIONAL parameter. If non-NULL, points to a string which
  97. is to be prepended to PathName before canonicalization of
  98. the concatenated strings. This will typically be another
  99. drive or path
  100. PathName - input path to canonicalize. May be already fully qualified,
  101. or may be one of the following:
  102. - relative local path name (eg foo\bar)
  103. - remote path name (eg \\computer\share\foo\bar\filename.ext)
  104. - device name (eg LPT1:)
  105. Buffer - place to store the canonicalized name
  106. BufferSize - size (in bytes) of Buffer
  107. RequiredSize- OPTIONAL parameter. If supplied AND Buffer was not sufficient
  108. to hold the results of the canonicalization then will contain
  109. the size of buffer necessary to retrieve canonicalized version
  110. of PathName (optionally prefixed by PathPrefix)
  111. Return Value:
  112. DWORD
  113. Success - NERR_Success
  114. Failure - ERROR_INVALID_NAME
  115. There is a fundamental problem with PathName (like too
  116. many ..\ macros) or the name is too long
  117. NERR_BufTooSmall
  118. Buffer is too small to hold the canonicalized path
  119. --*/
  120. {
  121. TCHAR pathBuffer[MAX_PATH*2 + 1];
  122. DWORD prefixLen;
  123. DWORD pathLen;
  124. if (ARGUMENT_PRESENT(PathPrefix)) {
  125. prefixLen = STRLEN(PathPrefix);
  126. if (prefixLen) {
  127. // Make sure we don't overrun our buffer
  128. if (prefixLen > MAX_PATH*2 ) {
  129. return ERROR_INVALID_NAME;
  130. }
  131. STRCPY(pathBuffer, PathPrefix);
  132. if (!IS_PATH_SEPARATOR(pathBuffer[prefixLen - 1])) {
  133. STRCAT(pathBuffer, TEXT("\\"));
  134. ++prefixLen;
  135. }
  136. if (IS_PATH_SEPARATOR(*PathName)) {
  137. ++PathName;
  138. }
  139. }
  140. } else {
  141. prefixLen = 0;
  142. pathBuffer[0] = 0;
  143. }
  144. pathLen = STRLEN(PathName);
  145. if (pathLen + prefixLen > MAX_PATH*2 - 1) {
  146. return ERROR_INVALID_NAME;
  147. }
  148. STRCAT(pathBuffer, PathName);
  149. ConvertPathCharacters(pathBuffer);
  150. if (!ConvertDeviceName(pathBuffer)) {
  151. if (!ConvertPathMacros(pathBuffer)) {
  152. return ERROR_INVALID_NAME;
  153. }
  154. }
  155. pathLen = STRSIZE(pathBuffer);
  156. if (pathLen > BufferSize) {
  157. if (ARGUMENT_PRESENT(RequiredSize)) {
  158. *RequiredSize = pathLen;
  159. }
  160. return NERR_BufTooSmall;
  161. }
  162. STRCPY(Buffer, pathBuffer);
  163. return NERR_Success;
  164. }
  165. STATIC
  166. VOID
  167. ConvertPathCharacters(
  168. IN LPTSTR Path
  169. )
  170. /*++
  171. Routine Description:
  172. Converts non-standard path component characters to their canonical
  173. counterparts. Currently all this routine does is convert / to \. It may
  174. be enhanced in future to perform case conversion
  175. Arguments:
  176. Path - pointer to path buffer to transform. Performs conversion in place
  177. Return Value:
  178. None.
  179. --*/
  180. {
  181. while (*Path) {
  182. if (*Path == TCHAR_FWDSLASH) {
  183. *Path = TCHAR_BACKSLASH;
  184. }
  185. ++Path;
  186. }
  187. }
  188. STATIC
  189. BOOL
  190. ConvertDeviceName(
  191. IN OUT LPTSTR PathName
  192. )
  193. /*++
  194. Routine Description:
  195. If PathBuffer contains a device name of AUX or PRN (case insensitive),
  196. convert to COM1 and LPT1 resp. If PathBuffer is a device and has a local
  197. device prefix (\dev\ (LM20 style) or \\.\) then skips it, but leaves the
  198. prefix in the buffer.
  199. Device names (including DISK devices) will be UPPERCASEd, whatever that
  200. means for other locales.
  201. ASSUMES: Disk Device is single CHARACTER, followed by ':' (optionally
  202. followed by rest of path)
  203. Arguments:
  204. PathName - pointer to buffer containing possible device name. Performs
  205. conversion in place
  206. Return Value:
  207. BOOL
  208. TRUE - PathName is a DOS device name
  209. FALSE - PathName not a DOS device
  210. --*/
  211. {
  212. BOOL isDeviceName = FALSE;
  213. #ifndef UNICODE
  214. UNICODE_STRING PathName_U;
  215. OEM_STRING PathName_A;
  216. PWSTR PathName_W;
  217. NetpInitOemString(&PathName_A, PathName);
  218. RtlOemStringToUnicodeString(&PathName_U, &PathName_A, TRUE);
  219. PathName_W = PathName_U.Buffer;
  220. if (RtlIsDosDeviceName_U(PathName_W)) {
  221. LPTSTR deviceName = PathName;
  222. DWORD deviceLength;
  223. ParseLocalDevicePrefix(&deviceName);
  224. deviceLength = STRLEN(deviceName) - 1;
  225. if (deviceName[deviceLength] == TCHAR_COLON) {
  226. deviceName[deviceLength] = 0;
  227. --deviceLength;
  228. }
  229. if (!STRICMP(deviceName, text_PRN)) {
  230. STRCPY(deviceName, text_LPT);
  231. STRCAT(deviceName, TEXT("1"));
  232. } else if (!STRICMP(deviceName, text_AUX)) {
  233. STRCPY(deviceName, text_COM);
  234. STRCAT(deviceName, TEXT("1"));
  235. }
  236. isDeviceName = TRUE;
  237. STRUPR(deviceName);
  238. } else {
  239. switch (RtlDetermineDosPathNameType_U(PathName_W)) {
  240. case RtlPathTypeDriveRelative:
  241. case RtlPathTypeDriveAbsolute:
  242. *PathName = TOUPPER(*PathName);
  243. }
  244. }
  245. RtlFreeUnicodeString(&PathName_U);
  246. #else
  247. if (RtlIsDosDeviceName_U(PathName)) {
  248. LPTSTR deviceName = PathName;
  249. DWORD deviceLength;
  250. ParseLocalDevicePrefix(&deviceName);
  251. deviceLength = STRLEN(deviceName) - 1;
  252. if (deviceName[deviceLength] == TCHAR_COLON) {
  253. deviceName[deviceLength] = 0;
  254. --deviceLength;
  255. }
  256. if (!STRICMP(deviceName, text_PRN)) {
  257. STRCPY(deviceName, text_LPT);
  258. STRCAT(deviceName, TEXT("1"));
  259. } else if (!STRICMP(deviceName, text_AUX)) {
  260. STRCPY(deviceName, text_COM);
  261. STRCAT(deviceName, TEXT("1"));
  262. }
  263. isDeviceName = TRUE;
  264. STRUPR(deviceName);
  265. } else {
  266. switch (RtlDetermineDosPathNameType_U(PathName)) {
  267. case RtlPathTypeDriveRelative:
  268. case RtlPathTypeDriveAbsolute:
  269. *PathName = TOUPPER(*PathName);
  270. }
  271. }
  272. #endif
  273. return isDeviceName;
  274. }
  275. STATIC
  276. BOOL
  277. ParseLocalDevicePrefix(
  278. IN OUT LPTSTR* DeviceName
  279. )
  280. /*++
  281. Routine Description:
  282. If a device name starts with a local device name specifier - "\\.\" or
  283. "\DEV\" - then move DeviceName past the prefix and return TRUE, else FALSE
  284. Arguments:
  285. DeviceName - pointer to string containing potential local device name,
  286. prefixed by "\\.\" or "\DEV\". If the local device prefix
  287. is present the string pointer is advanced past it to the
  288. device name proper
  289. Return Value:
  290. BOOL
  291. TRUE - DeviceName has a local device prefix. DeviceName now points at
  292. the name after the prefix
  293. FALSE - DeviceName doesn't have a local device prefix
  294. --*/
  295. {
  296. LPTSTR devName = *DeviceName;
  297. if (IS_PATH_SEPARATOR(*devName)) {
  298. ++devName;
  299. if (!STRNICMP(devName, text_DEV, 3)) {
  300. devName += 3;
  301. } else if (IS_PATH_SEPARATOR(*devName)) {
  302. ++devName;
  303. if (*devName == TCHAR_DOT) {
  304. ++devName;
  305. } else {
  306. return FALSE;
  307. }
  308. } else {
  309. return FALSE;
  310. }
  311. if (IS_PATH_SEPARATOR(*devName)) {
  312. ++devName;
  313. *DeviceName = devName;
  314. return TRUE;
  315. }
  316. }
  317. return FALSE;
  318. }
  319. STATIC
  320. BOOL
  321. ConvertPathMacros(
  322. IN OUT LPTSTR Path
  323. )
  324. /*++
  325. Routine Description:
  326. Removes path macros (\.. and \.) and replaces them with the correct level
  327. of path components. This routine expects path macros to appear in a path
  328. like this:
  329. <path>\.
  330. <path>\.\<more-path>
  331. <path>\..
  332. <path>\..\<more-path>
  333. I.e. a macro will either be terminated by the End-Of-String character (\0)
  334. or another path separator (\).
  335. Assumes Path has \ for path separator, not /
  336. Arguments:
  337. Path - pointer to a string containing a path to convert. Path must
  338. contain all the path components that will appear in the result
  339. E.g. Path = "d:\alpha\beta\gamma\..\delta\..\..\zeta\foo\bar"
  340. will result in Path = "d:\zeta\foo\bar"
  341. Path should contain back slashes (\) for path separators if the
  342. correct results are to be produced
  343. Return Value:
  344. TRUE - Path converted
  345. FALSE - Path contained an error
  346. --*/
  347. {
  348. LPTSTR ptr = Path;
  349. LPTSTR lastSlash = NULL;
  350. LPTSTR previousLastSlash = NULL;
  351. TCHAR ch;
  352. //
  353. // if this path is UNC then move the pointer past the computer name to the
  354. // start of the (supposed) share name. Treat the remnants as a relative path
  355. //
  356. if (IS_PATH_SEPARATOR(Path[0]) && IS_PATH_SEPARATOR(Path[1])) {
  357. Path += 2;
  358. while (!IS_PATH_SEPARATOR(*Path) && *Path) {
  359. ++Path;
  360. }
  361. if (!*Path) {
  362. return FALSE; // we had \\computername which is bad
  363. }
  364. ++Path; // past \ into share name
  365. if (IS_PATH_SEPARATOR(*Path)) {
  366. return FALSE; // we had \\computername\\ which is bad
  367. }
  368. }
  369. ptr = Path;
  370. //
  371. // remove all \., .\, \.. and ..\ from path
  372. //
  373. while ((ch = *ptr) != TCHAR_EOS) {
  374. if (ch == TCHAR_BACKSLASH) {
  375. if (lastSlash == ptr - 1) {
  376. return FALSE;
  377. }
  378. previousLastSlash = lastSlash;
  379. lastSlash = ptr;
  380. } else if ((ch == TCHAR_DOT) && ((lastSlash == ptr - 1) || (ptr == Path))) {
  381. TCHAR nextCh = *(ptr + 1);
  382. if (nextCh == TCHAR_DOT) {
  383. TCHAR nextCh = *(ptr + 2);
  384. if ((nextCh == TCHAR_BACKSLASH) || (nextCh == TCHAR_EOS)) {
  385. if (!previousLastSlash) {
  386. return FALSE;
  387. }
  388. STRCPY(previousLastSlash, ptr + 2);
  389. if (nextCh == TCHAR_EOS) {
  390. break;
  391. }
  392. ptr = lastSlash = previousLastSlash;
  393. previousLastSlash = BackUpPath(Path, ptr - 1);
  394. }
  395. } else if (nextCh == TCHAR_BACKSLASH) {
  396. LPTSTR src = lastSlash ? ptr + 1 : ptr + 2;
  397. LPTSTR dst = lastSlash ? lastSlash : ptr;
  398. STRCPY(dst, src);
  399. continue; // at current character position
  400. } else if (nextCh == TCHAR_EOS) {
  401. *(lastSlash ? lastSlash : ptr) = TCHAR_EOS;
  402. break;
  403. }
  404. }
  405. ++ptr;
  406. }
  407. //
  408. // path may be empty
  409. //
  410. return TRUE;
  411. }
  412. STATIC
  413. LPTSTR
  414. BackUpPath(
  415. IN LPTSTR Stopper,
  416. IN LPTSTR Path
  417. )
  418. /*++
  419. Routine Description:
  420. Searches backwards in a string for a path separator character (back-slash)
  421. Arguments:
  422. Stopper - pointer past which Path cannot be backed up
  423. Path - pointer to path to back up
  424. Return Value:
  425. Pointer to backed-up path, or NULL if an error occurred
  426. --*/
  427. {
  428. while ((*Path != TCHAR_BACKSLASH) && (Path != Stopper)) {
  429. --Path;
  430. }
  431. return (*Path == TCHAR_BACKSLASH) ? Path : NULL;
  432. }