Exemple #1
0
        public static string GetExtension(string path)
        {
            if (path == null)
            {
                return(null);
            }

            PathInternal.CheckInvalidPathChars(path);
            int length = path.Length;

            for (int i = length - 1; i >= 0; i--)
            {
                char ch = path[i];
                if (ch == '.')
                {
                    if (i != length - 1)
                    {
                        return(path.Substring(i, length - i));
                    }
                    else
                    {
                        return(string.Empty);
                    }
                }
                if (IsDirectoryOrVolumeSeparator(ch))
                {
                    break;
                }
            }
            return(string.Empty);
        }
Exemple #2
0
        // Changes the extension of a file path. The path parameter
        // specifies a file path, and the extension parameter
        // specifies a file extension (with a leading period, such as
        // ".exe" or ".cs").
        //
        // The function returns a file path with the same root, directory, and base
        // name parts as path, but with the file extension changed to
        // the specified extension. If path is null, the function
        // returns null. If path does not contain a file extension,
        // the new file extension is appended to the path. If extension
        // is null, any existing extension is removed from path.
        public static string ChangeExtension(string path, string extension)
        {
            if (path != null)
            {
                PathInternal.CheckInvalidPathChars(path);

                string s = path;
                for (int i = path.Length - 1; i >= 0; i--)
                {
                    char ch = path[i];
                    if (ch == '.')
                    {
                        s = path.Substring(0, i);
                        break;
                    }
                    if (IsDirectoryOrVolumeSeparator(ch))
                    {
                        break;
                    }
                }

                if (extension != null && path.Length != 0)
                {
                    s = (extension.Length == 0 || extension[0] != '.') ?
                        s + "." + extension :
                        s + extension;
                }

                return(s);
            }
            return(null);
        }
        private Win32FileSystemEnumerableIterator(string fullPath, string normalizedSearchPath, string searchCriteria, string userPath, SearchOption searchOption, SearchResultHandler <TSource> resultHandler)
        {
            _fullPath             = fullPath;
            _normalizedSearchPath = normalizedSearchPath;
            _searchCriteria       = searchCriteria;
            _resultHandler        = resultHandler;
            _userPath             = userPath;
            _searchOption         = searchOption;

            if (searchCriteria != null)
            {
                PathInternal.CheckInvalidPathChars(fullPath);
                if (PathInternal.HasWildCardCharacters(fullPath))
                {
                    throw new ArgumentException(SR.Argument_InvalidPathChars, nameof(fullPath));
                }

                _searchData = new PathPair(userPath, normalizedSearchPath);
                CommonInit();
            }
            else
            {
                _empty = true;
            }
        }
Exemple #4
0
        // Returns the directory path of a file path. This method effectively
        // removes the last element of the given file path, i.e. it returns a
        // string consisting of all characters up to but not including the last
        // backslash ("\") in the file path. The returned value is null if the file
        // path is null or if the file path denotes a root (such as "\", "C:", or
        // "\\server\share").
        public static string GetDirectoryName(string path)
        {
            if (path == null)
            {
                return(null);
            }

            if (PathInternal.IsEffectivelyEmpty(path))
            {
                throw new ArgumentException(SR.Arg_PathEmpty, nameof(path));
            }

            PathInternal.CheckInvalidPathChars(path);
            path = PathInternal.NormalizeDirectorySeparators(path);
            int root = PathInternal.GetRootLength(path);

            int i = path.Length;

            if (i > root)
            {
                while (i > root && !PathInternal.IsDirectorySeparator(path[--i]))
                {
                    ;
                }
                return(path.Substring(0, i));
            }

            return(null);
        }
Exemple #5
0
        // Returns the directory path of a file path. This method effectively
        // removes the last element of the given file path, i.e. it returns a
        // string consisting of all characters up to but not including the last
        // backslash ("\") in the file path. The returned value is null if the file
        // path is null or if the file path denotes a root (such as "\", "C:", or
        // "\\server\share").
        public static string GetDirectoryName(string path)
        {
            if (path != null)
            {
                PathInternal.CheckInvalidPathChars(path);
                path = PathInternal.NormalizeDirectorySeparators(path);
                int root = PathInternal.GetRootLength(path);

                int i = path.Length;
                if (i > root)
                {
                    i = path.Length;
                    if (i == root)
                    {
                        return(null);
                    }
                    while (i > root && !PathInternal.IsDirectorySeparator(path[--i]))
                    {
                        ;
                    }
                    return(path.Substring(0, i));
                }
            }
            return(null);
        }
