diff --git a/src/System.IO.FileSystem/src/System/IO/DirectoryInfo.cs b/src/System.IO.FileSystem/src/System/IO/DirectoryInfo.cs index ecdda000903a..f07ee9ad87ff 100644 --- a/src/System.IO.FileSystem/src/System/IO/DirectoryInfo.cs +++ b/src/System.IO.FileSystem/src/System/IO/DirectoryInfo.cs @@ -61,17 +61,22 @@ public DirectoryInfo CreateSubdirectory(string path) if (Path.IsPathRooted(path)) throw new ArgumentException(SR.Arg_Path2IsRooted, nameof(path)); - string fullPath = Path.GetFullPath(Path.Combine(FullPath, path)); + string newPath = Path.GetFullPath(Path.Combine(FullPath, path)); - if (fullPath.Length < FullPath.Length - || (fullPath.Length > FullPath.Length && !PathInternal.IsDirectorySeparator(fullPath[FullPath.Length])) - || string.Compare(FullPath, 0, fullPath, 0, FullPath.Length, PathInternal.StringComparison) != 0) + ReadOnlySpan trimmedNewPath = PathInternal.TrimEndingDirectorySeparator(newPath.AsSpan()); + ReadOnlySpan trimmedCurrentPath = PathInternal.TrimEndingDirectorySeparator(FullPath.AsSpan()); + + // We want to make sure the requested directory is actually under the subdirectory. + if (trimmedNewPath.StartsWith(trimmedCurrentPath, PathInternal.StringComparison) + // Allow the exact same path, but prevent allowing "..\FooBar" through when the directory is "Foo" + && ((trimmedNewPath.Length == trimmedCurrentPath.Length) || PathInternal.IsDirectorySeparator(newPath[trimmedCurrentPath.Length]))) { - throw new ArgumentException(SR.Format(SR.Argument_InvalidSubPath, path, FullPath), nameof(path)); + FileSystem.CreateDirectory(newPath); + return new DirectoryInfo(newPath); } - FileSystem.CreateDirectory(fullPath); - return new DirectoryInfo(fullPath); + // We weren't nested + throw new ArgumentException(SR.Format(SR.Argument_InvalidSubPath, path, FullPath), nameof(path)); } public void Create() => FileSystem.CreateDirectory(FullPath); diff --git a/src/System.IO.FileSystem/tests/DirectoryInfo/CreateSubdirectory.cs b/src/System.IO.FileSystem/tests/DirectoryInfo/CreateSubdirectory.cs index 75af106c41a9..7ed2e27e4a25 100644 --- a/src/System.IO.FileSystem/tests/DirectoryInfo/CreateSubdirectory.cs +++ b/src/System.IO.FileSystem/tests/DirectoryInfo/CreateSubdirectory.cs @@ -89,17 +89,41 @@ public void SubDirectoryIsParentDirectory_ThrowsArgumentException() Assert.Throws(() => new DirectoryInfo(TestDirectory + "/path").CreateSubdirectory("../../path2")); } + [Fact] + [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] + public void SubdirectoryOverlappingName_ThrowsArgumentException() + { + // What we're looking for here is trying to create C:\FooBar under C:\Foo by passing "..\FooBar" + DirectoryInfo info = Directory.CreateDirectory(GetTestFilePath()); + + string overlappingName = ".." + Path.DirectorySeparatorChar + info.Name + "overlap"; + + Assert.Throws(() => info.CreateSubdirectory(overlappingName)); + + // Now try with an info with a trailing separator + info = new DirectoryInfo(info.FullName + Path.DirectorySeparatorChar); + Assert.Throws(() => info.CreateSubdirectory(overlappingName)); + } + [Theory, MemberData(nameof(ValidPathComponentNames))] public void ValidPathWithTrailingSlash(string component) { DirectoryInfo testDir = Directory.CreateDirectory(GetTestFilePath()); - string path = IOServices.AddTrailingSlashIfNeeded(component); + string path = component + Path.DirectorySeparatorChar; DirectoryInfo result = new DirectoryInfo(testDir.FullName).CreateSubdirectory(path); Assert.Equal(Path.Combine(testDir.FullName, path), result.FullName); Assert.True(Directory.Exists(result.FullName)); + + // Now try creating subdirectories when the directory info itself has a slash + testDir = Directory.CreateDirectory(GetTestFilePath() + Path.DirectorySeparatorChar); + + result = new DirectoryInfo(testDir.FullName).CreateSubdirectory(path); + + Assert.Equal(Path.Combine(testDir.FullName, path), result.FullName); + Assert.True(Directory.Exists(result.FullName)); } [Theory, @@ -114,6 +138,13 @@ public void ValidPathWithoutTrailingSlash(string component) Assert.Equal(Path.Combine(testDir.FullName, path), result.FullName); Assert.True(Directory.Exists(result.FullName)); + // Now try creating subdirectories when the directory info itself has a slash + testDir = Directory.CreateDirectory(GetTestFilePath() + Path.DirectorySeparatorChar); + + result = new DirectoryInfo(testDir.FullName).CreateSubdirectory(path); + + Assert.Equal(Path.Combine(testDir.FullName, path), result.FullName); + Assert.True(Directory.Exists(result.FullName)); } [Fact]