Exemplo n.º 1
0
        public static string Join(params string[] paths)
        {
            if (paths == null)
            {
                throw new ArgumentNullException(nameof(paths));
            }

            if (paths.Length == 0)
            {
                return(string.Empty);
            }

            int maxSize = 0;

            foreach (string path in paths)
            {
                maxSize += path?.Length ?? 0;
            }
            maxSize += paths.Length - 1;

            Span <char> initialBuffer = stackalloc char[260];    // MaxShortPath on Windows
            var         builder       = new ValueStringBuilder(initialBuffer);

            builder.EnsureCapacity(maxSize);

            for (int i = 0; i < paths.Length; i++)
            {
                if ((paths[i]?.Length ?? 0) == 0)
                {
                    continue;
                }

                string path = paths[i];

                if (builder.Length == 0)
                {
                    builder.Append(path);
                }
                else
                {
                    if (!PathInternal.IsDirectorySeparator(builder[builder.Length - 1]) && !PathInternal.IsDirectorySeparator(path[0]))
                    {
                        builder.Append(PathInternal.DirectorySeparatorChar);
                    }

                    builder.Append(path);
                }
            }

            return(builder.ToString());
        }
Exemplo n.º 2
0
 internal static void CheckSearchPattern(string searchPattern)
 {
     // ".." should not be used to move up directories. On Windows, this is more strict, and ".."
     // can only be used in particular places in a name, whereas on Unix it can be used anywhere.
     // So, throw if we find a ".." that's its own component in the path.
     for (int index = 0; (index = searchPattern.IndexOf("..", index, StringComparison.Ordinal)) >= 0; index += 2)
     {
         if ((index == 0 || PathInternal.IsDirectorySeparator(searchPattern[index - 1])) &&                      // previous character is directory separator
             (index + 2 == searchPattern.Length || PathInternal.IsDirectorySeparator(searchPattern[index + 2]))) // next character is directory separator
         {
             throw new ArgumentException(SR.Arg_InvalidSearchPattern, "searchPattern");
         }
     }
 }
Exemplo n.º 3
0
        public static bool IsPathRooted(string path)
        {
            // Want to avoid PathInternal.CheckInvalidPathChars on Path.IsPathRooted

            if (path != null)
            {
                int length = path.Length;
                if ((length >= 1 && PathInternal.IsDirectorySeparator(path[0])) ||
                    (length >= 2 && PathInternal.IsValidDriveChar(path[0]) && path[1] == Path.VolumeSeparatorChar))
                {
                    return(true);
                }
            }
            return(false);
        }
Exemplo n.º 4
0
 public static bool HasExtension(ReadOnlySpan <char> path)
 {
     for (int i = path.Length - 1; i >= 0; i--)
     {
         char ch = path[i];
         if (ch == '.')
         {
             return(i != path.Length - 1);
         }
         if (PathInternal.IsDirectorySeparator(ch))
         {
             break;
         }
     }
     return(false);
 }
Exemplo n.º 5
0
        public static string?ChangeExtension(string?path, string?extension)
        {
            if (path == null)
            {
                return(null);
            }

            int subLength = path.Length;

            if (subLength == 0)
            {
                return(string.Empty);
            }

            for (int i = path.Length - 1; i >= 0; i--)
            {
                char ch = path[i];

                if (ch == '.')
                {
                    subLength = i;
                    break;
                }

                if (PathInternal.IsDirectorySeparator(ch))
                {
                    break;
                }
            }

            if (extension == null)
            {
                return(path.Substring(0, subLength));
            }

            ReadOnlySpan <char> subpath = path.AsSpan(0, subLength);

#if MS_IO_REDIST
            return(extension.Length != 0 && extension[0] == '.' ?
                   StringExtensions.Concat(subpath, extension.AsSpan()) :
                   StringExtensions.Concat(subpath, ".".AsSpan(), extension.AsSpan()));
#else
            return(extension.StartsWith('.') ?
                   string.Concat(subpath, extension) :
                   string.Concat(subpath, ".", extension));
#endif
        }
