Skip to content

Commit cccf6c7

Browse files
authored
fix(mod_zip): implement better file size checks for geode files (#52)
1 parent af0de37 commit cccf6c7

File tree

1 file changed

+48
-16
lines changed

1 file changed

+48
-16
lines changed

src/mod_zip.rs

Lines changed: 48 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@ use zip::ZipArchive;
1212
use crate::types::api::ApiError;
1313

1414
pub 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

6370
pub 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

119133
async 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

Comments
 (0)