@@ -55,20 +55,30 @@ public class StreamingMultipartFormDataParser : IStreamingMultipartFormDataParse
5555 /// </remarks>
5656 private const int DefaultBufferSize = 4096 ;
5757
58+ /// <summary>
59+ /// The mimetypes that are considered a file by default.
60+ /// </summary>
61+ private static readonly string [ ] DefaultBinaryMimeTypes = { "application/octet-stream" } ;
62+
5863 #endregion
5964
6065 #region Fields
6166
6267 /// <summary>
6368 /// List of mimetypes that should be detected as file.
6469 /// </summary>
65- private readonly string [ ] binaryMimeTypes = { "application/octet-stream" } ;
70+ private readonly string [ ] binaryMimeTypes ;
6671
6772 /// <summary>
6873 /// The stream we are parsing.
6974 /// </summary>
7075 private readonly Stream stream ;
7176
77+ /// <summary>
78+ /// Determines if we should throw an exception when we enconter an invalid part or ignore it.
79+ /// </summary>
80+ private readonly bool ignoreInvalidParts ;
81+
7282 /// <summary>
7383 /// The boundary of the multipart message as a string.
7484 /// </summary>
@@ -118,8 +128,11 @@ public class StreamingMultipartFormDataParser : IStreamingMultipartFormDataParse
118128 /// <param name="binaryMimeTypes">
119129 /// List of mimetypes that should be detected as file.
120130 /// </param>
121- public StreamingMultipartFormDataParser ( Stream stream , Encoding encoding , int binaryBufferSize = DefaultBufferSize , string [ ] binaryMimeTypes = null )
122- : this ( stream , null , encoding , binaryBufferSize , binaryMimeTypes )
131+ /// <param name="ignoreInvalidParts">
132+ /// By default the parser will throw an exception if it encounters an invalid part. set this to true to ignore invalid parts.
133+ /// </param>
134+ public StreamingMultipartFormDataParser ( Stream stream , Encoding encoding , int binaryBufferSize = DefaultBufferSize , string [ ] binaryMimeTypes = null , bool ignoreInvalidParts = false )
135+ : this ( stream , null , encoding , binaryBufferSize , binaryMimeTypes , ignoreInvalidParts )
123136 {
124137 }
125138
@@ -144,7 +157,10 @@ public StreamingMultipartFormDataParser(Stream stream, Encoding encoding, int bi
144157 /// <param name="binaryMimeTypes">
145158 /// List of mimetypes that should be detected as file.
146159 /// </param>
147- public StreamingMultipartFormDataParser ( Stream stream , string boundary = null , Encoding encoding = null , int binaryBufferSize = DefaultBufferSize , string [ ] binaryMimeTypes = null )
160+ /// <param name="ignoreInvalidParts">
161+ /// By default the parser will throw an exception if it encounters an invalid part. set this to true to ignore invalid parts.
162+ /// </param>
163+ public StreamingMultipartFormDataParser ( Stream stream , string boundary = null , Encoding encoding = null , int binaryBufferSize = DefaultBufferSize , string [ ] binaryMimeTypes = null , bool ignoreInvalidParts = false )
148164 {
149165 if ( stream == null || stream == Stream . Null ) { throw new ArgumentNullException ( nameof ( stream ) ) ; }
150166
@@ -153,10 +169,8 @@ public StreamingMultipartFormDataParser(Stream stream, string boundary = null, E
153169 Encoding = encoding ?? Encoding . UTF8 ;
154170 BinaryBufferSize = binaryBufferSize ;
155171 readEndBoundary = false ;
156- if ( binaryMimeTypes != null )
157- {
158- this . binaryMimeTypes = binaryMimeTypes ;
159- }
172+ this . binaryMimeTypes = binaryMimeTypes ?? DefaultBinaryMimeTypes ;
173+ this . ignoreInvalidParts = ignoreInvalidParts ;
160174 }
161175
162176 #endregion
@@ -346,24 +360,43 @@ private static async Task<string> DetectBoundaryAsync(RebufferableBinaryReader r
346360 }
347361
348362 /// <summary>
349- /// Use a few assumptions to determine if a section contains a file or a "data" parameter .
363+ /// Use a few assumptions to determine if a section contains a file.
350364 /// </summary>
351365 /// <param name="parameters">The section parameters.</param>
352366 /// <returns>true if the section contains a file, false otherwise.</returns>
353- private bool IsFilePart ( IDictionary < string , string > parameters )
367+ private bool IsFilePart ( IDictionary < string , string > parameters ! ! )
354368 {
369+ // A section without any parameter is invalid. It is very likely to contain just a bunch of blank lines.
370+ if ( parameters . Count == 0 ) return false ;
371+
355372 // If a section contains filename, then it's a file.
356- if ( parameters . ContainsKey ( "filename" ) ) return true ;
373+ else if ( parameters . ContainsKey ( "filename" ) ) return true ;
357374
358375 // Check if mimetype is a binary file
359- else if ( parameters . ContainsKey ( "content-type" ) &&
360- binaryMimeTypes . Contains ( parameters [ "content-type" ] ) ) return true ;
376+ else if ( parameters . ContainsKey ( "content-type" ) && binaryMimeTypes . Contains ( parameters [ "content-type" ] ) ) return true ;
361377
362378 // If the section is missing the filename and the name, then it's a file.
363379 // For example, images in an mjpeg stream have neither a name nor a filename.
364380 else if ( ! parameters . ContainsKey ( "name" ) ) return true ;
365381
366- // In all other cases, we assume it's a "data" parameter.
382+ // Otherwise this section does not contain a file.
383+ return false ;
384+ }
385+
386+ /// <summary>
387+ /// Use a few assumptions to determine if a section contains a "data" parameter.
388+ /// </summary>
389+ /// <param name="parameters">The section parameters.</param>
390+ /// <returns>true if the section contains a data parameter, false otherwise.</returns>
391+ private bool IsParameterPart ( IDictionary < string , string > parameters ! ! )
392+ {
393+ // A section without any parameter is invalid. It is very likely to contain just a bunch of blank lines.
394+ if ( parameters . Count == 0 ) return false ;
395+
396+ // A data parameter MUST have a name.
397+ else if ( parameters . ContainsKey ( "name" ) ) return true ;
398+
399+ // Otherwise this section does not contain a data parameter.
367400 return false ;
368401 }
369402
@@ -950,6 +983,58 @@ private async Task ParseParameterPartAsync(Dictionary<string, string> parameters
950983 ParameterHandler ( part ) ;
951984 }
952985
986+ /// <summary>
987+ /// Skip a section of the stream.
988+ /// This is used when a section is deemed to be invalid and the developer has requested to ignore invalid parts.
989+ /// </summary>
990+ /// <param name="reader">
991+ /// The StreamReader to read the data from.
992+ /// </param>
993+ /// <exception cref="MultipartParseException">
994+ /// thrown if unexpected data is found such as running out of stream before hitting the boundary.
995+ /// </exception>
996+ private void SkipPart ( RebufferableBinaryReader reader )
997+ {
998+ // Our job is to consume the lines in this section and discard them
999+ string line = reader . ReadLine ( ) ;
1000+ while ( line != boundary && line != endBoundary )
1001+ {
1002+ if ( line == null ) throw new MultipartParseException ( "Unexpected end of stream. Is there an end boundary?" ) ;
1003+ line = reader . ReadLine ( ) ;
1004+ }
1005+
1006+ if ( line == endBoundary ) readEndBoundary = true ;
1007+ }
1008+
1009+ /// <summary>
1010+ /// Asynchronously skip a section of the stream.
1011+ /// This is used when a section is deemed to be invalid and the developer has requested to ignore invalid parts.
1012+ /// </summary>
1013+ /// <param name="reader">
1014+ /// The StreamReader to read the data from.
1015+ /// </param>
1016+ /// <param name="cancellationToken">
1017+ /// The cancellation token.
1018+ /// </param>
1019+ /// <returns>
1020+ /// The asynchronous task.
1021+ /// </returns>
1022+ /// <exception cref="MultipartParseException">
1023+ /// thrown if unexpected data is found such as running out of stream before hitting the boundary.
1024+ /// </exception>
1025+ private async Task SkipPartAsync ( RebufferableBinaryReader reader , CancellationToken cancellationToken = default )
1026+ {
1027+ // Our job is to consume the lines in this section and discard them
1028+ string line = await reader . ReadLineAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
1029+ while ( line != boundary && line != endBoundary )
1030+ {
1031+ if ( line == null ) throw new MultipartParseException ( "Unexpected end of stream. Is there an end boundary?" ) ;
1032+ line = await reader . ReadLineAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
1033+ }
1034+
1035+ if ( line == endBoundary ) readEndBoundary = true ;
1036+ }
1037+
9531038 /// <summary>
9541039 /// Parses the header of the next section of the multipart stream and
9551040 /// determines if it contains file data or parameter data.
@@ -1029,10 +1114,18 @@ private void ParseSection(RebufferableBinaryReader reader)
10291114 {
10301115 ParseFilePart ( parameters , reader ) ;
10311116 }
1032- else
1117+ else if ( IsParameterPart ( parameters ) )
10331118 {
10341119 ParseParameterPart ( parameters , reader ) ;
10351120 }
1121+ else if ( ignoreInvalidParts )
1122+ {
1123+ SkipPart ( reader ) ;
1124+ }
1125+ else
1126+ {
1127+ throw new MultipartParseException ( "Unable to determine the section type. Some possible reasons include: section is malformed, required parameters such as 'name', 'content-type' or 'filename' are missing, section contains nothing but empty lines." ) ;
1128+ }
10361129 }
10371130
10381131 /// <summary>
@@ -1120,10 +1213,18 @@ private async Task ParseSectionAsync(RebufferableBinaryReader reader, Cancellati
11201213 {
11211214 await ParseFilePartAsync ( parameters , reader , cancellationToken ) . ConfigureAwait ( false ) ;
11221215 }
1123- else
1216+ else if ( IsParameterPart ( parameters ) )
11241217 {
11251218 await ParseParameterPartAsync ( parameters , reader , cancellationToken ) . ConfigureAwait ( false ) ;
11261219 }
1220+ else if ( ignoreInvalidParts )
1221+ {
1222+ await SkipPartAsync ( reader ) . ConfigureAwait ( false ) ;
1223+ }
1224+ else
1225+ {
1226+ throw new MultipartParseException ( "Unable to determine the section type. Some possible reasons include: section is malformed, required parameters such as 'name', 'content-type' or 'filename' are missing, section contains nothing but empty lines." ) ;
1227+ }
11271228 }
11281229
11291230 /// <summary>
0 commit comments