@@ -12,7 +12,7 @@ use rand::{thread_rng, Rng};
1212use serde_json:: Value ;
1313/// Payload validation logic
1414use std:: cmp:: min;
15- use std:: io:: Cursor ;
15+ use std:: io:: { Cursor , Read } ;
1616use std:: iter;
1717use std:: sync:: Arc ;
1818use thiserror:: Error ;
@@ -51,6 +51,7 @@ impl Validation {
5151 max_input_length : usize ,
5252 max_total_tokens : usize ,
5353 disable_grammar_support : bool ,
54+ max_image_fetch_size : usize ,
5455 ) -> Self {
5556 let workers = if let Tokenizer :: Python { .. } = & tokenizer {
5657 1
@@ -78,6 +79,7 @@ impl Validation {
7879 config_clone,
7980 preprocessor_config_clone,
8081 tokenizer_receiver,
82+ max_image_fetch_size,
8183 )
8284 } ) ;
8385 }
@@ -480,6 +482,7 @@ fn tokenizer_worker(
480482 config : Option < Config > ,
481483 preprocessor_config : Option < HubPreprocessorConfig > ,
482484 mut receiver : mpsc:: UnboundedReceiver < TokenizerRequest > ,
485+ max_image_fetch_size : usize ,
483486) {
484487 match tokenizer {
485488 Tokenizer :: Python {
@@ -503,6 +506,7 @@ fn tokenizer_worker(
503506 & tokenizer,
504507 config. as_ref ( ) ,
505508 preprocessor_config. as_ref ( ) ,
509+ max_image_fetch_size,
506510 ) )
507511 . unwrap_or ( ( ) )
508512 } )
@@ -524,6 +528,7 @@ fn tokenizer_worker(
524528 & tokenizer,
525529 config. as_ref ( ) ,
526530 preprocessor_config. as_ref ( ) ,
531+ max_image_fetch_size,
527532 ) )
528533 . unwrap_or ( ( ) )
529534 } )
@@ -562,10 +567,35 @@ fn format_to_mimetype(format: ImageFormat) -> String {
562567 . to_string ( )
563568}
564569
565- fn fetch_image ( input : & str ) -> Result < ( Vec < u8 > , String , usize , usize ) , ValidationError > {
570+ fn fetch_image (
571+ input : & str ,
572+ max_image_fetch_size : usize ,
573+ ) -> Result < ( Vec < u8 > , String , usize , usize ) , ValidationError > {
566574 if input. starts_with ( " || input. starts_with ( " {
567575 let url = & input[ " ..input. len ( ) - 1 ] ;
568- let data = reqwest:: blocking:: get ( url) ?. bytes ( ) ?;
576+ let response = reqwest:: blocking:: get ( url) ?;
577+
578+ // Check Content-Length header if present
579+ if let Some ( content_length) = response. content_length ( ) {
580+ if content_length as usize > max_image_fetch_size {
581+ return Err ( ValidationError :: ImageTooLarge (
582+ content_length as usize ,
583+ max_image_fetch_size,
584+ ) ) ;
585+ }
586+ }
587+
588+ // Read the body with size limit to prevent unbounded memory allocation
589+ let mut data = Vec :: new ( ) ;
590+ let mut limited_reader = response. take ( ( max_image_fetch_size + 1 ) as u64 ) ;
591+ limited_reader. read_to_end ( & mut data) ?;
592+
593+ if data. len ( ) > max_image_fetch_size {
594+ return Err ( ValidationError :: ImageTooLarge (
595+ data. len ( ) ,
596+ max_image_fetch_size,
597+ ) ) ;
598+ }
569599
570600 let format = image:: guess_format ( & data) ?;
571601 // TODO Remove this clone
@@ -787,6 +817,7 @@ fn prepare_input<T: TokenizerTrait>(
787817 tokenizer : & T ,
788818 config : Option < & Config > ,
789819 preprocessor_config : Option < & HubPreprocessorConfig > ,
820+ max_image_fetch_size : usize ,
790821) -> Result < ( tokenizers:: Encoding , Vec < Chunk > ) , ValidationError > {
791822 use Config :: * ;
792823 static RE : Lazy < Regex > = Lazy :: new ( || Regex :: new ( r"!\[\]\([^\)]*\)" ) . unwrap ( ) ) ;
@@ -805,7 +836,8 @@ fn prepare_input<T: TokenizerTrait>(
805836 input_chunks. push ( Chunk :: Text ( inputs[ start..chunk_start] . to_string ( ) ) ) ;
806837 tokenizer_query. push_str ( & inputs[ start..chunk_start] ) ;
807838 }
808- let ( data, mimetype, height, width) = fetch_image ( & inputs[ chunk_start..chunk_end] ) ?;
839+ let ( data, mimetype, height, width) =
840+ fetch_image ( & inputs[ chunk_start..chunk_end] , max_image_fetch_size) ?;
809841 input_chunks. push ( Chunk :: Image ( Image { data, mimetype } ) ) ;
810842 tokenizer_query. push_str ( & image_tokens ( config, preprocessor_config, height, width) ) ;
811843 start = chunk_end;
@@ -990,6 +1022,10 @@ pub enum ValidationError {
9901022 InvalidImageContent ( String ) ,
9911023 #[ error( "Could not fetch image: {0}" ) ]
9921024 FailedFetchImage ( #[ from] reqwest:: Error ) ,
1025+ #[ error( "Image size {0} bytes exceeds maximum allowed size of {1} bytes" ) ]
1026+ ImageTooLarge ( usize , usize ) ,
1027+ #[ error( "Failed to read image data: {0}" ) ]
1028+ ImageReadError ( #[ from] std:: io:: Error ) ,
9931029 #[ error( "{0} modality is not supported" ) ]
9941030 UnsupportedModality ( & ' static str ) ,
9951031}
@@ -1023,6 +1059,7 @@ mod tests {
10231059 max_input_length,
10241060 max_total_tokens,
10251061 disable_grammar_support,
1062+ 1024 * 1024 * 1024 , // 1GB
10261063 ) ;
10271064
10281065 let max_new_tokens = 10 ;
@@ -1058,6 +1095,7 @@ mod tests {
10581095 max_input_length,
10591096 max_total_tokens,
10601097 disable_grammar_support,
1098+ 1024 * 1024 * 1024 , // 1GB
10611099 ) ;
10621100
10631101 let max_new_tokens = 10 ;
@@ -1092,6 +1130,7 @@ mod tests {
10921130 max_input_length,
10931131 max_total_tokens,
10941132 disable_grammar_support,
1133+ 1024 * 1024 * 1024 , // 1GB
10951134 ) ;
10961135 match validation
10971136 . validate ( GenerateRequest {
@@ -1132,6 +1171,7 @@ mod tests {
11321171 max_input_length,
11331172 max_total_tokens,
11341173 disable_grammar_support,
1174+ 1024 * 1024 * 1024 , // 1GB
11351175 ) ;
11361176 match validation
11371177 . validate ( GenerateRequest {
@@ -1203,6 +1243,7 @@ mod tests {
12031243 max_input_length,
12041244 max_total_tokens,
12051245 disable_grammar_support,
1246+ 1024 * 1024 * 1024 , // 1GB
12061247 ) ;
12071248 match validation
12081249 . validate ( GenerateRequest {
@@ -1293,6 +1334,7 @@ mod tests {
12931334 max_input_length,
12941335 max_total_tokens,
12951336 disable_grammar_support,
1337+ 1024 * 1024 * 1024 , // 1GB
12961338 ) ;
12971339
12981340 let chunks = match validation
@@ -1349,6 +1391,7 @@ mod tests {
13491391 max_input_length,
13501392 max_total_tokens,
13511393 disable_grammar_support,
1394+ 1024 * 1024 * 1024 , // 1GB
13521395 ) ;
13531396
13541397 let ( encoding, chunks) = match validation
0 commit comments