Exemple #6
0
        // Expands the given path to a fully qualified path. 
        public static string GetFullPath(string path)
        {
            if (path == null)
                throw new ArgumentNullException(nameof(path));

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

            PathInternal.CheckInvalidPathChars(path);

            // Expand with current directory if necessary
            if (!IsPathRooted(path))
            {
                path = Combine(Interop.Sys.GetCwd(), 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.
            string collapsedString = RemoveRelativeSegments(path);

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

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

            string result = collapsedString.Length == 0 ? DirectorySeparatorCharAsString : collapsedString;

            return result;
        }
Exemple #7
0
        public static bool IsPathRooted(string path)
        {
            if (path == null)
                return false;

            PathInternal.CheckInvalidPathChars(path);
            return path.Length > 0 && path[0] == DirectorySeparatorChar;
        }
Exemple #8
0
        // Returns the root portion of the given path. The resulting string
        // consists of those rightmost characters of the path that constitute the
        // root of the path. Possible patterns for the resulting string are: An
        // empty string (a relative path on the current drive), "\" (an absolute
        // path on the current drive), "X:" (a relative path on a given drive,
        // where X is the drive letter), "X:\" (an absolute path on a given drive),
        // and "\\server\share" (a UNC path for a given server and share name).
        // The resulting string is null if path is null.
        public static string GetPathRoot(string path)
        {
            if (path == null)
            {
                return(null);
            }
            PathInternal.CheckInvalidPathChars(path);
            int pathRoot = PathInternal.GetRootLength(path);

            return(pathRoot <= 0 ? String.Empty : path.Substring(0, pathRoot));
        }
Exemple #9
0
        // Returns the root portion of the given path. The resulting string
        // consists of those rightmost characters of the path that constitute the
        // root of the path. Possible patterns for the resulting string are: An
        // empty string (a relative path on the current drive), "\" (an absolute
        // path on the current drive), "X:" (a relative path on a given drive,
        // where X is the drive letter), "X:\" (an absolute path on a given drive),
        // and "\\server\share" (a UNC path for a given server and share name).
        // The resulting string is null if path is null.
        public static string GetPathRoot(string path)
        {
            if (path == null)
            {
                return(null);
            }
            PathInternal.CheckInvalidPathChars(path);
            int pathRoot = PathInternal.GetRootLength(path);

            // Need to return the normalized directory separator
            return(pathRoot <= 0 ? string.Empty : path.Substring(0, pathRoot).Replace(AltDirectorySeparatorChar, DirectorySeparatorChar));
        }
Exemple #10
0
        public static string Combine(string path1, string path2)
        {
            if (path1 == null || path2 == null)
            {
                throw new ArgumentNullException((path1 == null) ? nameof(path1) : nameof(path2));
            }

            PathInternal.CheckInvalidPathChars(path1);
            PathInternal.CheckInvalidPathChars(path2);

            return(CombineNoChecks(path1, path2));
        }
Exemple #11
0
        public static string Combine(string path1, string path2)
        {
            if (path1 == null || path2 == null)
            {
                throw new ArgumentNullException((path1 == null) ? "path1" : "path2");
            }
            Contract.EndContractBlock();

            PathInternal.CheckInvalidPathChars(path1);
            PathInternal.CheckInvalidPathChars(path2);

            return(CombineNoChecks(path1, path2));
        }
Exemple #12
0
        // Tests if the given path contains a root. A path is considered rooted
        // if it starts with a backslash ("\") or a drive letter and a colon (":").
        public static bool IsPathRooted(string path)
        {
            if (path != null)
            {
                PathInternal.CheckInvalidPathChars(path);

                int length = path.Length;
                if ((length >= 1 && PathInternal.IsDirectorySeparator(path[0])) ||
                    (length >= 2 && path[1] == VolumeSeparatorChar))
                {
                    return(true);
                }
            }
            return(false);
        }
Exemple #13
0
        public static string Combine(string path1, string path2, string path3, string path4)
        {
            if (path1 == null || path2 == null || path3 == null || path4 == null)
            {
                throw new ArgumentNullException((path1 == null) ? nameof(path1) : (path2 == null) ? nameof(path2) : (path3 == null) ? nameof(path3) : nameof(path4));
            }
            Contract.EndContractBlock();

            PathInternal.CheckInvalidPathChars(path1);
            PathInternal.CheckInvalidPathChars(path2);
            PathInternal.CheckInvalidPathChars(path3);
            PathInternal.CheckInvalidPathChars(path4);

            return(CombineNoChecks(path1, path2, path3, path4));
        }
Exemple #14
0
        public static string GetFullPath(string path)
        {
            string fullPath = GetFullPathInternal(path);

            // Emulate FileIOPermissions checks, retained for compatibility
            PathInternal.CheckInvalidPathChars(fullPath, true);

            int startIndex = PathInternal.IsExtended(fullPath) ? PathInternal.ExtendedPathPrefix.Length + 2 : 2;

            if (fullPath.Length > startIndex && fullPath.IndexOf(':', startIndex) != -1)
            {
                throw new NotSupportedException(SR.Argument_PathFormatNotSupported);
            }

            return(fullPath);
        }
        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);
            }
        }
