use bytes::Buf;
use clap::Parser;
use std::{
fs::File,
io::{Read, Write},
path::PathBuf,
};
use zip_core::{raw::parse::Parse, signature::Signature, structs::CompressionMethod};
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct Args {
#[arg(short, long)]
input: PathBuf,
#[arg(short, long)]
output: Option<PathBuf>,
#[arg(long)]
selected_file: Option<String>,
#[arg(long, default_value_t = true)]
raw: bool,
}
pub fn main() {
let args = Args::parse();
println!("Loading whole file to memory");
let mut file = File::open(&args.input).expect("Problem opening file");
let mut buf = Vec::new();
file.read_to_end(&mut buf).expect("Problem reading file to end");
println!("File read successful");
println!("Find EndOfCentralDirectory");
const EXPECTED_POSITION_OFFSET: usize = 10_000;
const END_SIGNATURE: [u8; 4] =
zip_core::raw::EndOfCentralDirectoryFixed::END_OF_CENTRAL_DIR_SIGNATURE.to_le_bytes();
let lower_start = buf.len().saturating_sub(EXPECTED_POSITION_OFFSET);
let end_of_central_directory_pos = zip_core::raw::parse::find_next_signature(&buf[lower_start..], END_SIGNATURE)
.map(|v| v + lower_start)
.or_else(|| zip_core::raw::parse::find_next_signature(&buf, END_SIGNATURE))
.expect("No End Of Central Directory Found, is this a zip file?");
println!("EndOfCentralDirectory found at byte: {end_of_central_directory_pos}");
let mut eoc_buf = &buf[end_of_central_directory_pos..];
let end_of_central_directory = zip_core::raw::EndOfCentralDirectory::from_buf(&mut eoc_buf)
.expect("It seems like EndOfCentralDirectory is invalid");
if !end_of_central_directory.is_valid_signature() {
println!("WARN, EndOfCentralDirectory has wrong signature");
}
println!("EndOfCentralDirectory valid and parsed");
let eoc_f = &end_of_central_directory.fixed;
if eoc_f.total_number_of_entries_in_the_central_directory
!= eoc_f.total_number_of_entries_in_the_central_directory_on_this_disk
|| 0 != eoc_f.number_of_the_disk_with_the_start_of_the_central_directory
|| 0 != eoc_f.number_of_this_disk
{
println!("WARN: it seems like you have a multi-file ZIP file, this is not supported");
}
println!("parsing CentralDirectrories");
let mut cd_buf = &buf[eoc_f.offset_of_start_of_central_directory_with_respect_to_the_starting_disk_number as usize
..eoc_f.offset_of_start_of_central_directory_with_respect_to_the_starting_disk_number as usize
+ eoc_f.size_of_the_central_directory as usize];
println!("CentralDirectrories parsed");
let mut cds = Vec::new();
while let Ok(cd) = zip_core::raw::CentralDirectoryHeader::from_buf(&mut cd_buf) {
let is_file = cd.fixed.general_purpose_bit_flag != 0 && cd.fixed.uncompressed_size != 0;
if !cd.is_valid_signature() {
println!("WARN, CentralDirectory has wrong signature");
}
if is_file {
cds.push(cd);
}
}
let mut selected = None;
if let Some(try_select) = args.selected_file {
selected = cds.iter().position(|cd| {
let filename = std::str::from_utf8(&cd.file_name).expect("non UTF-8 filename found");
filename == try_select
});
if selected.is_none() {
println!("WARN: you selected a file, but that couldn't be found, please select");
}
}
if selected.is_none() {
println!("\nselect file to extract:");
for (i, cd) in cds.iter().enumerate() {
let filename = std::str::from_utf8(&cd.file_name).expect("non UTF-8 filename found");
println!("{i}: {filename}");
}
println!("Please select number to extract:");
let mut user_sel: String = String::new();
let _ = std::io::stdin()
.read_line(&mut user_sel)
.expect("Did not enter a correct string");
selected = Some(
user_sel
.trim()
.parse()
.expect("You didn't add a number :/ Shame on you"),
);
}
let selected = selected.unwrap();
let selected_cd = cds[selected].clone();
let filename = std::str::from_utf8(&selected_cd.file_name).expect("non UTF-8 filename found");
let compressed_size = selected_cd.fixed.compressed_size as usize;
let compression_method = selected_cd.fixed.compression_method;
let compression_method = CompressionMethod::try_from(compression_method).expect("Unknown compression method used");
println!("Trying to extract file #{selected}: {filename}");
let mut lfh_buf = &buf[selected_cd.fixed.relative_offset_of_local_header as usize..];
let lfh = zip_core::raw::LocalFileHeader::from_buf(&mut lfh_buf).expect("Error reading LocalFileHeader");
if !lfh.is_valid_signature() {
println!("WARN: LocalFileHeader has an invalid Signature");
}
println!("LocalFileHeader valid");
let compressed_data = Buf::take(lfh_buf, compressed_size);
let mut show_data = if args.raw {
compressed_data
} else {
match compression_method {
CompressionMethod::Stored => compressed_data,
other => {
panic!("Sorry, we don't support the format {other:?} yet");
},
}
};
println!("File extracted");
if let Some(outfile) = args.output {
let mut file = File::create(outfile).expect("Error creating outfile");
let bytes = show_data.copy_to_bytes(show_data.remaining());
file.write_all(&bytes).unwrap();
} else {
let bytes = show_data.copy_to_bytes(show_data.remaining());
std::io::stdout().write_all(&bytes).expect("Couldn't write to stdout");
}
}