Contains internal path helpers that are shared between many projects.
Exemple #1
0
        private SafeFileHandle OpenHandle(FileMode mode, FileShare share, FileOptions options)
        {
            Interop.Kernel32.SECURITY_ATTRIBUTES secAttrs = GetSecAttrs(share);

            int fAccess =
                ((_access & FileAccess.Read) == FileAccess.Read ? GENERIC_READ : 0) |
                ((_access & FileAccess.Write) == FileAccess.Write ? GENERIC_WRITE : 0);

            // Our Inheritable bit was stolen from Windows, but should be set in
            // the security attributes class.  Don't leave this bit set.
            share &= ~FileShare.Inheritable;

            // Must use a valid Win32 constant here...
            if (mode == FileMode.Append)
            {
                mode = FileMode.OpenOrCreate;
            }

            int flagsAndAttributes = (int)options;

            // For mitigating local elevation of privilege attack through named pipes
            // make sure we always call CreateFile with SECURITY_ANONYMOUS so that the
            // named pipe server can't impersonate a high privileged client security context
            flagsAndAttributes |= (Interop.Kernel32.SecurityOptions.SECURITY_SQOS_PRESENT | Interop.Kernel32.SecurityOptions.SECURITY_ANONYMOUS);

            // Don't pop up a dialog for reading from an empty floppy drive
            uint oldMode;
            bool success = Interop.Kernel32.SetThreadErrorMode(Interop.Kernel32.SEM_FAILCRITICALERRORS, out oldMode);

            try
            {
                SafeFileHandle fileHandle = Interop.Kernel32.CreateFile(_path, fAccess, share, ref secAttrs, mode, flagsAndAttributes, IntPtr.Zero);
                fileHandle.IsAsync = _useAsyncIO;

                if (fileHandle.IsInvalid)
                {
                    // Return a meaningful exception with the full path.

                    // NT5 oddity - when trying to open "C:\" as a Win32FileStream,
                    // we usually get ERROR_PATH_NOT_FOUND from the OS.  We should
                    // probably be consistent w/ every other directory.
                    int errorCode = Marshal.GetLastWin32Error();

                    if (errorCode == Interop.Errors.ERROR_PATH_NOT_FOUND && _path.Length == PathInternal.GetRootLength(_path))
                    {
                        errorCode = Interop.Errors.ERROR_ACCESS_DENIED;
                    }

                    throw Win32Marshal.GetExceptionForWin32Error(errorCode, _path);
                }

                int fileType = Interop.Kernel32.GetFileType(fileHandle);
                if (fileType != Interop.Kernel32.FileTypes.FILE_TYPE_DISK)
                {
                    fileHandle.Dispose();
                    throw new NotSupportedException(SR.NotSupported_FileStreamOnNonFiles);
                }

                return(fileHandle);
            }
            finally
            {
                if (success)
                {
                    Interop.Kernel32.SetThreadErrorMode(oldMode, out oldMode);
                }
            }
        }