Exemplo n.º 6
0
        public static ReadOnlySpan <char> GetFileName(ReadOnlySpan <char> path)
        {
            int root = GetPathRoot(path).Length;

            // We don't want to cut off "C:\file.txt:stream" (i.e. should be "file.txt:stream")
            // but we *do* want "C:Foo" => "Foo". This necessitates checking for the root.

            for (int i = path.Length; --i >= 0;)
            {
                if (i < root || PathInternal.IsDirectorySeparator(path[i]))
                {
                    return(path.Slice(i + 1, path.Length - i - 1));
                }
            }

            return(path);
        }
Exemplo n.º 7
0
        // ".." can only be used if it is specified as a part of a valid File/Directory name. We disallow
        //  the user being able to use it to move up directories. Here are some examples eg
        //    Valid: a..b  abc..d
        //    Invalid: ..ab   ab..  ..   abc..d\abc..
        //
        internal static void CheckSearchPattern(string searchPattern)
        {
            for (int index = 0; (index = searchPattern.IndexOf("..", index, StringComparison.Ordinal)) != -1; index += 2)
            {
                // Terminal ".." or "..\". File and directory names cannot end in "..".
                if (index + 2 == searchPattern.Length ||
                    PathInternal.IsDirectorySeparator(searchPattern[index + 2]))
                {
                    throw new ArgumentException(SR.Arg_InvalidSearchPattern, "searchPattern");
                }
            }

            if (searchPattern.Length >= PathInternal.MaxComponentLength)
            {
                throw new PathTooLongException(SR.IO_PathTooLong);
            }
        }
Exemplo n.º 8
0
            private static bool DoesItemExist(ReadOnlySpan <char> path, bool isFile)
            {
                if (path.IsEmpty || path.Length == 0)
                {
                    return(false);
                }

                if (!isFile)
                {
                    return(FileSystem.DirectoryExists(path));
                }
#if MONO
                return(path[path.Length - 1] == '/'
#else
                return PathInternal.IsDirectorySeparator(path[path.Length - 1])
#endif
                    ? false
                    : FileSystem.FileExists(path));
            }
Exemplo n.º 9
0
        private static int GetDirectoryNameOffset(ReadOnlySpan <char> path)
        {
            int rootLength = PathInternal.GetRootLength(path);
            int end        = path.Length;

            if (end <= rootLength)
            {
                return(-1);
            }

            while (end > rootLength && !PathInternal.IsDirectorySeparator(path[--end]))
            {
                ;
            }

            // Trim off any remaining separators (to deal with C:\foo\\bar)
            while (end > rootLength && PathInternal.IsDirectorySeparator(path[end - 1]))
            {
                end--;
            }

            return(end);
        }
Exemplo n.º 10
0
        public static string Combine(params string[] paths)
        {
            if (paths == null)
            {
                throw new ArgumentNullException(nameof(paths));
            }

            int finalSize      = 0;
            int firstComponent = 0;

            // We have two passes, the first calculates how large a buffer to allocate and does some precondition
            // checks on the paths passed in.  The second actually does the combination.

            for (int i = 0; i < paths.Length; i++)
            {
                if (paths[i] == null)
                {
                    throw new ArgumentNullException(nameof(paths));
                }

                if (paths[i].Length == 0)
                {
                    continue;
                }

                if (IsPathRooted(paths[i]))
                {
                    firstComponent = i;
                    finalSize      = paths[i].Length;
                }
                else
                {
                    finalSize += paths[i].Length;
                }

                char ch = paths[i][paths[i].Length - 1];
                if (!PathInternal.IsDirectorySeparator(ch))
                {
                    finalSize++;
                }
            }

            StringBuilder finalPath = StringBuilderCache.Acquire(finalSize);

            for (int i = firstComponent; i < paths.Length; i++)
            {
                if (paths[i].Length == 0)
                {
                    continue;
                }

                if (finalPath.Length == 0)
                {
                    finalPath.Append(paths[i]);
                }
                else
                {
                    char ch = finalPath[finalPath.Length - 1];
                    if (!PathInternal.IsDirectorySeparator(ch))
                    {
                        finalPath.Append(PathInternal.DirectorySeparatorChar);
                    }

                    finalPath.Append(paths[i]);
                }
            }

            return(StringBuilderCache.GetStringAndRelease(finalPath));
        }