Exemple #16
0
        /// <summary>
        /// Returns the start index of the filename
        /// in the given path, or 0 if no directory
        /// or volume separator is found.
        /// </summary>
        /// <param name="path">The path in which to find the index of the filename.</param>
        /// <remarks>
        /// This method returns path.Length for
        /// inputs like "/usr/foo/" on Unix. As such,
        /// it is not safe for being used to index
        /// the string without additional verification.
        /// </remarks>
        internal static int FindFileNameIndex(string path)
        {
            Debug.Assert(path != null);
            PathInternal.CheckInvalidPathChars(path);

            for (int i = path.Length - 1; i >= 0; i--)
            {
                char ch = path[i];
                if (IsDirectoryOrVolumeSeparator(ch))
                {
                    return(i + 1);
                }
            }

            return(0); // the whole path is the filename
        }
Exemple #17
0
        [System.Security.SecuritySafeCritical]    // auto-generated
        public DriveInfo(String driveName)
        {
            if (driveName == null)
            {
                throw new ArgumentNullException(nameof(driveName));
            }
            Contract.EndContractBlock();
            if (driveName.Length == 1)
            {
                _name = driveName + ":\\";
            }
            else
            {
                // GetPathRoot does not check all invalid characters
                PathInternal.CheckInvalidPathChars(driveName);
                _name = Path.GetPathRoot(driveName);
                // Disallow null or empty drive letters and UNC paths
                if (_name == null || _name.Length == 0 || _name.StartsWith("\\\\", StringComparison.Ordinal))
                {
                    throw new ArgumentException(Environment.GetResourceString("Arg_MustBeDriveLetterOrRootDir"));
                }
            }
            // We want to normalize to have a trailing backslash so we don't have two equivalent forms and
            // because some Win32 API don't work without it.
            if (_name.Length == 2 && _name[1] == ':')
            {
                _name = _name + "\\";
            }

            // Now verify that the drive letter could be a real drive name.
            // On Windows this means it's between A and Z, ignoring case.
            // On a Unix platform, perhaps this should be a device name with
            // a partition like /dev/hdc0, or possibly a mount point.
            char letter = driveName[0];

            if (!((letter >= 'A' && letter <= 'Z') || (letter >= 'a' && letter <= 'z')))
            {
                throw new ArgumentException(Environment.GetResourceString("Arg_MustBeDriveLetterOrRootDir"));
            }

            // Now do a security check.
            String demandPath = _name + '.';

            new FileIOPermission(FileIOPermissionAccess.PathDiscovery, demandPath).Demand();
        }
