Add option to disable adding trailing slash #2449 (#12669)

Middlewares affected:
- DefaultFilesMiddleware
- DirectoryBrowserMiddleware
This commit is contained in:
Mateusz Wójcik 2019-07-30 19:55:00 +02:00 committed by Chris Ross
parent 1bff37bec1
commit c703093346
8 changed files with 189 additions and 58 deletions

View File

@ -120,12 +120,14 @@ namespace Microsoft.AspNetCore.StaticFiles.Infrastructure
{
public SharedOptions() { }
public Microsoft.Extensions.FileProviders.IFileProvider FileProvider { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
public bool RedirectToAppendTrailingSlash { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
public Microsoft.AspNetCore.Http.PathString RequestPath { get { throw null; } set { } }
}
public abstract partial class SharedOptionsBase
{
protected SharedOptionsBase(Microsoft.AspNetCore.StaticFiles.Infrastructure.SharedOptions sharedOptions) { }
public Microsoft.Extensions.FileProviders.IFileProvider FileProvider { get { throw null; } set { } }
public bool RedirectToAppendTrailingSlash { get { throw null; } set { } }
public Microsoft.AspNetCore.Http.PathString RequestPath { get { throw null; } set { } }
protected Microsoft.AspNetCore.StaticFiles.Infrastructure.SharedOptions SharedOptions { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
}

View File

@ -80,17 +80,13 @@ namespace Microsoft.AspNetCore.StaticFiles
{
// If the path matches a directory but does not end in a slash, redirect to add the slash.
// This prevents relative links from breaking.
if (!Helpers.PathEndsInSlash(context.Request.Path))
if (!Helpers.PathEndsInSlash(context.Request.Path) && _options.RedirectToAppendTrailingSlash)
{
context.Response.StatusCode = StatusCodes.Status301MovedPermanently;
var request = context.Request;
var redirect = UriHelper.BuildAbsolute(request.Scheme, request.Host, request.PathBase, request.Path + "/", request.QueryString);
context.Response.Headers[HeaderNames.Location] = redirect;
Helpers.RedirectToPathWithSlash(context);
return Task.CompletedTask;
}
// Match found, re-write the url. A later middleware will actually serve the file.
context.Request.Path = new PathString(context.Request.Path.Value + defaultFile);
context.Request.Path = new PathString(Helpers.GetPathValueWithSlash(context.Request.Path) + defaultFile);
break;
}
}

View File

@ -87,12 +87,9 @@ namespace Microsoft.AspNetCore.StaticFiles
{
// If the path matches a directory but does not end in a slash, redirect to add the slash.
// This prevents relative links from breaking.
if (!Helpers.PathEndsInSlash(context.Request.Path))
if (!Helpers.PathEndsInSlash(context.Request.Path) && _options.RedirectToAppendTrailingSlash)
{
context.Response.StatusCode = StatusCodes.Status301MovedPermanently;
var request = context.Request;
var redirect = UriHelper.BuildAbsolute(request.Scheme, request.Host, request.PathBase, request.Path + "/", request.QueryString);
context.Response.Headers[HeaderNames.Location] = redirect;
Helpers.RedirectToPathWithSlash(context);
return Task.CompletedTask;
}

View File

@ -2,9 +2,12 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.Extensions.FileProviders;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.StaticFiles
{
@ -12,7 +15,8 @@ namespace Microsoft.AspNetCore.StaticFiles
{
internal static IFileProvider ResolveFileProvider(IWebHostEnvironment hostingEnv)
{
if (hostingEnv.WebRootFileProvider == null) {
if (hostingEnv.WebRootFileProvider == null)
{
throw new InvalidOperationException("Missing FileProvider.");
}
return hostingEnv.WebRootFileProvider;
@ -28,6 +32,23 @@ namespace Microsoft.AspNetCore.StaticFiles
return path.Value.EndsWith("/", StringComparison.Ordinal);
}
internal static string GetPathValueWithSlash(PathString path)
{
if (!PathEndsInSlash(path))
{
return path.Value + "/";
}
return path.Value;
}
internal static void RedirectToPathWithSlash(HttpContext context)
{
context.Response.StatusCode = StatusCodes.Status301MovedPermanently;
var request = context.Request;
var redirect = UriHelper.BuildAbsolute(request.Scheme, request.Host, request.PathBase, request.Path + "/", request.QueryString);
context.Response.Headers[HeaderNames.Location] = redirect;
}
internal static bool TryMatchPath(HttpContext context, PathString matchUrl, bool forDirectory, out PathString subpath)
{
var path = context.Request.Path;

View File

@ -42,5 +42,10 @@ namespace Microsoft.AspNetCore.StaticFiles.Infrastructure
/// The file system used to locate resources
/// </summary>
public IFileProvider FileProvider { get; set; }
/// <summary>
/// Indicates whether to redirect to add a trailing slash at the end of path. Relative resource links may require this.
/// </summary>
public bool RedirectToAppendTrailingSlash { get; set; } = true;
}
}

View File

@ -48,5 +48,14 @@ namespace Microsoft.AspNetCore.StaticFiles.Infrastructure
get { return SharedOptions.FileProvider; }
set { SharedOptions.FileProvider = value; }
}
/// <summary>
/// Indicates whether to redirect to add a trailing slash at the end of path. Relative resource links may require this.
/// </summary>
public bool RedirectToAppendTrailingSlash
{
get { return SharedOptions.RedirectToAppendTrailingSlash; }
set { SharedOptions.RedirectToAppendTrailingSlash = value; }
}
}
}

View File

@ -38,9 +38,14 @@ namespace Microsoft.AspNetCore.StaticFiles
[InlineData("/subdir", @".", "/subdir/missing.dir")]
[InlineData("/subdir", @".", "/subdir/missing.dir/")]
[InlineData("", @"./", "/missing.dir")]
public async Task NoMatch_PassesThrough_All(string baseUrl, string baseDir, string requestUrl)
[InlineData("", @".", "/missing.dir", false)]
[InlineData("", @".", "/missing.dir/", false)]
[InlineData("/subdir", @".", "/subdir/missing.dir", false)]
[InlineData("/subdir", @".", "/subdir/missing.dir/", false)]
[InlineData("", @"./", "/missing.dir", false)]
public async Task NoMatch_PassesThrough_All(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true)
{
await NoMatch_PassesThrough(baseUrl, baseDir, requestUrl);
await NoMatch_PassesThrough(baseUrl, baseDir, requestUrl, appendTrailingSlash);
}
[ConditionalTheory]
@ -48,12 +53,14 @@ namespace Microsoft.AspNetCore.StaticFiles
[OSSkipCondition(OperatingSystems.MacOSX)]
[InlineData("", @".\", "/missing.dir")]
[InlineData("", @".\", "/Missing.dir")]
public async Task NoMatch_PassesThrough_Windows(string baseUrl, string baseDir, string requestUrl)
[InlineData("", @".\", "/missing.dir", false)]
[InlineData("", @".\", "/Missing.dir", false)]
public async Task NoMatch_PassesThrough_Windows(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true)
{
await NoMatch_PassesThrough(baseUrl, baseDir, requestUrl);
await NoMatch_PassesThrough(baseUrl, baseDir, requestUrl, appendTrailingSlash);
}
private async Task NoMatch_PassesThrough(string baseUrl, string baseDir, string requestUrl)
private async Task NoMatch_PassesThrough(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true)
{
using (var fileProvider = new PhysicalFileProvider(Path.Combine(AppContext.BaseDirectory, baseDir)))
{
@ -62,7 +69,8 @@ namespace Microsoft.AspNetCore.StaticFiles
app.UseDefaultFiles(new DefaultFilesOptions
{
RequestPath = new PathString(baseUrl),
FileProvider = fileProvider
FileProvider = fileProvider,
RedirectToAppendTrailingSlash = appendTrailingSlash
});
app.Run(context => context.Response.WriteAsync(context.Request.Path.Value));
});
@ -102,7 +110,7 @@ namespace Microsoft.AspNetCore.StaticFiles
FileProvider = fileProvider
});
app.UseEndpoints(endpoints => {});
app.UseEndpoints(endpoints => { });
},
services => { services.AddDirectoryBrowser(); services.AddRouting(); });
@ -118,9 +126,19 @@ namespace Microsoft.AspNetCore.StaticFiles
[InlineData("", @"./SubFolder", "/")]
[InlineData("", @"./SubFolder", "/你好/")]
[InlineData("", @"./SubFolder", "/你好/世界/")]
public async Task FoundDirectoryWithDefaultFile_PathModified_All(string baseUrl, string baseDir, string requestUrl)
[InlineData("", @".", "/SubFolder/", false)]
[InlineData("", @"./", "/SubFolder/", false)]
[InlineData("", @"./SubFolder", "/", false)]
[InlineData("", @"./SubFolder", "/你好/", false)]
[InlineData("", @"./SubFolder", "/你好/世界/", false)]
[InlineData("", @".", "/SubFolder", false)]
[InlineData("", @"./", "/SubFolder", false)]
[InlineData("", @"./SubFolder", "", false)]
[InlineData("", @"./SubFolder", "/你好", false)]
[InlineData("", @"./SubFolder", "/你好/世界", false)]
public async Task FoundDirectoryWithDefaultFile_PathModified_All(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true)
{
await FoundDirectoryWithDefaultFile_PathModified(baseUrl, baseDir, requestUrl);
await FoundDirectoryWithDefaultFile_PathModified(baseUrl, baseDir, requestUrl, appendTrailingSlash);
}
[ConditionalTheory]
@ -130,12 +148,20 @@ namespace Microsoft.AspNetCore.StaticFiles
[InlineData("", @".\subFolder", "/")]
[InlineData("", @".\SubFolder", "/你好/")]
[InlineData("", @".\SubFolder", "/你好/世界/")]
public async Task FoundDirectoryWithDefaultFile_PathModified_Windows(string baseUrl, string baseDir, string requestUrl)
[InlineData("", @".\", "/SubFolder/", false)]
[InlineData("", @".\subFolder", "/", false)]
[InlineData("", @".\SubFolder", "/你好/", false)]
[InlineData("", @".\SubFolder", "/你好/世界/", false)]
[InlineData("", @".\", "/SubFolder", false)]
[InlineData("", @".\subFolder", "", false)]
[InlineData("", @".\SubFolder", "/你好", false)]
[InlineData("", @".\SubFolder", "/你好/世界", false)]
public async Task FoundDirectoryWithDefaultFile_PathModified_Windows(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true)
{
await FoundDirectoryWithDefaultFile_PathModified(baseUrl, baseDir, requestUrl);
await FoundDirectoryWithDefaultFile_PathModified(baseUrl, baseDir, requestUrl, appendTrailingSlash);
}
private async Task FoundDirectoryWithDefaultFile_PathModified(string baseUrl, string baseDir, string requestUrl)
private async Task FoundDirectoryWithDefaultFile_PathModified(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true)
{
using (var fileProvider = new PhysicalFileProvider(Path.Combine(AppContext.BaseDirectory, baseDir)))
{
@ -144,14 +170,17 @@ namespace Microsoft.AspNetCore.StaticFiles
app.UseDefaultFiles(new DefaultFilesOptions
{
RequestPath = new PathString(baseUrl),
FileProvider = fileProvider
FileProvider = fileProvider,
RedirectToAppendTrailingSlash = appendTrailingSlash
});
app.Run(context => context.Response.WriteAsync(context.Request.Path.Value));
});
var response = await server.CreateClient().GetAsync(requestUrl);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(requestUrl + "default.html", await response.Content.ReadAsStringAsync()); // Should be modified
var requestUrlWithSlash = requestUrl.EndsWith("/") ? requestUrl : requestUrl + "/";
Assert.Equal(requestUrlWithSlash + "default.html", await response.Content.ReadAsStringAsync()); // Should be modified and be valid path to file
}
}
@ -202,9 +231,17 @@ namespace Microsoft.AspNetCore.StaticFiles
[InlineData("/SubFolder", @".", "/somedir/")]
[InlineData("", @"./SubFolder", "/")]
[InlineData("", @"./SubFolder/", "/")]
public async Task PostDirectory_PassesThrough_All(string baseUrl, string baseDir, string requestUrl)
[InlineData("/SubFolder", @"./", "/SubFolder/", false)]
[InlineData("/SubFolder", @".", "/somedir/", false)]
[InlineData("", @"./SubFolder", "/", false)]
[InlineData("", @"./SubFolder/", "/", false)]
[InlineData("/SubFolder", @"./", "/SubFolder", false)]
[InlineData("/SubFolder", @".", "/somedir", false)]
[InlineData("", @"./SubFolder", "", false)]
[InlineData("", @"./SubFolder/", "", false)]
public async Task PostDirectory_PassesThrough_All(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true)
{
await PostDirectory_PassesThrough(baseUrl, baseDir, requestUrl);
await PostDirectory_PassesThrough(baseUrl, baseDir, requestUrl, appendTrailingSlash);
}
[ConditionalTheory]
@ -213,24 +250,37 @@ namespace Microsoft.AspNetCore.StaticFiles
[InlineData("/SubFolder", @".\", "/SubFolder/")]
[InlineData("", @".\SubFolder", "/")]
[InlineData("", @".\SubFolder\", "/")]
public async Task PostDirectory_PassesThrough_Windows(string baseUrl, string baseDir, string requestUrl)
[InlineData("/SubFolder", @".\", "/SubFolder/", false)]
[InlineData("", @".\SubFolder", "/", false)]
[InlineData("", @".\SubFolder\", "/", false)]
[InlineData("/SubFolder", @".\", "/SubFolder", false)]
[InlineData("", @".\SubFolder", "", false)]
[InlineData("", @".\SubFolder\", "", false)]
public async Task PostDirectory_PassesThrough_Windows(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true)
{
await PostDirectory_PassesThrough(baseUrl, baseDir, requestUrl);
await PostDirectory_PassesThrough(baseUrl, baseDir, requestUrl, appendTrailingSlash);
}
private async Task PostDirectory_PassesThrough(string baseUrl, string baseDir, string requestUrl)
private async Task PostDirectory_PassesThrough(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true)
{
using (var fileProvider = new PhysicalFileProvider(Path.Combine(AppContext.BaseDirectory, baseDir)))
{
var server = StaticFilesTestServer.Create(app => app.UseDefaultFiles(new DefaultFilesOptions
{
RequestPath = new PathString(baseUrl),
FileProvider = fileProvider
FileProvider = fileProvider,
RedirectToAppendTrailingSlash = appendTrailingSlash
}));
var response = await server.CreateRequest(requestUrl).GetAsync();
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); // Passed through
}
}
[Fact]
public void Options_AppendTrailingSlashByDefault()
{
Assert.True(new DefaultFilesOptions().RedirectToAppendTrailingSlash);
}
}
}

View File

@ -56,9 +56,14 @@ namespace Microsoft.AspNetCore.StaticFiles
[InlineData("/subdir", @".", "/subdir/missing.dir")]
[InlineData("/subdir", @".", "/subdir/missing.dir/")]
[InlineData("", @"./", "/missing.dir")]
public async Task NoMatch_PassesThrough_All(string baseUrl, string baseDir, string requestUrl)
[InlineData("", @".", "/missing.dir", false)]
[InlineData("", @".", "/missing.dir/", false)]
[InlineData("/subdir", @".", "/subdir/missing.dir", false)]
[InlineData("/subdir", @".", "/subdir/missing.dir/", false)]
[InlineData("", @"./", "/missing.dir", false)]
public async Task NoMatch_PassesThrough_All(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true)
{
await NoMatch_PassesThrough(baseUrl, baseDir, requestUrl);
await NoMatch_PassesThrough(baseUrl, baseDir, requestUrl, appendTrailingSlash);
}
[ConditionalTheory]
@ -66,12 +71,14 @@ namespace Microsoft.AspNetCore.StaticFiles
[OSSkipCondition(OperatingSystems.MacOSX)]
[InlineData("", @".\", "/missing.dir")]
[InlineData("", @".\", "/Missing.dir")]
public async Task NoMatch_PassesThrough_Windows(string baseUrl, string baseDir, string requestUrl)
[InlineData("", @".\", "/missing.dir", false)]
[InlineData("", @".\", "/Missing.dir", false)]
public async Task NoMatch_PassesThrough_Windows(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true)
{
await NoMatch_PassesThrough(baseUrl, baseDir, requestUrl);
await NoMatch_PassesThrough(baseUrl, baseDir, requestUrl, appendTrailingSlash);
}
private async Task NoMatch_PassesThrough(string baseUrl, string baseDir, string requestUrl)
private async Task NoMatch_PassesThrough(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true)
{
using (var fileProvider = new PhysicalFileProvider(Path.Combine(AppContext.BaseDirectory, baseDir)))
{
@ -79,7 +86,8 @@ namespace Microsoft.AspNetCore.StaticFiles
app => app.UseDirectoryBrowser(new DirectoryBrowserOptions
{
RequestPath = new PathString(baseUrl),
FileProvider = fileProvider
FileProvider = fileProvider,
RedirectToAppendTrailingSlash = appendTrailingSlash
}),
services => services.AddDirectoryBrowser());
var response = await server.CreateRequest(requestUrl).GetAsync();
@ -117,7 +125,7 @@ namespace Microsoft.AspNetCore.StaticFiles
FileProvider = fileProvider
});
app.UseEndpoints(endpoints => {});
app.UseEndpoints(endpoints => { });
},
services => { services.AddDirectoryBrowser(); services.AddRouting(); });
@ -133,9 +141,19 @@ namespace Microsoft.AspNetCore.StaticFiles
[InlineData("/somedir", @".", "/somedir/")]
[InlineData("/somedir", @"./", "/somedir/")]
[InlineData("/somedir", @".", "/somedir/SubFolder/")]
public async Task FoundDirectory_Served_All(string baseUrl, string baseDir, string requestUrl)
[InlineData("", @".", "/", false)]
[InlineData("", @".", "/SubFolder/", false)]
[InlineData("/somedir", @".", "/somedir/", false)]
[InlineData("/somedir", @"./", "/somedir/", false)]
[InlineData("/somedir", @".", "/somedir/SubFolder/", false)]
[InlineData("", @".", "", false)]
[InlineData("", @".", "/SubFolder", false)]
[InlineData("/somedir", @".", "/somedir", false)]
[InlineData("/somedir", @"./", "/somedir", false)]
[InlineData("/somedir", @".", "/somedir/SubFolder", false)]
public async Task FoundDirectory_Served_All(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true)
{
await FoundDirectory_Served(baseUrl, baseDir, requestUrl);
await FoundDirectory_Served(baseUrl, baseDir, requestUrl, appendTrailingSlash);
}
[ConditionalTheory]
@ -143,12 +161,16 @@ namespace Microsoft.AspNetCore.StaticFiles
[OSSkipCondition(OperatingSystems.MacOSX)]
[InlineData("/somedir", @".\", "/somedir/")]
[InlineData("/somedir", @".", "/somedir/subFolder/")]
public async Task FoundDirectory_Served_Windows(string baseUrl, string baseDir, string requestUrl)
[InlineData("/somedir", @".\", "/somedir/", false)]
[InlineData("/somedir", @".", "/somedir/subFolder/", false)]
[InlineData("/somedir", @".\", "/somedir", false)]
[InlineData("/somedir", @".", "/somedir/subFolder", false)]
public async Task FoundDirectory_Served_Windows(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true)
{
await FoundDirectory_Served(baseUrl, baseDir, requestUrl);
await FoundDirectory_Served(baseUrl, baseDir, requestUrl, appendTrailingSlash);
}
private async Task FoundDirectory_Served(string baseUrl, string baseDir, string requestUrl)
private async Task FoundDirectory_Served(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true)
{
using (var fileProvider = new PhysicalFileProvider(Path.Combine(AppContext.BaseDirectory, baseDir)))
{
@ -156,7 +178,8 @@ namespace Microsoft.AspNetCore.StaticFiles
app => app.UseDirectoryBrowser(new DirectoryBrowserOptions
{
RequestPath = new PathString(baseUrl),
FileProvider = fileProvider
FileProvider = fileProvider,
RedirectToAppendTrailingSlash = appendTrailingSlash,
}),
services => services.AddDirectoryBrowser());
var response = await server.CreateRequest(requestUrl).GetAsync();
@ -215,21 +238,31 @@ namespace Microsoft.AspNetCore.StaticFiles
[InlineData("", @".", "/SubFolder/")]
[InlineData("/somedir", @".", "/somedir/")]
[InlineData("/somedir", @".", "/somedir/SubFolder/")]
public async Task PostDirectory_PassesThrough_All(string baseUrl, string baseDir, string requestUrl)
[InlineData("", @".", "/", false)]
[InlineData("", @".", "/SubFolder/", false)]
[InlineData("/somedir", @".", "/somedir/", false)]
[InlineData("/somedir", @".", "/somedir/SubFolder/", false)]
[InlineData("", @".", "", false)]
[InlineData("", @".", "/SubFolder", false)]
[InlineData("/somedir", @".", "/somedir", false)]
[InlineData("/somedir", @".", "/somedir/SubFolder", false)]
public async Task PostDirectory_PassesThrough_All(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true)
{
await PostDirectory_PassesThrough(baseUrl, baseDir, requestUrl);
await PostDirectory_PassesThrough(baseUrl, baseDir, requestUrl, appendTrailingSlash);
}
[ConditionalTheory]
[OSSkipCondition(OperatingSystems.Linux)]
[OSSkipCondition(OperatingSystems.MacOSX)]
[InlineData("/somedir", @".", "/somedir/subFolder/")]
public async Task PostDirectory_PassesThrough_Windows(string baseUrl, string baseDir, string requestUrl)
[InlineData("/somedir", @".", "/somedir/subFolder/", false)]
[InlineData("/somedir", @".", "/somedir/subFolder", false)]
public async Task PostDirectory_PassesThrough_Windows(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true)
{
await PostDirectory_PassesThrough(baseUrl, baseDir, requestUrl);
await PostDirectory_PassesThrough(baseUrl, baseDir, requestUrl, appendTrailingSlash);
}
private async Task PostDirectory_PassesThrough(string baseUrl, string baseDir, string requestUrl)
private async Task PostDirectory_PassesThrough(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true)
{
using (var fileProvider = new PhysicalFileProvider(Path.Combine(AppContext.BaseDirectory, baseDir)))
{
@ -237,7 +270,8 @@ namespace Microsoft.AspNetCore.StaticFiles
app => app.UseDirectoryBrowser(new DirectoryBrowserOptions
{
RequestPath = new PathString(baseUrl),
FileProvider = fileProvider
FileProvider = fileProvider,
RedirectToAppendTrailingSlash = appendTrailingSlash
}),
services => services.AddDirectoryBrowser());
@ -251,21 +285,31 @@ namespace Microsoft.AspNetCore.StaticFiles
[InlineData("", @".", "/SubFolder/")]
[InlineData("/somedir", @".", "/somedir/")]
[InlineData("/somedir", @".", "/somedir/SubFolder/")]
public async Task HeadDirectory_HeadersButNotBodyServed_All(string baseUrl, string baseDir, string requestUrl)
[InlineData("", @".", "/", false)]
[InlineData("", @".", "/SubFolder/", false)]
[InlineData("/somedir", @".", "/somedir/", false)]
[InlineData("/somedir", @".", "/somedir/SubFolder/", false)]
[InlineData("", @".", "", false)]
[InlineData("", @".", "/SubFolder", false)]
[InlineData("/somedir", @".", "/somedir", false)]
[InlineData("/somedir", @".", "/somedir/SubFolder", false)]
public async Task HeadDirectory_HeadersButNotBodyServed_All(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true)
{
await HeadDirectory_HeadersButNotBodyServed(baseUrl, baseDir, requestUrl);
await HeadDirectory_HeadersButNotBodyServed(baseUrl, baseDir, requestUrl, appendTrailingSlash);
}
[ConditionalTheory]
[OSSkipCondition(OperatingSystems.Linux)]
[OSSkipCondition(OperatingSystems.MacOSX)]
[InlineData("/somedir", @".", "/somedir/subFolder/")]
public async Task HeadDirectory_HeadersButNotBodyServed_Windows(string baseUrl, string baseDir, string requestUrl)
[InlineData("/somedir", @".", "/somedir/subFolder/", false)]
[InlineData("/somedir", @".", "/somedir/subFolder", false)]
public async Task HeadDirectory_HeadersButNotBodyServed_Windows(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true)
{
await HeadDirectory_HeadersButNotBodyServed(baseUrl, baseDir, requestUrl);
await HeadDirectory_HeadersButNotBodyServed(baseUrl, baseDir, requestUrl, appendTrailingSlash);
}
private async Task HeadDirectory_HeadersButNotBodyServed(string baseUrl, string baseDir, string requestUrl)
private async Task HeadDirectory_HeadersButNotBodyServed(string baseUrl, string baseDir, string requestUrl, bool appendTrailingSlash = true)
{
using (var fileProvider = new PhysicalFileProvider(Path.Combine(AppContext.BaseDirectory, baseDir)))
{
@ -273,7 +317,8 @@ namespace Microsoft.AspNetCore.StaticFiles
app => app.UseDirectoryBrowser(new DirectoryBrowserOptions
{
RequestPath = new PathString(baseUrl),
FileProvider = fileProvider
FileProvider = fileProvider,
RedirectToAppendTrailingSlash = appendTrailingSlash
}),
services => services.AddDirectoryBrowser());
@ -285,5 +330,11 @@ namespace Microsoft.AspNetCore.StaticFiles
Assert.Empty((await response.Content.ReadAsByteArrayAsync()));
}
}
[Fact]
public void Options_AppendTrailingSlashByDefault()
{
Assert.True(new DirectoryBrowserOptions().RedirectToAppendTrailingSlash);
}
}
}