Skip to content
This repository was archived by the owner on Nov 20, 2018. It is now read-only.

Commit 8196f2a

Browse files
committed
#605 Fix regressions in FormReader / FileBufferingReadStream.
1 parent ac12319 commit 8196f2a

File tree

2 files changed

+164
-6
lines changed

2 files changed

+164
-6
lines changed

src/Microsoft.AspNetCore.WebUtilities/FileBufferingReadStream.cs

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -192,10 +192,26 @@ public override int Read(byte[] buffer, int offset, int count)
192192
if (_inMemory && _buffer.Length + read > _memoryThreshold)
193193
{
194194
_inMemory = false;
195+
var oldBuffer = _buffer;
195196
_buffer = CreateTempFile();
196-
_buffer.Write(_rentedBuffer, 0, (int)_buffer.Length);
197-
_bytePool.Return(_rentedBuffer);
198-
_rentedBuffer = null;
197+
if (_rentedBuffer == null)
198+
{
199+
oldBuffer.Position = 0;
200+
var rentedBuffer = _bytePool.Rent(Math.Min((int)oldBuffer.Length, _maxRentedBufferSize));
201+
var copyRead = oldBuffer.Read(rentedBuffer, 0, rentedBuffer.Length);
202+
while (copyRead > 0)
203+
{
204+
_buffer.Write(rentedBuffer, 0, copyRead);
205+
copyRead = oldBuffer.Read(rentedBuffer, 0, rentedBuffer.Length);
206+
}
207+
_bytePool.Return(rentedBuffer);
208+
}
209+
else
210+
{
211+
_buffer.Write(_rentedBuffer, 0, (int)oldBuffer.Length);
212+
_bytePool.Return(_rentedBuffer);
213+
_rentedBuffer = null;
214+
}
199215
}
200216

