@@ -12,6 +12,13 @@ use zip::ZipArchive;
1212use crate :: types:: api:: ApiError ;
1313
1414pub fn extract_mod_logo ( file : & mut ZipFile < Cursor < Bytes > > ) -> Result < Vec < u8 > , ApiError > {
15+ const FIVE_MEGABYTES : u64 = 5 * 1000 * 1000 ;
16+ if file. size ( ) > FIVE_MEGABYTES {
17+ return Err ( ApiError :: BadRequest (
18+ "Logo size excedes max allowed size (5 MB)" . into ( ) ,
19+ ) ) ;
20+ }
21+
1522 let mut logo: Vec < u8 > = Vec :: with_capacity ( file. size ( ) as usize ) ;
1623 file. read_to_end ( & mut logo)
1724 . inspect_err ( |e| log:: error!( "logo.png read fail: {}" , e) )
@@ -61,6 +68,13 @@ pub fn extract_mod_logo(file: &mut ZipFile<Cursor<Bytes>>) -> Result<Vec<u8>, Ap
6168}
6269
6370pub fn validate_mod_logo ( file : & mut ZipFile < Cursor < Bytes > > ) -> Result < ( ) , ApiError > {
71+ const FIVE_MEGABYTES : u64 = 5 * 1000 * 1000 ;
72+ if file. size ( ) > FIVE_MEGABYTES {
73+ return Err ( ApiError :: BadRequest (
74+ "Logo size excedes max allowed size (5 MB)" . into ( ) ,
75+ ) ) ;
76+ }
77+
6478 let mut logo: Vec < u8 > = Vec :: with_capacity ( file. size ( ) as usize ) ;
6579 file. read_to_end ( & mut logo)
6680 . inspect_err ( |e| log:: error!( "logo.png read fail: {}" , e) )
@@ -117,26 +131,44 @@ pub fn bytes_to_ziparchive(bytes: Bytes) -> Result<ZipArchive<Cursor<Bytes>>, Ap
117131}
118132
119133async fn download ( url : & str , limit_mb : u32 ) -> Result < Bytes , ApiError > {
120- let limit_bytes = limit_mb * 1_000_000 ;
121- let response = reqwest:: get ( url) . await . map_err ( |e| {
122- log:: error!( "Failed to fetch .geode: {}" , e) ;
123- ApiError :: BadRequest ( "Couldn't download .geode file" . into ( ) )
124- } ) ?;
134+ let limit_bytes: u64 = limit_mb as u64 * 1_000_000 ;
135+ let mut response = reqwest:: get ( url)
136+ . await
137+ . inspect_err ( |e| log:: error!( "Failed to fetch .geode file: {e}" ) )
138+ . or ( Err ( ApiError :: BadRequest (
139+ "Failed to fetch .geode file" . into ( ) ,
140+ ) ) ) ?;
125141
126- let len = response . content_length ( ) . ok_or ( ApiError :: BadRequest (
127- "Couldn't determine .geode file size" . into ( ) ,
128- ) ) ? ;
142+ // Check Content-Length, but the server can lie about this, so we'll also stream the file
143+ // If the header is somehow unavailable, we'll just check the size when streaming
144+ let content_length = response . content_length ( ) . unwrap_or ( 0 ) ;
129145
130- if len > limit_bytes as u64 {
146+ if content_length > limit_bytes {
147+ let len_mb = content_length / 1_000_000 ;
131148 return Err ( ApiError :: BadRequest ( format ! (
132- "File size is too large, max {}MB " ,
133- limit_mb
149+ "Mod file is too large ({} mb) , max size is {} mb " ,
150+ len_mb , limit_mb
134151 ) ) ) ;
135152 }
136153
137- response
138- . bytes ( )
139- . await
140- . inspect_err ( |e| log:: error!( "Failed to get bytes from .geode: {}" , e) )
141- . or ( Err ( ApiError :: InternalError ) )
154+ let mut data: Vec < u8 > = Vec :: with_capacity ( content_length as usize ) ;
155+
156+ let mut streamed: u64 = 0 ;
157+ while let Some ( chunk) = response. chunk ( ) . await . or ( Err ( ApiError :: BadRequest (
158+ "Failed to read .geode chunk" . into ( ) ,
159+ ) ) ) ? {
160+ streamed += chunk. len ( ) as u64 ;
161+
162+ if streamed > limit_bytes {
163+ let len_mb = streamed / 1_000_000 ;
164+ return Err ( ApiError :: BadRequest ( format ! (
165+ "Mod file is too large ({} mb), max size is {} mb" ,
166+ len_mb, limit_mb
167+ ) ) ) ;
168+ }
169+
170+ data. extend_from_slice ( & chunk) ;
171+ }
172+
173+ Ok ( Bytes :: from ( data) )
142174}
0 commit comments