[Fixes #1201] Handle virtual paths in FilePathResult
This commit is contained in:
parent
9a08e42612
commit
f8035d6b04
|
|
@ -5,9 +5,12 @@ using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.FileSystems;
|
||||||
|
using Microsoft.AspNet.Hosting;
|
||||||
using Microsoft.AspNet.Http;
|
using Microsoft.AspNet.Http;
|
||||||
using Microsoft.AspNet.HttpFeature;
|
using Microsoft.AspNet.HttpFeature;
|
||||||
using Microsoft.AspNet.Mvc.Core;
|
using Microsoft.AspNet.Mvc.Core;
|
||||||
|
using Microsoft.Framework.DependencyInjection;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc
|
namespace Microsoft.AspNet.Mvc
|
||||||
{
|
{
|
||||||
|
|
@ -31,38 +34,170 @@ namespace Microsoft.AspNet.Mvc
|
||||||
public FilePathResult([NotNull] string fileName, [NotNull] string contentType)
|
public FilePathResult([NotNull] string fileName, [NotNull] string contentType)
|
||||||
: base(contentType)
|
: base(contentType)
|
||||||
{
|
{
|
||||||
if (!Path.IsPathRooted(fileName))
|
|
||||||
{
|
|
||||||
var message = Resources.FormatFileResult_InvalidPathType_RelativeOrVirtualPath(fileName);
|
|
||||||
throw new ArgumentException(message, "fileName");
|
|
||||||
}
|
|
||||||
|
|
||||||
FileName = fileName;
|
FileName = fileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new <see cref="FilePathResult"/> instance with
|
||||||
|
/// the provided <paramref name="fileName"/> and the
|
||||||
|
/// provided <paramref name="contentType"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fileName">The path to the file. The path must be an absolute
|
||||||
|
/// path. Relative and virtual paths are not supported.</param>
|
||||||
|
/// <param name="contentType">The Content-Type header of the response.</param>
|
||||||
|
public FilePathResult(
|
||||||
|
[NotNull] string fileName,
|
||||||
|
[NotNull] string contentType,
|
||||||
|
[NotNull] IFileSystem fileSystem)
|
||||||
|
: base(contentType)
|
||||||
|
{
|
||||||
|
FileName = fileName;
|
||||||
|
FileSystem = fileSystem;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the path to the file that will be sent back as the response.
|
/// Gets the path to the file that will be sent back as the response.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string FileName { get; private set; }
|
public string FileName { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the <see cref="IFileSystem"/> used to resolve paths.
|
||||||
|
/// </summary>
|
||||||
|
public IFileSystem FileSystem { get; private set; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override Task WriteFileAsync(HttpResponse response, CancellationToken cancellation)
|
protected override Task WriteFileAsync(HttpResponse response, CancellationToken cancellation)
|
||||||
{
|
{
|
||||||
var sendFile = response.HttpContext.GetFeature<IHttpSendFileFeature>();
|
var sendFile = response.HttpContext.GetFeature<IHttpSendFileFeature>();
|
||||||
|
|
||||||
|
var fileSystem = GetFileSystem(response.HttpContext.RequestServices);
|
||||||
|
|
||||||
|
var filePath = ResolveFilePath(fileSystem);
|
||||||
|
|
||||||
if (sendFile != null)
|
if (sendFile != null)
|
||||||
{
|
{
|
||||||
return sendFile.SendFileAsync(
|
return sendFile.SendFileAsync(
|
||||||
FileName,
|
filePath,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
length: null,
|
length: null,
|
||||||
cancellation: cancellation);
|
cancellation: cancellation);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return CopyStreamToResponse(FileName, response, cancellation);
|
return CopyStreamToResponse(filePath, response, cancellation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal string ResolveFilePath(IFileSystem fileSystem)
|
||||||
|
{
|
||||||
|
// Let the file system try to get the file and if it can't,
|
||||||
|
// fallback to trying the path directly unless the path starts with '/'.
|
||||||
|
// In that case we consider it relative and won't try to resolve it as
|
||||||
|
// a full path
|
||||||
|
|
||||||
|
var path = NormalizePath(FileName);
|
||||||
|
|
||||||
|
if (IsPathRooted(path))
|
||||||
|
{
|
||||||
|
// The path is absolute
|
||||||
|
// C:\...\file.ext
|
||||||
|
// C:/.../file.ext
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
IFileInfo fileInfo = null;
|
||||||
|
if (fileSystem.TryGetFileInfo(path, out fileInfo))
|
||||||
|
{
|
||||||
|
// The path is relative and IFileSystem found the file, so return the full
|
||||||
|
// path.
|
||||||
|
return fileInfo.PhysicalPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We are absolutely sure the path is relative, and couldn't find the file
|
||||||
|
// on the file system.
|
||||||
|
var message = Resources.FormatFileResult_InvalidPath(path);
|
||||||
|
throw new FileNotFoundException(message, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal for unit testing purposes only
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a normalized representation of the given <paramref name="path"/>. The default
|
||||||
|
/// implementation doesn't support files with '\' in the file name and treats the '\' as
|
||||||
|
/// a directory separator. The default implementation will convert all the '\' into '/'
|
||||||
|
/// and will remove leading '~' characters.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">The path to normalize.</param>
|
||||||
|
/// <returns>The normalized path.</returns>
|
||||||
|
protected internal virtual string NormalizePath([NotNull] string path)
|
||||||
|
{
|
||||||
|
// Unix systems support '\' as part of the file name. So '\' is not
|
||||||
|
// a valid directory separator in those systems. Here we make the conscious
|
||||||
|
// choice of replacing '\' for '/' which means that file names with '\' will
|
||||||
|
// not be supported.
|
||||||
|
|
||||||
|
if (path.StartsWith("~/", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
// We don't support virtual paths for now, so we just treat them as relative
|
||||||
|
// paths.
|
||||||
|
return path.Substring(1).Replace('\\', '/');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (path.StartsWith("~\\", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
// ~\ is not a valid virtual path, and we don't want to replace '\' with '/' as it
|
||||||
|
// ofuscates the error, so just return the original path and throw at a later point
|
||||||
|
// when we can't find the file.
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
return path.Replace('\\', '/');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal for unit testing purposes only
|
||||||
|
/// <summary>
|
||||||
|
/// Determines if the provided path is absolute or relative. The default implementation considers
|
||||||
|
/// paths starting with '/' to be relative.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">The path to examine.</param>
|
||||||
|
/// <returns>True if the path is absolute.</returns>
|
||||||
|
protected internal virtual bool IsPathRooted([NotNull] string path)
|
||||||
|
{
|
||||||
|
// We consider paths to be rooted if they start with '<<VolumeLetter>>:' and do
|
||||||
|
// not start with '\' or '/'. In those cases, even that the paths are 'traditionally'
|
||||||
|
// rooted, we consider them to be relative.
|
||||||
|
// In Unix rooted paths start with '/' which is not supported by this action result
|
||||||
|
// by default.
|
||||||
|
|
||||||
|
return Path.IsPathRooted(path) && (IsNetworkPath(path) || !StartsWithForwardOrBackSlash(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool StartsWithForwardOrBackSlash(string path)
|
||||||
|
{
|
||||||
|
return path.StartsWith("/", StringComparison.Ordinal) ||
|
||||||
|
path.StartsWith("\\", StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsNetworkPath(string path)
|
||||||
|
{
|
||||||
|
return path.StartsWith("//", StringComparison.Ordinal) ||
|
||||||
|
path.StartsWith("\\\\", StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IFileSystem GetFileSystem(IServiceProvider requestServices)
|
||||||
|
{
|
||||||
|
if (FileSystem != null)
|
||||||
|
{
|
||||||
|
return FileSystem;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For right now until we can use IWebRootFileSystemProvider, see
|
||||||
|
// https://github.com/aspnet/Hosting/issues/86 for details.
|
||||||
|
var hostingEnvironment = requestServices.GetService<IHostingEnvironment>();
|
||||||
|
FileSystem = new PhysicalFileSystem(hostingEnvironment.WebRoot);
|
||||||
|
|
||||||
|
return FileSystem;
|
||||||
|
}
|
||||||
|
|
||||||
private static async Task CopyStreamToResponse(
|
private static async Task CopyStreamToResponse(
|
||||||
string fileName,
|
string fileName,
|
||||||
HttpResponse response,
|
HttpResponse response,
|
||||||
|
|
|
||||||
|
|
@ -1515,19 +1515,19 @@ namespace Microsoft.AspNet.Mvc.Core
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "The path to the file must be absolute: {0}"
|
/// Could not find file: {0}
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string FileResult_InvalidPathType_RelativeOrVirtualPath
|
internal static string FileResult_InvalidPath
|
||||||
{
|
{
|
||||||
get { return GetString("FileResult_InvalidPathType_RelativeOrVirtualPath"); }
|
get { return GetString("FileResult_InvalidPath"); }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "The path to the file must be absolute: {0}"
|
/// Could not find file: {0}
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string FormatFileResult_InvalidPathType_RelativeOrVirtualPath(object p0)
|
internal static string FormatFileResult_InvalidPath(object p0)
|
||||||
{
|
{
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("FileResult_InvalidPathType_RelativeOrVirtualPath"), p0);
|
return string.Format(CultureInfo.CurrentCulture, GetString("FileResult_InvalidPath"), p0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetString(string name, params string[] formatterNames)
|
private static string GetString(string name, params string[] formatterNames)
|
||||||
|
|
|
||||||
|
|
@ -409,8 +409,8 @@
|
||||||
<value>Multiple actions matched. The following actions matched route data and had all constraints satisfied:{0}{0}{1}</value>
|
<value>Multiple actions matched. The following actions matched route data and had all constraints satisfied:{0}{0}{1}</value>
|
||||||
<comment>0 is the newline - 1 is a newline separate list of action display names</comment>
|
<comment>0 is the newline - 1 is a newline separate list of action display names</comment>
|
||||||
</data>
|
</data>
|
||||||
<data name="FileResult_InvalidPathType_RelativeOrVirtualPath" xml:space="preserve">
|
<data name="FileResult_InvalidPath" xml:space="preserve">
|
||||||
<value>"The path to the file must be absolute: {0}"</value>
|
<value>Could not find file: {0}</value>
|
||||||
<comment>{0} is the value for the provided path</comment>
|
<comment>{0} is the value for the provided path</comment>
|
||||||
</data>
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.AspNet.FileSystems": "1.0.0-*",
|
"Microsoft.AspNet.FileSystems": "1.0.0-*",
|
||||||
"Microsoft.AspNet.Http": "1.0.0-*",
|
"Microsoft.AspNet.Hosting": "1.0.0-*",
|
||||||
"Microsoft.AspNet.Mvc.HeaderValueAbstractions": "1.0.0-*",
|
"Microsoft.AspNet.Mvc.HeaderValueAbstractions": "1.0.0-*",
|
||||||
"Microsoft.AspNet.Mvc.Common": { "version": "6.0.0-*", "type": "build" },
|
"Microsoft.AspNet.Mvc.Common": { "version": "6.0.0-*", "type": "build" },
|
||||||
"Microsoft.AspNet.Mvc.ModelBinding": "6.0.0-*",
|
"Microsoft.AspNet.Mvc.ModelBinding": "6.0.0-*",
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,18 @@
|
||||||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Hosting;
|
||||||
using Microsoft.AspNet.HttpFeature;
|
using Microsoft.AspNet.HttpFeature;
|
||||||
|
using Microsoft.AspNet.FileSystems;
|
||||||
using Microsoft.AspNet.PipelineCore;
|
using Microsoft.AspNet.PipelineCore;
|
||||||
using Microsoft.AspNet.Routing;
|
using Microsoft.AspNet.Routing;
|
||||||
|
using Microsoft.Framework.DependencyInjection;
|
||||||
|
using Microsoft.Framework.DependencyInjection.Fallback;
|
||||||
|
using Microsoft.Framework.Runtime;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
|
|
@ -30,7 +36,8 @@ namespace Microsoft.AspNet.Mvc
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var path = Path.GetFullPath(Path.Combine("TestFiles", "FilePathResultTestFile.txt"));
|
var path = Path.GetFullPath(Path.Combine("TestFiles", "FilePathResultTestFile.txt"));
|
||||||
var result = new FilePathResult(path, "text/plain");
|
var fileSystem = new PhysicalFileSystem(Path.GetFullPath("."));
|
||||||
|
var result = new FilePathResult(path, "text/plain", fileSystem);
|
||||||
|
|
||||||
var httpContext = new DefaultHttpContext();
|
var httpContext = new DefaultHttpContext();
|
||||||
httpContext.Response.Body = new MemoryStream();
|
httpContext.Response.Body = new MemoryStream();
|
||||||
|
|
@ -47,12 +54,42 @@ namespace Microsoft.AspNet.Mvc
|
||||||
Assert.Equal("FilePathResultTestFile contents", contents);
|
Assert.Equal("FilePathResultTestFile contents", contents);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ExecuteResultAsync_FallsBackToThePhysicalFileSystem_IfNoFileSystemIsPresent()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var path = Path.Combine("TestFiles", "FilePathResultTestFile.txt");
|
||||||
|
var result = new FilePathResult(path, "text/plain");
|
||||||
|
|
||||||
|
var appEnvironment = new Mock<IHostingEnvironment>();
|
||||||
|
appEnvironment.Setup(app => app.WebRoot)
|
||||||
|
.Returns(Directory.GetCurrentDirectory());
|
||||||
|
|
||||||
|
var httpContext = new DefaultHttpContext();
|
||||||
|
httpContext.Response.Body = new MemoryStream();
|
||||||
|
httpContext.RequestServices = new ServiceCollection()
|
||||||
|
.AddInstance<IHostingEnvironment>(appEnvironment.Object)
|
||||||
|
.BuildServiceProvider();
|
||||||
|
|
||||||
|
var context = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await result.ExecuteResultAsync(context);
|
||||||
|
httpContext.Response.Body.Position = 0;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(httpContext.Response.Body);
|
||||||
|
var contents = await new StreamReader(httpContext.Response.Body).ReadToEndAsync();
|
||||||
|
Assert.Equal("FilePathResultTestFile contents", contents);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ExecuteResultAsync_CallsSendFileAsync_IfIHttpSendFilePresent()
|
public async Task ExecuteResultAsync_CallsSendFileAsync_IfIHttpSendFilePresent()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var path = Path.GetFullPath(Path.Combine("TestFiles", "FilePathResultTestFile.txt"));
|
var path = Path.GetFullPath(Path.Combine("TestFiles", "FilePathResultTestFile.txt"));
|
||||||
var result = new FilePathResult(path, "text/plain");
|
var fileSystem = new PhysicalFileSystem(Path.GetFullPath("."));
|
||||||
|
var result = new FilePathResult(path, "text/plain", fileSystem);
|
||||||
|
|
||||||
var sendFileMock = new Mock<IHttpSendFileFeature>();
|
var sendFileMock = new Mock<IHttpSendFileFeature>();
|
||||||
sendFileMock
|
sendFileMock
|
||||||
|
|
@ -70,5 +107,301 @@ namespace Microsoft.AspNet.Mvc
|
||||||
// Assert
|
// Assert
|
||||||
sendFileMock.Verify();
|
sendFileMock.Verify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ExecuteResultAsync_WorksWithAbsolutePaths_UsingBackSlash()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
// path will be C:\...\TestFiles\FilePathResultTestFile.txt
|
||||||
|
var path = Path.GetFullPath(Path.Combine(".", "TestFiles", "FilePathResultTestFile.txt"));
|
||||||
|
// We want ot ensure that the path that we provide has backslashes to ensure they get normalized into
|
||||||
|
// forward slashes.
|
||||||
|
path = path.Replace('/', '\\');
|
||||||
|
|
||||||
|
// Point the FileSystemRoot to a subfolder
|
||||||
|
var fileSystem = new PhysicalFileSystem(Path.GetFullPath("Utils"));
|
||||||
|
var result = new FilePathResult(path, "text/plain", fileSystem);
|
||||||
|
|
||||||
|
var httpContext = new DefaultHttpContext();
|
||||||
|
httpContext.Response.Body = new MemoryStream();
|
||||||
|
|
||||||
|
var context = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await result.ExecuteResultAsync(context);
|
||||||
|
httpContext.Response.Body.Position = 0;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(httpContext.Response.Body);
|
||||||
|
var contents = await new StreamReader(httpContext.Response.Body).ReadToEndAsync();
|
||||||
|
Assert.Equal("FilePathResultTestFile contents", contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ExecuteResultAsync_WorksWithAbsolutePaths_UsingForwardSlash()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
// path will be C:/.../TestFiles/FilePathResultTestFile.txt
|
||||||
|
var path = Path.GetFullPath(Path.Combine(".", "TestFiles", "FilePathResultTestFile.txt"));
|
||||||
|
path = path.Replace(@"\", "/");
|
||||||
|
|
||||||
|
// Point the FileSystemRoot to a subfolder
|
||||||
|
var fileSystem = new PhysicalFileSystem(Path.GetFullPath("Utils"));
|
||||||
|
var result = new FilePathResult(path, "text/plain", fileSystem);
|
||||||
|
|
||||||
|
var httpContext = new DefaultHttpContext();
|
||||||
|
httpContext.Response.Body = new MemoryStream();
|
||||||
|
|
||||||
|
var context = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await result.ExecuteResultAsync(context);
|
||||||
|
httpContext.Response.Body.Position = 0;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(httpContext.Response.Body);
|
||||||
|
var contents = await new StreamReader(httpContext.Response.Body).ReadToEndAsync();
|
||||||
|
Assert.Equal("FilePathResultTestFile contents", contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
// Root of the file system, forward slash and back slash
|
||||||
|
[InlineData("FilePathResultTestFile.txt", "TestFiles/FilePathResultTestFile.txt")]
|
||||||
|
[InlineData("/FilePathResultTestFile.txt", "TestFiles/FilePathResultTestFile.txt")]
|
||||||
|
[InlineData("\\FilePathResultTestFile.txt", "TestFiles/FilePathResultTestFile.txt")]
|
||||||
|
// Paths with subfolders and mixed slash kinds
|
||||||
|
[InlineData("/SubFolder/SubFolderTestFile.txt", "TestFiles/SubFolder/SubFolderTestFile.txt")]
|
||||||
|
[InlineData("\\SubFolder\\SubFolderTestFile.txt", "TestFiles/SubFolder/SubFolderTestFile.txt")]
|
||||||
|
[InlineData("/SubFolder\\SubFolderTestFile.txt", "TestFiles/SubFolder/SubFolderTestFile.txt")]
|
||||||
|
[InlineData("\\SubFolder/SubFolderTestFile.txt", "TestFiles/SubFolder/SubFolderTestFile.txt")]
|
||||||
|
// '.' has no special meaning
|
||||||
|
[InlineData("./FilePathResultTestFile.txt", "TestFiles/FilePathResultTestFile.txt")]
|
||||||
|
[InlineData(".\\FilePathResultTestFile.txt", "TestFiles/FilePathResultTestFile.txt")]
|
||||||
|
[InlineData("./SubFolder/SubFolderTestFile.txt", "TestFiles/SubFolder/SubFolderTestFile.txt")]
|
||||||
|
[InlineData(".\\SubFolder\\SubFolderTestFile.txt", "TestFiles/SubFolder/SubFolderTestFile.txt")]
|
||||||
|
[InlineData("./SubFolder\\SubFolderTestFile.txt", "TestFiles/SubFolder/SubFolderTestFile.txt")]
|
||||||
|
[InlineData(".\\SubFolder/SubFolderTestFile.txt", "TestFiles/SubFolder/SubFolderTestFile.txt")]
|
||||||
|
// Traverse to the parent directory and back to the file system directory
|
||||||
|
[InlineData("..\\TestFiles/FilePathResultTestFile.txt", "TestFiles/FilePathResultTestFile.txt")]
|
||||||
|
[InlineData("..\\TestFiles\\FilePathResultTestFile.txt", "TestFiles/FilePathResultTestFile.txt")]
|
||||||
|
[InlineData("..\\TestFiles/SubFolder/SubFolderTestFile.txt", "TestFiles/SubFolder/SubFolderTestFile.txt")]
|
||||||
|
[InlineData("..\\TestFiles\\SubFolder\\SubFolderTestFile.txt", "TestFiles/SubFolder/SubFolderTestFile.txt")]
|
||||||
|
[InlineData("..\\TestFiles/SubFolder\\SubFolderTestFile.txt", "TestFiles/SubFolder/SubFolderTestFile.txt")]
|
||||||
|
[InlineData("..\\TestFiles\\SubFolder/SubFolderTestFile.txt", "TestFiles/SubFolder/SubFolderTestFile.txt")]
|
||||||
|
// '~/' and '~\' mean the application root folder
|
||||||
|
[InlineData("~/FilePathResultTestFile.txt", "TestFiles/FilePathResultTestFile.txt")]
|
||||||
|
[InlineData("~/SubFolder/SubFolderTestFile.txt", "TestFiles/SubFolder/SubFolderTestFile.txt")]
|
||||||
|
[InlineData("~/SubFolder\\SubFolderTestFile.txt", "TestFiles/SubFolder/SubFolderTestFile.txt")]
|
||||||
|
public void GetFilePath_Resolves_RelativePaths(string path, string relativePathToFile)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var fileSystem = new PhysicalFileSystem(Path.GetFullPath("./TestFiles"));
|
||||||
|
var expectedPath = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), relativePathToFile));
|
||||||
|
var filePathResult = new FilePathResult(path, "text/plain", fileSystem);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = filePathResult.ResolveFilePath(fileSystem);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expectedPath, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("~\\FilePathResultTestFile.txt", "TestFiles/FilePathResultTestFile.txt")]
|
||||||
|
[InlineData("~\\SubFolder\\SubFolderTestFile.txt", "TestFiles/SubFolder/SubFolderTestFile.txt")]
|
||||||
|
[InlineData("~\\SubFolder/SubFolderTestFile.txt", "TestFiles/SubFolder/SubFolderTestFile.txt")]
|
||||||
|
public void GetFilePath_FailsToResolve_InvalidVirtualPaths(string path, string relativePathToFile)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var fileSystem = new PhysicalFileSystem(Path.GetFullPath("./TestFiles"));
|
||||||
|
var expectedPath = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), relativePathToFile));
|
||||||
|
var filePathResult = new FilePathResult(path, "text/plain", fileSystem);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var ex = Assert.Throws<FileNotFoundException>(() => filePathResult.ResolveFilePath(fileSystem));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("Could not find file: " + path, ex.Message);
|
||||||
|
Assert.Equal(path, ex.FileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
// Root of the file system, forward slash and back slash
|
||||||
|
[InlineData("FilePathResultTestFile.txt")]
|
||||||
|
[InlineData("/FilePathResultTestFile.txt")]
|
||||||
|
[InlineData("\\FilePathResultTestFile.txt")]
|
||||||
|
// Paths with subfolders and mixed slash kinds
|
||||||
|
[InlineData("/SubFolder/SubFolderTestFile.txt")]
|
||||||
|
[InlineData("\\SubFolder\\SubFolderTestFile.txt")]
|
||||||
|
[InlineData("/SubFolder\\SubFolderTestFile.txt")]
|
||||||
|
[InlineData("\\SubFolder/SubFolderTestFile.txt")]
|
||||||
|
// '.' has no special meaning
|
||||||
|
[InlineData("./FilePathResultTestFile.txt")]
|
||||||
|
[InlineData(".\\FilePathResultTestFile.txt")]
|
||||||
|
[InlineData("./SubFolder/SubFolderTestFile.txt")]
|
||||||
|
[InlineData(".\\SubFolder\\SubFolderTestFile.txt")]
|
||||||
|
[InlineData("./SubFolder\\SubFolderTestFile.txt")]
|
||||||
|
[InlineData(".\\SubFolder/SubFolderTestFile.txt")]
|
||||||
|
// Traverse to the parent directory and back to the file system directory
|
||||||
|
[InlineData("..\\TestFiles/FilePathResultTestFile.txt")]
|
||||||
|
[InlineData("..\\TestFiles\\FilePathResultTestFile.txt")]
|
||||||
|
[InlineData("..\\TestFiles/SubFolder/SubFolderTestFile.txt")]
|
||||||
|
[InlineData("..\\TestFiles\\SubFolder\\SubFolderTestFile.txt")]
|
||||||
|
[InlineData("..\\TestFiles/SubFolder\\SubFolderTestFile.txt")]
|
||||||
|
[InlineData("..\\TestFiles\\SubFolder/SubFolderTestFile.txt")]
|
||||||
|
// '~/' and '~\' mean the application root folder
|
||||||
|
[InlineData("~/FilePathResultTestFile.txt")]
|
||||||
|
[InlineData("~/SubFolder/SubFolderTestFile.txt")]
|
||||||
|
[InlineData("~/SubFolder\\SubFolderTestFile.txt")]
|
||||||
|
public void GetFilePath_ThrowsFileNotFound_IfItCanNotFindTheFile(string path)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
|
||||||
|
// Point the IFileSystem root to a different subfolder
|
||||||
|
var fileSystem = new PhysicalFileSystem(Path.GetFullPath("./Utils"));
|
||||||
|
var filePathResult = new FilePathResult(path, "text/plain", fileSystem);
|
||||||
|
var expectedFileName = path.TrimStart('~').Replace('\\', '/');
|
||||||
|
var expectedMessage = "Could not find file: " + expectedFileName;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var ex = Assert.Throws<FileNotFoundException>(() => filePathResult.ResolveFilePath(fileSystem));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expectedMessage, ex.Message);
|
||||||
|
Assert.Equal(expectedFileName, ex.FileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("FilePathResultTestFile.txt", "FilePathResultTestFile.txt")]
|
||||||
|
[InlineData("/FilePathResultTestFile.txt", "/FilePathResultTestFile.txt")]
|
||||||
|
[InlineData("\\FilePathResultTestFile.txt", "/FilePathResultTestFile.txt")]
|
||||||
|
// Paths with subfolders and mixed slash kinds
|
||||||
|
[InlineData("/SubFolder/SubFolderTestFile.txt", "/SubFolder/SubFolderTestFile.txt")]
|
||||||
|
[InlineData("\\SubFolder\\SubFolderTestFile.txt", "/SubFolder/SubFolderTestFile.txt")]
|
||||||
|
[InlineData("/SubFolder\\SubFolderTestFile.txt", "/SubFolder/SubFolderTestFile.txt")]
|
||||||
|
[InlineData("\\SubFolder/SubFolderTestFile.txt", "/SubFolder/SubFolderTestFile.txt")]
|
||||||
|
// '.' has no special meaning
|
||||||
|
[InlineData("./FilePathResultTestFile.txt", "./FilePathResultTestFile.txt")]
|
||||||
|
[InlineData(".\\FilePathResultTestFile.txt", "./FilePathResultTestFile.txt")]
|
||||||
|
[InlineData("./SubFolder/SubFolderTestFile.txt", "./SubFolder/SubFolderTestFile.txt")]
|
||||||
|
[InlineData(".\\SubFolder\\SubFolderTestFile.txt", "./SubFolder/SubFolderTestFile.txt")]
|
||||||
|
[InlineData("./SubFolder\\SubFolderTestFile.txt", "./SubFolder/SubFolderTestFile.txt")]
|
||||||
|
[InlineData(".\\SubFolder/SubFolderTestFile.txt", "./SubFolder/SubFolderTestFile.txt")]
|
||||||
|
// Traverse to the parent directory and back to the file system directory
|
||||||
|
[InlineData("..\\TestFiles/FilePathResultTestFile.txt", "../TestFiles/FilePathResultTestFile.txt")]
|
||||||
|
[InlineData("..\\TestFiles\\FilePathResultTestFile.txt", "../TestFiles/FilePathResultTestFile.txt")]
|
||||||
|
[InlineData("..\\TestFiles/SubFolder/SubFolderTestFile.txt", "../TestFiles/SubFolder/SubFolderTestFile.txt")]
|
||||||
|
[InlineData("..\\TestFiles\\SubFolder\\SubFolderTestFile.txt", "../TestFiles/SubFolder/SubFolderTestFile.txt")]
|
||||||
|
[InlineData("..\\TestFiles/SubFolder\\SubFolderTestFile.txt", "../TestFiles/SubFolder/SubFolderTestFile.txt")]
|
||||||
|
[InlineData("..\\TestFiles\\SubFolder/SubFolderTestFile.txt", "../TestFiles/SubFolder/SubFolderTestFile.txt")]
|
||||||
|
// Absolute paths
|
||||||
|
[InlineData("C:\\Folder\\SubFolder\\File.txt", "C:/Folder/SubFolder/File.txt")]
|
||||||
|
[InlineData("C:/Folder/SubFolder/File.txt", "C:/Folder/SubFolder/File.txt")]
|
||||||
|
[InlineData("\\\\NetworkLocation\\Folder\\SubFolder\\File.txt", "//NetworkLocation/Folder/SubFolder/File.txt")]
|
||||||
|
[InlineData("//NetworkLocation/Folder/SubFolder/File.txt", "//NetworkLocation/Folder/SubFolder/File.txt")]
|
||||||
|
public void NormalizePath_ConvertsBackSlashes_IntoForwardSlashes(string path, string expectedPath)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var fileResult = new FilePathResult(path, "text/plain", Mock.Of<IFileSystem>());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var normalizedPath = fileResult.NormalizePath(path);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expectedPath, normalizedPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
// '~/' and '~\' mean the application root folder
|
||||||
|
[InlineData("~/FilePathResultTestFile.txt", "/FilePathResultTestFile.txt")]
|
||||||
|
[InlineData("~/SubFolder/SubFolderTestFile.txt", "/SubFolder/SubFolderTestFile.txt")]
|
||||||
|
[InlineData("~/SubFolder\\SubFolderTestFile.txt", "/SubFolder/SubFolderTestFile.txt")]
|
||||||
|
public void NormalizePath_ConvertsVirtualPaths_IntoRelativePaths(string path, string expectedPath)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var fileResult = new FilePathResult(path, "text/plain", Mock.Of<IFileSystem>());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var normalizedPath = fileResult.NormalizePath(path);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expectedPath, normalizedPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
// '~/' and '~\' mean the application root folder
|
||||||
|
[InlineData("~\\FilePathResultTestFile.txt")]
|
||||||
|
[InlineData("~\\SubFolder\\SubFolderTestFile.txt")]
|
||||||
|
[InlineData("~\\SubFolder/SubFolderTestFile.txt")]
|
||||||
|
public void NormalizePath_DoesNotConvert_InvalidVirtualPathsIntoRelativePaths(string path)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var fileResult = new FilePathResult(path, "text/plain", Mock.Of<IFileSystem>());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var normalizedPath = fileResult.NormalizePath(path);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(path, normalizedPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("C:\\Folder\\SubFolder\\File.txt")]
|
||||||
|
[InlineData("C:/Folder/SubFolder/File.txt")]
|
||||||
|
[InlineData("\\\\NetworkLocation\\Folder\\SubFolder\\File.txt")]
|
||||||
|
[InlineData("//NetworkLocation/Folder/SubFolder/File.txt")]
|
||||||
|
public void IsPathRooted_ReturnsTrue_ForAbsolutePaths(string path)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var fileResult = new FilePathResult(path, "text/plain", Mock.Of<IFileSystem>());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var isRooted = fileResult.IsPathRooted(path);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(isRooted);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("FilePathResultTestFile.txt")]
|
||||||
|
[InlineData("/FilePathResultTestFile.txt")]
|
||||||
|
[InlineData("\\FilePathResultTestFile.txt")]
|
||||||
|
// Paths with subfolders and mixed slash kinds
|
||||||
|
[InlineData("/SubFolder/SubFolderTestFile.txt")]
|
||||||
|
[InlineData("\\SubFolder\\SubFolderTestFile.txt")]
|
||||||
|
[InlineData("/SubFolder\\SubFolderTestFile.txt")]
|
||||||
|
[InlineData("\\SubFolder/SubFolderTestFile.txt")]
|
||||||
|
// '.' has no special meaning
|
||||||
|
[InlineData("./FilePathResultTestFile.txt")]
|
||||||
|
[InlineData(".\\FilePathResultTestFile.txt")]
|
||||||
|
[InlineData("./SubFolder/SubFolderTestFile.txt")]
|
||||||
|
[InlineData(".\\SubFolder\\SubFolderTestFile.txt")]
|
||||||
|
[InlineData("./SubFolder\\SubFolderTestFile.txt")]
|
||||||
|
[InlineData(".\\SubFolder/SubFolderTestFile.txt")]
|
||||||
|
// Traverse to the parent directory and back to the file system directory
|
||||||
|
[InlineData("..\\TestFiles/FilePathResultTestFile.txt")]
|
||||||
|
[InlineData("..\\TestFiles\\FilePathResultTestFile.txt")]
|
||||||
|
[InlineData("..\\TestFiles/SubFolder/SubFolderTestFile.txt")]
|
||||||
|
[InlineData("..\\TestFiles\\SubFolder\\SubFolderTestFile.txt")]
|
||||||
|
[InlineData("..\\TestFiles/SubFolder\\SubFolderTestFile.txt")]
|
||||||
|
[InlineData("..\\TestFiles\\SubFolder/SubFolderTestFile.txt")]
|
||||||
|
// '~/' and '~\' mean the application root folder
|
||||||
|
[InlineData("~/FilePathResultTestFile.txt")]
|
||||||
|
[InlineData("~\\FilePathResultTestFile.txt")]
|
||||||
|
[InlineData("~/SubFolder/SubFolderTestFile.txt")]
|
||||||
|
[InlineData("~\\SubFolder\\SubFolderTestFile.txt")]
|
||||||
|
[InlineData("~/SubFolder\\SubFolderTestFile.txt")]
|
||||||
|
[InlineData("~\\SubFolder/SubFolderTestFile.txt")]
|
||||||
|
public void IsPathRooted_ReturnsFalse_ForRelativePaths(string path)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var fileResult = new FilePathResult(path, "text/plain", Mock.Of<IFileSystem>());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var isRooted = fileResult.IsPathRooted(path);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(isRooted);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
FilePathResultTestFile contents
|
||||||
|
|
@ -7,5 +7,6 @@
|
||||||
"frameworks": {
|
"frameworks": {
|
||||||
"aspnet50": { },
|
"aspnet50": { },
|
||||||
"aspnetcore50": { }
|
"aspnetcore50": { }
|
||||||
}
|
},
|
||||||
|
"webroot" : "."
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue