Example #1
0
        /// <summary>
        /// Returns the volume name for dos, UNC and device paths.
        /// </summary>
        internal static ReadOnlySpan <char> GetVolumeName(ReadOnlySpan <char> path)
        {
            // 3 cases: UNC ("\\server\share"), Device ("\\?\C:\"), or Dos ("C:\")
            ReadOnlySpan <char> root = GetPathRoot(path);

            if (root.Length == 0)
            {
                return(root);
            }

            // Cut from "\\?\UNC\Server\Share" to "Server\Share"
            // Cut from  "\\Server\Share" to "Server\Share"
            int startOffset = GetUncRootLength(path);

            if (startOffset == -1)
            {
                if (PathInternal.IsDevice(path))
                {
                    startOffset = 4; // Cut from "\\?\C:\" to "C:"
                }
                else
                {
                    startOffset = 0; // e.g. "C:"
                }
            }

            ReadOnlySpan <char> pathToTrim = root.Slice(startOffset);

            return(Path.EndsInDirectorySeparator(pathToTrim) ? pathToTrim.Slice(0, pathToTrim.Length - 1) : pathToTrim);
        }
Example #2
0
        internal unsafe static bool HasWildCardCharacters(string path)
        {
            // Question mark is part of dos device syntax so we have to skip if we are
            int startIndex = PathInternal.IsDevice(path) ? ExtendedPathPrefix.Length : 0;

            return(path.IndexOfAny(s_wildcardChars, startIndex) >= 0);
        }
Example #3
0
        /// <summary>
        /// Returns the volume name for dos, UNC and device paths.
        /// </summary>
        internal static ReadOnlySpan <char> GetVolumeName(ReadOnlySpan <char> path)
        {
            // 3 cases: UNC ("\\server\share"), Device ("\\?\C:\"), or Dos ("C:\")
            ReadOnlySpan <char> root = GetPathRoot(path);

            if (root.Length == 0)
            {
                return(root);
            }

            int offset = GetUncRootLength(path);

            if (offset >= 0)
            {
                // Cut from "\\?\UNC\Server\Share" to "Server\Share"
                // Cut from  "\\Server\Share" to "Server\Share"
                return(TrimEndingDirectorySeparator(root.Slice(offset)));
            }
            else if (PathInternal.IsDevice(path))
            {
                return(TrimEndingDirectorySeparator(root.Slice(4))); // Cut from "\\?\C:\" to "C:"
            }

            return(TrimEndingDirectorySeparator(root)); // e.g. "C:"
        }
 internal static string EnsureExtendedPrefix(string path)
 {
     if (PathInternal.IsPartiallyQualified(path.AsSpan()) || PathInternal.IsDevice(path.AsSpan()))
     {
         return(path);
     }
     return(path.StartsWith("\\\\", StringComparison.OrdinalIgnoreCase) ? path.Insert(2, "?\\UNC\\") : "\\\\?\\" + path);
 }
        private static void VerifyPath(string path)
        {
            if (path != null)
            {
                path = path.Trim();

#if !PLATFORM_UNIX
                if (!PathInternal.IsDevice(path) && PathInternal.HasInvalidVolumeSeparator(path))
                {
                    throw new ArgumentException(Environment.GetResourceString("Argument_PathFormatNotSupported"));
                }
#endif

                PathInternal.CheckInvalidPathChars(path);
            }
        }
