Path.ns21.cs 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. using System.Globalization;
  2. using System.Runtime.CompilerServices;
  3. using System.Runtime.InteropServices;
  4. using System.Security;
  5. using System.Security.Cryptography;
  6. using System.Security.Permissions;
  7. using System.Text;
  8. using System.Diagnostics;
  9. namespace System.IO
  10. {
  11. partial class Path
  12. {
  13. public static ReadOnlySpan<char> GetExtension (ReadOnlySpan<char> path) => GetExtension (path.ToString ()).AsSpan ();
  14. public static ReadOnlySpan<char> GetFileNameWithoutExtension (ReadOnlySpan<char> path) => GetFileNameWithoutExtension (path.ToString ()).AsSpan ();
  15. public static ReadOnlySpan<char> GetPathRoot (ReadOnlySpan<char> path) => GetPathRoot (path.ToString ()).AsSpan ();
  16. public static bool HasExtension (ReadOnlySpan<char> path) => HasExtension (path.ToString ());
  17. public static string GetRelativePath(string relativeTo, string path)
  18. {
  19. return GetRelativePath(relativeTo, path, StringComparison);
  20. }
  21. private static string GetRelativePath(string relativeTo, string path, StringComparison comparisonType)
  22. {
  23. if (string.IsNullOrEmpty(relativeTo)) throw new ArgumentNullException(nameof(relativeTo));
  24. if (PathInternal.IsEffectivelyEmpty(path.AsSpan())) throw new ArgumentNullException(nameof(path));
  25. Debug.Assert(comparisonType == StringComparison.Ordinal || comparisonType == StringComparison.OrdinalIgnoreCase);
  26. relativeTo = GetFullPath(relativeTo);
  27. path = GetFullPath(path);
  28. // Need to check if the roots are different- if they are we need to return the "to" path.
  29. if (!PathInternal.AreRootsEqual(relativeTo, path, comparisonType))
  30. return path;
  31. int commonLength = PathInternal.GetCommonPathLength(relativeTo, path, ignoreCase: comparisonType == StringComparison.OrdinalIgnoreCase);
  32. // If there is nothing in common they can't share the same root, return the "to" path as is.
  33. if (commonLength == 0)
  34. return path;
  35. // Trailing separators aren't significant for comparison
  36. int relativeToLength = relativeTo.Length;
  37. if (PathInternal.EndsInDirectorySeparator(relativeTo.AsSpan()))
  38. relativeToLength--;
  39. bool pathEndsInSeparator = PathInternal.EndsInDirectorySeparator(path.AsSpan());
  40. int pathLength = path.Length;
  41. if (pathEndsInSeparator)
  42. pathLength--;
  43. // If we have effectively the same path, return "."
  44. if (relativeToLength == pathLength && commonLength >= relativeToLength) return ".";
  45. // We have the same root, we need to calculate the difference now using the
  46. // common Length and Segment count past the length.
  47. //
  48. // Some examples:
  49. //
  50. // C:\Foo C:\Bar L3, S1 -> ..\Bar
  51. // C:\Foo C:\Foo\Bar L6, S0 -> Bar
  52. // C:\Foo\Bar C:\Bar\Bar L3, S2 -> ..\..\Bar\Bar
  53. // C:\Foo\Foo C:\Foo\Bar L7, S1 -> ..\Bar
  54. StringBuilder sb = StringBuilderCache.Acquire(Math.Max(relativeTo.Length, path.Length));
  55. // Add parent segments for segments past the common on the "from" path
  56. if (commonLength < relativeToLength)
  57. {
  58. sb.Append("..");
  59. for (int i = commonLength + 1; i < relativeToLength; i++)
  60. {
  61. if (PathInternal.IsDirectorySeparator(relativeTo[i]))
  62. {
  63. sb.Append(DirectorySeparatorChar);
  64. sb.Append("..");
  65. }
  66. }
  67. }
  68. else if (PathInternal.IsDirectorySeparator(path[commonLength]))
  69. {
  70. // No parent segments and we need to eat the initial separator
  71. // (C:\Foo C:\Foo\Bar case)
  72. commonLength++;
  73. }
  74. // Now add the rest of the "to" path, adding back the trailing separator
  75. int differenceLength = pathLength - commonLength;
  76. if (pathEndsInSeparator)
  77. differenceLength++;
  78. if (differenceLength > 0)
  79. {
  80. if (sb.Length > 0)
  81. {
  82. sb.Append(DirectorySeparatorChar);
  83. }
  84. sb.Append(path, commonLength, differenceLength);
  85. }
  86. return StringBuilderCache.GetStringAndRelease(sb);
  87. }
  88. /// <summary>Returns a comparison that can be used to compare file and directory names for equality.</summary>
  89. internal static StringComparison StringComparison
  90. {
  91. get
  92. {
  93. return IsCaseSensitive ?
  94. StringComparison.Ordinal :
  95. StringComparison.OrdinalIgnoreCase;
  96. }
  97. }
  98. internal static bool IsCaseSensitive => !IsWindows;
  99. static bool IsWindows
  100. {
  101. get
  102. {
  103. PlatformID platform = Environment.OSVersion.Platform;
  104. if (platform == PlatformID.Win32S ||
  105. platform == PlatformID.Win32Windows ||
  106. platform == PlatformID.Win32NT ||
  107. platform == PlatformID.WinCE) {
  108. return true;
  109. }
  110. return false;
  111. }
  112. }
  113. public static bool IsPathFullyQualified(string path)
  114. {
  115. if (path == null)
  116. throw new ArgumentNullException(nameof(path));
  117. return IsPathFullyQualified(path.AsSpan());
  118. }
  119. public static bool IsPathFullyQualified(ReadOnlySpan<char> path)
  120. {
  121. return !PathInternal.IsPartiallyQualified(path);
  122. }
  123. public static string GetFullPath(string path, string basePath)
  124. {
  125. if (path == null)
  126. throw new ArgumentNullException(nameof(path));
  127. if (basePath == null)
  128. throw new ArgumentNullException(nameof(basePath));
  129. if (!IsPathFullyQualified(basePath))
  130. throw new ArgumentException(SR.Arg_BasePathNotFullyQualified, nameof(basePath));
  131. if (basePath.Contains('\0') || path.Contains('\0'))
  132. throw new ArgumentException(SR.Argument_InvalidPathChars);
  133. if (IsPathFullyQualified(path))
  134. return GetFullPath(path);
  135. return GetFullPath(CombineInternal(basePath, path));
  136. }
  137. private static string CombineInternal(string first, string second)
  138. {
  139. if (string.IsNullOrEmpty(first))
  140. return second;
  141. if (string.IsNullOrEmpty(second))
  142. return first;
  143. if (IsPathRooted(second.AsSpan()))
  144. return second;
  145. return JoinInternal(first.AsSpan(), second.AsSpan());
  146. }
  147. }
  148. }