Skip to content
This repository has been archived by the owner on Dec 14, 2018. It is now read-only.

Commit

Permalink
Treat names with slashes that do not end in .cshtml as paths
Browse files Browse the repository at this point in the history
Fixes #6672
  • Loading branch information
pranavkm committed Sep 5, 2017
1 parent 1d1a520 commit 2fd2125
Show file tree
Hide file tree
Showing 7 changed files with 103 additions and 21 deletions.
38 changes: 30 additions & 8 deletions src/Microsoft.AspNetCore.Mvc.Core/Internal/ViewEnginePath.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,24 @@ namespace Microsoft.AspNetCore.Mvc.Internal
{
public static class ViewEnginePath
{
public static readonly char[] PathSeparators = new[] { '/', '\\' };
private const string CurrentDirectoryToken = ".";
private const string ParentDirectoryToken = "..";
private static readonly char[] _pathSeparators = new[] { '/', '\\' };
private static readonly string[] TokensRequiringNormalization = new string[]
{
// ./
CurrentDirectoryToken + PathSeparators[0],
// .\
CurrentDirectoryToken + PathSeparators[1],
// ../
ParentDirectoryToken + PathSeparators[0],
// ..\
ParentDirectoryToken + PathSeparators[1],
// //
"" + PathSeparators[0] + PathSeparators[0],
// \\
"" + PathSeparators[1] + PathSeparators[1],
};

public static string CombinePath(string first, string second)
{
Expand Down Expand Up @@ -42,18 +57,18 @@ public static string CombinePath(string first, string second)
result = first.Substring(0, index + 1) + second;
}

return ResolvePath(result);
return NormalizePath(result);
}

public static string ResolvePath(string path)
public static string NormalizePath(string path)
{
if (!RequiresPathResolution(path))
if (!RequiresPathNormalization(path))
{
return path;
}

var pathSegments = new List<StringSegment>();
var tokenizer = new StringTokenizer(path, _pathSeparators);
var tokenizer = new StringTokenizer(path, PathSeparators);
foreach (var segment in tokenizer)
{
if (segment.Length == 0)
Expand Down Expand Up @@ -93,10 +108,17 @@ public static string ResolvePath(string path)
return builder.ToString();
}

private static bool RequiresPathResolution(string path)
private static bool RequiresPathNormalization(string path)
{
return path.IndexOf(ParentDirectoryToken, StringComparison.Ordinal) != -1 ||
path.IndexOf(CurrentDirectoryToken, StringComparison.Ordinal) != -1;
for (var i = 0; i < TokensRequiringNormalization.Length; i++)
{
if (path.IndexOf(TokensRequiringNormalization[i]) != -1)
{
return true;
}
}

return false;
}
}
}
45 changes: 37 additions & 8 deletions src/Microsoft.AspNetCore.Mvc.Razor/RazorViewEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ public RazorPageResult GetPage(string executingFilePath, string pagePath)
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(pagePath));
}

if (!(IsApplicationRelativePath(pagePath) || IsRelativePath(pagePath)))
if (!LooksLikePath(pagePath))
{
// Not a path this method can handle.
return new RazorPageResult(pagePath, Enumerable.Empty<string>());
Expand Down Expand Up @@ -191,7 +191,7 @@ public ViewEngineResult GetView(string executingFilePath, string viewPath, bool
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(viewPath));
}