Exemple #2
0
        public static string Combine(params string[] paths)
        {
            if (paths == null)
            {
                throw new ArgumentNullException("paths");
            }
            Contract.EndContractBlock();

            int finalSize      = 0;
            int firstComponent = 0;

            // We have two passes, the first calcuates 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("paths");
                }

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

                PathInternal.CheckInvalidPathChars(paths[i]);

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

                char ch = paths[i][paths[i].Length - 1];
                if (!IsDirectoryOrVolumeSeparator(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 (!IsDirectoryOrVolumeSeparator(ch))
                    {
                        finalPath.Append(DirectorySeparatorChar);
                    }

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

            return(StringBuilderCache.GetStringAndRelease(finalPath));
        }
Exemple #3
0
        public void MoveTo(String destDirName)
        {
            if (destDirName == null)
            {
                throw new ArgumentNullException("destDirName");
            }
            if (destDirName.Length == 0)
            {
                throw new ArgumentException(SR.Argument_EmptyFileName, "destDirName");
            }
            Contract.EndContractBlock();

            String fullDestDirName = PathHelpers.GetFullPathInternal(destDirName);

            if (fullDestDirName[fullDestDirName.Length - 1] != Path.DirectorySeparatorChar)
            {
                fullDestDirName = fullDestDirName + PathHelpers.DirectorySeparatorCharAsString;
            }

            String fullSourcePath;

            if (FullPath.Length > 0 && FullPath[FullPath.Length - 1] == Path.DirectorySeparatorChar)
            {
                fullSourcePath = FullPath;
            }
            else
            {
                fullSourcePath = FullPath + PathHelpers.DirectorySeparatorCharAsString;
            }

            if (PathInternal.IsDirectoryTooLong(fullSourcePath))
            {
                throw new PathTooLongException(SR.IO_PathTooLong);
            }

            if (PathInternal.IsDirectoryTooLong(fullDestDirName))
            {
                throw new PathTooLongException(SR.IO_PathTooLong);
            }

            StringComparison pathComparison = PathInternal.StringComparison;

            if (String.Equals(fullSourcePath, fullDestDirName, pathComparison))
            {
                throw new IOException(SR.IO_SourceDestMustBeDifferent);
            }

            String sourceRoot      = Path.GetPathRoot(fullSourcePath);
            String destinationRoot = Path.GetPathRoot(fullDestDirName);

            if (!String.Equals(sourceRoot, destinationRoot, pathComparison))
            {
                throw new IOException(SR.IO_SourceDestMustHaveSameRoot);
            }

            FileSystem.Current.MoveDirectory(FullPath, fullDestDirName);

            FullPath     = fullDestDirName;
            OriginalPath = destDirName;
            DisplayPath  = GetDisplayName(OriginalPath, FullPath);

            // Flush any cached information about the directory.
            Invalidate();
        }
Exemple #4
0
        private static string CombineNoChecks(string path1, string path2, string path3, string path4)
        {
            if (path1.Length == 0)
            {
                return(CombineNoChecks(path2, path3, path4));
            }
            if (path2.Length == 0)
            {
                return(CombineNoChecks(path1, path3, path4));
            }
            if (path3.Length == 0)
            {
                return(CombineNoChecks(path1, path2, path4));
            }
            if (path4.Length == 0)
            {
                return(CombineNoChecks(path1, path2, path3));
            }

            if (IsPathRooted(path4))
            {
                return(path4);
            }
            if (IsPathRooted(path3))
            {
                return(CombineNoChecks(path3, path4));
            }
            if (IsPathRooted(path2))
            {
                return(CombineNoChecks(path2, path3, path4));
            }

            bool hasSep1 = PathInternal.IsDirectoryOrVolumeSeparator(path1[path1.Length - 1]);
            bool hasSep2 = PathInternal.IsDirectoryOrVolumeSeparator(path2[path2.Length - 1]);
            bool hasSep3 = PathInternal.IsDirectoryOrVolumeSeparator(path3[path3.Length - 1]);

            if (hasSep1 && hasSep2 && hasSep3)
            {
                // Use string.Concat overload that takes four strings
                return(path1 + path2 + path3 + path4);
            }
            else
            {
                // string.Concat only has string-based overloads up to four arguments; after that requires allocating
                // a params string[].  Instead, try to use a cached StringBuilder.
                StringBuilder sb = StringBuilderCache.Acquire(path1.Length + path2.Length + path3.Length + path4.Length + 3);

                sb.Append(path1);
                if (!hasSep1)
                {
                    sb.Append(DirectorySeparatorChar);
                }

                sb.Append(path2);
                if (!hasSep2)
                {
                    sb.Append(DirectorySeparatorChar);
                }

                sb.Append(path3);
                if (!hasSep3)
                {
                    sb.Append(DirectorySeparatorChar);
                }

                sb.Append(path4);

                return(StringBuilderCache.GetStringAndRelease(sb));
            }
        }
Exemple #5
0
 private static bool IsDirectoryOrVolumeSeparator(char c)
 {
     return(PathInternal.IsDirectorySeparator(c) || VolumeSeparatorChar == c);
 }
Exemple #6
0
 /// <summary>
 /// Returns true if the character is a directory or volume separator.
 /// </summary>
 /// <param name="ch">The character to test.</param>
 internal static bool IsDirectoryOrVolumeSeparator(char ch)
 {
     return(PathInternal.IsDirectorySeparator(ch) || Path.VolumeSeparatorChar == ch);
 }
        internal unsafe bool TryExpandShortFileName()
        {
            if (doNotTryExpandShortFileName)
            {
                return(false);
            }

            if (useStackAlloc)
            {
                NullTerminate();
                char *buffer = UnsafeGetArrayPtr();
                char *shortFileNameBuffer = stackalloc char[Path.MaxPath + 1];

                int r = Interop.mincore.GetLongPathNameUnsafe(buffer, shortFileNameBuffer, Path.MaxPath);

                // If success, the return buffer length does not account for the terminating null character.
                // If in-sufficient buffer, the return buffer length does account for the path + the terminating null character.
                // If failure, the return buffer length is zero
                if (r >= Path.MaxPath)
                {
                    throw new PathTooLongException(SR.IO_PathTooLong);
                }

                if (r == 0)
                {
                    // Note: GetLongPathName will return ERROR_INVALID_FUNCTION on a
                    // path like \\.\PHYSICALDEVICE0 - some device driver doesn't
                    // support GetLongPathName on that string.  This behavior is
                    // by design, according to the Core File Services team.
                    // We also get ERROR_NOT_ENOUGH_QUOTA in SQL_CLR_STRESS runs
                    // intermittently on paths like D:\DOCUME~1\user\LOCALS~1\Temp\

                    // We do not need to call GetLongPathName if we know it will fail becasue the path does not exist:
                    int lastErr = Marshal.GetLastWin32Error();
                    if (lastErr == Interop.mincore.Errors.ERROR_FILE_NOT_FOUND || lastErr == Interop.mincore.Errors.ERROR_PATH_NOT_FOUND)
                    {
                        doNotTryExpandShortFileName = true;
                    }

                    return(false);
                }

                // Safe to copy as we have already done Path.MaxPath bound checking
                Wstrcpy(buffer, shortFileNameBuffer, r);
                Length = r;
                // We should explicitly null terminate as in some cases the long version of the path
                // might actually be shorter than what we started with because of Win32's normalization
                // Safe to write directly as bufferLength is guaranteed to be < Path.MaxPath
                NullTerminate();
                return(true);
            }
            else
            {
                StringBuilder sb = GetStringBuilder();

                String origName    = sb.ToString();
                String tempName    = origName;
                bool   addedPrefix = false;
                if (tempName.Length > Path.MaxPath)
                {
                    tempName    = PathInternal.EnsureExtendedPrefix(tempName);
                    addedPrefix = true;
                }
                sb.Capacity = m_capacity;
                sb.Length   = 0;
                int r = Interop.mincore.GetLongPathName(tempName, sb, m_capacity);

                if (r == 0)
                {
                    // Note: GetLongPathName will return ERROR_INVALID_FUNCTION on a
                    // path like \\.\PHYSICALDEVICE0 - some device driver doesn't
                    // support GetLongPathName on that string.  This behavior is
                    // by design, according to the Core File Services team.
                    // We also get ERROR_NOT_ENOUGH_QUOTA in SQL_CLR_STRESS runs
                    // intermittently on paths like D:\DOCUME~1\user\LOCALS~1\Temp\

                    // We do not need to call GetLongPathName if we know it will fail becasue the path does not exist:
                    int lastErr = Marshal.GetLastWin32Error();
                    if (Interop.mincore.Errors.ERROR_FILE_NOT_FOUND == lastErr || Interop.mincore.Errors.ERROR_PATH_NOT_FOUND == lastErr)
                    {
                        doNotTryExpandShortFileName = true;
                    }

                    sb.Length = 0;
                    sb.Append(origName);
                    return(false);
                }

                if (addedPrefix)
                {
                    r -= 4;
                    sb = PathInternal.RemoveExtendedPrefix(sb);
                }

                // If success, the return buffer length does not account for the terminating null character.
                // If in-sufficient buffer, the return buffer length does account for the path + the terminating null character.
                // If failure, the return buffer length is zero
                if (r >= m_maxPath)
                {
                    throw new PathTooLongException(SR.IO_PathTooLong);
                }

                Length = sb.Length;
                return(true);
            }
        }
Exemple #8
0
        private static string TryExpandShortFileName(ref ValueStringBuilder outputBuilder, string originalPath)
        {
            // We guarantee we'll expand short names for paths that only partially exist. As such, we need to find the part of the path that actually does exist. To
            // avoid allocating like crazy we'll create only one input array and modify the contents with embedded nulls.

            Debug.Assert(!PathInternal.IsPartiallyQualified(outputBuilder.AsSpan()), "should have resolved by now");

            // We'll have one of a few cases by now (the normalized path will have already:
            //
            //  1. Dos path (C:\)
            //  2. Dos UNC (\\Server\Share)
            //  3. Dos device path (\\.\C:\, \\?\C:\)
            //
            // We want to put the extended syntax on the front if it doesn't already have it (for long path support and speed), which may mean switching from \\.\.
            //
            // Note that we will never get \??\ here as GetFullPathName() does not recognize \??\ and will return it as C:\??\ (or whatever the current drive is).

            int  rootLength = PathInternal.GetRootLength(outputBuilder.AsSpan());
            bool isDevice   = PathInternal.IsDevice(outputBuilder.AsSpan());

            // As this is a corner case we're not going to add a stackalloc here to keep the stack pressure down.
            ValueStringBuilder inputBuilder = new ValueStringBuilder();

            bool isDosUnc       = false;
            int  rootDifference = 0;
            bool wasDotDevice   = false;

            // Add the extended prefix before expanding to allow growth over MAX_PATH
            if (isDevice)
            {
                // We have one of the following (\\?\ or \\.\)
                inputBuilder.Append(outputBuilder.AsSpan());

                if (outputBuilder[2] == '.')
                {
                    wasDotDevice    = true;
                    inputBuilder[2] = '?';
                }
            }
            else
            {
                isDosUnc       = !PathInternal.IsDevice(outputBuilder.AsSpan()) && outputBuilder.Length > 1 && outputBuilder[0] == '\\' && outputBuilder[1] == '\\';
                rootDifference = PrependDevicePathChars(ref outputBuilder, isDosUnc, ref inputBuilder);
            }

            rootLength += rootDifference;
            int inputLength = inputBuilder.Length;

            bool success    = false;
            int  foundIndex = inputBuilder.Length - 1;

            // Need to null terminate the input builder
            inputBuilder.Append('\0');

            while (!success)
            {
                uint result = Interop.Kernel32.GetLongPathNameW(ref inputBuilder.GetPinnableReference(), ref outputBuilder.GetPinnableReference(), (uint)outputBuilder.Capacity);

                // Replace any temporary null we added
                if (inputBuilder[foundIndex] == '\0')
                {
                    inputBuilder[foundIndex] = '\\';
                }

                if (result == 0)
                {
                    // Look to see if we couldn't find the file
                    int error = Marshal.GetLastWin32Error();
                    if (error != Interop.Errors.ERROR_FILE_NOT_FOUND && error != Interop.Errors.ERROR_PATH_NOT_FOUND)
                    {
                        // Some other failure, give up
                        break;
                    }

                    // We couldn't find the path at the given index, start looking further back in the string.
                    foundIndex--;

                    for (; foundIndex > rootLength && inputBuilder[foundIndex] != '\\'; foundIndex--)
                    {
                        ;
                    }
                    if (foundIndex == rootLength)
                    {
                        // Can't trim the path back any further
                        break;
                    }
                    else
                    {
                        // Temporarily set a null in the string to get Windows to look further up the path
                        inputBuilder[foundIndex] = '\0';
                    }
                }
                else if (result > outputBuilder.Capacity)
                {
                    // Not enough space. The result count for this API does not include the null terminator.
                    outputBuilder.EnsureCapacity(checked ((int)result));
                    result = Interop.Kernel32.GetLongPathNameW(ref inputBuilder.GetPinnableReference(), ref outputBuilder.GetPinnableReference(), (uint)outputBuilder.Capacity);
                }
                else
                {
                    // Found the path
                    success = true;
                    outputBuilder.Length = checked ((int)result);
                    if (foundIndex < inputLength - 1)
                    {
                        // It was a partial find, put the non-existent part of the path back
                        outputBuilder.Append(inputBuilder.AsSpan().Slice(foundIndex, inputBuilder.Length - foundIndex));
                    }
                }
            }

            // Need to trim out the trailing separator in the input builder
            inputBuilder.Length = inputBuilder.Length - 1;

            // If we were able to expand the path, use it, otherwise use the original full path result
            ref ValueStringBuilder builderToUse = ref (success ? ref outputBuilder : ref inputBuilder);
        /// <summary>
        /// Gets reparse point information associated to <paramref name="linkPath"/>.
        /// </summary>
        /// <returns>The immediate link target, absolute or relative or null if the file is not a supported link.</returns>
        internal static unsafe string?GetImmediateLinkTarget(string linkPath, bool isDirectory, bool throwOnError, bool returnFullPath)
        {
            using SafeFileHandle handle = OpenSafeFileHandle(linkPath,
                                                             Interop.Kernel32.FileOperations.FILE_FLAG_BACKUP_SEMANTICS |
                                                             Interop.Kernel32.FileOperations.FILE_FLAG_OPEN_REPARSE_POINT);

            if (handle.IsInvalid)
            {
                if (!throwOnError)
                {
                    return(null);
                }

                int error = Marshal.GetLastWin32Error();
                // File not found doesn't make much sense coming from a directory.
                if (isDirectory && error == Interop.Errors.ERROR_FILE_NOT_FOUND)
                {
                    error = Interop.Errors.ERROR_PATH_NOT_FOUND;
                }

                throw Win32Marshal.GetExceptionForWin32Error(error, linkPath);
            }

            byte[] buffer = ArrayPool <byte> .Shared.Rent(Interop.Kernel32.MAXIMUM_REPARSE_DATA_BUFFER_SIZE);

            try
            {
                bool success = Interop.Kernel32.DeviceIoControl(
                    handle,
                    dwIoControlCode: Interop.Kernel32.FSCTL_GET_REPARSE_POINT,
                    lpInBuffer: IntPtr.Zero,
                    nInBufferSize: 0,
                    lpOutBuffer: buffer,
                    nOutBufferSize: Interop.Kernel32.MAXIMUM_REPARSE_DATA_BUFFER_SIZE,
                    out _,
                    IntPtr.Zero);

                if (!success)
                {
                    if (!throwOnError)
                    {
                        return(null);
                    }

                    int error = Marshal.GetLastWin32Error();
                    // The file or directory is not a reparse point.
                    if (error == Interop.Errors.ERROR_NOT_A_REPARSE_POINT)
                    {
                        return(null);
                    }

                    throw Win32Marshal.GetExceptionForWin32Error(error, linkPath);
                }

                Span <byte> bufferSpan = new(buffer);
                success = MemoryMarshal.TryRead(bufferSpan, out Interop.Kernel32.SymbolicLinkReparseBuffer rbSymlink);
                Debug.Assert(success);

                // We always use SubstituteName(Offset|Length) instead of PrintName(Offset|Length),
                // the latter is just the display name of the reparse point and it can show something completely unrelated to the target.

                if (rbSymlink.ReparseTag == Interop.Kernel32.IOReparseOptions.IO_REPARSE_TAG_SYMLINK)
                {
                    int offset = sizeof(Interop.Kernel32.SymbolicLinkReparseBuffer) + rbSymlink.SubstituteNameOffset;
                    int length = rbSymlink.SubstituteNameLength;

                    Span <char> targetPath = MemoryMarshal.Cast <byte, char>(bufferSpan.Slice(offset, length));

                    bool isRelative = (rbSymlink.Flags & Interop.Kernel32.SYMLINK_FLAG_RELATIVE) != 0;
                    if (!isRelative)
                    {
                        // Absolute target is in NT format and we need to clean it up before return it to the user.
                        if (targetPath.StartsWith(PathInternal.UncNTPathPrefix.AsSpan()))
                        {
                            // We need to prepend the Win32 equivalent of UNC NT prefix.
                            return(Path.Join(PathInternal.UncPathPrefix.AsSpan(), targetPath.Slice(PathInternal.UncNTPathPrefix.Length)));
                        }

                        return(GetTargetPathWithoutNTPrefix(targetPath));
                    }
                    else if (returnFullPath)
                    {
                        return(Path.Join(Path.GetDirectoryName(linkPath.AsSpan()), targetPath));
                    }
                    else
                    {
                        return(targetPath.ToString());
                    }
                }
                else if (rbSymlink.ReparseTag == Interop.Kernel32.IOReparseOptions.IO_REPARSE_TAG_MOUNT_POINT)
                {
                    success = MemoryMarshal.TryRead(bufferSpan, out Interop.Kernel32.MountPointReparseBuffer rbMountPoint);
                    Debug.Assert(success);

                    int offset = sizeof(Interop.Kernel32.MountPointReparseBuffer) + rbMountPoint.SubstituteNameOffset;
                    int length = rbMountPoint.SubstituteNameLength;

                    Span <char> targetPath = MemoryMarshal.Cast <byte, char>(bufferSpan.Slice(offset, length));

                    // Unlike symbolic links, mount point paths cannot be relative.
                    Debug.Assert(!PathInternal.IsPartiallyQualified(targetPath));
                    // Mount points cannot point to a remote location.
                    Debug.Assert(!targetPath.StartsWith(PathInternal.UncNTPathPrefix.AsSpan()));
                    return(GetTargetPathWithoutNTPrefix(targetPath));
                }

                return(null);
            }
            finally
            {
                ArrayPool <byte> .Shared.Return(buffer);
            }
Exemple #10
0
        private string TryExpandShortFileName(StringBuilder outputBuffer)
        {
            // We guarantee we'll expand short names for paths that only partially exist. As such, we need to find the part of the path that actually does exist. To
            // avoid allocating like crazy we'll create only one input array and modify the contents with embedded nulls.

            Debug.Assert(!PathInternal.IsRelative(outputBuffer), "should have resolved by now");
            Debug.Assert(!PathInternal.IsExtended(outputBuffer), "expanding short names expects normal paths");

            // Add the extended prefix before expanding to allow growth over MAX_PATH
            char[] inputBuffer    = null;
            int    rootLength     = PathInternal.GetRootLength(outputBuffer);
            bool   isUnc          = this.IsUnc(outputBuffer);
            int    rootDifference = this.GetInputBuffer(outputBuffer, isUnc, out inputBuffer);

            rootLength += rootDifference;
            int inputLength = inputBuffer.Length;

            bool success    = false;
            int  foundIndex = inputBuffer.Length - 1;

            while (!success)
            {
                int result = Interop.mincore.GetLongPathNameW(inputBuffer, outputBuffer, outputBuffer.Capacity + 1);

                // Replace any temporary null we added
                if (inputBuffer[foundIndex] == '\0')
                {
                    inputBuffer[foundIndex] = '\\';
                }

                if (result == 0)
                {
                    // Look to see if we couldn't find the file
                    int error = Marshal.GetLastWin32Error();
                    if (error != Interop.mincore.Errors.ERROR_FILE_NOT_FOUND && error != Interop.mincore.Errors.ERROR_PATH_NOT_FOUND)
                    {
                        // Some other failure, give up
                        break;
                    }

                    // We couldn't find the path at the given index, start looking further back in the string.
                    foundIndex--;

                    for (; foundIndex > rootLength && inputBuffer[foundIndex] != '\\'; foundIndex--)
                    {
                        ;
                    }
                    if (foundIndex == rootLength)
                    {
                        // Can't trim the path back any further
                        break;
                    }
                    else
                    {
                        // Temporarily set a null in the string to get Windows to look further up the path
                        inputBuffer[foundIndex] = '\0';
                    }
                }
                else if (result > outputBuffer.Capacity)
                {
                    // Not enough space. The result count for this API does not include the null terminator.
                    outputBuffer.EnsureCapacity(result);
                    result = Interop.mincore.GetLongPathNameW(inputBuffer, outputBuffer, outputBuffer.Capacity + 1);
                }
                else
                {
                    // Found the path
                    success = true;
                    if (foundIndex < inputLength - 1)
                    {
                        // It was a partial find, put the non-existant part of the path back
                        outputBuffer.Append(inputBuffer, foundIndex, inputBuffer.Length - foundIndex);
                    }
                }
            }

            // Strip out the prefix and return the string
            if (success)
            {
                if (isUnc)
                {
                    // Need to go from \\?\UNC\ to \\?\UN\\
                    outputBuffer[PathInternal.UncExtendedPathPrefix.Length - 1] = '\\';
                    return(outputBuffer.ToString(rootDifference, outputBuffer.Length - rootDifference));
                }
                else
                {
                    return(outputBuffer.ToString(rootDifference, outputBuffer.Length - rootDifference));
                }
            }
            else
            {
                // Failed to get and expanded path, clean up our input
                if (isUnc)
                {
                    // Need to go from \\?\UNC\ to \\?\UN\\
                    inputBuffer[PathInternal.UncExtendedPathPrefix.Length - 1] = '\\';
                    return(new string(inputBuffer, rootDifference, inputBuffer.Length - rootDifference));
                }
                else
                {
                    return(new string(inputBuffer, rootDifference, inputBuffer.Length - rootDifference));
                }
            }
        }
Exemple #11
0
        private static string GetRelativePath(string relativeTo, string path, StringComparison comparisonType)
        {
            if (string.IsNullOrEmpty(relativeTo))
            {
                throw new ArgumentNullException(nameof(relativeTo));
            }
            if (PathInternal.IsEffectivelyEmpty(path))
            {
                throw new ArgumentNullException(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 (PathInternal.EndsInDirectorySeparator(relativeTo))
            {
                relativeToLength--;
            }

            bool pathEndsInSeparator = PathInternal.EndsInDirectorySeparator(path);
            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

            StringBuilder sb = StringBuilderCache.Acquire(Math.Max(relativeTo.Length, path.Length));

            // Add parent segments for segments past the common on the "from" path
            if (commonLength < relativeToLength)
            {
                sb.Append(PathInternal.ParentDirectoryPrefix);

                for (int i = commonLength; i < relativeToLength; i++)
                {
                    if (PathInternal.IsDirectorySeparator(relativeTo[i]))
                    {
                        sb.Append(PathInternal.ParentDirectoryPrefix);
                    }
                }
            }
            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 count = pathLength - commonLength;

            if (pathEndsInSeparator)
            {
                count++;
            }

            sb.Append(path, commonLength, count);
            return(StringBuilderCache.GetStringAndRelease(sb));
        }
Exemple #12
0
 public static bool IsPathFullyQualified(ReadOnlySpan <char> path)
 {
     return(!PathInternal.IsPartiallyQualified(path));
 }
Exemple #13
0
        public static string Join(params string?[] paths)
        {
            ArgumentNullException.ThrowIfNull(paths);

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

            int maxSize = 0;

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

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

            builder.EnsureCapacity(maxSize);

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

                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());
        }
Exemple #14
0
        public static string Combine(params string[] paths)
        {
            ArgumentNullException.ThrowIfNull(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++)
            {
                ArgumentNullException.ThrowIfNull(paths[i], 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++;
                }
            }

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

            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());
        }
Exemple #15
0
        /// <summary>
        /// Try to remove relative segments from the given path (without combining with a root).
        /// </summary>
        /// <param name="rootLength">The length of the root of the given path</param>
        internal static string RemoveRelativeSegments(string path, int rootLength)
        {
            Debug.Assert(rootLength > 0);
            bool flippedSeparator = false;

            int skip = rootLength;

            // We treat "\.." , "\." and "\\" as a relative segment. We want to collapse the first separator past the root presuming
            // the root actually ends in a separator. Otherwise the first segment for RemoveRelativeSegments
            // in cases like "\\?\C:\.\" and "\\?\C:\..\", the first segment after the root will be ".\" and "..\" which is not considered as a relative segment and hence not be removed.
            if (PathInternal.IsDirectorySeparator(path[skip - 1]))
            {
                skip--;
            }

            Span <char>        initialBuffer = stackalloc char[260 /* PathInternal.MaxShortPath */];
            ValueStringBuilder sb            = new ValueStringBuilder(initialBuffer);

            // 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.
            if (skip > 0)
            {
                sb.Append(path.AsSpan(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 >= skip; s--)
                        {
                            if (PathInternal.IsDirectorySeparator(sb[s]))
                            {
                                sb.Length = (i + 3 >= path.Length && s == skip) ? s + 1 : s; // to avoid removing the complete "\tmp\" segment in cases like \\?\C:\tmp\..\, C:\tmp\..
                                break;
                            }
                        }
                        if (s < skip)
                        {
                            sb.Length = skip;
                        }

                        i += 2;
                        continue;
                    }
                }

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

                sb.Append(c);
            }

            // If we haven't changed the source path, return the original
            if (!flippedSeparator && sb.Length == path.Length)
            {
                sb.Dispose();
                return(path);
            }

            return(sb.Length < rootLength?path.Substring(0, rootLength) : sb.ToString());
        }
Exemple #16
0
        [System.Security.SecurityCritical]  // auto-generated
        internal static String GetDisplayablePath(String path, bool isInvalidPath)
        {
            if (String.IsNullOrEmpty(path))
            {
                return(String.Empty);
            }

            // Is it a fully qualified path?
            bool isFullyQualified = false;

            if (path.Length < 2)
            {
                return(path);
            }
            if (PathInternal.IsDirectorySeparator(path[0]) && PathInternal.IsDirectorySeparator(path[1]))
            {
                isFullyQualified = true;
            }
            else if (path[1] == Path.VolumeSeparatorChar)
            {
                isFullyQualified = true;
            }

            if (!isFullyQualified && !isInvalidPath)
            {
                return(path);
            }

            bool safeToReturn = false;

            try {
                if (!isInvalidPath)
                {
#if !FEATURE_CORECLR
                    new FileIOPermission(FileIOPermissionAccess.PathDiscovery, new String[] { path }, false, false).Demand();
#endif
                    safeToReturn = true;
                }
            }
            catch (SecurityException) {
            }
            catch (ArgumentException) {
                // ? and * characters cause ArgumentException to be thrown from HasIllegalCharacters
                // inside FileIOPermission.AddPathList
            }
            catch (NotSupportedException) {
                // paths like "!Bogus\\dir:with/junk_.in it" can cause NotSupportedException to be thrown
                // from Security.Util.StringExpressionSet.CanonicalizePath when ':' is found in the path
                // beyond string index position 1.
            }

            if (!safeToReturn)
            {
                if (PathInternal.IsDirectorySeparator(path[path.Length - 1]))
                {
                    path = Environment.GetResourceString("IO.IO_NoPermissionToDirectoryName");
                }
                else
                {
                    path = Path.GetFileName(path);
                }
            }

            return(path);
        }
Exemple #17
0
        public static void Move(string sourceDirName, string destDirName)
        {
            if (sourceDirName == null)
            {
                throw new ArgumentNullException(nameof(sourceDirName));
            }
            if (sourceDirName.Length == 0)
            {
                throw new ArgumentException(SR.Argument_EmptyFileName, nameof(sourceDirName));
            }

            if (destDirName == null)
            {
                throw new ArgumentNullException(nameof(destDirName));
            }
            if (destDirName.Length == 0)
            {
                throw new ArgumentException(SR.Argument_EmptyFileName, nameof(destDirName));
            }
            Contract.EndContractBlock();

            string fullsourceDirName = Path.GetFullPath(sourceDirName);
            string sourcePath        = EnsureTrailingDirectorySeparator(fullsourceDirName);

            if (PathInternal.IsDirectoryTooLong(sourcePath))
            {
                throw new PathTooLongException(SR.IO_PathTooLong);
            }

            string fulldestDirName = Path.GetFullPath(destDirName);
            string destPath        = EnsureTrailingDirectorySeparator(fulldestDirName);

            if (PathInternal.IsDirectoryTooLong(destPath))
            {
                throw new PathTooLongException(SR.IO_PathTooLong);
            }

            StringComparison pathComparison = PathInternal.StringComparison;

            if (string.Equals(sourcePath, destPath, pathComparison))
            {
                throw new IOException(SR.IO_SourceDestMustBeDifferent);
            }

            string sourceRoot      = Path.GetPathRoot(sourcePath);
            string destinationRoot = Path.GetPathRoot(destPath);

            if (!string.Equals(sourceRoot, destinationRoot, pathComparison))
            {
                throw new IOException(SR.IO_SourceDestMustHaveSameRoot);
            }

            // Windows will throw if the source file/directory doesn't exist, we preemptively check
            // to make sure our cross platform behavior matches NetFX behavior.
            if (!FileSystem.Current.DirectoryExists(fullsourceDirName) && !FileSystem.Current.FileExists(fullsourceDirName))
            {
                throw new DirectoryNotFoundException(SR.Format(SR.IO_PathNotFound_Path, fullsourceDirName));
            }

            if (FileSystem.Current.DirectoryExists(fulldestDirName))
            {
                throw new IOException(SR.Format(SR.IO_AlreadyExists_Name, fulldestDirName));
            }

            FileSystem.Current.MoveDirectory(fullsourceDirName, fulldestDirName);
        }
Exemple #18
0
        private static string NormalizePath(string path, bool fullCheck = true, bool expandShortPaths = true)
        {
            Debug.Assert(path != null, "path can't be null");

            bool isExtended = PathInternal.IsExtended(path);

            if (fullCheck)
            {
                // Embedded null characters are the only invalid character case we want to check up front.
                // This is because the nulls will signal the end of the string to Win32 and therefore have
                // unpredictable results. Other invalid characters we give a chance to be normalized out.
                if (path.IndexOf('\0') != -1)
                {
                    throw new ArgumentException(SR.Argument_InvalidPathChars, "path");
                }

                // Toss out paths with colons that aren't a valid drive specifier.
                // Cannot start with a colon and can only be of the form "C:" or "\\?\C:".
                // (Note that we used to explicitly check "http:" and "file:"- these are caught by this check now.)
                int startIndex = PathInternal.PathStartSkip(path) + 2;
                if (isExtended)
                {
                    startIndex += PathInternal.ExtendedPathPrefix.Length;
                }

                if ((path.Length > 0 && path[0] == VolumeSeparatorChar) ||
                    (path.Length >= startIndex && path[startIndex - 1] == VolumeSeparatorChar && !PathInternal.IsValidDriveChar(path[startIndex - 2])) ||
                    (path.Length > startIndex && path.IndexOf(VolumeSeparatorChar, startIndex) != -1))
                {
                    throw new NotSupportedException(SR.Argument_PathFormatNotSupported);
                }
            }

            if (isExtended)
            {
                return(NormalizeExtendedPath(path, fullCheck));
            }
            else
            {
                return(NormalizeStandardPath(path, fullCheck, expandShortPaths));
            }
        }
Exemple #19
0
        private static string NormalizePath(
            string path, bool fullCheck,
            int maxPathLength, bool expandShortPaths) // ignored on Unix
        {
            Debug.Assert(path != null);

            if (path.Length == 0)
            {
                throw new ArgumentException(SR.Arg_PathIllegal);
            }

            if (fullCheck)
            {
                PathInternal.CheckInvalidPathChars(path);

                // Expand with current directory if necessary
                if (!IsPathRooted(path))
                {
                    path = Combine(Interop.libc.getcwd(), path);
                }
            }

            // Remove "//", "/./", and "/../" from the path.  We would ideally use realpath
            // to do this, but it resolves symlinks, requires that the file actually exist,
            // and turns it into a full path, which we only want if fullCheck is true.
            // Instead, we do the normalization manually, 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 = StringBuilderCache.Acquire(path.Length);
            int componentCharCount = 0;

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

                if (PathInternal.IsDirectorySeparator(c) && i + 1 < path.Length)
                {
                    componentCharCount = 0;

                    // 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;
                    }
                }

                if (++componentCharCount > MaxComponentLength)
                {
                    throw new PathTooLongException(SR.IO_PathTooLong);
                }
                sb.Append(c);
            }

            Debug.Assert(sb.Length < path.Length || sb.ToString() == path,
                         "Either we've removed characters, or the string should be unmodified from the input path.");

            if (sb.Length > MaxPath)
            {
                throw new PathTooLongException(SR.IO_PathTooLong);
            }

            string result =
                sb.Length == 0 ? (fullCheck ? DirectorySeparatorCharAsString : string.Empty) :
                sb.Length == path.Length ? path :
                sb.ToString();

            StringBuilderCache.Release(sb);
            return(result);
        }
