use rawzip::{RECOMMENDED_BUFFER_SIZE, ZipArchive};
use std::env;
use std::fs::File;
use std::io::Write;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let args: Vec<String> = env::args().collect();
if args.len() != 2 {
eprintln!("Usage: {} <archive.zip>", args[0]);
eprintln!("List the contents of a ZIP archive");
std::process::exit(1);
}
let archive_path = &args[1];
let file = File::open(archive_path)?;
let mut buffer = vec![0u8; RECOMMENDED_BUFFER_SIZE];
let archive = ZipArchive::from_file(file, &mut buffer)?;
println!("Archive: {archive_path}");
let mut comment_reader = archive.comment();
if comment_reader.remaining() > 0 {
print!("Comment: ");
std::io::copy(&mut comment_reader, &mut std::io::stdout().lock())?;
println!();
}
println!();
println!(" Length Date/Time Perms Name");
println!("--------- -------------------- ---------- -------");
let mut total_uncompressed = 0u64;
let mut total_compressed = 0u64;
let mut file_count = 0u64;
let expected_entries = archive.entries_hint();
let mut entries = archive.entries(&mut buffer);
while let Some(entry) = entries.next_entry()? {
let uncompressed_size = entry.uncompressed_size_hint();
let compressed_size = entry.compressed_size_hint();
total_uncompressed += uncompressed_size;
total_compressed += compressed_size;
file_count += 1;
let mode = entry.mode();
let permissions_str = format_permissions(mode.value());
let size_str = if entry.is_dir() {
format!("{:9}", "")
} else {
format!("{uncompressed_size:9}")
};
print!(
"{} {:20} {:10} ",
size_str,
entry.last_modified(),
permissions_str
);
std::io::stdout().write_all(entry.file_path().as_ref())?;
println!();
}
println!("--------- -------------------- ---------- -------");
println!(
"{total_uncompressed:9} {file_count} files"
);
if file_count != expected_entries {
eprintln!(
"warning: central directory contains {file_count} entries, but the EOCD declares {expected_entries}"
);
}
if total_compressed > 0 && total_uncompressed > 0 {
let compression_ratio = (total_compressed as f64 / total_uncompressed as f64) * 100.0;
println!("Compressed size: {total_compressed} bytes ({compression_ratio:.1}%)");
}
Ok(())
}
fn format_permissions(mode: u32) -> String {
let file_type = match mode & 0o170000 {
0o040000 => 'd', 0o120000 => 'l', 0o100000 => '-', 0o060000 => 'b', 0o020000 => 'c', 0o010000 => 'p', 0o140000 => 's', _ => '?', };
let owner = format!(
"{}{}{}",
if mode & 0o400 != 0 { 'r' } else { '-' },
if mode & 0o200 != 0 { 'w' } else { '-' },
if mode & 0o100 != 0 { 'x' } else { '-' }
);
let group = format!(
"{}{}{}",
if mode & 0o040 != 0 { 'r' } else { '-' },
if mode & 0o020 != 0 { 'w' } else { '-' },
if mode & 0o010 != 0 { 'x' } else { '-' }
);
let other = format!(
"{}{}{}",
if mode & 0o004 != 0 { 'r' } else { '-' },
if mode & 0o002 != 0 { 'w' } else { '-' },
if mode & 0o001 != 0 { 'x' } else { '-' }
);
format!("{file_type}{owner}{group}{other}")
}