Exemplo n.º 11
0
        public static string Combine(params string[] paths)
        {
            if (paths == null)
            {
                throw new ArgumentNullException(nameof(paths));
            }

            int maxSize        = 0;
            int firstComponent = 0;

            // We have two passes, the first calculates how large a buffer to allocate and does some precondition
            // checks on the paths passed in.  The second actually does the combination.

            for (int i = 0; i < paths.Length; i++)
            {
                if (paths[i] == null)
                {
                    throw new ArgumentNullException(nameof(paths));
                }

                if (paths[i].Length == 0)
                {
                    continue;
                }

                if (IsPathRooted(paths[i]))
                {
                    firstComponent = i;
                    maxSize        = paths[i].Length;
                }
                else
                {
                    maxSize += paths[i].Length;
                }

                char ch = paths[i][paths[i].Length - 1];
                if (!PathInternal.IsDirectorySeparator(ch))
                {
                    maxSize++;
                }
            }

            Span <char> initialBuffer = stackalloc char[260];    // MaxShortPath on Windows
            var         builder       = new ValueStringBuilder(initialBuffer);

            builder.EnsureCapacity(maxSize);

            for (int i = firstComponent; i < paths.Length; i++)
            {
                if (paths[i].Length == 0)
                {
                    continue;
                }

                if (builder.Length == 0)
                {
                    builder.Append(paths[i]);
                }
                else
                {
                    char ch = builder[builder.Length - 1];
                    if (!PathInternal.IsDirectorySeparator(ch))
                    {
                        builder.Append(PathInternal.DirectorySeparatorChar);
                    }

                    builder.Append(paths[i]);
                }
            }

            return(builder.ToString());
        }
Exemplo n.º 12
0
        /// <summary>
        /// Try to remove relative segments from the given path (without combining with a root).
        /// </summary>
        /// <param name="skip">Skip the specified number of characters before evaluating.</param>
        public static string RemoveRelativeSegments(string path, int skip = 0)
        {
            var flippedSeparator = false;

            // Remove "//", "/./", and "/../" from the path by copying each character to the output,
            // except the ones we're removing, such that the builder contains the normalized path
            // at the end.
            var sb = new StringBuilder(path.Length);

            if (skip > 0)
            {
                sb.Append(path, 0, skip);
            }

            for (int i = skip; i < path.Length; i++)
            {
                char c = path[i];

                if (PathInternal.IsDirectorySeparator(c) && i + 1 < path.Length)
                {
                    // Skip this character if it's a directory separator and if the next character is, too,
                    // e.g. "parent//child" => "parent/child"
                    if (PathInternal.IsDirectorySeparator(path[i + 1]))
                    {
                        continue;
                    }

                    // Skip this character and the next if it's referring to the current directory,
                    // e.g. "parent/./child" => "parent/child"
                    if ((i + 2 == path.Length || PathInternal.IsDirectorySeparator(path[i + 2])) && path[i + 1] == '.')
                    {
                        i++;
                        continue;
                    }

                    // Skip this character and the next two if it's referring to the parent directory,
                    // e.g. "parent/child/../grandchild" => "parent/grandchild"
                    if (i + 2 < path.Length &&
                        (i + 3 == path.Length || PathInternal.IsDirectorySeparator(path[i + 3])) &&
                        path[i + 1] == '.' &&
                        path[i + 2] == '.')
                    {
                        // Unwind back to the last slash (and if there isn't one, clear out everything).
                        int s;
                        for (s = sb.Length - 1; s >= 0; s--)
                        {
                            if (PathInternal.IsDirectorySeparator(sb[s]))
                            {
                                sb.Length = s;
                                break;
                            }
                        }

                        if (s < 0)
                        {
                            sb.Length = 0;
                        }

                        i += 2;
                        continue;
                    }
                }

                // Normalize the directory separator if needed
                if (c != Path.DirectorySeparatorChar && c == Path.AltDirectorySeparatorChar)
                {
                    c = Path.DirectorySeparatorChar;
                    flippedSeparator = true;
                }

                sb.Append(c);
            }

            if (flippedSeparator || sb.Length != path.Length)
            {
                return(sb.ToString());
            }

            // We haven't changed the source path, return the original
            return(path);
        }
