use super::cli::{NoteOptions, ZE_OK};
use super::messages::{self as m, printf};
use super::readzip::{self, CFile, InArchive, cstr};
use super::sink::{FileSink, Temp, mkstemp, replace};
use super::split::{self, Prompt};
use super::unix;
use super::write::set_mode;
use super::zipfile::{self, PutError, ZEntry, ZOut};
use super::zipup::{write_failed, zerr};
use super::{Exit, Zr};
use crate::compat::host::{self, Host};
const ZE_BIG: i32 = 6;
const ZE_NOTE: i32 = 7;
const ZE_TEMP: i32 = 10;
const ZE_NAME: i32 = 13;
const ZE_CREAT: i32 = 15;
const MARK: u8 = b'@';
const LINE_SIZE: usize = 2048;
fn putclean(out: &mut Vec<u8>, s: &[u8]) {
let mut e = b'\n';
for &c in s {
if c == MARK || c == b'\\' {
out.push(b'\\');
}
if c >= b' ' || c == b'\t' || c == b'\n' {
e = c;
out.push(c);
}
}
if e != b'\n' {
out.push(b'\n');
}
}
fn zgetline(p: &Prompt, size: usize) -> Option<Vec<u8>> {
let mut line = split::fgets_n(p, size)?;
let len = cstr(&line).len();
if len > 0 {
if len == size - 1 && line[len - 1] != b'\n' {
return None;
}
line.truncate(len);
if line[len - 1] == b'\n' {
line.pop();
}
} else {
line.clear();
}
Some(line)
}
fn catalloc(comment: &mut Vec<u8>, s: &[u8]) {
let mut t = Vec::with_capacity(s.len());
let mut i = 0;
while i < s.len() {
if s[i] == b'\\' && i + 1 < s.len() {
i += 1;
}
t.push(s[i]);
i += 1;
}
if !comment.is_empty() {
comment.extend_from_slice(b"\r\n");
}
comment.extend_from_slice(&t);
}
pub(crate) fn run(z: &mut Zr, o: NoteOptions) -> Exit {
host::install_zip_abort(&z.host, m::NOTE_INTERRUPTED);
z.set_line(false);
let zipfile = o.zipfile.clone();
z.o.zipfile = Some(zipfile.clone());
z.o.in_path = Some(zipfile.clone());
z.o.out_path = Some(zipfile.clone());
let mut ar = match readzip::read_archive(z) {
Ok(ar) => ar,
Err(x) => return x,
};
if ar.zfiles.is_empty() {
return z.ziperr(ZE_NAME, &zipfile);
}
if !o.write {
let mut out = Vec::new();
for zf in &ar.zfiles {
printf(&mut out, m::NOTE_NAME, &[cstr(&zf.zname)]);
putclean(&mut out, &zf.e.comment);
out.extend_from_slice(m::NOTE_END_OF_COMMENT.as_bytes());
}
out.extend_from_slice(m::NOTE_ZIP_COMMENT.as_bytes());
putclean(&mut out, &ar.zcomment);
z.stdout_write(&out);
return Exit { code: ZE_OK };
}
let zp = z.host.path(&zipfile);
if let Err(e) = std::fs::File::options().append(true).create(true).open(&zp) {
return z.ziperr_os(ZE_CREAT, &zipfile, &e);
}
let attr = std::fs::metadata(&zp).map_or(0, |md| unix::meta_of(&md).mode);
let p = split::prompt(z);
let size = LINE_SIZE;
let markz = m::NOTE_MARKZ.as_bytes();
let n = ar.zfiles.len();
let mut zi = 0usize;
let mut at_zip_comment = false;
while let Some(a) = zgetline(&p, size) {
if a.first() == Some(&MARK) && &a[1..] == markz {
at_zip_comment = true;
break;
}
if a.first() != Some(&MARK) || a.get(1) != Some(&b' ') {
return z.helper_err(ZE_NOTE, m::UNEXPECTED_INPUT.as_bytes());
}
while zi < n && a[2..] != *cstr(&ar.zfiles[zi].zname) {
zi += 1;
}
if zi >= n {
return z.helper_err(ZE_NOTE, m::UNKNOWN_ENTRY_NAME.as_bytes());
}
let zf = &mut ar.zfiles[zi];
let mut a = zgetline(&p, size);
if let Some(l) = &a
&& l.first() == Some(&MARK)
&& l.get(1) == Some(&b'=')
{
zf.e.iname = l[2..].to_vec();
a = zgetline(&p, size);
}
let mut comment = Vec::new();
while let Some(l) = &a
&& l.first() != Some(&MARK)
{
catalloc(&mut comment, l);
a = zgetline(&p, size);
}
comment.truncate(comment.len() & 0xffff);
zf.e.comment = comment;
zi += 1;
}
if at_zip_comment {
let mut zcomment = Vec::new();
while let Some(l) = zgetline(&p, size) {
catalloc(&mut zcomment, &l);
}
zcomment.truncate(zcomment.len() & 0xffff);
ar.zcomment = zcomment;
}
let dir: Vec<u8> = match &o.tempath {
Some(t) => {
let mut d = t.clone();
if d.last() != Some(&b'/') {
d.push(b'/');
}
d
}
None => match zipfile.iter().rposition(|&b| b == b'/') {
Some(i) => zipfile[..=i].to_vec(),
None => Vec::new(),
},
};
let (tempzip, r) = mkstemp(&z.host, &dir);
let (file, armed) = match r {
Ok(x) => x,
Err(e) => return z.ziperr_os(ZE_TEMP, &tempzip, &e),
};
let mut temp = Temp {
path: Some(z.host.path(&tempzip)),
};
let mut ia = InArchive { f: None };
match CFile::open(z, &zipfile) {
Ok(f) => ia.f = Some(f),
Err(_) => return z.ziperr(ZE_NAME, &zipfile),
}
z.current_in_disk = 0;
let budget = match &z.host {
Host::Sandbox(s) => Some(s.budget.clone()),
Host::Real => None,
};
let mut out = ZOut::new(Box::new(FileSink::new(file, budget)), z.o.force_zip64, None);
out.unicode = z.unicode_out();
if ar.zipbeg != 0
&& let Err(f) = readzip::bfcopy(z, &mut ia, &mut out, ar.zipbeg)
{
let h = match &f {
readzip::CopyFail::Code(ZE_TEMP, _) => tempzip.clone(),
_ => zipfile.clone(),
};
return super::zipcloak::fail_exit(z, f, &h);
}
let mut tempzn = ar.zipbeg;
z.o.fix = 3;
for zf in ar.zfiles.iter_mut() {
if let Err(f) = readzip::zipcopy(z, &mut ia, &mut out, &mut tempzn, zf) {
return super::zipcloak::fail_exit(z, f, m::WAS_COPYING_AN_ENTRY.as_bytes());
}
}
ia.f = None;
let mut central: Vec<ZEntry> = ar.zfiles.iter().map(|f| f.e.clone()).collect();
let mut at = out.w.offset();
let r = zipfile::write_central_as(
&mut central,
&ar.zcomment,
&mut out,
&mut at,
&mut |_, _| {},
true,
);
match r {
Ok(_) => {}
Err(PutError::Big { .. }) => return z.ziperr(ZE_BIG, &tempzip),
Err(PutError::Write(err)) => return write_failed(z, &err),
}
if let Err(err) = out.w.flush() {
return z.ziperr_os(ZE_TEMP, &tempzip, &err);
}
drop(out);
let tmp = temp.path.take().unwrap_or_default();
if let Err((code, err)) = replace(&z.host.path(&zipfile), &tmp) {
z.zipwarn(m::NEW_ZIP_LEFT_AS.as_bytes(), &tempzip);
return zerr(z, code, m::WAS_REPLACING.as_bytes(), Some(&err));
}
drop(armed);
if attr != 0 {
set_mode(&zp, attr);
}
Exit { code: ZE_OK }
}
#[cfg(test)]
mod tests;