Example #6
0
        /// <summary>
        /// Returns offset as -1 if the path is not in Unc format, otherwise returns the root length.
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        internal static int GetUncRootLength(ReadOnlySpan <char> path)
        {
            bool isDevice = PathInternal.IsDevice(path);

            if (!isDevice && StringSpanHelpers.Equals(path.Slice(0, 2), @"\\"))
            {
                return(2);
            }
            else if (isDevice && path.Length >= 8 &&
                     (StringSpanHelpers.Equals(path.Slice(0, 8), PathInternal.UncExtendedPathPrefix) ||
                      StringSpanHelpers.Equals(path.Slice(5, 4), @"UNC\")))
            {
                return(8);
            }

            return(-1);
        }
Example #7
0
        /// <summary>
        /// Returns offset as -1 if the path is not in Unc format, otherwise returns the root length.
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        internal static int GetUncRootLength(ReadOnlySpan <char> path)
        {
            bool isDevice = PathInternal.IsDevice(path);

            if (!isDevice && path.Slice(0, 2).EqualsOrdinal(@"\\"))
            {
                return(2);
            }
            else if (isDevice && path.Length >= 8 &&
                     (path.Slice(0, 8).EqualsOrdinal(PathInternal.UncExtendedPathPrefix) ||
                      path.Slice(5, 4).EqualsOrdinal(@"UNC\")))
            {
                return(8);
            }

            return(-1);
        }
        internal static int GetRootLength(ReadOnlySpan <char> path)
        {
            int  length = path.Length;
            int  index  = 0;
            bool flag1  = PathInternal.IsDevice(path);
            bool flag2  = flag1 && PathInternal.IsDeviceUNC(path);

            if (!flag1 | flag2 && length > 0 && PathInternal.IsDirectorySeparator(path[0]))
            {
                if (flag2 || length > 1 && PathInternal.IsDirectorySeparator(path[1]))
                {
                    index = flag2 ? 8 : 2;
                    int num = 2;
                    while (index < length && (!PathInternal.IsDirectorySeparator(path[index]) || --num > 0))
                    {
                        ++index;
                    }
                }
                else
                {
                    index = 1;
                }
            }
            else if (flag1)
            {
                index = 4;
                while (index < length && !PathInternal.IsDirectorySeparator(path[index]))
                {
                    ++index;
                }
                if (index < length && index > 4 && PathInternal.IsDirectorySeparator(path[index]))
                {
                    ++index;
                }
            }
            else if (length >= 2 && path[1] == ':' && PathInternal.IsValidDriveChar(path[0]))
            {
                index = 2;
                if (length > 2 && PathInternal.IsDirectorySeparator(path[2]))
                {
                    ++index;
                }
            }
            return(index);
        }
Example #9
0
        public static string GetFullPath(string path, string basePath)
        {
            if (path == null)
            {
                throw new ArgumentNullException(nameof(path));
            }

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

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

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

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

            int    length       = path.Length;
            string combinedPath = null;

            if ((length >= 1 && PathInternal.IsDirectorySeparator(path[0])))
            {
                // Path is current drive rooted i.e. starts with \:
                // "\Foo" and "C:\Bar" => "C:\Foo"
                // "\Foo" and "\\?\C:\Bar" => "\\?\C:\Foo"
                combinedPath = Join(GetPathRoot(basePath.AsSpan()), path);
            }
            else if (length >= 2 && PathInternal.IsValidDriveChar(path[0]) && path[1] == PathInternal.VolumeSeparatorChar)
            {
                // Drive relative paths
                Debug.Assert(length == 2 || !PathInternal.IsDirectorySeparator(path[2]));

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

            // Device paths are normalized by definition, so passing something of this format
            // to GetFullPath() won't do anything by design. Additionally, GetFullPathName() in
            // Windows doesn't root them properly. As such we need to manually remove segments.
            return(PathInternal.IsDevice(combinedPath)
                ? RemoveRelativeSegments(combinedPath, PathInternal.GetRootLength(combinedPath))
                : GetFullPath(combinedPath));
        }
Example #10
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.Format(SR.Argument_PathFormatNotSupported_Path, path));
                }
            }

            // Technically this doesn't matter but we used to throw for this case
            if (PathInternal.IsEffectivelyEmpty(path))
            {
                throw new ArgumentException(SR.Arg_PathEmpty, nameof(path));
            }

            // 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);
        }
Example #11
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 = new StringBuffer(PathInternal.MaxShortPath);

            try
            {
                GetFullPathName(path, ref fullPath);

                // Trim whitespace off the end of the string. Win32 normalization trims only U+0020.
                fullPath.TrimEnd(PathInternal.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(ref fullPath);
                bool possibleBadUnc = specialPath && !isDevice;
                int  index          = specialPath ? 2 : 0;
                int  lastSeparator  = specialPath ? 1 : 0;
                int  segmentLength;
                char current;

                while (index < fullPath.Length)
                {
                    current = fullPath[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 > 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 > 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(ref fullPath, originalPath: path));
                }
                else
                {
                    if (fullPath.Length == 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();
            }
        }
Example #12
0
        private static string TryExpandShortFileName(ref 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(ref 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).

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

            StringBuffer inputBuffer = new StringBuffer(0);

            try
            {
                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 \\.\)
                    inputBuffer.Append(ref outputBuffer);

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

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

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

                while (!success)
                {
                    uint result = Interop.Kernel32.GetLongPathNameW(inputBuffer.UnderlyingArray, outputBuffer.UnderlyingArray, (uint)outputBuffer.Capacity);

                    // 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.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 && 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(checked ((int)result));
                        result = Interop.Kernel32.GetLongPathNameW(inputBuffer.UnderlyingArray, outputBuffer.UnderlyingArray, (uint)outputBuffer.Capacity);
                    }
                    else
                    {
                        // Found the path
                        success             = true;
                        outputBuffer.Length = checked ((int)result);
                        if (foundIndex < inputLength - 1)
                        {
                            // It was a partial find, put the non-existent part of the path back
                            outputBuffer.Append(ref inputBuffer, foundIndex, inputBuffer.Length - foundIndex);
                        }
                    }
                }

                // Strip out the prefix and return the string
                ref StringBuffer bufferToUse = ref Choose(success, ref outputBuffer, ref 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);
                }

                return(returnValue);
            }
Example #13
0
 private static bool IsDosUnc(ref StringBuffer buffer)
 {
     return(!PathInternal.IsDevice(ref buffer) && buffer.Length > 1 && buffer[0] == '\\' && buffer[1] == '\\');
 }
Example #14
0
        internal 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 a lot 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.
            var 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;

            while (!success)
            {
                uint result = Interop.Kernel32.GetLongPathNameW(
                    ref inputBuilder.GetPinnableReference(terminate: true), 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));
                }
                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(foundIndex, inputBuilder.Length - foundIndex));
                    }
                }
            }

            // 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);
        internal unsafe static string Normalize(string path, uint maxPathLength, bool checkInvalidCharacters, bool expandShortPaths)
        {
            StringBuffer stringBuffer;

            if ((stringBuffer = LongPathHelper.t_fullPathBuffer) == null)
            {
                stringBuffer = (LongPathHelper.t_fullPathBuffer = new StringBuffer(260U));
            }
            StringBuffer stringBuffer2 = stringBuffer;
            string       result;

            try
            {
                LongPathHelper.GetFullPathName(path, stringBuffer2);
                stringBuffer2.TrimEnd(LongPathHelper.s_trimEndChars);
                if (stringBuffer2.Length >= maxPathLength)
                {
                    throw new PathTooLongException();
                }
                bool  flag        = false;
                bool  flag2       = false;
                bool  flag3       = stringBuffer2.Length > 1U && stringBuffer2[0U] == '\\' && stringBuffer2[1U] == '\\';
                bool  flag4       = PathInternal.IsDevice(stringBuffer2);
                bool  flag5       = flag3 && !flag4;
                uint  num         = flag3 ? 2U : 0U;
                uint  num2        = flag3 ? 1U : 0U;
                char *charPointer = stringBuffer2.CharPointer;
                uint  num3;
                while (num < stringBuffer2.Length)
                {
                    char c = charPointer[(ulong)num * 2UL / 2UL];
                    if (c < '?' || c == '\\' || c == '|' || c == '~')
                    {
                        if (c <= '>')
                        {
                            if (c != '"' && c != '<' && c != '>')
                            {
                                goto IL_155;
                            }
                        }
                        else if (c != '\\')
                        {
                            if (c != '|')
                            {
                                if (c != '~')
                                {
                                    goto IL_155;
                                }
                                flag2 = true;
                                goto IL_16E;
                            }
                        }
                        else
                        {
                            num3 = num - num2 - 1U;
                            if (num3 > (uint)PathInternal.MaxComponentLength)
                            {
                                throw new PathTooLongException();
                            }
                            num2 = num;
                            if (flag2)
                            {
                                if (num3 <= 12U)
                                {
                                    flag = true;
                                }
                                flag2 = false;
                            }
                            if (!flag5)
                            {
                                goto IL_16E;
                            }
                            if (num == stringBuffer2.Length - 1U)
                            {
                                throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegalUNC"));
                            }
                            flag5 = false;
                            goto IL_16E;
                        }
                        if (checkInvalidCharacters)
                        {
                            throw new ArgumentException(Environment.GetResourceString("Argument_InvalidPathChars"));
                        }
                        flag2 = false;
                        goto IL_16E;
IL_155:
                        if (checkInvalidCharacters && c < ' ')
                        {
                            throw new ArgumentException(Environment.GetResourceString("Argument_InvalidPathChars"));
                        }
                    }
IL_16E:
                    num += 1U;
                }
                if (flag5)
                {
                    throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegalUNC"));
                }
                num3 = stringBuffer2.Length - num2 - 1U;
                if (num3 > (uint)PathInternal.MaxComponentLength)
                {
                    throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong"));
                }
                if (flag2 && num3 <= 12U)
                {
                    flag = true;
                }
                if (expandShortPaths && flag)
                {
                    result = LongPathHelper.TryExpandShortFileName(stringBuffer2, path);
                }
                else if (stringBuffer2.Length == (uint)path.Length && stringBuffer2.StartsWith(path))
                {
                    result = path;
                }
                else
                {
                    result = stringBuffer2.ToString();
                }
            }
            finally
            {
                stringBuffer2.Free();
            }
            return(result);
        }
 internal static bool IsDeviceUNC(ReadOnlySpan <char> path)
 {
     return(path.Length >= 8 && PathInternal.IsDevice(path) && (PathInternal.IsDirectorySeparator(path[7]) && path[4] == 'U') && path[5] == 'N' && path[6] == 'C');
 }
