dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
35.23k stars 9.95k forks source link

MultipartReader cannot detect boundary if the value of the boundary parameter is enclosed in quotation marks #31648

Closed aetos382 closed 3 years ago

aetos382 commented 3 years ago

Describe the bug

When using MultipartReader to read a request, the ReadNextSectionAsync method will throw an exception if the boundary parameter value in the Content-Type header is enclosed in double-quotes.

This is probably because MultipartReader strictly compares the boundary in the request body with the value of the boundary parameter. Since they do not match, it fails to detect the boundary in the body.

If you use the MultipartContent class to construct a multipart request, it will always enclose the boundary parameter value in double-quotes. So, the MultipartReader will not be able to process requests generated by MultipartContent.

RFC 2045 §5.1 specifies that the value of the boundary parameter is the same regardless of the enclosing quotation marks.

Note that the value of a quoted string parameter does not include the quotes.
That is, the quotation marks in a quoted-string are not a part of the value of the parameter, but are merely used to delimit that parameter value.
In addition, comments are allowed in accordance with RFC 822 rules for structured header fields.
Thus the following two forms

  Content-type: text/plain; charset=us-ascii (Plain text)

  Content-type: text/plain; charset="us-ascii"

are completely equivalent.

To Reproduce

You can get the reproduction code in this repository.

Exceptions

Error Message:
   System.IO.IOException : Unexpected end of Stream, the content may have already been read by another component.
  Stack Trace:
     at Microsoft.AspNetCore.WebUtilities.MultipartReaderStream.ReadAsync(Byte[] buffer, Int32 offset, Int32 count, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.WebUtilities.StreamHelperExtensions.DrainAsync(Stream stream, ArrayPool`1 bytePool, Nullable`1 limit, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.WebUtilities.MultipartReader.ReadNextSectionAsync(CancellationToken cancellationToken)
   at MultipartReaderIssue.MultipartReaderTest.<>c__DisplayClass0_0.<<DoTest>b__1>d.MoveNext() in /mnt/c/Users/aetos/Documents/Source/GitHub/aetos382/MultipartReaderIssue/MultipartReaderIssue/MultipartReaderTest.cs:line 42
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.TestHost.HttpContextBuilder.<>c__DisplayClass23_0.<<SendAsync>g__RunRequestAsync|0>d.MoveNext()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.TestHost.ClientHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   at System.Net.Http.HttpClient.SendAsyncCore(HttpRequestMessage request, HttpCompletionOption completionOption, Boolean async, Boolean emitTelemetryStartStop, CancellationToken cancellationToken)
   at MultipartReaderIssue.MultipartReaderTest.DoTest(Boolean removeQuotes) in /mnt/c/Users/aetos/Documents/Source/GitHub/aetos382/MultipartReaderIssue/MultipartReaderIssue/MultipartReaderTest.cs:line 62
--- End of stack trace from previous location ---

Workarounds

When creating an instance of MultipartReader, remove the double-quotes from the value pass to its constructor's boundary parameter, and it will work.

For example:

var reader = new MultipartReader(boundary.Trim('"'), request.Body);

Further technical details

Runtime Environment: OS Name: Windows OS Version: 10.0.21354 OS Platform: Windows RID: win10-x64 Base Path: C:\Program Files\dotnet\sdk\5.0.202\

Host (useful for support): Version: 5.0.5 Commit: 2f740adc14

.NET SDKs installed: 5.0.201 [C:\Program Files\dotnet\sdk] 5.0.202 [C:\Program Files\dotnet\sdk]

.NET runtimes installed: Microsoft.AspNetCore.App 3.1.13 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 5.0.4 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 5.0.5 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.NETCore.App 3.1.13 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 5.0.4 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 5.0.5 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.WindowsDesktop.App 3.1.13 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Microsoft.WindowsDesktop.App 5.0.4 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Microsoft.WindowsDesktop.App 5.0.5 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]

To install additional .NET runtimes or SDKs: https://aka.ms/dotnet-download



- The IDE (VS / VS Code/ VS4Mac) you're running on, and its version
  - VS2019 16.9.3
  - VSCode 1.55.1
Tratcher commented 3 years ago

This is by design, the quotes are not part of the boundary, they're part of the header formatting and need to be removed before calling this API.

How are you getting the boundary from the request? There are a few helper apis for that.

Tratcher commented 3 years ago

https://docs.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads?view=aspnetcore-5.0#upload-large-files-with-streaming

aetos382 commented 3 years ago

I understand. If so, I would like a note to that effect to be added to the MultipartReader constructor documentation page.

Tratcher commented 3 years ago

That content comes from here: https://github.com/dotnet/aspnetcore/blob/81936a792c1ad3d063121061452e8e8f76b170d0/src/Http/WebUtilities/src/MultipartReader.cs#L35-L50 Feel free to send a clarifying PR.