use std::ffi::OsString;
use std::io::{self, Read, Write};
use stuffr::infozip::crypt::{Keys, RAND_HEAD_LEN};
use stuffr::infozip::{self, ByteSource, Stop, WSIZE};
use super::messages::{self as m, printf};
use super::{Uz, cli, flavour, ttyio};
use crate::compat::host::{self, Host};
const ZIPMAG: u16 = 0x4b50;
const LOCREM: u16 = 0x0403;
const LOCSIG: u32 = 0x0403_4b50;
const LOCFLG: usize = 4;
const CRPFLG: u8 = 1;
const EXTFLG: u8 = 8;
const LOCHOW: usize = 6;
const LOCTIM: usize = 8;
const LOCCRC: usize = 12;
const LOCSIZ: usize = 16;
const LOCLEN: usize = 20;
const LOCFIL: usize = 24;
const LOCEXT: usize = 26;
const LOCHDR: usize = 28;
const EXTHDR: usize = 16;
const GZPMAG: u16 = 0x8b1f;
const GZPHOW: usize = 0;
const GZPFLG: usize = 1;
const GZPMUL: u8 = 2;
const GZPISX: u8 = 4;
const GZPISF: u8 = 8;
const GZPISC: u8 = 16;
const GZPISE: u8 = 32;
const GZPHDR: usize = 8;
const STORED: u16 = 0;
const DEFLATED: u16 = 8;
const ENHDEFLATED: u16 = 9;
const EOF: i32 = -1;
const BUFSIZ: usize = 4096;
fn sh(p: &[u8]) -> u16 {
u16::from(p[0]) | (u16::from(p[1]) << 8)
}
fn lg(p: &[u8]) -> u32 {
u32::from(sh(p)) | (u32::from(sh(&p[2..])) << 16)
}
fn getc2(inp: &mut In) -> u16 {
let lo = inp.getc();
let hi = inp.getc();
(lo as u16) | ((hi << 8) as u16)
}
struct In {
src: Box<dyn Read>,
buf: Box<[u8]>,
pos: usize,
len: usize,
eof: bool,
}
impl In {
fn new(src: Box<dyn Read>) -> In {
In {
src,
buf: vec![0; BUFSIZ].into_boxed_slice(),
pos: 0,
len: 0,
eof: false,
}
}
fn fill(&mut self) -> bool {
while !self.eof {
match self.src.read(&mut self.buf) {
Ok(0) => self.eof = true,
Ok(n) => {
self.pos = 0;
self.len = n;
return true;
}
Err(e) if e.kind() == io::ErrorKind::Interrupted => {}
Err(_) => self.eof = true,
}
}
false
}
fn getc(&mut self) -> i32 {
if self.pos == self.len && !self.fill() {
return EOF;
}
let c = self.buf[self.pos];
self.pos += 1;
i32::from(c)
}
fn fread(&mut self, out: &mut [u8]) -> usize {
let mut n = 0;
while n < out.len() {
if self.pos == self.len && !self.fill() {
break;
}
let k = (self.len - self.pos).min(out.len() - n);
out[n..n + k].copy_from_slice(&self.buf[self.pos..self.pos + k]);
self.pos += k;
n += k;
}
n
}
}
struct Src {
inp: In,
keys: Option<Keys>,
}
impl Src {
fn nextbyte(&mut self) -> i32 {
let c = self.inp.getc();
match self.keys.as_mut() {
None => c,
Some(k) => {
let low = k.decrypt_byte((c & 0xff) as u8);
if c == EOF {
EOF ^ i32::from(low ^ 0xff)
} else {
i32::from(low)
}
}
}
}
}
impl ByteSource for Src {
fn next_byte(&mut self) -> Option<u8> {
self.next_bits().map(|c| c as u8)
}
fn next_bits(&mut self) -> Option<u64> {
match self.nextbyte() {
EOF => None,
c => Some(i64::from(c) as u64),
}
}
fn remaining(&self) -> u64 {
0
}
}
struct Out<'w> {
out: &'w mut dyn Write,
crc: flate2::Crc,
outsiz: u64,
}
struct OutOfSpace;
impl Out<'_> {
fn flush(&mut self, w: &[u8]) -> Result<(), OutOfSpace> {
self.crc.update(w);
if !w.is_empty() {
match self.out.write_all(w).and_then(|()| self.out.flush()) {
Err(e) if e.kind() != io::ErrorKind::BrokenPipe => return Err(OutOfSpace),
_ => {}
}
}
self.outsiz += w.len() as u64;
Ok(())
}
}
fn err(uz: &mut Uz, n: i32, msg: &str) -> i32 {
printf(uz.err, m::FUNZIP_ERROR, &[msg.as_bytes()]);
let _ = uz.err.flush();
n
}
#[cfg(unix)]
fn fdopen_ok(fd: libc::c_int, write: bool) -> bool {
let fl = unsafe { libc::fcntl(fd, libc::F_GETFL) };
if fl == -1 {
return false;
}
match fl & libc::O_ACCMODE {
libc::O_RDWR => true,
libc::O_WRONLY => write,
_ => !write,
}
}
#[cfg(not(unix))]
fn fdopen_ok(_fd: i32, _write: bool) -> bool {
true
}
pub(crate) fn funzip(uz: &mut Uz, args: &[OsString]) -> i32 {
let a = cli::parse_funzip(args);
if a.input.is_none() && uz.host.stdin_is_tty() {
printf(
uz.err,
flavour::funzip_banner(uz.flavour),
&[env!("CARGO_PKG_VERSION").as_bytes()],
);
let _ = uz.err.write_all(m::FUNZIP_USAGE.as_bytes());
return 3;
}
let real = matches!(uz.host, Host::Real);
let src: Box<dyn Read> = if let Some(name) = &a.input {
let path = uz.host.path(name.as_encoded_bytes());
match std::fs::File::open(&path) {
Ok(f) => Box::new(uz.host.track(&path, f)),
Err(_) => return err(uz, 2, m::FZ_NO_INPUT_FILE),
}
} else if real {
if !fdopen_ok(0, false) {
return err(uz, 2, m::FZ_NO_STDIN);
}
match host::stdin_file() {
Ok(f) => Box::new(f),
Err(_) => Box::new(io::stdin()),
}
} else {
Box::new(uz.host.stdin())
};
if real && !fdopen_ok(1, true) {
return err(uz, 2, m::FZ_NO_STDOUT);
}
let mut src = Src {
inp: In::new(src),
keys: None,
};
let code = filter(uz, &mut src, a.password.as_deref());
let _ = uz.out.flush();
code
}
fn filter(uz: &mut Uz, src: &mut Src, password: Option<&[u8]>) -> i32 {
let mut h = [0u8; LOCHDR];
let g: bool; let method: u16;
let encrypted: bool;
let n = getc2(&mut src.inp);
if n == ZIPMAG {
if src.inp.fread(&mut h) != LOCHDR || sh(&h) != LOCREM {
return err(uz, 3, m::FZ_INVALID_ZIPFILE);
}
method = sh(&h[LOCHOW..]);
if !matches!(method, STORED | DEFLATED | ENHDEFLATED) {
return err(uz, 3, m::FZ_NOT_DEFLATED_OR_STORED);
}
for _ in 0..sh(&h[LOCFIL..]) {
src.inp.getc();
}
for _ in 0..sh(&h[LOCEXT..]) {
src.inp.getc();
}
g = false;
encrypted = h[LOCFLG] & CRPFLG != 0;
} else if n == GZPMAG {
if src.inp.fread(&mut h[..GZPHDR]) != GZPHDR {
return err(uz, 3, m::FZ_INVALID_GZIP);
}
method = u16::from(h[GZPHOW]);
if method != DEFLATED && method != ENHDEFLATED {
return err(uz, 3, m::FZ_GZIP_NOT_DEFLATED);
}
if h[GZPFLG] & GZPMUL != 0 {
return err(uz, 3, m::FZ_MULTI_PART);
}
if h[GZPFLG] & GZPISX != 0 {
for _ in 0..getc2(&mut src.inp) {
src.inp.getc();
}
}
for flag in [GZPISF, GZPISC] {
if h[GZPFLG] & flag != 0 {
loop {
let c = src.inp.getc();
if c == 0 || c == EOF {
break;
}
}
}
}
g = true;
encrypted = h[GZPFLG] & GZPISE != 0;
} else {
return err(uz, 3, m::FZ_NOT_ZIP_OR_GZIP);
}
if encrypted {
let typed;
let p: &[u8] = match password {
Some(p) => p,
None => {
let _restore = host::arm_tty_restore(&uz.host, host::FUNZIP_SIGNALS);
match ttyio::getp(uz, m::FZ_ENTER_PASSWORD.as_bytes()) {
Ok(pw) => {
typed = pw;
typed.key()
}
Err(_) => return err(uz, 1, m::FZ_NO_TTY),
}
}
};
src.keys = Some(Keys::new(p));
let mut e: u16 = 0;
for _ in 0..RAND_HEAD_LEN {
e = src.nextbyte() as u16;
}
let check = if h[LOCFLG] & EXTFLG != 0 {
h[LOCTIM + 1]
} else {
h[LOCCRC + 3]
};
if e != u16::from(check) {
return err(uz, 3, m::FZ_INCORRECT_PASSWORD);
}
}
let Uz {
slide, out, err: e, ..
} = uz;
let stderr: &mut dyn Write = &mut **e;
let mut o = Out {
out: out.get_mut(),
crc: flate2::Crc::new(),
outsiz: 0,
};
let failed = if g || h[LOCHOW] != 0 {
let mut flush = |w: &[u8]| o.flush(w);
let mut message = |s: &'static str| {
let _ = stderr.write_all(s.as_bytes());
let _ = stderr.flush();
};
match infozip::inflate(
src,
method == ENHDEFLATED,
&mut slide[..WSIZE],
&mut flush,
&mut message,
) {
Ok(()) => None,
Err(Stop::Flush(OutOfSpace)) => Some((9, m::FZ_OUT_OF_SPACE)),
Err(Stop::Code(3)) => Some((1, m::FZ_OUT_OF_MEMORY)),
Err(_) => Some((4, m::FZ_FORMAT_VIOLATED)),
}
} else {
stored(src, &h, encrypted, slide, &mut o, stderr)
};
let (crc, outsiz) = (o.crc.sum(), o.outsiz);
if let Some((n, msg)) = failed {
return err(uz, n, msg);
}
if g {
if src.inp.fread(&mut h[LOCCRC..LOCCRC + 8]) != 8 {
return err(uz, 3, m::FZ_GZIP_ENDED);
}
} else if h[LOCFLG] & EXTFLG != 0
&& src.inp.fread(&mut h[LOCCRC - 4..LOCCRC - 4 + EXTHDR]) != EXTHDR
{
return err(uz, 3, m::FZ_ZIP_ENDED);
}
if lg(&h[LOCCRC..]) != crc {
return err(uz, 4, m::FZ_CRC_ERROR);
}
if u64::from(lg(&h[if g { LOCSIZ } else { LOCLEN }..])) != outsiz {
return err(uz, 4, m::FZ_LENGTH_ERROR);
}
if !g && src.inp.fread(&mut h[..4]) == 4 && lg(&h) == LOCSIG {
let _ = uz.err.write_all(m::FZ_MORE_THAN_ONE.as_bytes());
let _ = uz.err.flush();
}
0
}
fn stored(
src: &mut Src,
h: &[u8; LOCHDR],
encrypted: bool,
slide: &mut [u8],
o: &mut Out,
stderr: &mut dyn Write,
) -> Option<(i32, &'static str)> {
let mut n = u64::from(lg(&h[LOCLEN..]));
let siz = u64::from(lg(&h[LOCSIZ..]));
let want = if encrypted {
siz.wrapping_sub(RAND_HEAD_LEN as u64)
} else {
siz
};
if n != want {
printf(
stderr,
m::FZ_LEN_SIZ,
&[n.to_string().as_bytes(), siz.to_string().as_bytes()],
);
return Some((4, m::FZ_LENGTH_MISMATCH));
}
let mut outcnt = 0;
while n > 0 {
n -= 1;
let c = (src.inp.getc() & 0xff) as u8;
slide[outcnt] = match src.keys.as_mut() {
Some(k) => k.decrypt_byte(c),
None => c,
};
outcnt += 1;
if outcnt == WSIZE {
if o.flush(&slide[..outcnt]).is_err() {
return Some((9, m::FZ_OUT_OF_SPACE));
}
outcnt = 0;
}
}
if outcnt > 0 && o.flush(&slide[..outcnt]).is_err() {
return Some((9, m::FZ_OUT_OF_SPACE));
}
None
}
#[cfg(test)]
mod tests;