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
14 changes: 11 additions & 3 deletions src/Http/WebUtilities/src/MultipartReaderStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,11 @@ public override void Write(byte[] buffer, int offset, int count)
throw new NotSupportedException();
}

public override void Write(ReadOnlySpan<byte> buffer)
{
throw new NotSupportedException();
}

public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
{
throw new NotSupportedException();
Expand Down Expand Up @@ -164,6 +169,9 @@ private int UpdatePosition(int read)
}

public override int Read(byte[] buffer, int offset, int count)
=> Read(buffer.AsSpan(offset, count));

public override int Read(Span<byte> buffer)
{
if (_finished)
{
Expand All @@ -184,7 +192,7 @@ public override int Read(byte[] buffer, int offset, int count)
if (index != 0)
{
// Sync, it's already buffered
var slice = buffer.AsSpan(offset, Math.Min(count, index));
var slice = buffer[..Math.Min(buffer.Length, index)];

var readAmount = _innerStream.Read(slice);
return UpdatePosition(readAmount);
Comment thread
SimonCropp marked this conversation as resolved.
Expand All @@ -204,7 +212,7 @@ public override int Read(byte[] buffer, int offset, int count)
// We found a possible match, return any data before it.
if (matchOffset > bufferedData.Offset)
{
read = _innerStream.Read(buffer, offset, Math.Min(count, matchOffset - bufferedData.Offset));
read = _innerStream.Read(buffer[..Math.Min(buffer.Length, matchOffset - bufferedData.Offset)]);
return UpdatePosition(read);
}

Expand All @@ -215,7 +223,7 @@ public override int Read(byte[] buffer, int offset, int count)
}

// No possible boundary match within the buffered data, return the data from the buffer.
read = _innerStream.Read(buffer, offset, Math.Min(count, bufferedData.Count));
read = _innerStream.Read(buffer[..Math.Min(buffer.Length, bufferedData.Count)]);
return UpdatePosition(read);

static int ReadBoundary(MultipartReaderStream stream, int length)
Expand Down
35 changes: 35 additions & 0 deletions src/Http/WebUtilities/test/MultipartReaderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,18 @@ public class MultipartReaderTests
"\r\n" +
"--9051914041544843365";

private static string ReadToEndUsingSpans(Stream stream, int spanLength)
{
var buffer = new byte[spanLength];
var result = new MemoryStream();
int read;
while ((read = stream.Read(buffer.AsSpan())) > 0)
{
result.Write(buffer, 0, read);
}
return Encoding.ASCII.GetString(result.ToArray());
}

private static MemoryStream MakeStream(string text)
{
return new MemoryStream(Encoding.UTF8.GetBytes(text));
Expand Down Expand Up @@ -502,6 +514,29 @@ public async Task SyncReadWithOffsetWorks()
Assert.Null(await reader.ReadNextSectionAsync());
}

[Theory]
// The default buffer holds the whole body, so every read sees the full boundary.
[InlineData(4096)]
// A buffer smaller than the body forces partial boundary matches at the end of the buffered data.
[InlineData(64)]
public async Task MultipartReader_ReadSectionBodyIntoSpan_ReadsEachSection(int bufferSize)
{
var stream = MakeStream(TwoPartBody);
var reader = new MultipartReader(Boundary, stream, bufferSize);

var section = await reader.ReadNextSectionAsync();
Assert.NotNull(section);
Assert.Equal("form-data; name=\"text\"", section.Headers["Content-Disposition"][0]);
Assert.Equal("text default", ReadToEndUsingSpans(section.Body, spanLength: 5));

section = await reader.ReadNextSectionAsync();
Assert.NotNull(section);
Assert.Equal("form-data; name=\"file1\"; filename=\"a.txt\"", section.Headers["Content-Disposition"][0]);
Assert.Equal("Content of a.txt.\r\n", ReadToEndUsingSpans(section.Body, spanLength: 5));

Assert.Null(await reader.ReadNextSectionAsync());
}

[Fact]
public async Task MultipartReader_BoundaryWithUnexpectedTrailingData_ThrowsIOException()
{
Expand Down
Loading