diff --git a/Cargo.toml b/Cargo.toml index 955c9d2c..c9109eeb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "zip" -version = "0.0.12" +version = "0.1.0" authors = ["Mathijs van de Nes "] license = "MIT" repository = "https://github.com/mvdnes/zip-rs.git" @@ -10,6 +10,6 @@ Library to support the reading and writing of zip files. """ [dependencies] -flate2 = "^0.1" -bzip2 = "^0.1" +flate2 = "^0.2" +bzip2 = "^0.2" time = "*" diff --git a/examples/extract.rs b/examples/extract.rs index 3c230fa7..7e32f987 100644 --- a/examples/extract.rs +++ b/examples/extract.rs @@ -1,8 +1,9 @@ -#![feature(old_path, old_io, env)] +#![feature(old_path, io, fs, env)] extern crate zip; -use std::old_io; +use std::io; +use std::fs; fn main() { @@ -13,40 +14,41 @@ fn main() return; } let fname = Path::new(&*args[1]); - let file = old_io::File::open(&fname).unwrap(); + let file = fs::File::open(&fname).unwrap(); - let zipcontainer = zip::ZipReader::new(file).unwrap(); + let mut archive = zip::ZipArchive::new(file).unwrap(); - for file in zipcontainer.files() + for i in 1..archive.len() { - let outpath = sanitize_filename(&*file.file_name); + let mut file = archive.by_index(i).unwrap(); + let outpath = sanitize_filename(file.name()); println!("{}", outpath.display()); - let comment = &file.file_comment; - if comment.len() > 0 { println!(" File comment: {}", comment); } + { + let comment = file.comment(); + if comment.len() > 0 { println!(" File comment: {}", comment); } + } - old_io::fs::mkdir_recursive(&outpath.dir_path(), old_io::USER_DIR).unwrap(); + fs::create_dir_all(&outpath.dir_path()).unwrap(); - if (&*file.file_name).ends_with("/") { + if (&*file.name()).ends_with("/") { create_directory(outpath); } else { - write_file(&zipcontainer, file, outpath); + write_file(&mut file, outpath); } } } -fn write_file(zipcontainer: &zip::ZipReader, file: &zip::ZipFile, outpath: Path) +fn write_file(reader: &mut zip::read::ZipFileReader, outpath: Path) { - let mut outfile = old_io::File::create(&outpath); - let mut reader = zipcontainer.read_file(file).unwrap(); - old_io::util::copy(&mut reader, &mut outfile).unwrap(); - old_io::fs::chmod(&outpath, old_io::USER_FILE).unwrap(); + let mut outfile = fs::File::create(&outpath).unwrap(); + io::copy(reader, &mut outfile).unwrap(); } fn create_directory(outpath: Path) { - old_io::fs::mkdir_recursive(&outpath, old_io::USER_DIR).unwrap(); + fs::create_dir_all(&outpath).unwrap(); } fn sanitize_filename(filename: &str) -> Path diff --git a/examples/extract_lorem.rs b/examples/extract_lorem.rs index 1bab70f0..e9aff77e 100644 --- a/examples/extract_lorem.rs +++ b/examples/extract_lorem.rs @@ -1,4 +1,6 @@ -#![feature(old_path, old_io, env)] +#![feature(old_path, io, fs, env)] + +use std::io::prelude::*; extern crate zip; @@ -11,17 +13,17 @@ fn main() return; } let fname = Path::new(&*args[1]); - let file = std::old_io::File::open(&fname).unwrap(); + let zipfile = std::fs::File::open(&fname).unwrap(); - let zipcontainer = zip::ZipReader::new(file).unwrap(); + let mut archive = zip::ZipArchive::new(zipfile).unwrap(); - let file = match zipcontainer.get("test/lorem_ipsum.txt") + let mut file = match archive.by_name("test/lorem_ipsum.txt") { - Some(file) => file, - None => { println!("File test/lorem_ipsum.txt not found"); return } + Ok(file) => file, + Err(..) => { println!("File test/lorem_ipsum.txt not found"); return } }; - let data = zipcontainer.read_file(file).unwrap().read_to_end().unwrap(); - let contents = String::from_utf8(data).unwrap(); + let mut contents = String::new(); + file.read_to_string(&mut contents).unwrap(); println!("{}", contents); } diff --git a/examples/write_sample.rs b/examples/write_sample.rs index 9d1d43da..923c66e0 100644 --- a/examples/write_sample.rs +++ b/examples/write_sample.rs @@ -1,4 +1,6 @@ -#![feature(old_io, old_path, env)] +#![feature(io, fs, old_path, env)] + +use std::io::prelude::*; extern crate zip; @@ -22,7 +24,7 @@ fn main() fn doit(filename: &str) -> zip::result::ZipResult<()> { let path = Path::new(filename); - let file = std::old_io::File::create(&path).unwrap(); + let file = std::fs::File::create(&path).unwrap(); let mut zip = zip::ZipWriter::new(file); diff --git a/src/compression.rs b/src/compression.rs index 67adeb7a..df7cc644 100644 --- a/src/compression.rs +++ b/src/compression.rs @@ -37,5 +37,5 @@ pub enum CompressionMethod /// PPMd version I, Rev 1 PPMdI1 = 98, /// Unknown (invalid) compression - Unknown = 100000, + Unknown = 10000, } diff --git a/src/crc32.rs b/src/crc32.rs index 26e64037..8f7503c8 100644 --- a/src/crc32.rs +++ b/src/crc32.rs @@ -1,6 +1,7 @@ //! Helper module to compute a CRC32 checksum -use std::old_io; +use std::io; +use std::io::prelude::*; static CRC32_TABLE : [u32; 256] = [ 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, @@ -69,7 +70,7 @@ pub struct Crc32Reader check: u32, } -impl Crc32Reader +impl Crc32Reader { /// Get a new Crc32Reader which check the inner reader against checksum. pub fn new(inner: R, checksum: u32) -> Crc32Reader @@ -88,19 +89,14 @@ impl Crc32Reader } } -impl Reader for Crc32Reader +impl Read for Crc32Reader { - fn read(&mut self, buf: &mut [u8]) -> old_io::IoResult + fn read(&mut self, buf: &mut [u8]) -> io::Result { let count = match self.inner.read(buf) { + Ok(0) if !self.check_matches() => { return Err(io::Error::new(io::ErrorKind::Other, "Invalid checksum", None)) }, Ok(n) => n, - Err(ref e) if e.kind == old_io::EndOfFile => - { - return - if self.check_matches() { Err(e.clone()) } - else { Err(old_io::IoError { kind: old_io::OtherIoError, desc: "Invalid checksum", detail: None, }) } - }, Err(e) => return Err(e), }; self.crc = update(self.crc, &buf[0..count]); diff --git a/src/lib.rs b/src/lib.rs index 15f2f472..3bd8f736 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,25 +3,22 @@ #![feature(unsafe_destructor)] #![warn(missing_docs)] -#![feature(core, collections, old_io, std_misc)] +#![feature(core, old_io, io)] extern crate time; extern crate flate2; extern crate bzip2; -pub use reader::ZipReader; -pub use writer::ZipWriter; +pub use read::ZipArchive; +pub use write::ZipWriter; pub use compression::CompressionMethod; -pub use types::ZipFile; mod util; mod spec; -mod reader_spec; -mod writer_spec; mod crc32; -mod reader; mod types; -pub mod compression; -mod writer; +pub mod read; +mod compression; +pub mod write; mod cp437; pub mod result; diff --git a/src/read.rs b/src/read.rs new file mode 100644 index 00000000..db2abcee --- /dev/null +++ b/src/read.rs @@ -0,0 +1,308 @@ +//! 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 std::num::FromPrimitive; +use flate2; +use flate2::FlateReadExt; +use bzip2::reader::BzDecompressor; +use util; +use util::ReadIntExt; +use types::ZipFileData; + +/// 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 1..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>>), + 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 1..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 as u64; + + 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 as u64); + + 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)) + }, + CompressionMethod::Bzip2 => + { + let bzip2_reader = BzDecompressor::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_le_u32()); + if signature != spec::CENTRAL_DIRECTORY_HEADER_SIGNATURE + { + return Err(ZipError::InvalidArchive("Invalid Central Directory header")) + } + + try!(reader.read_le_u16()); + try!(reader.read_le_u16()); + let flags = try!(reader.read_le_u16()); + let encrypted = flags & 1 == 1; + let is_utf8 = flags & (1 << 11) != 0; + let compression_method = try!(reader.read_le_u16()); + let last_mod_time = try!(reader.read_le_u16()); + let last_mod_date = try!(reader.read_le_u16()); + let crc32 = try!(reader.read_le_u32()); + let compressed_size = try!(reader.read_le_u32()); + let uncompressed_size = try!(reader.read_le_u32()); + let file_name_length = try!(reader.read_le_u16()) as usize; + let extra_field_length = try!(reader.read_le_u16()) as usize; + let file_comment_length = try!(reader.read_le_u16()) as usize; + try!(reader.read_le_u16()); + try!(reader.read_le_u16()); + try!(reader.read_le_u32()); + let offset = try!(reader.read_le_u32()) as u64; + let file_name_raw = try!(reader.read_exact(file_name_length)); + let extra_field = try!(reader.read_exact(extra_field_length)); + let file_comment_raw = try!(reader.read_exact(file_comment_length)); + + let file_name = match is_utf8 + { + true => String::from_utf8_lossy(&*file_name_raw).into_owned(), + false => ::cp437::to_string(&*file_name_raw), + }; + let file_comment = match is_utf8 + { + true => String::from_utf8_lossy(&*file_comment_raw).into_owned(), + false => ::cp437::to_string(&*file_comment_raw), + }; + + // 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_le_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_le_u16()) as u64; + let extra_field_length = try!(reader.read_le_u16()) as u64; + let magic_and_header = 4 + 22 + 2 + 2; + let data_start = offset as u64 + magic_and_header + file_name_length + extra_field_length; + + // Construct the result + let mut result = ZipFileData + { + encrypted: encrypted, + compression_method: FromPrimitive::from_u16(compression_method).unwrap_or(CompressionMethod::Unknown), + last_modified_time: util::msdos_datetime_to_tm(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 as u64, + data_start: data_start, + }; + + 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_le_u16()); + let len = try!(reader.read_le_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, + ZipFileReader::Bzip2(ref mut r) => r as &mut Read, + } + } + /// 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 + } +} + +impl<'a> Read for ZipFile<'a> { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.get_reader().read(buf) + } +} diff --git a/src/reader.rs b/src/reader.rs deleted file mode 100644 index 83138a89..00000000 --- a/src/reader.rs +++ /dev/null @@ -1,155 +0,0 @@ -use crc32::Crc32Reader; -use types::ZipFile; -use compression::CompressionMethod; -use spec; -use reader_spec; -use result::{ZipResult, ZipError}; -use std::old_io; -use std::cell::{RefCell, BorrowState}; -use std::collections::HashMap; -use flate2::FlateReader; -use bzip2::reader::BzDecompressor; - -/// Wrapper for reading the contents of a ZIP file. -/// -/// ``` -/// fn doit() -> zip::result::ZipResult<()> -/// { -/// // For demonstration purposes we read from an empty buffer. -/// // Normally a File object would be used. -/// let buf = [0u8; 128]; -/// let mut reader = std::old_io::BufReader::new(&buf); -/// -/// let zip = try!(zip::ZipReader::new(reader)); -/// -/// for file in zip.files() -/// { -/// println!("Filename: {}", file.file_name); -/// let mut file_reader = try!(zip.read_file(file)); -/// let first_byte = try!(file_reader.read_byte()); -/// println!("{}", first_byte); -/// } -/// Ok(()) -/// } -/// -/// println!("Result: {:?}", doit()); -/// ``` -pub struct ZipReader -{ - inner: RefCell, - files: Vec, - names_map: HashMap, -} - -fn unsupported_zip_error(detail: &'static str) -> ZipResult -{ - Err(ZipError::UnsupportedZipFile(detail)) -} - -impl ZipReader -{ - /// Opens a ZIP file and parses the content headers. - pub fn new(mut reader: T) -> 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 i64; - 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(directory_start, old_io::SeekSet)); - for _ in (0 .. number_of_files) - { - let file = try!(reader_spec::central_header_to_zip_file(&mut reader)); - names_map.insert(file.file_name.clone(), files.len()); - files.push(file); - } - - Ok(ZipReader { inner: RefCell::new(reader), files: files, names_map: names_map }) - } - - /// An iterator over the information of all contained files. - pub fn files(&self) -> ::std::slice::Iter - { - (&*self.files).iter() - } - - /// Search for a file entry by name - pub fn get(&self, name: &str) -> Option<&ZipFile> - { - self.names_map.get(name).map(|index| &self.files[*index]) - } - - /// Gets a reader for a contained zipfile. - /// - /// May return `ReaderUnavailable` if there is another reader borrowed. - pub fn read_file<'a>(&'a self, file: &ZipFile) -> ZipResult> - { - let mut inner_reader = match self.inner.borrow_state() - { - BorrowState::Unused => self.inner.borrow_mut(), - _ => return Err(ZipError::ReaderUnavailable), - }; - let pos = file.data_start as i64; - - if file.encrypted - { - return unsupported_zip_error("Encrypted files are not supported") - } - - try!(inner_reader.seek(pos, old_io::SeekSet)); - let refmut_reader = ::util::RefMutReader::new(inner_reader); - let limit_reader = old_io::util::LimitReader::new(refmut_reader, file.compressed_size as usize); - - let reader = match file.compression_method - { - CompressionMethod::Stored => - { - Box::new( - Crc32Reader::new( - limit_reader, - file.crc32)) - as Box - }, - CompressionMethod::Deflated => - { - let deflate_reader = limit_reader.deflate_decode(); - Box::new( - Crc32Reader::new( - deflate_reader, - file.crc32)) - as Box - }, - CompressionMethod::Bzip2 => - { - let bzip2_reader = BzDecompressor::new(limit_reader); - Box::new( - Crc32Reader::new( - bzip2_reader, - file.crc32)) - as Box - }, - _ => return unsupported_zip_error("Compression method not supported"), - }; - Ok(reader) - } - - /// Unwrap and return the inner reader object - /// - /// The position of the reader is undefined. - pub fn into_inner(self) -> T - { - self.inner.into_inner() - } - - /// Deprecated method equal to `into_inner()` - #[deprecated="renamed to into_inner()"] - pub fn unwrap(self) -> T - { - self.into_inner() - } -} diff --git a/src/reader_spec.rs b/src/reader_spec.rs deleted file mode 100644 index 5e02f5bd..00000000 --- a/src/reader_spec.rs +++ /dev/null @@ -1,104 +0,0 @@ -use std::old_io; -use std::num::FromPrimitive; -use result::{ZipResult, ZipError}; -use types::ZipFile; -use compression::CompressionMethod; -use spec; -use util; - -pub fn central_header_to_zip_file(reader: &mut R) -> ZipResult -{ - // Parse central header - let signature = try!(reader.read_le_u32()); - if signature != spec::CENTRAL_DIRECTORY_HEADER_SIGNATURE - { - return Err(ZipError::InvalidZipFile("Invalid Central Directory header")) - } - - try!(reader.read_le_u16()); - try!(reader.read_le_u16()); - let flags = try!(reader.read_le_u16()); - let encrypted = flags & 1 == 1; - let is_utf8 = flags & (1 << 11) != 0; - let compression_method = try!(reader.read_le_u16()); - let last_mod_time = try!(reader.read_le_u16()); - let last_mod_date = try!(reader.read_le_u16()); - let crc32 = try!(reader.read_le_u32()); - let compressed_size = try!(reader.read_le_u32()); - let uncompressed_size = try!(reader.read_le_u32()); - let file_name_length = try!(reader.read_le_u16()) as usize; - let extra_field_length = try!(reader.read_le_u16()) as usize; - let file_comment_length = try!(reader.read_le_u16()) as usize; - try!(reader.read_le_u16()); - try!(reader.read_le_u16()); - try!(reader.read_le_u32()); - let offset = try!(reader.read_le_u32()) as i64; - let file_name_raw = try!(reader.read_exact(file_name_length)); - let extra_field = try!(reader.read_exact(extra_field_length)); - let file_comment_raw = try!(reader.read_exact(file_comment_length)); - - let file_name = match is_utf8 - { - true => String::from_utf8_lossy(&*file_name_raw).into_owned(), - false => ::cp437::to_string(&*file_name_raw), - }; - let file_comment = match is_utf8 - { - true => String::from_utf8_lossy(&*file_comment_raw).into_owned(), - false => ::cp437::to_string(&*file_comment_raw), - }; - - // Remember end of central header - let return_position = try!(reader.tell()) as i64; - - // Parse local header - try!(reader.seek(offset, old_io::SeekSet)); - let signature = try!(reader.read_le_u32()); - if signature != spec::LOCAL_FILE_HEADER_SIGNATURE - { - return Err(ZipError::InvalidZipFile("Invalid local file header")) - } - - try!(reader.seek(22, old_io::SeekCur)); - let file_name_length = try!(reader.read_le_u16()) as u64; - let extra_field_length = try!(reader.read_le_u16()) as u64; - let magic_and_header = 4 + 22 + 2 + 2; - let data_start = offset as u64 + magic_and_header + file_name_length + extra_field_length; - - // Construct the result - let mut result = ZipFile - { - encrypted: encrypted, - compression_method: FromPrimitive::from_u16(compression_method).unwrap_or(CompressionMethod::Unknown), - last_modified_time: util::msdos_datetime_to_tm(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 as u64, - data_start: data_start, - }; - - try!(parse_extra_field(&mut result, &*extra_field)); - - // Go back after the central header - try!(reader.seek(return_position, old_io::SeekSet)); - - Ok(result) -} - -fn parse_extra_field(_file: &mut ZipFile, data: &[u8]) -> ZipResult<()> -{ - let mut reader = old_io::BufReader::new(data); - while !reader.eof() - { - let kind = try!(reader.read_le_u16()); - let len = try!(reader.read_le_u16()); - match kind - { - _ => try!(reader.seek(len as i64, old_io::SeekCur)), - } - } - Ok(()) -} diff --git a/src/result.rs b/src/result.rs index 9575bfa2..31cde8e4 100644 --- a/src/result.rs +++ b/src/result.rs @@ -1,6 +1,6 @@ //! Error types that can be emitted from this library -use std::old_io::IoError; +use std::io; use std::error; use std::fmt; @@ -12,16 +12,16 @@ pub type ZipResult = Result; pub enum ZipError { /// An Error caused by I/O - Io(IoError), + Io(io::Error), - /// This file is probably not a zipfile. The argument is enclosed. - InvalidZipFile(&'static str), + /// This file is probably not a zip archive + InvalidArchive(&'static str), - /// This file is unsupported. The reason is enclosed. - UnsupportedZipFile(&'static str), + /// This archive is not supported + UnsupportedArchive(&'static str), - /// The ZipReader is not available. - ReaderUnavailable, + /// The requested file could not be found in the archive + FileNotFound, } impl ZipError @@ -36,19 +36,19 @@ impl ZipError ZipError::Io(ref io_err) => { ("Io Error: ".to_string() + io_err.description()).into_cow() }, - ZipError::InvalidZipFile(msg) | ZipError::UnsupportedZipFile(msg) => { + ZipError::InvalidArchive(msg) | ZipError::UnsupportedArchive(msg) => { (self.description().to_string() + ": " + msg).into_cow() }, - ZipError::ReaderUnavailable => { + ZipError::FileNotFound => { self.description().into_cow() }, } } } -impl error::FromError for ZipError +impl error::FromError for ZipError { - fn from_error(err: IoError) -> ZipError + fn from_error(err: io::Error) -> ZipError { ZipError::Io(err) } @@ -69,9 +69,9 @@ impl error::Error for ZipError match *self { ZipError::Io(ref io_err) => io_err.description(), - ZipError::InvalidZipFile(..) => "Invalid Zip File", - ZipError::UnsupportedZipFile(..) => "Unsupported Zip File", - ZipError::ReaderUnavailable => "No reader available", + ZipError::InvalidArchive(..) => "Invalid Zip archive", + ZipError::UnsupportedArchive(..) => "Unsupported Zip archive", + ZipError::FileNotFound => "Specified file not found in archive", } } diff --git a/src/spec.rs b/src/spec.rs index f97e7247..4c9f651f 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -1,6 +1,8 @@ -use std::old_io; +use std::io; +use std::io::prelude::*; use result::{ZipResult, ZipError}; use std::iter::range_step_inclusive; +use util::{ReadIntExt, WriteIntExt}; pub static LOCAL_FILE_HEADER_SIGNATURE : u32 = 0x04034b50; pub static CENTRAL_DIRECTORY_HEADER_SIGNATURE : u32 = 0x02014b50; @@ -19,12 +21,12 @@ pub struct CentralDirectoryEnd impl CentralDirectoryEnd { - pub fn parse(reader: &mut T) -> ZipResult + pub fn parse(reader: &mut T) -> ZipResult { let magic = try!(reader.read_le_u32()); if magic != CENTRAL_DIRECTORY_END_SIGNATURE { - return Err(ZipError::UnsupportedZipFile("Invalid digital signature header")) + return Err(ZipError::InvalidArchive("Invalid digital signature header")) } let disk_number = try!(reader.read_le_u16()); let disk_with_central_directory = try!(reader.read_le_u16()); @@ -47,32 +49,31 @@ impl CentralDirectoryEnd }) } - pub fn find_and_parse(reader: &mut T) -> ZipResult + pub fn find_and_parse(reader: &mut T) -> ZipResult { let header_size = 22; let bytes_between_magic_and_comment_size = header_size - 6; - try!(reader.seek(0, old_io::SeekEnd)); - let file_length = try!(reader.tell()) as i64; + let file_length = try!(reader.seek(io::SeekFrom::End(0))) as i64; let search_upper_bound = ::std::cmp::max(0, file_length - header_size - ::std::u16::MAX as i64); for pos in range_step_inclusive(file_length - header_size, search_upper_bound, -1) { - try!(reader.seek(pos, old_io::SeekSet)); + try!(reader.seek(io::SeekFrom::Start(pos as u64))); if try!(reader.read_le_u32()) == CENTRAL_DIRECTORY_END_SIGNATURE { - try!(reader.seek(bytes_between_magic_and_comment_size, old_io::SeekCur)); + try!(reader.seek(io::SeekFrom::Current(bytes_between_magic_and_comment_size))); let comment_length = try!(reader.read_le_u16()) as i64; if file_length - pos - header_size == comment_length { - try!(reader.seek(pos, old_io::SeekSet)); + try!(reader.seek(io::SeekFrom::Start(pos as u64))); return CentralDirectoryEnd::parse(reader); } } } - Err(ZipError::UnsupportedZipFile("Could not find central directory end")) + Err(ZipError::InvalidArchive("Could not find central directory end")) } - pub fn write(&self, writer: &mut T) -> ZipResult<()> + pub fn write(&self, writer: &mut T) -> ZipResult<()> { try!(writer.write_le_u32(CENTRAL_DIRECTORY_END_SIGNATURE)); try!(writer.write_le_u16(self.disk_number)); diff --git a/src/types.rs b/src/types.rs index 71a88e5d..51d72b34 100644 --- a/src/types.rs +++ b/src/types.rs @@ -2,9 +2,8 @@ use time; -#[derive(Clone)] /// Structure representing a ZIP file. -pub struct ZipFile +pub struct ZipFileData { /// True if the file is encrypted. pub encrypted: bool, diff --git a/src/util.rs b/src/util.rs index 5184ee77..2285fe53 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,6 +1,7 @@ use time; use time::Tm; -use std::cell::RefMut; +use std::io; +use std::io::prelude::*; pub fn msdos_datetime_to_tm(time: u16, date: u16) -> Tm { @@ -40,23 +41,81 @@ pub fn tm_to_msdos_date(time: Tm) -> u16 (time.tm_mday | ((time.tm_mon + 1) << 5) | ((time.tm_year - 80) << 9)) as u16 } -pub struct RefMutReader<'a, R:'a> -{ - inner: RefMut<'a, R>, +/// Additional integer write methods for a io::Read +pub trait WriteIntExt { + /// Write a u32 in little-endian mode + fn write_le_u32(&mut self, u32) -> io::Result<()>; + /// Write a u16 in little-endian mode + fn write_le_u16(&mut self, u16) -> io::Result<()>; } -impl<'a, R: Reader> RefMutReader<'a, R> -{ - pub fn new(inner: RefMut<'a, R>) -> RefMutReader<'a, R> - { - RefMutReader { inner: inner, } +impl WriteIntExt for W { + fn write_le_u32(&mut self, val: u32) -> io::Result<()> { + let mut buf = [0u8; 4]; + let v = val; + buf[0] = ((v >> 0) & 0xFF) as u8; + buf[1] = ((v >> 8) & 0xFF) as u8; + buf[2] = ((v >> 16) & 0xFF) as u8; + buf[3] = ((v >> 24) & 0xFF) as u8; + self.write_all(&buf) } + + fn write_le_u16(&mut self, val: u16) -> io::Result<()> { + let mut buf = [0u8; 2]; + let v = val; + buf[0] = ((v >> 0) & 0xFF) as u8; + buf[1] = ((v >> 8) & 0xFF) as u8; + self.write_all(&buf) + } +} +/// Additional integer write methods for a io::Read +pub trait ReadIntExt { + /// Read a u32 in little-endian mode + fn read_le_u32(&mut self) -> io::Result; + /// Read a u16 in little-endian mode + fn read_le_u16(&mut self) -> io::Result; + /// Read exactly n bytes + fn read_exact(&mut self, usize) -> io::Result>; } -impl<'a, R: Reader> Reader for RefMutReader<'a, R> -{ - fn read(&mut self, buf: &mut [u8]) -> ::std::old_io::IoResult - { - self.inner.read(buf) +fn fill_exact(reader: &mut R, buf: &mut [u8]) -> io::Result<()> { + let mut idx = 0; + while idx < buf.len() { + match reader.read(&mut buf[idx..]) { + Err(v) => return Err(v), + Ok(0) => return Err(io::Error::new(io::ErrorKind::ResourceUnavailable, "Could not fill the buffer", None)), + Ok(i) => idx += i, + } + } + Ok(()) +} + +impl ReadIntExt for R { + fn read_le_u32(&mut self) -> io::Result { + let mut buf = [0u8; 4]; + try!(fill_exact(self, &mut buf)); + + Ok( + buf[0] as u32 + | ((buf[1] as u32) << 8) + | ((buf[2] as u32) << 16) + | ((buf[3] as u32) << 24) + ) + } + + fn read_le_u16(&mut self) -> io::Result { + let mut buf = [0u8; 2]; + try!(fill_exact(self, &mut buf)); + + Ok( + buf[0] as u16 + | ((buf[1] as u16) << 8) + ) + } + + fn read_exact(&mut self, n: usize) -> io::Result> { + let mut res = vec![0u8; n]; + try!(fill_exact(self, &mut res)); + Ok(res) } } diff --git a/src/writer.rs b/src/write.rs similarity index 50% rename from src/writer.rs rename to src/write.rs index bb1dc258..e3920cdc 100644 --- a/src/writer.rs +++ b/src/write.rs @@ -1,20 +1,26 @@ +//! Structs for creating a new zip archive + use compression::CompressionMethod; -use types::ZipFile; +use types::ZipFileData; use spec; -use writer_spec; use crc32; use result::{ZipResult, ZipError}; use std::default::Default; -use std::old_io; +use std::io; +use std::io::prelude::*; use std::mem; +use std::error::Error; +use std::ascii::AsciiExt; use time; use flate2; -use flate2::FlateWriter; -use flate2::writer::DeflateEncoder; +use flate2::FlateWriteExt; +use flate2::write::DeflateEncoder; use bzip2; use bzip2::writer::BzCompressor; +use util; +use util::WriteIntExt; -enum GenericZipWriter +enum GenericZipWriter { Closed, Storer(W), @@ -27,13 +33,15 @@ enum GenericZipWriter /// ``` /// fn doit() -> zip::result::ZipResult<()> /// { +/// use std::io::Write; +/// /// // For this example we write to a buffer, but normally you should use a File -/// let mut buf = [0u8; 65536]; -/// let w = std::old_io::BufWriter::new(&mut buf); +/// let mut buf: &mut [u8] = &mut [0u8; 65536]; +/// let mut w = std::io::Cursor::new(buf); /// let mut zip = zip::ZipWriter::new(w); /// /// try!(zip.start_file("hello_world.txt", zip::CompressionMethod::Stored)); -/// try!(zip.write_all(b"Hello, World!")); +/// try!(zip.write(b"Hello, World!")); /// /// // Optionally finish the zip. (this is also done on drop) /// try!(zip.finish()); @@ -43,10 +51,10 @@ enum GenericZipWriter /// /// println!("Result: {:?}", doit()); /// ``` -pub struct ZipWriter +pub struct ZipWriter { inner: GenericZipWriter, - files: Vec, + files: Vec, stats: ZipWriterStats, } @@ -58,18 +66,29 @@ struct ZipWriterStats bytes_written: u64, } -impl Writer for ZipWriter +impl Write for ZipWriter { - fn write_all(&mut self, buf: &[u8]) -> old_io::IoResult<()> + fn write(&mut self, buf: &[u8]) -> io::Result { - if self.files.len() == 0 { return Err(old_io::IoError { kind: old_io::OtherIoError, desc: "No file has been started", detail: None, }) } + if self.files.len() == 0 { return Err(io::Error::new(io::ErrorKind::Other, "No file has been started", None)) } self.stats.update(buf); match self.inner { - GenericZipWriter::Storer(ref mut w) => w.write_all(buf), - GenericZipWriter::Deflater(ref mut w) => w.write_all(buf), - GenericZipWriter::Bzip2(ref mut w) => w.write_all(buf), - GenericZipWriter::Closed => Err(old_io::standard_error(old_io::Closed)), + GenericZipWriter::Storer(ref mut w) => w.write(buf), + GenericZipWriter::Deflater(ref mut w) => w.write(buf), + GenericZipWriter::Bzip2(ref mut w) => w.write(buf), + GenericZipWriter::Closed => Err(io::Error::new(io::ErrorKind::BrokenPipe, "ZipWriter was already closed", None)), + } + } + + fn flush(&mut self) -> io::Result<()> + { + let result = self.finalize(); + self.inner = GenericZipWriter::Closed; + match result { + Ok(..) => Ok(()), + Err(ZipError::Io(io_err)) => Err(io_err), + Err(zip_err) => Err(io::Error::new(io::ErrorKind::Other, "A zip error occured", Some(zip_err.description().to_string()))), } } } @@ -83,7 +102,7 @@ impl ZipWriterStats } } -impl ZipWriter +impl ZipWriter { /// Initializes the ZipWriter. /// @@ -105,9 +124,9 @@ impl ZipWriter { let writer = self.inner.get_plain(); - let header_start = try!(writer.tell()); + let header_start = try!(writer.seek(io::SeekFrom::Current(0))); - let mut file = ZipFile + let mut file = ZipFileData { encrypted: false, compression_method: compression, @@ -115,14 +134,14 @@ impl ZipWriter crc32: 0, compressed_size: 0, uncompressed_size: 0, - file_name: String::from_str(name), + file_name: name.to_string(), file_comment: String::new(), header_start: header_start, data_start: 0, }; - try!(writer_spec::write_local_file_header(writer, &file)); + try!(write_local_file_header(writer, &file)); - let header_end = try!(writer.tell()); + let header_end = try!(writer.seek(io::SeekFrom::Current(0))); self.stats.start = header_end; file.data_start = header_end; @@ -149,10 +168,10 @@ impl ZipWriter }; file.crc32 = self.stats.crc32; file.uncompressed_size = self.stats.bytes_written; - file.compressed_size = try!(writer.tell()) - self.stats.start; + file.compressed_size = try!(writer.seek(io::SeekFrom::Current(0))) - self.stats.start; - try!(writer_spec::update_local_file_header(writer, file)); - try!(writer.seek(0, old_io::SeekEnd)); + try!(update_local_file_header(writer, file)); + try!(writer.seek(io::SeekFrom::End(0))); Ok(()) } @@ -174,12 +193,12 @@ impl ZipWriter { let writer = self.inner.get_plain(); - let central_start = try!(writer.tell()); + let central_start = try!(writer.seek(io::SeekFrom::Current(0))); for file in self.files.iter() { - try!(writer_spec::write_central_directory_header(writer, file)); + try!(write_central_directory_header(writer, file)); } - let central_size = try!(writer.tell()) - central_start; + let central_size = try!(writer.seek(io::SeekFrom::Current(0))) - central_start; let footer = spec::CentralDirectoryEnd { @@ -200,20 +219,20 @@ impl ZipWriter } #[unsafe_destructor] -impl Drop for ZipWriter +impl Drop for ZipWriter { fn drop(&mut self) { if !self.inner.is_closed() { if let Err(e) = self.finalize() { - let _ = write!(&mut old_io::stdio::stderr(), "ZipWriter drop failed: {:?}", e); + let _ = write!(&mut ::std::old_io::stdio::stderr(), "ZipWriter drop failed: {:?}", e); } } } } -impl GenericZipWriter +impl GenericZipWriter { fn switch_to(&mut self, compression: CompressionMethod) -> ZipResult<()> { @@ -222,15 +241,15 @@ impl GenericZipWriter GenericZipWriter::Storer(w) => w, GenericZipWriter::Deflater(w) => try!(w.finish()), GenericZipWriter::Bzip2(w) => match w.into_inner() { Ok(r) => r, Err((_, err)) => try!(Err(err)) }, - GenericZipWriter::Closed => try!(Err(old_io::standard_error(old_io::Closed))), + GenericZipWriter::Closed => try!(Err(io::Error::new(io::ErrorKind::BrokenPipe, "ZipWriter was already closed", None))), }; *self = match compression { CompressionMethod::Stored => GenericZipWriter::Storer(bare), - CompressionMethod::Deflated => GenericZipWriter::Deflater(bare.deflate_encode(flate2::CompressionLevel::Default)), - CompressionMethod::Bzip2 => GenericZipWriter::Bzip2(BzCompressor::new(bare, bzip2::CompressionLevel::Default)), - _ => return Err(ZipError::UnsupportedZipFile("Unsupported compression")), + CompressionMethod::Deflated => GenericZipWriter::Deflater(bare.deflate_encode(flate2::Compression::Default)), + CompressionMethod::Bzip2 => GenericZipWriter::Bzip2(BzCompressor::new(bare, bzip2::Compress::Default)), + _ => return Err(ZipError::UnsupportedArchive("Unsupported compression")), }; Ok(()) @@ -263,3 +282,68 @@ impl GenericZipWriter } } } + +fn write_local_file_header(writer: &mut T, file: &ZipFileData) -> ZipResult<()> +{ + try!(writer.write_le_u32(spec::LOCAL_FILE_HEADER_SIGNATURE)); + try!(writer.write_le_u16(20)); + let flag = if !file.file_name.is_ascii() { 1u16 << 11 } else { 0 }; + try!(writer.write_le_u16(flag)); + try!(writer.write_le_u16(file.compression_method as u16)); + try!(writer.write_le_u16(util::tm_to_msdos_time(file.last_modified_time))); + try!(writer.write_le_u16(util::tm_to_msdos_date(file.last_modified_time))); + try!(writer.write_le_u32(file.crc32)); + try!(writer.write_le_u32(file.compressed_size as u32)); + try!(writer.write_le_u32(file.uncompressed_size as u32)); + try!(writer.write_le_u16(file.file_name.as_bytes().len() as u16)); + let extra_field = try!(build_extra_field(file)); + try!(writer.write_le_u16(extra_field.len() as u16)); + try!(writer.write_all(file.file_name.as_bytes())); + try!(writer.write_all(extra_field.as_slice())); + + Ok(()) +} + +fn update_local_file_header(writer: &mut T, file: &ZipFileData) -> ZipResult<()> +{ + static CRC32_OFFSET : u64 = 14; + try!(writer.seek(io::SeekFrom::Start(file.header_start + CRC32_OFFSET))); + try!(writer.write_le_u32(file.crc32)); + try!(writer.write_le_u32(file.compressed_size as u32)); + try!(writer.write_le_u32(file.uncompressed_size as u32)); + Ok(()) +} + +fn write_central_directory_header(writer: &mut T, file: &ZipFileData) -> ZipResult<()> +{ + try!(writer.write_le_u32(spec::CENTRAL_DIRECTORY_HEADER_SIGNATURE)); + try!(writer.write_le_u16(0x14FF)); + try!(writer.write_le_u16(20)); + let flag = if !file.file_name.is_ascii() { 1u16 << 11 } else { 0 }; + try!(writer.write_le_u16(flag)); + try!(writer.write_le_u16(file.compression_method as u16)); + try!(writer.write_le_u16(util::tm_to_msdos_time(file.last_modified_time))); + try!(writer.write_le_u16(util::tm_to_msdos_date(file.last_modified_time))); + try!(writer.write_le_u32(file.crc32)); + try!(writer.write_le_u32(file.compressed_size as u32)); + try!(writer.write_le_u32(file.uncompressed_size as u32)); + try!(writer.write_le_u16(file.file_name.as_bytes().len() as u16)); + let extra_field = try!(build_extra_field(file)); + try!(writer.write_le_u16(extra_field.len() as u16)); + try!(writer.write_le_u16(0)); + try!(writer.write_le_u16(0)); + try!(writer.write_le_u16(0)); + try!(writer.write_le_u32(0)); + try!(writer.write_le_u32(file.header_start as u32)); + try!(writer.write_all(file.file_name.as_bytes())); + try!(writer.write_all(extra_field.as_slice())); + + Ok(()) +} + +fn build_extra_field(_file: &ZipFileData) -> ZipResult> +{ + let writer = Vec::new(); + // Future work + Ok(writer) +} diff --git a/src/writer_spec.rs b/src/writer_spec.rs deleted file mode 100644 index 5df725b4..00000000 --- a/src/writer_spec.rs +++ /dev/null @@ -1,71 +0,0 @@ -use std::old_io; -use std::ascii::AsciiExt; -use types::ZipFile; -use result::ZipResult; -use spec; -use util; - -pub fn write_local_file_header(writer: &mut T, file: &ZipFile) -> ZipResult<()> -{ - try!(writer.write_le_u32(spec::LOCAL_FILE_HEADER_SIGNATURE)); - try!(writer.write_le_u16(20)); - let flag = if !file.file_name.is_ascii() { 1u16 << 11 } else { 0 }; - try!(writer.write_le_u16(flag)); - try!(writer.write_le_u16(file.compression_method as u16)); - try!(writer.write_le_u16(util::tm_to_msdos_time(file.last_modified_time))); - try!(writer.write_le_u16(util::tm_to_msdos_date(file.last_modified_time))); - try!(writer.write_le_u32(file.crc32)); - try!(writer.write_le_u32(file.compressed_size as u32)); - try!(writer.write_le_u32(file.uncompressed_size as u32)); - try!(writer.write_le_u16(file.file_name.as_bytes().len() as u16)); - let extra_field = try!(build_extra_field(file)); - try!(writer.write_le_u16(extra_field.len() as u16)); - try!(writer.write_all(file.file_name.as_bytes())); - try!(writer.write_all(extra_field.as_slice())); - - Ok(()) -} - -pub fn update_local_file_header(writer: &mut T, file: &ZipFile) -> ZipResult<()> -{ - static CRC32_OFFSET : i64 = 14; - try!(writer.seek(file.header_start as i64 + CRC32_OFFSET, old_io::SeekSet)); - try!(writer.write_le_u32(file.crc32)); - try!(writer.write_le_u32(file.compressed_size as u32)); - try!(writer.write_le_u32(file.uncompressed_size as u32)); - Ok(()) -} - -pub fn write_central_directory_header(writer: &mut T, file: &ZipFile) -> ZipResult<()> -{ - try!(writer.write_le_u32(spec::CENTRAL_DIRECTORY_HEADER_SIGNATURE)); - try!(writer.write_le_u16(0x14FF)); - try!(writer.write_le_u16(20)); - let flag = if !file.file_name.is_ascii() { 1u16 << 11 } else { 0 }; - try!(writer.write_le_u16(flag)); - try!(writer.write_le_u16(file.compression_method as u16)); - try!(writer.write_le_u16(util::tm_to_msdos_time(file.last_modified_time))); - try!(writer.write_le_u16(util::tm_to_msdos_date(file.last_modified_time))); - try!(writer.write_le_u32(file.crc32)); - try!(writer.write_le_u32(file.compressed_size as u32)); - try!(writer.write_le_u32(file.uncompressed_size as u32)); - try!(writer.write_le_u16(file.file_name.as_bytes().len() as u16)); - let extra_field = try!(build_extra_field(file)); - try!(writer.write_le_u16(extra_field.len() as u16)); - try!(writer.write_le_u16(0)); - try!(writer.write_le_u16(0)); - try!(writer.write_le_u16(0)); - try!(writer.write_le_u32(0)); - try!(writer.write_le_u32(file.header_start as u32)); - try!(writer.write_all(file.file_name.as_bytes())); - try!(writer.write_all(extra_field.as_slice())); - - Ok(()) -} - -fn build_extra_field(_file: &ZipFile) -> ZipResult> -{ - let writer = old_io::MemWriter::new(); - // Future work - Ok(writer.into_inner()) -}