Exemple #20
0
        /// <summary>
        /// Checks for known bad extended paths (paths that start with \\?\)
        /// </summary>
        /// <param name="fullCheck">Check for invalid characters if true.</param>
        /// <returns>'true' if the path passes validity checks.</returns>
        private static bool ValidateExtendedPath(string path, bool fullCheck)
        {
            if (path.Length == PathInternal.ExtendedPathPrefix.Length)
            {
                // Effectively empty and therefore invalid
                return(false);
            }

            if (path.StartsWith(PathInternal.UncExtendedPathPrefix, StringComparison.Ordinal))
            {
                // UNC specific checks
                if (path.Length == PathInternal.UncExtendedPathPrefix.Length || path[PathInternal.UncExtendedPathPrefix.Length] == DirectorySeparatorChar)
                {
                    // Effectively empty and therefore invalid (\\?\UNC\ or \\?\UNC\\)
                    return(false);
                }

                int serverShareSeparator = path.IndexOf(DirectorySeparatorChar, PathInternal.UncExtendedPathPrefix.Length);
                if (serverShareSeparator == -1 || serverShareSeparator == path.Length - 1)
                {
                    // Need at least a Server\Share
                    return(false);
                }
            }

            // Segments can't be empty "\\" or contain *just* "." or ".."
            char twoBack = '?';
            char oneBack = DirectorySeparatorChar;
            char currentCharacter;
            bool periodSegment = false;

            for (int i = PathInternal.ExtendedPathPrefix.Length; i < path.Length; i++)
            {
                currentCharacter = path[i];
                switch (currentCharacter)
                {
                case '\\':
                    if (oneBack == DirectorySeparatorChar || periodSegment)
                    {
                        throw new ArgumentException(SR.Arg_PathIllegal);
                    }
                    periodSegment = false;
                    break;

                case '.':
                    periodSegment = (oneBack == DirectorySeparatorChar || (twoBack == DirectorySeparatorChar && oneBack == '.'));
                    break;

                default:
                    periodSegment = false;
                    break;
                }

                twoBack = oneBack;
                oneBack = currentCharacter;
            }

            if (periodSegment)
            {
                return(false);
            }

            if (fullCheck)
            {
                // Look for illegal path characters.
                PathInternal.CheckInvalidPathChars(path);
            }

            return(true);
        }
