// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics;
using System.Text;
namespace System.IO
{
/// Contains internal path helpers that are shared between many projects.
internal static partial class PathInternal
{
// Trim trailing white spaces, tabs etc but don't be aggressive in removing everything that has UnicodeCategory of trailing space.
// string.WhitespaceChars will trim more aggressively than what the underlying FS does (for ex, NTFS, FAT).
//
// (This is for compatibility with old behavior.)
internal static readonly char[] s_trimEndChars =
{
(char)0x9, // Horizontal tab
(char)0xA, // Line feed
(char)0xB, // Vertical tab
(char)0xC, // Form feed
(char)0xD, // Carriage return
(char)0x20, // Space
(char)0x85, // Next line
(char)0xA0 // Non breaking space
};
///
/// Checks for invalid path characters in the given path.
///
/// Thrown if the path is null.
/// Thrown if the path has invalid characters.
/// The path to check for invalid characters.
internal static void CheckInvalidPathChars(string path)
{
if (path == null)
throw new ArgumentNullException(nameof(path));
if (HasIllegalCharacters(path))
throw new ArgumentException(SR.Argument_InvalidPathChars, nameof(path));
}
///
/// Returns the start index of the filename
/// in the given path, or 0 if no directory
/// or volume separator is found.
///
/// The path in which to find the index of the filename.
///
/// This method returns path.Length for
/// inputs like "/usr/foo/" on Unix. As such,
/// it is not safe for being used to index
/// the string without additional verification.
///
internal static int FindFileNameIndex(string path)
{
Debug.Assert(path != null);
CheckInvalidPathChars(path);
for (int i = path.Length - 1; i >= 0; i--)
{
char ch = path[i];
if (IsDirectoryOrVolumeSeparator(ch))
return i + 1;
}
return 0; // the whole path is the filename
}
///
/// Returns true if the path ends in a directory separator.
///
internal static bool EndsInDirectorySeparator(string path) =>
!string.IsNullOrEmpty(path) && IsDirectorySeparator(path[path.Length - 1]);
///
/// Get the common path length from the start of the string.
///
internal static int GetCommonPathLength(string first, string second, bool ignoreCase)
{
int commonChars = EqualStartingCharacterCount(first, second, ignoreCase: ignoreCase);
// If nothing matches
if (commonChars == 0)
return commonChars;
// Or we're a full string and equal length or match to a separator
if (commonChars == first.Length
&& (commonChars == second.Length || IsDirectorySeparator(second[commonChars])))
return commonChars;
if (commonChars == second.Length && IsDirectorySeparator(first[commonChars]))
return commonChars;
// It's possible we matched somewhere in the middle of a segment e.g. C:\Foodie and C:\Foobar.
while (commonChars > 0 && !IsDirectorySeparator(first[commonChars - 1]))
commonChars--;
return commonChars;
}
///
/// Gets the count of common characters from the left optionally ignoring case
///
unsafe internal static int EqualStartingCharacterCount(string first, string second, bool ignoreCase)
{
if (string.IsNullOrEmpty(first) || string.IsNullOrEmpty(second)) return 0;
int commonChars = 0;
fixed (char* f = first)
fixed (char* s = second)
{
char* l = f;
char* r = s;
char* leftEnd = l + first.Length;
char* rightEnd = r + second.Length;
while (l != leftEnd && r != rightEnd
&& (*l == *r || (ignoreCase && char.ToUpperInvariant((*l)) == char.ToUpperInvariant((*r)))))
{
commonChars++;
l++;
r++;
}
}
return commonChars;
}
///
/// Returns true if the two paths have the same root
///
internal static bool AreRootsEqual(string first, string second, StringComparison comparisonType)
{
int firstRootLength = GetRootLength(first);
int secondRootLength = GetRootLength(second);
return firstRootLength == secondRootLength
&& string.Compare(
strA: first,
indexA: 0,
strB: second,
indexB: 0,
length: firstRootLength,
comparisonType: comparisonType) == 0;
}
///
/// Returns false for ".." unless it is specified as a part of a valid File/Directory name.
/// (Used to avoid moving up directories.)
///
/// Valid: a..b abc..d
/// Invalid: ..ab ab.. .. abc..d\abc..
///
internal static void CheckSearchPattern(string searchPattern)
{
int index;
while ((index = searchPattern.IndexOf("..", StringComparison.Ordinal)) != -1)
{
// Terminal ".." . Files names cannot end in ".."
if (index + 2 == searchPattern.Length
|| IsDirectorySeparator(searchPattern[index + 2]))
throw new ArgumentException(SR.Arg_InvalidSearchPattern);
searchPattern = searchPattern.Substring(index + 2);
}
}
}
}