//! Structs for reading a ZIP archive use crc32::Crc32Reader; use compression::CompressionMethod; use spec; use result::{ZipResult, ZipError}; use std::io; use std::io::prelude::*; use std::collections::HashMap; use flate2; use flate2::FlateReadExt; use podio::{ReadPodExt, LittleEndian}; use types::{ZipFileData, System}; use cp437::FromCp437; use msdos_time::{TmMsDosExt, MsDosDateTime}; #[cfg(feature = "bzip2")] use bzip2::read::BzDecoder; mod ffi { pub const S_IFDIR: u32 = 0o0040000; pub const S_IFREG: u32 = 0o0100000; } /// Wrapper for reading the contents of a ZIP file. /// /// ``` /// fn doit() -> zip::result::ZipResult<()> /// { /// use std::io::prelude::*; /// /// // For demonstration purposes we read from an empty buffer. /// // Normally a File object would be used. /// let buf: &[u8] = &[0u8; 128]; /// let mut reader = std::io::Cursor::new(buf); /// /// let mut zip = try!(zip::ZipArchive::new(reader)); /// /// for i in 0..zip.len() /// { /// let mut file = zip.by_index(i).unwrap(); /// println!("Filename: {}", file.name()); /// let first_byte = try!(file.bytes().next().unwrap()); /// println!("{}", first_byte); /// } /// Ok(()) /// } /// /// println!("Result: {:?}", doit()); /// ``` pub struct ZipArchive { reader: R, files: Vec, names_map: HashMap, } enum ZipFileReader<'a> { Stored(Crc32Reader>), Deflated(Crc32Reader>>), #[cfg(feature = "bzip2")] Bzip2(Crc32Reader>>), } /// A struct for reading a zip file pub struct ZipFile<'a> { data: &'a ZipFileData, reader: ZipFileReader<'a>, } fn unsupported_zip_error(detail: &'static str) -> ZipResult { Err(ZipError::UnsupportedArchive(detail)) } impl ZipArchive { /// Opens a Zip archive and parses the central directory pub fn new(mut reader: R) -> ZipResult> { let footer = try!(spec::CentralDirectoryEnd::find_and_parse(&mut reader)); if footer.disk_number != footer.disk_with_central_directory { return unsupported_zip_error("Support for multi-disk files is not implemented") } let directory_start = footer.central_directory_offset as u64; let number_of_files = footer.number_of_files_on_this_disk as usize; let mut files = Vec::with_capacity(number_of_files); let mut names_map = HashMap::new(); try!(reader.seek(io::SeekFrom::Start(directory_start))); for _ in 0 .. number_of_files { let file = try!(central_header_to_zip_file(&mut reader)); names_map.insert(file.file_name.clone(), files.len()); files.push(file); } Ok(ZipArchive { reader: reader, files: files, names_map: names_map }) } /// Number of files contained in this zip. /// /// ``` /// fn iter() { /// let mut zip = zip::ZipArchive::new(std::io::Cursor::new(vec![])).unwrap(); /// /// for i in 0..zip.len() { /// let mut file = zip.by_index(i).unwrap(); /// // Do something with file i /// } /// } /// ``` pub fn len(&self) -> usize { self.files.len() } /// Search for a file entry by name pub fn by_name<'a>(&'a mut self, name: &str) -> ZipResult> { let index = match self.names_map.get(name) { Some(index) => *index, None => { return Err(ZipError::FileNotFound); }, }; self.by_index(index) } /// Get a contained file by index pub fn by_index<'a>(&'a mut self, file_number: usize) -> ZipResult> { if file_number >= self.files.len() { return Err(ZipError::FileNotFound); } let ref data = self.files[file_number]; let pos = data.data_start; if data.encrypted { return unsupported_zip_error("Encrypted files are not supported") } try!(self.reader.seek(io::SeekFrom::Start(pos))); let limit_reader = (self.reader.by_ref() as &mut Read).take(data.compressed_size); let reader = match data.compression_method { CompressionMethod::Stored => { ZipFileReader::Stored(Crc32Reader::new( limit_reader, data.crc32)) }, CompressionMethod::Deflated => { let deflate_reader = limit_reader.deflate_decode(); ZipFileReader::Deflated(Crc32Reader::new( deflate_reader, data.crc32)) }, #[cfg(feature = "bzip2")] CompressionMethod::Bzip2 => { let bzip2_reader = BzDecoder::new(limit_reader); ZipFileReader::Bzip2(Crc32Reader::new( bzip2_reader, data.crc32)) }, _ => return unsupported_zip_error("Compression method not supported"), }; Ok(ZipFile { reader: reader, data: data }) } /// Unwrap and return the inner reader object /// /// The position of the reader is undefined. pub fn into_inner(self) -> R { self.reader } } fn central_header_to_zip_file(reader: &mut R) -> ZipResult { // Parse central header let signature = try!(reader.read_u32::()); if signature != spec::CENTRAL_DIRECTORY_HEADER_SIGNATURE { return Err(ZipError::InvalidArchive("Invalid Central Directory header")) } let version_made_by = try!(reader.read_u16::()); let _version_to_extract = try!(reader.read_u16::()); let flags = try!(reader.read_u16::()); let encrypted = flags & 1 == 1; let is_utf8 = flags & (1 << 11) != 0; let compression_method = try!(reader.read_u16::()); let last_mod_time = try!(reader.read_u16::()); let last_mod_date = try!(reader.read_u16::()); let crc32 = try!(reader.read_u32::()); let compressed_size = try!(reader.read_u32::()); let uncompressed_size = try!(reader.read_u32::()); let file_name_length = try!(reader.read_u16::()) as usize; let extra_field_length = try!(reader.read_u16::()) as usize; let file_comment_length = try!(reader.read_u16::()) as usize; let _disk_number = try!(reader.read_u16::()); let _internal_file_attributes = try!(reader.read_u16::()); let external_file_attributes = try!(reader.read_u32::()); let offset = try!(reader.read_u32::()) as u64; let file_name_raw = try!(ReadPodExt::read_exact(reader, file_name_length)); let extra_field = try!(ReadPodExt::read_exact(reader, extra_field_length)); let file_comment_raw = try!(ReadPodExt::read_exact(reader, file_comment_length)); let file_name = match is_utf8 { true => String::from_utf8_lossy(&*file_name_raw).into_owned(), false => file_name_raw.from_cp437(), }; let file_comment = match is_utf8 { true => String::from_utf8_lossy(&*file_comment_raw).into_owned(), false => file_comment_raw.from_cp437(), }; // Remember end of central header let return_position = try!(reader.seek(io::SeekFrom::Current(0))); // Parse local header try!(reader.seek(io::SeekFrom::Start(offset))); let signature = try!(reader.read_u32::()); if signature != spec::LOCAL_FILE_HEADER_SIGNATURE { return Err(ZipError::InvalidArchive("Invalid local file header")) } try!(reader.seek(io::SeekFrom::Current(22))); let file_name_length = try!(reader.read_u16::()) as u64; let extra_field_length = try!(reader.read_u16::()) as u64; let magic_and_header = 4 + 22 + 2 + 2; let data_start = offset + magic_and_header + file_name_length + extra_field_length; // Construct the result let mut result = ZipFileData { system: System::from_u8((version_made_by >> 8) as u8), version_made_by: version_made_by as u8, encrypted: encrypted, compression_method: CompressionMethod::from_u16(compression_method), last_modified_time: try!(::time::Tm::from_msdos(MsDosDateTime::new(last_mod_time, last_mod_date))), crc32: crc32, compressed_size: compressed_size as u64, uncompressed_size: uncompressed_size as u64, file_name: file_name, file_comment: file_comment, header_start: offset, data_start: data_start, external_attributes: external_file_attributes, }; try!(parse_extra_field(&mut result, &*extra_field)); // Go back after the central header try!(reader.seek(io::SeekFrom::Start(return_position))); Ok(result) } fn parse_extra_field(_file: &mut ZipFileData, data: &[u8]) -> ZipResult<()> { let mut reader = io::Cursor::new(data); while (reader.position() as usize) < data.len() { let kind = try!(reader.read_u16::()); let len = try!(reader.read_u16::()); match kind { _ => try!(reader.seek(io::SeekFrom::Current(len as i64))), }; } Ok(()) } /// Methods for retreiving information on zip files impl<'a> ZipFile<'a> { fn get_reader(&mut self) -> &mut Read { match self.reader { ZipFileReader::Stored(ref mut r) => r as &mut Read, ZipFileReader::Deflated(ref mut r) => r as &mut Read, #[cfg(feature = "bzip2")] ZipFileReader::Bzip2(ref mut r) => r as &mut Read, } } /// Get the version of the file pub fn version_made_by(&self) -> (u8, u8) { (self.data.version_made_by / 10, self.data.version_made_by % 10) } /// Get the name of the file pub fn name(&self) -> &str { &*self.data.file_name } /// Get the comment of the file pub fn comment(&self) -> &str { &*self.data.file_comment } /// Get the compression method used to store the file pub fn compression(&self) -> CompressionMethod { self.data.compression_method } /// Get the size of the file in the archive pub fn compressed_size(&self) -> u64 { self.data.compressed_size } /// Get the size of the file when uncompressed pub fn size(&self) -> u64 { self.data.uncompressed_size } /// Get the time the file was last modified pub fn last_modified(&self) -> ::time::Tm { self.data.last_modified_time } /// Get unix mode for the file pub fn unix_mode(&self) -> Option { match self.data.system { System::Unix => { Some(self.data.external_attributes >> 16) }, System::Dos => { // Interpret MSDOS directory bit let mut mode = if 0x10 == (self.data.external_attributes & 0x10) { ffi::S_IFDIR | 0o0775 } else { ffi::S_IFREG | 0o0664 }; if 0x01 == (self.data.external_attributes & 0x01) { // Read-only bit; strip write permissions mode &= 0o0555; } Some(mode) }, _ => None, } } /// Get the CRC32 hash of the original file pub fn crc32(&self) -> u32 { self.data.crc32 } } impl<'a> Read for ZipFile<'a> { fn read(&mut self, buf: &mut [u8]) -> io::Result { self.get_reader().read(buf) } }