Use a "FakeRoot" to allow Globbing pattern to walk up the heirarchy (#14803)

Use a "FakeRoot" to allow Globbing pattern to walk up the heirarchy
This commit is contained in:
Ryan Brandenburg 2019-10-29 16:08:04 -07:00 committed by GitHub
parent a168e50d12
commit 405f002f58
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 230 additions and 10 deletions

View File

@ -2,6 +2,10 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.FileProviders;
@ -29,7 +33,7 @@ namespace Microsoft.AspNetCore.Hosting.StaticWebAssets
public StaticWebAssetsFileProvider(string pathPrefix, string contentRoot)
{
BasePath = new PathString(pathPrefix.StartsWith("/") ? pathPrefix : "/" + pathPrefix);
BasePath = NormalizePath(pathPrefix);
InnerProvider = new PhysicalFileProvider(contentRoot);
}
@ -40,20 +44,30 @@ namespace Microsoft.AspNetCore.Hosting.StaticWebAssets
/// <inheritdoc />
public IDirectoryContents GetDirectoryContents(string subpath)
{
if (!StartsWithBasePath(subpath, out var physicalPath))
{
return NotFoundDirectoryContents.Singleton;
}
else
var modifiedSub = NormalizePath(subpath);
if (StartsWithBasePath(modifiedSub, out var physicalPath))
{
return InnerProvider.GetDirectoryContents(physicalPath.Value);
}
else if (string.Equals(subpath, string.Empty) || string.Equals(modifiedSub, "/"))
{
return new StaticWebAssetsDirectoryRoot(BasePath);
}
else if (BasePath.StartsWithSegments(modifiedSub, FilePathComparison, out var remaining))
{
return new StaticWebAssetsDirectoryRoot(remaining);
}
return NotFoundDirectoryContents.Singleton;
}
/// <inheritdoc />
public IFileInfo GetFileInfo(string subpath)
{
if (!StartsWithBasePath(subpath, out var physicalPath))
var modifiedSub = NormalizePath(subpath);
if (!StartsWithBasePath(modifiedSub, out var physicalPath))
{
return new NotFoundFileInfo(subpath);
}
@ -69,9 +83,69 @@ namespace Microsoft.AspNetCore.Hosting.StaticWebAssets
return InnerProvider.Watch(filter);
}
private static string NormalizePath(string path)
{
path = path.Replace('\\', '/');
return path != null && path.StartsWith("/") ? path : "/" + path;
}
private bool StartsWithBasePath(string subpath, out PathString rest)
{
return new PathString(subpath).StartsWithSegments(BasePath, FilePathComparison, out rest);
}
private class StaticWebAssetsDirectoryRoot : IDirectoryContents
{
private readonly string _nextSegment;
public StaticWebAssetsDirectoryRoot(PathString remainingPath)
{
// We MUST use the Value property here because it is unescaped.
_nextSegment = remainingPath.Value.Split("/", StringSplitOptions.RemoveEmptyEntries).FirstOrDefault();
}
public bool Exists => true;
public IEnumerator<IFileInfo> GetEnumerator()
{
return GenerateEnum();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GenerateEnum();
}
private IEnumerator<IFileInfo> GenerateEnum()
{
return new[] { new StaticWebAssetsFileInfo(_nextSegment) }
.Cast<IFileInfo>().GetEnumerator();
}
private class StaticWebAssetsFileInfo : IFileInfo
{
public StaticWebAssetsFileInfo(string name)
{
Name = name;
}
public bool Exists => true;
public long Length => throw new NotImplementedException();
public string PhysicalPath => throw new NotImplementedException();
public DateTimeOffset LastModified => throw new NotImplementedException();
public bool IsDirectory => true;
public string Name { get; }
public Stream CreateReadStream()
{
throw new NotImplementedException();
}
}
}
}
}

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
@ -9,7 +9,6 @@
<Compile Include="$(SharedSourceRoot)EventSource.Testing\TestEventListener.cs" />
<Compile Include="$(SharedSourceRoot)EventSource.Testing\TestCounterListener.cs" />
<Content Include="testroot\**\*" CopyToOutputDirectory="PreserveNewest" CopyToPublishDirectory="PreserveNewest" />
<None Remove="testroot\wwwroot\Static Web Assets.txt" />
<Content Include="Microsoft.AspNetCore.Hosting.StaticWebAssets.xml" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>

View File

