Path.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433
  1. //------------------------------------------------------------------------------
  2. //
  3. // System.IO.Path.cs
  4. //
  5. // Copyright (C) 2001 Moonlight Enterprises, All Rights Reserved
  6. // Copyright (C) 2002 Ximian, Inc. (http://www.ximian.com)
  7. // Copyright (C) 2003 Ben Maurer
  8. //
  9. // Author: Jim Richardson, [email protected]
  10. // Dan Lewis ([email protected])
  11. // Gonzalo Paniagua Javier ([email protected])
  12. // Ben Maurer ([email protected])
  13. // Created: Saturday, August 11, 2001
  14. //
  15. //------------------------------------------------------------------------------
  16. //
  17. // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
  18. //
  19. // Permission is hereby granted, free of charge, to any person obtaining
  20. // a copy of this software and associated documentation files (the
  21. // "Software"), to deal in the Software without restriction, including
  22. // without limitation the rights to use, copy, modify, merge, publish,
  23. // distribute, sublicense, and/or sell copies of the Software, and to
  24. // permit persons to whom the Software is furnished to do so, subject to
  25. // the following conditions:
  26. //
  27. // The above copyright notice and this permission notice shall be
  28. // included in all copies or substantial portions of the Software.
  29. //
  30. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  31. // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  32. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  33. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  34. // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  35. // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  36. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  37. //
  38. using System;
  39. using System.Runtime.CompilerServices;
  40. namespace System.IO
  41. {
  42. public sealed class Path
  43. {
  44. public static readonly char AltDirectorySeparatorChar;
  45. public static readonly char DirectorySeparatorChar;
  46. public static readonly char[] InvalidPathChars;
  47. public static readonly char PathSeparator;
  48. internal static readonly string DirectorySeparatorStr;
  49. public static readonly char VolumeSeparatorChar;
  50. private static readonly char[] PathSeparatorChars;
  51. private static readonly bool dirEqualsVolume;
  52. private Path ()
  53. {
  54. }
  55. // class methods
  56. public static string ChangeExtension (string path, string extension)
  57. {
  58. if (path == null)
  59. return null;
  60. if (path.IndexOfAny (InvalidPathChars) != -1)
  61. throw new ArgumentException ("Illegal characters in path", "path");
  62. int iExt = findExtension (path);
  63. if (extension == null)
  64. return iExt < 0 ? path : path.Substring (0, iExt);
  65. else if (extension == String.Empty)
  66. return iExt < 0 ? path + '.' : path.Substring (0, iExt + 1);
  67. else if (path.Length != 0) {
  68. if (extension.Length > 0 && extension [0] != '.')
  69. extension = "." + extension;
  70. } else
  71. extension = String.Empty;
  72. if (iExt < 0) {
  73. return path + extension;
  74. } else if (iExt > 0) {
  75. string temp = path.Substring (0, iExt);
  76. return temp + extension;
  77. }
  78. return extension;
  79. }
  80. public static string Combine (string path1, string path2)
  81. {
  82. if (path1 == null)
  83. throw new ArgumentNullException ("path1");
  84. if (path2 == null)
  85. throw new ArgumentNullException ("path2");
  86. if (path1 == String.Empty)
  87. return path2;
  88. if (path2 == String.Empty)
  89. return path1;
  90. if (path1.IndexOfAny (InvalidPathChars) != -1)
  91. throw new ArgumentException ("Illegal characters in path", "path1");
  92. if (path2.IndexOfAny (InvalidPathChars) != -1)
  93. throw new ArgumentException ("Illegal characters in path", "path2");
  94. //TODO???: UNC names
  95. // LAMESPEC: MS says that if path1 is not empty and path2 is a full path
  96. // it should throw ArgumentException
  97. if (IsPathRooted (path2))
  98. return path2;
  99. if (Array.IndexOf (PathSeparatorChars, path1 [path1.Length - 1]) == -1)
  100. return path1 + DirectorySeparatorChar + path2;
  101. return path1 + path2;
  102. }
  103. public static string GetDirectoryName (string path)
  104. {
  105. // LAMESPEC: For empty string MS docs say both
  106. // return null AND throw exception. Seems .NET throws.
  107. if (path == String.Empty)
  108. throw new ArgumentException();
  109. if (path == null || GetPathRoot (path) == path)
  110. return null;
  111. CheckArgument.WhitespaceOnly (path);
  112. CheckArgument.PathChars (path);
  113. int nLast = path.LastIndexOfAny (PathSeparatorChars);
  114. if (nLast == 0)
  115. nLast++;
  116. if (nLast > 0) {
  117. string ret = path.Substring (0, nLast);
  118. int l = ret.Length;
  119. if (l >= 2 && ret [l - 1] == VolumeSeparatorChar)
  120. return ret + DirectorySeparatorChar;
  121. else
  122. return ret;
  123. }
  124. return String.Empty;
  125. }
  126. public static string GetExtension (string path)
  127. {
  128. if (path == null)
  129. return null;
  130. if (path.IndexOfAny (InvalidPathChars) != -1)
  131. throw new ArgumentException ("Illegal characters in path", "path");
  132. int iExt = findExtension (path);
  133. if (iExt > -1)
  134. {
  135. if (iExt < path.Length - 1)
  136. return path.Substring (iExt);
  137. }
  138. return string.Empty;
  139. }
  140. public static string GetFileName (string path)
  141. {
  142. if (path == null || path == String.Empty)
  143. return path;
  144. if (path.IndexOfAny (InvalidPathChars) != -1)
  145. throw new ArgumentException ("Illegal characters in path", "path");
  146. int nLast = path.LastIndexOfAny (PathSeparatorChars);
  147. if (nLast >= 0)
  148. return path.Substring (nLast + 1);
  149. return path;
  150. }
  151. public static string GetFileNameWithoutExtension (string path)
  152. {
  153. return ChangeExtension (GetFileName (path), null);
  154. }
  155. public static string GetFullPath (string path)
  156. {
  157. if (path == null)
  158. throw (new ArgumentNullException (
  159. "path",
  160. "You must specify a path when calling System.IO.Path.GetFullPath"));
  161. if (path.Trim () == String.Empty)
  162. throw new ArgumentException ("The path is not of a legal form", "path");
  163. if (path.Length >= 2 &&
  164. IsDsc (path [0]) &&
  165. IsDsc (path [1])) {
  166. if (path.Length == 2 || path.IndexOf (path [0], 2) < 0)
  167. throw new ArgumentException ("UNC pass should be of the form \\\\server\\share.");
  168. else
  169. if (path [0] == DirectorySeparatorChar)
  170. return path; // UNC
  171. else
  172. return path.Replace (AltDirectorySeparatorChar, DirectorySeparatorChar);
  173. }
  174. if (!IsPathRooted (path))
  175. path = Directory.GetCurrentDirectory () + DirectorySeparatorStr + path;
  176. else if (DirectorySeparatorChar == '\\' &&
  177. path.Length >= 2 &&
  178. IsDsc (path [0]) &&
  179. !IsDsc (path [1])) { // like `\abc\def'
  180. string current = Directory.GetCurrentDirectory ();
  181. if (current [1] == VolumeSeparatorChar)
  182. path = current.Substring (0, 2) + path;
  183. else
  184. path = current.Substring (0, current.IndexOf ('\\', current.IndexOf ("\\\\") + 1));
  185. }
  186. return CanonicalizePath (path);
  187. }
  188. static bool IsDsc (char c) {
  189. return c == DirectorySeparatorChar || c == AltDirectorySeparatorChar;
  190. }
  191. public static string GetPathRoot (string path)
  192. {
  193. if (path == null)
  194. return null;
  195. if (path == String.Empty)
  196. throw new ArgumentException ("This specified path is invalid.");
  197. if (!IsPathRooted (path))
  198. return String.Empty;
  199. if (DirectorySeparatorChar == '/') {
  200. // UNIX
  201. return IsDsc (path [0]) ? DirectorySeparatorStr : String.Empty;
  202. } else {
  203. // Windows
  204. int len = 2;
  205. if (path.Length == 1 && IsDsc (path [0]))
  206. return DirectorySeparatorStr;
  207. else if (path.Length < 2)
  208. return String.Empty;
  209. if (IsDsc (path [0]) && IsDsc (path[1])) {
  210. // UNC: \\server or \\server\share
  211. // Get server
  212. while (len < path.Length && !IsDsc (path [len])) len++;
  213. // Get share
  214. while (len < path.Length && !IsDsc (path [len])) len++;
  215. return DirectorySeparatorStr +
  216. DirectorySeparatorStr +
  217. path.Substring (2).Replace (AltDirectorySeparatorChar, DirectorySeparatorChar);
  218. } else if (IsDsc (path [0])) {
  219. // path starts with '\' or '/'
  220. return DirectorySeparatorStr;
  221. } else if (path[1] == VolumeSeparatorChar) {
  222. // C:\folder
  223. if (path.Length >= 3 && (IsDsc (path [2]))) len++;
  224. } else
  225. return Directory.GetCurrentDirectory ().Substring (0, 2);// + path.Substring (0, len);
  226. return path.Substring (0, len);
  227. }
  228. }
  229. public static string GetTempFileName ()
  230. {
  231. FileStream f = null;
  232. string path;
  233. Random rnd;
  234. int num = 0;
  235. rnd = new Random ();
  236. do {
  237. num = rnd.Next ();
  238. num++;
  239. path = Path.Combine (GetTempPath(), "tmp" + num.ToString("x"));
  240. try {
  241. f = new FileStream (path, FileMode.CreateNew);
  242. } catch {
  243. }
  244. } while (f == null);
  245. f.Close();
  246. return path;
  247. }
  248. /// <summary>
  249. /// Returns the path of the current systems temp directory
  250. /// </summary>
  251. public static string GetTempPath ()
  252. {
  253. string p = get_temp_path ();
  254. if (p.Length > 0 && p [p.Length - 1] != DirectorySeparatorChar)
  255. return p + DirectorySeparatorChar;
  256. return p;
  257. }
  258. [MethodImplAttribute(MethodImplOptions.InternalCall)]
  259. private static extern string get_temp_path ();
  260. public static bool HasExtension (string path)
  261. {
  262. if (path == null || path.Trim () == String.Empty)
  263. return false;
  264. int pos = findExtension (path);
  265. return 0 <= pos && pos < path.Length - 1;
  266. }
  267. public static bool IsPathRooted (string path)
  268. {
  269. if (path == null || path.Length == 0)
  270. return false;
  271. if (path.IndexOfAny (InvalidPathChars) != -1)
  272. throw new ArgumentException ("Illegal characters in path", "path");
  273. char c = path [0];
  274. return (c == DirectorySeparatorChar ||
  275. c == AltDirectorySeparatorChar ||
  276. (!dirEqualsVolume && path.Length > 1 && path [1] == VolumeSeparatorChar));
  277. }
  278. // private class methods
  279. private static int findExtension (string path)
  280. {
  281. // method should return the index of the path extension
  282. // start or -1 if no valid extension
  283. if (path != null){
  284. int iLastDot = path.LastIndexOf ('.');
  285. int iLastSep = path.LastIndexOfAny ( PathSeparatorChars );
  286. if (iLastDot > iLastSep)
  287. return iLastDot;
  288. }
  289. return -1;
  290. }
  291. static Path () {
  292. VolumeSeparatorChar = MonoIO.VolumeSeparatorChar;
  293. DirectorySeparatorChar = MonoIO.DirectorySeparatorChar;
  294. AltDirectorySeparatorChar = MonoIO.AltDirectorySeparatorChar;
  295. PathSeparator = MonoIO.PathSeparator;
  296. InvalidPathChars = MonoIO.InvalidPathChars;
  297. // internal fields
  298. DirectorySeparatorStr = DirectorySeparatorChar.ToString ();
  299. PathSeparatorChars = new char [] {
  300. DirectorySeparatorChar,
  301. AltDirectorySeparatorChar,
  302. VolumeSeparatorChar
  303. };
  304. dirEqualsVolume = (DirectorySeparatorChar == VolumeSeparatorChar);
  305. }
  306. static string CanonicalizePath (string path) {
  307. // STEP 1: Check for empty string
  308. if (path == null) return path;
  309. path = path.Trim ();
  310. if (path == String.Empty) return path;
  311. // STEP 2: Check to see if this is only a root
  312. string root = GetPathRoot (path);
  313. // it will return '\' for path '\', while it should return 'c:\' or so.
  314. // Note: commenting this out makes the ened for the (target == 1...) check in step 5
  315. //if (root == path) return path;
  316. // STEP 3: split the directories, this gets rid of consecutative "/"'s
  317. string [] dirs = path.Split (DirectorySeparatorChar, AltDirectorySeparatorChar);
  318. // STEP 4: Get rid of directories containing . and ..
  319. int target = 0;
  320. for (int i = 0; i < dirs.Length; i++) {
  321. if (dirs [i] == "." || (i != 0 && dirs [i] == String.Empty)) continue;
  322. else if (dirs [i] == "..") {
  323. if (target != 0) target--;
  324. }
  325. else
  326. dirs [target++] = dirs [i];
  327. }
  328. // STEP 5: Combine everything.
  329. if (target == 0 || (target == 1 && dirs [0] == ""))
  330. return root;
  331. else {
  332. string ret = String.Join (DirectorySeparatorStr, dirs, 0, target);
  333. switch (DirectorySeparatorChar) {
  334. case '\\': // Windows
  335. // In GetFullPath(), it is assured that here never comes UNC. So this must only applied to such path that starts with '\', without drive specification.
  336. if (path [0] != DirectorySeparatorChar && path.StartsWith (root)) {
  337. if (ret.Length <= 2 && !ret.EndsWith (DirectorySeparatorStr)) // '\' after "c:"
  338. ret += DirectorySeparatorChar;
  339. return ret;
  340. } else {
  341. string current = Directory.GetCurrentDirectory ();
  342. if (current.Length > 1 && current [1] == VolumeSeparatorChar) {
  343. // DOS local file path
  344. if (ret.Length == 0 || IsDsc (ret [0]))
  345. ret += '\\';
  346. return current.Substring (0, 2) + ret;
  347. }
  348. else if (IsDsc (current [current.Length - 1]) && IsDsc (ret [0]))
  349. return current + ret.Substring (1);
  350. else
  351. return current + ret;
  352. }
  353. default: // Unix/Mac
  354. return ret;
  355. }
  356. }
  357. }
  358. }
  359. }