201217
if (read > 0)
@@ -272,10 +288,27 @@ public override async Task<int> ReadAsync(byte[] buffer, int offset, int count,
272288
if (_inMemory && _buffer.Length + read > _memoryThreshold)
273289
{
274290
_inMemory = false;
291+
var oldBuffer = _buffer;
275292
_buffer = CreateTempFile();
276-
await _buffer.WriteAsync(_rentedBuffer, 0, (int)_buffer.Length, cancellationToken);
277-
_bytePool.Return(_rentedBuffer);
278-
_rentedBuffer = null;
293+
if (_rentedBuffer == null)
294+
{
295+
oldBuffer.Position = 0;
296+
var rentedBuffer = _bytePool.Rent(Math.Min((int)oldBuffer.Length, _maxRentedBufferSize));
297+
// oldBuffer is a MemoryStream, no need to do async reads.
298+
var copyRead = oldBuffer.Read(rentedBuffer, 0, rentedBuffer.Length);
299+
while (copyRead > 0)
300+
{
301+
await _buffer.WriteAsync(rentedBuffer, 0, copyRead, cancellationToken);
302+
copyRead = oldBuffer.Read(rentedBuffer, 0, rentedBuffer.Length);
303+
}
304+
_bytePool.Return(rentedBuffer);
305+
}
306+
else
307+
{
308+
await _buffer.WriteAsync(_rentedBuffer, 0, (int)oldBuffer.Length, cancellationToken);
309+
_bytePool.Return(_rentedBuffer);
310+
_rentedBuffer = null;
311+
}
279312
}
280313

281314
if (read > 0)

test/Microsoft.AspNetCore.Http.Tests/Features/FormFeatureTests.cs

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4+
using System;
45
using System.IO;
56
using System.Linq;
67
using System.Text;
@@ -347,5 +348,129 @@ public async Task ReadFormAsync_MultipartWithFieldAndFile_ReturnsParsedFormColle
347348

348349
await responseFeature.CompleteAsync();
349350
}
351+
352+
[Theory]
353+
// FileBufferingReadStream transitions to disk storage after 30kb, and stops pooling buffers at 1mb.
354+
[InlineData(true, 1024)]
355+
[InlineData(false, 1024)]
356+
[InlineData(true, 40 * 1024)]
357+
[InlineData(false, 40 * 1024)]
358+
[InlineData(true, 4 * 1024 * 1024)]
359+
[InlineData(false, 4 * 1024 * 1024)]
360+
public async Task ReadFormAsync_MultipartWithFieldAndMediumFile_ReturnsParsedFormCollection(bool bufferRequest, int fileSize)
361+
{
362+
var fileContents = CreateFile(fileSize);
363+
var formContent = CreateMultipartWithFormAndFile(fileContents);
364+
var context = new DefaultHttpContext();
365+
var responseFeature = new FakeResponseFeature();
366+
context.Features.Set<IHttpResponseFeature>(responseFeature);
367+
context.Request.ContentType = MultipartContentType;
368+
context.Request.Body = new NonSeekableReadStream(formContent);
369+
370+
if (bufferRequest)
371+
{
372+
context.Request.EnableRewind();
373+
}
374+
375+
// Not cached yet
376+
var formFeature = context.Features.Get<IFormFeature>();
377+
Assert.Null(formFeature);
378+
379+
var formCollection = await context.Request.ReadFormAsync();
380+
381+
Assert.NotNull(formCollection);
382+
383+
// Cached
384+
formFeature = context.Features.Get<IFormFeature>();
385+
Assert.NotNull(formFeature);
386+
Assert.NotNull(formFeature.Form);
387+
Assert.Same(formFeature.Form, formCollection);
388+
Assert.Same(formCollection, context.Request.Form);
389+
390+
// Content
391+
Assert.Equal(1, formCollection.Count);
392+
Assert.Equal("Foo", formCollection["description"]);
393+
394+
Assert.NotNull(formCollection.Files);
395+
Assert.Equal(1, formCollection.Files.Count);
396+
397+
var file = formCollection.Files["myfile1"];
398+
Assert.Equal("text/html", file.ContentType);
399+
Assert.Equal(@"form-data; name=""myfile1""; filename=""temp.html""", file.ContentDisposition);
400+
using (var body = file.OpenReadStream())
401+
{
402+
Assert.True(body.CanSeek);
403+
CompareStreams(fileContents, body);
404+
}
405+
406+
await responseFeature.CompleteAsync();
407+
}
408+
409+
private Stream CreateFile(int size)
410+
{
411+
var stream = new MemoryStream(size);
412+
var bytes = Encoding.ASCII.GetBytes("HelloWorld_ABCDEFGHIJKLMNOPQRSTUVWXYZ.abcdefghijklmnopqrstuvwxyz,0123456789;");
413+
int written = 0;
414+
while (written < size)
415+
{
416+
var toWrite = Math.Min(size - written, bytes.Length);
417+
stream.Write(bytes, 0, toWrite);
418+
written += toWrite;
419+
}
420+
stream.Position = 0;
421+
return stream;
422+
}
423+
424+
private Stream CreateMultipartWithFormAndFile(Stream fileContents)
425+
{
426+
var stream = new MemoryStream();
427+
var header =
428+
"--WebKitFormBoundary5pDRpGheQXaM8k3T\r\n" +
429+
"Content-Disposition: form-data; name=\"description\"\r\n" +
430+
"\r\n" +
431+
"Foo\r\n" +
432+
"--WebKitFormBoundary5pDRpGheQXaM8k3T\r\n" +
433+
"Content-Disposition: form-data; name=\"myfile1\"; filename=\"temp.html\"\r\n" +
434+
"Content-Type: text/html\r\n" +
435+
"\r\n";
436+
var footer =
437+
"\r\n--WebKitFormBoundary5pDRpGheQXaM8k3T--";
438+
439+
var bytes = Encoding.ASCII.GetBytes(header);
440+
stream.Write(bytes, 0, bytes.Length);
441+
442+
fileContents.CopyTo(stream);
443+
fileContents.Position = 0;
444+
445+
bytes = Encoding.ASCII.GetBytes(footer);
446+
stream.Write(bytes, 0, bytes.Length);
447+
stream.Position = 0;
448+
return stream;
449+
}
450+
451+
private void CompareStreams(Stream streamA, Stream streamB)
452+
{
453+
Assert.Equal(streamA.Length, streamB.Length);
454+
byte[] bytesA = new byte[1024], bytesB = new byte[1024];
455+
var readA = streamA.Read(bytesA, 0, bytesA.Length);
456+
var readB = streamB.Read(bytesB, 0, bytesB.Length);
457+
Assert.Equal(readA, readB);
458+
var loops = 0;
459+
while (readA > 0)
460+
{
461+
for (int i = 0; i < readA; i++)
462+
{
463+
if (bytesA[i] != bytesB[i])
464+
{
465+
throw new Exception($"Value mismatch at loop {loops}, index {i}; A:{bytesA[i]}, B:{bytesB[i]}");
466+
}
467+
}
468+
469+
readA = streamA.Read(bytesA, 0, bytesA.Length);
470+
readB = streamB.Read(bytesB, 0, bytesB.Length);
471+
Assert.Equal(readA, readB);
472+
loops++;
473+
}
474+
}
350475
}
351476
}

0 commit comments

Comments
 (0)