Preserve ZIP directory semantics in bundle reader

This commit is contained in:
Dylan M. Taylor committed 2026-11-13 04:26:31 +00:00
1 parent a2e0f58632
commit 7c10820123
3 files changed
+17 -4

No files matched your search

+3 -2
View File
@@ -95,10 +95,11 @@ pub fn read_bundle(bytes: &[u8], limits: BundleLimits) -> Result<(String, HashMa
let mut session = None;
let mut files = HashMap::new();
for index in 0..len {
let name = archive.entry_name(index).map_err(zip_error)?.ok_or_else(|| BundleError::Invalid("missing entry".into()))?;
if name.ends_with('/') {
let is_dir = archive.entry_is_dir(index).map_err(zip_error)?.ok_or_else(|| BundleError::Invalid("missing entry".into()))?;
if is_dir {
continue;
}
let name = archive.entry_name(index).map_err(zip_error)?.ok_or_else(|| BundleError::Invalid("missing entry".into()))?;
let is_session = !name.contains('/') && Path::new(&name).extension().and_then(OsStr::to_str) == Some(SESSION_EXTENSION);
let size = archive.declared_size(&name).ok_or_else(|| BundleError::Invalid(format!("{name}: missing entry")))?;
if is_session && session.is_none() {
+9
View File
@@ -78,6 +78,15 @@ impl<'a> Archive<'a> {
Ok(Some(name.into_owned()))
}
/// Whether the entry at `index` is a directory according to ZIP filename conventions.
pub fn entry_is_dir(&mut self, index: usize) -> Result<Option<bool>, Error> {
if index >= self.zip.len() {
return Ok(None);
}
let file = self.zip.by_index_raw(index).map_err(|error| Error::NotZip(error.to_string()))?;
Ok(Some(file.is_dir()))
}
/// Do two entries share a name? `zip` then keeps one of them, and which one a writer meant
/// is not known: a format that must not guess refuses the archive.
pub fn has_duplicate_names(&self) -> bool {
+5 -2
View File
@@ -231,11 +231,14 @@ mod tests {
zip.write_all(b"1").unwrap();
zip.start_file("second", opts).unwrap();
zip.write_all(b"2").unwrap();
zip.start_file("folder\\", opts).unwrap();
let bytes = zip.finish().unwrap().into_inner();
let mut archive = Archive::open(&bytes, Limits { max_entries: 2, max_total_bytes: 10 }).unwrap();
let mut archive = Archive::open(&bytes, Limits { max_entries: 3, max_total_bytes: 10 }).unwrap();
assert_eq!(archive.entry_name(0).unwrap().as_deref(), Some("first"));
assert_eq!(archive.entry_name(1).unwrap().as_deref(), Some("second"));
assert_eq!(archive.entry_name(2).unwrap(), None);
assert_eq!(archive.entry_is_dir(2).unwrap(), Some(true));
assert_eq!(archive.entry_name(2).unwrap().as_deref(), Some("folder\\"));
assert_eq!(archive.entry_name(3).unwrap(), None);
}
}