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
This commit is contained in:
N. Taylor Mullen 2017-03-22 12:32:34 -07:00
parent 72febdac64
commit c07759996f
4 changed files with 111 additions and 18 deletions

View File

@ -3,7 +3,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@ -25,7 +24,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(root)); throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(root));
} }
Root = root.TrimEnd('/', '\\'); Root = root.Replace('\\', '/').TrimEnd('/');
} }
/// <summary> /// <summary>
@ -36,10 +35,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
/// <inheritdoc /> /// <inheritdoc />
public override IEnumerable<RazorProjectItem> EnumerateItems(string basePath) public override IEnumerable<RazorProjectItem> EnumerateItems(string basePath)
{ {
EnsureValidPath(basePath); var absoluteBasePath = NormalizeAndEnsureValidPath(basePath);
Debug.Assert(basePath.StartsWith("/"));
var absoluteBasePath = Path.Combine(Root, basePath.Substring(1));
var directory = new DirectoryInfo(absoluteBasePath); var directory = new DirectoryInfo(absoluteBasePath);
if (!directory.Exists) if (!directory.Exists)
@ -53,17 +49,38 @@ namespace Microsoft.AspNetCore.Razor.Evolution
{ {
var relativePath = file.FullName.Substring(absoluteBasePath.Length).Replace(Path.DirectorySeparatorChar, '/'); var relativePath = file.FullName.Substring(absoluteBasePath.Length).Replace(Path.DirectorySeparatorChar, '/');
return new FileSystemRazorProjectItem(basePath, relativePath, file); return new FileSystemRazorProjectItem(basePath, relativePath, file);
}); });
} }
/// <inheritdoc /> /// <inheritdoc />
public override RazorProjectItem GetItem(string path) 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)); 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;
}
} }
} }

View File

@ -59,8 +59,8 @@ namespace Microsoft.AspNetCore.Razor.Evolution
/// </remarks> /// </remarks>
public virtual IEnumerable<RazorProjectItem> FindHierarchicalItems(string basePath, string path, string fileName) public virtual IEnumerable<RazorProjectItem> FindHierarchicalItems(string basePath, string path, string fileName)
{ {
EnsureValidPath(basePath); basePath = NormalizeAndEnsureValidPath(basePath);
EnsureValidPath(path); path = NormalizeAndEnsureValidPath(path);
if (string.IsNullOrEmpty(fileName)) if (string.IsNullOrEmpty(fileName))
{ {
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(fileName)); throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(fileName));
@ -109,7 +109,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
/// Performs validation for paths passed to methods of <see cref="RazorProject"/>. /// Performs validation for paths passed to methods of <see cref="RazorProject"/>.
/// </summary> /// </summary>
/// <param name="path">The path to validate.</param> /// <param name="path">The path to validate.</param>
protected virtual void EnsureValidPath(string path) protected virtual string NormalizeAndEnsureValidPath(string path)
{ {
if (string.IsNullOrEmpty(path)) if (string.IsNullOrEmpty(path))
{ {
@ -120,6 +120,8 @@ namespace Microsoft.AspNetCore.Razor.Evolution
{ {
throw new ArgumentException(Resources.RazorProject_PathMustStartWithForwardSlash, nameof(path)); throw new ArgumentException(Resources.RazorProject_PathMustStartWithForwardSlash, nameof(path));
} }
return path;
} }
} }
} }

View File

@ -3,6 +3,7 @@
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Microsoft.AspNetCore.Testing;
using Xunit; using Xunit;
namespace Microsoft.AspNetCore.Razor.Evolution namespace Microsoft.AspNetCore.Razor.Evolution
@ -12,6 +13,57 @@ namespace Microsoft.AspNetCore.Razor.Evolution
private static string TestFolder { get; } = private static string TestFolder { get; } =
Path.Combine(TestProject.GetProjectDirectory(), "TestFiles", "FileSystemRazorProject"); 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] [Fact]
public void EnumerateItems_DiscoversAllCshtmlFiles() public void EnumerateItems_DiscoversAllCshtmlFiles()
{ {
@ -104,5 +156,14 @@ namespace Microsoft.AspNetCore.Razor.Evolution
// Assert // Assert
Assert.False(file.Exists); Assert.False(file.Exists);
} }
private class TestFileSystemRazorProject : FileSystemRazorProject
{
public TestFileSystemRazorProject(string root) : base(root)
{
}
public new string NormalizeAndEnsureValidPath(string path) => base.NormalizeAndEnsureValidPath(path);
}
} }
} }

View File

@ -11,30 +11,43 @@ namespace Microsoft.AspNetCore.Razor.Evolution
{ {
public class RazorProjectTest public class RazorProjectTest
{ {
[Fact]
public void NormalizeAndEnsureValidPath_DoesNotModifyPath()
{
// Arrange
var project = new TestRazorProject(new Dictionary<string, RazorProjectItem>());
// Act
var path = project.NormalizeAndEnsureValidPath("/Views/Home/Index.cshtml");
// Assert
Assert.Equal("/Views/Home/Index.cshtml", path);
}
[Theory] [Theory]
[InlineData(null)] [InlineData(null)]
[InlineData("")] [InlineData("")]
public void EnsureValidPath_ThrowsIfPathIsNullOrEmpty(string path) public void NormalizeAndEnsureValidPath_ThrowsIfPathIsNullOrEmpty(string path)
{ {
// Arrange // Arrange
var project = new TestRazorProject(new Dictionary<string, RazorProjectItem>()); var project = new TestRazorProject(new Dictionary<string, RazorProjectItem>());
// Act and Assert // Act and Assert
ExceptionAssert.ThrowsArgumentNullOrEmptyString(() => project.EnsureValidPath(path), "path"); ExceptionAssert.ThrowsArgumentNullOrEmptyString(() => project.NormalizeAndEnsureValidPath(path), "path");
} }
[Theory] [Theory]
[InlineData("foo")] [InlineData("foo")]
[InlineData("~/foo")] [InlineData("~/foo")]
[InlineData("\\foo")] [InlineData("\\foo")]
public void EnsureValidPath_ThrowsIfPathDoesNotStartWithForwardSlash(string path) public void NormalizeAndEnsureValidPath_ThrowsIfPathDoesNotStartWithForwardSlash(string path)
{ {
// Arrange // Arrange
var project = new TestRazorProject(new Dictionary<string, RazorProjectItem>()); var project = new TestRazorProject(new Dictionary<string, RazorProjectItem>());
// Act and Assert // Act and Assert
ExceptionAssert.ThrowsArgument( ExceptionAssert.ThrowsArgument(
() => project.EnsureValidPath(path), () => project.NormalizeAndEnsureValidPath(path),
"path", "path",
"Path must begin with a forward slash '/'."); "Path must begin with a forward slash '/'.");
} }
@ -338,7 +351,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
return item; return item;
} }
public new void EnsureValidPath(string path) => base.EnsureValidPath(path); public new string NormalizeAndEnsureValidPath(string path) => base.NormalizeAndEnsureValidPath(path);
} }
} }
} }