use crate::driver::{ArchiveRead, ArchiveWrite, Driver}; use crate::zip::file::{BitFlag, CompressionMethod}; use crate::zip::structs::{deserialize, EOCDR64Locator, ExtraHeader, CDR, EOCDR, EOCDR64}; use crate::zip::{ZipError, ZipFile, ZipResult}; use chrono::{Local, NaiveDate, NaiveDateTime, NaiveTime}; use std::collections::HashMap as Map; use std::fs::File; use std::io::{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 = deserialize(&buf).unwrap(); let comment = { let mut buf: Vec = 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 = deserialize(&buf[4..]).unwrap(); 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 = deserialize(&buf[4..]).unwrap(); (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 = deserialize(&buf[p..p + 42]).unwrap(); 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; let mut compressed_size = cdr.compressed_size as u64; let mut size = cdr.size as u64; let mut header_pointer = cdr.header_pointer as u64; let mut ep: usize = 0; while ep < cdr.extra_field_len as usize { let header: ExtraHeader = deserialize(&extra_fields[ep..ep + 4]).unwrap(); ep += 4; match header.id { 0x0001 => { if size == 0xFFFFFFFF { compressed_size = deserialize(&extra_fields[ep..ep + 8]).unwrap(); ep += 8; } if compressed_size == 0xFFFFFFFF { size = deserialize(&extra_fields[ep..ep + 8]).unwrap(); ep += 8; } if header_pointer == 0xFFFFFFFF { header_pointer = deserialize(&extra_fields[ep..ep + 8]).unwrap(); ep += 8; } if cdr.disk == 0xFFFF { ep += 4 } } _ => ep += header.size as usize, } } files.insert( name.clone(), ZipFile::new( CompressionMethod::from_struct_id(cdr.compression_method)?, BitFlag::new(cdr.bit_flag), NaiveDateTime::new( NaiveDate::from_ymd_opt( (cdr.dos_date as i32 >> 9 & 0x7F) + 1980, cdr.dos_date as u32 >> 5 & 0xF, cdr.dos_date as u32 & 0x1F, ) .ok_or(ZipError::InvalidDate)?, NaiveTime::from_hms_opt( (cdr.dos_time as u32 >> 11) & 0x1F, (cdr.dos_time as u32 >> 5) & 0x3F, (cdr.dos_time as u32 & 0x1F) * 2, ) .ok_or(ZipError::InvalidTime)?, ) .and_local_timezone(Local) .unwrap(), cdr.crc, compressed_size, size, header_pointer, name, comment, ), ); } Ok(Self { io, files, comment }) } } impl Zip {} impl ArchiveWrite for Zip {} impl Zip {}