//! Zip archive codec. use crate::archive::{Archive, ArchiveWriter, checked_entry_size}; use crate::{AsyncContainer, ContainerError, Result, validate_path}; use std::io::{Read, Seek, Write}; use zip::ZipArchive; use zip::write::{SimpleFileOptions, ZipWriter as InnerZipWriter}; pub struct Zip; pub struct ZipWriter { inner: InnerZipWriter, options: SimpleFileOptions, } impl Archive for Zip { type Writer = ZipWriter; fn writer(output: W) -> Result> { Ok(ZipWriter { inner: InnerZipWriter::new(output), options: SimpleFileOptions::default().compression_method(zip::CompressionMethod::Deflated), }) } fn open(source: R, dest: &mut C) -> Result<()> { let mut archive = ZipArchive::new(source).map_err(zip_err)?; // Zip headers declare each entry's uncompressed size up front; `checked_entry_size` caps the running // total so a malicious archive can't exhaust memory or disk before any bytes are read. let mut total_size = 0u64; for index in 0..archive.len() { let mut entry = archive.by_index(index).map_err(zip_err)?; if !entry.is_file() { continue; } let name = entry.name().to_string(); validate_path(&name)?; let size = checked_entry_size(&mut total_size, entry.size())?; dest.write_sized_non_blocking(&name, size, &mut |buffer| { entry.read_exact(buffer).map_err(ContainerError::Io)?; Ok(()) })?; } Ok(()) } } impl ArchiveWriter for ZipWriter { type Sink = W; fn write_entry(&mut self, path: &str, bytes: &[u8]) -> Result<()> { validate_path(path)?; self.inner.start_file(path, self.options).map_err(zip_err)?; self.inner.write_all(bytes)?; Ok(()) } fn finish_into(self) -> Result { self.inner.finish().map_err(zip_err) } } fn zip_err(error: zip::result::ZipError) -> ContainerError { // Preserve a real I/O failure (disk full, etc.) as a structured `Io` so callers can tell it apart from a // corrupt-archive `Codec` error; only the genuinely archive-format errors collapse into `Codec`. match error { zip::result::ZipError::Io(io) => ContainerError::Io(io), other => ContainerError::Codec(format!("zip: {other}")), } }