Exemple #21
0
 private static bool IsDosUnc(StringBuffer buffer)
 {
     return(!PathInternal.IsDevice(buffer) && buffer.Length > 1 && buffer[0] == '\\' && buffer[1] == '\\');
 }
        public static void CreateDirectory(string fullPath)
        {
            // NOTE: This logic is primarily just carried forward from Win32FileSystem.CreateDirectory.

            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))
            {
                length--;
            }

            // For paths that are only // or ///
            if (length == 2 && PathInternal.IsDirectorySeparator(fullPath[1]))
            {
                throw new IOException(SR.Format(SR.IO_CannotCreateDirectory, fullPath));
            }

            // We can save a bunch of work if the directory we want to create already exists.
            if (DirectoryExists(fullPath))
            {
                return;
            }

            // Attempt to figure out which directories don't exist, and only create the ones we need.
            bool           somepathexists = false;
            Stack <string> stackDir       = new Stack <string>();
            int            lengthRoot     = PathInternal.GetRootLength(fullPath);

            if (length > lengthRoot)
            {
                int i = length - 1;
                while (i >= lengthRoot && !somepathexists)
                {
                    string dir = fullPath.Substring(0, i + 1);
                    if (!DirectoryExists(dir)) // Create only the ones missing
                    {
                        stackDir.Push(dir);
                    }
                    else
                    {
                        somepathexists = true;
                    }

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

            int count = stackDir.Count;

            if (count == 0 && !somepathexists)
            {
                string root = Directory.InternalGetDirectoryRoot(fullPath);
                if (!DirectoryExists(root))
                {
                    throw Interop.GetExceptionForIoErrno(Interop.Error.ENOENT.Info(), fullPath, isDirectory: true);
                }
                return;
            }

            // Create all the directories
            int result = 0;

            Interop.ErrorInfo firstError  = default(Interop.ErrorInfo);
            string            errorString = fullPath;

            while (stackDir.Count > 0)
            {
                string name = stackDir.Pop();

                // The mkdir command uses 0777 by default (it'll be AND'd with the process umask internally).
                // We do the same.
                result = Interop.Sys.MkDir(name, (int)Interop.Sys.Permissions.Mask);
                if (result < 0 && firstError.Error == 0)
                {
                    Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();

                    // While we tried to avoid creating directories that don't
                    // exist above, there are a few cases that can fail, e.g.
                    // a race condition where another process or thread creates
                    // the directory first, or there's a file at the location.
                    if (errorInfo.Error != Interop.Error.EEXIST)
                    {
                        firstError = errorInfo;
                    }
                    else if (FileExists(name) || (!DirectoryExists(name, out errorInfo) && errorInfo.Error == Interop.Error.EACCES))
                    {
                        // 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.
                        firstError  = errorInfo;
                        errorString = name;
                    }
                }
            }

            // Only throw an exception if creating the exact directory we wanted failed to work correctly.
            if (result < 0 && firstError.Error != 0)
            {
                throw Interop.GetExceptionForIoErrno(firstError, errorString, isDirectory: true);
            }
        }
Exemple #23
0
        private static string TryExpandShortFileName(StringBuffer outputBuffer, string originalPath)
        {
            // We guarantee we'll expand short names for paths that only partially exist. As such, we need to find the part of the path that actually does exist. To
            // avoid allocating like crazy we'll create only one input array and modify the contents with embedded nulls.

            Debug.Assert(!PathInternal.IsPartiallyQualified(outputBuffer), "should have resolved by now");

            // We'll have one of a few cases by now (the normalized path will have already:
            //
            //  1. Dos path (C:\)
            //  2. Dos UNC (\\Server\Share)
            //  3. Dos device path (\\.\C:\, \\?\C:\)
            //
            // We want to put the extended syntax on the front if it doesn't already have it, which may mean switching from \\.\.
            //
            // Note that we will never get \??\ here as GetFullPathName() does not recognize \??\ and will return it as C:\??\ (or whatever the current drive is).

            uint rootLength = PathInternal.GetRootLength(outputBuffer);
            bool isDevice   = PathInternal.IsDevice(outputBuffer);

            StringBuffer inputBuffer    = null;
            bool         isDosUnc       = false;
            uint         rootDifference = 0;
            bool         wasDotDevice   = false;

            // Add the extended prefix before expanding to allow growth over MAX_PATH
            if (isDevice)
            {
                // We have one of the following (\\?\ or \\.\)
                inputBuffer = new StringBuffer();
                inputBuffer.Append(outputBuffer);

                if (outputBuffer[2] == '.')
                {
                    wasDotDevice   = true;
                    inputBuffer[2] = '?';
                }
            }
            else
            {
                isDosUnc       = IsDosUnc(outputBuffer);
                rootDifference = GetInputBuffer(outputBuffer, isDosUnc, out inputBuffer);
            }

            rootLength += rootDifference;
            uint inputLength = inputBuffer.Length;

            bool success    = false;
            uint foundIndex = inputBuffer.Length - 1;

            while (!success)
            {
                uint result = Interop.mincore.GetLongPathNameW(inputBuffer.GetHandle(), outputBuffer.GetHandle(), outputBuffer.CharCapacity);

                // Replace any temporary null we added
                if (inputBuffer[foundIndex] == '\0')
                {
                    inputBuffer[foundIndex] = '\\';
                }

                if (result == 0)
                {
                    // Look to see if we couldn't find the file
                    int error = Marshal.GetLastWin32Error();
                    if (error != Interop.mincore.Errors.ERROR_FILE_NOT_FOUND && error != Interop.mincore.Errors.ERROR_PATH_NOT_FOUND)
                    {
                        // Some other failure, give up
                        break;
                    }

                    // We couldn't find the path at the given index, start looking further back in the string.
                    foundIndex--;

                    for (; foundIndex > rootLength && inputBuffer[foundIndex] != '\\'; foundIndex--)
                    {
                        ;
                    }
                    if (foundIndex == rootLength)
                    {
                        // Can't trim the path back any further
                        break;
                    }
                    else
                    {
                        // Temporarily set a null in the string to get Windows to look further up the path
                        inputBuffer[foundIndex] = '\0';
                    }
                }
                else if (result > outputBuffer.CharCapacity)
                {
                    // Not enough space. The result count for this API does not include the null terminator.
                    outputBuffer.EnsureCharCapacity(result);
                    result = Interop.mincore.GetLongPathNameW(inputBuffer.GetHandle(), outputBuffer.GetHandle(), outputBuffer.CharCapacity);
                }
                else
                {
                    // Found the path
                    success             = true;
                    outputBuffer.Length = result;
                    if (foundIndex < inputLength - 1)
                    {
                        // It was a partial find, put the non-existent part of the path back
                        outputBuffer.Append(inputBuffer, foundIndex, inputBuffer.Length - foundIndex);
                    }
                }
            }

            // Strip out the prefix and return the string
            StringBuffer bufferToUse = success ? outputBuffer : inputBuffer;

            // Switch back from \\?\ to \\.\ if necessary
            if (wasDotDevice)
            {
                bufferToUse[2] = '.';
            }

            string returnValue = null;

            int newLength = (int)(bufferToUse.Length - rootDifference);

            if (isDosUnc)
            {
                // Need to go from \\?\UNC\ to \\?\UN\\
                bufferToUse[PathInternal.UncExtendedPrefixLength - PathInternal.UncPrefixLength] = '\\';
            }

            // We now need to strip out any added characters at the front of the string
            if (bufferToUse.SubstringEquals(originalPath, rootDifference, newLength))
            {
                // Use the original path to avoid allocating
                returnValue = originalPath;
            }
            else
            {
                returnValue = bufferToUse.Substring(rootDifference, newLength);
            }

            inputBuffer.Dispose();
            return(returnValue);
        }
        public override 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 InternalExists 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 && PathHelpers.EndsInDirectorySeparator(fullPath))
            {
                length--;
            }

            int lengthRoot = PathInternal.GetRootLength(fullPath);

            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.  InternalExists
                    // 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 (File.InternalExists(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);
            }
        }
