Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/Http/Http/src/Features/FormFeature.cs
Original file line number Diff line number Diff line change
Expand Up @@ -251,9 +251,11 @@ private async Task<IFormCollection> InnerReadFormAsync(CancellationToken cancell
var fileSection = new FileMultipartSection(section, contentDisposition);

// Enable buffering for the file if not already done for the full body
// Only first file can be a reference to buffered body to avoid concurrency issues
section.EnableRewind(
_request.HttpContext.Response.RegisterForDispose,
_options.MemoryBufferThreshold, _options.MultipartBodyLengthLimit);
_options.MemoryBufferThreshold, _options.MultipartBodyLengthLimit,
forceBuffering: files is not null);

// Find the end
await section.Body.DrainAsync(cancellationToken);
Expand Down
5 changes: 3 additions & 2 deletions src/Http/Http/src/Internal/BufferingHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,17 @@ public static HttpRequest EnableRewind(this HttpRequest request, int bufferThres
}

public static MultipartSection EnableRewind(this MultipartSection section, Action<IDisposable> registerForDispose,
int bufferThreshold = DefaultBufferThreshold, long? bufferLimit = null)
int bufferThreshold = DefaultBufferThreshold, long? bufferLimit = null, bool forceBuffering = false)
{
ArgumentNullException.ThrowIfNull(section);
ArgumentNullException.ThrowIfNull(registerForDispose);

var body = section.Body;
if (!body.CanSeek)
if (!body.CanSeek || forceBuffering)
{
var fileStream = new FileBufferingReadStream(body, bufferThreshold, bufferLimit, AspNetCoreTempDirectory.TempDirectoryFactory);
section.Body = fileStream;
section.BaseStreamOffset = null;
registerForDispose(fileStream);
}
return section;
Expand Down
37 changes: 37 additions & 0 deletions src/Http/Http/test/Features/FormFeatureTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,11 @@ private class MockRequestBodyPipeFeature : IRequestBodyPipeFeature
MultipartFormFile +
MultipartFormEnd;

private const string MultipartFormWithFiles =
MultipartFormFile +
MultipartFormFile +
MultipartFormEnd;

private const string MultipartFormWithFieldAndFile =
MultipartFormField +
MultipartFormFile +
Expand Down Expand Up @@ -313,6 +318,38 @@ public async Task ReadFormAsync_MultipartWithFile_ReturnsParsedFormCollection(bo
await responseFeature.CompleteAsync();
}

[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task ReadFormAsync_MultipartWithFiles_ReturnsParsedFormCollection(bool bufferRequest)
{
var formContent = Encoding.UTF8.GetBytes(MultipartFormWithFiles);
var context = new DefaultHttpContext();
var responseFeature = new FakeResponseFeature();
context.Features.Set<IHttpResponseFeature>(responseFeature);
context.Request.ContentType = MultipartContentType;
context.Request.Body = new NonSeekableReadStream(formContent);

IFormFeature formFeature = new FormFeature(context.Request, new FormOptions() { BufferBody = bufferRequest });
context.Features.Set<IFormFeature>(formFeature);

var formCollection = await context.Request.ReadFormAsync();

Assert.NotNull(formCollection);
Assert.NotNull(formCollection.Files);
Assert.Equal(2, formCollection.Files.Count);

Stream[] streams = [formCollection.Files[0].OpenReadStream(), formCollection.Files[1].OpenReadStream()];
foreach (var stream in streams.Reverse())
{
using var reader = new StreamReader(stream);
Assert.True(stream.CanSeek);
await reader.ReadToEndAsync();
}

await responseFeature.CompleteAsync();
}

[Theory]
[InlineData(true)]
[InlineData(false)]
Expand Down
Loading