if (!(IsApplicationRelativePath(viewPath) || IsRelativePath(viewPath)))
if (!LooksLikePath(viewPath))
{
// Not a path this method can handle.
return ViewEngineResult.NotFound(viewPath, Enumerable.Empty<string>());
Expand All @@ -205,8 +205,7 @@ private ViewLocationCacheResult LocatePageFromPath(string executingFilePath, str
{
var applicationRelativePath = GetAbsolutePath(executingFilePath, pagePath);
var cacheKey = new ViewLocationCacheKey(applicationRelativePath, isMainPage);
ViewLocationCacheResult cacheResult;
if (!ViewLookupCache.TryGetValue(cacheKey, out cacheResult))
if (!ViewLookupCache.TryGetValue(cacheKey, out ViewLocationCacheResult cacheResult))
{
var expirationTokens = new HashSet<IChangeToken>();
cacheResult = CreateCacheResult(expirationTokens, applicationRelativePath, isMainPage);
Expand Down Expand Up @@ -300,11 +299,19 @@ public string GetAbsolutePath(string executingFilePath, string pagePath)

if (IsApplicationRelativePath(pagePath))
{
// An absolute path already; no change required.
return pagePath;
// An absolute path already; no need to concat it with executingFilePath.
return ViewEnginePath.NormalizePath(pagePath);
}

if (!IsRelativePath(pagePath))
if (pagePath.IndexOfAny(ViewEnginePath.PathSeparators) != -1)
{
// A name that looks like a path (e.g. Shared/_Partial). Add an extension to it and treat it like one.
if (!pagePath.EndsWith(ViewExtension, StringComparison.OrdinalIgnoreCase))
{
pagePath = pagePath + ViewExtension;
}
}
else if (!IsRelativePath(pagePath))
{
// A page name; no change required.
return pagePath;
Expand All @@ -316,7 +323,7 @@ public string GetAbsolutePath(string executingFilePath, string pagePath)
// path relative to currently-executing view, if any.
// Not yet executing a view. Start in app root.
var absolutePath = "/" + pagePath;
return ViewEnginePath.ResolvePath(absolutePath);
return ViewEnginePath.NormalizePath(absolutePath);
}

return ViewEnginePath.CombinePath(executingFilePath, pagePath);
Expand Down Expand Up @@ -492,5 +499,27 @@ private static bool IsRelativePath(string name)
// Though ./ViewName looks like a relative path, framework searches for that view using view locations.
return name.EndsWith(ViewExtension, StringComparison.OrdinalIgnoreCase);
}

private static bool LooksLikePath(string name)
{
Debug.Assert(!string.IsNullOrEmpty(name));

if (IsApplicationRelativePath(name))
{
return true;
}

if (IsRelativePath(name))
{
return true;
}

if (name.IndexOfAny(ViewEnginePath.PathSeparators) != -1)
{
return true;
}

return false;
}
}
}
13 changes: 13 additions & 0 deletions test/Microsoft.AspNetCore.Mvc.FunctionalTests/ViewEngineTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -493,5 +493,18 @@ public async Task ViewEngine_NormalizesPathsReturnedByViewLocationExpanders()
// Assert
Assert.Equal(expected, responseContent.Trim());
}

[Fact]
public async Task ViewEngine_ResolvesPathsWithSlashesThatDoNotHaveExtensions()
{
// Arrange
var expected = @"<embdedded-layout>Hello from EmbeddedHome\EmbeddedPartial</embdedded-layout>";

// Act
var responseContent = await Client.GetStringAsync("/EmbeddedViews/RelativeNonPath");

// Assert
Assert.Equal(expected, responseContent.Trim());
}
}
}
24 changes: 19 additions & 5 deletions test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorViewEngineTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
using Moq;
using Xunit;

namespace Microsoft.AspNetCore.Mvc.Razor.Test
namespace Microsoft.AspNetCore.Mvc.Razor
{
public class RazorViewEngineTest
{
Expand Down Expand Up @@ -1486,13 +1486,10 @@ public void GetPage_ResolvesRelativeToAppRoot_WithRelativePath_IfNoPageExecuting
[InlineData(null, null)]
[InlineData(null, "")]
[InlineData(null, "Page")]
[InlineData(null, "Folder/Page")]
[InlineData(null, "Folder1/Folder2/Page")]
[InlineData("/Home/Index.cshtml", null)]
[InlineData("/Home/Index.cshtml", "")]
[InlineData("/Home/Index.cshtml", "Page")]
[InlineData("/Home/Index.cshtml", "Folder/Page")]
[InlineData("/Home/Index.cshtml", "Folder1/Folder2/Page")]

public void GetAbsolutePath_ReturnsPagePathUnchanged_IfNotAPath(string executingFilePath, string pagePath)
{
// Arrange
Expand All @@ -1505,6 +1502,23 @@ public void GetAbsolutePath_ReturnsPagePathUnchanged_IfNotAPath(string executing
Assert.Same(pagePath, result);
}

[Theory]
[InlineData(null, "Folder/Page", "/Folder/Page.cshtml")]
[InlineData(null, "Folder1/Folder2/Page", "/Folder1/Folder2/Page.cshtml")]
[InlineData("/Home/Index.cshtml", "Folder/Page", "/Home/Folder/Page.cshtml")]
[InlineData("/Home/Index.cshtml", "Folder1/Folder2/Page", "/Home/Folder1/Folder2/Page.cshtml")]
public void GetAbsolutePath_ResolvesNamesThatLookLikePaths(string executingFilePath, string pagePath, string expected)
{
// Arrange
var viewEngine = CreateViewEngine();

// Act
var result = viewEngine.GetAbsolutePath(executingFilePath, pagePath);

// Assert
Assert.Equal(expected, result);
}

[Theory]
[InlineData("/Views/Home/Index.cshtml", "../Shared/_Partial.cshtml")]
[InlineData("/Views/Home/Index.cshtml", "..\\Shared\\_Partial.cshtml")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ namespace RazorWebSite.Controllers
public class EmbeddedViewsController : Controller
{
public IActionResult Index() => View("/Views/EmbeddedHome/Index.cshtml");
public IActionResult RelativeNonPath() => View("/Views/EmbeddedHome/RelativeNonPath.cshtml");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Hello from EmbeddedHome\EmbeddedPartial
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@{ Layout = "../EmbeddedShared/_Layout"; }
@Html.Partial("./EmbeddedPartial")

0 comments on commit 2fd2125

Please sign in to comment.