use std::fs::File;
use std::io::{self, BufRead, BufReader, Read, Seek, SeekFrom};
use super::cli::{ZE_OPEN, ZE_PARMS};
use super::flavour;
use super::messages::{self as m, printf};
use super::unicode;
use super::zipfile::{Bf, LOCHEAD, ZEntry, ZOut};
use super::{Exit, Zr};
use crate::compat::host::InFile;
pub(crate) const ZE_EOF: i32 = 2;
pub(crate) const ZE_FORM: i32 = 3;
pub(crate) const ZE_READ: i32 = 11;
pub(crate) const ENDHEAD: usize = 18;
const EC64LOC: usize = 16;
const EC64REC: usize = 52;
pub(crate) const CENHEAD: usize = 42;
use super::unicode::UTF8_BIT;
const NUM_HOSTS: u16 = 31;
pub(crate) const CBSZ: usize = 16384;
const ZIP_UWORD32_MAX: u64 = 0xffff_ffff;
const ZIP_UWORD16_MAX: u64 = 0xffff;
#[derive(Debug, Clone, Default)]
pub(crate) struct ZFile {
pub(crate) e: ZEntry,
pub(crate) zname: Vec<u8>,
pub(crate) mark: u8,
pub(crate) trash: bool,
pub(crate) current: bool,
pub(crate) dosflag: bool,
pub(crate) zuname: Option<Vec<u8>>,
pub(crate) ouname: Option<Vec<u8>>,
}
#[derive(Debug, Default)]
pub(crate) struct Archive {
pub(crate) exists: bool,
pub(crate) zfiles: Vec<ZFile>,
pub(crate) zsort: Vec<usize>,
pub(crate) zusort: Vec<usize>,
pub(crate) zipbeg: u64,
pub(crate) cenbeg: u64,
pub(crate) zcomment: Vec<u8>,
pub(crate) total_disks: u64,
}
pub(crate) fn cstr(b: &[u8]) -> &[u8] {
match b.iter().position(|&c| c == 0) {
Some(i) => &b[..i],
None => b,
}
}
pub(crate) struct CFile {
f: BufReader<Charged>,
back: Option<u8>,
eof: bool,
err: bool,
pub(crate) errno: i32,
}
impl CFile {
pub(crate) fn open(z: &Zr, name: &[u8]) -> io::Result<CFile> {
let p = z.host.path(name);
let f = File::open(&p)?;
let budget = match &z.host {
crate::compat::host::Host::Sandbox(s) if z.o.fix == 2 => Some(s.budget.clone()),
_ => None,
};
Ok(CFile {
f: BufReader::with_capacity(
8192,
Charged {
f: z.host.track(&p, f),
budget,
},
),
back: None,
eof: false,
err: false,
errno: 0,
})
}
fn failed(&mut self, e: &io::Error) {
self.errno = e.raw_os_error().unwrap_or(5);
}
pub(crate) fn getc(&mut self) -> Option<u8> {
if let Some(b) = self.back.take() {
return Some(b);
}
loop {
match self.f.fill_buf() {
Ok([]) => {
self.eof = true;
return None;
}
Ok(b) => {
let c = b[0];
self.f.consume(1);
return Some(c);
}
Err(e) if e.kind() == io::ErrorKind::Interrupted => {}
Err(e) => {
self.failed(&e);
self.err = true;
return None;
}
}
}
}
pub(crate) fn ungetc(&mut self, c: u8) {
self.back = Some(c);
self.eof = false;
}
pub(crate) fn fread(&mut self, n: usize) -> Vec<u8> {
let mut out = Vec::with_capacity(n.min(1 << 20));
if n > 0
&& let Some(b) = self.back.take()
{
out.push(b);
}
while out.len() < n {
let want = (n - out.len()).min(CBSZ);
let mut chunk = vec![0u8; want];
match self.f.read(&mut chunk) {
Ok(0) => {
self.eof = true;
break;
}
Ok(k) => out.extend_from_slice(&chunk[..k]),
Err(e) if e.kind() == io::ErrorKind::Interrupted => {}
Err(e) => {
self.failed(&e);
self.err = true;
break;
}
}
}
out
}
pub(crate) fn seek(&mut self, at: i128) -> bool {
self.back = None;
if at < 0 || at > i128::from(i64::MAX) {
self.errno = 22;
return false;
}
let r = match self.f.stream_position() {
Ok(here) => self.f.seek_relative((at - i128::from(here)) as i64),
Err(e) => Err(e),
};
match r {
Ok(_) => {
self.eof = false;
true
}
Err(e) => {
self.failed(&e);
false
}
}
}
pub(crate) fn end(&mut self) -> Option<u64> {
self.back = None;
match self.f.seek(SeekFrom::End(0)) {
Ok(p) => Some(p),
Err(e) => {
self.failed(&e);
None
}
}
}
pub(crate) fn tell(&mut self) -> u64 {
let p = self.f.stream_position().unwrap_or(0);
p - u64::from(self.back.is_some())
}
pub(crate) fn feof(&self) -> bool {
self.eof
}
pub(crate) fn ferror(&self) -> bool {
self.err
}
pub(crate) fn error(&self) -> io::Error {
io::Error::from_raw_os_error(self.errno)
}
}
struct Charged {
f: InFile,
budget: Option<std::sync::Arc<std::sync::atomic::AtomicU64>>,
}
impl Read for Charged {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let n = self.f.read(buf)?;
if let Some(b) = &self.budget {
crate::compat::host::take(b, n as u64)?;
}
Ok(n)
}
}
impl Seek for Charged {
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
self.f.seek(pos)
}
}
pub(crate) fn find_next_signature(f: &mut CFile, sigbuf: &mut [u8; 4]) -> bool {
let mut m = f.getc();
while let Some(c) = m {
if c == 0x50 {
sigbuf[0] = c;
let Some(c2) = f.getc() else { break };
if c2 != 0x4b {
f.ungetc(c2);
} else {
sigbuf[1] = c2;
let Some(c3) = f.getc() else { break };
if c3 == 0x50 {
f.ungetc(c3);
m = Some(c3);
continue;
} else if c3 >= 16 {
m = Some(c3);
continue;
}
sigbuf[2] = c3;
let Some(c4) = f.getc() else { break };
if c4 == 0x50 {
f.ungetc(c4);
m = Some(c4);
continue;
} else if c4 >= 16 {
m = Some(c4);
continue;
}
sigbuf[3] = c4;
return true;
}
}
m = f.getc();
}
false
}
pub(crate) fn find_signature(f: &mut CFile, sig: &[u8; 4], sigbuf: &mut [u8; 4]) -> bool {
while !f.feof() {
if !find_next_signature(f, sigbuf) {
return false;
}
if sigbuf == sig {
return true;
}
}
false
}
pub(crate) fn at_signature(f: &mut CFile, sig: &[u8; 4]) -> bool {
let b = f.fread(4);
b.len() == 4 && b == sig
}
pub(crate) fn sh(b: &[u8], at: usize) -> u16 {
u16::from_le_bytes([b[at], b[at + 1]])
}
pub(crate) fn lg(b: &[u8], at: usize) -> u32 {
u32::from_le_bytes([b[at], b[at + 1], b[at + 2], b[at + 3]])
}
pub(crate) fn llg(b: &[u8], at: usize) -> u64 {
u64::from(lg(b, at)) | (u64::from(lg(b, at + 4)) << 32)
}
pub(crate) fn padded(mut buf: Vec<u8>, n: usize) -> Vec<u8> {
buf.resize(n.max(buf.len()), 0);
buf
}
pub(crate) enum Fail {
Code(i32, Option<io::Error>),
Exit(Exit),
}
fn code(c: i32) -> Fail {
Fail::Code(c, None)
}
fn read_fail(f: &CFile) -> Fail {
if f.ferror() {
Fail::Code(ZE_READ, Some(f.error()))
} else {
code(ZE_EOF)
}
}
pub(crate) fn strerror(errno: i32) -> Vec<u8> {
crate::compat::host::strerror(&io::Error::from_raw_os_error(errno))
}
fn null_out(z: &Zr) -> bool {
matches!(z.program, "zipnote" | "zipsplit")
}
pub(crate) fn null_out_crash(z: &mut Zr) -> Exit {
z.mesg_write(b"\n");
z.helper_err(super::cli::ZE_ABORT, b"aborting")
}
pub(crate) fn read_archive(z: &mut Zr) -> Result<Archive, Exit> {
let mut ar = Archive {
total_disks: 1,
..Archive::default()
};
let zipfile = z.o.zipfile.clone().unwrap_or_default();
let in_path = z.o.in_path.clone().unwrap_or_else(|| zipfile.clone());
let out_path = z.o.out_path.clone().unwrap_or_else(|| zipfile.clone());
let opened = if zipfile.is_empty() || zipfile == b"-" {
None
} else {
Some(CFile::open(z, &zipfile))
};
match opened {
None => {
if null_out(z) && !z.o.zip_to_stdout && z.o.fix != 2 {
return Err(null_out_crash(z));
}
return Ok(ar);
}
Some(Err(e)) => {
if null_out(z) && !z.o.zip_to_stdout && z.o.fix != 2 {
return Err(null_out_crash(z));
}
if !z.o.zip_to_stdout && z.o.fix != 2 && in_path != out_path {
if z.o.fix == 1 {
z.zipwarn(m::NO_ZIP_FOUND_1.as_bytes(), m::NO_ZIP_FOUND_2.as_bytes());
}
return Err(z.ziperr_os(ZE_OPEN, &zipfile, &e));
}
return Ok(ar);
}
Some(Ok(_)) => ar.exists = true,
}
match scanzipf_regnew(z, &mut ar, &in_path, &out_path) {
Ok(()) => {}
Err(Fail::Exit(x)) => return Err(x),
Err(Fail::Code(c, e)) => {
return Err(super::zipup::zerr(z, c, &zipfile, e.as_ref()));
}
}
let mut order: Vec<usize> = (0..ar.zfiles.len()).collect();
let names: Vec<Vec<u8>> = ar
.zfiles
.iter()
.map(|f| cstr(&f.e.iname).to_vec())
.collect();
super::scan::qsort(z.flavour, &mut order, &|a, b| names[a].cmp(&names[b]));
ar.zsort = order;
if flavour::unicode_support(z.flavour) && z.o.fix != 2 {
let mut order: Vec<usize> = (0..ar.zfiles.len()).collect();
let unames: Vec<Vec<u8>> = ar
.zfiles
.iter()
.map(|f| cstr(f.zuname.as_ref().unwrap_or(&f.e.iname)).to_vec())
.collect();
super::scan::qsort(z.flavour, &mut order, &|a, b| unames[a].cmp(&unames[b]));
ar.zusort = order;
}
Ok(ar)
}
pub(crate) fn zsearch(ar: &Archive, n: &[u8]) -> Option<usize> {
let n = cstr(n);
if let Some(i) = search(&ar.zsort, n, &|i| cstr(&ar.zfiles[i].zname)) {
return Some(i);
}
search(&ar.zusort, n, &|i| {
let f = &ar.zfiles[i];
cstr(f.zuname.as_ref().unwrap_or(&f.zname))
})
}
fn search<'a>(a: &[usize], n: &[u8], key: &dyn Fn(usize) -> &'a [u8]) -> Option<usize> {
if a.is_empty() {
return None;
}
let (mut l, mut u) = (0i64, a.len() as i64 - 1);
while u >= l {
let i = l + ((u - l) >> 1);
let idx = a[i as usize];
match n.cmp(key(idx)) {
std::cmp::Ordering::Less => u = i - 1,
std::cmp::Ordering::Greater => l = i + 1,
std::cmp::Ordering::Equal => return Some(idx),
}
}
None
}
fn sig_hex(sig: &[u8; 4]) -> Vec<u8> {
let mut v = Vec::new();
for c in sig {
v.extend_from_slice(format!("{c:02x} ").as_bytes());
}
v
}
fn warn_fix(z: &mut Zr, fixed: &str, plain: &str, b: &[u8]) {
if z.o.fix == 1 {
z.zipwarn(fixed.as_bytes(), b);
z.zipwarn(m::TRY_FF.as_bytes(), b"");
} else {
z.zipwarn(plain.as_bytes(), b);
}
}
fn disk_path(z: &mut Zr, base: &[u8], disk: u64) -> Result<Vec<u8>, Fail> {
if disk == z.total_disks.wrapping_sub(1) {
return Ok(z.o.in_path.clone().unwrap_or_default());
}
super::split::get_in_split_path(z, base, disk).map_err(Fail::Exit)
}
fn scanzipf_regnew(
z: &mut Zr,
ar: &mut Archive,
in_path: &[u8],
out_path: &[u8],
) -> Result<(), Fail> {
let fix = z.o.fix;
let mut sigbuf = [0u8; 4];
let mut f = match CFile::open(z, in_path) {
Ok(f) => f,
Err(e) => {
z.zipwarn(m::COULD_NOT_OPEN_INPUT_ARCHIVE.as_bytes(), in_path);
return Err(Fail::Code(ZE_OPEN, Some(e)));
}
};
let back_ok = match f.end() {
Some(len) if len >= 0x20000 => f.seek(i128::from(len - 0x20000)),
Some(_) => {
f.errno = 22;
false
}
None => false,
};
if !back_ok && !f.seek(0) {
z.zipwarn(m::UNABLE_TO_SEEK.as_bytes(), in_path);
return Err(Fail::Code(ZE_READ, Some(f.error())));
}
if !find_signature(&mut f, b"PK\x05\x06", &mut sigbuf) {
if fix == 1 {
z.zipwarn(m::F_MISSING_END_1.as_bytes(), b"");
z.zipwarn(m::F_MISSING_END_2.as_bytes(), b"");
z.zipwarn(m::F_MISSING_END_3.as_bytes(), b"");
z.zipwarn(m::F_MISSING_END_4.as_bytes(), b"");
} else {
z.zipwarn(m::MISSING_END_1.as_bytes(), b"");
z.zipwarn(m::MISSING_END_2.as_bytes(), b"");
z.zipwarn(m::MISSING_END_3.as_bytes(), b"");
}
return Err(code(ZE_FORM));
}
let mut eocdr_offset = f.tell();
while find_signature(&mut f, b"PK\x05\x06", &mut sigbuf) {
eocdr_offset = f.tell();
}
if !f.seek(i128::from(eocdr_offset)) {
z.zipwarn(m::UNABLE_TO_SEEK.as_bytes(), in_path);
return Err(Fail::Code(ZE_READ, Some(f.error())));
}
let scbuf = padded(f.fread(ENDHEAD), ENDHEAD);
let eocdr_disk = u64::from(sh(&scbuf, 0));
z.total_disks = eocdr_disk + 1;
z.current_in_disk = z.total_disks - 1;
let mut in_cd_start_disk = u64::from(sh(&scbuf, 2));
let mut in_cd_start_offset = u64::from(lg(&scbuf, 12));
let mut cd_total_entries = u64::from(sh(&scbuf, 6));
let cd_total_size = u64::from(lg(&scbuf, 8));
ar.total_disks = z.total_disks;
let zcomlen = usize::from(sh(&scbuf, 16));
if zcomlen > 0 {
let c = f.fread(zcomlen);
if c.len() != zcomlen {
return Err(read_fail(&f));
}
ar.zcomment = c;
}
if cd_total_entries == 0 {
return Ok(());
}
if z.total_disks != 1 {
if z.o.adjust {
z.zipwarn(m::ADJUST_SPLITS.as_bytes(), b"");
return Err(code(ZE_FORM));
}
let ok = in_path.len() >= 4 && {
let e = &in_path[in_path.len() - 4..];
e[0] == b'.' && e[1..].eq_ignore_ascii_case(b"zip")
};
if !ok {
z.zipwarn(m::SPLIT_NAME_ZIP.as_bytes(), b"");
return Err(code(ZE_PARMS));
}
}
if (z.total_disks != 1 || z.o.split_method != 0) && z.o.show_files == 0 && null_out(z) {
return Err(Fail::Exit(null_out_crash(z)));
}
if (z.total_disks != 1 || z.o.split_method != 0) && z.o.show_files == 0 && in_path == out_path {
z.zipwarn(m::CANNOT_UPDATE_SPLIT.as_bytes(), b"");
return Err(code(ZE_PARMS));
}
if fix == 1 && in_path == out_path {
z.zipwarn(m::MUST_USE_OUT.as_bytes(), b"");
return Err(code(ZE_PARMS));
}
let mut adjust_offset: u64 = 0;
if (fix == 1 && z.total_disks == 1) || z.o.adjust {
if in_cd_start_offset != ZIP_UWORD32_MAX && cd_total_size != ZIP_UWORD32_MAX {
let cd_start = i128::from(eocdr_offset) - i128::from(cd_total_size) - 24 - 56;
if !f.seek(cd_start) {
warn_fix(
z,
m::F_SEEK_CENTRAL,
m::READING_ARCHIVE_FSEEK,
&strerror(f.errno),
);
return Err(code(ZE_FORM));
}
if find_signature(&mut f, b"PK\x01\x02", &mut sigbuf) {
adjust_offset = f.tell().wrapping_sub(4).wrapping_sub(in_cd_start_offset);
} else {
z.zipwarn(m::CENTRAL_NOT_WHERE_EXPECTED.as_bytes(), b"");
z.zipwarn(m::TRY_FF.as_bytes(), b"");
return Err(code(ZE_FORM));
}
} else {
if !f.seek(i128::from(eocdr_offset) - 24) {
warn_fix(
z,
m::F_SEEK_EOCDL,
m::READING_ARCHIVE_FSEEK,
&strerror(f.errno),
);
return Err(code(ZE_FORM));
}
if at_signature(&mut f, b"PK\x06\x07") {
let z64eocdl_offset = f.tell().wrapping_sub(4);
let loc = f.fread(EC64LOC);
if loc.len() != EC64LOC {
z.zipwarn(m::READING_ARCHIVE.as_bytes(), &strerror(f.errno));
return Err(Fail::Code(ZE_READ, Some(f.error())));
}
if !at_signature(&mut f, b"PK\x05\x06") {
z.zipwarn(m::UNABLE_TO_READ_EOCD.as_bytes(), in_path);
return Err(Fail::Code(ZE_READ, Some(f.error())));
}
let z64eocdr_offset = llg(&loc, 4);
z.total_disks = u64::from(lg(&loc, 12));
ar.total_disks = z.total_disks;
if z.total_disks != 1 {
z.zipwarn(m::ADJUST_SPLITS_NOT_SUPPORTED.as_bytes(), in_path);
z.zipwarn(m::TRY_FF.as_bytes(), b"");
return Err(code(ZE_FORM));
}
if !f.seek(i128::from(z64eocdr_offset)) {
z.zipwarn(m::READING_ARCHIVE_FSEEK.as_bytes(), &strerror(f.errno));
return Err(code(ZE_FORM));
}
if !at_signature(&mut f, b"PK\x06\x06") {
let start = i128::from(z64eocdl_offset) - 24 - 56;
if !f.seek(start) {
warn_fix(
z,
m::F_SEEK_EOCDR,
m::READING_ARCHIVE_FSEEK,
&strerror(f.errno),
);
return Err(code(ZE_FORM));
}
if find_next_signature(&mut f, &mut sigbuf) && &sigbuf == b"PK\x06\x06" {
adjust_offset = f.tell().wrapping_sub(4).wrapping_sub(z64eocdr_offset);
} else {
z.zipwarn(m::COULD_NOT_DETERMINE_OFFSET.as_bytes(), b"");
z.zipwarn(m::TRY_FF.as_bytes(), b"");
return Err(code(ZE_FORM));
}
}
}
}
if z.o.noisy {
let mut s = Vec::new();
if adjust_offset != 0 {
printf(
&mut s,
m::OFFSETS_OFF_BY,
&[(adjust_offset as i64).to_string().as_bytes()],
);
} else {
s.extend_from_slice(m::OFFSETS_DO_NOT_NEED.as_bytes());
}
z.zipmessage(&s, b"");
}
}
if !f.seek(i128::from(eocdr_offset) - 24) {
warn_fix(
z,
m::F_BAD_SEEK_EOCDL,
m::READING_ARCHIVE_FSEEK,
&strerror(f.errno),
);
return Err(code(ZE_FORM));
}
let mut in_file = Some(f);
if at_signature(in_file.as_mut().expect("open"), b"PK\x06\x07") {
let f = in_file.as_mut().expect("open");
let z64eocdl_offset = f.tell().wrapping_sub(4);
let loc = f.fread(EC64LOC);
if loc.len() != EC64LOC {
z.zipwarn(m::READING_ARCHIVE.as_bytes(), &strerror(f.errno));
return Err(Fail::Code(ZE_READ, Some(f.error())));
}
if !at_signature(f, b"PK\x05\x06") {
z.zipwarn(m::UNABLE_TO_READ_EOCD.as_bytes(), in_path);
return Err(Fail::Code(ZE_READ, Some(f.error())));
}
let z64eocdr_disk = u64::from(lg(&loc, 0));
let z64eocdr_offset = llg(&loc, 4).wrapping_add(adjust_offset);
z.total_disks = u64::from(lg(&loc, 12));
ar.total_disks = z.total_disks;
z.current_in_disk = z.total_disks.wrapping_sub(1);
if z64eocdr_disk != z.total_disks.wrapping_sub(1) {
drop(in_file.take());
let mut split_path =
super::split::get_in_split_path(z, in_path, z64eocdr_disk).map_err(Fail::Exit)?;
loop {
if let Ok(f) = CFile::open(z, &split_path) {
in_file = Some(f);
break;
}
let r =
super::split::ask_for_split_read_path(z, z64eocdr_disk).map_err(Fail::Exit)?;
if r != super::split::ZE_OK {
return Err(code(super::cli::ZE_ABORT));
}
let ip = z.o.in_path.clone().unwrap_or_default();
split_path =
super::split::get_in_split_path(z, &ip, z64eocdr_disk).map_err(Fail::Exit)?;
}
}
z.current_in_disk = z64eocdr_disk;
let f = in_file.as_mut().expect("open");
if !f.seek(i128::from(z64eocdr_offset)) {
z.zipwarn(m::READING_ARCHIVE_FSEEK.as_bytes(), &strerror(f.errno));
return Err(code(ZE_FORM));
}
if !at_signature(f, b"PK\x06\x06") {
let start = i128::from(z64eocdl_offset) - 24 - 56;
if !f.seek(start) {
warn_fix(
z,
m::F_BAD_SEEK_EOCDR,
m::READING_ARCHIVE_FSEEK,
&strerror(f.errno),
);
return Err(code(ZE_FORM));
}
if find_next_signature(f, &mut sigbuf) && &sigbuf == b"PK\x06\x06" {
adjust_offset = f.tell().wrapping_sub(4).wrapping_sub(z64eocdr_offset);
z.zipwarn(m::Z64_COMPENSATING.as_bytes(), b"");
z.zipwarn(m::TRY_A.as_bytes(), b"");
} else {
let ip = z.o.in_path.clone().unwrap_or_default();
if fix == 1 {
z.zipwarn(m::F_Z64_NOT_IN_SPLIT.as_bytes(), &ip);
z.zipwarn(m::TRY_FF.as_bytes(), b"");
} else {
z.zipwarn(m::Z64_EOCDR_NOT_FOUND.as_bytes(), &ip);
}
return Err(code(ZE_FORM));
}
}
let rec = f.fread(EC64REC);
if rec.len() < EC64REC {
if fix == 1 {
z.zipwarn(m::F_Z64_TRUNCATED.as_bytes(), b"");
z.zipwarn(m::TRY_FF.as_bytes(), b"");
} else {
z.zipwarn(m::Z64_EOCDR_TRUNCATED.as_bytes(), b"");
}
return Err(code(ZE_FORM));
}
let version_needed = sh(&rec, 10);
in_cd_start_disk = u64::from(lg(&rec, 16));
cd_total_entries = llg(&rec, 28);
in_cd_start_offset = llg(&rec, 44).wrapping_add(adjust_offset);
if version_needed > 46 {
let major = version_needed / 10;
let minor = version_needed - major * 10;
let mut s = Vec::new();
printf(
&mut s,
m::REQUIRES_VERSION,
&[major.to_string().as_bytes(), minor.to_string().as_bytes()],
);
z.zipwarn(&s, b"");
z.zipwarn(m::SUPPORTS_UP_TO_46.as_bytes(), b"");
z.zipwarn(m::UP_TO_45.as_bytes(), b"");
if fix == 1 {
z.zipwarn(m::F_TRY_FF_SALVAGE.as_bytes(), b"");
} else if fix == 2 {
z.zipwarn(m::FF_SALVAGING.as_bytes(), b"");
} else {
z.zipwarn(m::TRY_F_READ_ANYWAY.as_bytes(), b"");
return Err(code(ZE_FORM));
}
}
}
in_cd_start_offset = in_cd_start_offset.wrapping_add(adjust_offset);
ar.cenbeg = in_cd_start_offset;
let mut zipbegset = false;
ar.zipbeg = 0;
let mut first_cd = true;
let mut skipped_disk = false;
if z.current_in_disk != in_cd_start_disk {
in_file = None;
}
let zipfile = z.o.zipfile.clone().unwrap_or_default();
z.current_in_disk = in_cd_start_disk;
while z.current_in_disk < z.total_disks {
let disk = z.current_in_disk;
let base = z.o.in_path.clone().unwrap_or_default();
let mut split_path = disk_path(z, &base, disk)?;
if in_file.is_none() {
let mut skip_disk = false;
loop {
if let Ok(f) = CFile::open(z, &split_path) {
in_file = Some(f);
break;
}
let r = super::split::ask_for_split_read_path(z, disk).map_err(Fail::Exit)?;
if super::split::is_abort(r) {
z.zipwarn(m::COULD_NOT_FIND_SPLIT.as_bytes(), &split_path);
return Err(code(super::cli::ZE_ABORT));
} else if r == super::split::ANSWER_SKIP {
let mut s = Vec::new();
printf(&mut s, m::SKIPPING_DISK, &[disk.to_string().as_bytes()]);
z.zipwarn(&s, b"");
skip_disk = true;
break;
}
split_path = disk_path(z, &zipfile, disk)?;
}
if skip_disk {
skipped_disk = true;
z.current_in_disk += 1;
continue;
}
}
let f = in_file.as_mut().expect("open");
if skipped_disk {
first_cd = false;
} else if first_cd {
if !f.seek(i128::from(in_cd_start_offset)) {
z.zipwarn(m::UNABLE_TO_SEEK.as_bytes(), &split_path);
return Err(Fail::Code(ZE_READ, Some(f.error())));
}
first_cd = false;
}
while find_next_signature(f, &mut sigbuf) {
let current_in_offset = f.tell();
if &sigbuf == b"PK\x05\x06" || &sigbuf == b"PK\x06\x06" {
break;
} else if &sigbuf != b"PK\x01\x02" {
let at = current_in_offset.wrapping_sub(4).to_string();
if fix == 1 {
let mut s = m::F_UNEXPECTED_SIGNATURE.as_bytes().to_vec();
s.extend_from_slice(&sig_hex(&sigbuf));
printf(
&mut s,
m::SIG_ON_DISK,
&[disk.to_string().as_bytes(), at.as_bytes()],
);
z.zipwarn(&s, b"");
z.zipwarn(m::SKIPPING_SIGNATURE.as_bytes(), b"");
continue;
}
let mut s = Vec::new();
printf(
&mut s,
m::UNEXPECTED_SIGNATURE,
&[disk.to_string().as_bytes(), at.as_bytes()],
);
z.zipwarn(&s, b"");
z.zipwarn(m::NOT_IN_CORRECT_FORMAT.as_bytes(), &split_path);
z.zipwarn(m::TRY_F.as_bytes(), b"");
return Err(code(ZE_FORM));
}
if z.o.verbose > 0 && fix == 1 {
let mut s = Vec::new();
printf(
&mut s,
m::CENTRAL_SIG_ON_DISK,
&[
disk.to_string().as_bytes(),
current_in_offset.wrapping_sub(4).to_string().as_bytes(),
],
);
z.mesg_write(&s);
}
let cen = f.fread(CENHEAD);
if cen.len() != CENHEAD {
z.zipwarn(m::READING_CENTRAL.as_bytes(), &strerror(f.errno));
if fix == 1 {
z.zipwarn(m::F_ERROR_READING_CENTRAL.as_bytes(), b"");
z.zipwarn(m::SKIPPING_ENTRY.as_bytes(), b"");
continue;
}
return Err(read_fail(f));
}
let mut e = ZEntry {
vem: sh(&cen, 0),
ver: sh(&cen, 2),
flg: sh(&cen, 4),
how: sh(&cen, 6),
tim: lg(&cen, 8),
crc: lg(&cen, 12),
siz: u64::from(lg(&cen, 16)),
len: u64::from(lg(&cen, 20)),
dsk: u64::from(sh(&cen, 30)),
att: sh(&cen, 32),
atx: lg(&cen, 34),
off: u64::from(lg(&cen, 38)),
..ZEntry::default()
};
let nam = usize::from(sh(&cen, 24));
let cext = usize::from(sh(&cen, 26));
let com = usize::from(sh(&cen, 28));
let dosflag = (e.vem & 0xff00) == 0;
if nam == 0 {
z.zipwarn(
m::ZERO_LENGTH_NAME.as_bytes(),
(ar.zfiles.len() + 1).to_string().as_bytes(),
);
if fix == 1 {
z.zipwarn(m::SKIPPING_ENTRY.as_bytes(), b"");
continue;
}
return Err(code(ZE_FORM));
}
e.iname = f.fread(nam);
let mut ok = e.iname.len() == nam;
if ok && cext > 0 {
e.cextra = f.fread(cext);
ok = e.cextra.len() == cext;
}
if ok && com > 0 {
e.comment = f.fread(com);
ok = e.comment.len() == com;
}
if !ok {
if fix == 1 {
z.zipwarn(m::ERROR_READING_ENTRY.as_bytes(), &strerror(f.errno));
z.zipwarn(m::SKIPPING_ENTRY.as_bytes(), b"");
continue;
}
return Err(read_fail(f));
}
if flavour::unicode_support(z.flavour) && z.o.unicode_mismatch != 3 {
if e.flg & UTF8_BIT != 0 {
let u = cstr(&e.iname).to_vec();
let Some(local) =
unicode::utf8_to_local(z.using_utf8, z.o.unicode_escape_all, &u)
else {
return Err(Fail::Exit(unicode::segv(z)));
};
e.uname = Some(u);
e.nam = Some(e.iname.len());
e.iname = local;
} else {
let iname = e.iname.clone();
e.uname = unicode::read_unicode_path(
z,
&e.cextra,
&iname,
unicode::PathReader::Central,
)
.map_err(Fail::Exit)?;
}
}
adjust_zip_central_entry(&mut e);
if (fix == 1 && z.total_disks == 1) || z.o.adjust {
e.off = e.off.wrapping_add(adjust_offset);
}
if e.dsk == 0 && (!zipbegset || e.off < ar.zipbeg) {
ar.zipbeg = e.off;
zipbegset = true;
}
let zname = cstr(&e.iname).to_vec();
e.name = zname.clone();
e.oname = super::scan::display(z, &zname);
let (mut zuname, mut ouname) = (None, None);
if z.program == "zip"
&& z.o.unicode_mismatch != 3
&& let Some(u) = &e.uname
{
let Some(name) = unicode::utf8_to_local(z.using_utf8, z.o.unicode_escape_all, u)
else {
return Err(Fail::Exit(unicode::segv(z)));
};
ouname = Some(if z.o.unicode_escape_all {
match unicode::utf8_to_escape(u) {
Some(o) => o,
None => return Err(Fail::Exit(unicode::segv(z))),
}
} else {
name.clone()
});
zuname = Some(name);
}
if z.o.verbose > 0 && fix == 0 {
zipoddities(z, &e);
}
ar.zfiles.push(ZFile {
e,
zname,
mark: 0,
trash: false,
current: false,
dosflag,
zuname,
ouname,
});
}
in_file = None;
if &sigbuf != b"PK\x01\x02" {
break;
}
z.current_in_disk += 1;
}
if ar.zfiles.len() as u64 != cd_total_entries {
let mut s = Vec::new();
printf(
&mut s,
m::EXPECTED_ENTRIES,
&[
cd_total_entries.to_string().as_bytes(),
ar.zfiles.len().to_string().as_bytes(),
],
);
z.zipwarn(&s, b"");
return Err(code(ZE_FORM));
}
Ok(())
}
fn adjust_zip_central_entry(e: &mut ZEntry) {
let Some(at) = get_extra_field(0x0001, &e.cextra) else {
return;
};
let size = usize::from(sh(&e.cextra, at + 2));
let data_end = (at + 4 + size).min(e.cextra.len());
let block = padded(e.cextra[(at + 4).min(data_end)..data_end].to_vec(), 28);
let mut p = 0;
if e.len == ZIP_UWORD32_MAX {
e.len = llg(&block, p);
p += 8;
}
if e.siz == ZIP_UWORD32_MAX {
e.siz = llg(&block, p);
p += 8;
}
if e.off == ZIP_UWORD32_MAX {
e.off = llg(&block, p);
p += 8;
}
if e.dsk == ZIP_UWORD16_MAX {
e.dsk = u64::from(lg(&block, p));
}
}
pub(crate) fn get_extra_field(tag: u16, extra: &[u8]) -> Option<usize> {
let mut p = 0usize;
while p + 4 < extra.len() {
if sh(extra, p) == tag {
return Some(p);
}
p += usize::from(sh(extra, p + 2)) + 4;
}
None
}
pub(crate) fn copy_nondup_extra_fields(old: &[u8], new: &[u8]) -> Vec<u8> {
let mut out = Vec::new();
let mut p = 0usize;
while p < old.len() {
if p + 4 > old.len() {
break;
}
let tag = sh(old, p);
let size = usize::from(sh(old, p + 2));
let end = p + 4 + size;
if end > old.len() {
break;
}
if get_extra_field(tag, new).is_none() {
out.extend_from_slice(&old[p..end]);
}
p = end;
}
out.extend_from_slice(new);
out
}
pub(crate) fn zipoddities(z: &mut Zr, e: &ZEntry) {
let oname = e.oname.clone();
let warn = |z: &mut Zr, fmt: &str, args: &[&[u8]]| {
let mut s = Vec::new();
printf(&mut s, fmt, args);
z.zipwarn(&s, &oname);
};
if (e.vem >> 8) >= NUM_HOSTS {
let v = e.vem & 0xff;
warn(
z,
m::MADE_BY_VERSION,
&[
(v / 10).to_string().as_bytes(),
(v % 10).to_string().as_bytes(),
(e.vem >> 8).to_string().as_bytes(),
],
);
}
if e.ver != 10 && e.ver != 11 && e.ver != 20 {
let v = e.ver & 0xff;
warn(
z,
m::NEEDS_UNZIP,
&[
(v / 10).to_string().as_bytes(),
(v % 10).to_string().as_bytes(),
(e.ver >> 8).to_string().as_bytes(),
],
);
}
if e.flg & !0xf != 0 && (e.flg & !0xf0) != UTF8_BIT {
warn(z, m::UNDEFINED_BITS, &[format!("{:04x}", e.flg).as_bytes()]);
}
if e.how > flavour::last_known_method(z.flavour) {
warn(z, m::UNKNOWN_METHOD, &[e.how.to_string().as_bytes()]);
}
if e.dsk != 0 {
warn(z, m::STARTS_ON_DISK, &[e.dsk.to_string().as_bytes()]);
}
if e.att != 1 && e.att != 0 && e.att != 2 {
warn(
z,
m::UNKNOWN_INTERNAL,
&[format!("{:04x}", e.att).as_bytes()],
);
}
}
pub(crate) struct InArchive {
pub(crate) f: Option<CFile>,
}
pub(crate) enum OpenFail {
Code(i32),
Exit(Exit),
}
fn open_entry_disk(
z: &mut Zr,
ia: &mut InArchive,
start_disk: u64,
copying: bool,
) -> Result<(), OpenFail> {
if start_disk != z.current_in_disk {
ia.f = None;
}
z.current_in_disk = start_disk;
let ip = z.o.in_path.clone().unwrap_or_default();
let mut split_path =
super::split::get_in_split_path(z, &ip, start_disk).map_err(OpenFail::Exit)?;
while ia.f.is_none() {
match CFile::open(z, &split_path) {
Ok(f) => ia.f = Some(f),
Err(e) => {
if copying && !z.o.noisy {
return Err(OpenFail::Exit(z.ziperr_os(ZE_OPEN, &split_path, &e)));
}
let r =
super::split::ask_for_split_read_path(z, start_disk).map_err(OpenFail::Exit)?;
if !copying {
if r != super::split::ZE_OK {
return Err(OpenFail::Code(super::cli::ZE_ABORT));
}
} else if super::split::is_abort(r) {
return Err(OpenFail::Code(super::cli::ZE_ABORT));
} else if (z.o.fix == 1 || z.o.fix == 2) && r == super::split::ANSWER_SKIP {
return Err(OpenFail::Code(ZE_FORM));
}
let ip = z.o.in_path.clone().unwrap_or_default();
split_path =
super::split::get_in_split_path(z, &ip, start_disk).map_err(OpenFail::Exit)?;
}
}
}
Ok(())
}
pub(crate) struct Local {
pub(crate) lflg: u16,
pub(crate) extra: Vec<u8>,
pub(crate) ver: u16,
pub(crate) how: u16,
pub(crate) tim: u32,
pub(crate) crc: u32,
pub(crate) iname: Vec<u8>,
}
pub(crate) enum LocalFail {
Code(i32),
Exit(Exit),
}
pub(crate) fn readlocal(z: &mut Zr, ia: &mut InArchive, zf: &ZFile) -> Result<Local, LocalFail> {
match open_entry_disk(z, ia, zf.e.dsk, false) {
Ok(()) => {}
Err(OpenFail::Code(c)) => return Err(LocalFail::Code(c)),
Err(OpenFail::Exit(x)) => return Err(LocalFail::Code(x.code)),
}
let e = &zf.e;
let f = ia.f.as_mut().expect("opened above");
if !f.seek(i128::from(e.off)) {
let s = strerror(f.errno);
ia.f = None;
z.zipwarn(m::READING_ARCHIVE_FSEEK.as_bytes(), &s);
return Err(LocalFail::Code(ZE_READ));
}
if !at_signature(f, b"PK\x03\x04") {
ia.f = None;
z.zipwarn(m::DID_NOT_FIND_ENTRY.as_bytes(), cstr(&e.iname));
return Err(LocalFail::Code(ZE_FORM));
}
let buf = f.fread(LOCHEAD as usize);
if buf.len() != LOCHEAD as usize {
let (err, s) = (f.ferror(), strerror(f.errno));
ia.f = None;
z.zipwarn(m::READING_LOCAL_ENTRY.as_bytes(), &s);
return Err(LocalFail::Code(if err { ZE_READ } else { ZE_EOF }));
}
let ver = sh(&buf, 0);
let lflg = sh(&buf, 2);
let how = sh(&buf, 4);
let tim = lg(&buf, 6);
let crc = lg(&buf, 10);
let nam = usize::from(sh(&buf, 22));
let ext = usize::from(sh(&buf, 24));
let name = f.fread(nam);
let extra = if name.len() == nam {
f.fread(ext)
} else {
Vec::new()
};
if name.len() != nam || extra.len() != ext {
let err = f.ferror();
ia.f = None;
return Err(LocalFail::Code(if err { ZE_READ } else { ZE_EOF }));
}
if flavour::unicode_support(z.flavour) && z.o.unicode_mismatch != 3 {
let cut = &name[..name.len().min(e.nam.unwrap_or(e.iname.len()))];
unicode::read_unicode_path(z, &extra, cut, unicode::PathReader::ReadLocal)
.map_err(LocalFail::Exit)?;
}
let iname = cstr(&e.iname).to_vec();
if ver != e.ver {
let mut s = Vec::new();
printf(
&mut s,
m::LOCAL_VERSION_NEEDED,
&[ver.to_string().as_bytes(), e.ver.to_string().as_bytes()],
);
z.zipwarn(&s, &iname);
}
if lflg != e.flg {
z.zipwarn(m::LOCAL_FLAG_MISMATCH.as_bytes(), &iname);
}
if crc != e.crc {
z.zipwarn(m::LOCAL_CRC_MISMATCH.as_bytes(), &iname);
}
Ok(Local {
lflg,
extra,
ver,
how,
tim,
crc,
iname: name,
})
}
pub(crate) fn copy_dot(z: &mut Zr, k: usize) {
if z.o.dot_size > 0 {
if z.o.noisy && z.o.dot_count == -1 {
z.mesg_write(b" ");
z.o.dot_count += 1;
}
z.o.dot_count += k as i64;
if z.o.dot_size <= z.o.dot_count {
z.o.dot_count = 0;
}
}
if (z.o.verbose > 0 || z.o.noisy) && z.o.dot_size != 0 && z.o.dot_count == 0 {
z.mesg_write(b".");
z.set_line(true);
}
}
pub(crate) enum CopyFail {
Code(i32, Option<io::Error>),
Write(io::Error),
Exit(Exit),
}
pub(crate) fn next_in_split(z: &mut Zr, ia: &mut InArchive) -> Result<bool, CopyFail> {
z.current_in_disk += 1;
if z.current_in_disk >= z.total_disks {
return Ok(false);
}
let ip = z.o.in_path.clone().unwrap_or_default();
let mut split_path = if z.current_in_disk == z.total_disks - 1 {
ip
} else {
super::split::get_in_split_path(z, &ip, z.current_in_disk).map_err(CopyFail::Exit)?
};
ia.f = None;
loop {
if let Ok(f) = CFile::open(z, &split_path) {
ia.f = Some(f);
break;
}
if z.o.fix == 1 && z.skip_this_disk != 0 {
return Err(CopyFail::Code(ZE_FORM, None));
}
let r =
super::split::ask_for_split_read_path(z, z.current_in_disk).map_err(CopyFail::Exit)?;
if super::split::is_abort(r) {
z.zipwarn(m::COULD_NOT_FIND_SPLIT.as_bytes(), &split_path);
return Err(CopyFail::Code(super::cli::ZE_ABORT, None));
}
if r == super::split::ANSWER_EOF {
z.zipmessage_nl(b"", true);
z.zipwarn(m::USER_ENDED_READING.as_bytes(), b"");
return Err(CopyFail::Code(ZE_EOF, None));
}
if z.o.fix == 2 && z.skip_this_disk != 0 {
z.zipwarn(m::SKIPPING_SPLIT_FILE.as_bytes(), &split_path);
z.current_in_disk += 1;
}
split_path = if z.current_in_disk == z.total_disks.wrapping_sub(1) {
z.o.in_path.clone().unwrap_or_default()
} else {
let zf = z.o.zipfile.clone().unwrap_or_default();
super::split::get_in_split_path(z, &zf, z.current_in_disk).map_err(CopyFail::Exit)?
};
}
if z.o.fix == 2 && z.skip_this_disk != 0 {
return Err(CopyFail::Code(ZE_FORM, None));
}
Ok(true)
}
pub(crate) fn bfcopy(
z: &mut Zr,
ia: &mut InArchive,
out: &mut ZOut,
n: u64,
) -> Result<(), CopyFail> {
let copy_dots = z.o.copy_only && !z.o.display_globaldots;
if copy_dots {
z.o.dot_count = -1;
}
let mut m: u64 = 0;
while m < n {
let brd = (n - m).min(CBSZ as u64) as usize;
let Some(f) = ia.f.as_mut() else { break };
let b = f.fread(brd);
if b.is_empty() {
if f.ferror() {
return Err(CopyFail::Code(ZE_READ, Some(f.error())));
}
break;
}
let r = out.bfwrite(&b, Bf::Data);
super::zipup::drain_dots(z, out);
r.map_err(CopyFail::Write)?;
m += b.len() as u64;
if copy_dots {
copy_dot(z, b.len());
}
if m < n && ia.f.as_ref().is_some_and(CFile::feof) && !next_in_split(z, ia)? {
break;
}
}
Ok(())
}
pub(crate) fn zipcopy(
z: &mut Zr,
ia: &mut InArchive,
out: &mut ZOut,
tempzn: &mut u64,
zf: &mut ZFile,
) -> Result<(), CopyFail> {
match open_entry_disk(z, ia, zf.e.dsk, true) {
Ok(()) => {}
Err(OpenFail::Code(c)) => return Err(CopyFail::Code(c, None)),
Err(OpenFail::Exit(x)) => return Err(CopyFail::Exit(x)),
}
let f = ia.f.as_mut().expect("opened above");
if !f.seek(i128::from(zf.e.off)) {
let (s, err) = (strerror(f.errno), f.error());
ia.f = None;
z.zipwarn(m::READING_ARCHIVE_FSEEK.as_bytes(), &s);
return Err(CopyFail::Code(ZE_READ, Some(err)));
}
if !at_signature(f, b"PK\x03\x04") {
ia.f = None;
z.zipwarn(m::DID_NOT_FIND_ENTRY.as_bytes(), cstr(&zf.e.iname));
return Err(CopyFail::Code(ZE_FORM, None));
}
let buf = f.fread(LOCHEAD as usize);
if buf.len() != LOCHEAD as usize {
let (err, s) = (f.ferror(), strerror(f.errno));
ia.f = None;
z.zipwarn(m::READING_LOCAL_ENTRY.as_bytes(), &s);
return Err(CopyFail::Code(if err { ZE_READ } else { ZE_EOF }, None));
}
let mut lz = ZEntry {
ver: sh(&buf, 0),
lflg: sh(&buf, 2),
how: sh(&buf, 4),
tim: lg(&buf, 6),
crc: lg(&buf, 10),
..ZEntry::default()
};
let mut nam = usize::from(sh(&buf, 22));
let ext = usize::from(sh(&buf, 24));
lz.iname = f.fread(nam);
if lz.iname.len() != nam {
return Err(read_or_eof(f));
}
lz.extra = f.fread(ext);
if lz.extra.len() != ext {
return Err(read_or_eof(f));
}
lz.name = cstr(&lz.iname).to_vec();
out.zip64_entry = get_extra_field(0x0001, &lz.extra).is_some();
lz.vem = zf.e.vem;
if flavour::unicode_support(z.flavour) && z.o.unicode_mismatch != 3 {
if zf.e.flg & UTF8_BIT != 0 {
let u = cstr(&lz.iname).to_vec();
let Some(local) = unicode::utf8_to_local(z.using_utf8, z.o.unicode_escape_all, &u)
else {
return Err(CopyFail::Exit(unicode::segv(z)));
};
lz.uname = Some(u);
lz.iname = local;
} else {
let iname = lz.iname.clone();
lz.uname = unicode::read_unicode_path(
z,
&lz.extra,
&iname,
unicode::PathReader::Copy(&lz.name.clone()),
)
.map_err(CopyFail::Exit)?;
}
}
let ciname = cstr(&zf.e.iname).to_vec();
if lz.ver != zf.e.ver {
z.zipwarn(m::LOCAL_VERSION_MISMATCH.as_bytes(), &ciname);
}
if lz.lflg != zf.e.flg {
z.zipwarn(m::LOCAL_FLAG_MISMATCH.as_bytes(), &ciname);
}
if zf.e.flg & 8 == 0 && lz.crc != zf.e.crc {
z.zipwarn(m::LOCAL_CRC_MISMATCH.as_bytes(), &ciname);
}
if z.o.fix != 3 && cstr(&lz.iname) != ciname.as_slice() {
z.zipwarn(m::LOCAL_NAME_MISMATCH.as_bytes(), &ciname);
}
lz.len = zf.e.len;
lz.siz = zf.e.siz;
if z.o.fix == 3 {
zf.e.nam = None;
zf.e.iname = ciname.clone();
nam = ciname.len();
lz.iname = ciname.clone();
}
zf.e.dsk = out.current_disk;
zf.e.off = out.w.offset();
if zf.e.flg & 1 == 0 {
zf.e.flg &= !8;
lz.lflg &= !8;
}
zf.e.lflg = lz.lflg;
let e = if zf.e.lflg & 8 != 0 {
if out.zip64_entry { 24 } else { 16 }
} else {
0
};
*tempzn = tempzn
.wrapping_add(4 + LOCHEAD + nam as u64 + ext as u64 + e)
.wrapping_add(zf.e.siz);
lz.crc = zf.e.crc;
lz.oname = b"(null)".to_vec();
let r = super::zipfile::putlocal(&mut lz, out);
super::zipup::drain_dots(z, out);
match r {
Ok(()) => {}
Err(super::zipfile::PutError::Write(err)) => return Err(CopyFail::Write(err)),
Err(pe) => {
return Err(CopyFail::Exit(super::zipup::put_failed(z, &lz, pe)));
}
}
let r = bfcopy(z, ia, out, lz.siz);
if let Err(CopyFail::Code(super::cli::ZE_ABORT, _)) = r {
return Err(CopyFail::Exit(z.ziperr(
super::cli::ZE_ABORT,
m::COULD_NOT_FIND_SPLIT_ABORT.as_bytes(),
)));
}
let (code, err) = match r {
Ok(()) => (0, None),
Err(CopyFail::Code(c, e)) => (c, e),
Err(e) => return Err(e),
};
if code == ZE_EOF || z.skip_this_disk != 0 {
z.zipwarn(m::ABORTING_ENTRY.as_bytes(), &zf.e.oname.clone());
return Err(CopyFail::Code(if code == 0 { ZE_FORM } else { code }, None));
}
if zf.e.flg & 8 != 0 {
let r = super::zipfile::putextended(&lz, out);
super::zipup::drain_dots(z, out);
r.map_err(CopyFail::Write)?;
}
let r = out.close_local_split();
super::zipup::drain_dots(z, out);
r.map_err(CopyFail::Write)?;
if code != 0 {
return Err(CopyFail::Code(code, err));
}
Ok(())
}
fn read_or_eof(f: &CFile) -> CopyFail {
CopyFail::Code(if f.ferror() { ZE_READ } else { ZE_EOF }, None)
}
fn ef_scan_ut_time(ef: &[u8], is_cent: bool) -> (u8, Option<i64>) {
let mut flags: u8 = 0;
let mut mtime: Option<i64> = None;
let mut have_new_type_eb = false;
let mut p = 0usize;
while ef.len() - p >= 4 {
let id = sh(ef, p);
let len = usize::from(sh(ef, p + 2));
if len > ef.len() - p - 4 {
break;
}
let data = &ef[p + 4..p + 4 + len];
match id {
0x5455 => {
flags = 0;
have_new_type_eb = true;
if len >= 1 {
flags |= data[0];
if flags & 1 != 0 {
if 4 < len {
mtime = Some(i64::from(lg(data, 1)));
} else {
flags &= !1;
}
}
if !is_cent {
let mut idx = 1 + if flags & 1 != 0 { 4 } else { 0 };
if flags & 2 != 0 {
if idx + 4 <= len {
idx += 4;
} else {
flags &= !2;
}
}
if flags & 4 != 0 && idx + 4 > len {
flags &= !4;
}
}
}
}
0x7855 if !have_new_type_eb => {
flags = 0;
have_new_type_eb = true;
}
0x5855 if len >= 8 && !have_new_type_eb => {
mtime = Some(i64::from(lg(data, 4)));
flags |= 1 | 2;
}
_ => {}
}
p += len + 4;
}
(flags, mtime)
}
pub(crate) fn ut_mtime(e: &ZEntry) -> Option<i64> {
let (mut flags, mut t) = if e.extra.is_empty() {
(0, None)
} else {
ef_scan_ut_time(&e.extra, false)
};
if flags == 0 && !e.cextra.is_empty() {
(flags, t) = ef_scan_ut_time(&e.cextra, true);
}
if flags & 1 != 0 { t } else { None }
}
pub(crate) fn disk_time(z: &Zr, name: &[u8]) -> (u64, i64, i64) {
let stripped = name.strip_suffix(b"/").unwrap_or(name);
if stripped.is_empty() {
return (0, -1, 0);
}
let p = z.host.path(stripped);
let md = if z.o.linkput {
std::fs::symlink_metadata(&p)
} else {
std::fs::metadata(&p)
};
let Ok(md) = md else {
return (0, -1, 0);
};
let meta = super::unix::meta_of(&md);
let usize = if meta.file_type() == super::unix::S_IFREG {
meta.size as i64
} else {
-1
};
(super::unix::unix2dostime(z, meta.mtime), usize, meta.mtime)
}
#[cfg(test)]
mod tests;