Exemple #18
0
        public static string GetFileName(string path)
        {
            if (path != null)
            {
                PathInternal.CheckInvalidPathChars(path);

                int length = path.Length;
                for (int i = length - 1; i >= 0; i--)
                {
                    char ch = path[i];
                    if (IsDirectoryOrVolumeSeparator(ch))
                    {
                        return(path.Substring(i + 1, length - i - 1));
                    }
                }
            }
            return(path);
        }
Exemple #19
0
        // Returns the root portion of the given path. The resulting string
        // consists of those rightmost characters of the path that constitute the
        // root of the path. Possible patterns for the resulting string are: An
        // empty string (a relative path on the current drive), "\" (an absolute
        // path on the current drive), "X:" (a relative path on a given drive,
        // where X is the drive letter), "X:\" (an absolute path on a given drive),
        // and "\\server\share" (a UNC path for a given server and share name).
        // The resulting string is null if path is null. If the path is empty or
        // only contains whitespace characters an ArgumentException gets thrown.
        public static string GetPathRoot(string path)
        {
            if (path == null)
            {
                return(null);
            }
            if (string.IsNullOrWhiteSpace(path))
            {
                throw new ArgumentException(SR.Arg_PathEmpty, nameof(path));
            }

            PathInternal.CheckInvalidPathChars(path);

            // Need to return the normalized directory separator
            path = PathInternal.NormalizeDirectorySeparators(path);

            int pathRoot = PathInternal.GetRootLength(path);

            return(pathRoot <= 0 ? string.Empty : path.Substring(0, pathRoot));
        }
Exemple #20
0
        public static bool HasExtension(string path)
        {
            if (path != null)
            {
                PathInternal.CheckInvalidPathChars(path);

                for (int i = path.Length - 1; i >= 0; i--)
                {
                    char ch = path[i];
                    if (ch == '.')
                    {
                        return(i != path.Length - 1);
                    }
                    if (PathInternal.IsDirectoryOrVolumeSeparator(ch))
                    {
                        break;
                    }
                }
            }
            return(false);
        }
        private Win32FileSystemEnumerableIterator(string fullPath, string normalizedSearchPath, string searchCriteria, string userPath, SearchOption searchOption, SearchResultHandler <TSource> resultHandler)
        {
            _fullPath             = fullPath;
            _normalizedSearchPath = normalizedSearchPath;
            _searchCriteria       = searchCriteria;
            _resultHandler        = resultHandler;
            _userPath             = userPath;
            _searchOption         = searchOption;

            if (searchCriteria != null)
            {
                PathInternal.CheckInvalidPathChars(fullPath, true);

                _searchData = new PathPair(userPath, normalizedSearchPath);
                CommonInit();
            }
            else
            {
                _empty = true;
            }
        }
Exemple #22
0
        public static string Combine(params string[] paths)
        {
            if (paths == null)
            {
                throw new ArgumentNullException(nameof(paths));
            }
            Contract.EndContractBlock();

            int finalSize      = 0;
            int firstComponent = 0;

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

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

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

                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 (!PathInternal.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 (!PathInternal.IsDirectoryOrVolumeSeparator(ch))
                    {
                        finalPath.Append(PathInternal.DirectorySeparatorChar);
                    }

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

            return(StringBuilderCache.GetStringAndRelease(finalPath));
        }
Exemple #23
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 #24
0
 internal static int GetRootLength(string path)
 {
     PathInternal.CheckInvalidPathChars(path);
     return(path.Length > 0 && IsDirectorySeparator(path[0]) ? 1 : 0);
 }
Exemple #25
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);
        }
        /// <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));
            }
        }
