summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoel Klinghed <the_jk@spawned.biz>2024-04-26 00:50:35 +0200
committerJoel Klinghed <the_jk@spawned.biz>2024-04-26 00:50:35 +0200
commit8ca6740d01fa6e324d1a9f1cc4c332da78500656 (patch)
treedcad6658724e0c5b8e3d007ff7555627fc4770df
parent8394910a7a57f6ddecf055f37931b476b8d1742d (diff)
zip: New moduleHEADmain
Only support for finding and reading central directory yet.
-rw-r--r--src/main.rs92
-rw-r--r--src/zip.rs203
2 files changed, 284 insertions, 11 deletions
diff --git a/src/main.rs b/src/main.rs
index de2ebb2..c68f26a 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,5 +1,8 @@
mod args;
+mod zip;
+use std::fs::File;
+use std::io;
use std::process::ExitCode;
fn guess_parser(args: &mut dyn Iterator<Item = String>) -> Box<dyn args::Parser> {
@@ -13,7 +16,14 @@ fn guess_parser(args: &mut dyn Iterator<Item = String>) -> Box<dyn args::Parser>
Box::new(args::LongOnlyParser::new())
}
-fn parse_args() -> Result<bool, String> {
+struct Program {
+ classname: String,
+ args: Vec<String>,
+ classpath: String,
+ verbose: bool,
+}
+
+fn parse_args() -> Result<Option<Program>, String> {
let mut options = args::Options::new();
let help_idx = options.push(
args::OptionBuilder::default()
@@ -38,6 +48,22 @@ fn parse_args() -> Result<bool, String> {
.build()
.unwrap(),
);
+ let cp_idx = options.push(
+ args::OptionBuilder::default()
+ .long("cp")
+ .description("class search path of directories and zip/jar files")
+ .value(args::ValueRequirement::Required("CLASSPATH"))
+ .build()
+ .unwrap(),
+ );
+ let classpath_idx = options.push(
+ args::OptionBuilder::default()
+ .long("classpath")
+ .description("class search path of directories and zip/jar files")
+ .value(args::ValueRequirement::Required("CLASSPATH"))
+ .build()
+ .unwrap(),
+ );
let parser = guess_parser(&mut std::env::args());
let maybe_args = parser.run(&mut options, &mut std::env::args());
@@ -46,33 +72,77 @@ fn parse_args() -> Result<bool, String> {
let help = &options[help_idx];
if help.is_set() {
parser.print_help(&options);
- return Ok(true);
+ return Ok(None);
}
let args = maybe_args?;
let version = &options[version_idx];
if version.is_set() {
println!("Version is 0.0.1");
- return Ok(true);
+ return Ok(None);
}
let verbose = &options[verbose_idx];
- println!("verbose: {}", verbose.is_set());
- println!("args: {:?}", args.args);
- Ok(false)
+ let cp = &options[cp_idx];
+ let classpath = &options[classpath_idx];
+ let actual_classpath: &str;
+ if cp.is_set() {
+ if classpath.is_set() {
+ return Err("Both -cp and -classpath set, pick one.".to_string());
+ }
+ actual_classpath = cp.value().as_ref().unwrap();
+ } else if classpath.is_set() {
+ actual_classpath = classpath.value().as_ref().unwrap();
+ } else {
+ actual_classpath = "";
+ }
+
+ let mut run_args = args.args.iter();
+ if let Some(classname) = run_args.next() {
+ Ok(Some(Program {
+ classname: classname.clone(),
+ args: run_args.map(|s| s.clone()).collect(),
+ classpath: actual_classpath.to_string(),
+ verbose: verbose.is_set(),
+ }))
+ } else {
+ Err("Missing class name".to_string())
+ }
+}
+
+fn run(program: Program) -> io::Result<ExitCode> {
+ let mut paths = program.classpath.split(':');
+ let mut file = File::open(paths.next().unwrap())?;
+ let layout = zip::Layout::new(&mut file)?;
+ for (name, idx) in layout.names() {
+ let entry = &layout.entries()[*idx];
+ println!(
+ "{name}: {} {}",
+ entry.compressed_size(),
+ entry.uncompressed_size()
+ );
+ }
+ return Ok(ExitCode::SUCCESS);
}
fn main() -> ExitCode {
match parse_args() {
- Ok(exit) => {
- if exit {
+ Ok(maybe_program) => match maybe_program {
+ Some(program) => match run(program) {
+ Ok(exit_code) => {
+ return exit_code;
+ }
+ Err(err) => {
+ eprintln!("{}", err);
+ return ExitCode::FAILURE;
+ }
+ },
+ None => {
return ExitCode::SUCCESS;
}
- }
+ },
Err(msg) => {
eprintln!("{}", msg);
return ExitCode::FAILURE;
}
}
-
- ExitCode::SUCCESS
}
diff --git a/src/zip.rs b/src/zip.rs
new file mode 100644
index 0000000..f72359a
--- /dev/null
+++ b/src/zip.rs
@@ -0,0 +1,203 @@
+#![allow(dead_code)]
+
+use std::cmp::min;
+use std::collections::HashMap;
+use std::fs::File;
+use std::io::{Error, ErrorKind, Read, Result, Seek, SeekFrom};
+
+pub enum Compression {
+ None,
+ Deflate,
+}
+
+pub struct Entry {
+ name: String,
+ offset: u64,
+ compressed_size: u64,
+ uncompressed_size: u64,
+ compression: Compression,
+}
+
+impl Entry {
+ pub fn name(&self) -> &str {
+ return self.name.as_str();
+ }
+
+ pub fn offset(&self) -> u64 {
+ return self.offset;
+ }
+
+ pub fn compressed_size(&self) -> u64 {
+ return self.compressed_size;
+ }
+
+ pub fn uncompressed_size(&self) -> u64 {
+ return self.uncompressed_size;
+ }
+
+ pub fn compression(&self) -> &Compression {
+ return &self.compression;
+ }
+}
+
+pub struct Layout {
+ entries: Vec<Entry>,
+ names: HashMap<String, usize>,
+}
+
+fn get_u16(buf: &[u8], offset: usize) -> u16 {
+ return u16::from_le_bytes(buf[offset..offset + 2].try_into().unwrap());
+}
+
+fn get_u32(buf: &[u8], offset: usize) -> u32 {
+ return u32::from_le_bytes(buf[offset..offset + 4].try_into().unwrap());
+}
+
+fn read_u16(buf: &[u8], offset: &mut usize) -> u16 {
+ let end = *offset + 2;
+ let value = u16::from_le_bytes(buf[*offset..end].try_into().unwrap());
+ *offset = end;
+ return value;
+}
+
+fn read_u32(buf: &[u8], offset: &mut usize) -> u32 {
+ let end = *offset + 4;
+ let value = u32::from_le_bytes(buf[*offset..end].try_into().unwrap());
+ *offset = end;
+ return value;
+}
+
+const CFH_SIZE: usize = 24 + 22;
+const CFH_SIGNATURE: [u8; 4] = [0x50, 0x4B, 0x01, 0x02];
+
+fn parse_central_directory<'a, 'b>(buf: &'a [u8]) -> Result<Layout> {
+ let mut i = 0;
+ let mut layout = Layout {
+ entries: Vec::new(),
+ names: HashMap::new(),
+ };
+
+ while i < buf.len() {
+ if buf.len() - i < CFH_SIZE || buf[i..i + 4] != CFH_SIGNATURE {
+ return Err(Error::new(ErrorKind::Other, "Invalid central directory"));
+ }
+ i += 4;
+ let _version_made_by = read_u16(buf, &mut i);
+ let _version_needed_to_extract = read_u16(buf, &mut i);
+ let _general_purpose_bit_flag = read_u16(buf, &mut i);
+ let compression_method = read_u16(buf, &mut i);
+ let _last_mod_file_time = read_u16(buf, &mut i);
+ let _last_mod_file_date = read_u16(buf, &mut i);
+ let _crc32 = read_u32(buf, &mut i);
+ let compressed_size = read_u32(buf, &mut i);
+ let uncompressed_size = read_u32(buf, &mut i);
+ let filename_length = usize::from(read_u16(buf, &mut i));
+ let extra_field_length = usize::from(read_u16(buf, &mut i));
+ let file_comment_length = usize::from(read_u16(buf, &mut i));
+ let _disk_number_start = read_u16(buf, &mut i);
+ let _internal_file_attributes = read_u16(buf, &mut i);
+ let _external_file_attributes = read_u32(buf, &mut i);
+ let relative_offset_of_local_header = read_u32(buf, &mut i);
+ let need = filename_length + extra_field_length + file_comment_length;
+ if need > buf.len() || buf.len() - need < i {
+ return Err(Error::new(ErrorKind::Other, "Invalid central file entry"));
+ }
+ let maybe_filename = String::from_utf8(buf[i..i + filename_length].to_vec());
+ if maybe_filename.is_err() {
+ return Err(Error::new(
+ ErrorKind::Other,
+ "Bad UTF-8 in central file entry",
+ ));
+ }
+ let filename = maybe_filename.unwrap();
+ i += filename_length;
+ i += extra_field_length;
+ i += file_comment_length;
+
+ let compression = match compression_method {
+ 0 => Compression::None,
+ 8 => Compression::Deflate,
+ _ => {
+ return Err(Error::new(
+ ErrorKind::Other,
+ "Unsupported compression in central file entry",
+ ));
+ }
+ };
+
+ layout.names.insert(filename.clone(), layout.entries.len());
+ layout.entries.push(Entry {
+ name: filename,
+ offset: u64::from(relative_offset_of_local_header),
+ compressed_size: u64::from(compressed_size),
+ uncompressed_size: u64::from(uncompressed_size),
+ compression,
+ });
+ }
+ return Ok(layout);
+}
+
+const EOCD_SIZE: usize = 22;
+const EOCD_SIGNATURE: [u8; 4] = [0x50, 0x4B, 0x05, 0x06];
+
+impl Layout {
+ pub fn new(file: &mut File) -> Result<Layout> {
+ let size = file.seek(SeekFrom::End(0))?;
+ // sizeof(End of central dir record) + max size of comment
+ let max = min(EOCD_SIZE + 65535, usize::try_from(size).unwrap());
+ let buf_start = file.seek(SeekFrom::End(-i64::try_from(max).unwrap()))?;
+ let mut buf = vec![0u8; usize::try_from(max).unwrap()];
+ file.read_exact(buf.as_mut_slice())?;
+
+ for i in (0..=max - EOCD_SIZE).rev() {
+ if buf[i..i + 4] == EOCD_SIGNATURE
+ && get_u16(&buf, i + EOCD_SIZE - 2) == u16::try_from(max - (i + EOCD_SIZE)).unwrap()
+ {
+ let mut i = i + 4;
+ let number_of_this_disk = read_u16(&buf, &mut i);
+ let number_of_the_disk_with_the_start_of_the_central_directory =
+ read_u16(&buf, &mut i);
+ if number_of_this_disk != 0
+ || number_of_the_disk_with_the_start_of_the_central_directory != 0
+ {
+ return Err(Error::new(
+ ErrorKind::Other,
+ "Multi disk ZIPs are not supported.",
+ ));
+ }
+ let _total_number_of_entries_in_the_central_dir_on_this_disk =
+ read_u16(&buf, &mut i);
+ let _total_number_of_entries_in_the_central_directory = read_u16(&buf, &mut i);
+ let size_of_central_directory = read_u32(&buf, &mut i);
+ let offset_of_start_of_central_directory = read_u32(&buf, &mut i);
+ let usize_size = usize::try_from(size_of_central_directory).unwrap();
+ if usize_size <= max {
+ let u64_start = u64::from(offset_of_start_of_central_directory);
+ if u64_start >= buf_start {
+ let offset = usize::try_from(u64_start - buf_start).unwrap();
+ return parse_central_directory(&buf[offset..offset + usize_size]);
+ }
+ } else {
+ buf.resize(usize_size, 0);
+ }
+ file.seek(SeekFrom::Start(u64::from(
+ offset_of_start_of_central_directory,
+ )))?;
+ file.read_exact(&mut buf[0..usize_size])?;
+ return parse_central_directory(&buf[0..usize_size]);
+ }
+ }
+ Err(Error::new(
+ ErrorKind::Other,
+ "Unable to find end of central directory record, is it a ZIP file?",
+ ))
+ }
+
+ pub fn entries(&self) -> &Vec<Entry> {
+ return &self.entries;
+ }
+
+ pub fn names(&self) -> &HashMap<String, usize> {
+ return &self.names;
+ }
+}