diff --git a/src/Microsoft.AspNetCore.Http/Features/FormFeature.cs b/src/Microsoft.AspNetCore.Http/Features/FormFeature.cs index 1aee72ff..ff1fd9cc 100644 --- a/src/Microsoft.AspNetCore.Http/Features/FormFeature.cs +++ b/src/Microsoft.AspNetCore.Http/Features/FormFeature.cs @@ -181,7 +181,9 @@ private async Task InnerReadFormAsync(CancellationToken cancell await section.Body.DrainAsync(cancellationToken); var name = HeaderUtilities.RemoveQuotes(contentDisposition.Name) ?? string.Empty; - var fileName = HeaderUtilities.RemoveQuotes(contentDisposition.FileName) ?? string.Empty; + var fileName = HeaderUtilities.RemoveQuotes(contentDisposition.FileNameStar) ?? + HeaderUtilities.RemoveQuotes(contentDisposition.FileName) ?? + string.Empty; FormFile file; if (section.BaseStreamOffset.HasValue) diff --git a/test/Microsoft.AspNetCore.Http.Tests/Features/FormFeatureTests.cs b/test/Microsoft.AspNetCore.Http.Tests/Features/FormFeatureTests.cs index 79bdb9f9..7a73c1f4 100644 --- a/test/Microsoft.AspNetCore.Http.Tests/Features/FormFeatureTests.cs +++ b/test/Microsoft.AspNetCore.Http.Tests/Features/FormFeatureTests.cs @@ -63,6 +63,12 @@ public async Task ReadFormAsync_SimpleData_ReturnsParsedFormCollection(bool buff "Content-Disposition: form-data; name=\"myfile1\"; filename=\"temp.html\"\r\n" + "Content-Type: text/html\r\n" + "\r\n" + +"Hello World\r\n"; + + private const string MultipartFormEncodedFilename = "--WebKitFormBoundary5pDRpGheQXaM8k3T\r\n" + +"Content-Disposition: form-data; name=\"myfile1\"; filename=\"temp.html\"; filename*=utf-8\'\'t%c3%a9mp.html\r\n" + +"Content-Type: text/html\r\n" + +"\r\n" + "Hello World\r\n"; private const string MultipartFormWithField = @@ -78,6 +84,10 @@ public async Task ReadFormAsync_SimpleData_ReturnsParsedFormCollection(bool buff MultipartFormFile + MultipartFormEnd; + private const string MultipartFormWithEncodedFilename = + MultipartFormEncodedFilename + + MultipartFormEnd; + [Theory] [InlineData(true)] [InlineData(false)] @@ -198,6 +208,54 @@ public async Task ReadFormAsync_MultipartWithFile_ReturnsParsedFormCollection(bo await responseFeature.CompleteAsync(); } + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task ReadFormAsync_MultipartWithEncodedFilename_ReturnsParsedFormCollection(bool bufferRequest) + { + var formContent = Encoding.UTF8.GetBytes(MultipartFormWithEncodedFilename); + var context = new DefaultHttpContext(); + var responseFeature = new FakeResponseFeature(); + context.Features.Set(responseFeature); + context.Request.ContentType = MultipartContentType; + context.Request.Body = new NonSeekableReadStream(formContent); + + IFormFeature formFeature = new FormFeature(context.Request, new FormOptions() { BufferBody = bufferRequest }); + context.Features.Set(formFeature); + + var formCollection = await context.Request.ReadFormAsync(); + + Assert.NotNull(formCollection); + + // Cached + formFeature = context.Features.Get(); + Assert.NotNull(formFeature); + Assert.NotNull(formFeature.Form); + Assert.Same(formFeature.Form, formCollection); + Assert.Same(formCollection, context.Request.Form); + + // Content + Assert.Equal(0, formCollection.Count); + + Assert.NotNull(formCollection.Files); + Assert.Equal(1, formCollection.Files.Count); + + var file = formCollection.Files["myfile1"]; + Assert.Equal("myfile1", file.Name); + Assert.Equal("t\u00e9mp.html", file.FileName); + Assert.Equal("text/html", file.ContentType); + Assert.Equal(@"form-data; name=""myfile1""; filename=""temp.html""; filename*=utf-8''t%c3%a9mp.html", file.ContentDisposition); + var body = file.OpenReadStream(); + using (var reader = new StreamReader(body)) + { + Assert.True(body.CanSeek); + var content = reader.ReadToEnd(); + Assert.Equal(content, "Hello World"); + } + + await responseFeature.CompleteAsync(); + } + [Theory] [InlineData(true)] [InlineData(false)]