use crate::driver::{ArchiveRead, ArchiveWrite, Driver}; use crate::zip::error::{ZipError, ZipResult}; use crate::zip::structs::{EOCDR64Locator, CDR, EOCDR, EOCDR64}; use crate::zip::ZipFile; use std::collections::HashMap as Map; use std::fs::File; use std::io::{Cursor, Read, Seek, SeekFrom, Write}; pub struct Zip { io: IO, files: Map, comment: String, } impl Driver for Zip { type Error = ZipError; type IO = IO; type File = ZipFile; } impl ArchiveRead for Zip { fn read(mut io: Self::IO) -> ZipResult { // Search eocdr let limit = 65557.min(io.seek(SeekFrom::End(0))?) as i64; let start = io.seek(SeekFrom::End(-limit))?; let pos = start + { let mut buf = vec![0; limit as usize]; io.read(&mut buf)?; buf[..buf.len() - 18] .windows(4) .rposition(|v| u32::from_le_bytes(v.try_into().unwrap()) == 0x06054b50) .ok_or(ZipError::EOCDRNotFound)? as u64 }; // Read eocdr io.seek(SeekFrom::Start(pos + 4))?; let buf = { let mut buf = [0; 18]; io.read(&mut buf)?; buf }; let eocdr: EOCDR = bincode::deserialize(&buf).map_err(|_| ZipError::InvalidEOCDR)?; let comment = { let mut buf = vec![0; eocdr.comment_len as usize]; io.read(&mut buf)?; String::from_utf8(buf).map_err(|_| ZipError::InvalidArchiveComment)? }; // Try to find eocdr64locator io.seek(SeekFrom::Start(pos - 20))?; let buf = { let mut buf = [0; 20]; io.read(&mut buf)?; buf }; let (cd_pointer, cd_size, cd_records) = if u32::from_le_bytes(buf[0..4].try_into().unwrap()) == 0x07064b50 { let eocdr64locator: EOCDR64Locator = bincode::deserialize(&buf[4..]).map_err(|_| ZipError::InvalidEOCDR64Locator)?; io.seek(SeekFrom::Start(eocdr64locator.eocdr64_pointer))?; let buf = { let mut buf = [0; 56]; io.read(&mut buf)?; buf }; if u32::from_le_bytes(buf[0..4].try_into().unwrap()) != 0x06064b50 { return Err(ZipError::InvalidEOCDR64Signature.into()); } let eocdr64: EOCDR64 = bincode::deserialize(&buf[4..]).map_err(|_| ZipError::InvalidEOCDR64)?; (eocdr64.cd_pointer, eocdr64.cd_size, eocdr64.cd_records) } else { ( eocdr.cd_pointer as u64, eocdr.cd_size as u64, eocdr.cd_records as u64, ) }; // Read cd records let mut files = Map::with_capacity(cd_records as usize); io.seek(SeekFrom::Start(cd_pointer))?; let buf = { let mut buf = vec![0; cd_size as usize]; io.read(&mut buf)?; buf }; let mut p: usize = 0; for _ in 0..cd_records { if u32::from_le_bytes(buf[p..p + 4].try_into().unwrap()) != 0x02014b50 { return Err(ZipError::InvalidCDRSignature.into()); } p += 4; let cdr: CDR = bincode::deserialize(&buf[p..p + 42]).map_err(|_| ZipError::InvalidCDR)?; p += 42; let name = String::from_utf8(buf[p..p + cdr.name_len as usize].into()).unwrap(); p += cdr.name_len as usize; let extra_fields: Vec = buf[p..p + cdr.extra_field_len as usize].into(); p += cdr.extra_field_len as usize; let comment = String::from_utf8(buf[p..p + cdr.comment_len as usize].into()).unwrap(); p += cdr.comment_len as usize; files.insert( name.clone(), ZipFile::new( name, cdr.dos_date, cdr.dos_time, cdr.compression_method, cdr.compressed_size as u64, cdr.size as u64, comment, ), ); } Ok(Self { io, files, comment }) } } impl Zip {} impl ArchiveWrite for Zip {} impl Zip {}