diff --git a/build/dependencies.props b/build/dependencies.props index f8d54b4212..53fad8ff02 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -21,6 +21,7 @@ 2.0.0 2.1.0-preview2-26130-04 15.6.0 + 4.7.49 0.8.0 2.3.1 2.4.0-beta.1.build3945 diff --git a/src/Microsoft.AspNetCore.StaticFiles/StaticFileMiddleware.cs b/src/Microsoft.AspNetCore.StaticFiles/StaticFileMiddleware.cs index d8910bcdbf..46594fc35d 100644 --- a/src/Microsoft.AspNetCore.StaticFiles/StaticFileMiddleware.cs +++ b/src/Microsoft.AspNetCore.StaticFiles/StaticFileMiddleware.cs @@ -3,6 +3,7 @@ using System; using System.Diagnostics; +using System.IO; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; @@ -67,7 +68,7 @@ namespace Microsoft.AspNetCore.StaticFiles /// /// /// - public Task Invoke(HttpContext context) + public async Task Invoke(HttpContext context) { var fileContext = new StaticFileContext(context, _options, _matchUrl, _logger, _fileProvider, _contentTypeProvider); @@ -97,21 +98,36 @@ namespace Microsoft.AspNetCore.StaticFiles case StaticFileContext.PreconditionState.ShouldProcess: if (fileContext.IsHeadMethod) { - return fileContext.SendStatusAsync(Constants.Status200Ok); + await fileContext.SendStatusAsync(Constants.Status200Ok); + return; } - if (fileContext.IsRangeRequest) + + try { - return fileContext.SendRangeAsync(); + if (fileContext.IsRangeRequest) + { + await fileContext.SendRangeAsync(); + return; + } + + await fileContext.SendAsync(); + _logger.LogFileServed(fileContext.SubPath, fileContext.PhysicalPath); + return; } - _logger.LogFileServed(fileContext.SubPath, fileContext.PhysicalPath); - return fileContext.SendAsync(); + catch (FileNotFoundException) + { + context.Response.Clear(); + } + break; case StaticFileContext.PreconditionState.NotModified: _logger.LogPathNotModified(fileContext.SubPath); - return fileContext.SendStatusAsync(Constants.Status304NotModified); + await fileContext.SendStatusAsync(Constants.Status304NotModified); + return; case StaticFileContext.PreconditionState.PreconditionFailed: _logger.LogPreconditionFailed(fileContext.SubPath); - return fileContext.SendStatusAsync(Constants.Status412PreconditionFailed); + await fileContext.SendStatusAsync(Constants.Status412PreconditionFailed); + return; default: var exception = new NotImplementedException(fileContext.GetPreconditionState().ToString()); @@ -120,7 +136,7 @@ namespace Microsoft.AspNetCore.StaticFiles } } - return _next(context); + await _next(context); } } } diff --git a/test/Microsoft.AspNetCore.StaticFiles.Tests/Microsoft.AspNetCore.StaticFiles.Tests.csproj b/test/Microsoft.AspNetCore.StaticFiles.Tests/Microsoft.AspNetCore.StaticFiles.Tests.csproj index 9a59dfca29..5826c54b9c 100644 --- a/test/Microsoft.AspNetCore.StaticFiles.Tests/Microsoft.AspNetCore.StaticFiles.Tests.csproj +++ b/test/Microsoft.AspNetCore.StaticFiles.Tests/Microsoft.AspNetCore.StaticFiles.Tests.csproj @@ -18,6 +18,7 @@ + diff --git a/test/Microsoft.AspNetCore.StaticFiles.Tests/StaticFileMiddlewareTests.cs b/test/Microsoft.AspNetCore.StaticFiles.Tests/StaticFileMiddlewareTests.cs index 7517782e51..31c6894d2b 100644 --- a/test/Microsoft.AspNetCore.StaticFiles.Tests/StaticFileMiddlewareTests.cs +++ b/test/Microsoft.AspNetCore.StaticFiles.Tests/StaticFileMiddlewareTests.cs @@ -3,16 +3,20 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Net; +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.TestHost; using Microsoft.AspNetCore.Testing.xunit; using Microsoft.Extensions.FileProviders; +using Moq; using Xunit; namespace Microsoft.AspNetCore.StaticFiles @@ -29,6 +33,59 @@ namespace Microsoft.AspNetCore.StaticFiles var response = await server.CreateClient().GetAsync("/ranges.txt"); Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + Assert.Null(response.Headers.ETag); + } + + [ConditionalFact] + [OSSkipCondition(OperatingSystems.Windows, SkipReason = "Symlinks not supported on Windows")] + public async Task ReturnsNotFoundForBrokenSymlink() + { + var badLink = Path.Combine(AppContext.BaseDirectory, Path.GetRandomFileName() + ".txt"); + + Process.Start("ln", $"-s \"/tmp/{Path.GetRandomFileName()}\" \"{badLink}\"").WaitForExit(); + Assert.True(File.Exists(badLink), "Should have created a symlink"); + + try + { + var builder = new WebHostBuilder() + .Configure(app => app.UseStaticFiles(new StaticFileOptions { ServeUnknownFileTypes = true })) + .UseWebRoot(AppContext.BaseDirectory); + var server = new TestServer(builder); + + var response = await server.CreateClient().GetAsync(Path.GetFileName(badLink)); + + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + Assert.Null(response.Headers.ETag); + } + finally + { + File.Delete(badLink); + } + } + + [Fact] + public async Task ReturnsNotFoundIfSendFileThrows() + { + var mockSendFile = new Mock(); + mockSendFile.Setup(m => m.SendFileAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .ThrowsAsync(new FileNotFoundException()); + var builder = new WebHostBuilder() + .Configure(app => + { + app.Use(async (ctx, next) => + { + ctx.Features.Set(mockSendFile.Object); + await next(); + }); + app.UseStaticFiles(new StaticFileOptions { ServeUnknownFileTypes = true }); + }) + .UseWebRoot(AppContext.BaseDirectory); + var server = new TestServer(builder); + + var response = await server.CreateClient().GetAsync("TestDocument.txt"); + + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + Assert.Null(response.Headers.ETag); } [Fact] @@ -101,6 +158,7 @@ namespace Microsoft.AspNetCore.StaticFiles Assert.Equal("text/plain", response.Content.Headers.ContentType.ToString()); Assert.True(response.Content.Headers.ContentLength == fileInfo.Length); Assert.Equal(response.Content.Headers.ContentLength, responseContent.Length); + Assert.NotNull(response.Headers.ETag); using (var stream = fileInfo.CreateReadStream()) {