use std::io::{self, Write};
use super::readzip::cstr;
use super::unicode::{self, UTF8_BIT};
const LOCSIG: u32 = 0x0403_4b50;
const CENSIG: u32 = 0x0201_4b50;
const ENDSIG: u32 = 0x0605_4b50;
const EXTLOCSIG: u32 = 0x0807_4b50;
const ZIP64_CENTRAL_DIR_TAIL_SIG: u32 = 0x0606_4b50;
const ZIP64_CENTRAL_DIR_TAIL_END_SIG: u32 = 0x0706_4b50;
const ZIP64_CENTRAL_DIR_TAIL_SIZE: u64 = 56 - 8 - 4;
pub(crate) const ZIP64_MIN_VER: u16 = 45;
const ZIP64_EF_TAG: u16 = 0x0001;
const ZIP_EF_HEADER_SIZE: usize = 4;
const ZIP_UWORD32_MAX: u64 = 0xffff_ffff;
const ZIP_UWORD16_MAX: u64 = 0xffff;
pub(crate) const LOCHEAD: u64 = 26;
pub(crate) const CENHEAD: u64 = 42;
#[derive(Debug, Clone, Default)]
pub(crate) struct ZEntry {
pub(crate) name: Vec<u8>,
pub(crate) iname: Vec<u8>,
pub(crate) oname: Vec<u8>,
pub(crate) vem: u16,
pub(crate) ver: u16,
pub(crate) flg: u16,
pub(crate) lflg: u16,
pub(crate) how: u16,
pub(crate) tim: u32,
pub(crate) crc: u32,
pub(crate) siz: u64,
pub(crate) len: u64,
pub(crate) extra: Vec<u8>,
pub(crate) cextra: Vec<u8>,
pub(crate) comment: Vec<u8>,
pub(crate) dsk: u64,
pub(crate) att: u16,
pub(crate) atx: u32,
pub(crate) off: u64,
pub(crate) uname: Option<Vec<u8>>,
pub(crate) nam: Option<usize>,
}
pub(crate) trait ZWrite: Write {
fn seekable(&self) -> bool;
fn offset(&self) -> u64;
fn seek_to(&mut self, at: u64) -> io::Result<()>;
fn write_here(&mut self, bytes: &[u8]) -> io::Result<()>;
fn seek_end(&mut self) -> io::Result<()>;
fn seek_here(&mut self) -> io::Result<()>;
fn ferror(&self) -> Option<io::Error>;
fn seek_raw(&mut self, _at: u64) -> io::Result<()> {
Err(io::Error::from_raw_os_error(29))
}
fn tell_raw(&mut self) -> io::Result<u64> {
Err(io::Error::from_raw_os_error(29))
}
fn set_offset(&mut self, _at: u64) {}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Bf {
Data,
LocalHeader,
CentralHeader,
Header,
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct GlobalDots {
pub(crate) dot_size: i64,
pub(crate) dot_count: i64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum Pend {
Dots(Vec<u8>),
Raw(Vec<u8>),
NlIfLine,
Message(Vec<u8>),
LogOnly(Vec<u8>),
}
pub(crate) struct ZOut {
pub(crate) w: Box<dyn ZWrite>,
pub(crate) bytes_this_entry: u64,
pub(crate) current_local_offset: u64,
pub(crate) current_local_disk: u64,
pub(crate) current_disk: u64,
pub(crate) bytes_prev_splits: u64,
cd_start: Option<(u64, u64)>,
pub(crate) cd_entries_this_disk: u64,
total_cd_entries: u64,
pub(crate) zip64_entry: bool,
pub(crate) zip64_archive: bool,
pub(crate) force_zip64: i8,
pub(crate) gdots: Option<GlobalDots>,
pub(crate) pending: Vec<Pend>,
pub(crate) split: Option<Box<super::split::SplitW>>,
pub(crate) split_size: u64,
pub(crate) unicode: Option<(bool, bool)>,
}
impl ZOut {
pub(crate) fn new(w: Box<dyn ZWrite>, force_zip64: i8, gdots: Option<GlobalDots>) -> ZOut {
ZOut {
w,
bytes_this_entry: 0,
current_local_offset: 0,
current_local_disk: 0,
current_disk: 0,
bytes_prev_splits: 0,
cd_start: None,
cd_entries_this_disk: 0,
total_cd_entries: 0,
zip64_entry: false,
zip64_archive: false,
force_zip64,
gdots,
pending: Vec::new(),
split: None,
split_size: 0,
unicode: None,
}
}
pub(crate) fn restart(&mut self, w: Box<dyn ZWrite>) {
self.w = w;
self.bytes_this_entry = 0;
self.current_local_offset = 0;
self.current_local_disk = 0;
self.current_disk = 0;
self.bytes_prev_splits = 0;
self.cd_start = None;
self.cd_entries_this_disk = 0;
self.total_cd_entries = 0;
self.pending.clear();
}
#[cfg(test)]
pub(crate) fn pending_dots(&self) -> Vec<u8> {
let mut d = Vec::new();
for p in &self.pending {
if let Pend::Dots(b) = p {
d.extend_from_slice(b);
}
}
d
}
fn dot(&mut self, c: u8) {
if let Some(Pend::Dots(d)) = self.pending.last_mut() {
d.push(c);
} else {
self.pending.push(Pend::Dots(vec![c]));
}
}
pub(crate) fn bfwrite(&mut self, b: &[u8], mode: Bf) -> io::Result<()> {
if mode == Bf::LocalHeader {
self.bytes_this_entry = 0;
if let Some(s) = self.split.as_mut() {
s.local_header_starts();
}
self.current_local_disk = self.current_disk;
self.current_local_offset = self.w.offset();
}
let header = mode != Bf::Data;
let split_size = self.split_size;
let left = if split_size == 0 {
b.len() as u64
} else {
split_size.saturating_sub(self.w.offset())
};
let mut to_write = b.len();
if to_write as u64 > left {
to_write = if header { 0 } else { left as usize };
}
if mode == Bf::CentralHeader {
if self.cd_start.is_none() {
self.cd_start = Some((self.current_disk, self.w.offset()));
}
self.cd_entries_this_disk += 1;
self.total_cd_entries += 1;
}
let mut written = 0usize;
let mut result = Ok(());
if to_write > 0 {
match self.w.write_all(&b[..to_write]) {
Ok(()) => written = to_write,
Err(e) => result = Err(e),
}
if mode == Bf::Data {
self.bytes_this_entry += written as u64;
}
}
if written < b.len() && self.split.is_some() {
result = self.next_split(&b[written..], mode, result.err());
}
if let Some(mut g) = self.gdots {
if g.dot_size > 0 {
if g.dot_count == -1 {
self.dot(b' ');
g.dot_count = 1;
}
if b.len() > 1000 {
g.dot_count += 1;
if g.dot_size <= g.dot_count.saturating_mul(b.len() as i64) {
g.dot_count = 0;
}
}
}
if g.dot_size != 0 && g.dot_count == 0 {
g.dot_count += 1;
self.dot(b'.');
}
self.gdots = Some(g);
}
result
}
fn end_disks(&self) -> (u64, u64, u64) {
let (d, o) = self.cd_start.unwrap_or((0, 0));
(self.current_disk, d, o)
}
}
fn put16(b: &mut Vec<u8>, v: u16) {
b.extend_from_slice(&v.to_le_bytes());
}
fn put32(b: &mut Vec<u8>, v: u32) {
b.extend_from_slice(&v.to_le_bytes());
}
fn put64(b: &mut Vec<u8>, v: u64) {
b.extend_from_slice(&v.to_le_bytes());
}
fn get_extra_field(tag: u16, extra: &[u8]) -> Option<usize> {
let mut p = 0usize;
while p + ZIP_EF_HEADER_SIZE < extra.len() {
let block_tag = u16::from_le_bytes([extra[p], extra[p + 1]]);
let size = u16::from_le_bytes([extra[p + 2], extra[p + 3]]);
if block_tag == tag {
return Some(p);
}
p += usize::from(size) + ZIP_EF_HEADER_SIZE;
}
None
}
fn add_local_zip64_extra_field(z: &mut ZEntry) {
const Z64_LOCAL_LEN: usize = ZIP_EF_HEADER_SIZE + 8 + 8;
let at = match get_extra_field(ZIP64_EF_TAG, &z.extra) {
None => {
let at = z.extra.len();
z.extra.resize(at + Z64_LOCAL_LEN, 0);
at
}
Some(old) => {
let size = usize::from(u16::from_le_bytes([z.extra[old + 2], z.extra[old + 3]]));
if size == Z64_LOCAL_LEN - ZIP_EF_HEADER_SIZE {
old
} else {
let end = (old + ZIP_EF_HEADER_SIZE + size).min(z.extra.len());
let tail = z.extra.split_off(end);
z.extra.truncate(old);
let at = z.extra.len();
z.extra.resize(at + Z64_LOCAL_LEN, 0);
z.extra.extend_from_slice(&tail);
at
}
}
};
let mut b = Vec::with_capacity(Z64_LOCAL_LEN);
put16(&mut b, ZIP64_EF_TAG);
put16(&mut b, (Z64_LOCAL_LEN - ZIP_EF_HEADER_SIZE) as u16);
put64(&mut b, z.len);
put64(&mut b, z.siz);
z.extra[at..at + Z64_LOCAL_LEN].copy_from_slice(&b);
}
fn add_central_zip64_extra_field(z: &mut ZEntry, force_zip64: i8) -> Result<(), ()> {
let mut efsize = ZIP_EF_HEADER_SIZE;
let mut used = false;
if z.len > ZIP_UWORD32_MAX || force_zip64 == 1 {
efsize += 8;
used = true;
}
if z.siz > ZIP_UWORD32_MAX {
efsize += 8;
used = true;
}
if z.off > ZIP_UWORD32_MAX {
efsize += 8;
used = true;
}
if z.dsk > ZIP_UWORD16_MAX {
efsize += 4;
used = true;
}
if used && force_zip64 == 0 {
return Err(());
}
let at = if z.cextra.is_empty() {
if efsize == ZIP_EF_HEADER_SIZE {
return Ok(());
}
z.cextra.resize(efsize, 0);
0
} else {
match get_extra_field(ZIP64_EF_TAG, &z.cextra) {
None => {
let at = z.cextra.len();
z.cextra.resize(at + efsize, 0);
at
}
Some(old) => {
let size = usize::from(u16::from_le_bytes([z.cextra[old + 2], z.cextra[old + 3]]));
let oldefsize = size + ZIP_EF_HEADER_SIZE;
if z.cextra.len() == oldefsize {
z.cextra.resize(efsize, 0);
0
} else {
let end = (old + oldefsize).min(z.cextra.len());
z.cextra.drain(old..end);
let at = z.cextra.len();
z.cextra.resize(at + efsize, 0);
at
}
}
}
};
let mut b = Vec::with_capacity(efsize);
put16(&mut b, ZIP64_EF_TAG);
put16(&mut b, (efsize - ZIP_EF_HEADER_SIZE) as u16);
if z.len > ZIP_UWORD32_MAX || force_zip64 == 1 {
put64(&mut b, z.len);
}
if z.siz > ZIP_UWORD32_MAX {
put64(&mut b, z.siz);
}
if z.off > ZIP_UWORD32_MAX {
put64(&mut b, z.off);
}
if z.dsk > ZIP_UWORD16_MAX {
put32(&mut b, z.dsk as u32);
}
z.cextra[at..at + efsize].copy_from_slice(&b);
Ok(())
}
#[derive(Debug)]
pub(crate) enum PutError {
Big {
warn: &'static str,
why: &'static str,
},
Write(io::Error),
}
fn local_header(z: &mut ZEntry, out: &mut ZOut, rewrite: bool) -> Result<Vec<u8>, PutError> {
use super::messages as m;
let streaming_in = z.name == b"-";
let force = out.force_zip64;
let needs = z.siz > ZIP_UWORD32_MAX
|| z.len > ZIP_UWORD32_MAX
|| force == 1
|| (force != 0 && streaming_in);
let mut was_zip64 = false;
if !rewrite {
out.zip64_entry = false;
if needs {
if force == 0 {
return Err(PutError::Big {
warn: m::ENTRY_TOO_BIG,
why: m::LARGE_DISABLED_NEEDED,
});
}
out.zip64_entry = true;
if z.ver < ZIP64_MIN_VER {
z.ver = ZIP64_MIN_VER;
}
was_zip64 = true;
}
} else {
was_zip64 = out.zip64_entry;
out.zip64_entry = needs;
if force == 0 && out.zip64_entry {
return Err(PutError::Big {
warn: m::ENTRY_TOO_BIG,
why: m::LARGE_DISABLED_ENTRY_NEEDS,
});
}
if !was_zip64 && out.zip64_entry {
return Err(PutError::Big {
warn: m::ENTRY_TOO_BIG,
why: if force == 0 {
m::UNEXPECTEDLY_LARGE
} else {
m::POOR_COMPRESSION
},
});
}
if out.zip64_entry {
out.zip64_archive = true;
if z.ver < ZIP64_MIN_VER {
z.ver = ZIP64_MIN_VER;
}
}
if was_zip64 && !out.zip64_entry {
z.ver = 20;
}
}
if out.zip64_entry || was_zip64 {
add_local_zip64_extra_field(z);
}
let mut name = z.iname.clone();
if let Some((using_utf8, utf8_force)) = out.unicode {
if let Some(u) = z.uname.clone() {
if utf8_force || using_utf8 {
z.lflg |= UTF8_BIT;
z.flg |= UTF8_BIT;
}
if z.flg & UTF8_BIT != 0 {
name = cstr(&u).to_vec();
} else {
unicode::add_unicode_path_field(&mut z.extra, &z.iname, &u);
}
} else {
z.flg &= !UTF8_BIT;
z.lflg &= !UTF8_BIT;
}
}
let mut b = Vec::with_capacity(30 + name.len() + z.extra.len());
put32(&mut b, LOCSIG);
put16(&mut b, z.ver);
put16(&mut b, z.lflg);
put16(&mut b, z.how);
put32(&mut b, z.tim);
put32(&mut b, z.crc);
if out.zip64_entry {
put32(&mut b, 0xffff_ffff);
put32(&mut b, 0xffff_ffff);
} else {
put32(&mut b, z.siz as u32);
put32(&mut b, z.len as u32);
}
put16(&mut b, name.len() as u16);
put16(&mut b, z.extra.len() as u16);
b.extend_from_slice(&name);
b.extend_from_slice(&z.extra);
Ok(b)
}
pub(crate) fn putlocal(z: &mut ZEntry, out: &mut ZOut) -> Result<(), PutError> {
let b = local_header(z, out, false)?;
out.bfwrite(&b, Bf::LocalHeader).map_err(PutError::Write)
}
pub(crate) fn putlocal_rewrite(z: &mut ZEntry, out: &mut ZOut) -> Result<(), PutError> {
let b = local_header(z, out, true)?;
if out.split.as_ref().is_some_and(|s| s.method == 1) {
return out.rewrite_in_local_split(&b).map_err(PutError::Write);
}
out.w.write_here(&b).map_err(PutError::Write)
}
pub(crate) fn putextended(z: &ZEntry, out: &mut ZOut) -> io::Result<()> {
let mut b = Vec::with_capacity(24);
put32(&mut b, EXTLOCSIG);
put32(&mut b, z.crc);
if out.zip64_entry {
put64(&mut b, z.siz);
put64(&mut b, z.len);
} else {
put32(&mut b, z.siz as u32);
put32(&mut b, z.len as u32);
}
out.bfwrite(&b, Bf::Header)
}
pub(crate) fn putcentral(z: &mut ZEntry, out: &mut ZOut) -> Result<(), PutError> {
let mut name = z.iname.clone();
if let Some((_, utf8_force)) = out.unicode {
if let Some(u) = z.uname.clone() {
if utf8_force {
z.flg |= UTF8_BIT;
}
if z.flg & UTF8_BIT != 0 {
name = cstr(&u).to_vec();
} else {
unicode::add_unicode_path_field(&mut z.cextra, &z.iname, &u);
}
} else {
z.flg &= !UTF8_BIT;
z.lflg &= !UTF8_BIT;
}
}
if (z.siz > ZIP_UWORD32_MAX
|| z.len > ZIP_UWORD32_MAX
|| z.off > ZIP_UWORD32_MAX
|| z.dsk > ZIP_UWORD16_MAX
|| out.force_zip64 == 1)
&& add_central_zip64_extra_field(z, out.force_zip64).is_err()
{
return Err(PutError::Big {
warn: "",
why: super::messages::LARGE_DISABLED_USING,
});
}
let mut b = Vec::with_capacity(46 + name.len() + z.cextra.len() + z.comment.len());
put32(&mut b, CENSIG);
put16(&mut b, z.vem);
put16(&mut b, z.ver);
put16(&mut b, z.flg);
put16(&mut b, z.how);
put32(&mut b, z.tim);
put32(&mut b, z.crc);
put32(&mut b, z.siz.min(ZIP_UWORD32_MAX) as u32);
if z.len > ZIP_UWORD32_MAX || out.force_zip64 == 1 {
put32(&mut b, 0xffff_ffff);
} else {
put32(&mut b, z.len as u32);
}
put16(&mut b, name.len() as u16);
put16(&mut b, z.cextra.len() as u16);
put16(&mut b, z.comment.len() as u16);
put16(&mut b, z.dsk.min(ZIP_UWORD16_MAX) as u16);
put16(&mut b, z.att);
put32(&mut b, z.atx);
put32(&mut b, z.off.min(ZIP_UWORD32_MAX) as u32);
b.extend_from_slice(&name);
b.extend_from_slice(&z.cextra);
b.extend_from_slice(&z.comment);
out.bfwrite(&b, Bf::CentralHeader).map_err(PutError::Write)
}
pub(crate) fn putend(out: &mut ZOut, n: u64, s: u64, c: u64, comment: &[u8]) -> io::Result<()> {
putend_with(out, n, s, c, comment.len(), comment)
}
pub(crate) fn putend_with(
out: &mut ZOut,
n: u64,
s: u64,
c: u64,
m: usize,
comment: &[u8],
) -> io::Result<()> {
let (current_disk, cd_start_disk, cd_start_offset) = out.end_disks();
let zip64_eocd_disk = current_disk;
let zip64_eocd_offset = out.w.offset();
let mut b = Vec::with_capacity(98 + comment.len());
if n > ZIP_UWORD16_MAX || s > ZIP_UWORD32_MAX || c > ZIP_UWORD32_MAX || out.zip64_archive {
put32(&mut b, ZIP64_CENTRAL_DIR_TAIL_SIG);
put64(&mut b, ZIP64_CENTRAL_DIR_TAIL_SIZE);
put16(&mut b, 0x300 + 30);
put16(&mut b, ZIP64_MIN_VER);
put32(&mut b, current_disk as u32);
put32(&mut b, cd_start_disk as u32);
put64(&mut b, out.cd_entries_this_disk);
put64(&mut b, n);
put64(&mut b, s);
put64(&mut b, cd_start_offset);
put32(&mut b, ZIP64_CENTRAL_DIR_TAIL_END_SIG);
put32(&mut b, zip64_eocd_disk as u32);
put64(&mut b, zip64_eocd_offset);
put32(&mut b, (current_disk + 1) as u32);
}
put32(&mut b, ENDSIG);
put16(&mut b, current_disk.min(0xffff) as u16);
put16(&mut b, cd_start_disk.min(0xffff) as u16);
put16(&mut b, out.cd_entries_this_disk.min(0xffff) as u16);
put16(&mut b, out.total_cd_entries.min(0xffff) as u16);
put32(&mut b, s.min(ZIP_UWORD32_MAX) as u32);
if out.force_zip64 == 1 || cd_start_offset > ZIP_UWORD32_MAX {
put32(&mut b, 0xffff_ffff);
} else {
put32(&mut b, cd_start_offset as u32);
}
put16(&mut b, m as u16);
b.extend_from_slice(comment);
out.bfwrite(&b, Bf::Header)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct Totals {
pub(crate) n: u64,
pub(crate) t: u64,
pub(crate) k: u64,
}
pub(crate) fn write_central(
entries: &mut [ZEntry],
comment: &[u8],
out: &mut ZOut,
tempzn: &mut u64,
before_end: &mut dyn FnMut(&mut ZOut, Totals),
) -> Result<Totals, PutError> {
write_central_as(entries, comment, out, tempzn, before_end, false)
}
pub(crate) fn write_central_as(
entries: &mut [ZEntry],
comment: &[u8],
out: &mut ZOut,
tempzn: &mut u64,
before_end: &mut dyn FnMut(&mut ZOut, Totals),
measured: bool,
) -> Result<Totals, PutError> {
let c = *tempzn;
let start = out.w.offset();
let mut tot = Totals { n: 0, t: 0, k: 0 };
for e in entries.iter_mut() {
putcentral(e, out)?;
*tempzn += 4
+ CENHEAD
+ e.nam.unwrap_or(e.iname.len()) as u64
+ e.cextra.len() as u64
+ e.comment.len() as u64;
tot.n = tot.n.wrapping_add(e.len);
tot.t = tot.t.wrapping_add(e.siz);
tot.k += 1;
}
before_end(out, tot);
let size = if measured {
out.w.offset().wrapping_sub(start)
} else {
*tempzn - c
};
putend(out, tot.k, size, c, comment).map_err(PutError::Write)?;
Ok(tot)
}
#[cfg(test)]
mod tests;