Exemplo n.º 13
0
 /// <summary>
 /// Returns true if the path ends in a directory separator.
 /// </summary>
 public static bool EndsInDirectorySeparator(string path)
 => path != null && path.Length > 0 && PathInternal.IsDirectorySeparator(path[path.Length - 1]);
Exemplo n.º 14
0
        public static void CreateDirectory(string fullPath)
        {
            // We can save a bunch of work if the directory we want to create already exists.  This also
            // saves us in the case where sub paths are inaccessible (due to ERROR_ACCESS_DENIED) but the
            // final path is accessible and the directory already exists.  For example, consider trying
            // to create c:\Foo\Bar\Baz, where everything already exists but ACLS prevent access to c:\Foo
            // and c:\Foo\Bar.  In that case, this code will think it needs to create c:\Foo, and c:\Foo\Bar
            // and fail to due so, causing an exception to be thrown.  This is not what we want.
            if (DirectoryExists(fullPath))
            {
                return;
            }

            List <string> stackDir = new List <string>();

            // Attempt to figure out which directories don't exist, and only
            // create the ones we need.  Note that FileExists may fail due
            // to Win32 ACL's preventing us from seeing a directory, and this
            // isn't threadsafe.

            bool somepathexists = false;

            int length = fullPath.Length;

            // We need to trim the trailing slash or the code will try to create 2 directories of the same name.
            if (length >= 2 && PathInternal.EndsInDirectorySeparator(fullPath.AsSpan()))
            {
                length--;
            }

            int lengthRoot = PathInternal.GetRootLength(fullPath.AsSpan());

            if (length > lengthRoot)
            {
                // Special case root (fullpath = X:\\)
                int i = length - 1;
                while (i >= lengthRoot && !somepathexists)
                {
                    string dir = fullPath.Substring(0, i + 1);

                    if (!DirectoryExists(dir)) // Create only the ones missing
                    {
                        stackDir.Add(dir);
                    }
                    else
                    {
                        somepathexists = true;
                    }

                    while (i > lengthRoot && !PathInternal.IsDirectorySeparator(fullPath[i]))
                    {
                        i--;
                    }
                    i--;
                }
            }

            int count = stackDir.Count;

            // If we were passed a DirectorySecurity, convert it to a security
            // descriptor and set it in he call to CreateDirectory.
            Interop.Kernel32.SECURITY_ATTRIBUTES secAttrs = default;

            bool   r           = true;
            int    firstError  = 0;
            string errorString = fullPath;

            // If all the security checks succeeded create all the directories
            while (stackDir.Count > 0)
            {
                string name = stackDir[stackDir.Count - 1];
                stackDir.RemoveAt(stackDir.Count - 1);

                r = Interop.Kernel32.CreateDirectory(name, ref secAttrs);
                if (!r && (firstError == 0))
                {
                    int currentError = Marshal.GetLastWin32Error();
                    // While we tried to avoid creating directories that don't
                    // exist above, there are at least two cases that will
                    // cause us to see ERROR_ALREADY_EXISTS here.  FileExists
                    // can fail because we didn't have permission to the
                    // directory.  Secondly, another thread or process could
                    // create the directory between the time we check and the
                    // time we try using the directory.  Thirdly, it could
                    // fail because the target does exist, but is a file.
                    if (currentError != Interop.Errors.ERROR_ALREADY_EXISTS)
                    {
                        firstError = currentError;
                    }
                    else
                    {
                        // If there's a file in this directory's place, or if we have ERROR_ACCESS_DENIED when checking if the directory already exists throw.
                        if (FileExists(name) || (!DirectoryExists(name, out currentError) && currentError == Interop.Errors.ERROR_ACCESS_DENIED))
                        {
                            firstError  = currentError;
                            errorString = name;
                        }
                    }
                }
            }

            // We need this check to mask OS differences
            // Handle CreateDirectory("X:\\") when X: doesn't exist. Similarly for n/w paths.
            if ((count == 0) && !somepathexists)
            {
                string root = Directory.InternalGetDirectoryRoot(fullPath);
                if (!DirectoryExists(root))
                {
                    throw Win32Marshal.GetExceptionForWin32Error(Interop.Errors.ERROR_PATH_NOT_FOUND, root);
                }
                return;
            }

            // Only throw an exception if creating the exact directory we
            // wanted failed to work correctly.
            if (!r && (firstError != 0))
            {
                throw Win32Marshal.GetExceptionForWin32Error(firstError, errorString);
            }
        }
