@@ -16,6 +16,23 @@ use hyper::body::Bytes;
1616use memchr:: memchr_iter;
1717use transform_stream:: { AsyncTryStream , Yielder } ;
1818
19+ /// Maximum size per form field (1 MB)
20+ /// This prevents `DoS` attacks via oversized individual fields
21+ const MAX_FORM_FIELD_SIZE : usize = 1024 * 1024 ;
22+
23+ /// Maximum total size for all form fields combined (20 MB)
24+ /// This prevents `DoS` attacks via accumulation of many fields
25+ const MAX_FORM_FIELDS_SIZE : usize = 20 * 1024 * 1024 ;
26+
27+ /// Maximum number of parts in multipart form
28+ /// This prevents `DoS` attacks via excessive part count
29+ const MAX_FORM_PARTS : usize = 1000 ;
30+
31+ /// Maximum file size for POST object (5 GB - S3 limit for single PUT)
32+ /// This prevents `DoS` attacks via oversized file uploads
33+ /// Note: S3 has a 5GB limit for single PUT object, so this is a reasonable default
34+ pub const MAX_POST_OBJECT_FILE_SIZE : u64 = 5 * 1024 * 1024 * 1024 ;
35+
1936/// Form file
2037#[ derive( Debug ) ]
2138pub struct File {
@@ -68,6 +85,32 @@ pub enum MultipartError {
6885 Underlying ( StdError ) ,
6986 #[ error( "MultipartError: InvalidFormat" ) ]
7087 InvalidFormat ,
88+ #[ error( "MultipartError: FieldTooLarge: field size {0} bytes exceeds limit of {1} bytes" ) ]
89+ FieldTooLarge ( usize , usize ) ,
90+ #[ error( "MultipartError: TotalSizeTooLarge: total form fields size {0} bytes exceeds limit of {1} bytes" ) ]
91+ TotalSizeTooLarge ( usize , usize ) ,
92+ #[ error( "MultipartError: TooManyParts: part count {0} exceeds limit of {1}" ) ]
93+ TooManyParts ( usize , usize ) ,
94+ #[ error( "MultipartError: FileTooLarge: file size {0} bytes exceeds limit of {1} bytes" ) ]
95+ FileTooLarge ( u64 , u64 ) ,
96+ }
97+
98+ /// Aggregates a file stream into a Vec<Bytes> with a size limit.
99+ /// Returns error if the total size exceeds the limit.
100+ pub async fn aggregate_file_stream_limited ( mut stream : FileStream , max_size : u64 ) -> Result < Vec < Bytes > , MultipartError > {
101+ use futures:: stream:: StreamExt ;
102+ let mut vec = Vec :: new ( ) ;
103+ let mut total_size: u64 = 0 ;
104+
105+ while let Some ( result) = stream. next ( ) . await {
106+ let bytes = result. map_err ( |e| MultipartError :: Underlying ( Box :: new ( e) ) ) ?;
107+ total_size = total_size. saturating_add ( bytes. len ( ) as u64 ) ;
108+ if total_size > max_size {
109+ return Err ( MultipartError :: FileTooLarge ( total_size, max_size) ) ;
110+ }
111+ vec. push ( bytes) ;
112+ }
113+ Ok ( vec)
71114}
72115
73116/// transform multipart
@@ -90,17 +133,29 @@ where
90133 } ;
91134
92135 let mut fields = Vec :: new ( ) ;
136+ let mut total_fields_size: usize = 0 ;
137+ let mut parts_count: usize = 0 ;
93138
94139 loop {
95140 // copy bytes to buf
96141 match body. as_mut ( ) . next ( ) . await {
97142 None => return Err ( MultipartError :: InvalidFormat ) ,
98143 Some ( Err ( e) ) => return Err ( MultipartError :: Underlying ( e) ) ,
99- Some ( Ok ( bytes) ) => buf. extend_from_slice ( & bytes) ,
144+ Some ( Ok ( bytes) ) => {
145+ // Check if adding these bytes would exceed reasonable buffer size
146+ // We use MAX_FORM_FIELDS_SIZE as the limit for the parsing buffer
147+ if buf. len ( ) . saturating_add ( bytes. len ( ) ) > MAX_FORM_FIELDS_SIZE {
148+ return Err ( MultipartError :: TotalSizeTooLarge (
149+ buf. len ( ) . saturating_add ( bytes. len ( ) ) ,
150+ MAX_FORM_FIELDS_SIZE ,
151+ ) ) ;
152+ }
153+ buf. extend_from_slice ( & bytes) ;
154+ }
100155 }
101156
102157 // try to parse
103- match try_parse ( body, pat, & buf, & mut fields, boundary) {
158+ match try_parse ( body, pat, & buf, & mut fields, boundary, & mut total_fields_size , & mut parts_count ) {
104159 Err ( ( b, p) ) => {
105160 body = b;
106161 pat = p;
@@ -118,6 +173,8 @@ fn try_parse<S>(
118173 buf : & ' _ [ u8 ] ,
119174 fields : & ' _ mut Vec < ( String , String ) > ,
120175 boundary : & ' _ [ u8 ] ,
176+ total_fields_size : & ' _ mut usize ,
177+ parts_count : & ' _ mut usize ,
121178) -> Result < Result < Multipart , MultipartError > , ( Pin < Box < S > > , Box < [ u8 ] > ) >
122179where
123180 S : Stream < Item = Result < Bytes , StdError > > + Send + Sync + ' static ,
@@ -152,6 +209,12 @@ where
152209
153210 let mut headers = [ httparse:: EMPTY_HEADER ; 2 ] ;
154211 loop {
212+ // Check parts count limit
213+ * parts_count += 1 ;
214+ if * parts_count > MAX_FORM_PARTS {
215+ return Ok ( Err ( MultipartError :: TooManyParts ( * parts_count, MAX_FORM_PARTS ) ) ) ;
216+ }
217+
155218 let ( idx, parsed_headers) = match httparse:: parse_headers ( lines. slice , & mut headers) {
156219 Ok ( httparse:: Status :: Complete ( ans) ) => ans,
157220 Ok ( _) => return Err ( ( body, pat) ) ,
@@ -184,6 +247,17 @@ where
184247 #[ allow( clippy:: indexing_slicing) ]
185248 let b = & b[ ..b. len ( ) . saturating_sub ( 2 ) ] ;
186249
250+ // Check per-field size limit
251+ if b. len ( ) > MAX_FORM_FIELD_SIZE {
252+ return Ok ( Err ( MultipartError :: FieldTooLarge ( b. len ( ) , MAX_FORM_FIELD_SIZE ) ) ) ;
253+ }
254+
255+ // Check total fields size limit
256+ * total_fields_size = total_fields_size. saturating_add ( b. len ( ) ) ;
257+ if * total_fields_size > MAX_FORM_FIELDS_SIZE {
258+ return Ok ( Err ( MultipartError :: TotalSizeTooLarge ( * total_fields_size, MAX_FORM_FIELDS_SIZE ) ) ) ;
259+ }
260+
187261 match std:: str:: from_utf8 ( b) {
188262 Err ( _) => return Ok ( Err ( MultipartError :: InvalidFormat ) ) ,
189263 Ok ( s) => s,
@@ -663,4 +737,120 @@ mod tests {
663737 assert_eq ! ( file_bytes, file_content) ;
664738 }
665739 }
740+
741+ #[ tokio:: test]
742+ async fn test_field_too_large ( ) {
743+ let boundary = "boundary123" ;
744+
745+ // Create a field value that exceeds MAX_FORM_FIELD_SIZE (1 MB)
746+ // This will exceed MAX_FORM_FIELD_SIZE and trigger FieldTooLarge error
747+ let field_size = MAX_FORM_FIELD_SIZE + 1000 ; // Just over 1 MB
748+ let large_value = "x" . repeat ( field_size) ;
749+
750+ let body_bytes = vec ! [
751+ Bytes :: from( format!( "--{boundary}\r \n " ) ) ,
752+ Bytes :: from( "Content-Disposition: form-data; name=\" large_field\" \r \n \r \n " ) ,
753+ Bytes :: from( large_value) ,
754+ Bytes :: from( format!( "\r \n --{boundary}--\r \n " ) ) ,
755+ ] ;
756+
757+ let body_stream = futures:: stream:: iter ( body_bytes. into_iter ( ) . map ( Ok :: < _ , StdError > ) ) ;
758+
759+ let result = transform_multipart ( body_stream, boundary. as_bytes ( ) ) . await ;
760+ // Either error is acceptable - both indicate the field/buffer is too large
761+ assert ! ( result. is_err( ) , "Should fail when field exceeds limits" ) ;
762+ }
763+
764+ #[ tokio:: test]
765+ async fn test_total_size_too_large ( ) {
766+ let boundary = "boundary123" ;
767+
768+ // Create multiple fields that together exceed MAX_FORM_FIELDS_SIZE (20 MB)
769+ let field_size = MAX_FORM_FIELD_SIZE ; // 1 MB per field
770+ let num_fields = 21 ; // 21 fields = 21 MB > 20 MB limit
771+
772+ let mut body_bytes = Vec :: new ( ) ;
773+
774+ for i in 0 ..num_fields {
775+ body_bytes. push ( format ! ( "--{boundary}\r \n " ) ) ;
776+ body_bytes. push ( format ! ( "Content-Disposition: form-data; name=\" field{i}\" \r \n \r \n " ) ) ;
777+ body_bytes. push ( "x" . repeat ( field_size) ) ;
778+ body_bytes. push ( "\r \n " . to_string ( ) ) ;
779+ }
780+ body_bytes. push ( format ! ( "--{boundary}--\r \n " ) ) ;
781+
782+ let body_stream = futures:: stream:: iter ( body_bytes. into_iter ( ) . map ( |s| Ok :: < _ , StdError > ( Bytes :: from ( s) ) ) ) ;
783+
784+ let result = transform_multipart ( body_stream, boundary. as_bytes ( ) ) . await ;
785+ match result {
786+ Err ( MultipartError :: TotalSizeTooLarge ( size, limit) ) => {
787+ assert_eq ! ( limit, MAX_FORM_FIELDS_SIZE ) ;
788+ assert ! ( size > MAX_FORM_FIELDS_SIZE ) ;
789+ }
790+ _ => panic ! ( "Expected TotalSizeTooLarge error" ) ,
791+ }
792+ }
793+
794+ #[ tokio:: test]
795+ async fn test_too_many_parts ( ) {
796+ let boundary = "boundary123" ;
797+
798+ // Create more parts than MAX_FORM_PARTS (1000)
799+ let num_parts = MAX_FORM_PARTS + 1 ;
800+
801+ let mut body_bytes = Vec :: new ( ) ;
802+
803+ for i in 0 ..num_parts {
804+ body_bytes. push ( format ! ( "--{boundary}\r \n " ) ) ;
805+ body_bytes. push ( format ! ( "Content-Disposition: form-data; name=\" field{i}\" \r \n \r \n " ) ) ;
806+ body_bytes. push ( "value" . to_string ( ) ) ;
807+ body_bytes. push ( "\r \n " . to_string ( ) ) ;
808+ }
809+ body_bytes. push ( format ! ( "--{boundary}--\r \n " ) ) ;
810+
811+ let body_stream = futures:: stream:: iter ( body_bytes. into_iter ( ) . map ( |s| Ok :: < _ , StdError > ( Bytes :: from ( s) ) ) ) ;
812+
813+ let result = transform_multipart ( body_stream, boundary. as_bytes ( ) ) . await ;
814+ match result {
815+ Err ( MultipartError :: TooManyParts ( count, limit) ) => {
816+ assert_eq ! ( limit, MAX_FORM_PARTS ) ;
817+ assert ! ( count > MAX_FORM_PARTS ) ;
818+ }
819+ _ => panic ! ( "Expected TooManyParts error" ) ,
820+ }
821+ }
822+
823+ #[ tokio:: test]
824+ async fn test_limits_within_bounds ( ) {
825+ let boundary = "boundary123" ;
826+
827+ // Create fields within limits
828+ let field_count = 10 ;
829+ let field_size = 100 ; // Small fields
830+
831+ let mut body_bytes = Vec :: new ( ) ;
832+
833+ for i in 0 ..field_count {
834+ body_bytes. push ( format ! ( "--{boundary}\r \n " ) ) ;
835+ body_bytes. push ( format ! ( "Content-Disposition: form-data; name=\" field{i}\" \r \n \r \n " ) ) ;
836+ body_bytes. push ( "x" . repeat ( field_size) ) ;
837+ body_bytes. push ( "\r \n " . to_string ( ) ) ;
838+ }
839+
840+ // Add a file
841+ body_bytes. push ( format ! ( "--{boundary}\r \n " ) ) ;
842+ body_bytes. push ( "Content-Disposition: form-data; name=\" file\" ; filename=\" test.txt\" \r \n " . to_string ( ) ) ;
843+ body_bytes. push ( "Content-Type: text/plain\r \n \r \n " . to_string ( ) ) ;
844+ body_bytes. push ( "file content" . to_string ( ) ) ;
845+ body_bytes. push ( format ! ( "\r \n --{boundary}--\r \n " ) ) ;
846+
847+ let body_stream = futures:: stream:: iter ( body_bytes. into_iter ( ) . map ( |s| Ok :: < _ , StdError > ( Bytes :: from ( s) ) ) ) ;
848+
849+ let result = transform_multipart ( body_stream, boundary. as_bytes ( ) ) . await ;
850+ assert ! ( result. is_ok( ) , "Should succeed when within limits" ) ;
851+
852+ let multipart = result. unwrap ( ) ;
853+ assert_eq ! ( multipart. fields( ) . len( ) , field_count) ;
854+ assert ! ( multipart. file. stream. is_some( ) ) ;
855+ }
666856}
0 commit comments