mirror of
https://github.com/storytold/craft-anything.git
synced 2026-11-05 20:47:45 +01:00
Keep ZIP64 values within their extra field (from #701)
This commit is contained in:
+21
-17
@@ -149,34 +149,38 @@ pub fn parse(data: &[u8], max_entries: usize) -> Result<ZipArchive, ZipError> {
|
||||
let unix_mode = if host == 3 { r.u32_le(pos + 38).map(|a| a >> 16) } else { None };
|
||||
let mut local = u64::from(r.u32_le(pos + 42).ok_or(ZipError::Truncated("directory entry"))?);
|
||||
let name_bytes = r.slice(pos + 46, name_len).ok_or(ZipError::Truncated("entry name"))?;
|
||||
let extra = r.slice(pos + 46 + name_len, extra_len).unwrap_or(&[]);
|
||||
// ZIP64 extended information extra field.
|
||||
let extra = r.slice(pos + 46 + name_len, extra_len).ok_or(ZipError::Truncated("entry extra fields"))?;
|
||||
// Each extra field owns its payload: ZIP64 values cannot borrow a following field's bytes.
|
||||
let needs_zip64 = usize_ == 0xffff_ffff || csize == 0xffff_ffff || local == 0xffff_ffff;
|
||||
let mut found_zip64 = false;
|
||||
let mut x = 0usize;
|
||||
let er = Reader::new(extra);
|
||||
while let (Some(id), Some(len)) = (er.u16_le(x), er.u16_le(x + 2)) {
|
||||
while x < extra.len() {
|
||||
let id = er.u16_le(x).ok_or(ZipError::Truncated("extra field header"))?;
|
||||
let len = usize::from(er.u16_le(x + 2).ok_or(ZipError::Truncated("extra field header"))?);
|
||||
let body = x + 4;
|
||||
let field = er.slice(body, len).ok_or(ZipError::Truncated("extra field payload"))?;
|
||||
if id == 0x0001 {
|
||||
let mut f = body;
|
||||
if usize_ == 0xffff_ffff
|
||||
&& let Some(v) = er.u64_le(f)
|
||||
{
|
||||
usize_ = v;
|
||||
let values = Reader::new(field);
|
||||
let mut f = 0;
|
||||
if usize_ == 0xffff_ffff {
|
||||
usize_ = values.u64_le(f).ok_or(ZipError::Truncated("zip64 uncompressed size"))?;
|
||||
f += 8;
|
||||
}
|
||||
if csize == 0xffff_ffff
|
||||
&& let Some(v) = er.u64_le(f)
|
||||
{
|
||||
csize = v;
|
||||
if csize == 0xffff_ffff {
|
||||
csize = values.u64_le(f).ok_or(ZipError::Truncated("zip64 compressed size"))?;
|
||||
f += 8;
|
||||
}
|
||||
if local == 0xffff_ffff
|
||||
&& let Some(v) = er.u64_le(f)
|
||||
{
|
||||
local = v;
|
||||
if local == 0xffff_ffff {
|
||||
local = values.u64_le(f).ok_or(ZipError::Truncated("zip64 local header offset"))?;
|
||||
}
|
||||
found_zip64 = true;
|
||||
break;
|
||||
}
|
||||
x = body + usize::from(len);
|
||||
x = body + len;
|
||||
}
|
||||
if needs_zip64 && !found_zip64 {
|
||||
return Err(ZipError::Corrupt("missing ZIP64 extended information".into()));
|
||||
}
|
||||
let name = decode_name(name_bytes, flags).replace('\\', "/");
|
||||
entries.push(ZipEntry {
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
use craft_anything_formats::zip;
|
||||
|
||||
fn archive(extra: &[u8], sentinel: usize) -> Vec<u8> {
|
||||
let mut data = craft_anything_testkit::electron::zip_stored(&[("a", b"x")]);
|
||||
let central = data.windows(4).position(|w| w == b"PK\x01\x02").unwrap();
|
||||
let end = data.len() - 22;
|
||||
data[central + sentinel..central + sentinel + 4].copy_from_slice(&u32::MAX.to_le_bytes());
|
||||
data[central + 30..central + 32].copy_from_slice(&(extra.len() as u16).to_le_bytes());
|
||||
data.splice(end..end, extra.iter().copied());
|
||||
let end = data.len() - 22;
|
||||
data[end + 12..end + 16].copy_from_slice(&((47 + extra.len()) as u32).to_le_bytes());
|
||||
data
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_short_zip64_field_cannot_borrow_values_from_its_neighbor() {
|
||||
for field in [20, 24, 42] {
|
||||
let mut extra = vec![1, 0, 0, 0, 2, 0, 8, 0];
|
||||
extra.extend_from_slice(&1u64.to_le_bytes());
|
||||
assert!(zip::parse(&archive(&extra, field), 10).is_err(), "field {field}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zip64_sentinels_require_complete_values() {
|
||||
for field in [20, 24, 42] {
|
||||
assert!(zip::parse(&archive(&[], field), 10).is_err());
|
||||
assert!(zip::parse(&archive(&[1, 0, 7, 0, 1, 0, 0, 0, 0, 0, 0], field), 10).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_complete_zip64_field_preserves_the_value() {
|
||||
for field in [20, 24, 42] {
|
||||
let value = if field == 42 { 0u64 } else { 1u64 };
|
||||
let mut extra = vec![1, 0, 8, 0];
|
||||
extra.extend_from_slice(&value.to_le_bytes());
|
||||
let data = archive(&extra, field);
|
||||
let parsed = zip::parse(&data, 10).unwrap();
|
||||
assert_eq!(zip::read_entry(&data, &parsed.entries[0], 1).unwrap(), b"x");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_legitimate_64_bit_value_equal_to_the_32_bit_sentinel_is_preserved() {
|
||||
let mut extra = vec![1, 0, 8, 0];
|
||||
extra.extend_from_slice(&u64::from(u32::MAX).to_le_bytes());
|
||||
let archive = zip::parse(&archive(&extra, 24), 10).unwrap();
|
||||
assert_eq!(archive.entries[0].uncompressed_size, u64::from(u32::MAX));
|
||||
}
|
||||
Reference in New Issue
Block a user