@ -37,6 +37,75 @@ namespace Microsoft.AspNetCore.Hosting.StaticWebAssets
Assert.Equal("/_content", provider.BasePath);
}
[Theory]
[InlineData("\\", "_content")]
[InlineData("\\_content\\RazorClassLib\\Dir", "Castle.Core.dll")]
[InlineData("", "_content")]
[InlineData("/", "_content")]
[InlineData("/_content", "RazorClassLib")]
[InlineData("/_content/RazorClassLib", "Dir")]
[InlineData("/_content/RazorClassLib/Dir", "Microsoft.AspNetCore.Hosting.Tests.dll")]
[InlineData("/_content/RazorClassLib/Dir/testroot/", "TextFile.txt")]
[InlineData("/_content/RazorClassLib/Dir/testroot/wwwroot/", "README")]
public void GetDirectoryContents_WalksUpContentRoot(string searchDir, string expected)
{
// Arrange
var provider = new StaticWebAssetsFileProvider("/_content/RazorClassLib/Dir", AppContext.BaseDirectory);
// Act
var directory = provider.GetDirectoryContents(searchDir);
// Assert
Assert.NotEmpty(directory);
Assert.Contains(directory, file => string.Equals(file.Name, expected));
}
[Fact]
public void GetDirectoryContents_DoesNotFindNonExistentFiles()
{
// Arrange
var provider = new StaticWebAssetsFileProvider("/_content/RazorClassLib/", AppContext.BaseDirectory);
// Act
var directory = provider.GetDirectoryContents("/_content/RazorClassLib/False");
// Assert
Assert.Empty(directory);
}
[Theory]
[InlineData("/False/_content/RazorClassLib/")]
[InlineData("/_content/RazorClass")]
public void GetDirectoryContents_PartialMatchFails(string requestedUrl)
{
// Arrange
var provider = new StaticWebAssetsFileProvider("/_content/RazorClassLib", AppContext.BaseDirectory);
// Act
var directory = provider.GetDirectoryContents(requestedUrl);
// Assert
Assert.Empty(directory);
}
[Fact]
public void GetDirectoryContents_HandlesWhitespaceInBase()
{
// Arrange
var provider = new StaticWebAssetsFileProvider("/_content/Static Web Assets",
Path.Combine(AppContext.BaseDirectory, "testroot", "wwwroot"));
// Act
var directory = provider.GetDirectoryContents("/_content/Static Web Assets/Static Web/");
// Assert
Assert.Collection(directory,
file =>
{
Assert.Equal("Static Web.txt", file.Name);
});
}
[Fact]
public void StaticWebAssetsFileProvider_FindsFileWithSpaces()
{

View File

@ -16,6 +16,7 @@
<ItemGroup>
<Compile Include="..\..\Mvc.Formatters.Xml\test\XmlAssert.cs" />
<EmbeddedResource Include="compiler\resources\**\*" />
<None Remove="compiler\resources\TagHelpersWebSite.Home.GlobbingTagHelpers.html" />
<None Include="xunit.runner.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>

View File

@ -34,6 +34,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
public HttpClient EncodedClient { get; }
[Theory]
[InlineData("GlobbingTagHelpers")]
[InlineData("Index")]
[InlineData("About")]
[InlineData("Help")]

View File

@ -0,0 +1,39 @@

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Globbing Page - My ASP.NET Application</title>
</head>
<body>
<h1 style="font-family: cursive;">ASP.NET vNext - Globbing Page</h1>
<p>| <a href="/" style="background-color: gray;color: white;border-radius: 3px;border: 1px solid black;padding: 3px;font-family: cursive;">My Home</a>
| <a href="/home/about" style="background-color: gray;color: white;border-radius: 3px;border: 1px solid black;padding: 3px;font-family: cursive;">My About</a>
| <a href="/home/help" style="background-color: gray;color: white;border-radius: 3px;border: 1px solid black;padding: 3px;font-family: cursive;">My Help</a> |</p>
<div>
Globbing
<hr />
<footer>
<!-- File wildcard -->
<script src="/js/dist/dashboardHome.js"></script>
<!-- RazorClassLib folder wildcard -->
<script src="/_content/RazorPagesClassLibrary/razorlib/file.js"></script>
<!-- RazorClassLib deep wildcard -->
<script src="/js/dist/dashboardHome.js"></script><script src="/_content/RazorPagesClassLibrary/razorlib/file.js"></script>
<!-- RazorClassLib Exclude local -->
<script src="/_content/RazorPagesClassLibrary/razorlib/file.js"></script>
<!-- local Exclude lib-->
<script src="/js/dist/dashboardHome.js"></script>
</footer>
</div>
</body>
</html>

View File

@ -5,7 +5,7 @@
<IsTestAssetProject>true</IsTestAssetProject>
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.AspNetCore.Mvc" />
</ItemGroup>

View File

@ -0,0 +1,5 @@
window.exampleJsFunctions = {
showPrompt: function (message) {
return prompt(message, 'Type anything here')
}
};

View File

@ -25,6 +25,11 @@ namespace TagHelpersWebSite.Controllers
return View();
}
public IActionResult GlobbingTagHelpers()
{
return View();
}
public IActionResult Help()
{
return View();

View File

@ -21,6 +21,7 @@ namespace TagHelpersWebSite
public void Configure(IApplicationBuilder app)
{
app.UseRouting();
app.UseStaticFiles();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
@ -38,6 +39,7 @@ namespace TagHelpersWebSite
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
new WebHostBuilder()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseStaticWebAssets()
.UseStartup<Startup>()
.UseKestrel()
.UseIISIntegration();

View File

@ -6,6 +6,10 @@
<IsTestAssetProject>true</IsTestAssetProject>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\RazorPagesClassLibrary\RazorPagesClassLibrary.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="Microsoft.AspNetCore.Mvc" />
<Reference Include="Microsoft.AspNetCore.Server.IISIntegration" />

View File

@ -0,0 +1,19 @@
@{
ViewData["Title"] = "Globbing Page";
}
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@section footerContent {
<!-- File wildcard -->
<script asp-src-include="/js/dist/dashboard*.js"></script>
<!-- RazorClassLib folder wildcard -->
<script asp-src-include="/_content/RazorPagesClassLibrary/**/file.js"></script>
<!-- RazorClassLib deep wildcard -->
<script asp-src-include="/**/*.js"></script>
<!-- RazorClassLib Exclude local -->
<script asp-src-exclude="/js/dist/*.js" asp-src-include="**/*.js"></script>
<!-- local Exclude lib-->
<script asp-src-exclude="/_content/**/*.js" asp-src-include="**/*.js"></script>
}
Globbing

View File

@ -0,0 +1 @@