Path.cs 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956
  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 alt = 0;
  121. int start = 0;
  122. // Host prefix?
  123. char s0 = s [0];
  124. if (l > 2 && s0 == '\\' && s [1] == '\\'){
  125. start = 2;
  126. }
  127. // We are only left with root
  128. if (l == 1 && (s0 == DirectorySeparatorChar || s0 == AltDirectorySeparatorChar))
  129. return s;
  130. // Cleanup
  131. for (int i = start; i < l; i++){
  132. char c = s [i];
  133. if (c != DirectorySeparatorChar && c != AltDirectorySeparatorChar)
  134. continue;
  135. if (DirectorySeparatorChar != AltDirectorySeparatorChar && c == AltDirectorySeparatorChar)
  136. alt++;
  137. if (i+1 == l)
  138. sub++;
  139. else {
  140. c = s [i + 1];
  141. if (c == DirectorySeparatorChar || c == AltDirectorySeparatorChar)
  142. sub++;
  143. }
  144. }
  145. if (sub == 0 && alt == 0)
  146. return s;
  147. char [] copy = new char [l-sub];
  148. if (start != 0){
  149. copy [0] = '\\';
  150. copy [1] = '\\';
  151. }
  152. for (int i = start, j = start; i < l && j < copy.Length; i++){
  153. char c = s [i];
  154. if (c != DirectorySeparatorChar && c != AltDirectorySeparatorChar){
  155. copy [j++] = c;
  156. continue;
  157. }
  158. // For non-trailing cases.
  159. if (j+1 != copy.Length){
  160. copy [j++] = DirectorySeparatorChar;
  161. for (;i < l-1; i++){
  162. c = s [i+1];
  163. if (c != DirectorySeparatorChar && c != AltDirectorySeparatorChar)
  164. break;
  165. }
  166. }
  167. }
  168. return new String (copy);
  169. }
  170. public static string GetDirectoryName (string path)
  171. {
  172. // LAMESPEC: For empty string MS docs say both
  173. // return null AND throw exception. Seems .NET throws.
  174. if (path == String.Empty)
  175. throw new ArgumentException("Invalid path");
  176. if (path == null || GetPathRoot (path) == path)
  177. return null;
  178. if (path.Trim ().Length == 0)
  179. throw new ArgumentException ("Argument string consists of whitespace characters only.");
  180. if (path.IndexOfAny (System.IO.Path.InvalidPathChars) > -1)
  181. throw new ArgumentException ("Path contains invalid characters");
  182. int nLast = path.LastIndexOfAny (PathSeparatorChars);
  183. if (nLast == 0)
  184. nLast++;
  185. if (nLast > 0) {
  186. string ret = path.Substring (0, nLast);
  187. int l = ret.Length;
  188. if (l >= 2 && DirectorySeparatorChar == '\\' && ret [l - 1] == VolumeSeparatorChar)
  189. return ret + DirectorySeparatorChar;
  190. else if (l == 1 && DirectorySeparatorChar == '\\' && path.Length >= 2 && path [nLast] == VolumeSeparatorChar)
  191. return ret + VolumeSeparatorChar;
  192. else {
  193. //
  194. // Important: do not use CanonicalizePath here, use
  195. // the custom CleanPath here, as this should not
  196. // return absolute paths
  197. //
  198. return CleanPath (ret);
  199. }
  200. }
  201. return String.Empty;
  202. }
  203. public static string GetExtension (string path)
  204. {
  205. if (path == null)
  206. return null;
  207. if (path.IndexOfAny (InvalidPathChars) != -1)
  208. throw new ArgumentException ("Illegal characters in path.");
  209. int iExt = findExtension (path);
  210. if (iExt > -1)
  211. {
  212. if (iExt < path.Length - 1)
  213. return path.Substring (iExt);
  214. }
  215. return string.Empty;
  216. }
  217. public static string GetFileName (string path)
  218. {
  219. if (path == null || path.Length == 0)
  220. return path;
  221. if (path.IndexOfAny (InvalidPathChars) != -1)
  222. throw new ArgumentException ("Illegal characters in path.");
  223. int nLast = path.LastIndexOfAny (PathSeparatorChars);
  224. if (nLast >= 0)
  225. return path.Substring (nLast + 1);
  226. return path;
  227. }
  228. public static string GetFileNameWithoutExtension (string path)
  229. {
  230. return ChangeExtension (GetFileName (path), null);
  231. }
  232. public static string GetFullPath (string path)
  233. {
  234. string fullpath = InsecureGetFullPath (path);
  235. SecurityManager.EnsureElevatedPermissions (); // this is a no-op outside moonlight
  236. #if MONO_FEATURE_CAS
  237. if (SecurityManager.SecurityEnabled) {
  238. new FileIOPermission (FileIOPermissionAccess.PathDiscovery, fullpath).Demand ();
  239. }
  240. #endif
  241. return fullpath;
  242. }
  243. internal static String GetFullPathInternal(String path)
  244. {
  245. return InsecureGetFullPath (path);
  246. }
  247. #if WIN_PLATFORM
  248. // http://msdn.microsoft.com/en-us/library/windows/desktop/aa364963%28v=vs.85%29.aspx
  249. [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
  250. private static extern int GetFullPathName(string path, int numBufferChars, StringBuilder buffer, ref IntPtr lpFilePartOrNull);
  251. internal static string GetFullPathName(string path)
  252. {
  253. const int MAX_PATH = 260;
  254. StringBuilder buffer = new StringBuilder(MAX_PATH);
  255. IntPtr ptr = IntPtr.Zero;
  256. int length = GetFullPathName(path, MAX_PATH, buffer, ref ptr);
  257. if (length == 0)
  258. {
  259. int error = Marshal.GetLastWin32Error();
  260. throw new IOException("Windows API call to GetFullPathName failed, Windows error code: " + error);
  261. }
  262. else if (length > MAX_PATH)
  263. {
  264. buffer = new StringBuilder(length);
  265. GetFullPathName(path, length, buffer, ref ptr);
  266. }
  267. return buffer.ToString();
  268. }
  269. internal static string WindowsDriveAdjustment (string path)
  270. {
  271. // three special cases to consider when a drive is specified
  272. if (path.Length < 2) {
  273. if (path.Length == 1 && (path[0] == '\\' || path[0] == '/'))
  274. return Path.GetPathRoot(Directory.GetCurrentDirectory());
  275. return path;
  276. }
  277. if ((path [1] != ':') || !Char.IsLetter (path [0]))
  278. return path;
  279. string current = Directory.InsecureGetCurrentDirectory ();
  280. // first, only the drive is specified
  281. if (path.Length == 2) {
  282. // then if the current directory is on the same drive
  283. if (current [0] == path [0])
  284. path = current; // we return it
  285. else
  286. path = GetFullPathName(path); // we have to use the GetFullPathName Windows API
  287. } else if ((path [2] != Path.DirectorySeparatorChar) && (path [2] != Path.AltDirectorySeparatorChar)) {
  288. // second, the drive + a directory is specified *without* a separator between them (e.g. C:dir).
  289. // If the current directory is on the specified drive...
  290. if (current [0] == path [0]) {
  291. // then specified directory is appended to the current drive directory
  292. path = Path.Combine (current, path.Substring (2, path.Length - 2));
  293. } else {
  294. // we have to use the GetFullPathName Windows API
  295. path = GetFullPathName(path);
  296. }
  297. }
  298. return path;
  299. }
  300. #endif
  301. // insecure - do not call directly
  302. internal static string InsecureGetFullPath (string path)
  303. {
  304. if (path == null)
  305. throw new ArgumentNullException ("path");
  306. if (path.Trim ().Length == 0) {
  307. string msg = Locale.GetText ("The specified path is not of a legal form (empty).");
  308. throw new ArgumentException (msg);
  309. }
  310. #if WIN_PLATFORM
  311. // adjust for drives, i.e. a special case for windows
  312. if (Environment.IsRunningOnWindows)
  313. path = WindowsDriveAdjustment (path);
  314. #endif
  315. // if the supplied path ends with a separator...
  316. char end = path [path.Length - 1];
  317. var canonicalize = true;
  318. if (path.Length >= 2 &&
  319. IsDirectorySeparator (path [0]) &&
  320. IsDirectorySeparator (path [1])) {
  321. if (path.Length == 2 || path.IndexOf (path [0], 2) < 0)
  322. throw new ArgumentException ("UNC paths should be of the form \\\\server\\share.");
  323. if (path [0] != DirectorySeparatorChar)
  324. path = path.Replace (AltDirectorySeparatorChar, DirectorySeparatorChar);
  325. } else {
  326. if (!IsPathRooted (path)) {
  327. // avoid calling expensive CanonicalizePath when possible
  328. if (!Environment.IsRunningOnWindows) {
  329. var start = 0;
  330. while ((start = path.IndexOf ('.', start)) != -1) {
  331. if (++start == path.Length || path [start] == DirectorySeparatorChar || path [start] == AltDirectorySeparatorChar)
  332. break;
  333. }
  334. canonicalize = start > 0;
  335. }
  336. var cwd = Directory.InsecureGetCurrentDirectory();
  337. if (cwd [cwd.Length - 1] == DirectorySeparatorChar)
  338. path = cwd + path;
  339. else
  340. path = cwd + DirectorySeparatorChar + path;
  341. } else if (DirectorySeparatorChar == '\\' &&
  342. path.Length >= 2 &&
  343. IsDirectorySeparator (path [0]) &&
  344. !IsDirectorySeparator (path [1])) { // like `\abc\def'
  345. string current = Directory.InsecureGetCurrentDirectory();
  346. if (current [1] == VolumeSeparatorChar)
  347. path = current.Substring (0, 2) + path;
  348. else
  349. path = current.Substring (0, current.IndexOf ('\\', current.IndexOfUnchecked ("\\\\", 0, current.Length) + 1));
  350. }
  351. }
  352. if (canonicalize)
  353. path = CanonicalizePath (path);
  354. // if the original ended with a [Alt]DirectorySeparatorChar then ensure the full path also ends with one
  355. if (IsDirectorySeparator (end) && (path [path.Length - 1] != DirectorySeparatorChar))
  356. path += DirectorySeparatorChar;
  357. return path;
  358. }
  359. internal static bool IsDirectorySeparator (char c) {
  360. return c == DirectorySeparatorChar || c == AltDirectorySeparatorChar;
  361. }
  362. public static string GetPathRoot (string path)
  363. {
  364. if (path == null)
  365. return null;
  366. if (path.Trim ().Length == 0)
  367. throw new ArgumentException ("The specified path is not of a legal form.");
  368. if (!IsPathRooted (path))
  369. return String.Empty;
  370. if (DirectorySeparatorChar == '/') {
  371. // UNIX
  372. return IsDirectorySeparator (path [0]) ? DirectorySeparatorStr : String.Empty;
  373. } else {
  374. // Windows
  375. int len = 2;
  376. if (path.Length == 1 && IsDirectorySeparator (path [0]))
  377. return DirectorySeparatorStr;
  378. else if (path.Length < 2)
  379. return String.Empty;
  380. if (IsDirectorySeparator (path [0]) && IsDirectorySeparator (path[1])) {
  381. // UNC: \\server or \\server\share
  382. // Get server
  383. while (len < path.Length && !IsDirectorySeparator (path [len])) len++;
  384. // Get share
  385. if (len < path.Length) {
  386. len++;
  387. while (len < path.Length && !IsDirectorySeparator (path [len])) len++;
  388. }
  389. return DirectorySeparatorStr +
  390. DirectorySeparatorStr +
  391. path.Substring (2, len - 2).Replace (AltDirectorySeparatorChar, DirectorySeparatorChar);
  392. } else if (IsDirectorySeparator (path [0])) {
  393. // path starts with '\' or '/'
  394. return DirectorySeparatorStr;
  395. } else if (path[1] == VolumeSeparatorChar) {
  396. // C:\folder
  397. if (path.Length >= 3 && (IsDirectorySeparator (path [2]))) len++;
  398. } else
  399. return Directory.GetCurrentDirectory ().Substring (0, 2);// + path.Substring (0, len);
  400. return path.Substring (0, len);
  401. }
  402. }
  403. // FIXME: Further limit the assertion when imperative Assert is implemented
  404. [FileIOPermission (SecurityAction.Assert, Unrestricted = true)]
  405. public static string GetTempFileName ()
  406. {
  407. FileStream f = null;
  408. string path;
  409. Random rnd;
  410. int num;
  411. int count = 0;
  412. SecurityManager.EnsureElevatedPermissions (); // this is a no-op outside moonlight
  413. rnd = new Random ();
  414. var tmp_path = GetTempPath ();
  415. do {
  416. num = rnd.Next ();
  417. num++;
  418. path = Path.Combine (tmp_path, "tmp" + num.ToString ("x", CultureInfo.InvariantCulture) + ".tmp");
  419. try {
  420. f = new FileStream (path, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.Read,
  421. 8192, false, (FileOptions) 1);
  422. } catch (IOException ex){
  423. if (ex._HResult != MonoIO.FileAlreadyExistsHResult || count ++ > 65536)
  424. throw;
  425. } catch (UnauthorizedAccessException ex) {
  426. if (count ++ > 65536)
  427. throw new IOException (ex.Message, ex);
  428. }
  429. } while (f == null);
  430. f.Close();
  431. return path;
  432. }
  433. [EnvironmentPermission (SecurityAction.Demand, Unrestricted = true)]
  434. public static string GetTempPath ()
  435. {
  436. SecurityManager.EnsureElevatedPermissions (); // this is a no-op outside moonlight
  437. string p = get_temp_path ();
  438. if (p.Length > 0 && p [p.Length - 1] != DirectorySeparatorChar)
  439. return p + DirectorySeparatorChar;
  440. return p;
  441. }
  442. [MethodImplAttribute(MethodImplOptions.InternalCall)]
  443. private static extern string get_temp_path ();
  444. public static bool HasExtension (string path)
  445. {
  446. if (path == null || path.Trim ().Length == 0)
  447. return false;
  448. if (path.IndexOfAny (InvalidPathChars) != -1)
  449. throw new ArgumentException ("Illegal characters in path.");
  450. int pos = findExtension (path);
  451. return 0 <= pos && pos < path.Length - 1;
  452. }
  453. public static bool IsPathRooted (string path)
  454. {
  455. if (path == null || path.Length == 0)
  456. return false;
  457. if (path.IndexOfAny (InvalidPathChars) != -1)
  458. throw new ArgumentException ("Illegal characters in path.");
  459. char c = path [0];
  460. return (c == DirectorySeparatorChar ||
  461. c == AltDirectorySeparatorChar ||
  462. (!dirEqualsVolume && path.Length > 1 && path [1] == VolumeSeparatorChar));
  463. }
  464. public static char[] GetInvalidFileNameChars ()
  465. {
  466. // return a new array as we do not want anyone to be able to change the values
  467. if (Environment.IsRunningOnWindows) {
  468. return new char [41] { '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07',
  469. '\x08', '\x09', '\x0A', '\x0B', '\x0C', '\x0D', '\x0E', '\x0F', '\x10', '\x11', '\x12',
  470. '\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1A', '\x1B', '\x1C', '\x1D',
  471. '\x1E', '\x1F', '\x22', '\x3C', '\x3E', '\x7C', ':', '*', '?', '\\', '/' };
  472. } else {
  473. return new char [2] { '\x00', '/' };
  474. }
  475. }
  476. public static char[] GetInvalidPathChars ()
  477. {
  478. // return a new array as we do not want anyone to be able to change the values
  479. if (Environment.IsRunningOnWindows) {
  480. return new char [36] { '\x22', '\x3C', '\x3E', '\x7C', '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07',
  481. '\x08', '\x09', '\x0A', '\x0B', '\x0C', '\x0D', '\x0E', '\x0F', '\x10', '\x11', '\x12',
  482. '\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1A', '\x1B', '\x1C', '\x1D',
  483. '\x1E', '\x1F' };
  484. } else {
  485. return new char [1] { '\x00' };
  486. }
  487. }
  488. public static string GetRandomFileName ()
  489. {
  490. // returns a 8.3 filename (total size 12)
  491. StringBuilder sb = new StringBuilder (12);
  492. // using strong crypto but without creating the file
  493. RandomNumberGenerator rng = RandomNumberGenerator.Create ();
  494. byte [] buffer = new byte [11];
  495. rng.GetBytes (buffer);
  496. for (int i = 0; i < buffer.Length; i++) {
  497. if (sb.Length == 8)
  498. sb.Append ('.');
  499. // restrict to length of range [a..z0..9]
  500. int b = (buffer [i] % 36);
  501. char c = (char) (b < 26 ? (b + 'a') : (b - 26 + '0'));
  502. sb.Append (c);
  503. }
  504. return sb.ToString ();
  505. }
  506. // private class methods
  507. private static int findExtension (string path)
  508. {
  509. // method should return the index of the path extension
  510. // start or -1 if no valid extension
  511. if (path != null){
  512. int iLastDot = path.LastIndexOf ('.');
  513. int iLastSep = path.LastIndexOfAny ( PathSeparatorChars );
  514. if (iLastDot > iLastSep)
  515. return iLastDot;
  516. }
  517. return -1;
  518. }
  519. static Path ()
  520. {
  521. VolumeSeparatorChar = MonoIO.VolumeSeparatorChar;
  522. DirectorySeparatorChar = MonoIO.DirectorySeparatorChar;
  523. AltDirectorySeparatorChar = MonoIO.AltDirectorySeparatorChar;
  524. PathSeparator = MonoIO.PathSeparator;
  525. // this copy will be modifiable ("by design")
  526. InvalidPathChars = GetInvalidPathChars ();
  527. // internal fields
  528. DirectorySeparatorStr = DirectorySeparatorChar.ToString ();
  529. PathSeparatorChars = new char [] {
  530. DirectorySeparatorChar,
  531. AltDirectorySeparatorChar,
  532. VolumeSeparatorChar
  533. };
  534. dirEqualsVolume = (DirectorySeparatorChar == VolumeSeparatorChar);
  535. }
  536. // returns the server and share part of a UNC. Assumes "path" is a UNC.
  537. static string GetServerAndShare (string path)
  538. {
  539. int len = 2;
  540. while (len < path.Length && !IsDirectorySeparator (path [len])) len++;
  541. if (len < path.Length) {
  542. len++;
  543. while (len < path.Length && !IsDirectorySeparator (path [len])) len++;
  544. }
  545. return path.Substring (2, len - 2).Replace (AltDirectorySeparatorChar, DirectorySeparatorChar);
  546. }
  547. // assumes Environment.IsRunningOnWindows == true
  548. static bool SameRoot (string root, string path)
  549. {
  550. // compare root - if enough details are available
  551. if ((root.Length < 2) || (path.Length < 2))
  552. return false;
  553. // UNC handling
  554. if (IsDirectorySeparator (root[0]) && IsDirectorySeparator (root[1])) {
  555. if (!(IsDirectorySeparator (path[0]) && IsDirectorySeparator (path[1])))
  556. return false;
  557. string rootShare = GetServerAndShare (root);
  558. string pathShare = GetServerAndShare (path);
  559. return String.Compare (rootShare, pathShare, true, CultureInfo.InvariantCulture) == 0;
  560. }
  561. // same volume/drive
  562. if (!root [0].Equals (path [0]))
  563. return false;
  564. // presence of the separator
  565. if (path[1] != Path.VolumeSeparatorChar)
  566. return false;
  567. if ((root.Length > 2) && (path.Length > 2)) {
  568. // but don't directory compare the directory separator
  569. return (IsDirectorySeparator (root[2]) && IsDirectorySeparator (path[2]));
  570. }
  571. return true;
  572. }
  573. static string CanonicalizePath (string path)
  574. {
  575. // STEP 1: Check for empty string
  576. if (path == null)
  577. return path;
  578. if (Environment.IsRunningOnWindows)
  579. path = path.Trim ();
  580. if (path.Length == 0)
  581. return path;
  582. // STEP 2: Check to see if this is only a root
  583. string root = Path.GetPathRoot (path);
  584. // it will return '\' for path '\', while it should return 'c:\' or so.
  585. // Note: commenting this out makes the need for the (target == 1...) check in step 5
  586. //if (root == path) return path;
  587. // STEP 3: split the directories, this gets rid of consecutative "/"'s
  588. string[] dirs = path.Split (Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
  589. // STEP 4: Get rid of directories containing . and ..
  590. int target = 0;
  591. bool isUnc = Environment.IsRunningOnWindows &&
  592. root.Length > 2 && IsDirectorySeparator (root[0]) && IsDirectorySeparator (root[1]);
  593. // Set an overwrite limit for UNC paths since '\' + server + share
  594. // must not be eliminated by the '..' elimination algorithm.
  595. int limit = isUnc ? 3 : 0;
  596. for (int i = 0; i < dirs.Length; i++) {
  597. // WIN32 path components must be trimmed
  598. if (Environment.IsRunningOnWindows)
  599. dirs[i] = dirs[i].TrimEnd ();
  600. if (dirs[i] == "." || (i != 0 && dirs[i].Length == 0))
  601. continue;
  602. else if (dirs[i] == "..") {
  603. // don't overwrite path segments below the limit
  604. if (target > limit)
  605. target--;
  606. } else
  607. dirs[target++] = dirs[i];
  608. }
  609. // STEP 5: Combine everything.
  610. if (target == 0 || (target == 1 && dirs[0] == ""))
  611. return root;
  612. else {
  613. string ret = String.Join (DirectorySeparatorStr, dirs, 0, target);
  614. if (Environment.IsRunningOnWindows) {
  615. #if WIN_PLATFORM
  616. // append leading '\' of the UNC path that was lost in STEP 3.
  617. if (isUnc)
  618. ret = Path.DirectorySeparatorStr + ret;
  619. if (!SameRoot (root, ret))
  620. ret = root + ret;
  621. if (isUnc) {
  622. return ret;
  623. } else if (!IsDirectorySeparator (path[0]) && SameRoot (root, path)) {
  624. if (ret.Length <= 2 && !ret.EndsWith (DirectorySeparatorStr)) // '\' after "c:"
  625. ret += Path.DirectorySeparatorChar;
  626. return ret;
  627. } else {
  628. string current = Directory.GetCurrentDirectory ();
  629. if (current.Length > 1 && current[1] == Path.VolumeSeparatorChar) {
  630. // DOS local file path
  631. if (ret.Length == 0 || IsDirectorySeparator (ret[0]))
  632. ret += '\\';
  633. return current.Substring (0, 2) + ret;
  634. } else if (IsDirectorySeparator (current[current.Length - 1]) && IsDirectorySeparator (ret[0]))
  635. return current + ret.Substring (1);
  636. else
  637. return current + ret;
  638. }
  639. #endif
  640. } else {
  641. if (root != "" && ret.Length > 0 && ret [0] != '/')
  642. ret = root + ret;
  643. }
  644. return ret;
  645. }
  646. }
  647. // required for FileIOPermission (and most proibably reusable elsewhere too)
  648. // both path MUST be "full paths"
  649. static internal bool IsPathSubsetOf (string subset, string path)
  650. {
  651. if (subset.Length > path.Length)
  652. return false;
  653. // check that everything up to the last separator match
  654. int slast = subset.LastIndexOfAny (PathSeparatorChars);
  655. if (String.Compare (subset, 0, path, 0, slast) != 0)
  656. return false;
  657. slast++;
  658. // then check if the last segment is identical
  659. int plast = path.IndexOfAny (PathSeparatorChars, slast);
  660. if (plast >= slast) {
  661. return String.Compare (subset, slast, path, slast, path.Length - plast) == 0;
  662. }
  663. if (subset.Length != path.Length)
  664. return false;
  665. return String.Compare (subset, slast, path, slast, subset.Length - slast) == 0;
  666. }
  667. public
  668. static string Combine (params string [] paths)
  669. {
  670. if (paths == null)
  671. throw new ArgumentNullException ("paths");
  672. bool need_sep;
  673. var ret = new StringBuilder ();
  674. int pathsLen = paths.Length;
  675. int slen;
  676. need_sep = false;
  677. foreach (var s in paths) {
  678. if (s == null)
  679. throw new ArgumentNullException ("One of the paths contains a null value", "paths");
  680. if (s.Length == 0)
  681. continue;
  682. if (s.IndexOfAny (InvalidPathChars) != -1)
  683. throw new ArgumentException ("Illegal characters in path.");
  684. if (need_sep) {
  685. need_sep = false;
  686. ret.Append (DirectorySeparatorStr);
  687. }
  688. pathsLen--;
  689. if (IsPathRooted (s))
  690. ret.Length = 0;
  691. ret.Append (s);
  692. slen = s.Length;
  693. if (slen > 0 && pathsLen > 0) {
  694. char p1end = s [slen - 1];
  695. if (p1end != DirectorySeparatorChar && p1end != AltDirectorySeparatorChar && p1end != VolumeSeparatorChar)
  696. need_sep = true;
  697. }
  698. }
  699. return ret.ToString ();
  700. }
  701. public
  702. static string Combine (string path1, string path2, string path3)
  703. {
  704. if (path1 == null)
  705. throw new ArgumentNullException ("path1");
  706. if (path2 == null)
  707. throw new ArgumentNullException ("path2");
  708. if (path3 == null)
  709. throw new ArgumentNullException ("path3");
  710. return Combine (new string [] { path1, path2, path3 });
  711. }
  712. public
  713. static string Combine (string path1, string path2, string path3, string path4)
  714. {
  715. if (path1 == null)
  716. throw new ArgumentNullException ("path1");
  717. if (path2 == null)
  718. throw new ArgumentNullException ("path2");
  719. if (path3 == null)
  720. throw new ArgumentNullException ("path3");
  721. if (path4 == null)
  722. throw new ArgumentNullException ("path4");
  723. return Combine (new string [] { path1, path2, path3, path4 });
  724. }
  725. internal static void Validate (string path)
  726. {
  727. Validate (path, "path");
  728. }
  729. internal static void Validate (string path, string parameterName)
  730. {
  731. if (path == null)
  732. throw new ArgumentNullException (parameterName);
  733. if (String.IsNullOrWhiteSpace (path))
  734. throw new ArgumentException (Locale.GetText ("Path is empty"));
  735. if (path.IndexOfAny (Path.InvalidPathChars) != -1)
  736. throw new ArgumentException (Locale.GetText ("Path contains invalid chars"));
  737. #if WIN_PLATFORM
  738. if (Environment.IsRunningOnWindows) {
  739. int idx = path.IndexOf (':');
  740. if (idx >= 0 && idx != 1)
  741. throw new ArgumentException (parameterName);
  742. }
  743. #endif
  744. }
  745. internal static string DirectorySeparatorCharAsString {
  746. get {
  747. return DirectorySeparatorStr;
  748. }
  749. }
  750. internal const int MAX_PATH = 260; // From WinDef.h
  751. #region Copied from referencesource
  752. // this was copied from corefx since it's not available in referencesource
  753. internal static readonly char[] trimEndCharsWindows = { (char)0x9, (char)0xA, (char)0xB, (char)0xC, (char)0xD, (char)0x20, (char)0x85, (char)0xA0 };
  754. internal static readonly char[] trimEndCharsUnix = { };
  755. internal static char[] TrimEndChars => Environment.IsRunningOnWindows ? trimEndCharsWindows : trimEndCharsUnix;
  756. // ".." can only be used if it is specified as a part of a valid File/Directory name. We disallow
  757. // the user being able to use it to move up directories. Here are some examples eg
  758. // Valid: a..b abc..d
  759. // Invalid: ..ab ab.. .. abc..d\abc..
  760. //
  761. internal static void CheckSearchPattern(String searchPattern)
  762. {
  763. int index;
  764. while ((index = searchPattern.IndexOf("..", StringComparison.Ordinal)) != -1) {
  765. if (index + 2 == searchPattern.Length) // Terminal ".." . Files names cannot end in ".."
  766. throw new ArgumentException(Environment.GetResourceString("Arg_InvalidSearchPattern"));
  767. if ((searchPattern[index+2] == DirectorySeparatorChar)
  768. || (searchPattern[index+2] == AltDirectorySeparatorChar))
  769. throw new ArgumentException(Environment.GetResourceString("Arg_InvalidSearchPattern"));
  770. searchPattern = searchPattern.Substring(index + 2);
  771. }
  772. }
  773. internal static void CheckInvalidPathChars(string path, bool checkAdditional = false)
  774. {
  775. if (path == null)
  776. throw new ArgumentNullException("path");
  777. if (PathInternal.HasIllegalCharacters(path, checkAdditional))
  778. throw new ArgumentException(Environment.GetResourceString("Argument_InvalidPathChars"));
  779. }
  780. internal static String InternalCombine(String path1, String path2) {
  781. if (path1==null || path2==null)
  782. throw new ArgumentNullException((path1==null) ? "path1" : "path2");
  783. CheckInvalidPathChars(path1);
  784. CheckInvalidPathChars(path2);
  785. if (path2.Length == 0)
  786. throw new ArgumentException(Environment.GetResourceString("Argument_PathEmpty"), "path2");
  787. if (IsPathRooted(path2))
  788. throw new ArgumentException(Environment.GetResourceString("Arg_Path2IsRooted"), "path2");
  789. int i = path1.Length;
  790. if (i == 0) return path2;
  791. char ch = path1[i - 1];
  792. if (ch != DirectorySeparatorChar && ch != AltDirectorySeparatorChar && ch != VolumeSeparatorChar)
  793. return path1 + DirectorySeparatorCharAsString + path2;
  794. return path1 + path2;
  795. }
  796. #endregion
  797. }
  798. }