Exemple #25
0
        /// <summary>
        /// Normalize the given path.
        /// </summary>
        /// <remarks>
        /// Normalizes via Win32 GetFullPathName(). It will also trim all "typical" whitespace at the end of the path (see s_trimEndChars). Will also trim initial
        /// spaces if the path is determined to be rooted.
        ///
        /// Note that invalid characters will be checked after the path is normalized, which could remove bad characters. (C:\|\..\a.txt -- C:\a.txt)
        /// </remarks>
        /// <param name="path">Path to normalize</param>
        /// <param name="checkInvalidCharacters">True to check for invalid characters</param>
        /// <param name="expandShortPaths">Attempt to expand short paths if true</param>
        /// <exception cref="ArgumentException">Thrown if the path is an illegal UNC (does not contain a full server/share) or contains illegal characters.</exception>
        /// <exception cref="PathTooLongException">Thrown if the path or a path segment exceeds the filesystem limits.</exception>
        /// <exception cref="FileNotFoundException">Thrown if Windows returns ERROR_FILE_NOT_FOUND. (See Win32Marshal.GetExceptionForWin32Error)</exception>
        /// <exception cref="DirectoryNotFoundException">Thrown if Windows returns ERROR_PATH_NOT_FOUND. (See Win32Marshal.GetExceptionForWin32Error)</exception>
        /// <exception cref="UnauthorizedAccessException">Thrown if Windows returns ERROR_ACCESS_DENIED. (See Win32Marshal.GetExceptionForWin32Error)</exception>
        /// <exception cref="IOException">Thrown if Windows returns an error that doesn't map to the above. (See Win32Marshal.GetExceptionForWin32Error)</exception>
        /// <returns>Normalized path</returns>
        internal static string Normalize(string path, bool checkInvalidCharacters, bool expandShortPaths)
        {
            // Get the full path
            StringBuffer fullPath = t_fullPathBuffer ?? (t_fullPathBuffer = new StringBuffer(PathInternal.MaxShortPath));

            try
            {
                GetFullPathName(path, fullPath);

                // Trim whitespace off the end of the string. Win32 normalization trims only U+0020.
                fullPath.TrimEnd(s_trimEndChars);

                if (fullPath.Length >= PathInternal.MaxLongPath)
                {
                    // Fullpath is genuinely too long
                    throw new PathTooLongException(SR.IO_PathTooLong);
                }

                // Checking path validity used to happen before getting the full path name. To avoid additional input allocation
                // (to trim trailing whitespace) we now do it after the Win32 call. This will allow legitimate paths through that
                // used to get kicked back (notably segments with invalid characters might get removed via "..").
                //
                // There is no way that GetLongPath can invalidate the path so we'll do this (cheaper) check before we attempt to
                // expand short file names.

                // Scan the path for:
                //
                //  - Illegal path characters.
                //  - Invalid UNC paths like \\, \\server, \\server\.
                //  - Segments that are too long (over MaxComponentLength)

                // As the path could be > 30K, we'll combine the validity scan. None of these checks are performed by the Win32
                // GetFullPathName() API.

                bool possibleShortPath = false;
                bool foundTilde        = false;

                // We can get UNCs as device paths through this code (e.g. \\.\UNC\), we won't validate them as there isn't
                // an easy way to normalize without extensive cost (we'd have to hunt down the canonical name for any device
                // path that contains UNC or  to see if the path was doing something like \\.\GLOBALROOT\Device\Mup\,
                // \\.\GLOBAL\UNC\, \\.\GLOBALROOT\GLOBAL??\UNC\, etc.
                bool  specialPath    = fullPath.Length > 1 && fullPath[0] == '\\' && fullPath[1] == '\\';
                bool  isDevice       = PathInternal.IsDevice(fullPath);
                bool  possibleBadUnc = specialPath && !isDevice;
                uint  index          = specialPath ? 2u : 0;
                uint  lastSeparator  = specialPath ? 1u : 0;
                uint  segmentLength;
                char *start = fullPath.CharPointer;
                char  current;

                while (index < fullPath.Length)
                {
                    current = start[index];

                    // Try to skip deeper analysis. '?' and higher are valid/ignorable except for '\', '|', and '~'
                    if (current < '?' || current == '\\' || current == '|' || current == '~')
                    {
                        switch (current)
                        {
                        case '|':
                        case '>':
                        case '<':
                        case '\"':
                            if (checkInvalidCharacters)
                            {
                                throw new ArgumentException(SR.Argument_InvalidPathChars);
                            }
                            foundTilde = false;
                            break;

                        case '~':
                            foundTilde = true;
                            break;

                        case '\\':
                            segmentLength = index - lastSeparator - 1;
                            if (segmentLength > (uint)PathInternal.MaxComponentLength)
                            {
                                throw new PathTooLongException(SR.IO_PathTooLong + fullPath.ToString());
                            }
                            lastSeparator = index;

                            if (foundTilde)
                            {
                                if (segmentLength <= MaxShortName)
                                {
                                    // Possibly a short path.
                                    possibleShortPath = true;
                                }

                                foundTilde = false;
                            }

                            if (possibleBadUnc)
                            {
                                // If we're at the end of the path and this is the first separator, we're missing the share.
                                // Otherwise we're good, so ignore UNC tracking from here.
                                if (index == fullPath.Length - 1)
                                {
                                    throw new ArgumentException(SR.Arg_PathIllegalUNC);
                                }
                                else
                                {
                                    possibleBadUnc = false;
                                }
                            }

                            break;

                        default:
                            if (checkInvalidCharacters && current < ' ')
                            {
                                throw new ArgumentException(SR.Argument_InvalidPathChars, nameof(path));
                            }
                            break;
                        }
                    }

                    index++;
                }

                if (possibleBadUnc)
                {
                    throw new ArgumentException(SR.Arg_PathIllegalUNC);
                }

                segmentLength = fullPath.Length - lastSeparator - 1;
                if (segmentLength > (uint)PathInternal.MaxComponentLength)
                {
                    throw new PathTooLongException(SR.IO_PathTooLong);
                }

                if (foundTilde && segmentLength <= MaxShortName)
                {
                    possibleShortPath = true;
                }

                // Check for a short filename path and try and expand it. Technically you don't need to have a tilde for a short name, but
                // this is how we've always done this. This expansion is costly so we'll continue to let other short paths slide.
                if (expandShortPaths && possibleShortPath)
                {
                    return(TryExpandShortFileName(fullPath, originalPath: path));
                }
                else
                {
                    if (fullPath.Length == (uint)path.Length && fullPath.StartsWith(path))
                    {
                        // If we have the exact same string we were passed in, don't bother to allocate another string from the StringBuilder.
                        return(path);
                    }
                    else
                    {
                        return(fullPath.ToString());
                    }
                }
            }
            finally
            {
                // Clear the buffer
                fullPath.Free();
            }
        }
