From b6a0de8b3cc77630c8bc261405bf100bc60c5a02 Mon Sep 17 00:00:00 2001 From: Mathijs van de Nes Date: Tue, 9 Sep 2014 15:56:15 +0200 Subject: [PATCH] Simple ZIPs can be extracted! --- src/bin/extract.rs | 39 +++++++++++++++++++++ src/bin/parse_foot.rs | 14 -------- src/crc32.rs | 33 +++++++++++++++++- src/reader.rs | 79 +++++++++++++++++++++++++++++++++++-------- src/spec.rs | 36 ++++++++++---------- src/util.rs | 3 +- 6 files changed, 154 insertions(+), 50 deletions(-) create mode 100644 src/bin/extract.rs delete mode 100644 src/bin/parse_foot.rs diff --git a/src/bin/extract.rs b/src/bin/extract.rs new file mode 100644 index 00000000..7dddcbb2 --- /dev/null +++ b/src/bin/extract.rs @@ -0,0 +1,39 @@ +extern crate zip; + +fn main() +{ + let args = std::os::args(); + let fname = Path::new(args[1].as_slice()); + let file = std::io::File::open(&fname); + + let mut zipcontainer = zip::reader::ZipContainer::new(file).unwrap(); + for mut i in zipcontainer.files() + { + println!("File: {}", i.name); + + if i.size == 0 { continue } + + let outpath = Path::new(i.name.as_slice()); + let dirname = Path::new(outpath.dirname()); + + std::io::fs::mkdir_recursive(&dirname, std::io::UserDir).unwrap(); + + let mut outfile = std::io::File::create(&outpath); + copy(&mut i.reader, &mut outfile).unwrap(); + } +} + +fn copy(reader: &mut R, writer: &mut W) -> std::io::IoResult<()> +{ + let mut buffer = [0u8, ..4096]; + loop + { + match reader.read(&mut buffer) + { + Err(ref e) if e.kind == std::io::EndOfFile => break, + Ok(n) => try!(writer.write(buffer.slice_to(n))), + Err(e) => return Err(e), + } + } + Ok(()) +} diff --git a/src/bin/parse_foot.rs b/src/bin/parse_foot.rs deleted file mode 100644 index 296b451c..00000000 --- a/src/bin/parse_foot.rs +++ /dev/null @@ -1,14 +0,0 @@ -extern crate zip; - -fn main() -{ - let args = std::os::args(); - let fname = Path::new(args[1].as_slice()); - let file = std::io::File::open(&fname); - - let mut zipcontainer = zip::reader::ZipContainer::new(file).unwrap(); - for i in zipcontainer.files() - { - println!("{}", i) - } -} diff --git a/src/crc32.rs b/src/crc32.rs index ccf3fc7f..636dec6c 100644 --- a/src/crc32.rs +++ b/src/crc32.rs @@ -62,6 +62,7 @@ pub struct Crc32Reader { inner: R, crc: u32, + check: Option, } impl Crc32Reader @@ -72,6 +73,17 @@ impl Crc32Reader { inner: inner, crc: 0, + check: None, + } + } + + pub fn new_with_check(inner: R, checksum: u32) -> Crc32Reader + { + Crc32Reader + { + inner: inner, + crc: 0, + check: Some(checksum), } } @@ -79,13 +91,32 @@ impl Crc32Reader { self.crc } + + fn check_matches(&self) -> bool + { + match self.check + { + None => true, + Some(check) => check == self.crc + } + } } impl Reader for Crc32Reader { fn read(&mut self, buf: &mut [u8]) -> io::IoResult { - let count = try!(self.inner.read(buf)); + let count = match self.inner.read(buf) + { + Ok(n) => n, + Err(ref e) if e.kind == io::EndOfFile => + { + return + if self.check_matches() { Err(e.clone()) } + else { Err(io::IoError { kind: io::OtherIoError, desc: "Invalid checksum", detail: None, }) } + }, + Err(e) => return Err(e), + }; self.crc = crc32(self.crc, buf.slice_to(count)); Ok(count) } diff --git a/src/reader.rs b/src/reader.rs index 54523d86..1c7b7d50 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -1,6 +1,8 @@ use spec; +use crc32::Crc32Reader; use std::io; use std::io::{IoResult, IoError}; +use flate2::FlateReader; pub struct ZipContainer { @@ -12,21 +14,29 @@ struct ZipFile { central_header: spec::CentralDirectoryHeader, local_header: spec::LocalFileHeader, + _data_descriptor: Option, } -struct ZipFileNames<'a, T:'a> +pub struct ZipFileItems<'a, T:'a> { container: &'a mut ZipContainer, pos: uint, } -fn unsupported_zip_error(detail: Option) -> IoResult +pub struct ZipFileItem<'a> +{ + pub name: String, + pub size: uint, + pub reader: Box, +} + +fn unsupported_zip_error(detail: String) -> IoResult { Err(IoError { kind: io::OtherIoError, desc: "This ZIP file is not supported", - detail: detail, + detail: Some(detail), }) } @@ -37,13 +47,13 @@ impl ZipContainer let mut result = ZipContainer { inner: inner, files: Vec::new() }; let footer = try!(spec::CentralDirectoryEnd::find_and_parse(&mut result.inner)); - if footer.number_of_disks > 1 { return unsupported_zip_error(Some("Support for multi-disk files is not implemented".to_string())) } + if footer.number_of_disks > 1 { return unsupported_zip_error("Support for multi-disk files is not implemented".to_string()) } let directory_start = footer.central_directory_offset as i64; let number_of_files = footer.number_of_files_on_this_disk as uint; - + let mut files = Vec::with_capacity(number_of_files); - + try!(result.inner.seek(directory_start, io::SeekSet)); for i in range(0, number_of_files) { @@ -58,21 +68,60 @@ impl ZipContainer { let cdh = try!(spec::CentralDirectoryHeader::parse(reader)); let pos = try!(reader.tell()) as i64; - try!(reader.seek(cdh.file_offset as i64, io::SeekSet)); - let lfh = try!(spec::LocalFileHeader::parse(reader)); + let result = ZipFile::new(reader, cdh); try!(reader.seek(pos, io::SeekSet)); - Ok(ZipFile { central_header: cdh, local_header: lfh } ) + result } - pub fn files<'a>(&'a mut self) -> ZipFileNames<'a, T> + pub fn files<'a>(&'a mut self) -> ZipFileItems<'a, T> { - ZipFileNames { container: self, pos: 0 } + ZipFileItems { container: self, pos: 0 } } } -impl<'a, T> Iterator for ZipFileNames<'a, T> +impl ZipFile { - fn next(&mut self) -> Option + pub fn new(reader: &mut T, central_directory_header: spec::CentralDirectoryHeader) -> IoResult + { + try!(reader.seek(central_directory_header.file_offset as i64, io::SeekSet)); + let lfh = try!(spec::LocalFileHeader::parse(reader)); + let desc = if lfh.has_descriptor + { + try!(reader.seek(lfh.compressed_size as i64, io::SeekCur)); + Some(try!(spec::DataDescriptor::parse(reader))) + } + else { None }; + + + Ok(ZipFile { central_header: central_directory_header, local_header: lfh, _data_descriptor: desc }) + } +} + +impl<'a> ZipFileItem<'a> +{ + fn new(reader: &mut S, file: &ZipFile) -> IoResult> + { + let fname = file.central_header.file_name.clone(); + let name = String::from_utf8(fname).unwrap_or("???".to_string()); + let pos = file.local_header.header_end as i64; + + try!(reader.seek(pos, io::SeekSet)); + let lreader = io::util::LimitReader::new(reader.by_ref(), file.central_header.compressed_size as uint); + + let reader = match file.central_header.compression_method + { + spec::Stored => box Crc32Reader::new_with_check(lreader, file.central_header.crc32) as Box, + spec::Deflated => box Crc32Reader::new_with_check(lreader.deflate_decode(), file.central_header.crc32) as Box, + _ => return unsupported_zip_error("Compression method not supported".to_string()), + }; + + Ok(ZipFileItem { name: name, reader: reader, size: file.central_header.uncompressed_size as uint }) + } +} + +impl<'a, T: Reader+Seek> Iterator> for ZipFileItems<'a, T> +{ + fn next(&mut self) -> Option> { self.pos += 1; if self.pos - 1 >= self.container.files.len() @@ -81,8 +130,8 @@ impl<'a, T> Iterator for ZipFileNames<'a, T> } else { - let fname = self.container.files.get(self.pos - 1).central_header.file_name.clone(); - String::from_utf8(fname).ok() + let result = ZipFileItem::new(&mut self.container.inner, &self.container.files[self.pos - 1]); + result.ok() } } } diff --git a/src/spec.rs b/src/spec.rs index 76eaa6eb..3d705761 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -34,29 +34,29 @@ pub enum CompressionMethod #[deriving(Show)] pub struct LocalFileHeader { - extract_version: u16, + pub extract_version: u16, // general purpose flags - encrypted: bool, // bit 0 + pub encrypted: bool, // bit 0 // bit 1 & 2 unused - has_descriptor: bool, // bit 3 + pub has_descriptor: bool, // bit 3 // bit 4 unused - is_compressed_patch: bool, // bit 5 - strong_encryption: bool, // bit 6 + pub is_compressed_patch: bool, // bit 5 + pub strong_encryption: bool, // bit 6 // bit 7 - 10 unused - is_utf8: bool, // bit 11 + pub is_utf8: bool, // bit 11 // bit 12 unused - is_masked: bool, // bit 13 + pub is_masked: bool, // bit 13 // bit 14 & 15 unused - - compression_method: CompressionMethod, - last_modified: Tm, + + pub compression_method: CompressionMethod, + pub last_modified: Tm, pub crc32: u32, - compressed_size: u32, - uncompressed_size: u32, + pub compressed_size: u32, + pub uncompressed_size: u32, pub file_name: Vec, - extra_field: Vec, - header_end: u64, + pub extra_field: Vec, + pub header_end: u64, } @@ -107,11 +107,11 @@ impl LocalFileHeader } } -struct DataDescriptor +pub struct DataDescriptor { - compressed_size: u32, - uncompressed_size: u32, - crc32: u32, + pub compressed_size: u32, + pub uncompressed_size: u32, + pub crc32: u32, } impl DataDescriptor diff --git a/src/util.rs b/src/util.rs index d89e43d6..7db4ac4f 100644 --- a/src/util.rs +++ b/src/util.rs @@ -10,7 +10,7 @@ pub fn msdos_datetime_to_tm(time: u16, date: u16) -> Tm let months = (date & 0b0000000111100000) >> 5; let years = (date & 0b1111111000000000) >> 9; - let datetime = format!("{:04u}-{:02u}-{:02u} {:02u}:{:02u}:{:02u}", + let datetime = format!("{:04u}-{:02u}-{:02u} {:02u}:{:02u}:{:02u}", years as uint + 1980, months, days, @@ -19,7 +19,6 @@ pub fn msdos_datetime_to_tm(time: u16, date: u16) -> Tm seconds); let format = "%Y-%m-%d %H:%M:%S"; - debug!("Parsing MsDos date: {}", datetime); match time::strptime(datetime.as_slice(), format) { Ok(tm) => tm,