Exemplo n.º 15
0
        /// <summary>
        /// Normalize separators in the given path. Converts forward slashes into back slashes and compresses slash runs, keeping initial 2 if present.
        /// Also trims initial whitespace in front of "rooted" paths (see PathStartSkip).
        ///
        /// This effectively replicates the behavior of the legacy NormalizePath when it was called with fullCheck=false and expandShortpaths=false.
        /// The current NormalizePath gets directory separator normalization from Win32's GetFullPathName(), which will resolve relative paths and as
        /// such can't be used here (and is overkill for our uses).
        ///
        /// Like the current NormalizePath this will not try and analyze periods/spaces within directory segments.
        /// </summary>
        /// <remarks>
        /// The only callers that used to use Path.Normalize(fullCheck=false) were Path.GetDirectoryName() and Path.GetPathRoot(). Both usages do
        /// not need trimming of trailing whitespace here.
        ///
        /// GetPathRoot() could technically skip normalizing separators after the second segment- consider as a future optimization.
        ///
        /// For legacy desktop behavior with ExpandShortPaths:
        /// - It has no impact on GetPathRoot() so doesn't need consideration.
        /// - It could impact GetDirectoryName(), but only if the path isn't relative (C:\ or \\Server\Share).
        ///
        /// In the case of GetDirectoryName() the ExpandShortPaths behavior was undocumented and provided inconsistent results if the path was
        /// fixed/relative. For example: "C:\PROGRA~1\A.TXT" would return "C:\Program Files" while ".\PROGRA~1\A.TXT" would return ".\PROGRA~1". If you
        /// ultimately call GetFullPath() this doesn't matter, but if you don't or have any intermediate string handling could easily be tripped up by
        /// this undocumented behavior.
        ///
        /// We won't match this old behavior because:
        ///
        /// 1. It was undocumented
        /// 2. It was costly (extremely so if it actually contained '~')
        /// 3. Doesn't play nice with string logic
        /// 4. Isn't a cross-plat friendly concept/behavior
        /// </remarks>
        public static string NormalizeDirectorySeparators(string path)
        {
            if (string.IsNullOrEmpty(path))
            {
                return(path);
            }

            char current;

            // Make a pass to see if we need to normalize so we can potentially skip allocating
            var normalized = true;

            for (var i = 0; i < path.Length; i++)
            {
                current = path[i];
                if (PathInternal.IsDirectorySeparator(current) &&
                    (current != Path.DirectorySeparatorChar
                     // Check for sequential separators past the first position (we need to keep initial two for UNC/extended)
                     || i > 0 && i + 1 < path.Length && PathInternal.IsDirectorySeparator(path[i + 1])))
                {
                    normalized = false;
                    break;
                }
            }

            if (normalized)
            {
                return(path);
            }

            var builder = new StringBuilder(path.Length);

            var start = 0;

            if (PathInternal.IsDirectorySeparator(path[start]))
            {
                start++;
                builder.Append(Path.DirectorySeparatorChar);
            }

            for (int i = start; i < path.Length; i++)
            {
                current = path[i];

                // If we have a separator
                if (PathInternal.IsDirectorySeparator(current))
                {
                    // If the next is a separator, skip adding this
                    if (i + 1 < path.Length && PathInternal.IsDirectorySeparator(path[i + 1]))
                    {
                        continue;
                    }

                    // Ensure it is the primary separator
                    current = Path.DirectorySeparatorChar;
                }

                builder.Append(current);
            }

            return(builder.ToString());
        }
