diff --git a/src/bin/write_sample.rs b/src/bin/write_sample.rs new file mode 100644 index 00000000..5d61c4a3 --- /dev/null +++ b/src/bin/write_sample.rs @@ -0,0 +1,52 @@ +extern crate zip; + +fn main() +{ + println!("{}", doit()); +} + +fn doit() -> std::io::IoResult<()> +{ + let args = std::os::args(); + let fname = Path::new(args[1].as_slice()); + let file = std::io::File::create(&fname).unwrap(); + + let mut zip = zip::writer::ZipWriter::new(file); + + +// try!(zip.start_file(b"test", zip::types::Stored)); +// try!(zip.write(b"")); + + try!(zip.start_file(b"test/readme.txt", zip::types::Stored)); + try!(zip.write(b"Hello, World!\n")); + + try!(zip.start_file(b"test/lorem.txt", zip::types::Deflated)); + try!(zip.write(b"Lorem ipsum dolor sit amet, consectetur adipiscing elit. In tellus elit, tristique vitae mattis egestas, ultricies vitae risus. Quisque sit amet quam ut urna aliquet +molestie. Proin blandit ornare dui, a tempor nisl accumsan in. Praesent a consequat felis. Morbi metus diam, auctor in auctor vel, feugiat id odio. Curabitur ex ex, +dictum quis auctor quis, suscipit id lorem. Aliquam vestibulum dolor nec enim vehicula, porta tristique augue tincidunt. Vivamus ut gravida est. Sed pellentesque, dolor +vitae tristique consectetur, neque lectus pulvinar dui, sed feugiat purus diam id lectus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per +inceptos himenaeos. Maecenas feugiat velit in ex ultrices scelerisque id id neque. + +Phasellus sed nisi in augue sodales pulvinar ut et leo. Pellentesque eget leo vitae massa bibendum sollicitudin. Curabitur erat lectus, congue quis auctor sed, aliquet +bibendum est. Ut porta ultricies turpis at maximus. Cras non lobortis justo. Duis rutrum magna sed velit facilisis, et sagittis metus laoreet. Pellentesque quam ligula, +dapibus vitae mauris quis, dapibus cursus leo. Sed sit amet condimentum eros. Nulla vestibulum enim sit amet lorem pharetra, eu fringilla nisl posuere. Sed tristique non +nibh at viverra. Vivamus sed accumsan lacus, nec pretium eros. Mauris elementum arcu eu risus fermentum, tempor ullamcorper neque aliquam. Sed tempor in erat eu +suscipit. In euismod in libero in facilisis. Donec sagittis, odio et fermentum dignissim, risus justo pretium nibh, eget vestibulum lectus metus vel lacus. + +Quisque feugiat, magna ac feugiat ullamcorper, augue justo consequat felis, ut fermentum arcu lorem vitae ligula. Quisque iaculis tempor maximus. In quis eros ac tellus +aliquam placerat quis id tellus. Donec non gravida nulla. Morbi faucibus neque sed faucibus aliquam. Sed accumsan mattis nunc, non interdum justo. Cras vitae facilisis +leo. Fusce sollicitudin ultrices sagittis. Maecenas eget massa id lorem dignissim ultrices non et ligula. Pellentesque aliquam mi ac neque tempus ornare. Morbi non enim +vulputate quam ullamcorper finibus id non neque. Quisque malesuada commodo lorem, ut ornare velit iaculis rhoncus. Mauris vel maximus ex. + +Morbi eleifend blandit diam, non vulputate ante iaculis in. Donec pellentesque augue id enim suscipit, eget suscipit lacus commodo. Ut vel ex vitae elit imperdiet +vulputate. Nunc eu mattis orci, ut pretium sem. Nam vitae purus mollis ante tempus malesuada a at magna. Integer mattis lectus non luctus lobortis. In a cursus quam, +eget faucibus sem. + +Donec vitae condimentum nisi, non efficitur massa. Praesent sed mi in massa sollicitudin iaculis. Pellentesque a libero ultrices, sodales lacus eu, ornare dui. In +laoreet est nec dolor aliquam consectetur. Integer iaculis felis venenatis libero pulvinar, ut pretium odio interdum. Donec in nisi eu dolor varius vestibulum eget vel +nunc. Morbi a venenatis quam, in vehicula justo. Nam risus dui, auctor eu accumsan at, sagittis ac lectus. Mauris iaculis dignissim interdum. Cras cursus dapibus auctor. +Donec sagittis massa vitae tortor viverra vehicula. Mauris fringilla nunc eu lorem ultrices placerat. Maecenas posuere porta quam at semper. Praesent eu bibendum eros. +Nunc congue sollicitudin ante, sollicitudin lacinia magna cursus vitae. +")); + zip.finalize() +} diff --git a/src/lib.rs b/src/lib.rs index 94559653..66652c14 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ #![feature(phase)] +#![feature(unsafe_destructor)] #[phase(plugin, link)] extern crate log; extern crate time; diff --git a/src/spec.rs b/src/spec.rs index 76155c5d..3dff5187 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -73,6 +73,7 @@ pub fn central_header_to_zip_file(reader: &mut R) -> IoResult(writer: &mut T, file: &ZipFile) -> IoR 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.len() as u16)); + try!(writer.write_le_u16(0)); + try!(writer.write(file.file_name.as_slice())); + + Ok(()) +} + +pub fn write_central_directory_header(writer: &mut T, file: &ZipFile) -> IoResult<()> +{ + try!(writer.write_le_u32(CENTRAL_DIRECTORY_HEADER_SIGNATURE)); + try!(writer.write_le_u16(1337)); + try!(writer.write_le_u16(20)); + let flag = if file.encrypted { 1 } 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.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_u16(0)); + try!(writer.write_le_u32(0)); + try!(writer.write_le_u32(file.header_start as u32)); + try!(writer.write(file.file_name.as_slice())); Ok(()) } @@ -168,4 +200,18 @@ impl CentralDirectoryEnd detail: None }) } + + pub fn write(&self, writer: &mut T) -> IoResult<()> + { + try!(writer.write_le_u32(CENTRAL_DIRECTORY_END_SIGNATURE)); + try!(writer.write_le_u16(self.disk_number)); + try!(writer.write_le_u16(self.disk_with_central_directory)); + try!(writer.write_le_u16(self.number_of_files_on_this_disk)); + try!(writer.write_le_u16(self.number_of_files)); + try!(writer.write_le_u32(self.central_directory_size)); + try!(writer.write_le_u32(self.central_directory_offset)); + try!(writer.write_le_u16(self.zip_file_comment.len() as u16)); + try!(writer.write(self.zip_file_comment.as_slice())); + Ok(()) + } } diff --git a/src/types.rs b/src/types.rs index 013be7ac..d0234e57 100644 --- a/src/types.rs +++ b/src/types.rs @@ -33,6 +33,7 @@ pub struct ZipFile pub uncompressed_size: u64, pub file_name: Vec, pub file_comment: Vec, + pub header_start: u64, pub data_start: u64, } diff --git a/src/util.rs b/src/util.rs index efebf86b..412b0151 100644 --- a/src/util.rs +++ b/src/util.rs @@ -29,7 +29,12 @@ pub fn msdos_datetime_to_tm(time: u16, date: u16) -> Tm pub fn tm_to_msdos_time(time: Tm) -> u16 { - ((time.tm_sec >> 2) | (time.tm_min << 5) | (time.tm_hour << 11)) as u16 + ((time.tm_sec >> 1) | (time.tm_min << 5) | (time.tm_hour << 11)) as u16 +} + +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> diff --git a/src/writer.rs b/src/writer.rs index 03888e04..27cc2d6d 100644 --- a/src/writer.rs +++ b/src/writer.rs @@ -12,7 +12,7 @@ use flate2::writer::DeflateEncoder; enum GenericZipWriter { - Invalid, + Closed, Storer(W), Deflater(DeflateEncoder), } @@ -32,6 +32,11 @@ struct ZipWriterStats bytes_written: u64, } +fn writer_closed_error() -> IoResult +{ + Err(IoError { kind: io::Closed, desc: "This writer has been closed", detail: None }) +} + impl Writer for ZipWriter { fn write(&mut self, buf: &[u8]) -> IoResult<()> @@ -42,7 +47,7 @@ impl Writer for ZipWriter { Storer(ref mut w) => w.write(buf), Deflater(ref mut w) => w.write(buf), - Invalid => Err(IoError { kind: io::OtherIoError, desc: "The writer has previously failed", detail: None }), + Closed => writer_closed_error(), } } } @@ -68,31 +73,35 @@ impl ZipWriter } } - pub fn start_new_file(&mut self, name: &[u8], compression: types::CompressionMethod) -> IoResult<()> + pub fn start_file(&mut self, name: &[u8], compression: types::CompressionMethod) -> IoResult<()> { try!(self.finish_file()); - let writer = match self.inner { - Storer(ref mut r) => r, - _ => fail!("Should have switched to stored first"), - }; - let header_start = try!(writer.tell()); - try!(writer.seek(30 + name.len() as i64, io::SeekCur)); - self.stats.start = header_start + 30 + name.len() as u64; + let writer = self.inner.get_plain(); + let header_start = try!(writer.tell()); + try!(writer.seek(30 + name.len() as i64, io::SeekCur)); - self.files.push(ZipFile - { - encrypted: false, - compression_method: compression, - last_modified_time: time::now(), - crc32: 0, - compressed_size: 0, - uncompressed_size: 0, - file_name: Vec::from_slice(name), - file_comment: Vec::new(), - data_start: self.stats.start, - }); + self.stats.start = header_start + 30 + name.len() as u64; + self.stats.bytes_written = 0; + self.stats.crc32 = 0; + + self.files.push(ZipFile + { + encrypted: false, + compression_method: compression, + last_modified_time: time::now(), + crc32: 0, + compressed_size: 0, + uncompressed_size: 0, + file_name: Vec::from_slice(name), + file_comment: Vec::new(), + header_start: header_start, + data_start: self.stats.start, + }); + } + + try!(self.inner.switch_to(compression)); Ok(()) } @@ -100,11 +109,7 @@ impl ZipWriter fn finish_file(&mut self) -> IoResult<()> { try!(self.inner.switch_to(types::Stored)); - let writer = match self.inner - { - Storer(ref mut r) => r, - _ => fail!("Should have switched to stored first"), - }; + let writer = self.inner.get_plain(); let file = match self.files.mut_last() { @@ -115,21 +120,70 @@ impl ZipWriter file.uncompressed_size = self.stats.bytes_written; file.compressed_size = try!(writer.tell()) - self.stats.start; - // write header + try!(writer.seek(file.header_start as i64, io::SeekSet)); try!(spec::write_local_file_header(writer, file)); + try!(writer.seek(0, io::SeekEnd)); Ok(()) } + + pub fn finalize(&mut self) -> IoResult<()> + { + try!(self.finish_file()); + + { + let writer = self.inner.get_plain(); + + let central_start = try!(writer.tell()); + for file in self.files.iter() + { + try!(spec::write_central_directory_header(writer, file)); + } + let central_size = try!(writer.tell()) - central_start; + + let footer = spec::CentralDirectoryEnd + { + disk_number: 0, + disk_with_central_directory: 0, + number_of_files_on_this_disk: self.files.len() as u16, + number_of_files: self.files.len() as u16, + central_directory_size: central_size as u32, + central_directory_offset: central_start as u32, + zip_file_comment: Vec::from_slice(b"zip-rs"), + }; + + try!(footer.write(writer)); + } + + self.inner = Closed; + Ok(()) + } +} + +#[unsafe_destructor] +impl Drop for ZipWriter +{ + fn drop(&mut self) + { + if !self.inner.is_closed() + { + match self.finalize() + { + Ok(_) => {}, + Err(e) => warn!("ZipWriter drop failed: {}", e), + } + } + } } impl GenericZipWriter { fn switch_to(&mut self, compression: types::CompressionMethod) -> IoResult<()> { - let bare = match mem::replace(self, Invalid) + let bare = match mem::replace(self, Closed) { Storer(w) => w, Deflater(w) => try!(w.finish()), - Invalid => return Err(IoError { kind: io::OtherIoError, desc: "The writer has previously failed", detail: None }), + Closed => return writer_closed_error(), }; *self = match compression @@ -141,4 +195,22 @@ impl GenericZipWriter Ok(()) } + + fn is_closed(&self) -> bool + { + match *self + { + Closed => true, + _ => false, + } + } + + fn get_plain(&mut self) -> &mut W + { + match *self + { + Storer(ref mut w) => w, + _ => fail!("Should have switched to stored beforehand"), + } + } }