diff --git a/src/Middleware/StaticFiles/src/DefaultFilesMiddleware.cs b/src/Middleware/StaticFiles/src/DefaultFilesMiddleware.cs index 1f020dbe1c..627c4b2b5f 100644 --- a/src/Middleware/StaticFiles/src/DefaultFilesMiddleware.cs +++ b/src/Middleware/StaticFiles/src/DefaultFilesMiddleware.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Endpoints; using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Options; using Microsoft.Net.Http.Headers; @@ -62,7 +63,8 @@ namespace Microsoft.AspNetCore.StaticFiles /// public Task Invoke(HttpContext context) { - if (Helpers.IsGetOrHeadMethod(context.Request.Method) + if (context.GetEndpoint() == null && + Helpers.IsGetOrHeadMethod(context.Request.Method) && Helpers.TryMatchPath(context, _matchUrl, forDirectory: true, subpath: out var subpath)) { var dirContents = _fileProvider.GetDirectoryContents(subpath.Value); diff --git a/src/Middleware/StaticFiles/src/DirectoryBrowserMiddleware.cs b/src/Middleware/StaticFiles/src/DirectoryBrowserMiddleware.cs index 1ac950ebe9..cc9c9118a6 100644 --- a/src/Middleware/StaticFiles/src/DirectoryBrowserMiddleware.cs +++ b/src/Middleware/StaticFiles/src/DirectoryBrowserMiddleware.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Endpoints; using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Options; using Microsoft.Net.Http.Headers; @@ -78,8 +79,9 @@ namespace Microsoft.AspNetCore.StaticFiles /// public Task Invoke(HttpContext context) { - // Check if the URL matches any expected paths - if (Helpers.IsGetOrHeadMethod(context.Request.Method) + // Check if the URL matches any expected paths, skip if an endpoint was selected + if (context.GetEndpoint() == null && + Helpers.IsGetOrHeadMethod(context.Request.Method) && Helpers.TryMatchPath(context, _matchUrl, forDirectory: true, subpath: out var subpath) && TryGetDirectoryInfo(subpath, out var contents)) { diff --git a/src/Middleware/StaticFiles/src/LoggerExtensions.cs b/src/Middleware/StaticFiles/src/LoggerExtensions.cs index 56afbeb5eb..8630cf8fa0 100644 --- a/src/Middleware/StaticFiles/src/LoggerExtensions.cs +++ b/src/Middleware/StaticFiles/src/LoggerExtensions.cs @@ -24,6 +24,7 @@ namespace Microsoft.AspNetCore.StaticFiles private static Action _sendingFileRange; private static Action _copyingFileRange; private static Action _writeCancelled; + private static Action _endpointMatched; static LoggerExtensions() { @@ -75,6 +76,10 @@ namespace Microsoft.AspNetCore.StaticFiles logLevel: LogLevel.Debug, eventId: new EventId(14, "WriteCancelled"), formatString: "The file transmission was cancelled"); + _endpointMatched = LoggerMessage.Define( + logLevel: LogLevel.Debug, + eventId: new EventId(15, "EndpointMatched"), + formatString: "Static files was skipped as the request already matched an endpoint."); } public static void RequestMethodNotSupported(this ILogger logger, string method) @@ -91,6 +96,11 @@ namespace Microsoft.AspNetCore.StaticFiles _fileServed(logger, virtualPath, physicalPath, null); } + public static void EndpointMatched(this ILogger logger) + { + _endpointMatched(logger, null); + } + public static void PathMismatch(this ILogger logger, string path) { _pathMismatch(logger, path, null); diff --git a/src/Middleware/StaticFiles/src/StaticFileContext.cs b/src/Middleware/StaticFiles/src/StaticFileContext.cs index 7473db4f36..e38b600f7b 100644 --- a/src/Middleware/StaticFiles/src/StaticFileContext.cs +++ b/src/Middleware/StaticFiles/src/StaticFileContext.cs @@ -8,6 +8,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Endpoints; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Headers; @@ -108,6 +109,12 @@ namespace Microsoft.AspNetCore.StaticFiles get { return _fileInfo?.PhysicalPath; } } + public bool ValidateNoEndpoint() + { + // Return true because we only want to run if there is no endpoint. + return _context.GetEndpoint() == null; + } + public bool ValidateMethod() { _method = _request.Method; diff --git a/src/Middleware/StaticFiles/src/StaticFileMiddleware.cs b/src/Middleware/StaticFiles/src/StaticFileMiddleware.cs index 1a97a5e90a..fe9dcd0119 100644 --- a/src/Middleware/StaticFiles/src/StaticFileMiddleware.cs +++ b/src/Middleware/StaticFiles/src/StaticFileMiddleware.cs @@ -72,7 +72,11 @@ namespace Microsoft.AspNetCore.StaticFiles { var fileContext = new StaticFileContext(context, _options, _matchUrl, _logger, _fileProvider, _contentTypeProvider); - if (!fileContext.ValidateMethod()) + if (!fileContext.ValidateNoEndpoint()) + { + _logger.EndpointMatched(); + } + else if (!fileContext.ValidateMethod()) { _logger.RequestMethodNotSupported(context.Request.Method); } diff --git a/src/Middleware/StaticFiles/test/FunctionalTests/StaticFileMiddlewareTests.cs b/src/Middleware/StaticFiles/test/FunctionalTests/StaticFileMiddlewareTests.cs index 7e55b79b78..7413eaf89d 100644 --- a/src/Middleware/StaticFiles/test/FunctionalTests/StaticFileMiddlewareTests.cs +++ b/src/Middleware/StaticFiles/test/FunctionalTests/StaticFileMiddlewareTests.cs @@ -14,6 +14,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Endpoints; using Microsoft.AspNetCore.Server.IntegrationTesting; using Microsoft.AspNetCore.Server.IntegrationTesting.Common; using Microsoft.AspNetCore.Testing; @@ -45,6 +46,44 @@ namespace Microsoft.AspNetCore.StaticFiles } } + [Fact] + public async Task Endpoint_PassesThrough() + { + var builder = new WebHostBuilder() + .ConfigureServices(services => services.AddSingleton(LoggerFactory)) + .UseKestrel() + .UseWebRoot(AppContext.BaseDirectory) + .Configure(app => + { + // Routing first => static files noops + app.Use(next => context => + { + // Assign an endpoint, this will make the default files noop. + context.SetEndpoint(new Endpoint((c) => + { + return context.Response.WriteAsync("Hi from endpoint."); + }, + new EndpointMetadataCollection(), + "test")); + + return next(context); + }); + + app.UseStaticFiles(); + }); + + using (var server = builder.Start(TestUrlHelper.GetTestUrl(ServerType.Kestrel))) + { + using (var client = new HttpClient { BaseAddress = new Uri(server.GetAddress()) }) + { + var response = await client.GetAsync("TestDocument.txt"); + + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("Hi from endpoint.", await response.Content.ReadAsStringAsync()); + } + } + } + [Fact] public async Task FoundFile_LastModifiedTrimsSeconds() { diff --git a/src/Middleware/StaticFiles/test/UnitTests/DefaultFilesMiddlewareTests.cs b/src/Middleware/StaticFiles/test/UnitTests/DefaultFilesMiddlewareTests.cs index 7df375d2d4..aff8e01a9c 100644 --- a/src/Middleware/StaticFiles/test/UnitTests/DefaultFilesMiddlewareTests.cs +++ b/src/Middleware/StaticFiles/test/UnitTests/DefaultFilesMiddlewareTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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; @@ -9,9 +9,11 @@ using System.Net.Http; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Endpoints; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.TestHost; using Microsoft.AspNetCore.Testing.xunit; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.FileProviders; using Xunit; @@ -72,6 +74,41 @@ namespace Microsoft.AspNetCore.StaticFiles } } + [Fact] + public async Task Endpoint_PassesThrough() + { + using (var fileProvider = new PhysicalFileProvider(Path.Combine(AppContext.BaseDirectory, "."))) + { + var server = StaticFilesTestServer.Create( + app => + { + app.Use(next => context => + { + // Assign an endpoint, this will make the default files noop. + context.SetEndpoint(new Endpoint((c) => + { + return context.Response.WriteAsync(context.Request.Path.Value); + }, + new EndpointMetadataCollection(), + "test")); + + return next(context); + }); + + app.UseDefaultFiles(new DefaultFilesOptions + { + RequestPath = new PathString(""), + FileProvider = fileProvider + }); + }, + services => services.AddDirectoryBrowser()); + + var response = await server.CreateRequest("/SubFolder/").GetAsync(); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("/SubFolder/", await response.Content.ReadAsStringAsync()); // Should not be modified + } + } + [Theory] [InlineData("", @".", "/SubFolder/")] [InlineData("", @"./", "/SubFolder/")] diff --git a/src/Middleware/StaticFiles/test/UnitTests/DirectoryBrowserMiddlewareTests.cs b/src/Middleware/StaticFiles/test/UnitTests/DirectoryBrowserMiddlewareTests.cs index 2a12c85486..8710f46c28 100644 --- a/src/Middleware/StaticFiles/test/UnitTests/DirectoryBrowserMiddlewareTests.cs +++ b/src/Middleware/StaticFiles/test/UnitTests/DirectoryBrowserMiddlewareTests.cs @@ -9,6 +9,8 @@ using System.Net.Http; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Endpoints; +using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.TestHost; using Microsoft.AspNetCore.Testing.xunit; using Microsoft.Extensions.DependencyInjection; @@ -86,6 +88,42 @@ namespace Microsoft.AspNetCore.StaticFiles } } + [Fact] + public async Task Endpoint_PassesThrough() + { + using (var fileProvider = new PhysicalFileProvider(Path.Combine(AppContext.BaseDirectory, "."))) + { + var server = StaticFilesTestServer.Create( + app => + { + app.Use(next => context => + { + // Assign an endpoint, this will make the directory browser noop + context.SetEndpoint(new Endpoint((c) => + { + c.Response.StatusCode = (int)HttpStatusCode.NotAcceptable; + return c.Response.WriteAsync("Hi from endpoint."); + }, + new EndpointMetadataCollection(), + "test")); + + return next(context); + }); + + app.UseDirectoryBrowser(new DirectoryBrowserOptions + { + RequestPath = new PathString(""), + FileProvider = fileProvider + }); + }, + services => services.AddDirectoryBrowser()); + + var response = await server.CreateRequest("/").GetAsync(); + Assert.Equal(HttpStatusCode.NotAcceptable, response.StatusCode); + Assert.Equal("Hi from endpoint.", await response.Content.ReadAsStringAsync()); + } + } + [Theory] [InlineData("", @".", "/")] [InlineData("", @".", "/SubFolder/")]