Exemplo n.º 16
0
    private static string GetRelativePath(string relativeTo, string path, StringComparison comparisonType)
    {
        if (relativeTo == null)
        {
            throw new ArgumentNullException(nameof(relativeTo));
        }
        if (PathInternal.IsEffectivelyEmpty(relativeTo.AsSpan()))
        {
            throw new ArgumentException(SR.Arg_PathEmpty, nameof(relativeTo));
        }
        if (path == null)
        {
            throw new ArgumentNullException(nameof(path));
        }
        if (PathInternal.IsEffectivelyEmpty(path.AsSpan()))
        {
            throw new ArgumentException(SR.Arg_PathEmpty, nameof(path));
        }
        Debug.Assert(comparisonType == StringComparison.Ordinal || comparisonType == StringComparison.OrdinalIgnoreCase);
        relativeTo = GetFullPath(relativeTo);
        path       = GetFullPath(path);
        // Need to check if the roots are different- if they are we need to return the "to" path.
        if (!PathInternal.AreRootsEqual(relativeTo, path, comparisonType))
        {
            return(path);
        }
        int commonLength = PathInternal.GetCommonPathLength(relativeTo, path, ignoreCase: comparisonType == StringComparison.OrdinalIgnoreCase);

        // If there is nothing in common they can't share the same root, return the "to" path as is.
        if (commonLength == 0)
        {
            return(path);
        }
        // Trailing separators aren't significant for comparison
        int relativeToLength = relativeTo.Length;

        if (EndsInDirectorySeparator(relativeTo.AsSpan()))
        {
            relativeToLength--;
        }
        bool pathEndsInSeparator = EndsInDirectorySeparator(path.AsSpan());
        int  pathLength          = path.Length;

        if (pathEndsInSeparator)
        {
            pathLength--;
        }
        // If we have effectively the same path, return "."
        if (relativeToLength == pathLength && commonLength >= relativeToLength)
        {
            return(".");
        }
        // We have the same root, we need to calculate the difference now using the
        // common Length and Segment count past the length.
        //
        // Some examples:
        //
        //  C:\Foo C:\Bar L3, S1 -> ..\Bar
        //  C:\Foo C:\Foo\Bar L6, S0 -> Bar
        //  C:\Foo\Bar C:\Bar\Bar L3, S2 -> ..\..\Bar\Bar
        //  C:\Foo\Foo C:\Foo\Bar L7, S1 -> ..\Bar
        // Original: var sb = new ValueStringBuilder(stackalloc char[260]);
        var sb = new StringBuilder(260);

        sb.EnsureCapacity(Math.Max(relativeTo.Length, path.Length));
        // Add parent segments for segments past the common on the "from" path
        if (commonLength < relativeToLength)
        {
            sb.Append("..");
            for (int i = commonLength + 1; i < relativeToLength; i++)
            {
                if (PathInternal.IsDirectorySeparator(relativeTo[i]))
                {
                    sb.Append(DirectorySeparatorChar);
                    sb.Append("..");
                }
            }
        }
        else if (PathInternal.IsDirectorySeparator(path[commonLength]))
        {
            // No parent segments and we need to eat the initial separator
            //  (C:\Foo C:\Foo\Bar case)
            commonLength++;
        }
        // Now add the rest of the "to" path, adding back the trailing separator
        int differenceLength = pathLength - commonLength;

        if (pathEndsInSeparator)
        {
            differenceLength++;
        }
        if (differenceLength > 0)
        {
            if (sb.Length > 0)
            {
                sb.Append(DirectorySeparatorChar);
            }
            sb.Append(path.AsSpan(commonLength, differenceLength));
        }
        return(sb.ToString());
    }
Exemplo n.º 17
0
 /// <summary>
 /// Returns true if the path ends in a directory separator.
 /// </summary>
 public static bool EndsInDirectorySeparator(string path)     // Originally was public static bool EndsInDirectorySeparator(ReadOnlySpan<char> path)
 => path.Length > 0 && PathInternal.IsDirectorySeparator(path[path.Length - 1]);
