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: RefCell, files: Vec, } fn unsupported_zip_error(detail: &str) -> IoResult { Err(IoError { kind: io::OtherIoError, desc: "This ZIP file is not supported", detail: Some(detail.to_string()), }) } impl ZipContainer { pub fn new(inner: T) -> IoResult> { let mut result = ZipContainer { inner: RefCell::new(inner), files: Vec::new() }; { 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; } 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 = ZipContainer::parse_file(reader, cdh); // Jump back for next directory try!(reader.seek(pos, io::SeekSet)); result } fn parse_file(reader: &mut T, central: spec::CentralDirectoryHeader) -> IoResult { 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 files(&self) -> ::std::slice::Items { self.files.as_slice().iter() } pub fn read_file(&self, file: &ZipFile) -> IoResult> { let mut inner_reader = match self.inner.try_borrow_mut() { 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) } }