use super::{
copy_entry_bounded, invalid, open_zip, relative_path, ExtractionLimits, MetadataBudget,
};
use std::fs::File;
use std::io::{self, Write};
pub(crate) struct Entry {
pub(crate) name: String,
pub(crate) bytes: u64,
}
pub(super) struct Reader {
archive: zip::ZipArchive<MetadataBudget>,
limits: ExtractionLimits,
remaining: u64,
poisoned: bool,
}
impl Reader {
pub(super) fn new(file: File, limits: ExtractionLimits) -> io::Result<Self> {
Ok(Self {
archive: open_zip(file, limits)?,
limits,
remaining: limits.max_output_bytes,
poisoned: false,
})
}
pub(super) fn entry(&mut self, index: usize) -> io::Result<Option<Entry>> {
if self.poisoned {
return Err(io::ErrorKind::BrokenPipe.into());
}
if index >= self.archive.len() {
return Ok(None);
}
let entry = self.archive.by_index(index).map_err(io::Error::other)?;
validate_entry(&entry, self.limits)?;
Ok(Some(Entry {
name: entry.name().to_owned(),
bytes: entry.size(),
}))
}
pub(super) fn copy_entry(&mut self, index: usize, sink: &mut impl Write) -> io::Result<u64> {
if self.poisoned {
return Err(io::ErrorKind::BrokenPipe.into());
}
self.poisoned = true;
let mut entry = self.archive.by_index(index).map_err(io::Error::other)?;
validate_entry(&entry, self.limits)?;
if entry.size() > self.remaining {
return Err(invalid("archive output exceeds byte limit"));
}
let before = self.remaining;
let expected = entry.size();
copy_entry_bounded(
&mut entry,
sink,
&mut self.remaining,
self.limits.max_entry_bytes,
)?;
let copied = before - self.remaining;
if copied != expected {
return Err(invalid("ZIP entry size disagrees with its metadata"));
}
self.poisoned = false;
Ok(copied)
}
}
fn validate_entry(entry: &zip::read::ZipFile<'_>, limits: ExtractionLimits) -> io::Result<()> {
let kind = entry.unix_mode().unwrap_or(0) & 0o170000;
if entry.is_dir() || entry.is_symlink() || !matches!(kind, 0 | 0o100000) {
return Err(invalid("unsupported streamed ZIP entry kind"));
}
if entry.size() > limits.max_entry_bytes {
return Err(invalid("ZIP entry exceeds byte limit"));
}
relative_path(entry.name(), limits.max_path_bytes)?;
Ok(())
}