use std::io::{Read, Seek};
use crate::date_time::DosDateTime;
use crate::error::{ArchiveError, Result};
#[derive(Debug, Clone)]
pub struct ZipFileHeader {
pub name: String,
pub compressed_size: u64,
pub original_size: u64,
pub compression_method: String,
pub date_time: Option<DosDateTime>,
pub crc32: u32,
pub is_directory: bool,
pub is_encrypted: bool,
pub index: usize,
}
pub struct ZipArchive<T: Read + Seek> {
archive: zip::ZipArchive<T>,
current_index: usize,
password: Option<Vec<u8>>,
}
impl<T: Read + Seek> ZipArchive<T> {
pub fn new(reader: T) -> Result<Self> {
let archive = zip::ZipArchive::new(reader).map_err(|e| ArchiveError::external_library("zip", e.to_string()))?;
Ok(Self {
archive,
current_index: 0,
password: None,
})
}
pub fn set_password<P: AsRef<[u8]>>(&mut self, password: P) {
self.password = Some(password.as_ref().to_vec());
}
pub fn clear_password(&mut self) {
self.password = None;
}
pub fn get_next_entry(&mut self) -> Result<Option<ZipFileHeader>> {
if self.current_index >= self.archive.len() {
return Ok(None);
}
let index = self.current_index;
self.current_index += 1;
let file = self
.archive
.by_index_raw(index)
.map_err(|e| ArchiveError::external_library("zip", e.to_string()))?;
let name = file.name().to_string();
let compressed_size = file.compressed_size();
let original_size = file.size();
let compression_method = format!("{:?}", file.compression());
let crc32 = file.crc32();
let is_directory = file.is_dir();
let is_encrypted = file.encrypted();
let date_time = file.last_modified().map(|dt| {
let year = (dt.year() as u32).saturating_sub(1980) & 0x7F;
let month = dt.month() as u32;
let day = dt.day() as u32;
let hour = dt.hour() as u32;
let minute = dt.minute() as u32;
let second = dt.second() as u32;
let dos_date = (year << 9) | (month << 5) | day;
let dos_time = (hour << 11) | (minute << 5) | (second / 2);
DosDateTime::new((dos_date << 16) | dos_time)
});
Ok(Some(ZipFileHeader {
name,
compressed_size,
original_size,
compression_method,
date_time,
crc32,
is_directory,
is_encrypted,
index,
}))
}
pub fn skip(&mut self, _header: &ZipFileHeader) -> Result<()> {
Ok(())
}
pub fn read(&mut self, header: &ZipFileHeader) -> Result<Vec<u8>> {
let password = self.password.clone();
self.read_with_password(header, password.as_deref())
}
pub fn read_with_password(&mut self, header: &ZipFileHeader, password: Option<&[u8]>) -> Result<Vec<u8>> {
if header.is_directory {
return Ok(Vec::new());
}
let mut data = Vec::with_capacity(header.original_size as usize);
if header.is_encrypted {
let password = password.ok_or_else(|| ArchiveError::encryption_required(&header.name, "ZIP"))?;
let mut file = self.archive.by_index_decrypt(header.index, password).map_err(|e| {
let msg = e.to_string();
if msg.contains("password") || msg.contains("decrypt") {
ArchiveError::invalid_password(&header.name, "ZIP")
} else {
ArchiveError::external_library("zip", msg)
}
})?;
file.read_to_end(&mut data)?;
} else {
let mut file = self
.archive
.by_index(header.index)
.map_err(|e| ArchiveError::external_library("zip", e.to_string()))?;
file.read_to_end(&mut data)?;
}
Ok(data)
}
pub fn create_password_verifier(&self, header: &ZipFileHeader, archive_data: Vec<u8>) -> Result<super::password_verifier::ZipPasswordVerifier> {
if !header.is_encrypted {
return Err(ArchiveError::unsupported_method("ZIP", "entry is not encrypted"));
}
Ok(super::password_verifier::ZipPasswordVerifier::new(
archive_data,
header.index,
header.crc32,
header.original_size,
header.name.clone(),
))
}
}