diff --git a/crates/format/src/lib.rs b/crates/format/src/lib.rs index 4273a80c1..aedfa31b9 100644 --- a/crates/format/src/lib.rs +++ b/crates/format/src/lib.rs @@ -180,8 +180,9 @@ const MAX_ENTRIES: usize = 100_000; /// How much a `.designcraft` archive may expand to when read. The sizes an archive claims are /// not trusted, so deflated entries are capped; a *stored* entry can't expand (its size is its -/// bytes in the archive, which is already in memory, and all stored entries read together are held to the archive's own size), so a placed image — stored as is by [`save`] — opens whatever its size -/// and doesn't count against the budget. Saving has no cap, and this keeps every saved +/// bytes in the archive, which is already in memory, and all stored entries read together are +/// held to the archive's own size), so a placed image — stored as is by [`save`] — opens whatever +/// its size and doesn't count against the budget. Saving has no cap, and this keeps every saved /// document openable (#2166). #[derive(Clone, Copy, Debug)] struct Limits { @@ -200,11 +201,24 @@ impl Limits { /// Read entry `name` of `z`, or `None` when it is missing or unreadable. Refuses a deflated entry /// that expands past `limit` bytes or past what is `left` of the archive's budget, without /// decompressing more than that: the sizes the archive claims are not trusted. A stored entry -/// is read at its size in the archive and charged to `stored_left`, the archive's own size. -fn read_entry(z: &mut zip::ZipArchive>, name: &str, limit: u64, left: &mut u64, stored_left: &mut u64) -> Result>, FormatError> { +/// is read at its size in the archive and charged to `stored_left`, the archive's own size; only a +/// stored asset is free of `limit`. +fn read_entry( + z: &mut zip::ZipArchive>, + name: &str, + limit: u64, + left: &mut u64, + stored_left: &mut u64, +) -> Result>, FormatError> { let Ok(f) = z.by_name(name) else { return Ok(None) }; let stored = f.compression() == zip::CompressionMethod::Stored; - let limit = if stored { f.compressed_size().min(*stored_left) } else { limit.min(*left) }; + let limit = if !stored { + limit.min(*left) + } else if name.starts_with("assets/") { + f.compressed_size().min(*stored_left) + } else { + f.compressed_size().min(*stored_left).min(limit) + }; let too_big = || FormatError::Io(format!("{name} is larger than the {limit}-byte limit (or the archive expands past its {}-byte budget)", *left)); if f.size() > limit { return Err(too_big()); @@ -235,7 +249,8 @@ fn load_zip_within(bytes: &[u8], limits: Limits) -> Result(&meta).ok()) @@ -244,7 +259,8 @@ fn load_zip_within(bytes: &[u8], limits: Limits) -> Result u64::from(VERSION) { return Err(FormatError::TooNew(version)); } - let json = read_entry(&mut z, "document.json", limits.json, &mut left, &mut stored_left)?.ok_or_else(|| FormatError::NotOurs("missing document.json".into()))?; + let json = read_entry(&mut z, "document.json", limits.json, &mut left, &mut stored_left)? + .ok_or_else(|| FormatError::NotOurs("missing document.json".into()))?; let mut v: Value = serde_json::from_slice(&json).map_err(|e| FormatError::NotOurs(e.to_string()))?; upgrade_json(&mut v); let mut doc: Document = serde_json::from_value(v).map_err(|e| FormatError::NotOurs(e.to_string()))?; @@ -260,7 +276,10 @@ fn load_zip_within(bytes: &[u8], limits: Limits) -> Result d.clone(),