Exemple #26
0
 public static char[] GetInvalidPathChars()
 {
     return(PathInternal.GetInvalidPathChars());
 }
Exemple #27
0
        // Expands the given path to a fully qualified path.
        public static string GetFullPath(string path)
        {
            if (path == null)
            {
                throw new ArgumentNullException(nameof(path));
            }

            // Embedded null characters are the only invalid character case we want to check up front.
            // This is because the nulls will signal the end of the string to Win32 and therefore have
            // unpredictable results. Other invalid characters we give a chance to be normalized out.
            if (path.IndexOf('\0') != -1)
            {
                throw new ArgumentException(SR.Argument_InvalidPathChars, nameof(path));
            }

            if (PathInternal.IsExtended(path))
            {
                // We can't really know what is valid for all cases of extended paths.
                //
                //  - object names can include other characters as well (':', '/', etc.)
                //  - even file objects have different rules (pipe names can contain most characters)
                //
                // As such we will do no further analysis of extended paths to avoid blocking known and unknown
                // scenarios as well as minimizing compat breaks should we block now and need to unblock later.
                return(path);
            }

            bool isDevice = PathInternal.IsDevice(path);

            if (!isDevice)
            {
                // Toss out paths with colons that aren't a valid drive specifier.
                // Cannot start with a colon and can only be of the form "C:".
                // (Note that we used to explicitly check "http:" and "file:"- these are caught by this check now.)
                int startIndex = PathInternal.PathStartSkip(path);

                // Move past the colon
                startIndex += 2;

                if ((path.Length > 0 && path[0] == PathInternal.VolumeSeparatorChar) ||
                    (path.Length >= startIndex && path[startIndex - 1] == PathInternal.VolumeSeparatorChar && !PathInternal.IsValidDriveChar(path[startIndex - 2])) ||
                    (path.Length > startIndex && path.IndexOf(PathInternal.VolumeSeparatorChar, startIndex) != -1))
                {
                    throw new NotSupportedException(SR.Argument_PathFormatNotSupported);
                }
            }

            // Technically this doesn't matter but we used to throw for this case
            if (string.IsNullOrWhiteSpace(path))
            {
                throw new ArgumentException(SR.Arg_PathIllegal);
            }

            // We don't want to check invalid characters for device format- see comments for extended above
            string fullPath = PathHelper.Normalize(path, checkInvalidCharacters: !isDevice, expandShortPaths: true);

            if (!isDevice)
            {
                // Emulate FileIOPermissions checks, retained for compatibility (normal invalid characters have already been checked)
                if (PathInternal.HasWildCardCharacters(fullPath))
                {
                    throw new ArgumentException(SR.Argument_InvalidPathChars, nameof(path));
                }
            }

            return(fullPath);
        }
