From c07759996fae51563a07d8034d978583b030a410 Mon Sep 17 00:00:00 2001 From: "N. Taylor Mullen" Date: Wed, 22 Mar 2017 12:32:34 -0700 Subject: [PATCH] Fix absolute path handling. - Normalize paths to be absolute and to also use forward slashes. - Updated our `EnsureValidPath` method to be `NormalizeAndEnsureValidPath`. - Added tests to validate new `NormalizeAndEnsureValidPath`. - Updated existing tests to react to `NormalizeAndEnsureValidPath` correctly. #1106 --- .../FileSystemRazorProject.cs | 37 ++++++++--- .../RazorProject.cs | 8 ++- .../FileSystemRazorProjectTest.cs | 61 +++++++++++++++++++ .../RazorProjectTest.cs | 23 +++++-- 4 files changed, 111 insertions(+), 18 deletions(-) diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/FileSystemRazorProject.cs b/src/Microsoft.AspNetCore.Razor.Evolution/FileSystemRazorProject.cs index 1d794919b..bf51f43d0 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/FileSystemRazorProject.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/FileSystemRazorProject.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; @@ -25,7 +24,7 @@ public FileSystemRazorProject(string root) throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(root)); } - Root = root.TrimEnd('/', '\\'); + Root = root.Replace('\\', '/').TrimEnd('/'); } /// @@ -36,10 +35,7 @@ public FileSystemRazorProject(string root) /// public override IEnumerable EnumerateItems(string basePath) { - EnsureValidPath(basePath); - - Debug.Assert(basePath.StartsWith("/")); - var absoluteBasePath = Path.Combine(Root, basePath.Substring(1)); + var absoluteBasePath = NormalizeAndEnsureValidPath(basePath); var directory = new DirectoryInfo(absoluteBasePath); if (!directory.Exists) @@ -53,17 +49,38 @@ public override IEnumerable EnumerateItems(string basePath) { var relativePath = file.FullName.Substring(absoluteBasePath.Length).Replace(Path.DirectorySeparatorChar, '/'); return new FileSystemRazorProjectItem(basePath, relativePath, file); - }); + }); } /// public override RazorProjectItem GetItem(string path) { - EnsureValidPath(path); + var absolutePath = NormalizeAndEnsureValidPath(path); - Debug.Assert(path.StartsWith("/")); - var absolutePath = Path.Combine(Root, path.Substring(1)); return new FileSystemRazorProjectItem("/", path, new FileInfo(absolutePath)); } + + protected override string NormalizeAndEnsureValidPath(string path) + { + if (string.IsNullOrEmpty(path)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(path)); + } + + var absolutePath = path; + if (!absolutePath.StartsWith(Root, StringComparison.OrdinalIgnoreCase)) + { + if (path[0] == '/' || path[0] == '\\') + { + path = path.Substring(1); + } + + absolutePath = Path.Combine(Root, path); + } + + absolutePath = absolutePath.Replace('\\', '/'); + + return absolutePath; + } } } diff --git a/src/Microsoft.AspNetCore.Razor.Evolution/RazorProject.cs b/src/Microsoft.AspNetCore.Razor.Evolution/RazorProject.cs index 536e25209..dbda49932 100644 --- a/src/Microsoft.AspNetCore.Razor.Evolution/RazorProject.cs +++ b/src/Microsoft.AspNetCore.Razor.Evolution/RazorProject.cs @@ -59,8 +59,8 @@ public IEnumerable FindHierarchicalItems(string path, string f /// public virtual IEnumerable FindHierarchicalItems(string basePath, string path, string fileName) { - EnsureValidPath(basePath); - EnsureValidPath(path); + basePath = NormalizeAndEnsureValidPath(basePath); + path = NormalizeAndEnsureValidPath(path); if (string.IsNullOrEmpty(fileName)) { throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(fileName)); @@ -109,7 +109,7 @@ public virtual IEnumerable FindHierarchicalItems(string basePa /// Performs validation for paths passed to methods of . /// /// The path to validate. - protected virtual void EnsureValidPath(string path) + protected virtual string NormalizeAndEnsureValidPath(string path) { if (string.IsNullOrEmpty(path)) { @@ -120,6 +120,8 @@ protected virtual void EnsureValidPath(string path) { throw new ArgumentException(Resources.RazorProject_PathMustStartWithForwardSlash, nameof(path)); } + + return path; } } } diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/FileSystemRazorProjectTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/FileSystemRazorProjectTest.cs index d9d21dc1f..945ea2cbb 100644 --- a/test/Microsoft.AspNetCore.Razor.Evolution.Test/FileSystemRazorProjectTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/FileSystemRazorProjectTest.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; +using Microsoft.AspNetCore.Testing; using Xunit; namespace Microsoft.AspNetCore.Razor.Evolution @@ -12,6 +13,57 @@ public class FileSystemRazorProjectTest private static string TestFolder { get; } = Path.Combine(TestProject.GetProjectDirectory(), "TestFiles", "FileSystemRazorProject"); + [Theory] + [InlineData(null)] + [InlineData("")] + public void NormalizeAndEnsureValidPath_ThrowsIfPathIsNullOrEmpty(string path) + { + // Arrange + var project = new TestFileSystemRazorProject("C:/some/test/path/root"); + + // Act and Assert + ExceptionAssert.ThrowsArgumentNullOrEmptyString(() => project.NormalizeAndEnsureValidPath(path), "path"); + } + + [Fact] + public void NormalizeAndEnsureValidPath_NormalizesToAbsolutePath() + { + // Arrange + var project = new TestFileSystemRazorProject("C:/some/test/path/root"); + + // Act + var absolutePath = project.NormalizeAndEnsureValidPath("file.cshtml"); + + // Assert + Assert.Equal("C:/some/test/path/root/file.cshtml", absolutePath); + } + + [Fact] + public void NormalizeAndEnsureValidPath_NormalizesToAbsolutePathWithoutForwardSlash() + { + // Arrange + var project = new TestFileSystemRazorProject("C:/some/test/path/root"); + + // Act + var absolutePath = project.NormalizeAndEnsureValidPath("/file.cshtml"); + + // Assert + Assert.Equal("C:/some/test/path/root/file.cshtml", absolutePath); + } + + [Fact] + public void NormalizeAndEnsureValidPath_NormalizesToForwardSlashes() + { + // Arrange + var project = new TestFileSystemRazorProject(@"C:\some\test\path\root"); + + // Act + var absolutePath = project.NormalizeAndEnsureValidPath(@"something\file.cshtml"); + + // Assert + Assert.Equal("C:/some/test/path/root/something/file.cshtml", absolutePath); + } + [Fact] public void EnumerateItems_DiscoversAllCshtmlFiles() { @@ -104,5 +156,14 @@ public void GetItem_ReturnsNotFoundResult() // Assert Assert.False(file.Exists); } + + private class TestFileSystemRazorProject : FileSystemRazorProject + { + public TestFileSystemRazorProject(string root) : base(root) + { + } + + public new string NormalizeAndEnsureValidPath(string path) => base.NormalizeAndEnsureValidPath(path); + } } } diff --git a/test/Microsoft.AspNetCore.Razor.Evolution.Test/RazorProjectTest.cs b/test/Microsoft.AspNetCore.Razor.Evolution.Test/RazorProjectTest.cs index 6e433de85..0222427e5 100644 --- a/test/Microsoft.AspNetCore.Razor.Evolution.Test/RazorProjectTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Evolution.Test/RazorProjectTest.cs @@ -11,30 +11,43 @@ namespace Microsoft.AspNetCore.Razor.Evolution { public class RazorProjectTest { + [Fact] + public void NormalizeAndEnsureValidPath_DoesNotModifyPath() + { + // Arrange + var project = new TestRazorProject(new Dictionary()); + + // Act + var path = project.NormalizeAndEnsureValidPath("/Views/Home/Index.cshtml"); + + // Assert + Assert.Equal("/Views/Home/Index.cshtml", path); + } + [Theory] [InlineData(null)] [InlineData("")] - public void EnsureValidPath_ThrowsIfPathIsNullOrEmpty(string path) + public void NormalizeAndEnsureValidPath_ThrowsIfPathIsNullOrEmpty(string path) { // Arrange var project = new TestRazorProject(new Dictionary()); // Act and Assert - ExceptionAssert.ThrowsArgumentNullOrEmptyString(() => project.EnsureValidPath(path), "path"); + ExceptionAssert.ThrowsArgumentNullOrEmptyString(() => project.NormalizeAndEnsureValidPath(path), "path"); } [Theory] [InlineData("foo")] [InlineData("~/foo")] [InlineData("\\foo")] - public void EnsureValidPath_ThrowsIfPathDoesNotStartWithForwardSlash(string path) + public void NormalizeAndEnsureValidPath_ThrowsIfPathDoesNotStartWithForwardSlash(string path) { // Arrange var project = new TestRazorProject(new Dictionary()); // Act and Assert ExceptionAssert.ThrowsArgument( - () => project.EnsureValidPath(path), + () => project.NormalizeAndEnsureValidPath(path), "path", "Path must begin with a forward slash '/'."); } @@ -338,7 +351,7 @@ public override RazorProjectItem GetItem(string path) return item; } - public new void EnsureValidPath(string path) => base.EnsureValidPath(path); + public new string NormalizeAndEnsureValidPath(string path) => base.NormalizeAndEnsureValidPath(path); } } }