Exemple #27
0
        [System.Security.SecurityCritical]  // auto-generated
        private unsafe static string NormalizePath(string path, bool fullCheck, int maxPathLength, bool expandShortPaths)
        {
            Contract.Requires(path != null, "path can't be null");

            // If the path is in extended syntax, we don't need to normalize, but we still do some basic validity checks
            if (PathInternal.IsExtended(path))
            {
                if (!ValidateExtendedPath(path, fullCheck))
                {
                    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);
                }

                return(path);
            }

            // If we're doing a full path check, trim whitespace and look for
            // illegal path characters.
            if (fullCheck)
            {
                // Trim whitespace off the end of the string.
                // Win32 normalization trims only U+0020.
                path = path.TrimEnd(TrimEndChars);

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

            int index = 0;

            // We prefer to allocate on the stack for workingset/perf gain. If the
            // starting path is less than MaxPath then we can stackalloc; otherwise we'll
            // use a StringBuilder (PathHelper does this under the hood). The latter may
            // happen in 2 cases:
            // 1. Starting path is greater than MaxPath but it normalizes down to MaxPath.
            // This is relevant for paths containing escape sequences. In this case, we
            // attempt to normalize down to MaxPath, but the caller pays a perf penalty
            // since StringBuilder is used.
            // 2. IsolatedStorage, which supports paths longer than MaxPath (value given
            // by maxPathLength.
            PathHelper newBuffer = null;

            if (path.Length + 1 <= MaxPath)
            {
                char *m_arrayPtr = stackalloc char[MaxPath];
                newBuffer = new PathHelper(m_arrayPtr, MaxPath);
            }
            else
            {
                newBuffer = new PathHelper(path.Length + MaxPath, maxPathLength);
            }

            uint numSpaces = 0;
            uint numDots   = 0;
            bool fixupDirectorySeparator = false;
            // Number of significant chars other than potentially suppressible
            // dots and spaces since the last directory or volume separator char
            uint numSigChars = 0;
            int  lastSigChar = -1; // Index of last significant character.
            // Whether this segment of the path (not the complete path) started
            // with a volume separator char.  Reject "c:...".
            bool startedWithVolumeSeparator = false;
            bool firstSegment = true;
            int  lastDirectorySeparatorPos = 0;

            bool mightBeShortFileName = false;

            // LEGACY: This code is here for backwards compatibility reasons. It
            // ensures that \\foo.cs\bar.cs stays \\foo.cs\bar.cs instead of being
            // turned into \foo.cs\bar.cs.
            if (path.Length > 0 && PathInternal.IsDirectorySeparator(path[0]))
            {
                newBuffer.Append('\\');
                index++;
                lastSigChar = 0;
            }

            // Normalize the string, stripping out redundant dots, spaces, and
            // slashes.
            while (index < path.Length)
            {
                char currentChar = path[index];

                // We handle both directory separators and dots specially.  For
                // directory separators, we consume consecutive appearances.
                // For dots, we consume all dots beyond the second in
                // succession.  All other characters are added as is.  In
                // addition we consume all spaces after the last other char
                // in a directory name up until the directory separator.

                if (PathInternal.IsDirectorySeparator(currentChar))
                {
                    // If we have a path like "123.../foo", remove the trailing dots.
                    // However, if we found "c:\temp\..\bar" or "c:\temp\...\bar", don't.
                    // Also remove trailing spaces from both files & directory names.
                    // This was agreed on with the OS team to fix undeletable directory
                    // names ending in spaces.

                    // If we saw a '\' as the previous last significant character and
                    // are simply going to write out dots, suppress them.
                    // If we only contain dots and slashes though, only allow
                    // a string like [dot]+ [space]*.  Ignore everything else.
                    // Legal: "\.. \", "\...\", "\. \"
                    // Illegal: "\.. .\", "\. .\", "\ .\"
                    if (numSigChars == 0)
                    {
                        // Dot and space handling
                        if (numDots > 0)
                        {
                            // Look for ".[space]*" or "..[space]*"
                            int start = lastSigChar + 1;
                            if (path[start] != '.')
                            {
                                throw new ArgumentException(SR.Arg_PathIllegal);
                            }

                            // Only allow "[dot]+[space]*", and normalize the
                            // legal ones to "." or ".."
                            if (numDots >= 2)
                            {
                                // Reject "C:..."
                                if (startedWithVolumeSeparator && numDots > 2)
                                {
                                    throw new ArgumentException(SR.Arg_PathIllegal);
                                }

                                if (path[start + 1] == '.')
                                {
                                    // Search for a space in the middle of the
                                    // dots and throw
                                    for (int i = start + 2; i < start + numDots; i++)
                                    {
                                        if (path[i] != '.')
                                        {
                                            throw new ArgumentException(SR.Arg_PathIllegal);
                                        }
                                    }

                                    numDots = 2;
                                }
                                else
                                {
                                    if (numDots > 1)
                                    {
                                        throw new ArgumentException(SR.Arg_PathIllegal);
                                    }
                                    numDots = 1;
                                }
                            }

                            if (numDots == 2)
                            {
                                newBuffer.Append('.');
                            }

                            newBuffer.Append('.');
                            fixupDirectorySeparator = false;
                            // Continue in this case, potentially writing out '\'.
                        }

                        if (numSpaces > 0 && firstSegment)
                        {
                            // Handle strings like " \\server\share".
                            if (index + 1 < path.Length && PathInternal.IsDirectorySeparator(path[index + 1]))
                            {
                                newBuffer.Append(DirectorySeparatorChar);
                            }
                        }
                    }
                    numDots   = 0;
                    numSpaces = 0;  // Suppress trailing spaces

                    if (!fixupDirectorySeparator)
                    {
                        fixupDirectorySeparator = true;
                        newBuffer.Append(DirectorySeparatorChar);
                    }
                    numSigChars = 0;
                    lastSigChar = index;
                    startedWithVolumeSeparator = false;
                    firstSegment = false;

                    // For short file names, we must try to expand each of them as
                    // soon as possible.  We need to allow people to specify a file
                    // name that doesn't exist using a path with short file names
                    // in it, such as this for a temp file we're trying to create:
                    // C:\DOCUME~1\USERNA~1.RED\LOCALS~1\Temp\bg3ylpzp
                    // We could try doing this afterwards piece by piece, but it's
                    // probably a lot simpler to do it here.
                    if (mightBeShortFileName)
                    {
                        newBuffer.TryExpandShortFileName();
                        mightBeShortFileName = false;
                    }

                    int thisPos = newBuffer.Length - 1;
                    if (thisPos - lastDirectorySeparatorPos > MaxComponentLength)
                    {
                        throw new PathTooLongException(SR.IO_PathTooLong);
                    }
                    lastDirectorySeparatorPos = thisPos;
                } // if (Found directory separator)
                else if (currentChar == '.')
                {
                    // Reduce only multiple .'s only after slash to 2 dots. For
                    // instance a...b is a valid file name.
                    numDots++;
                    // Don't flush out non-terminal spaces here, because they may in
                    // the end not be significant.  Turn "c:\ . .\foo" -> "c:\foo"
                    // which is the conclusion of removing trailing dots & spaces,
                    // as well as folding multiple '\' characters.
                }
                else if (currentChar == ' ')
                {
                    numSpaces++;
                }
                else
                {  // Normal character logic
                    if (currentChar == '~' && expandShortPaths)
                    {
                        mightBeShortFileName = true;
                    }

                    fixupDirectorySeparator = false;

                    // To reject strings like "C:...\foo" and "C  :\foo"
                    if (firstSegment && currentChar == VolumeSeparatorChar)
                    {
                        // Only accept "C:", not "c :" or ":"
                        // Get a drive letter or ' ' if index is 0.
                        char driveLetter = (index > 0) ? path[index - 1] : ' ';
                        bool validPath   = ((numDots == 0) && (numSigChars >= 1) && (driveLetter != ' '));
                        if (!validPath)
                        {
                            throw new ArgumentException(SR.Arg_PathIllegal);
                        }

                        startedWithVolumeSeparator = true;
                        // We need special logic to make " c:" work, we should not fix paths like "  foo::$DATA"
                        if (numSigChars > 1)
                        {                       // Common case, simply do nothing
                            int spaceCount = 0; // How many spaces did we write out, numSpaces has already been reset.
                            while ((spaceCount < newBuffer.Length) && newBuffer[spaceCount] == ' ')
                            {
                                spaceCount++;
                            }
                            if (numSigChars - spaceCount == 1)
                            {
                                //Safe to update stack ptr directly
                                newBuffer.Length = 0;
                                newBuffer.Append(driveLetter); // Overwrite spaces, we need a special case to not break "  foo" as a relative path.
                            }
                        }
                        numSigChars = 0;
                    }
                    else
                    {
                        numSigChars += 1 + numDots + numSpaces;
                    }

                    // Copy any spaces & dots since the last significant character
                    // to here.  Note we only counted the number of dots & spaces,
                    // and don't know what order they're in.  Hence the copy.
                    if (numDots > 0 || numSpaces > 0)
                    {
                        int numCharsToCopy = (lastSigChar >= 0) ? index - lastSigChar - 1 : index;
                        if (numCharsToCopy > 0)
                        {
                            for (int i = 0; i < numCharsToCopy; i++)
                            {
                                newBuffer.Append(path[lastSigChar + 1 + i]);
                            }
                        }
                        numDots   = 0;
                        numSpaces = 0;
                    }

                    newBuffer.Append(currentChar);
                    lastSigChar = index;
                }

                index++;
            } // end while

            if (newBuffer.Length - 1 - lastDirectorySeparatorPos > MaxComponentLength)
            {
                throw new PathTooLongException(SR.IO_PathTooLong);
            }

            // Drop any trailing dots and spaces from file & directory names, EXCEPT
            // we MUST make sure that "C:\foo\.." is correctly handled.
            // Also handle "C:\foo\." -> "C:\foo", while "C:\." -> "C:\"
            if (numSigChars == 0)
            {
                if (numDots > 0)
                {
                    // Look for ".[space]*" or "..[space]*"
                    int start = lastSigChar + 1;
                    if (path[start] != '.')
                    {
                        throw new ArgumentException(SR.Arg_PathIllegal);
                    }

                    // Only allow "[dot]+[space]*", and normalize the
                    // legal ones to "." or ".."
                    if (numDots >= 2)
                    {
                        // Reject "C:..."
                        if (startedWithVolumeSeparator && numDots > 2)
                        {
                            throw new ArgumentException(SR.Arg_PathIllegal);
                        }

                        if (path[start + 1] == '.')
                        {
                            // Search for a space in the middle of the
                            // dots and throw
                            for (int i = start + 2; i < start + numDots; i++)
                            {
                                if (path[i] != '.')
                                {
                                    throw new ArgumentException(SR.Arg_PathIllegal);
                                }
                            }

                            numDots = 2;
                        }
                        else
                        {
                            if (numDots > 1)
                            {
                                throw new ArgumentException(SR.Arg_PathIllegal);
                            }
                            numDots = 1;
                        }
                    }

                    if (numDots == 2)
                    {
                        newBuffer.Append('.');
                    }

                    newBuffer.Append('.');
                }
            } // if (numSigChars == 0)

            // If we ended up eating all the characters, bail out.
            if (newBuffer.Length == 0)
            {
                throw new ArgumentException(SR.Arg_PathIllegal);
            }

            // Disallow URL's here.  Some of our other Win32 API calls will reject
            // them later, so we might be better off rejecting them here.
            // Note we've probably turned them into "file:\D:\foo.tmp" by now.
            // But for compatibility, ensure that callers that aren't doing a
            // full check aren't rejected here.
            if (fullCheck)
            {
                if (newBuffer.OrdinalStartsWith("http:", false) ||
                    newBuffer.OrdinalStartsWith("file:", false))
                {
                    throw new ArgumentException(SR.Argument_PathUriFormatNotSupported);
                }
            }

            // If the last part of the path (file or directory name) had a tilde,
            // expand that too.
            if (mightBeShortFileName)
            {
                newBuffer.TryExpandShortFileName();
            }

            // Call the Win32 API to do the final canonicalization step.
            int result = 1;

            if (fullCheck)
            {
                // NOTE: Win32 GetFullPathName requires the input buffer to be big enough to fit the initial
                // path which is a concat of CWD and the relative path, this can be of an arbitrary
                // size and could be > MaxPath (which becomes an artificial limit at this point),
                // even though the final normalized path after fixing up the relative path syntax
                // might be well within the MaxPath restriction. For ex,
                // "c:\SomeReallyLongDirName(thinkGreaterThan_MAXPATH)\..\foo.txt" which actually requires a
                // buffer well with in the MaxPath as the normalized path is just "c:\foo.txt"
                // This buffer requirement seems wrong, it could be a bug or a perf optimization
                // like returning required buffer length quickly or avoid stratch buffer etc.
                // Either way we need to workaround it here...

                // Ideally we would get the required buffer length first by calling GetFullPathName
                // once without the buffer and use that in the later call but this doesn't always work
                // due to Win32 GetFullPathName bug. For instance, in Win2k, when the path we are trying to
                // fully qualify is a single letter name (such as "a", "1", ",") GetFullPathName
                // fails to return the right buffer size (i.e, resulting in insufficient buffer).
                // To workaround this bug we will start with MaxPath buffer and grow it once if the
                // return value is > MaxPath.

                result = newBuffer.GetFullPathName();

                // If we called GetFullPathName with something like "foo" and our
                // command window was in short file name mode (ie, by running edlin or
                // DOS versions of grep, etc), we might have gotten back a short file
                // name.  So, check to see if we need to expand it.
                mightBeShortFileName = false;
                for (int i = 0; i < newBuffer.Length && !mightBeShortFileName; i++)
                {
                    if (newBuffer[i] == '~' && expandShortPaths)
                    {
                        mightBeShortFileName = true;
                    }
                }

                if (mightBeShortFileName)
                {
                    bool r = newBuffer.TryExpandShortFileName();
                    // Consider how the path "Doesn'tExist" would expand.  If
                    // we add in the current directory, it too will need to be
                    // fully expanded, which doesn't happen if we use a file
                    // name that doesn't exist.
                    if (!r)
                    {
                        int lastSlash = -1;

                        for (int i = newBuffer.Length - 1; i >= 0; i--)
                        {
                            if (newBuffer[i] == DirectorySeparatorChar)
                            {
                                lastSlash = i;
                                break;
                            }
                        }

                        if (lastSlash >= 0)
                        {
                            // This bounds check is for safe memcpy but we should never get this far
                            if (newBuffer.Length >= maxPathLength)
                            {
                                throw new PathTooLongException(SR.IO_PathTooLong);
                            }

                            int lenSavedName = newBuffer.Length - lastSlash - 1;
                            Debug.Assert(lastSlash < newBuffer.Length, "path unexpectedly ended in a '\'");

                            newBuffer.Fixup(lenSavedName, lastSlash);
                        }
                    }
                }
            }

            if (result != 0)
            {
                /* Throw an ArgumentException for paths like \\, \\server, \\server\
                 * This check can only be properly done after normalizing, so
                 \\foo\.. will be properly rejected. */
                if (newBuffer.Length > 1 && newBuffer[0] == '\\' && newBuffer[1] == '\\')
                {
                    int startIndex = 2;
                    while (startIndex < result)
                    {
                        if (newBuffer[startIndex] == '\\')
                        {
                            startIndex++;
                            break;
                        }
                        else
                        {
                            startIndex++;
                        }
                    }
                    if (startIndex == result)
                    {
                        throw new ArgumentException(SR.Arg_PathIllegalUNC);
                    }
                }
            }

            // Check our result and form the managed string as necessary.
            if (newBuffer.Length >= maxPathLength)
            {
                throw new PathTooLongException(SR.IO_PathTooLong);
            }

            if (result == 0)
            {
                int errorCode = Marshal.GetLastWin32Error();
                if (errorCode == 0)
                {
                    errorCode = Interop.mincore.Errors.ERROR_BAD_PATHNAME;
                }
                throw Win32Marshal.GetExceptionForWin32Error(errorCode, path);
            }

            string returnVal = newBuffer.ToString();

            if (string.Equals(returnVal, path, StringComparison.Ordinal))
            {
                returnVal = path;
            }
            return(returnVal);
        }