Path.cs 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873
  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. // Copyright 2011 Xamarin Inc (http://www.xamarin.com).
  9. //
  10. // Author: Jim Richardson, [email protected]
  11. // Dan Lewis ([email protected])
  12. // Gonzalo Paniagua Javier ([email protected])
  13. // Ben Maurer ([email protected])
  14. // Sebastien Pouliot <[email protected]>
  15. // Created: Saturday, August 11, 2001
  16. //
  17. //------------------------------------------------------------------------------
  18. //
  19. // Copyright (C) 2004-2005 Novell, Inc (http://www.novell.com)
  20. //
  21. // Permission is hereby granted, free of charge, to any person obtaining
  22. // a copy of this software and associated documentation files (the
  23. // "Software"), to deal in the Software without restriction, including
  24. // without limitation the rights to use, copy, modify, merge, publish,
  25. // distribute, sublicense, and/or sell copies of the Software, and to
  26. // permit persons to whom the Software is furnished to do so, subject to
  27. // the following conditions:
  28. //
  29. // The above copyright notice and this permission notice shall be
  30. // included in all copies or substantial portions of the Software.
  31. //
  32. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  33. // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  34. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  35. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  36. // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  37. // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  38. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  39. //
  40. using System.Globalization;
  41. using System.Runtime.CompilerServices;
  42. using System.Runtime.InteropServices;
  43. using System.Security;
  44. using System.Security.Cryptography;
  45. using System.Security.Permissions;
  46. using System.Text;
  47. namespace System.IO {
  48. [ComVisible (true)]
  49. public static class Path {
  50. [Obsolete ("see GetInvalidPathChars and GetInvalidFileNameChars methods.")]
  51. public static readonly char[] InvalidPathChars;
  52. public static readonly char AltDirectorySeparatorChar;
  53. public static readonly char DirectorySeparatorChar;
  54. public static readonly char PathSeparator;
  55. internal static readonly string DirectorySeparatorStr;
  56. public static readonly char VolumeSeparatorChar;
  57. internal static readonly char[] PathSeparatorChars;
  58. private static readonly bool dirEqualsVolume;
  59. // class methods
  60. public static string ChangeExtension (string path, string extension)
  61. {
  62. if (path == null)
  63. return null;
  64. if (path.IndexOfAny (InvalidPathChars) != -1)
  65. throw new ArgumentException ("Illegal characters in path.");
  66. int iExt = findExtension (path);
  67. if (extension == null)
  68. return iExt < 0 ? path : path.Substring (0, iExt);
  69. else if (extension.Length == 0)
  70. return iExt < 0 ? path + '.' : path.Substring (0, iExt + 1);
  71. else if (path.Length != 0) {
  72. if (extension.Length > 0 && extension [0] != '.')
  73. extension = "." + extension;
  74. } else
  75. extension = String.Empty;
  76. if (iExt < 0) {
  77. return path + extension;
  78. } else if (iExt > 0) {
  79. string temp = path.Substring (0, iExt);
  80. return temp + extension;
  81. }
  82. return extension;
  83. }
  84. public static string Combine (string path1, string path2)
  85. {
  86. if (path1 == null)
  87. throw new ArgumentNullException ("path1");
  88. if (path2 == null)
  89. throw new ArgumentNullException ("path2");
  90. if (path1.Length == 0)
  91. return path2;
  92. if (path2.Length == 0)
  93. return path1;
  94. if (path1.IndexOfAny (InvalidPathChars) != -1)
  95. throw new ArgumentException ("Illegal characters in path.");
  96. if (path2.IndexOfAny (InvalidPathChars) != -1)
  97. throw new ArgumentException ("Illegal characters in path.");
  98. //TODO???: UNC names
  99. if (IsPathRooted (path2))
  100. return path2;
  101. char p1end = path1 [path1.Length - 1];
  102. if (p1end != DirectorySeparatorChar && p1end != AltDirectorySeparatorChar && p1end != VolumeSeparatorChar)
  103. return path1 + DirectorySeparatorStr + path2;
  104. return path1 + path2;
  105. }
  106. //
  107. // This routine:
  108. // * Removes duplicat path separators from a string
  109. // * If the string starts with \\, preserves the first two (hostname on Windows)
  110. // * Removes the trailing path separator.
  111. // * Returns the DirectorySeparatorChar for the single input DirectorySeparatorChar or AltDirectorySeparatorChar
  112. //
  113. // Unlike CanonicalizePath, this does not do any path resolution
  114. // (which GetDirectoryName is not supposed to do).
  115. //
  116. internal static string CleanPath (string s)
  117. {
  118. int l = s.Length;
  119. int sub = 0;
  120. int start = 0;
  121. // Host prefix?
  122. char s0 = s [0];
  123. if (l > 2 && s0 == '\\' && s [1] == '\\'){
  124. start = 2;
  125. }
  126. // We are only left with root
  127. if (l == 1 && (s0 == DirectorySeparatorChar || s0 == AltDirectorySeparatorChar))
  128. return s;
  129. // Cleanup
  130. for (int i = start; i < l; i++){
  131. char c = s [i];
  132. if (c != DirectorySeparatorChar && c != AltDirectorySeparatorChar)
  133. continue;
  134. if (i+1 == l)
  135. sub++;
  136. else {
  137. c = s [i + 1];
  138. if (c == DirectorySeparatorChar || c == AltDirectorySeparatorChar)
  139. sub++;
  140. }
  141. }
  142. if (sub == 0)
  143. return s;
  144. char [] copy = new char [l-sub];
  145. if (start != 0){
  146. copy [0] = '\\';
  147. copy [1] = '\\';
  148. }
  149. for (int i = start, j = start; i < l && j < copy.Length; i++){
  150. char c = s [i];
  151. if (c != DirectorySeparatorChar && c != AltDirectorySeparatorChar){
  152. copy [j++] = c;
  153. continue;
  154. }
  155. // For non-trailing cases.
  156. if (j+1 != copy.Length){
  157. copy [j++] = DirectorySeparatorChar;
  158. for (;i < l-1; i++){
  159. c = s [i+1];
  160. if (c != DirectorySeparatorChar && c != AltDirectorySeparatorChar)
  161. break;
  162. }
  163. }
  164. }
  165. return new String (copy);
  166. }
  167. public static string GetDirectoryName (string path)
  168. {
  169. // LAMESPEC: For empty string MS docs say both
  170. // return null AND throw exception. Seems .NET throws.
  171. if (path == String.Empty)
  172. throw new ArgumentException("Invalid path");
  173. if (path == null || GetPathRoot (path) == path)
  174. return null;
  175. if (path.Trim ().Length == 0)
  176. throw new ArgumentException ("Argument string consists of whitespace characters only.");
  177. if (path.IndexOfAny (System.IO.Path.InvalidPathChars) > -1)
  178. throw new ArgumentException ("Path contains invalid characters");
  179. int nLast = path.LastIndexOfAny (PathSeparatorChars);
  180. if (nLast == 0)
  181. nLast++;
  182. if (nLast > 0) {
  183. string ret = path.Substring (0, nLast);
  184. int l = ret.Length;
  185. if (l >= 2 && DirectorySeparatorChar == '\\' && ret [l - 1] == VolumeSeparatorChar)
  186. return ret + DirectorySeparatorChar;
  187. else if (l == 1 && DirectorySeparatorChar == '\\' && path.Length >= 2 && path [nLast] == VolumeSeparatorChar)
  188. return ret + VolumeSeparatorChar;
  189. else {
  190. //
  191. // Important: do not use CanonicalizePath here, use
  192. // the custom CleanPath here, as this should not
  193. // return absolute paths
  194. //
  195. return CleanPath (ret);
  196. }
  197. }
  198. return String.Empty;
  199. }
  200. public static string GetExtension (string path)
  201. {
  202. if (path == null)
  203. return null;
  204. if (path.IndexOfAny (InvalidPathChars) != -1)
  205. throw new ArgumentException ("Illegal characters in path.");
  206. int iExt = findExtension (path);
  207. if (iExt > -1)
  208. {
  209. if (iExt < path.Length - 1)
  210. return path.Substring (iExt);
  211. }
  212. return string.Empty;
  213. }
  214. public static string GetFileName (string path)
  215. {
  216. if (path == null || path.Length == 0)
  217. return path;
  218. if (path.IndexOfAny (InvalidPathChars) != -1)
  219. throw new ArgumentException ("Illegal characters in path.");
  220. int nLast = path.LastIndexOfAny (PathSeparatorChars);
  221. if (nLast >= 0)
  222. return path.Substring (nLast + 1);
  223. return path;
  224. }
  225. public static string GetFileNameWithoutExtension (string path)
  226. {
  227. return ChangeExtension (GetFileName (path), null);
  228. }
  229. public static string GetFullPath (string path)
  230. {
  231. string fullpath = InsecureGetFullPath (path);
  232. SecurityManager.EnsureElevatedPermissions (); // this is a no-op outside moonlight
  233. #if !NET_2_1
  234. if (SecurityManager.SecurityEnabled) {
  235. new FileIOPermission (FileIOPermissionAccess.PathDiscovery, fullpath).Demand ();
  236. }
  237. #endif
  238. return fullpath;
  239. }
  240. // http://msdn.microsoft.com/en-us/library/windows/desktop/aa364963%28v=vs.85%29.aspx
  241. [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
  242. private static extern int GetFullPathName(string path, int numBufferChars, StringBuilder buffer, ref IntPtr lpFilePartOrNull);
  243. internal static string GetFullPathName(string path)
  244. {
  245. const int MAX_PATH = 260;
  246. StringBuilder buffer = new StringBuilder(MAX_PATH);
  247. IntPtr ptr = IntPtr.Zero;
  248. int length = GetFullPathName(path, MAX_PATH, buffer, ref ptr);
  249. if (length == 0)
  250. {
  251. int error = Marshal.GetLastWin32Error();
  252. throw new IOException("Windows API call to GetFullPathName failed, Windows error code: " + error);
  253. }
  254. else if (length > MAX_PATH)
  255. {
  256. buffer = new StringBuilder(length);
  257. GetFullPathName(path, length, buffer, ref ptr);
  258. }
  259. return buffer.ToString();
  260. }
  261. internal static string WindowsDriveAdjustment (string path)
  262. {
  263. // two special cases to consider when a drive is specified
  264. if (path.Length < 2)
  265. return path;
  266. if ((path [1] != ':') || !Char.IsLetter (path [0]))
  267. return path;
  268. string current = Directory.InsecureGetCurrentDirectory ();
  269. // first, only the drive is specified
  270. if (path.Length == 2) {
  271. // then if the current directory is on the same drive
  272. if (current [0] == path [0])
  273. path = current; // we return it
  274. else
  275. path = GetFullPathName(path); // we have to use the GetFullPathName Windows API
  276. } else if ((path [2] != Path.DirectorySeparatorChar) && (path [2] != Path.AltDirectorySeparatorChar)) {
  277. // second, the drive + a directory is specified *without* a separator between them (e.g. C:dir).
  278. // If the current directory is on the specified drive...
  279. if (current [0] == path [0]) {
  280. // then specified directory is appended to the current drive directory
  281. path = Path.Combine (current, path.Substring (2, path.Length - 2));
  282. } else {
  283. // we have to use the GetFullPathName Windows API
  284. path = GetFullPathName(path);
  285. }
  286. }
  287. return path;
  288. }
  289. // insecure - do not call directly
  290. internal static string InsecureGetFullPath (string path)
  291. {
  292. if (path == null)
  293. throw new ArgumentNullException ("path");
  294. if (path.Trim ().Length == 0) {
  295. string msg = Locale.GetText ("The specified path is not of a legal form (empty).");
  296. throw new ArgumentException (msg);
  297. }
  298. // adjust for drives, i.e. a special case for windows
  299. if (Environment.IsRunningOnWindows)
  300. path = WindowsDriveAdjustment (path);
  301. // if the supplied path ends with a separator...
  302. char end = path [path.Length - 1];
  303. var canonicalize = true;
  304. if (path.Length >= 2 &&
  305. IsDirectorySeparator (path [0]) &&
  306. IsDirectorySeparator (path [1])) {
  307. if (path.Length == 2 || path.IndexOf (path [0], 2) < 0)
  308. throw new ArgumentException ("UNC paths should be of the form \\\\server\\share.");
  309. if (path [0] != DirectorySeparatorChar)
  310. path = path.Replace (AltDirectorySeparatorChar, DirectorySeparatorChar);
  311. } else {
  312. if (!IsPathRooted (path)) {
  313. // avoid calling expensive CanonicalizePath when possible
  314. if (!Environment.IsRunningOnWindows) {
  315. var start = 0;
  316. while ((start = path.IndexOf ('.', start)) != -1) {
  317. if (++start == path.Length || path [start] == DirectorySeparatorChar || path [start] == AltDirectorySeparatorChar)
  318. break;
  319. }
  320. canonicalize = start > 0;
  321. }
  322. var cwd = Directory.InsecureGetCurrentDirectory();
  323. if (cwd [cwd.Length - 1] == DirectorySeparatorChar)
  324. path = cwd + path;
  325. else
  326. path = cwd + DirectorySeparatorChar + path;
  327. } else if (DirectorySeparatorChar == '\\' &&
  328. path.Length >= 2 &&
  329. IsDirectorySeparator (path [0]) &&
  330. !IsDirectorySeparator (path [1])) { // like `\abc\def'
  331. string current = Directory.InsecureGetCurrentDirectory();
  332. if (current [1] == VolumeSeparatorChar)
  333. path = current.Substring (0, 2) + path;
  334. else
  335. path = current.Substring (0, current.IndexOf ('\\', current.IndexOfOrdinalUnchecked ("\\\\") + 1));
  336. }
  337. }
  338. if (canonicalize)
  339. path = CanonicalizePath (path);
  340. // if the original ended with a [Alt]DirectorySeparatorChar then ensure the full path also ends with one
  341. if (IsDirectorySeparator (end) && (path [path.Length - 1] != DirectorySeparatorChar))
  342. path += DirectorySeparatorChar;
  343. return path;
  344. }
  345. internal static bool IsDirectorySeparator (char c) {
  346. return c == DirectorySeparatorChar || c == AltDirectorySeparatorChar;
  347. }
  348. public static string GetPathRoot (string path)
  349. {
  350. if (path == null)
  351. return null;
  352. if (path.Trim ().Length == 0)
  353. throw new ArgumentException ("The specified path is not of a legal form.");
  354. if (!IsPathRooted (path))
  355. return String.Empty;
  356. if (DirectorySeparatorChar == '/') {
  357. // UNIX
  358. return IsDirectorySeparator (path [0]) ? DirectorySeparatorStr : String.Empty;
  359. } else {
  360. // Windows
  361. int len = 2;
  362. if (path.Length == 1 && IsDirectorySeparator (path [0]))
  363. return DirectorySeparatorStr;
  364. else if (path.Length < 2)
  365. return String.Empty;
  366. if (IsDirectorySeparator (path [0]) && IsDirectorySeparator (path[1])) {
  367. // UNC: \\server or \\server\share
  368. // Get server
  369. while (len < path.Length && !IsDirectorySeparator (path [len])) len++;
  370. // Get share
  371. if (len < path.Length) {
  372. len++;
  373. while (len < path.Length && !IsDirectorySeparator (path [len])) len++;
  374. }
  375. return DirectorySeparatorStr +
  376. DirectorySeparatorStr +
  377. path.Substring (2, len - 2).Replace (AltDirectorySeparatorChar, DirectorySeparatorChar);
  378. } else if (IsDirectorySeparator (path [0])) {
  379. // path starts with '\' or '/'
  380. return DirectorySeparatorStr;
  381. } else if (path[1] == VolumeSeparatorChar) {
  382. // C:\folder
  383. if (path.Length >= 3 && (IsDirectorySeparator (path [2]))) len++;
  384. } else
  385. return Directory.GetCurrentDirectory ().Substring (0, 2);// + path.Substring (0, len);
  386. return path.Substring (0, len);
  387. }
  388. }
  389. // FIXME: Further limit the assertion when imperative Assert is implemented
  390. [FileIOPermission (SecurityAction.Assert, Unrestricted = true)]
  391. public static string GetTempFileName ()
  392. {
  393. FileStream f = null;
  394. string path;
  395. Random rnd;
  396. int num;
  397. int count = 0;
  398. SecurityManager.EnsureElevatedPermissions (); // this is a no-op outside moonlight
  399. rnd = new Random ();
  400. var tmp_path = GetTempPath ();
  401. do {
  402. num = rnd.Next ();
  403. num++;
  404. path = Path.Combine (tmp_path, "tmp" + num.ToString ("x", CultureInfo.InvariantCulture) + ".tmp");
  405. try {
  406. f = new FileStream (path, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.Read,
  407. 8192, false, (FileOptions) 1);
  408. } catch (IOException ex){
  409. if (ex.hresult != MonoIO.FileAlreadyExistsHResult || count ++ > 65536)
  410. throw;
  411. } catch (UnauthorizedAccessException ex) {
  412. if (count ++ > 65536)
  413. throw new IOException (ex.Message, ex);
  414. }
  415. } while (f == null);
  416. f.Close();
  417. return path;
  418. }
  419. [EnvironmentPermission (SecurityAction.Demand, Unrestricted = true)]
  420. public static string GetTempPath ()
  421. {
  422. SecurityManager.EnsureElevatedPermissions (); // this is a no-op outside moonlight
  423. string p = get_temp_path ();
  424. if (p.Length > 0 && p [p.Length - 1] != DirectorySeparatorChar)
  425. return p + DirectorySeparatorChar;
  426. return p;
  427. }
  428. [MethodImplAttribute(MethodImplOptions.InternalCall)]
  429. private static extern string get_temp_path ();
  430. public static bool HasExtension (string path)
  431. {
  432. if (path == null || path.Trim ().Length == 0)
  433. return false;
  434. if (path.IndexOfAny (InvalidPathChars) != -1)
  435. throw new ArgumentException ("Illegal characters in path.");
  436. int pos = findExtension (path);
  437. return 0 <= pos && pos < path.Length - 1;
  438. }
  439. public static bool IsPathRooted (string path)
  440. {
  441. if (path == null || path.Length == 0)
  442. return false;
  443. if (path.IndexOfAny (InvalidPathChars) != -1)
  444. throw new ArgumentException ("Illegal characters in path.");
  445. char c = path [0];
  446. return (c == DirectorySeparatorChar ||
  447. c == AltDirectorySeparatorChar ||
  448. (!dirEqualsVolume && path.Length > 1 && path [1] == VolumeSeparatorChar));
  449. }
  450. public static char[] GetInvalidFileNameChars ()
  451. {
  452. // return a new array as we do not want anyone to be able to change the values
  453. if (Environment.IsRunningOnWindows) {
  454. return new char [41] { '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07',
  455. '\x08', '\x09', '\x0A', '\x0B', '\x0C', '\x0D', '\x0E', '\x0F', '\x10', '\x11', '\x12',
  456. '\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1A', '\x1B', '\x1C', '\x1D',
  457. '\x1E', '\x1F', '\x22', '\x3C', '\x3E', '\x7C', ':', '*', '?', '\\', '/' };
  458. } else {
  459. return new char [2] { '\x00', '/' };
  460. }
  461. }
  462. public static char[] GetInvalidPathChars ()
  463. {
  464. // return a new array as we do not want anyone to be able to change the values
  465. if (Environment.IsRunningOnWindows) {
  466. return new char [36] { '\x22', '\x3C', '\x3E', '\x7C', '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07',
  467. '\x08', '\x09', '\x0A', '\x0B', '\x0C', '\x0D', '\x0E', '\x0F', '\x10', '\x11', '\x12',
  468. '\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1A', '\x1B', '\x1C', '\x1D',
  469. '\x1E', '\x1F' };
  470. } else {
  471. return new char [1] { '\x00' };
  472. }
  473. }
  474. public static string GetRandomFileName ()
  475. {
  476. // returns a 8.3 filename (total size 12)
  477. StringBuilder sb = new StringBuilder (12);
  478. // using strong crypto but without creating the file
  479. RandomNumberGenerator rng = RandomNumberGenerator.Create ();
  480. byte [] buffer = new byte [11];
  481. rng.GetBytes (buffer);
  482. for (int i = 0; i < buffer.Length; i++) {
  483. if (sb.Length == 8)
  484. sb.Append ('.');
  485. // restrict to length of range [a..z0..9]
  486. int b = (buffer [i] % 36);
  487. char c = (char) (b < 26 ? (b + 'a') : (b - 26 + '0'));
  488. sb.Append (c);
  489. }
  490. return sb.ToString ();
  491. }
  492. // private class methods
  493. private static int findExtension (string path)
  494. {
  495. // method should return the index of the path extension
  496. // start or -1 if no valid extension
  497. if (path != null){
  498. int iLastDot = path.LastIndexOf ('.');
  499. int iLastSep = path.LastIndexOfAny ( PathSeparatorChars );
  500. if (iLastDot > iLastSep)
  501. return iLastDot;
  502. }
  503. return -1;
  504. }
  505. static Path ()
  506. {
  507. VolumeSeparatorChar = MonoIO.VolumeSeparatorChar;
  508. DirectorySeparatorChar = MonoIO.DirectorySeparatorChar;
  509. AltDirectorySeparatorChar = MonoIO.AltDirectorySeparatorChar;
  510. PathSeparator = MonoIO.PathSeparator;
  511. // this copy will be modifiable ("by design")
  512. InvalidPathChars = GetInvalidPathChars ();
  513. // internal fields
  514. DirectorySeparatorStr = DirectorySeparatorChar.ToString ();
  515. PathSeparatorChars = new char [] {
  516. DirectorySeparatorChar,
  517. AltDirectorySeparatorChar,
  518. VolumeSeparatorChar
  519. };
  520. dirEqualsVolume = (DirectorySeparatorChar == VolumeSeparatorChar);
  521. }
  522. // returns the server and share part of a UNC. Assumes "path" is a UNC.
  523. static string GetServerAndShare (string path)
  524. {
  525. int len = 2;
  526. while (len < path.Length && !IsDirectorySeparator (path [len])) len++;
  527. if (len < path.Length) {
  528. len++;
  529. while (len < path.Length && !IsDirectorySeparator (path [len])) len++;
  530. }
  531. return path.Substring (2, len - 2).Replace (AltDirectorySeparatorChar, DirectorySeparatorChar);
  532. }
  533. // assumes Environment.IsRunningOnWindows == true
  534. static bool SameRoot (string root, string path)
  535. {
  536. // compare root - if enough details are available
  537. if ((root.Length < 2) || (path.Length < 2))
  538. return false;
  539. // UNC handling
  540. if (IsDirectorySeparator (root[0]) && IsDirectorySeparator (root[1])) {
  541. if (!(IsDirectorySeparator (path[0]) && IsDirectorySeparator (path[1])))
  542. return false;
  543. string rootShare = GetServerAndShare (root);
  544. string pathShare = GetServerAndShare (path);
  545. return String.Compare (rootShare, pathShare, true, CultureInfo.InvariantCulture) == 0;
  546. }
  547. // same volume/drive
  548. if (!root [0].Equals (path [0]))
  549. return false;
  550. // presence of the separator
  551. if (path[1] != Path.VolumeSeparatorChar)
  552. return false;
  553. if ((root.Length > 2) && (path.Length > 2)) {
  554. // but don't directory compare the directory separator
  555. return (IsDirectorySeparator (root[2]) && IsDirectorySeparator (path[2]));
  556. }
  557. return true;
  558. }
  559. static string CanonicalizePath (string path)
  560. {
  561. // STEP 1: Check for empty string
  562. if (path == null)
  563. return path;
  564. if (Environment.IsRunningOnWindows)
  565. path = path.Trim ();
  566. if (path.Length == 0)
  567. return path;
  568. // STEP 2: Check to see if this is only a root
  569. string root = Path.GetPathRoot (path);
  570. // it will return '\' for path '\', while it should return 'c:\' or so.
  571. // Note: commenting this out makes the need for the (target == 1...) check in step 5
  572. //if (root == path) return path;
  573. // STEP 3: split the directories, this gets rid of consecutative "/"'s
  574. string[] dirs = path.Split (Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
  575. // STEP 4: Get rid of directories containing . and ..
  576. int target = 0;
  577. bool isUnc = Environment.IsRunningOnWindows &&
  578. root.Length > 2 && IsDirectorySeparator (root[0]) && IsDirectorySeparator (root[1]);
  579. // Set an overwrite limit for UNC paths since '\' + server + share
  580. // must not be eliminated by the '..' elimination algorithm.
  581. int limit = isUnc ? 3 : 0;
  582. for (int i = 0; i < dirs.Length; i++) {
  583. // WIN32 path components must be trimmed
  584. if (Environment.IsRunningOnWindows)
  585. dirs[i] = dirs[i].TrimEnd ();
  586. if (dirs[i] == "." || (i != 0 && dirs[i].Length == 0))
  587. continue;
  588. else if (dirs[i] == "..") {
  589. // don't overwrite path segments below the limit
  590. if (target > limit)
  591. target--;
  592. } else
  593. dirs[target++] = dirs[i];
  594. }
  595. // STEP 5: Combine everything.
  596. if (target == 0 || (target == 1 && dirs[0] == ""))
  597. return root;
  598. else {
  599. string ret = String.Join (DirectorySeparatorStr, dirs, 0, target);
  600. if (Environment.IsRunningOnWindows) {
  601. // append leading '\' of the UNC path that was lost in STEP 3.
  602. if (isUnc)
  603. ret = Path.DirectorySeparatorStr + ret;
  604. if (!SameRoot (root, ret))
  605. ret = root + ret;
  606. if (isUnc) {
  607. return ret;
  608. } else if (!IsDirectorySeparator (path[0]) && SameRoot (root, path)) {
  609. if (ret.Length <= 2 && !ret.EndsWith (DirectorySeparatorStr)) // '\' after "c:"
  610. ret += Path.DirectorySeparatorChar;
  611. return ret;
  612. } else {
  613. string current = Directory.GetCurrentDirectory ();
  614. if (current.Length > 1 && current[1] == Path.VolumeSeparatorChar) {
  615. // DOS local file path
  616. if (ret.Length == 0 || IsDirectorySeparator (ret[0]))
  617. ret += '\\';
  618. return current.Substring (0, 2) + ret;
  619. } else if (IsDirectorySeparator (current[current.Length - 1]) && IsDirectorySeparator (ret[0]))
  620. return current + ret.Substring (1);
  621. else
  622. return current + ret;
  623. }
  624. } else {
  625. if (root != "" && ret.Length > 0 && ret [0] != '/')
  626. ret = root + ret;
  627. }
  628. return ret;
  629. }
  630. }
  631. // required for FileIOPermission (and most proibably reusable elsewhere too)
  632. // both path MUST be "full paths"
  633. static internal bool IsPathSubsetOf (string subset, string path)
  634. {
  635. if (subset.Length > path.Length)
  636. return false;
  637. // check that everything up to the last separator match
  638. int slast = subset.LastIndexOfAny (PathSeparatorChars);
  639. if (String.Compare (subset, 0, path, 0, slast) != 0)
  640. return false;
  641. slast++;
  642. // then check if the last segment is identical
  643. int plast = path.IndexOfAny (PathSeparatorChars, slast);
  644. if (plast >= slast) {
  645. return String.Compare (subset, slast, path, slast, path.Length - plast) == 0;
  646. }
  647. if (subset.Length != path.Length)
  648. return false;
  649. return String.Compare (subset, slast, path, slast, subset.Length - slast) == 0;
  650. }
  651. public
  652. static string Combine (params string [] paths)
  653. {
  654. if (paths == null)
  655. throw new ArgumentNullException ("paths");
  656. bool need_sep;
  657. var ret = new StringBuilder ();
  658. int pathsLen = paths.Length;
  659. int slen;
  660. need_sep = false;
  661. foreach (var s in paths) {
  662. if (s == null)
  663. throw new ArgumentNullException ("One of the paths contains a null value", "paths");
  664. if (s.Length == 0)
  665. continue;
  666. if (s.IndexOfAny (InvalidPathChars) != -1)
  667. throw new ArgumentException ("Illegal characters in path.");
  668. if (need_sep) {
  669. need_sep = false;
  670. ret.Append (DirectorySeparatorStr);
  671. }
  672. pathsLen--;
  673. if (IsPathRooted (s))
  674. ret.Length = 0;
  675. ret.Append (s);
  676. slen = s.Length;
  677. if (slen > 0 && pathsLen > 0) {
  678. char p1end = s [slen - 1];
  679. if (p1end != DirectorySeparatorChar && p1end != AltDirectorySeparatorChar && p1end != VolumeSeparatorChar)
  680. need_sep = true;
  681. }
  682. }
  683. return ret.ToString ();
  684. }
  685. public
  686. static string Combine (string path1, string path2, string path3)
  687. {
  688. if (path1 == null)
  689. throw new ArgumentNullException ("path1");
  690. if (path2 == null)
  691. throw new ArgumentNullException ("path2");
  692. if (path3 == null)
  693. throw new ArgumentNullException ("path3");
  694. return Combine (new string [] { path1, path2, path3 });
  695. }
  696. public
  697. static string Combine (string path1, string path2, string path3, string path4)
  698. {
  699. if (path1 == null)
  700. throw new ArgumentNullException ("path1");
  701. if (path2 == null)
  702. throw new ArgumentNullException ("path2");
  703. if (path3 == null)
  704. throw new ArgumentNullException ("path3");
  705. if (path4 == null)
  706. throw new ArgumentNullException ("path4");
  707. return Combine (new string [] { path1, path2, path3, path4 });
  708. }
  709. internal static void Validate (string path)
  710. {
  711. Validate (path, "path");
  712. }
  713. internal static void Validate (string path, string parameterName)
  714. {
  715. if (path == null)
  716. throw new ArgumentNullException (parameterName);
  717. if (String.IsNullOrWhiteSpace (path))
  718. throw new ArgumentException (Locale.GetText ("Path is empty"));
  719. if (path.IndexOfAny (Path.InvalidPathChars) != -1)
  720. throw new ArgumentException (Locale.GetText ("Path contains invalid chars"));
  721. if (Environment.IsRunningOnWindows) {
  722. int idx = path.IndexOf (':');
  723. if (idx >= 0 && idx != 1)
  724. throw new ArgumentException (parameterName);
  725. }
  726. }
  727. }
  728. }