2
0

Path.cs 43 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246
  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. using System.Diagnostics;
  48. namespace System.IO {
  49. [ComVisible (true)]
  50. public static class Path {
  51. [Obsolete ("see GetInvalidPathChars and GetInvalidFileNameChars methods.")]
  52. public static readonly char[] InvalidPathChars;
  53. public static readonly char AltDirectorySeparatorChar;
  54. public static readonly char DirectorySeparatorChar;
  55. public static readonly char PathSeparator;
  56. internal static readonly string DirectorySeparatorStr;
  57. public static readonly char VolumeSeparatorChar;
  58. internal static readonly char[] PathSeparatorChars;
  59. private static readonly bool dirEqualsVolume;
  60. // class methods
  61. public static string ChangeExtension (string path, string extension)
  62. {
  63. if (path == null)
  64. return null;
  65. if (path.IndexOfAny (InvalidPathChars) != -1)
  66. throw new ArgumentException ("Illegal characters in path.");
  67. int iExt = findExtension (path);
  68. if (extension == null)
  69. return iExt < 0 ? path : path.Substring (0, iExt);
  70. else if (extension.Length == 0)
  71. return iExt < 0 ? path + '.' : path.Substring (0, iExt + 1);
  72. else if (path.Length != 0) {
  73. if (extension.Length > 0 && extension [0] != '.')
  74. extension = "." + extension;
  75. } else
  76. extension = String.Empty;
  77. if (iExt < 0) {
  78. return path + extension;
  79. } else if (iExt > 0) {
  80. string temp = path.Substring (0, iExt);
  81. return temp + extension;
  82. }
  83. return extension;
  84. }
  85. public static string Combine (string path1, string path2)
  86. {
  87. if (path1 == null)
  88. throw new ArgumentNullException ("path1");
  89. if (path2 == null)
  90. throw new ArgumentNullException ("path2");
  91. if (path1.Length == 0)
  92. return path2;
  93. if (path2.Length == 0)
  94. return path1;
  95. if (path1.IndexOfAny (InvalidPathChars) != -1)
  96. throw new ArgumentException ("Illegal characters in path.");
  97. if (path2.IndexOfAny (InvalidPathChars) != -1)
  98. throw new ArgumentException ("Illegal characters in path.");
  99. //TODO???: UNC names
  100. if (IsPathRooted (path2))
  101. return path2;
  102. char p1end = path1 [path1.Length - 1];
  103. if (p1end != DirectorySeparatorChar && p1end != AltDirectorySeparatorChar && p1end != VolumeSeparatorChar)
  104. return path1 + DirectorySeparatorStr + path2;
  105. return path1 + path2;
  106. }
  107. //
  108. // This routine:
  109. // * Removes duplicat path separators from a string
  110. // * If the string starts with \\, preserves the first two (hostname on Windows)
  111. // * Removes the trailing path separator.
  112. // * Returns the DirectorySeparatorChar for the single input DirectorySeparatorChar or AltDirectorySeparatorChar
  113. //
  114. // Unlike CanonicalizePath, this does not do any path resolution
  115. // (which GetDirectoryName is not supposed to do).
  116. //
  117. internal static string CleanPath (string s)
  118. {
  119. int l = s.Length;
  120. int sub = 0;
  121. int alt = 0;
  122. int start = 0;
  123. // Host prefix?
  124. char s0 = s [0];
  125. if (l > 2 && s0 == '\\' && s [1] == '\\'){
  126. start = 2;
  127. }
  128. // We are only left with root
  129. if (l == 1 && (s0 == DirectorySeparatorChar || s0 == AltDirectorySeparatorChar))
  130. return s;
  131. // Cleanup
  132. for (int i = start; i < l; i++){
  133. char c = s [i];
  134. if (c != DirectorySeparatorChar && c != AltDirectorySeparatorChar)
  135. continue;
  136. if (DirectorySeparatorChar != AltDirectorySeparatorChar && c == AltDirectorySeparatorChar)
  137. alt++;
  138. if (i+1 == l)
  139. sub++;
  140. else {
  141. c = s [i + 1];
  142. if (c == DirectorySeparatorChar || c == AltDirectorySeparatorChar)
  143. sub++;
  144. }
  145. }
  146. if (sub == 0 && alt == 0)
  147. return s;
  148. char [] copy = new char [l-sub];
  149. if (start != 0){
  150. copy [0] = '\\';
  151. copy [1] = '\\';
  152. }
  153. for (int i = start, j = start; i < l && j < copy.Length; i++){
  154. char c = s [i];
  155. if (c != DirectorySeparatorChar && c != AltDirectorySeparatorChar){
  156. copy [j++] = c;
  157. continue;
  158. }
  159. // For non-trailing cases.
  160. if (j+1 != copy.Length){
  161. copy [j++] = DirectorySeparatorChar;
  162. for (;i < l-1; i++){
  163. c = s [i+1];
  164. if (c != DirectorySeparatorChar && c != AltDirectorySeparatorChar)
  165. break;
  166. }
  167. }
  168. }
  169. return new String (copy);
  170. }
  171. public static string GetDirectoryName (string path)
  172. {
  173. // LAMESPEC: For empty string MS docs say both
  174. // return null AND throw exception. Seems .NET throws.
  175. if (path == String.Empty)
  176. throw new ArgumentException("Invalid path");
  177. if (path == null || GetPathRoot (path) == path)
  178. return null;
  179. if (path.Trim ().Length == 0)
  180. throw new ArgumentException ("Argument string consists of whitespace characters only.");
  181. if (path.IndexOfAny (System.IO.Path.InvalidPathChars) > -1)
  182. throw new ArgumentException ("Path contains invalid characters");
  183. int nLast = path.LastIndexOfAny (PathSeparatorChars);
  184. if (nLast == 0)
  185. nLast++;
  186. if (nLast > 0) {
  187. string ret = path.Substring (0, nLast);
  188. int l = ret.Length;
  189. if (l >= 2 && DirectorySeparatorChar == '\\' && ret [l - 1] == VolumeSeparatorChar)
  190. return ret + DirectorySeparatorChar;
  191. else if (l == 1 && DirectorySeparatorChar == '\\' && path.Length >= 2 && path [nLast] == VolumeSeparatorChar)
  192. return ret + VolumeSeparatorChar;
  193. else {
  194. //
  195. // Important: do not use CanonicalizePath here, use
  196. // the custom CleanPath here, as this should not
  197. // return absolute paths
  198. //
  199. return CleanPath (ret);
  200. }
  201. }
  202. return String.Empty;
  203. }
  204. public static ReadOnlySpan<char> GetDirectoryName (ReadOnlySpan<char> path)
  205. {
  206. return Path.GetDirectoryName (path.ToString ()).AsSpan ();
  207. }
  208. public static string GetExtension (string path)
  209. {
  210. if (path == null)
  211. return null;
  212. if (path.IndexOfAny (InvalidPathChars) != -1)
  213. throw new ArgumentException ("Illegal characters in path.");
  214. int iExt = findExtension (path);
  215. if (iExt > -1)
  216. {
  217. if (iExt < path.Length - 1)
  218. return path.Substring (iExt);
  219. }
  220. return string.Empty;
  221. }
  222. public static string GetFileName (string path)
  223. {
  224. if (path == null || path.Length == 0)
  225. return path;
  226. if (path.IndexOfAny (InvalidPathChars) != -1)
  227. throw new ArgumentException ("Illegal characters in path.");
  228. int nLast = path.LastIndexOfAny (PathSeparatorChars);
  229. if (nLast >= 0)
  230. return path.Substring (nLast + 1);
  231. return path;
  232. }
  233. public static string GetFileNameWithoutExtension (string path)
  234. {
  235. return ChangeExtension (GetFileName (path), null);
  236. }
  237. public static string GetFullPath (string path)
  238. {
  239. string fullpath = InsecureGetFullPath (path);
  240. SecurityManager.EnsureElevatedPermissions (); // this is a no-op outside moonlight
  241. #if MONO_FEATURE_CAS
  242. if (SecurityManager.SecurityEnabled) {
  243. new FileIOPermission (FileIOPermissionAccess.PathDiscovery, fullpath).Demand ();
  244. }
  245. #endif
  246. return fullpath;
  247. }
  248. internal static String GetFullPathInternal(String path)
  249. {
  250. return InsecureGetFullPath (path);
  251. }
  252. #if WIN_PLATFORM
  253. // http://msdn.microsoft.com/en-us/library/windows/desktop/aa364963%28v=vs.85%29.aspx
  254. [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
  255. private static extern int GetFullPathName(string path, int numBufferChars, StringBuilder buffer, ref IntPtr lpFilePartOrNull);
  256. internal static string GetFullPathName(string path)
  257. {
  258. const int MAX_PATH = 260;
  259. StringBuilder buffer = new StringBuilder(MAX_PATH);
  260. IntPtr ptr = IntPtr.Zero;
  261. int length = GetFullPathName(path, MAX_PATH, buffer, ref ptr);
  262. if (length == 0)
  263. {
  264. int error = Marshal.GetLastWin32Error();
  265. throw new IOException("Windows API call to GetFullPathName failed, Windows error code: " + error);
  266. }
  267. else if (length > MAX_PATH)
  268. {
  269. buffer = new StringBuilder(length);
  270. GetFullPathName(path, length, buffer, ref ptr);
  271. }
  272. return buffer.ToString();
  273. }
  274. internal static string WindowsDriveAdjustment (string path)
  275. {
  276. // three special cases to consider when a drive is specified
  277. if (path.Length < 2) {
  278. if (path.Length == 1 && (path[0] == '\\' || path[0] == '/'))
  279. return Path.GetPathRoot(Directory.GetCurrentDirectory());
  280. return path;
  281. }
  282. if ((path [1] != ':') || !Char.IsLetter (path [0]))
  283. return path;
  284. string current = Directory.InsecureGetCurrentDirectory ();
  285. // first, only the drive is specified
  286. if (path.Length == 2) {
  287. // then if the current directory is on the same drive
  288. if (current [0] == path [0])
  289. path = current; // we return it
  290. else
  291. path = GetFullPathName(path); // we have to use the GetFullPathName Windows API
  292. } else if ((path [2] != Path.DirectorySeparatorChar) && (path [2] != Path.AltDirectorySeparatorChar)) {
  293. // second, the drive + a directory is specified *without* a separator between them (e.g. C:dir).
  294. // If the current directory is on the specified drive...
  295. if (current [0] == path [0]) {
  296. // then specified directory is appended to the current drive directory
  297. path = Path.Combine (current, path.Substring (2, path.Length - 2));
  298. } else {
  299. // we have to use the GetFullPathName Windows API
  300. path = GetFullPathName(path);
  301. }
  302. }
  303. return path;
  304. }
  305. #endif
  306. // insecure - do not call directly
  307. internal static string InsecureGetFullPath (string path)
  308. {
  309. if (path == null)
  310. throw new ArgumentNullException ("path");
  311. if (path.Trim ().Length == 0) {
  312. string msg = Locale.GetText ("The specified path is not of a legal form (empty).");
  313. throw new ArgumentException (msg);
  314. }
  315. #if WIN_PLATFORM
  316. // adjust for drives, i.e. a special case for windows
  317. if (Environment.IsRunningOnWindows)
  318. path = WindowsDriveAdjustment (path);
  319. #endif
  320. // if the supplied path ends with a separator...
  321. char end = path [path.Length - 1];
  322. var canonicalize = true;
  323. if (path.Length >= 2 &&
  324. IsDirectorySeparator (path [0]) &&
  325. IsDirectorySeparator (path [1])) {
  326. if (path.Length == 2 || path.IndexOf (path [0], 2) < 0)
  327. throw new ArgumentException ("UNC paths should be of the form \\\\server\\share.");
  328. if (path [0] != DirectorySeparatorChar)
  329. path = path.Replace (AltDirectorySeparatorChar, DirectorySeparatorChar);
  330. } else {
  331. if (!IsPathRooted (path)) {
  332. // avoid calling expensive CanonicalizePath when possible
  333. if (!Environment.IsRunningOnWindows) {
  334. var start = 0;
  335. while ((start = path.IndexOf ('.', start)) != -1) {
  336. if (++start == path.Length || path [start] == DirectorySeparatorChar || path [start] == AltDirectorySeparatorChar)
  337. break;
  338. }
  339. canonicalize = start > 0;
  340. }
  341. var cwd = Directory.InsecureGetCurrentDirectory();
  342. if (cwd [cwd.Length - 1] == DirectorySeparatorChar)
  343. path = cwd + path;
  344. else
  345. path = cwd + DirectorySeparatorChar + path;
  346. } else if (DirectorySeparatorChar == '\\' &&
  347. path.Length >= 2 &&
  348. IsDirectorySeparator (path [0]) &&
  349. !IsDirectorySeparator (path [1])) { // like `\abc\def'
  350. string current = Directory.InsecureGetCurrentDirectory();
  351. if (current [1] == VolumeSeparatorChar)
  352. path = current.Substring (0, 2) + path;
  353. else
  354. path = current.Substring (0, current.IndexOf ('\\', current.IndexOfUnchecked ("\\\\", 0, current.Length) + 1));
  355. }
  356. }
  357. if (canonicalize)
  358. path = CanonicalizePath (path);
  359. // if the original ended with a [Alt]DirectorySeparatorChar then ensure the full path also ends with one
  360. if (IsDirectorySeparator (end) && (path [path.Length - 1] != DirectorySeparatorChar))
  361. path += DirectorySeparatorChar;
  362. return path;
  363. }
  364. internal static bool IsDirectorySeparator (char c) {
  365. return c == DirectorySeparatorChar || c == AltDirectorySeparatorChar;
  366. }
  367. public static string GetPathRoot (string path)
  368. {
  369. if (path == null)
  370. return null;
  371. if (path.Trim ().Length == 0)
  372. throw new ArgumentException ("The specified path is not of a legal form.");
  373. if (!IsPathRooted (path))
  374. return String.Empty;
  375. if (DirectorySeparatorChar == '/') {
  376. // UNIX
  377. return IsDirectorySeparator (path [0]) ? DirectorySeparatorStr : String.Empty;
  378. } else {
  379. // Windows
  380. int len = 2;
  381. if (path.Length == 1 && IsDirectorySeparator (path [0]))
  382. return DirectorySeparatorStr;
  383. else if (path.Length < 2)
  384. return String.Empty;
  385. if (IsDirectorySeparator (path [0]) && IsDirectorySeparator (path[1])) {
  386. // UNC: \\server or \\server\share
  387. // Get server
  388. while (len < path.Length && !IsDirectorySeparator (path [len])) len++;
  389. // Get share
  390. if (len < path.Length) {
  391. len++;
  392. while (len < path.Length && !IsDirectorySeparator (path [len])) len++;
  393. }
  394. return DirectorySeparatorStr +
  395. DirectorySeparatorStr +
  396. path.Substring (2, len - 2).Replace (AltDirectorySeparatorChar, DirectorySeparatorChar);
  397. } else if (IsDirectorySeparator (path [0])) {
  398. // path starts with '\' or '/'
  399. return DirectorySeparatorStr;
  400. } else if (path[1] == VolumeSeparatorChar) {
  401. // C:\folder
  402. if (path.Length >= 3 && (IsDirectorySeparator (path [2]))) len++;
  403. } else
  404. return Directory.GetCurrentDirectory ().Substring (0, 2);// + path.Substring (0, len);
  405. return path.Substring (0, len);
  406. }
  407. }
  408. // FIXME: Further limit the assertion when imperative Assert is implemented
  409. [FileIOPermission (SecurityAction.Assert, Unrestricted = true)]
  410. public static string GetTempFileName ()
  411. {
  412. FileStream f = null;
  413. string path;
  414. Random rnd;
  415. int num;
  416. int count = 0;
  417. SecurityManager.EnsureElevatedPermissions (); // this is a no-op outside moonlight
  418. rnd = new Random ();
  419. var tmp_path = GetTempPath ();
  420. do {
  421. num = rnd.Next ();
  422. num++;
  423. path = Path.Combine (tmp_path, "tmp" + num.ToString ("x", CultureInfo.InvariantCulture) + ".tmp");
  424. try {
  425. f = new FileStream (path, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.Read,
  426. 8192, false, (FileOptions) 1);
  427. } catch (IOException ex){
  428. if (ex._HResult != MonoIO.FileAlreadyExistsHResult || count ++ > 65536)
  429. throw;
  430. } catch (UnauthorizedAccessException ex) {
  431. if (count ++ > 65536)
  432. throw new IOException (ex.Message, ex);
  433. }
  434. } while (f == null);
  435. f.Close();
  436. return path;
  437. }
  438. [EnvironmentPermission (SecurityAction.Demand, Unrestricted = true)]
  439. public static string GetTempPath ()
  440. {
  441. SecurityManager.EnsureElevatedPermissions (); // this is a no-op outside moonlight
  442. string p = get_temp_path ();
  443. if (p.Length > 0 && p [p.Length - 1] != DirectorySeparatorChar)
  444. return p + DirectorySeparatorChar;
  445. return p;
  446. }
  447. [MethodImplAttribute(MethodImplOptions.InternalCall)]
  448. private static extern string get_temp_path ();
  449. public static bool HasExtension (string path)
  450. {
  451. if (path == null || path.Trim ().Length == 0)
  452. return false;
  453. if (path.IndexOfAny (InvalidPathChars) != -1)
  454. throw new ArgumentException ("Illegal characters in path.");
  455. int pos = findExtension (path);
  456. return 0 <= pos && pos < path.Length - 1;
  457. }
  458. public static bool IsPathRooted (ReadOnlySpan<char> path)
  459. {
  460. if (path.Length == 0)
  461. return false;
  462. char c = path [0];
  463. return (c == DirectorySeparatorChar ||
  464. c == AltDirectorySeparatorChar ||
  465. (!dirEqualsVolume && path.Length > 1 && path [1] == VolumeSeparatorChar));
  466. }
  467. public static bool IsPathRooted (string path)
  468. {
  469. if (path == null || path.Length == 0)
  470. return false;
  471. if (path.IndexOfAny (InvalidPathChars) != -1)
  472. throw new ArgumentException ("Illegal characters in path.");
  473. return IsPathRooted (path.AsSpan());
  474. }
  475. public static char[] GetInvalidFileNameChars ()
  476. {
  477. // return a new array as we do not want anyone to be able to change the values
  478. if (Environment.IsRunningOnWindows) {
  479. return new char [41] { '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07',
  480. '\x08', '\x09', '\x0A', '\x0B', '\x0C', '\x0D', '\x0E', '\x0F', '\x10', '\x11', '\x12',
  481. '\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1A', '\x1B', '\x1C', '\x1D',
  482. '\x1E', '\x1F', '\x22', '\x3C', '\x3E', '\x7C', ':', '*', '?', '\\', '/' };
  483. } else {
  484. return new char [2] { '\x00', '/' };
  485. }
  486. }
  487. public static char[] GetInvalidPathChars ()
  488. {
  489. // return a new array as we do not want anyone to be able to change the values
  490. if (Environment.IsRunningOnWindows) {
  491. return new char [36] { '\x22', '\x3C', '\x3E', '\x7C', '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07',
  492. '\x08', '\x09', '\x0A', '\x0B', '\x0C', '\x0D', '\x0E', '\x0F', '\x10', '\x11', '\x12',
  493. '\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1A', '\x1B', '\x1C', '\x1D',
  494. '\x1E', '\x1F' };
  495. } else {
  496. return new char [1] { '\x00' };
  497. }
  498. }
  499. public static string GetRandomFileName ()
  500. {
  501. // returns a 8.3 filename (total size 12)
  502. StringBuilder sb = new StringBuilder (12);
  503. // using strong crypto but without creating the file
  504. RandomNumberGenerator rng = RandomNumberGenerator.Create ();
  505. byte [] buffer = new byte [11];
  506. rng.GetBytes (buffer);
  507. for (int i = 0; i < buffer.Length; i++) {
  508. if (sb.Length == 8)
  509. sb.Append ('.');
  510. // restrict to length of range [a..z0..9]
  511. int b = (buffer [i] % 36);
  512. char c = (char) (b < 26 ? (b + 'a') : (b - 26 + '0'));
  513. sb.Append (c);
  514. }
  515. return sb.ToString ();
  516. }
  517. // private class methods
  518. private static int findExtension (string path)
  519. {
  520. // method should return the index of the path extension
  521. // start or -1 if no valid extension
  522. if (path != null){
  523. int iLastDot = path.LastIndexOf ('.');
  524. int iLastSep = path.LastIndexOfAny ( PathSeparatorChars );
  525. if (iLastDot > iLastSep)
  526. return iLastDot;
  527. }
  528. return -1;
  529. }
  530. static Path ()
  531. {
  532. VolumeSeparatorChar = MonoIO.VolumeSeparatorChar;
  533. DirectorySeparatorChar = MonoIO.DirectorySeparatorChar;
  534. AltDirectorySeparatorChar = MonoIO.AltDirectorySeparatorChar;
  535. PathSeparator = MonoIO.PathSeparator;
  536. // this copy will be modifiable ("by design")
  537. InvalidPathChars = GetInvalidPathChars ();
  538. // internal fields
  539. DirectorySeparatorStr = DirectorySeparatorChar.ToString ();
  540. PathSeparatorChars = new char [] {
  541. DirectorySeparatorChar,
  542. AltDirectorySeparatorChar,
  543. VolumeSeparatorChar
  544. };
  545. dirEqualsVolume = (DirectorySeparatorChar == VolumeSeparatorChar);
  546. }
  547. // returns the server and share part of a UNC. Assumes "path" is a UNC.
  548. static string GetServerAndShare (string path)
  549. {
  550. int len = 2;
  551. while (len < path.Length && !IsDirectorySeparator (path [len])) len++;
  552. if (len < path.Length) {
  553. len++;
  554. while (len < path.Length && !IsDirectorySeparator (path [len])) len++;
  555. }
  556. return path.Substring (2, len - 2).Replace (AltDirectorySeparatorChar, DirectorySeparatorChar);
  557. }
  558. // assumes Environment.IsRunningOnWindows == true
  559. static bool SameRoot (string root, string path)
  560. {
  561. // compare root - if enough details are available
  562. if ((root.Length < 2) || (path.Length < 2))
  563. return false;
  564. // UNC handling
  565. if (IsDirectorySeparator (root[0]) && IsDirectorySeparator (root[1])) {
  566. if (!(IsDirectorySeparator (path[0]) && IsDirectorySeparator (path[1])))
  567. return false;
  568. string rootShare = GetServerAndShare (root);
  569. string pathShare = GetServerAndShare (path);
  570. return String.Compare (rootShare, pathShare, true, CultureInfo.InvariantCulture) == 0;
  571. }
  572. // same volume/drive
  573. if (!root [0].Equals (path [0]))
  574. return false;
  575. // presence of the separator
  576. if (path[1] != Path.VolumeSeparatorChar)
  577. return false;
  578. if ((root.Length > 2) && (path.Length > 2)) {
  579. // but don't directory compare the directory separator
  580. return (IsDirectorySeparator (root[2]) && IsDirectorySeparator (path[2]));
  581. }
  582. return true;
  583. }
  584. static string CanonicalizePath (string path)
  585. {
  586. // STEP 1: Check for empty string
  587. if (path == null)
  588. return path;
  589. if (Environment.IsRunningOnWindows)
  590. path = path.Trim ();
  591. if (path.Length == 0)
  592. return path;
  593. // STEP 2: Check to see if this is only a root
  594. string root = Path.GetPathRoot (path);
  595. // it will return '\' for path '\', while it should return 'c:\' or so.
  596. // Note: commenting this out makes the need for the (target == 1...) check in step 5
  597. //if (root == path) return path;
  598. // STEP 3: split the directories, this gets rid of consecutative "/"'s
  599. string[] dirs = path.Split (Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
  600. // STEP 4: Get rid of directories containing . and ..
  601. int target = 0;
  602. bool isUnc = Environment.IsRunningOnWindows &&
  603. root.Length > 2 && IsDirectorySeparator (root[0]) && IsDirectorySeparator (root[1]);
  604. // Set an overwrite limit for UNC paths since '\' + server + share
  605. // must not be eliminated by the '..' elimination algorithm.
  606. int limit = isUnc ? 3 : 0;
  607. for (int i = 0; i < dirs.Length; i++) {
  608. // WIN32 path components must be trimmed
  609. if (Environment.IsRunningOnWindows)
  610. dirs[i] = dirs[i].TrimEnd ();
  611. if (dirs[i] == "." || (i != 0 && dirs[i].Length == 0))
  612. continue;
  613. else if (dirs[i] == "..") {
  614. // don't overwrite path segments below the limit
  615. if (target > limit)
  616. target--;
  617. } else
  618. dirs[target++] = dirs[i];
  619. }
  620. // STEP 5: Combine everything.
  621. if (target == 0 || (target == 1 && dirs[0] == ""))
  622. return root;
  623. else {
  624. string ret = String.Join (DirectorySeparatorStr, dirs, 0, target);
  625. if (Environment.IsRunningOnWindows) {
  626. #if WIN_PLATFORM
  627. // append leading '\' of the UNC path that was lost in STEP 3.
  628. if (isUnc)
  629. ret = Path.DirectorySeparatorStr + ret;
  630. if (!SameRoot (root, ret))
  631. ret = root + ret;
  632. if (isUnc) {
  633. return ret;
  634. } else if (!IsDirectorySeparator (path[0]) && SameRoot (root, path)) {
  635. if (ret.Length <= 2 && !ret.EndsWith (DirectorySeparatorStr)) // '\' after "c:"
  636. ret += Path.DirectorySeparatorChar;
  637. return ret;
  638. } else {
  639. string current = Directory.GetCurrentDirectory ();
  640. if (current.Length > 1 && current[1] == Path.VolumeSeparatorChar) {
  641. // DOS local file path
  642. if (ret.Length == 0 || IsDirectorySeparator (ret[0]))
  643. ret += '\\';
  644. return current.Substring (0, 2) + ret;
  645. } else if (IsDirectorySeparator (current[current.Length - 1]) && IsDirectorySeparator (ret[0]))
  646. return current + ret.Substring (1);
  647. else
  648. return current + ret;
  649. }
  650. #endif
  651. } else {
  652. if (root != "" && ret.Length > 0 && ret [0] != '/')
  653. ret = root + ret;
  654. }
  655. return ret;
  656. }
  657. }
  658. // required for FileIOPermission (and most proibably reusable elsewhere too)
  659. // both path MUST be "full paths"
  660. static internal bool IsPathSubsetOf (string subset, string path)
  661. {
  662. if (subset.Length > path.Length)
  663. return false;
  664. // check that everything up to the last separator match
  665. int slast = subset.LastIndexOfAny (PathSeparatorChars);
  666. if (String.Compare (subset, 0, path, 0, slast) != 0)
  667. return false;
  668. slast++;
  669. // then check if the last segment is identical
  670. int plast = path.IndexOfAny (PathSeparatorChars, slast);
  671. if (plast >= slast) {
  672. return String.Compare (subset, slast, path, slast, path.Length - plast) == 0;
  673. }
  674. if (subset.Length != path.Length)
  675. return false;
  676. return String.Compare (subset, slast, path, slast, subset.Length - slast) == 0;
  677. }
  678. public
  679. static string Combine (params string [] paths)
  680. {
  681. if (paths == null)
  682. throw new ArgumentNullException ("paths");
  683. bool need_sep;
  684. var ret = new StringBuilder ();
  685. int pathsLen = paths.Length;
  686. int slen;
  687. need_sep = false;
  688. foreach (var s in paths) {
  689. if (s == null)
  690. throw new ArgumentNullException ("One of the paths contains a null value", "paths");
  691. if (s.Length == 0)
  692. continue;
  693. if (s.IndexOfAny (InvalidPathChars) != -1)
  694. throw new ArgumentException ("Illegal characters in path.");
  695. if (need_sep) {
  696. need_sep = false;
  697. ret.Append (DirectorySeparatorStr);
  698. }
  699. pathsLen--;
  700. if (IsPathRooted (s))
  701. ret.Length = 0;
  702. ret.Append (s);
  703. slen = s.Length;
  704. if (slen > 0 && pathsLen > 0) {
  705. char p1end = s [slen - 1];
  706. if (p1end != DirectorySeparatorChar && p1end != AltDirectorySeparatorChar && p1end != VolumeSeparatorChar)
  707. need_sep = true;
  708. }
  709. }
  710. return ret.ToString ();
  711. }
  712. public
  713. static string Combine (string path1, string path2, string path3)
  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. return Combine (new string [] { path1, path2, path3 });
  722. }
  723. public
  724. static string Combine (string path1, string path2, string path3, string path4)
  725. {
  726. if (path1 == null)
  727. throw new ArgumentNullException ("path1");
  728. if (path2 == null)
  729. throw new ArgumentNullException ("path2");
  730. if (path3 == null)
  731. throw new ArgumentNullException ("path3");
  732. if (path4 == null)
  733. throw new ArgumentNullException ("path4");
  734. return Combine (new string [] { path1, path2, path3, path4 });
  735. }
  736. internal static void Validate (string path)
  737. {
  738. Validate (path, "path");
  739. }
  740. internal static void Validate (string path, string parameterName)
  741. {
  742. if (path == null)
  743. throw new ArgumentNullException (parameterName);
  744. if (String.IsNullOrWhiteSpace (path))
  745. throw new ArgumentException (Locale.GetText ("Path is empty"));
  746. if (path.IndexOfAny (Path.InvalidPathChars) != -1)
  747. throw new ArgumentException (Locale.GetText ("Path contains invalid chars"));
  748. #if WIN_PLATFORM
  749. if (Environment.IsRunningOnWindows) {
  750. int idx = path.IndexOf (':');
  751. if (idx >= 0 && idx != 1)
  752. throw new ArgumentException (parameterName);
  753. }
  754. #endif
  755. }
  756. internal static string DirectorySeparatorCharAsString {
  757. get {
  758. return DirectorySeparatorStr;
  759. }
  760. }
  761. internal const int MAX_PATH = 260; // From WinDef.h
  762. #region Copied from referencesource
  763. // this was copied from corefx since it's not available in referencesource
  764. internal static readonly char[] trimEndCharsWindows = { (char)0x9, (char)0xA, (char)0xB, (char)0xC, (char)0xD, (char)0x20, (char)0x85, (char)0xA0 };
  765. internal static readonly char[] trimEndCharsUnix = { };
  766. internal static char[] TrimEndChars => Environment.IsRunningOnWindows ? trimEndCharsWindows : trimEndCharsUnix;
  767. // ".." can only be used if it is specified as a part of a valid File/Directory name. We disallow
  768. // the user being able to use it to move up directories. Here are some examples eg
  769. // Valid: a..b abc..d
  770. // Invalid: ..ab ab.. .. abc..d\abc..
  771. //
  772. internal static void CheckSearchPattern(String searchPattern)
  773. {
  774. int index;
  775. while ((index = searchPattern.IndexOf("..", StringComparison.Ordinal)) != -1) {
  776. if (index + 2 == searchPattern.Length) // Terminal ".." . Files names cannot end in ".."
  777. throw new ArgumentException(Environment.GetResourceString("Arg_InvalidSearchPattern"));
  778. if ((searchPattern[index+2] == DirectorySeparatorChar)
  779. || (searchPattern[index+2] == AltDirectorySeparatorChar))
  780. throw new ArgumentException(Environment.GetResourceString("Arg_InvalidSearchPattern"));
  781. searchPattern = searchPattern.Substring(index + 2);
  782. }
  783. }
  784. internal static void CheckInvalidPathChars(string path, bool checkAdditional = false)
  785. {
  786. if (path == null)
  787. throw new ArgumentNullException("path");
  788. if (PathInternal.HasIllegalCharacters(path, checkAdditional))
  789. throw new ArgumentException(Environment.GetResourceString("Argument_InvalidPathChars"));
  790. }
  791. internal static String InternalCombine(String path1, String path2) {
  792. if (path1==null || path2==null)
  793. throw new ArgumentNullException((path1==null) ? "path1" : "path2");
  794. CheckInvalidPathChars(path1);
  795. CheckInvalidPathChars(path2);
  796. if (path2.Length == 0)
  797. throw new ArgumentException(Environment.GetResourceString("Argument_PathEmpty"), "path2");
  798. if (IsPathRooted(path2))
  799. throw new ArgumentException(Environment.GetResourceString("Arg_Path2IsRooted"), "path2");
  800. int i = path1.Length;
  801. if (i == 0) return path2;
  802. char ch = path1[i - 1];
  803. if (ch != DirectorySeparatorChar && ch != AltDirectorySeparatorChar && ch != VolumeSeparatorChar)
  804. return path1 + DirectorySeparatorCharAsString + path2;
  805. return path1 + path2;
  806. }
  807. #endregion
  808. #region Copied from corefx
  809. public static ReadOnlySpan<char> GetFileName(ReadOnlySpan<char> path)
  810. {
  811. int root = GetPathRoot(new string (path)).Length;
  812. // We don't want to cut off "C:\file.txt:stream" (i.e. should be "file.txt:stream")
  813. // but we *do* want "C:Foo" => "Foo". This necessitates checking for the root.
  814. for (int i = path.Length; --i >= 0;)
  815. {
  816. if (i < root || IsDirectorySeparator(path[i]))
  817. return path.Slice(i + 1, path.Length - i - 1);
  818. }
  819. return path;
  820. }
  821. public static string Join(ReadOnlySpan<char> path1, ReadOnlySpan<char> path2)
  822. {
  823. if (path1.Length == 0)
  824. return new string(path2);
  825. if (path2.Length == 0)
  826. return new string(path1);
  827. return JoinInternal(path1, path2);
  828. }
  829. public static string Join(ReadOnlySpan<char> path1, ReadOnlySpan<char> path2, ReadOnlySpan<char> path3)
  830. {
  831. if (path1.Length == 0)
  832. return Join(path2, path3);
  833. if (path2.Length == 0)
  834. return Join(path1, path3);
  835. if (path3.Length == 0)
  836. return Join(path1, path2);
  837. return JoinInternal(path1, path2, path3);
  838. }
  839. public static bool TryJoin(ReadOnlySpan<char> path1, ReadOnlySpan<char> path2, Span<char> destination, out int charsWritten)
  840. {
  841. charsWritten = 0;
  842. if (path1.Length == 0 && path2.Length == 0)
  843. return true;
  844. if (path1.Length == 0 || path2.Length == 0)
  845. {
  846. ref ReadOnlySpan<char> pathToUse = ref path1.Length == 0 ? ref path2 : ref path1;
  847. if (destination.Length < pathToUse.Length)
  848. {
  849. return false;
  850. }
  851. pathToUse.CopyTo(destination);
  852. charsWritten = pathToUse.Length;
  853. return true;
  854. }
  855. bool needsSeparator = !(PathInternal.EndsInDirectorySeparator(path1) || PathInternal.StartsWithDirectorySeparator(path2));
  856. int charsNeeded = path1.Length + path2.Length + (needsSeparator ? 1 : 0);
  857. if (destination.Length < charsNeeded)
  858. return false;
  859. path1.CopyTo(destination);
  860. if (needsSeparator)
  861. destination[path1.Length] = DirectorySeparatorChar;
  862. path2.CopyTo(destination.Slice(path1.Length + (needsSeparator ? 1 : 0)));
  863. charsWritten = charsNeeded;
  864. return true;
  865. }
  866. public static bool TryJoin(ReadOnlySpan<char> path1, ReadOnlySpan<char> path2, ReadOnlySpan<char> path3, Span<char> destination, out int charsWritten)
  867. {
  868. charsWritten = 0;
  869. if (path1.Length == 0 && path2.Length == 0 && path3.Length == 0)
  870. return true;
  871. if (path1.Length == 0)
  872. return TryJoin(path2, path3, destination, out charsWritten);
  873. if (path2.Length == 0)
  874. return TryJoin(path1, path3, destination, out charsWritten);
  875. if (path3.Length == 0)
  876. return TryJoin(path1, path2, destination, out charsWritten);
  877. int neededSeparators = PathInternal.EndsInDirectorySeparator(path1) || PathInternal.StartsWithDirectorySeparator(path2) ? 0 : 1;
  878. bool needsSecondSeparator = !(PathInternal.EndsInDirectorySeparator(path2) || PathInternal.StartsWithDirectorySeparator(path3));
  879. if (needsSecondSeparator)
  880. neededSeparators++;
  881. int charsNeeded = path1.Length + path2.Length + path3.Length + neededSeparators;
  882. if (destination.Length < charsNeeded)
  883. return false;
  884. bool result = TryJoin(path1, path2, destination, out charsWritten);
  885. Debug.Assert(result, "should never fail joining first two paths");
  886. if (needsSecondSeparator)
  887. destination[charsWritten++] = DirectorySeparatorChar;
  888. path3.CopyTo(destination.Slice(charsWritten));
  889. charsWritten += path3.Length;
  890. return true;
  891. }
  892. private static unsafe string JoinInternal(ReadOnlySpan<char> first, ReadOnlySpan<char> second)
  893. {
  894. Debug.Assert(first.Length > 0 && second.Length > 0, "should have dealt with empty paths");
  895. bool hasSeparator = PathInternal.IsDirectorySeparator(first[first.Length - 1])
  896. || PathInternal.IsDirectorySeparator(second[0]);
  897. fixed (char* f = &MemoryMarshal.GetReference(first), s = &MemoryMarshal.GetReference(second))
  898. {
  899. return string.Create(
  900. first.Length + second.Length + (hasSeparator ? 0 : 1),
  901. (First: (IntPtr)f, FirstLength: first.Length, Second: (IntPtr)s, SecondLength: second.Length, HasSeparator: hasSeparator),
  902. (destination, state) =>
  903. {
  904. new Span<char>((char*)state.First, state.FirstLength).CopyTo(destination);
  905. if (!state.HasSeparator)
  906. destination[state.FirstLength] = PathInternal.DirectorySeparatorChar;
  907. new Span<char>((char*)state.Second, state.SecondLength).CopyTo(destination.Slice(state.FirstLength + (state.HasSeparator ? 0 : 1)));
  908. });
  909. }
  910. }
  911. #if !__MonoCS__
  912. private static unsafe string JoinInternal(ReadOnlySpan<char> first, ReadOnlySpan<char> second, ReadOnlySpan<char> third)
  913. {
  914. Debug.Assert(first.Length > 0 && second.Length > 0 && third.Length > 0, "should have dealt with empty paths");
  915. bool firstHasSeparator = PathInternal.IsDirectorySeparator(first[first.Length - 1])
  916. || PathInternal.IsDirectorySeparator(second[0]);
  917. bool thirdHasSeparator = PathInternal.IsDirectorySeparator(second[second.Length - 1])
  918. || PathInternal.IsDirectorySeparator(third[0]);
  919. fixed (char* f = &MemoryMarshal.GetReference(first), s = &MemoryMarshal.GetReference(second), t = &MemoryMarshal.GetReference(third))
  920. {
  921. return string.Create(
  922. first.Length + second.Length + third.Length + (firstHasSeparator ? 0 : 1) + (thirdHasSeparator ? 0 : 1),
  923. (First: (IntPtr)f, FirstLength: first.Length, Second: (IntPtr)s, SecondLength: second.Length,
  924. Third: (IntPtr)t, ThirdLength: third.Length, FirstHasSeparator: firstHasSeparator, ThirdHasSeparator: thirdHasSeparator),
  925. (destination, state) =>
  926. {
  927. new Span<char>((char*)state.First, state.FirstLength).CopyTo(destination);
  928. if (!state.FirstHasSeparator)
  929. destination[state.FirstLength] = PathInternal.DirectorySeparatorChar;
  930. new Span<char>((char*)state.Second, state.SecondLength).CopyTo(destination.Slice(state.FirstLength + (state.FirstHasSeparator ? 0 : 1)));
  931. if (!state.ThirdHasSeparator)
  932. destination[destination.Length - state.ThirdLength - 1] = PathInternal.DirectorySeparatorChar;
  933. new Span<char>((char*)state.Third, state.ThirdLength).CopyTo(destination.Slice(destination.Length - state.ThirdLength));
  934. });
  935. }
  936. }
  937. private static unsafe string JoinInternal(ReadOnlySpan<char> first, ReadOnlySpan<char> second, ReadOnlySpan<char> third, ReadOnlySpan<char> fourth)
  938. {
  939. Debug.Assert(first.Length > 0 && second.Length > 0 && third.Length > 0 && fourth.Length > 0, "should have dealt with empty paths");
  940. bool firstHasSeparator = PathInternal.IsDirectorySeparator(first[first.Length - 1])
  941. || PathInternal.IsDirectorySeparator(second[0]);
  942. bool thirdHasSeparator = PathInternal.IsDirectorySeparator(second[second.Length - 1])
  943. || PathInternal.IsDirectorySeparator(third[0]);
  944. bool fourthHasSeparator = PathInternal.IsDirectorySeparator(third[third.Length - 1])
  945. || PathInternal.IsDirectorySeparator(fourth[0]);
  946. fixed (char* f = &MemoryMarshal.GetReference(first), s = &MemoryMarshal.GetReference(second), t = &MemoryMarshal.GetReference(third), u = &MemoryMarshal.GetReference(fourth))
  947. {
  948. return string.Create(
  949. first.Length + second.Length + third.Length + fourth.Length + (firstHasSeparator ? 0 : 1) + (thirdHasSeparator ? 0 : 1) + (fourthHasSeparator ? 0 : 1),
  950. (First: (IntPtr)f, FirstLength: first.Length, Second: (IntPtr)s, SecondLength: second.Length,
  951. Third: (IntPtr)t, ThirdLength: third.Length, Fourth: (IntPtr)u, FourthLength:fourth.Length,
  952. FirstHasSeparator: firstHasSeparator, ThirdHasSeparator: thirdHasSeparator, FourthHasSeparator: fourthHasSeparator),
  953. (destination, state) =>
  954. {
  955. new Span<char>((char*)state.First, state.FirstLength).CopyTo(destination);
  956. if (!state.FirstHasSeparator)
  957. destination[state.FirstLength] = PathInternal.DirectorySeparatorChar;
  958. new Span<char>((char*)state.Second, state.SecondLength).CopyTo(destination.Slice(state.FirstLength + (state.FirstHasSeparator ? 0 : 1)));
  959. if (!state.ThirdHasSeparator)
  960. destination[state.FirstLength + state.SecondLength + (state.FirstHasSeparator ? 0 : 1)] = PathInternal.DirectorySeparatorChar;
  961. new Span<char>((char*)state.Third, state.ThirdLength).CopyTo(destination.Slice(state.FirstLength + state.SecondLength + (state.FirstHasSeparator ? 0 : 1) + (state.ThirdHasSeparator ? 0 : 1)));
  962. if (!state.FourthHasSeparator)
  963. destination[destination.Length - state.FourthLength - 1] = PathInternal.DirectorySeparatorChar;
  964. new Span<char>((char*)state.Fourth, state.FourthLength).CopyTo(destination.Slice(destination.Length - state.FourthLength));
  965. });
  966. }
  967. }
  968. #else // MCS cannot handle tuples with more than 7 members
  969. private struct JoinData {
  970. public IntPtr First;
  971. public int FirstLength;
  972. public bool FirstHasSeparator;
  973. public IntPtr Second;
  974. public int SecondLength;
  975. public IntPtr Third;
  976. public int ThirdLength;
  977. public bool ThirdHasSeparator;
  978. public IntPtr Fourth;
  979. public int FourthLength;
  980. public bool FourthHasSeparator;
  981. }
  982. private static unsafe string JoinInternal(ReadOnlySpan<char> first, ReadOnlySpan<char> second, ReadOnlySpan<char> third)
  983. {
  984. Debug.Assert(first.Length > 0 && second.Length > 0 && third.Length > 0, "should have dealt with empty paths");
  985. bool firstHasSeparator = PathInternal.IsDirectorySeparator(first[first.Length - 1])
  986. || PathInternal.IsDirectorySeparator(second[0]);
  987. bool thirdHasSeparator = PathInternal.IsDirectorySeparator(second[second.Length - 1])
  988. || PathInternal.IsDirectorySeparator(third[0]);
  989. fixed (char* f = &MemoryMarshal.GetReference(first), s = &MemoryMarshal.GetReference(second), t = &MemoryMarshal.GetReference(third))
  990. {
  991. return string.Create(
  992. first.Length + second.Length + third.Length + (firstHasSeparator ? 0 : 1) + (thirdHasSeparator ? 0 : 1),
  993. new JoinData { First = (IntPtr)f, FirstLength = first.Length, Second = (IntPtr)s, SecondLength = second.Length,
  994. Third = (IntPtr)t, ThirdLength = third.Length, FirstHasSeparator = firstHasSeparator, ThirdHasSeparator = thirdHasSeparator },
  995. (destination, state) =>
  996. {
  997. new Span<char>((char*)state.First, state.FirstLength).CopyTo(destination);
  998. if (!state.FirstHasSeparator)
  999. destination[state.FirstLength] = PathInternal.DirectorySeparatorChar;
  1000. new Span<char>((char*)state.Second, state.SecondLength).CopyTo(destination.Slice(state.FirstLength + (state.FirstHasSeparator ? 0 : 1)));
  1001. if (!state.ThirdHasSeparator)
  1002. destination[destination.Length - state.ThirdLength - 1] = PathInternal.DirectorySeparatorChar;
  1003. new Span<char>((char*)state.Third, state.ThirdLength).CopyTo(destination.Slice(destination.Length - state.ThirdLength));
  1004. });
  1005. }
  1006. }
  1007. private static unsafe string JoinInternal(ReadOnlySpan<char> first, ReadOnlySpan<char> second, ReadOnlySpan<char> third, ReadOnlySpan<char> fourth)
  1008. {
  1009. Debug.Assert(first.Length > 0 && second.Length > 0 && third.Length > 0 && fourth.Length > 0, "should have dealt with empty paths");
  1010. bool firstHasSeparator = PathInternal.IsDirectorySeparator(first[first.Length - 1])
  1011. || PathInternal.IsDirectorySeparator(second[0]);
  1012. bool thirdHasSeparator = PathInternal.IsDirectorySeparator(second[second.Length - 1])
  1013. || PathInternal.IsDirectorySeparator(third[0]);
  1014. bool fourthHasSeparator = PathInternal.IsDirectorySeparator(third[third.Length - 1])
  1015. || PathInternal.IsDirectorySeparator(fourth[0]);
  1016. fixed (char* f = &MemoryMarshal.GetReference(first), s = &MemoryMarshal.GetReference(second), t = &MemoryMarshal.GetReference(third), u = &MemoryMarshal.GetReference(fourth))
  1017. {
  1018. return string.Create(
  1019. first.Length + second.Length + third.Length + fourth.Length + (firstHasSeparator ? 0 : 1) + (thirdHasSeparator ? 0 : 1) + (fourthHasSeparator ? 0 : 1),
  1020. new JoinData { First = (IntPtr)f, FirstLength = first.Length, Second = (IntPtr)s, SecondLength = second.Length,
  1021. Third = (IntPtr)t, ThirdLength = third.Length, Fourth = (IntPtr)u, FourthLength = fourth.Length,
  1022. FirstHasSeparator = firstHasSeparator, ThirdHasSeparator = thirdHasSeparator, FourthHasSeparator = fourthHasSeparator},
  1023. (destination, state) =>
  1024. {
  1025. new Span<char>((char*)state.First, state.FirstLength).CopyTo(destination);
  1026. if (!state.FirstHasSeparator)
  1027. destination[state.FirstLength] = PathInternal.DirectorySeparatorChar;
  1028. new Span<char>((char*)state.Second, state.SecondLength).CopyTo(destination.Slice(state.FirstLength + (state.FirstHasSeparator ? 0 : 1)));
  1029. if (!state.ThirdHasSeparator)
  1030. destination[state.FirstLength + state.SecondLength + (state.FirstHasSeparator ? 0 : 1)] = PathInternal.DirectorySeparatorChar;
  1031. new Span<char>((char*)state.Third, state.ThirdLength).CopyTo(destination.Slice(state.FirstLength + state.SecondLength + (state.FirstHasSeparator ? 0 : 1) + (state.ThirdHasSeparator ? 0 : 1)));
  1032. if (!state.FourthHasSeparator)
  1033. destination[destination.Length - state.FourthLength - 1] = PathInternal.DirectorySeparatorChar;
  1034. new Span<char>((char*)state.Fourth, state.FourthLength).CopyTo(destination.Slice(destination.Length - state.FourthLength));
  1035. });
  1036. }
  1037. }
  1038. #endif
  1039. #endregion
  1040. }
  1041. }