diff --git a/TruePath.Tests/AbsolutePathTests.cs b/TruePath.Tests/AbsolutePathTests.cs index fa1cea3..0f989cb 100644 --- a/TruePath.Tests/AbsolutePathTests.cs +++ b/TruePath.Tests/AbsolutePathTests.cs @@ -19,18 +19,18 @@ public void ConstructionTest() [Fact] public void PathRootReturnsRoot() { - var root = new AbsolutePath(OperatingSystem.IsWindows() ? @"A:\" : "/"); - var path = root / "foo" / "bar"; + var root = new AbsolutePath(OperatingSystem.IsWindows() ? @"A:\" : "/"); + var path = root / "foo" / "bar"; - Assert.Equal(root, path.PathRoot()); + Assert.Equal(root, path.PathRoot()); } [Fact] public void PathRootOfRootReturnsItself() { - var root = new AbsolutePath(OperatingSystem.IsWindows() ? @"A:\" : "/"); + var root = new AbsolutePath(OperatingSystem.IsWindows() ? @"A:\" : "/"); - Assert.Equal(root, root.PathRoot()); + Assert.Equal(root, root.PathRoot()); } [Fact] @@ -261,6 +261,102 @@ public void PathIsNormalizedOnCreation() Assert.Equal(@"C:\Users\John Doe\Documents", absolutePath.Value); } + [Theory] + [InlineData(@"/.")] + [InlineData(@"/./.")] + [InlineData(@"/././.")] + public void ConstructorCreatesValidPathWithDotInUnix(string path) + { + if (OperatingSystem.IsWindows()) return; + const string expectedPath = @"/"; + + var absolutePath = new AbsolutePath(path); + + Assert.Equal(expectedPath, absolutePath.Value); + } + + [Theory] + [InlineData(@"/...")] + [InlineData(@"/..SomeFolder")] + [InlineData(@"/..00")] + [InlineData(@"/..#")] + public void ConstructorCreatesValidPathCorrectlyInUnix(string path) + { + if (OperatingSystem.IsWindows()) return; + + var absolutePath = new AbsolutePath(path); + + Assert.Equal(path, absolutePath.Value); + } + + [Theory] + [InlineData(@"/../")] + [InlineData(@"/../..")] + [InlineData(@"/../../SomeFolder")] + [InlineData(@"/../.SomeFolder")] + [InlineData(@"/../SomeFolder")] + [InlineData(@"/../1123")] + [InlineData(@"/../()")] + [InlineData(@"/./..")] + [InlineData(@"/./../.")] + public void ConstructorThrowsOnInvalidPathInUnix(string path) + { + if (OperatingSystem.IsWindows()) return; + string expectedMessage = $"Path \"{path}\" is not valid."; + + var ex = Assert.Throws(() => new AbsolutePath(path)); + + Assert.Equal(expectedMessage, ex.Message); + } + + [Theory] + [InlineData(@"C:\.")] + [InlineData(@"C:\.\.")] + [InlineData(@"C:\.\.\.")] + public void ConstructorCreatesValidPathWithDotInWindows(string path) + { + if (OperatingSystem.IsWindows() is false) return; + const string expectedPath = @"C:\"; + + var absolutePath = new AbsolutePath(path); + + Assert.Equal(expectedPath, absolutePath.Value); + } + + [Theory] + [InlineData(@"C:\...")] + [InlineData(@"C:\..SomeFolder")] + [InlineData(@"C:\..00")] + [InlineData(@"C:\..#")] + public void ConstructorCreatesValidPathCorrectlyInWindows(string path) + { + if (OperatingSystem.IsWindows() is false) return; + + var absolutePath = new AbsolutePath(path); + + Assert.Equal(path, absolutePath.Value); + } + + [Theory] + [InlineData(@"C:\..\")] + [InlineData(@"C:\..\..")] + [InlineData(@"C:\..\..SomeFolder")] + [InlineData(@"C:\..\.SomeFolder")] + [InlineData(@"C:\..\SomeFolder")] + [InlineData(@"C:\..\1123")] + [InlineData(@"C:\..\()")] + [InlineData(@"C:\.\..")] + [InlineData(@"C:\.\..\.")] + public void ConstructorThrowsOnInvalidPathInWindows(string path) + { + if (OperatingSystem.IsWindows() is false) return; + string expectedMessage = $"Path \"{path}\" is not valid."; + + var ex = Assert.Throws(() => new AbsolutePath(path)); + + Assert.Equal(expectedMessage, ex.Message); + } + [Fact] public void ConstructorThrowsOnNonRootedPath() { diff --git a/TruePath/AbsolutePath.cs b/TruePath/AbsolutePath.cs index 873df39..a5872d6 100644 --- a/TruePath/AbsolutePath.cs +++ b/TruePath/AbsolutePath.cs @@ -46,12 +46,24 @@ namespace TruePath; /// Path string to normalize. /// Flag indicating whether absoluteness of path should be checked /// Thrown if the passed string does not represent an absolute path.> + /// + /// Thrown if the passed string matches the dot-dot directory directly after root (e.g. C:/../ or /../) + /// > + private AbsolutePath(string value, bool checkAbsoluteness) { Underlying = new LocalPath(value); if (checkAbsoluteness && Underlying.IsAbsolute is false) throw new ArgumentException($"Path \"{value}\" is not absolute."); + + var pathRoot = Path.GetPathRoot(Underlying.Value)!; + var pathWithoutRoot = Underlying.Value[pathRoot.Length..]; + if (pathWithoutRoot.StartsWith("../") || pathWithoutRoot.StartsWith("..\\") || + pathWithoutRoot.Equals("..")) + { + throw new ArgumentException($"Path \"{value}\" is not valid."); + } } ///