Exemple #28
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>
        private static string RemoveRelativeSegments(string path, int skip = 0)
        {
            bool 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 = StringBuilderCache.Acquire(path.Length);

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

            int componentCharCount = 0;

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

                if (PathInternal.IsDirectorySeparator(c) && i + 1 < path.Length)
                {
                    componentCharCount = 0;

                    // 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;
                    }
                }

                if (++componentCharCount > PathInternal.MaxComponentLength)
                {
                    throw new PathTooLongException(SR.IO_PathTooLong);
                }

                // 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(StringBuilderCache.GetStringAndRelease(sb));
            }
            else
            {
                // We haven't changed the source path, return the original
                StringBuilderCache.Release(sb);
                return(path);
            }
        }
Exemple #29
0
        private unsafe static void InternalCreateDirectory(string fullPath, string path, object dirSecurityObj)
        {
            DirectorySecurity directorySecurity = (DirectorySecurity)dirSecurityObj;
            int num = fullPath.Length;

            if (num >= 2 && Path.IsDirectorySeparator(fullPath[num - 1]))
            {
                num--;
            }
            int rootLength = LongPath.GetRootLength(fullPath);

            if (num == 2 && Path.IsDirectorySeparator(fullPath[1]))
            {
                throw new IOException(Environment.GetResourceString("IO.IO_CannotCreateDirectory", new object[]
                {
                    path
                }));
            }
            List <string> list = new List <string>();
            bool          flag = false;

            if (num > rootLength)
            {
                int num2 = num - 1;
                while (num2 >= rootLength && !flag)
                {
                    string text = fullPath.Substring(0, num2 + 1);
                    if (!LongPathDirectory.InternalExists(text))
                    {
                        list.Add(text);
                    }
                    else
                    {
                        flag = true;
                    }
                    while (num2 > rootLength && fullPath[num2] != Path.DirectorySeparatorChar && fullPath[num2] != Path.AltDirectorySeparatorChar)
                    {
                        num2--;
                    }
                    num2--;
                }
            }
            int count = list.Count;

            if (list.Count != 0 && !CodeAccessSecurityEngine.QuickCheckForAllDemands())
            {
                string[] array = new string[list.Count];
                list.CopyTo(array, 0);
                for (int i = 0; i < array.Length; i++)
                {
                    string[] array2 = array;
                    int      num3   = i;
                    array2[num3] += "\\.";
                }
                AccessControlActions control = (directorySecurity == null) ? AccessControlActions.None : AccessControlActions.Change;
                FileIOPermission.QuickDemand(FileIOPermissionAccess.Write, control, array, false, false);
            }
            Win32Native.SECURITY_ATTRIBUTES security_ATTRIBUTES = null;
            if (directorySecurity != null)
            {
                security_ATTRIBUTES         = new Win32Native.SECURITY_ATTRIBUTES();
                security_ATTRIBUTES.nLength = Marshal.SizeOf <Win32Native.SECURITY_ATTRIBUTES>(security_ATTRIBUTES);
                byte[] securityDescriptorBinaryForm = directorySecurity.GetSecurityDescriptorBinaryForm();
                byte * ptr = stackalloc byte[checked (unchecked ((UIntPtr)securityDescriptorBinaryForm.Length) * 1)];
                Buffer.Memcpy(ptr, 0, securityDescriptorBinaryForm, 0, securityDescriptorBinaryForm.Length);
                security_ATTRIBUTES.pSecurityDescriptor = ptr;
            }
            bool   flag2         = true;
            int    num4          = 0;
            string maybeFullPath = path;

            while (list.Count > 0)
            {
                string text2 = list[list.Count - 1];
                list.RemoveAt(list.Count - 1);
                if (text2.Length >= 32767)
                {
                    throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong"));
                }
                flag2 = Win32Native.CreateDirectory(PathInternal.EnsureExtendedPrefix(text2), security_ATTRIBUTES);
                if (!flag2 && num4 == 0)
                {
                    int lastWin32Error = Marshal.GetLastWin32Error();
                    if (lastWin32Error != 183)
                    {
                        num4 = lastWin32Error;
                    }
                    else if (LongPathFile.InternalExists(text2) || (!LongPathDirectory.InternalExists(text2, out lastWin32Error) && lastWin32Error == 5))
                    {
                        num4 = lastWin32Error;
                        try
                        {
                            FileIOPermission.QuickDemand(FileIOPermissionAccess.PathDiscovery, LongPathDirectory.GetDemandDir(text2, true), false, false);
                            maybeFullPath = text2;
                        }
                        catch (SecurityException)
                        {
                        }
                    }
                }
            }
            if (count == 0 && !flag)
            {
                string path2 = LongPathDirectory.InternalGetDirectoryRoot(fullPath);
                if (!LongPathDirectory.InternalExists(path2))
                {
                    __Error.WinIOError(3, LongPathDirectory.InternalGetDirectoryRoot(path));
                }
                return;
            }
            if (!flag2 && num4 != 0)
            {
                __Error.WinIOError(num4, maybeFullPath);
            }
        }
 InlineData(PathInternal.ExtendedPathPrefix, PathInternal.ExtendedPathPrefix)
        /// <summary>
        /// Normalize the path and check for bad characters or other invalid syntax.
        /// </summary>
        /// <remarks>
        /// The legacy NormalizePath
        /// </remarks>
        private static string NormalizeAndValidatePath(string path)
        {
            Debug.Assert(path != null, "path can't be null");

            // Embedded null characters are the only invalid character case we want to check up front.
            // This is because the nulls will signal the end of the string to Win32 and therefore have
            // unpredictable results. Other invalid characters we give a chance to be normalized out.
            if (path.IndexOf('\0') != -1)
            {
                throw new ArgumentException(SR.Argument_InvalidPathChars, "path");
            }

            // Toss out paths with colons that aren't a valid drive specifier.
            // Cannot start with a colon and can only be of the form "C:" or "\\?\C:".
            // (Note that we used to explicitly check "http:" and "file:"- these are caught by this check now.)
            int  startIndex = PathInternal.PathStartSkip(path);
            bool isExtended = path.Length >= PathInternal.ExtendedPathPrefix.Length + startIndex &&
                              path.IndexOf(PathInternal.ExtendedPathPrefix, startIndex, PathInternal.ExtendedPathPrefix.Length, StringComparison.Ordinal) >= 0;

            if (isExtended)
            {
                startIndex += PathInternal.ExtendedPathPrefix.Length;
            }

            // Move past the colon
            startIndex += 2;

            if ((path.Length > 0 && path[0] == VolumeSeparatorChar) ||
                (path.Length >= startIndex && path[startIndex - 1] == VolumeSeparatorChar && !PathInternal.IsValidDriveChar(path[startIndex - 2])) ||
                (path.Length > startIndex && path.IndexOf(VolumeSeparatorChar, startIndex) != -1))
            {
                throw new NotSupportedException(SR.Argument_PathFormatNotSupported);
            }

            if (isExtended)
            {
                // If the path is in extended syntax, we don't need to normalize, but we still do some basic validity checks
                if (!ValidateExtendedPath(path))
                {
                    throw new ArgumentException(SR.Arg_PathIllegal);
                }

                // \\?\GLOBALROOT gives access to devices out of the scope of the current user, we
                // don't want to allow this for security reasons.
                // https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247.aspx#nt_namespaces
                if (path.StartsWith(@"\\?\globalroot", StringComparison.OrdinalIgnoreCase))
                {
                    throw new ArgumentException(SR.Arg_PathGlobalRoot);
                }


                // Look for illegal path characters.
                PathInternal.CheckInvalidPathChars(path);

                return(path);
            }
            else
            {
                // Technically this doesn't matter but we used to throw for this case
                if (String.IsNullOrWhiteSpace(path))
                {
                    throw new ArgumentException(SR.Arg_PathIllegal);
                }

                return(PathHelper.Normalize(path, checkInvalidCharacters: true, expandShortPaths: true));
            }
        }