mirror of
https://github.com/storytold/figcraft.git
synced 2026-10-29 12:56:47 -06:00
#1398: share the member size check between JSON and images (capped_member)
# Conflicts: # crates/format/src/sketch.rs
This commit is contained in:
+25
-27
@@ -56,10 +56,10 @@ pub const NEWEST_TESTED_VERSION: u64 = 139;
|
||||
pub const TOO_OLD: &str = "saved by a Sketch older than 48; open it in a newer Sketch and save it again";
|
||||
/// Largest JSON member read (a real page of a few thousand layers takes 33 MB).
|
||||
const MAX_JSON_MEMBER: u64 = 512 * 1024 * 1024;
|
||||
/// Most bytes JSON members may unpack to, per compressed byte (real files pack 5–20 times; a
|
||||
/// zero-filled array a thousand times), with a floor for small members.
|
||||
const MAX_JSON_RATIO: u64 = 100;
|
||||
const MIN_JSON_ALLOWANCE: u64 = 16 * 1024 * 1024;
|
||||
/// Most bytes a member may unpack to, per compressed byte (real JSON packs 5–20 times and images
|
||||
/// barely; a zero-filled member a thousand times), with a floor for small members.
|
||||
const MAX_RATIO: u64 = 100;
|
||||
const MIN_ALLOWANCE: u64 = 16 * 1024 * 1024;
|
||||
/// Most JSON read from one file in all.
|
||||
#[cfg(not(target_pointer_width = "32"))]
|
||||
const MAX_JSON_TOTAL: u64 = 2 * 1024 * 1024 * 1024;
|
||||
@@ -67,10 +67,6 @@ const MAX_JSON_TOTAL: u64 = 2 * 1024 * 1024 * 1024;
|
||||
const MAX_JSON_TOTAL: u64 = 512 * 1024 * 1024;
|
||||
/// Largest image member read.
|
||||
const MAX_IMAGE: u64 = figcraft_doc::ImageData::MAX_BYTES;
|
||||
/// Most bytes an image member may unpack to, per compressed byte (images are already compressed,
|
||||
/// a zero-filled one packs a thousand times), with a floor for small members.
|
||||
const MAX_IMAGE_RATIO: u64 = 100;
|
||||
const MIN_IMAGE_ALLOWANCE: u64 = 16 * 1024 * 1024;
|
||||
/// Most image bytes read from one file in all.
|
||||
#[cfg(not(target_pointer_width = "32"))]
|
||||
const MAX_IMAGE_TOTAL: u64 = 1024 * 1024 * 1024;
|
||||
@@ -256,37 +252,39 @@ impl<'b> Archive<'b> {
|
||||
}
|
||||
// A clone shares the parsed directory and the bytes; the resolver only has shared access.
|
||||
let mut z = self.zip.clone();
|
||||
let member = format!("images/{name}");
|
||||
let (size, packed) = {
|
||||
let f = z.by_name(&member).ok()?;
|
||||
(f.size(), f.compressed_size())
|
||||
};
|
||||
let allowed = MAX_IMAGE
|
||||
.min(packed.saturating_mul(MAX_IMAGE_RATIO).max(MIN_IMAGE_ALLOWANCE))
|
||||
.min(MAX_IMAGE_TOTAL.saturating_sub(self.image_total.get()));
|
||||
if size > allowed {
|
||||
return None;
|
||||
}
|
||||
let data = crate::read_member(&mut z, &member, allowed).ok()?;
|
||||
self.image_total.set(self.image_total.get() + data.len() as u64);
|
||||
Some(data)
|
||||
let mut total = self.image_total.get();
|
||||
let data = capped_member(&mut z, &format!("images/{name}"), MAX_IMAGE, &mut total, MAX_IMAGE_TOTAL).ok();
|
||||
self.image_total.set(total);
|
||||
data
|
||||
}
|
||||
}
|
||||
|
||||
/// JSON member `name`, refused before unpacking when it would be larger than `limit`, than
|
||||
/// [`MAX_JSON_RATIO`] times what it takes in the archive (past a floor), or than what is left of
|
||||
/// the file's [`MAX_JSON_TOTAL`] (`total`: read so far).
|
||||
fn json_member(z: &mut zip::ZipArchive<std::io::Cursor<&[u8]>>, name: &str, limit: u64, total: &mut u64) -> Result<Value, FormatError> {
|
||||
/// Member `name`, refused before unpacking when it would be larger than `limit`, than
|
||||
/// [`MAX_RATIO`] times what it takes in the archive (past a floor), or than what is left of
|
||||
/// `max_total` (`total`: read so far, counted up).
|
||||
fn capped_member(
|
||||
z: &mut zip::ZipArchive<std::io::Cursor<&[u8]>>,
|
||||
name: &str,
|
||||
limit: u64,
|
||||
total: &mut u64,
|
||||
max_total: u64,
|
||||
) -> Result<Vec<u8>, FormatError> {
|
||||
let (size, packed) = {
|
||||
let f = z.by_name(name).map_err(|e| corrupt(format!("{name}: {e}")))?;
|
||||
(f.size(), f.compressed_size())
|
||||
};
|
||||
let allowed = limit.min(packed.saturating_mul(MAX_JSON_RATIO).max(MIN_JSON_ALLOWANCE)).min(MAX_JSON_TOTAL.saturating_sub(*total));
|
||||
let allowed = limit.min(packed.saturating_mul(MAX_RATIO).max(MIN_ALLOWANCE)).min(max_total.saturating_sub(*total));
|
||||
if size > allowed {
|
||||
return Err(corrupt(format!("{name} is too large")));
|
||||
}
|
||||
let data = crate::read_member(z, name, allowed)?;
|
||||
*total += data.len() as u64;
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
/// JSON member `name`, read by [`capped_member`] within the file's [`MAX_JSON_TOTAL`].
|
||||
fn json_member(z: &mut zip::ZipArchive<std::io::Cursor<&[u8]>>, name: &str, limit: u64, total: &mut u64) -> Result<Value, FormatError> {
|
||||
let data = capped_member(z, name, limit, total, MAX_JSON_TOTAL)?;
|
||||
serde_json::from_slice(&data).map_err(|e| corrupt(format!("{name}: {e}")))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user