diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/FileProviderRazorProject.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/FileProviderRazorProject.cs index f05429bb2a..2fd32e9ad0 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/FileProviderRazorProject.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/FileProviderRazorProject.cs @@ -37,15 +37,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal path = NormalizeAndEnsureValidPath(path); var fileInfo = _provider.GetFileInfo(path); - string relativePhysicalPath = null; - if (fileInfo != null && fileInfo.Exists) - { - var absoluteBasePath = _hostingEnvironment.ContentRootPath; - relativePhysicalPath = fileInfo?.PhysicalPath?.Substring(absoluteBasePath.Length + 1); // Include leading separator - relativePhysicalPath = relativePhysicalPath ?? path; // Use the incoming path if the file is not directly accessible - } - - return new FileProviderRazorProjectItem(fileInfo, basePath: string.Empty, filePath: path, relativePhysicalPath: relativePhysicalPath); + return new FileProviderRazorProjectItem(fileInfo, basePath: string.Empty, filePath: path, root: _hostingEnvironment.ContentRootPath); } public override IEnumerable EnumerateItems(string path) @@ -58,11 +50,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal { if (directory.Exists) { - foreach (var file in directory) + foreach (var fileInfo in directory) { - if (file.IsDirectory) + if (fileInfo.IsDirectory) { - var relativePath = prefix + "/" + file.Name; + var relativePath = prefix + "/" + fileInfo.Name; var subDirectory = _provider.GetDirectoryContents(JoinPath(basePath, relativePath)); var children = EnumerateFiles(subDirectory, basePath, relativePath); foreach (var child in children) @@ -70,14 +62,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal yield return child; } } - else if (string.Equals(RazorFileExtension, Path.GetExtension(file.Name), StringComparison.OrdinalIgnoreCase)) + else if (string.Equals(RazorFileExtension, Path.GetExtension(fileInfo.Name), StringComparison.OrdinalIgnoreCase)) { - var filePath = prefix + "/" + file.Name; - var absoluteBasePath = _hostingEnvironment.ContentRootPath; - var relativePhysicalPath = file.PhysicalPath?.Substring(absoluteBasePath.Length + 1); // Include leading separator - relativePhysicalPath = relativePhysicalPath ?? filePath; // Use the incoming path if the file is not directly accessible + var filePath = prefix + "/" + fileInfo.Name; - yield return new FileProviderRazorProjectItem(file, basePath, filePath: filePath, relativePhysicalPath: relativePhysicalPath); + yield return new FileProviderRazorProjectItem(fileInfo, basePath, filePath: filePath, root: _hostingEnvironment.ContentRootPath); } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/FileProviderRazorProjectItem.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/FileProviderRazorProjectItem.cs index e7238fb542..6108d6875b 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/FileProviderRazorProjectItem.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/FileProviderRazorProjectItem.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.IO; using Microsoft.AspNetCore.Razor.Language; using Microsoft.Extensions.FileProviders; @@ -9,12 +10,16 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal { public class FileProviderRazorProjectItem : RazorProjectItem { - public FileProviderRazorProjectItem(IFileInfo fileInfo, string basePath, string filePath, string relativePhysicalPath) + private string _root; + private string _relativePhysicalPath; + private bool _isRelativePhysicalPathSet; + + public FileProviderRazorProjectItem(IFileInfo fileInfo, string basePath, string filePath, string root) { FileInfo = fileInfo; BasePath = basePath; FilePath = filePath; - RelativePhysicalPath = relativePhysicalPath; + _root = root; } public IFileInfo FileInfo { get; } @@ -27,7 +32,30 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal public override string PhysicalPath => FileInfo.PhysicalPath; - public override string RelativePhysicalPath { get; } + public override string RelativePhysicalPath + { + get + { + if (!_isRelativePhysicalPathSet) + { + _isRelativePhysicalPathSet = true; + + if (Exists) + { + if (_root != null && + !string.IsNullOrEmpty(PhysicalPath) && + PhysicalPath.StartsWith(_root, StringComparison.OrdinalIgnoreCase) && + PhysicalPath.Length > _root.Length && + (PhysicalPath[_root.Length] == Path.DirectorySeparatorChar || PhysicalPath[_root.Length] == Path.AltDirectorySeparatorChar)) + { + _relativePhysicalPath = PhysicalPath.Substring(_root.Length + 1); // Include leading separator + } + } + } + + return _relativePhysicalPath; + } + } public override Stream Read() { diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/CompilerFailedExceptionFactoryTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/CompilerFailedExceptionFactoryTest.cs index 149103a8e4..56d5a54412 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/CompilerFailedExceptionFactoryTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/CompilerFailedExceptionFactoryTest.cs @@ -39,7 +39,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal // Assert var failure = Assert.Single(compilationResult.CompilationFailures); - Assert.Equal(viewPath, failure.SourceFilePath); + Assert.Equal(Path.Combine("Views", "Home", "Index.cshtml"), failure.SourceFilePath); Assert.Collection(failure.Messages, message => Assert.StartsWith( @"Unterminated string literal.", @@ -143,7 +143,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal compilationResult.CompilationFailures, failure => { - Assert.Equal(viewPath, failure.SourceFilePath); + Assert.Equal(Path.Combine("Views", "Home", "Index.cshtml"), failure.SourceFilePath); Assert.Collection(failure.Messages, message => { diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/FileProviderRazorProjectTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/FileProviderRazorProjectTest.cs index f192efebc1..8642206778 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/FileProviderRazorProjectTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/FileProviderRazorProjectTest.cs @@ -17,8 +17,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal { // Arrange var fileProvider = new TestFileProvider("BasePath"); - var file1 = fileProvider.AddFile("File1.txt", "content"); - var file2 = fileProvider.AddFile("File2.js", "content"); + var file1 = fileProvider.AddFile("/File1.txt", "content"); + var file2 = fileProvider.AddFile("/File2.js", "content"); fileProvider.AddDirectoryContent("/", new IFileInfo[] { file1, file2 }); var accessor = Mock.Of(a => a.FileProvider == fileProvider); @@ -37,9 +37,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal { // Arrange var fileProvider = new TestFileProvider("BasePath"); - var file1 = fileProvider.AddFile("File1.cshtml", "content"); - var file2 = fileProvider.AddFile("File2.js", "content"); - var file3 = fileProvider.AddFile("File3.cshtml", "content"); + var file1 = fileProvider.AddFile("/File1.cshtml", "content"); + var file2 = fileProvider.AddFile("/File2.js", "content"); + var file3 = fileProvider.AddFile("/File3.cshtml", "content"); fileProvider.AddDirectoryContent("/", new IFileInfo[] { file1, file2, file3 }); var accessor = Mock.Of(a => a.FileProvider == fileProvider); @@ -84,9 +84,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal }; fileProvider.AddDirectoryContent("/", new IFileInfo[] { directory1, file1, directory2 }); - var file2 = fileProvider.AddFile(Path.Combine("Level1-Dir1", "File2.cshtml"), "content"); - var file3 = fileProvider.AddFile(Path.Combine("Level1-Dir1", "File3.cshtml"), "content"); - var file4 = fileProvider.AddFile(Path.Combine("Level1-Dir1", "File4.txt"), "content"); + var file2 = fileProvider.AddFile("/Level1-Dir1/File2.cshtml", "content"); + var file3 = fileProvider.AddFile("/Level1-Dir1/File3.cshtml", "content"); + var file4 = fileProvider.AddFile("/Level1-Dir1/File4.txt", "content"); var directory3 = new TestDirectoryFileInfo { Name = "Level2-Dir1" @@ -144,16 +144,16 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal { Name = "Level1-Dir1", }; - var file1 = fileProvider.AddFile(Path.Combine("BasePath", "File1.cshtml"), "content"); + var file1 = fileProvider.AddFile("/File1.cshtml", "content"); var directory2 = new TestDirectoryFileInfo { Name = "Level1-Dir2", }; fileProvider.AddDirectoryContent("/", new IFileInfo[] { directory1, file1, directory2 }); - var file2 = fileProvider.AddFile(Path.Combine("Level1-Dir1", "File2.cshtml"), "content"); - var file3 = fileProvider.AddFile(Path.Combine("Level1-Dir1", "File3.cshtml"), "content"); - var file4 = fileProvider.AddFile(Path.Combine("Level1-Dir1", "File4.txt"), "content"); + var file2 = fileProvider.AddFile("/Level1-Dir1/File2.cshtml", "content"); + var file3 = fileProvider.AddFile("/Level1-Dir1/File3.cshtml", "content"); + var file4 = fileProvider.AddFile("/Level1-Dir1/File4.txt", "content"); var directory3 = new TestDirectoryFileInfo { Name = "Level2-Dir1" @@ -188,13 +188,13 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal }); } - [Fact(Skip = "Need to follow-up https://github.com/aspnet/Mvc/pull/7228")] + [Fact] public void GetItem_ReturnsFileFromDisk() { var fileProvider = new TestFileProvider("BasePath"); - var file1 = fileProvider.AddFile("File1.cshtml", "content"); - var file2 = fileProvider.AddFile("File2.js", "content"); - var file3 = fileProvider.AddFile("File3.cshtml", "content"); + var file1 = fileProvider.AddFile("/File1.cshtml", "content"); + var file2 = fileProvider.AddFile("/File2.js", "content"); + var file3 = fileProvider.AddFile("/File3.cshtml", "content"); fileProvider.AddDirectoryContent("/", new IFileInfo[] { file1, file2, file3 }); var accessor = Mock.Of(a => a.FileProvider == fileProvider); @@ -212,12 +212,36 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal Assert.Equal("File3.cshtml", item.RelativePhysicalPath); } + [Fact] + public void GetItem_PhysicalPathDoesNotStartWithContentRoot_ReturnsNull() + { + var fileProvider = new TestFileProvider("BasePath2"); + var file1 = fileProvider.AddFile("/File1.cshtml", "content"); + var file2 = fileProvider.AddFile("/File2.js", "content"); + var file3 = fileProvider.AddFile("/File3.cshtml", "content"); + fileProvider.AddDirectoryContent("/", new IFileInfo[] { file1, file2, file3 }); + + var accessor = Mock.Of(a => a.FileProvider == fileProvider); + + var razorProject = new FileProviderRazorProject(accessor, Mock.Of(e => e.ContentRootPath == "BasePath")); + + // Act + var item = razorProject.GetItem("/File3.cshtml"); + + // Assert + Assert.True(item.Exists); + Assert.Equal("/File3.cshtml", item.FilePath); + Assert.Equal(string.Empty, item.BasePath); + Assert.Equal(Path.Combine("BasePath2", "File3.cshtml"), item.PhysicalPath); + Assert.Null(item.RelativePhysicalPath); + } + [Fact] public void GetItem_ReturnsNotFoundResult() { // Arrange var fileProvider = new TestFileProvider("BasePath"); - var file = fileProvider.AddFile("SomeFile.cshtml", "content"); + var file = fileProvider.AddFile("/SomeFile.cshtml", "content"); fileProvider.AddDirectoryContent("/", new IFileInfo[] { file }); var accessor = Mock.Of(a => a.FileProvider == fileProvider); diff --git a/test/Microsoft.AspNetCore.Mvc.TestCommon/TestFileProvider.cs b/test/Microsoft.AspNetCore.Mvc.TestCommon/TestFileProvider.cs index a92d8c4e56..3e3f62a9e5 100644 --- a/test/Microsoft.AspNetCore.Mvc.TestCommon/TestFileProvider.cs +++ b/test/Microsoft.AspNetCore.Mvc.TestCommon/TestFileProvider.cs @@ -46,7 +46,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor var fileInfo = new TestFileInfo { Content = contents, - PhysicalPath = Path.Combine(Root, path), + PhysicalPath = Path.Combine(Root, NormalizeAndEnsureValidPhysicalPath(path)), Name = Path.GetFileName(path), LastModified = DateTime.UtcNow, }; @@ -109,6 +109,23 @@ namespace Microsoft.AspNetCore.Mvc.Razor return _fileTriggers[filter]; } + private static string NormalizeAndEnsureValidPhysicalPath(string filePath) + { + if (string.IsNullOrEmpty(filePath)) + { + return filePath; + } + + filePath = filePath.Replace('/', Path.DirectorySeparatorChar); + + if (filePath[0] == Path.DirectorySeparatorChar) + { + filePath = filePath.Substring(1); + } + + return filePath; + } + private class NotFoundFileInfo : IFileInfo { public bool Exists