use alloc::vec::Vec;
use alloc::vec;
use crate::io;
use super::{get_arg, open_read, open_write_create, create_parent_dirs};
use super::gzip::inflate;
pub fn unzip(argc: i32, argv: *const *const u8) -> i32 {
let mut list_only = false;
let mut archive: Option<&[u8]> = None;
for i in 1..argc {
if let Some(arg) = unsafe { get_arg(argv, i) } {
if arg == b"-l" {
list_only = true;
} else if !arg.starts_with(b"-") && archive.is_none() {
archive = Some(arg);
}
}
}
let archive = match archive {
Some(a) => a,
None => {
io::write_str(2, b"unzip: specify archive\n");
return 1;
}
};
let fd = open_read(archive);
if fd < 0 {
io::write_str(2, b"unzip: cannot open archive\n");
return 1;
}
let mut data = Vec::new();
let mut buf = [0u8; 4096];
loop {
let n = io::read(fd, &mut buf);
if n <= 0 { break; }
data.extend_from_slice(&buf[..n as usize]);
}
io::close(fd);
let mut offset = 0usize;
while offset + 30 <= data.len() {
if &data[offset..offset + 4] != &[0x50, 0x4b, 0x03, 0x04] {
break;
}
let compression = u16::from_le_bytes([data[offset + 8], data[offset + 9]]);
let compressed_size = u32::from_le_bytes([
data[offset + 18], data[offset + 19], data[offset + 20], data[offset + 21]
]) as usize;
let uncompressed_size = u32::from_le_bytes([
data[offset + 22], data[offset + 23], data[offset + 24], data[offset + 25]
]) as usize;
let filename_len = u16::from_le_bytes([data[offset + 26], data[offset + 27]]) as usize;
let extra_len = u16::from_le_bytes([data[offset + 28], data[offset + 29]]) as usize;
let filename_start = offset + 30;
let filename_end = filename_start + filename_len;
let data_start = filename_end + extra_len;
let data_end = data_start + compressed_size;
if data_end > data.len() {
break;
}
let filename = &data[filename_start..filename_end];
if list_only {
io::write_num(1, uncompressed_size as u64);
io::write_str(1, b" ");
io::write_all(1, filename);
io::write_str(1, b"\n");
} else {
io::write_str(1, b" inflating: ");
io::write_all(1, filename);
io::write_str(1, b"\n");
if !filename.ends_with(b"/") {
create_parent_dirs(filename);
let mut path_z = vec![0u8; filename.len() + 1];
path_z[..filename.len()].copy_from_slice(filename);
let out_fd = open_write_create(&path_z, 0o644);
if out_fd >= 0 {
let compressed_data = &data[data_start..data_end];
match compression {
0 => {
io::write_all(out_fd, compressed_data);
}
8 => {
let decompressed = inflate(compressed_data);
io::write_all(out_fd, &decompressed);
}
_ => {
io::write_str(2, b"unzip: unsupported compression\n");
}
}
io::close(out_fd);
}
} else {
let mut path_z = vec![0u8; filename.len() + 1];
path_z[..filename.len()].copy_from_slice(filename);
unsafe { libc::mkdir(path_z.as_ptr() as *const i8, 0o755) };
}
}
offset = data_end;
}
0
}
#[cfg(test)]
mod tests {
extern crate std;
use std::process::Command;
use std::path::PathBuf;
fn get_armybox_path() -> PathBuf {
if let Ok(path) = std::env::var("ARMYBOX_PATH") {
return PathBuf::from(path);
}
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR")
.map(PathBuf::from)
.unwrap_or_else(|_| std::env::current_dir().unwrap());
let release = manifest_dir.join("target/release/armybox");
if release.exists() { return release; }
manifest_dir.join("target/debug/armybox")
}
#[test]
fn test_unzip_no_archive() {
let armybox = get_armybox_path();
if !armybox.exists() { return; }
let output = Command::new(&armybox)
.args(["unzip"])
.output()
.unwrap();
assert_eq!(output.status.code(), Some(1));
let stderr = std::string::String::from_utf8_lossy(&output.stderr);
assert!(stderr.contains("specify archive"));
}
#[test]
fn test_unzip_cannot_open() {
let armybox = get_armybox_path();
if !armybox.exists() { return; }
let output = Command::new(&armybox)
.args(["unzip", "/nonexistent/file.zip"])
.output()
.unwrap();
assert_eq!(output.status.code(), Some(1));
let stderr = std::string::String::from_utf8_lossy(&output.stderr);
assert!(stderr.contains("cannot open"));
}
}