Example #17
0
        // Token: 0x06001953 RID: 6483 RVA: 0x0005437C File Offset: 0x0005257C
        internal static bool HasWildCardCharacters(string path)
        {
            int startIndex = AppContextSwitches.UseLegacyPathHandling ? 0 : (PathInternal.IsDevice(path) ? "\\\\?\\".Length : 0);

            return(PathInternal.AnyPathHasWildCardCharacters(path, startIndex));
        }
Example #18
0
 // Token: 0x06001951 RID: 6481 RVA: 0x00054344 File Offset: 0x00052544
 internal static bool HasIllegalCharacters(string path, bool checkAdditional = false)
 {
     return((AppContextSwitches.UseLegacyPathHandling || !PathInternal.IsDevice(path)) && PathInternal.AnyPathHasIllegalCharacters(path, checkAdditional));
 }
Example #19
0
        public static string GetFullPath(string path, string basePath)
        {
            if (path == null)
            {
                throw new ArgumentNullException(nameof(path));
            }

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

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

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

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

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

            int    length = path.Length;
            string combinedPath;

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

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

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

            return(PathInternal.IsDevice(combinedPath.AsSpan())
                ? PathInternal.RemoveRelativeSegments(combinedPath, PathInternal.GetRootLength(combinedPath.AsSpan()))
                : GetFullPathInternal(combinedPath));
        }
Example #20
0
        private static string TryExpandShortFileName(StringBuffer outputBuffer, string originalPath)
        {
            // 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 \\.\.

            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 \\.\)
                // We will never get \??\ here as GetFullPathName() does not recognize \??\ and will return it as C:\??\ (or whatever the current drive is).
                inputBuffer = new StringBuffer();
                inputBuffer.Append(outputBuffer);

                if (outputBuffer[2] == '.')
                {
                    wasDotDevice   = true;
                    inputBuffer[2] = '?';
                }
            }
            else
            {
                // \\Server\Share, but not \\.\ or \\?\.
                // We need to know this to be able to push \\?\UNC\ on if required
                isDosUnc       = outputBuffer.Length > 1 && outputBuffer[0] == '\\' && outputBuffer[1] == '\\' && !PathInternal.IsDevice(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 = Win32Native.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 != Win32Native.ERROR_FILE_NOT_FOUND && error != Win32Native.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 = Win32Native.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;

            if (wasDotDevice)
            {
                bufferToUse[2] = '.';
            }

            string returnValue = null;

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

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

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