use super::fileio::{self, DoString, c_str};
use super::list::ratio;
use super::messages as m;
use super::process::{self, CENTRAL_HDR_SIG, CentralEntry};
use super::{PK_BADERR, PK_COOL, PK_EOF, PK_FIND, PK_WARN, Uz};
mod extra;
mod long;
mod short;
#[cfg(test)]
mod tests;
pub(super) const NUM_HOSTS: u8 = 31;
pub(super) const NUM_METHODS: usize = 17;
const LREC_SIZE: u64 = 26;
pub(super) const FS_FAT_: u8 = 0;
pub(super) const AMIGA_: u8 = 1;
pub(super) const VMS_: u8 = 2;
pub(super) const UNIX_: u8 = 3;
pub(super) const VM_CMS_: u8 = 4;
pub(super) const FS_HPFS_: u8 = 6;
pub(super) const FS_NTFS_: u8 = 11;
pub(super) const ACORN_: u8 = 13;
pub(super) const FS_VFAT_: u8 = 14;
pub(super) const MVS_: u8 = 15;
pub(super) const THEOS_: u8 = 18;
pub(super) const IMPLODED: u16 = 6;
pub(super) const DEFLATED: u16 = 8;
pub(super) const ENHDEFLATED: u16 = 9;
pub(super) fn zu(v: u64) -> Vec<u8> {
v.to_string().into_bytes()
}
pub(super) fn zu_w(v: u64, w: usize) -> Vec<u8> {
format!("{v:>w$}").into_bytes()
}
pub(super) fn zx(v: u64) -> Vec<u8> {
format!("{v:016X}").into_bytes()
}
pub(crate) fn zi_end_central(uz: &mut Uz) {
if uz.o.lflag > 9 {
uz.info(0, m::END_CENT_DIR_REC.as_bytes());
uz.info(0, m::LINE_SEPARATORS.as_bytes());
let ziplen = uz.zin.ziplen;
let size = format!("{ziplen:>11}").into_bytes();
uz.info_fmt(0, m::ZIP_FSIZE_VERBOSE, &[&size, &zx(ziplen as u64)]);
let (real, expect) = (uz.real_ecrec_offset as u64, uz.expect_ecrec_offset as u64);
uz.info_fmt(
0,
m::ACT_OFFSET_CENT_DIR,
&[&zu_w(real, 11), &zx(real), &zu_w(expect, 11), &zx(expect)],
);
let ec = uz.ecrec;
let entries = |n: u64| if n == 1 { m::ENTRY } else { m::ENTRIES };
if ec.number_this_disk == 0 {
uz.info_fmt(
0,
m::SINGLE_PART_ARCHIVE1,
&[
&zu(ec.total_entries_central_dir),
entries(ec.total_entries_central_dir).as_bytes(),
&zu(ec.size_central_directory),
&zx(ec.size_central_directory),
],
);
uz.info_fmt(
0,
m::SINGLE_PART_ARCHIVE2,
&[
&zu(ec.offset_start_central_directory),
&zx(ec.offset_start_central_directory),
],
);
} else {
uz.info_fmt(
0,
m::MULTI_PART_ARCHIVE1,
&[
&zu(ec.number_this_disk.wrapping_add(1)),
&zu(ec.num_disk_start_cdir.wrapping_add(1)),
],
);
uz.info_fmt(
0,
m::MULTI_PART_ARCHIVE2,
&[
&zu(ec.offset_start_central_directory),
&zx(ec.offset_start_central_directory),
&zu(ec.size_central_directory),
&zx(ec.size_central_directory),
],
);
let this = ec.num_entries_centrl_dir_ths_disk;
let verb = if this == 1 { m::IS } else { m::ARE };
uz.info_fmt(
0,
m::MULTI_PART_ARCHIVE3,
&[
&zu(this),
verb.as_bytes(),
&zu(ec.total_entries_central_dir),
entries(ec.total_entries_central_dir).as_bytes(),
],
);
}
} else if uz.o.hflag {
let size = uz.zin.ziplen.to_string().into_bytes();
uz.info_fmt(
0,
m::ZIP_INF_HEADER2,
&[&size, &zu(uz.ecrec.total_entries_central_dir)],
);
}
}
fn skip(uz: &mut Uz, length: u16) -> Option<i32> {
(length != 0).then(|| fileio::do_string(uz, usize::from(length), DoString::Skip, None))
}
pub(crate) fn zipinfo(uz: &mut Uz) -> i32 {
let mut error_in_archive = PK_COOL;
let mut members: u64 = 0;
let (mut tot_csize, mut tot_ucsize) = (0u64, 0u64);
let mut fn_matched = vec![false; uz.pfnames.len()];
let mut xn_matched = vec![false; uz.pxnames.len()];
uz.o.l_flag = 0;
let mut endprev: u64 = if uz.crec_relative_offset_local_header == 4 {
4
} else {
0
};
let mut j: u64 = 1;
loop {
if process::read_sig(uz) == 0 {
error_in_archive = PK_EOF;
break;
}
if uz.sig != CENTRAL_HDR_SIG {
let mask = if uz.ecrec.have_ecr64 {
u64::MAX
} else {
0xffff
};
if (j.wrapping_sub(1) & mask) == uz.ecrec.total_entries_central_dir {
break;
}
let n = j.to_string();
uz.info_fmt(0x401, m::CENT_SIG_MSG, &[n.as_bytes()]);
uz.info(0x401, m::REPORT_MSG.as_bytes());
error_in_archive = PK_BADERR; break;
}
let mut e = match process::process_cdir_file_hdr(uz) {
Ok(e) => e,
Err(code) => {
error_in_archive = code; break;
}
};
e.index = j;
uz.crec_relative_offset_local_header = e.relative_offset_local_header;
let pinfo = e.pinfo;
let mut error = fileio::read_filename(uz, usize::from(e.filename_length), &pinfo, false);
if error != PK_COOL {
if error > error_in_archive {
error_in_archive = error;
}
if error > PK_WARN {
break;
}
}
e.filename = c_str(&uz.filename).to_vec();
let do_this_file = process::selected(
uz,
&e.filename,
Some(&mut fn_matched),
Some(&mut xn_matched),
);
if do_this_file {
error = process::read_extra_field(uz, &mut e);
if error != 0 {
e.extra = None; error_in_archive = error;
}
e.filename = c_str(&uz.filename).to_vec();
uz.crec_relative_offset_local_header = e.relative_offset_local_header;
match uz.o.lflag {
1 | 2 => {
fileio::fnprint(uz);
if let Some(err) = skip(uz, e.file_comment_length) {
error = err;
}
}
3..=5 => {
error = short::zi_short(uz, &e);
if error != PK_COOL {
error_in_archive = error; }
}
10 => {
let n = j.to_string();
uz.info_fmt(0, m::CENTRAL_DIR_ENTRY, &[n.as_bytes()]);
error = long::zi_long(uz, &e, &mut endprev, error_in_archive);
if error != PK_COOL {
error_in_archive = error; }
}
_ => {
if let Some(err) = skip(uz, e.file_comment_length) {
error = err;
}
}
}
if error > PK_WARN {
break;
}
tot_csize = tot_csize.wrapping_add(e.csize);
tot_ucsize = tot_ucsize.wrapping_add(e.ucsize);
if e.general_purpose_bit_flag & 1 != 0 {
tot_csize = tot_csize.wrapping_sub(12); }
members += 1;
} else {
skip(uz, e.extra_field_length);
skip(uz, e.file_comment_length);
if endprev != 0 {
endprev = 0;
}
}
j = j.wrapping_add(1);
}
if error_in_archive <= PK_WARN && uz.o.tflag {
let r = ratio(tot_ucsize, tot_csize);
let sgn: &[u8] = if r < 0 { b"-" } else { b"" };
let cfactor = r.unsigned_abs();
let plural: &[u8] = if members == 1 {
b""
} else {
m::PLUR_SUFX.as_bytes()
};
let (n, whole, tenth) = (
members.to_string(),
(cfactor / 10).to_string(),
(cfactor % 10).to_string(),
);
uz.info_fmt(
0,
m::ZIPFILE_STATS,
&[
n.as_bytes(),
plural,
&zu(tot_ucsize),
&zu(tot_csize),
sgn,
whole.as_bytes(),
tenth.as_bytes(),
],
);
}
if error_in_archive <= PK_WARN {
let names = uz.pfnames.clone();
for (name, _) in names.iter().zip(&fn_matched).filter(|(_, hit)| !**hit) {
uz.info_fmt(0x401, m::FILENAME_NOT_MATCHED, &[name]);
}
let names = uz.pxnames.clone();
for (name, _) in names.iter().zip(&xn_matched).filter(|(_, hit)| !**hit) {
uz.info_fmt(0x401, m::EXCL_FILENAME_NOT_MATCHED, &[name]);
}
}
if error_in_archive <= PK_WARN {
let sig = uz.sig;
let want = if uz.ecrec.have_ecr64 {
process::END_CENTRAL64_SIG
} else {
process::END_CENTRAL_SIG
};
if sig != want && !uz.ecrec.is_zip64_archive && sig != process::END_CENTRAL_SIG {
uz.info(0x401, m::END_SIG_MSG.as_bytes());
error_in_archive = PK_WARN; }
if members == 0 && error_in_archive <= PK_WARN {
error_in_archive = PK_FIND;
}
if uz.o.lflag >= 10 {
uz.info(0, b"\n");
}
}
error_in_archive
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) enum Clock {
Local,
Utc,
}
pub(super) fn zi_time(uz: &Uz, datetimez: u32, modtimez: Option<i64>, clock: Clock) -> Vec<u8> {
let t = match modtimez {
Some(mt) => {
let t = match clock {
Clock::Utc => Some(fileio::gmtime(mt)),
Clock::Local => fileio::localtime(uz, mt),
};
if uz.o.lflag > 9 && t.is_none() {
return m::LNG_YMDHMS_TIME_ERROR.as_bytes().to_vec();
}
t
}
None => None,
};
let (yr, mo, dy, hh, mm, ss) = match t {
Some(t) => ((t.year - 1900) as u32, t.mon, t.mday, t.hour, t.min, t.sec),
None => (
((datetimez >> 25) & 0x7f) + 80,
(datetimez >> 21) & 0x0f,
(datetimez >> 16) & 0x1f,
(datetimez >> 11) & 0x1f,
(datetimez >> 5) & 0x3f,
(datetimez << 1) & 0x3e,
),
};
let monthbuf;
let monthstr: &[u8] = if mo == 0 || mo > 12 {
monthbuf = format!("{mo:03}").into_bytes(); &monthbuf
} else {
m::MONTHS[(mo - 1) as usize].as_bytes()
};
if uz.o.lflag > 9 {
m::lng_ymdhms_time(yr.wrapping_add(1900), monthstr, dy, hh, mm, ss)
} else if uz.o.t_flag {
m::decimal_time(yr.wrapping_add(1900), mo, dy, hh, mm, ss)
} else {
m::sht_ymdhm_time(yr % 100, monthstr, dy, hh, mm)
}
}
pub(super) fn ut_mtime(f: super::Flavour, e: &CentralEntry) -> Option<i64> {
let ef = e.extra.as_deref()?;
let mut z_utime = process::IzTimes::default();
let flags = process::ef_scan_for_izux(f, ef, true, e.last_mod_dos_datetime, Some(&mut z_utime));
(flags & process::EB_UT_FL_MTIME != 0).then_some(z_utime.mtime)
}
pub(super) fn next_endprev(e: &CentralEntry) -> u64 {
e.relative_offset_local_header
.wrapping_add(4 + LREC_SIZE)
.wrapping_add(u64::from(e.filename_length))
.wrapping_add(u64::from(e.extra_field_length))
.wrapping_add(e.csize)
}