@@ -14,7 +14,10 @@ namespace Microsoft.AspNetCore.Http.Features
1414{
1515 public class FormFeature : IFormFeature
1616 {
17+ private static readonly FormOptions DefaultFormOptions = new FormOptions ( ) ;
18+
1719 private readonly HttpRequest _request ;
20+ private readonly FormOptions _options ;
1821 private Task < IFormCollection > _parsedFormTask ;
1922 private IFormCollection _form ;
2023
@@ -27,15 +30,24 @@ public FormFeature(IFormCollection form)
2730
2831 Form = form ;
2932 }
30-
3133 public FormFeature ( HttpRequest request )
34+ : this ( request , DefaultFormOptions )
35+ {
36+ }
37+
38+ public FormFeature ( HttpRequest request , FormOptions options )
3239 {
3340 if ( request == null )
3441 {
3542 throw new ArgumentNullException ( nameof ( request ) ) ;
3643 }
44+ if ( options == null )
45+ {
46+ throw new ArgumentNullException ( nameof ( options ) ) ;
47+ }
3748
3849 _request = request ;
50+ _options = options ;
3951 }
4052
4153 private MediaTypeHeaderValue ContentType
@@ -118,6 +130,11 @@ private async Task<IFormCollection> InnerReadFormAsync(CancellationToken cancell
118130
119131 cancellationToken . ThrowIfCancellationRequested ( ) ;
120132
133+ if ( _options . BufferBody )
134+ {
135+ _request . EnableRewind ( _options . MemoryBufferThreshold , _options . BufferBodyLengthLimit ) ;
136+ }
137+
121138 FormCollection formFields = null ;
122139 FormFileCollection files = null ;
123140
@@ -129,14 +146,27 @@ private async Task<IFormCollection> InnerReadFormAsync(CancellationToken cancell
129146 if ( HasApplicationFormContentType ( contentType ) )
130147 {
131148 var encoding = FilterEncoding ( contentType . Encoding ) ;
132- formFields = new FormCollection ( await FormReader . ReadFormAsync ( _request . Body , encoding , cancellationToken ) ) ;
149+ using ( var formReader = new FormReader ( _request . Body , encoding )
150+ {
151+ KeyCountLimit = _options . KeyCountLimit ,
152+ KeyLengthLimit = _options . KeyLengthLimit ,
153+ ValueLengthLimit = _options . ValueLengthLimit ,
154+ } )
155+ {
156+ formFields = new FormCollection ( await formReader . ReadFormAsync ( cancellationToken ) ) ;
157+ }
133158 }
134159 else if ( HasMultipartFormContentType ( contentType ) )
135160 {
136161 var formAccumulator = new KeyValueAccumulator ( ) ;
137162
138- var boundary = GetBoundary ( contentType ) ;
139- var multipartReader = new MultipartReader ( boundary , _request . Body ) ;
163+ var boundary = GetBoundary ( contentType , _options . MultipartBoundaryLengthLimit ) ;
164+ var multipartReader = new MultipartReader ( boundary , _request . Body )
165+ {
166+ HeadersCountLimit = _options . MultipartHeadersCountLimit ,
167+ HeadersLengthLimit = _options . MultipartHeadersLengthLimit ,
168+ BodyLengthLimit = _options . MultipartBodyLengthLimit ,
169+ } ;
140170 var section = await multipartReader . ReadNextSectionAsync ( cancellationToken ) ;
141171 while ( section != null )
142172 {
@@ -145,7 +175,8 @@ private async Task<IFormCollection> InnerReadFormAsync(CancellationToken cancell
145175 if ( HasFileContentDisposition ( contentDisposition ) )
146176 {
147177 // Enable buffering for the file if not already done for the full body
148- section . EnableRewind ( _request . HttpContext . Response . RegisterForDispose ) ;
178+ section . EnableRewind ( _request . HttpContext . Response . RegisterForDispose ,
179+ _options . MemoryBufferThreshold , _options . MultipartBodyLengthLimit ) ;
149180 // Find the end
150181 await section . Body . DrainAsync ( cancellationToken ) ;
151182
@@ -169,6 +200,10 @@ private async Task<IFormCollection> InnerReadFormAsync(CancellationToken cancell
169200 {
170201 files = new FormFileCollection ( ) ;
171202 }
203+ if ( files . Count >= _options . KeyCountLimit )
204+ {
205+ throw new InvalidDataException ( $ "Form key count limit { _options . KeyCountLimit } exceeded.") ;
206+ }
172207 files . Add ( file ) ;
173208 }
174209 else if ( HasFormDataContentDisposition ( contentDisposition ) )
@@ -177,14 +212,20 @@ private async Task<IFormCollection> InnerReadFormAsync(CancellationToken cancell
177212 //
178213 // value
179214
215+ // Do not limit the key name length here because the mulipart headers length limit is already in effect.
180216 var key = HeaderUtilities . RemoveQuotes ( contentDisposition . Name ) ;
181217 MediaTypeHeaderValue mediaType ;
182218 MediaTypeHeaderValue . TryParse ( section . ContentType , out mediaType ) ;
183219 var encoding = FilterEncoding ( mediaType ? . Encoding ) ;
184220 using ( var reader = new StreamReader ( section . Body , encoding , detectEncodingFromByteOrderMarks : true , bufferSize : 1024 , leaveOpen : true ) )
185221 {
222+ // The value length limit is enforced by MultipartBodyLengthLimit
186223 var value = await reader . ReadToEndAsync ( ) ;
187224 formAccumulator . Append ( key , value ) ;
225+ if ( formAccumulator . Count > _options . KeyCountLimit )
226+ {
227+ throw new InvalidDataException ( $ "Form key count limit { _options . KeyCountLimit } exceeded.") ;
228+ }
188229 }
189230 }
190231 else
@@ -261,13 +302,17 @@ private bool HasFileContentDisposition(ContentDispositionHeaderValue contentDisp
261302 }
262303
263304 // Content-Type: multipart/form-data; boundary="----WebKitFormBoundarymx2fSWqWSd0OxQqq"
264- // TODO: Limit the length of boundary we accept. The spec says ~ 70 chars .
265- private static string GetBoundary ( MediaTypeHeaderValue contentType )
305+ // The spec says 70 characters is a reasonable limit .
306+ private static string GetBoundary ( MediaTypeHeaderValue contentType , int lengthLimit )
266307 {
267308 var boundary = HeaderUtilities . RemoveQuotes ( contentType . Boundary ) ;
268309 if ( string . IsNullOrWhiteSpace ( boundary ) )
269310 {
270- throw new InvalidOperationException ( "Missing content-type boundary." ) ;
311+ throw new InvalidDataException ( "Missing content-type boundary." ) ;
312+ }
313+ if ( boundary . Length > lengthLimit )
314+ {
315+ throw new InvalidDataException ( $ "Multipart boundary length limit { lengthLimit } exceeded.") ;
271316 }
272317 return boundary ;
273318 }
0 commit comments