use std::path::{Component, Path, PathBuf};
pub const LOCAL_FILE_HEADER_SIGNATURE: u32 = 0x04034b50;
pub const CENTRAL_DIRECTORY_SIGNATURE: u32 = 0x02014b50;
pub const END_OF_CENTRAL_DIRECTORY_SIGNATURE: u32 = 0x06054b50;
pub const ZIP64_END_OF_CENTRAL_DIRECTORY_SIGNATURE: u32 = 0x06064b50;
pub const MAX_ENTRY_ALLOC: u64 = 2 * 1024 * 1024 * 1024;
#[derive(Debug, Clone)]
pub struct ZipEntry {
pub name: String,
pub compressed_size: u64,
pub uncompressed_size: u64,
pub compression_method: u16,
pub offset: u64,
pub crc32: u32,
pub is_encrypted: bool,
}
impl ZipEntry {
pub fn safe_path(&self) -> PathBuf {
Path::new(&self.name)
.components()
.filter(|c| matches!(c, Component::Normal(_)))
.collect()
}
}
#[inline]
pub fn find_eocd_in_buffer(buffer: &[u8], search_start: u64) -> Option<u64> {
for i in (0..buffer.len().saturating_sub(3)).rev() {
if buffer[i] == 0x50
&& buffer[i + 1] == 0x4b
&& buffer[i + 2] == 0x05
&& buffer[i + 3] == 0x06
{
return Some(search_start + i as u64);
}
}
None
}
#[inline]
pub fn find_zip64_eocd_offset(buffer: &[u8]) -> Option<u64> {
for i in (0..buffer.len().saturating_sub(3)).rev() {
if buffer[i] == 0x50
&& buffer[i + 1] == 0x4b
&& buffer[i + 2] == 0x06
&& buffer[i + 3] == 0x07
{
if i + 16 > buffer.len() {
return None;
}
let rel_off_bytes = &buffer[i + 8..i + 16];
let offset = u64::from_le_bytes([
rel_off_bytes[0],
rel_off_bytes[1],
rel_off_bytes[2],
rel_off_bytes[3],
rel_off_bytes[4],
rel_off_bytes[5],
rel_off_bytes[6],
rel_off_bytes[7],
]);
return Some(offset);
}
}
None
}
#[inline]
pub fn parse_zip64_extra_field(
extra_buf: &[u8],
compressed_size_32: u64,
uncompressed_size_32: u64,
offset_32: u64,
) -> (u64, u64, u64) {
let mut compressed_size = compressed_size_32;
let mut uncompressed_size = uncompressed_size_32;
let mut offset = offset_32;
let mut i = 0usize;
while i + 4 <= extra_buf.len() {
let id = u16::from_le_bytes([extra_buf[i], extra_buf[i + 1]]);
let data_len = u16::from_le_bytes([extra_buf[i + 2], extra_buf[i + 3]]) as usize;
i += 4;
if i + data_len > extra_buf.len() {
break;
}
if id == 0x0001 {
let mut cursor = 0usize;
if uncompressed_size_32 == 0xFFFFFFFF && cursor + 8 <= data_len {
uncompressed_size =
u64::from_le_bytes(extra_buf[i + cursor..i + cursor + 8].try_into().unwrap());
cursor += 8;
}
if compressed_size_32 == 0xFFFFFFFF && cursor + 8 <= data_len {
compressed_size =
u64::from_le_bytes(extra_buf[i + cursor..i + cursor + 8].try_into().unwrap());
cursor += 8;
}
if offset_32 == 0xFFFFFFFF && cursor + 8 <= data_len {
offset =
u64::from_le_bytes(extra_buf[i + cursor..i + cursor + 8].try_into().unwrap());
}
break;
}
i += data_len;
}
(uncompressed_size, compressed_size, offset)
}
#[inline]
pub fn parse_aes_extra_field_buf(extra_buf: &[u8]) -> Option<u8> {
let mut i = 0usize;
while i + 4 <= extra_buf.len() {
let id = u16::from_le_bytes([extra_buf[i], extra_buf[i + 1]]);
let data_len = u16::from_le_bytes([extra_buf[i + 2], extra_buf[i + 3]]) as usize;
i += 4;
if i + data_len > extra_buf.len() {
break;
}
if id == 0x9901 {
if data_len >= 7 {
return Some(extra_buf[i + 4]);
}
}
i += data_len;
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_find_eocd_in_buffer_found() {
let mut buf = vec![0u8; 40];
buf[10] = 0x50;
buf[11] = 0x4b;
buf[12] = 0x05;
buf[13] = 0x06;
let offset = find_eocd_in_buffer(&buf, 1000).unwrap();
assert_eq!(offset, 1010);
}
#[test]
fn test_find_eocd_in_buffer_not_found() {
let buf = vec![0u8; 40];
assert!(find_eocd_in_buffer(&buf, 0).is_none());
}
#[test]
fn test_parse_zip64_extra_no_placeholder() {
let extra = [];
let (u, c, o) = parse_zip64_extra_field(&extra, 100, 200, 300);
assert_eq!((u, c, o), (200, 100, 300));
}
#[test]
fn test_parse_zip64_extra_with_zip64_field() {
let unc: u64 = 0xDEAD_BEEF_0000_0001;
let com: u64 = 0xDEAD_BEEF_0000_0002;
let off: u64 = 0xDEAD_BEEF_0000_0003;
let mut extra = Vec::new();
extra.extend_from_slice(&0x0001u16.to_le_bytes()); extra.extend_from_slice(&24u16.to_le_bytes()); extra.extend_from_slice(&unc.to_le_bytes());
extra.extend_from_slice(&com.to_le_bytes());
extra.extend_from_slice(&off.to_le_bytes());
let (u, c, o) = parse_zip64_extra_field(&extra, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF);
assert_eq!(u, unc);
assert_eq!(c, com);
assert_eq!(o, off);
}
#[test]
fn test_safe_path_strips_dotdot() {
let entry = ZipEntry {
name: "../../etc/passwd".to_string(),
compressed_size: 0,
uncompressed_size: 0,
compression_method: 0,
offset: 0,
crc32: 0,
is_encrypted: false,
};
let p = entry.safe_path();
assert_eq!(p, PathBuf::from("etc/passwd"));
}
}