Exemplo n.º 18
0
 internal static string TrimEndingDirectorySeparator(string path) =>
 path.Length > 1 && PathInternal.IsDirectorySeparator(path[path.Length - 1]) ?     // exclude root "/"
 path.Substring(0, path.Length - 1) :
 path;
Exemplo n.º 19
0
        public static string GetFullPath(string path, string basePath)
        {
            if (path == null)
            {
                throw new ArgumentNullException(nameof(path));
            }

            if (basePath == null)
            {
                throw new ArgumentNullException(nameof(basePath));
            }

            if (!IsPathFullyQualified(basePath))
            {
                throw new ArgumentException(SR.Arg_BasePathNotFullyQualified, nameof(basePath));
            }

            if (basePath.Contains('\0') || path.Contains('\0'))
            {
                throw new ArgumentException(SR.Argument_InvalidPathChars);
            }

            if (IsPathFullyQualified(path))
            {
                return(GetFullPath(path));
            }

            if (PathInternal.IsEffectivelyEmpty(path.AsSpan()))
            {
                return(basePath);
            }

            int    length = path.Length;
            string combinedPath;

            if (length >= 1 && PathInternal.IsDirectorySeparator(path[0]))
            {
                // Path is current drive rooted i.e. starts with \:
                // "\Foo" and "C:\Bar" => "C:\Foo"
                // "\Foo" and "\\?\C:\Bar" => "\\?\C:\Foo"
                combinedPath = Join(GetPathRoot(basePath.AsSpan()), path.AsSpan(1)); // Cut the separator to ensure we don't end up with two separators when joining with the root.
            }
            else if (length >= 2 && PathInternal.IsValidDriveChar(path[0]) && path[1] == PathInternal.VolumeSeparatorChar)
            {
                // Drive relative paths
                Debug.Assert(length == 2 || !PathInternal.IsDirectorySeparator(path[2]));

                if (GetVolumeName(path.AsSpan()).EqualsOrdinal(GetVolumeName(basePath.AsSpan())))
                {
                    // Matching root
                    // "C:Foo" and "C:\Bar" => "C:\Bar\Foo"
                    // "C:Foo" and "\\?\C:\Bar" => "\\?\C:\Bar\Foo"
                    combinedPath = Join(basePath.AsSpan(), path.AsSpan(2));
                }
                else
                {
                    // No matching root, root to specified drive
                    // "D:Foo" and "C:\Bar" => "D:Foo"
                    // "D:Foo" and "\\?\C:\Bar" => "\\?\D:\Foo"
                    combinedPath = !PathInternal.IsDevice(basePath.AsSpan())
                        ? path.Insert(2, @"\")
                        : length == 2
                            ? JoinInternal(basePath.AsSpan(0, 4), path.AsSpan(), @"\".AsSpan())
                            : JoinInternal(basePath.AsSpan(0, 4), path.AsSpan(0, 2), @"\".AsSpan(), path.AsSpan(2));
                }
            }
            else
            {
                // "Simple" relative path
                // "Foo" and "C:\Bar" => "C:\Bar\Foo"
                // "Foo" and "\\?\C:\Bar" => "\\?\C:\Bar\Foo"
                combinedPath = JoinInternal(basePath.AsSpan(), path.AsSpan());
            }

            // Device paths are normalized by definition, so passing something of this format (i.e. \\?\C:\.\tmp, \\.\C:\foo)
            // to Windows APIs won't do anything by design. Additionally, GetFullPathName() in Windows doesn't root
            // them properly. As such we need to manually remove segments and not use GetFullPath().

            return(PathInternal.IsDevice(combinedPath.AsSpan())
                ? PathInternal.RemoveRelativeSegments(combinedPath, PathInternal.GetRootLength(combinedPath.AsSpan()))
                : GetFullPath(combinedPath));
        }
Exemplo n.º 20
0
 internal static bool EndsInDirectorySeparator(String path)
 {
     return(path.Length > 0 && PathInternal.IsDirectorySeparator(path[path.Length - 1]));
 }