diff --git a/src/bin/extract.rs b/src/bin/extract.rs index 4487aa7e..5743690c 100644 --- a/src/bin/extract.rs +++ b/src/bin/extract.rs @@ -6,21 +6,21 @@ fn main() let fname = Path::new(args[1].as_slice()); let file = std::io::File::open(&fname); - let mut zipcontainer = zip::reader::ZipContainer::new(file).unwrap(); + let zipcontainer = zip::reader::ZipContainer::new(file).unwrap(); - for i in zipcontainer.files() + for file in zipcontainer.files() { - println!("{}", String::from_utf8_lossy(i.name.as_slice())); + println!("{}", file.file_name_string()); - if i.size == 0 { continue } + if file.uncompressed_size == 0 { continue } - let outpath = Path::new(i.name.as_slice()); + let outpath = Path::new(file.file_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); - let mut reader = zipcontainer.read_file(&i); + let mut reader = zipcontainer.read_file(file); copy(&mut reader, &mut outfile).unwrap(); } } diff --git a/src/lib.rs b/src/lib.rs index 958a656c..a5b1872f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,3 +8,4 @@ mod util; mod spec; pub mod crc32; pub mod reader; +pub mod types; diff --git a/src/reader.rs b/src/reader.rs index 4e1e8a91..2ec1c35c 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -1,43 +1,25 @@ use spec; use crc32::Crc32Reader; +use types::ZipFile; +use types; use std::io; use std::io::{IoResult, IoError}; +use std::cell::RefCell; use flate2::FlateReader; pub struct ZipContainer { - inner: T, + inner: RefCell, files: Vec, } -#[deriving(Clone)] -struct ZipFile -{ - central_header: spec::CentralDirectoryHeader, - local_header: spec::LocalFileHeader, - _data_descriptor: Option, -} - -pub struct ZipFileItems -{ - list: Vec, - pos: uint, -} - -pub struct ZipFileItem -{ - pub name: Vec, - pub size: uint, - index: uint, -} - -fn unsupported_zip_error(detail: String) -> IoResult +fn unsupported_zip_error(detail: &str) -> IoResult { Err(IoError { kind: io::OtherIoError, desc: "This ZIP file is not supported", - detail: Some(detail), + detail: Some(detail.to_string()), }) } @@ -45,101 +27,110 @@ impl ZipContainer { pub fn new(inner: T) -> IoResult> { - let mut result = ZipContainer { inner: inner, files: Vec::new() }; - let footer = try!(spec::CentralDirectoryEnd::find_and_parse(&mut result.inner)); + let mut result = ZipContainer { inner: RefCell::new(inner), files: Vec::new() }; - 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) { - files.push(try!(ZipContainer::parse_directory(&mut result.inner))); + let reader = &mut *result.inner.borrow_mut(); + let footer = try!(spec::CentralDirectoryEnd::find_and_parse(reader)); + + if footer.number_of_disks > 1 { return unsupported_zip_error("Support for multi-disk files is not implemented") } + + 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!(reader.seek(directory_start, io::SeekSet)); + for i in range(0, number_of_files) + { + files.push(try!(ZipContainer::parse_directory(reader))); + } + + result.files = files; } - result.files = files; Ok(result) } fn parse_directory(reader: &mut T) -> IoResult { let cdh = try!(spec::CentralDirectoryHeader::parse(reader)); + // Remember position let pos = try!(reader.tell()) as i64; - let result = ZipFile::new(reader, cdh); + let result = ZipContainer::parse_file(reader, cdh); + // Jump back for next directory try!(reader.seek(pos, io::SeekSet)); result } - pub fn files(&self) -> ZipFileItems + fn parse_file(reader: &mut T, central: spec::CentralDirectoryHeader) -> IoResult { - ZipFileItems { list: self.files.clone(), pos: 0 } + try!(reader.seek(central.file_offset as i64, io::SeekSet)); + let local = try!(spec::LocalFileHeader::parse(reader)); + + Ok(ZipFile + { + encrypted: central.encrypted, + compression_method: central.compression_method, + last_modified_time: central.last_modified_time, + crc32: central.crc32, + compressed_size: central.compressed_size as u64, + uncompressed_size: central.uncompressed_size as u64, + file_name: central.file_name.clone(), + file_comment: central.file_comment.clone(), + data_start: local.header_end, + }) } - pub fn read_file(&mut self, item: &ZipFileItem) -> IoResult> + pub fn files(&self) -> ::std::slice::Items { - let file = self.files.get_mut(item.index); - let reader = &mut self.inner; - let pos = file.local_header.header_end as i64; + self.files.as_slice().iter() + } - 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 + pub fn read_file(&self, file: &ZipFile) -> IoResult> + { + let mut inner_reader = match self.inner.try_borrow_mut() { - 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()), + Some(reader) => reader, + None => return Err(IoError + { + kind: io::ResourceUnavailable, + desc: "There is already a ZIP reader active", + detail: None + }), + }; + let pos = file.data_start as i64; + + if file.encrypted + { + return unsupported_zip_error("Encrypted files are not supported") + } + + try!(inner_reader.seek(pos, io::SeekSet)); + let refmut_reader = ::util::RefMutReader::new(inner_reader); + let limit_reader = io::util::LimitReader::new(refmut_reader, file.compressed_size as uint); + + let reader = match file.compression_method + { + types::Stored => + { + box + Crc32Reader::new_with_check( + limit_reader, + file.crc32) + as Box + }, + types::Deflated => + { + let deflate_reader = limit_reader.deflate_decode(); + box + Crc32Reader::new_with_check( + deflate_reader, + file.crc32) + as Box + }, + _ => return unsupported_zip_error("Compression method not supported"), }; Ok(reader) } } - -impl ZipFile -{ - 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 ZipFileItem -{ - fn new(list: &Vec, index: uint) -> IoResult - { - let file = &list[index]; - - let name = file.central_header.file_name.clone(); - - Ok(ZipFileItem { name: name, size: file.central_header.uncompressed_size as uint, index: index }) - } -} - -impl Iterator for ZipFileItems -{ - fn next(&mut self) -> Option - { - self.pos += 1; - if self.pos - 1 >= self.list.len() - { - None - } - else - { - ZipFileItem::new(&self.list, self.pos - 1).ok() - } - } -} diff --git a/src/spec.rs b/src/spec.rs index 9d89f261..2bf0e9fe 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -2,6 +2,7 @@ use std::io; use std::io::{IoResult, IoError}; use std::iter::range_step_inclusive; use time::Tm; +use types; use util; static LOCAL_FILE_HEADER_SIGNATURE : u32 = 0x04034b50; @@ -9,28 +10,6 @@ static DATA_DESCRIPTOR_SIGNATURE : u32 = 0x08074b50; static CENTRAL_DIRECTORY_HEADER_SIGNATURE : u32 = 0x02014b50; static CENTRAL_DIRECTORY_END_SIGNATURE : u32 = 0x06054b50; -#[deriving(FromPrimitive, Clone)] -pub enum CompressionMethod -{ - Stored = 0, - Shrunk = 1, - Reduced1 = 2, - Reduced2 = 3, - Reduced3 = 4, - Reduced4 = 5, - Imploded = 6, - Deflated = 8, - Deflate64 = 9, - PkwareImploding = 10, - Bzip2 = 12, - LZMA = 14, - IBMTerse = 18, - LZ77 = 19, - WavPack = 97, - PPMdI1 = 98, - Unknown = 100000, -} - #[deriving(Clone)] pub struct LocalFileHeader { @@ -49,7 +28,7 @@ pub struct LocalFileHeader pub is_masked: bool, // bit 13 // bit 14 & 15 unused - pub compression_method: CompressionMethod, + pub compression_method: types::CompressionMethod, pub last_modified: Tm, pub crc32: u32, pub compressed_size: u32, @@ -95,7 +74,7 @@ impl LocalFileHeader strong_encryption: (flags & (1 << 6)) != 0, is_utf8: (flags & (1 << 11)) != 0, is_masked: (flags & (1 << 13)) != 0, - compression_method: FromPrimitive::from_u16(compression_method).unwrap_or(Unknown), + compression_method: FromPrimitive::from_u16(compression_method).unwrap_or(types::Unknown), last_modified: util::msdos_datetime_to_tm(last_mod_time, last_mod_date), crc32: crc, compressed_size: compressed_size, @@ -160,7 +139,7 @@ pub struct CentralDirectoryHeader pub is_masked: bool, // bit 13 // bit 14 & 15 unused - pub compression_method: CompressionMethod, + pub compression_method: types::CompressionMethod, pub last_modified_time: Tm, pub crc32: u32, pub compressed_size: u32, @@ -215,7 +194,7 @@ impl CentralDirectoryHeader strong_encryption: flags & (1 << 6) != 0, is_utf8: flags & (1 << 11) != 0, is_masked: flags & (1 << 13) != 0, - compression_method: FromPrimitive::from_u16(compression).unwrap_or(Unknown), + compression_method: FromPrimitive::from_u16(compression).unwrap_or(types::Unknown), last_modified_time: util::msdos_datetime_to_tm(last_mod_time, last_mod_date), crc32: crc, compressed_size: compressed_size, diff --git a/src/types.rs b/src/types.rs new file mode 100644 index 00000000..71e967a5 --- /dev/null +++ b/src/types.rs @@ -0,0 +1,45 @@ +use time; + +#[deriving(FromPrimitive, Clone)] +pub enum CompressionMethod +{ + Stored = 0, + Shrunk = 1, + Reduced1 = 2, + Reduced2 = 3, + Reduced3 = 4, + Reduced4 = 5, + Imploded = 6, + Deflated = 8, + Deflate64 = 9, + PkwareImploding = 10, + Bzip2 = 12, + LZMA = 14, + IBMTerse = 18, + LZ77 = 19, + WavPack = 97, + PPMdI1 = 98, + Unknown = 100000, +} + + +pub struct ZipFile +{ + pub encrypted: bool, + pub compression_method: CompressionMethod, + pub last_modified_time: time::Tm, + pub crc32: u32, + pub compressed_size: u64, + pub uncompressed_size: u64, + pub file_name: Vec, + pub file_comment: Vec, + pub data_start: u64, +} + +impl ZipFile +{ + pub fn file_name_string(&self) -> String + { + String::from_utf8_lossy(self.file_name.as_slice()).into_string() + } +} diff --git a/src/util.rs b/src/util.rs index 7db4ac4f..b35c559d 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,5 +1,6 @@ use time; use time::Tm; +use std::cell::RefMut; pub fn msdos_datetime_to_tm(time: u16, date: u16) -> Tm { @@ -25,3 +26,24 @@ pub fn msdos_datetime_to_tm(time: u16, date: u16) -> Tm Err(m) => { debug!("Failed parsing date: {}", m); time::empty_tm() }, } } + +pub struct RefMutReader<'a, R:'a> +{ + inner: RefMut<'a, R>, +} + +impl<'a, R: Reader> RefMutReader<'a, R> +{ + pub fn new(inner: RefMut<'a, R>) -> RefMutReader<'a, R> + { + RefMutReader { inner: inner, } + } +} + +impl<'a, R: Reader> Reader for RefMutReader<'a, R> +{ + fn read(&mut self, buf: &mut [u8]) -> ::std::io::IoResult + { + self.inner.read(buf) + } +}