1 //! Structs for creating a new zip archive
2 
3 use compression::CompressionMethod;
4 use types::{ZipFileData, System, DEFAULT_VERSION};
5 use spec;
6 use crc32;
7 use result::{ZipResult, ZipError};
8 use std::default::Default;
9 use std::io;
10 use std::io::prelude::*;
11 use std::mem;
12 use time;
13 use podio::{WritePodExt, LittleEndian};
14 use msdos_time::TmMsDosExt;
15 
16 #[cfg(feature = "flate2")]
17 use flate2;
18 #[cfg(feature = "flate2")]
19 use flate2::write::DeflateEncoder;
20 
21 #[cfg(feature = "bzip2")]
22 use bzip2;
23 #[cfg(feature = "bzip2")]
24 use bzip2::write::BzEncoder;
25 #[allow(unused_imports)] // Rust <1.23 compat
26 use std::ascii::AsciiExt;
27 
28 enum GenericZipWriter<W: Write + io::Seek>
29 {
30     Closed,
31     Storer(W),
32     #[cfg(feature = "flate2")]
33     Deflater(DeflateEncoder<W>),
34     #[cfg(feature = "bzip2")]
35     Bzip2(BzEncoder<W>),
36 }
37 
38 /// Generator for ZIP files.
39 ///
40 /// ```
41 /// fn doit() -> zip::result::ZipResult<()>
42 /// {
43 ///     use std::io::Write;
44 ///
45 ///     // For this example we write to a buffer, but normally you should use a File
46 ///     let mut buf: &mut [u8] = &mut [0u8; 65536];
47 ///     let mut w = std::io::Cursor::new(buf);
48 ///     let mut zip = zip::ZipWriter::new(w);
49 ///
50 ///     let options = zip::write::FileOptions::default().compression_method(zip::CompressionMethod::Stored);
51 ///     try!(zip.start_file("hello_world.txt", options));
52 ///     try!(zip.write(b"Hello, World!"));
53 ///
54 ///     // Optionally finish the zip. (this is also done on drop)
55 ///     try!(zip.finish());
56 ///
57 ///     Ok(())
58 /// }
59 ///
60 /// println!("Result: {:?}", doit().unwrap());
61 /// ```
62 pub struct ZipWriter<W: Write + io::Seek>
63 {
64     inner: GenericZipWriter<W>,
65     files: Vec<ZipFileData>,
66     stats: ZipWriterStats,
67 }
68 
69 #[derive(Default)]
70 struct ZipWriterStats
71 {
72     crc32: u32,
73     start: u64,
74     bytes_written: u64,
75 }
76 
77 /// Metadata for a file to be written
78 #[derive(Copy, Clone)]
79 pub struct FileOptions {
80     compression_method: CompressionMethod,
81     last_modified_time: time::Tm,
82     permissions: Option<u32>,
83 }
84 
85 impl FileOptions {
86     #[cfg(feature = "flate2")]
87     /// Construct a new FileOptions object
default() -> FileOptions88     pub fn default() -> FileOptions {
89         FileOptions {
90             compression_method: CompressionMethod::Deflated,
91             last_modified_time: time::now(),
92             permissions: None,
93         }
94     }
95 
96     #[cfg(not(feature = "flate2"))]
97     /// Construct a new FileOptions object
default() -> FileOptions98     pub fn default() -> FileOptions {
99         FileOptions {
100             compression_method: CompressionMethod::Stored,
101             last_modified_time: time::now(),
102             permissions: None,
103         }
104     }
105 
106     /// Set the compression method for the new file
107     ///
108     /// The default is `CompressionMethod::Deflated`. If the deflate compression feature is
109     /// disabled, `CompressionMethod::Stored` becomes the default.
110     /// otherwise.
compression_method(mut self, method: CompressionMethod) -> FileOptions111     pub fn compression_method(mut self, method: CompressionMethod) -> FileOptions {
112         self.compression_method = method;
113         self
114     }
115 
116     /// Set the last modified time
117     ///
118     /// The default is the current timestamp
last_modified_time(mut self, mod_time: time::Tm) -> FileOptions119     pub fn last_modified_time(mut self, mod_time: time::Tm) -> FileOptions {
120         self.last_modified_time = mod_time;
121         self
122     }
123 
124     /// Set the permissions for the new file.
125     ///
126     /// The format is represented with unix-style permissions.
127     /// The default is `0o644`, which represents `rw-r--r--` for files,
128     /// and `0o755`, which represents `rwxr-xr-x` for directories
unix_permissions(mut self, mode: u32) -> FileOptions129     pub fn unix_permissions(mut self, mode: u32) -> FileOptions {
130         self.permissions = Some(mode & 0o777);
131         self
132     }
133 }
134 
135 impl<W: Write+io::Seek> Write for ZipWriter<W>
136 {
write(&mut self, buf: &[u8]) -> io::Result<usize>137     fn write(&mut self, buf: &[u8]) -> io::Result<usize>
138     {
139         if self.files.len() == 0 { return Err(io::Error::new(io::ErrorKind::Other, "No file has been started")) }
140         match self.inner.ref_mut()
141         {
142             Some(ref mut w) => {
143                 let write_result = w.write(buf);
144                 if let Ok(count) = write_result {
145                     self.stats.update(&buf[0..count]);
146                 }
147                 write_result
148 
149             }
150             None => Err(io::Error::new(io::ErrorKind::BrokenPipe, "ZipWriter was already closed")),
151         }
152     }
153 
flush(&mut self) -> io::Result<()>154     fn flush(&mut self) -> io::Result<()>
155     {
156         match self.inner.ref_mut()
157         {
158             Some(ref mut w) => w.flush(),
159             None => Err(io::Error::new(io::ErrorKind::BrokenPipe, "ZipWriter was already closed")),
160         }
161     }
162 }
163 
164 impl ZipWriterStats
165 {
update(&mut self, buf: &[u8])166     fn update(&mut self, buf: &[u8])
167     {
168         self.crc32 = crc32::update(self.crc32, buf);
169         self.bytes_written += buf.len() as u64;
170     }
171 }
172 
173 impl<W: Write+io::Seek> ZipWriter<W>
174 {
175     /// Initializes the ZipWriter.
176     ///
177     /// Before writing to this object, the start_file command should be called.
new(inner: W) -> ZipWriter<W>178     pub fn new(inner: W) -> ZipWriter<W>
179     {
180         ZipWriter
181         {
182             inner: GenericZipWriter::Storer(inner),
183             files: Vec::new(),
184             stats: Default::default(),
185         }
186     }
187 
188     /// Start a new file for with the requested options.
start_entry<S>(&mut self, name: S, options: FileOptions) -> ZipResult<()> where S: Into<String>189     fn start_entry<S>(&mut self, name: S, options: FileOptions) -> ZipResult<()>
190         where S: Into<String>
191     {
192         try!(self.finish_file());
193 
194         {
195             let writer = self.inner.get_plain();
196             let header_start = try!(writer.seek(io::SeekFrom::Current(0)));
197 
198             let permissions = options.permissions.unwrap_or(0o100644);
199             let file_name = name.into();
200             let file_name_raw = file_name.clone().into_bytes();
201             let mut file = ZipFileData
202             {
203                 system: System::Unix,
204                 version_made_by: DEFAULT_VERSION,
205                 encrypted: false,
206                 compression_method: options.compression_method,
207                 last_modified_time: options.last_modified_time,
208                 crc32: 0,
209                 compressed_size: 0,
210                 uncompressed_size: 0,
211                 file_name: file_name,
212                 file_name_raw: file_name_raw,
213                 file_comment: String::new(),
214                 header_start: header_start,
215                 data_start: 0,
216                 external_attributes: permissions << 16,
217             };
218             try!(write_local_file_header(writer, &file));
219 
220             let header_end = try!(writer.seek(io::SeekFrom::Current(0)));
221             self.stats.start = header_end;
222             file.data_start = header_end;
223 
224             self.stats.bytes_written = 0;
225             self.stats.crc32 = 0;
226 
227             self.files.push(file);
228         }
229 
230         try!(self.inner.switch_to(options.compression_method));
231 
232         Ok(())
233     }
234 
finish_file(&mut self) -> ZipResult<()>235     fn finish_file(&mut self) -> ZipResult<()>
236     {
237         try!(self.inner.switch_to(CompressionMethod::Stored));
238         let writer = self.inner.get_plain();
239 
240         let file = match self.files.last_mut()
241         {
242             None => return Ok(()),
243             Some(f) => f,
244         };
245         file.crc32 = self.stats.crc32;
246         file.uncompressed_size = self.stats.bytes_written;
247 
248         let file_end = try!(writer.seek(io::SeekFrom::Current(0)));
249         file.compressed_size = file_end - self.stats.start;
250 
251         try!(update_local_file_header(writer, file));
252         try!(writer.seek(io::SeekFrom::Start(file_end)));
253         Ok(())
254     }
255 
256     /// Starts a file.
start_file<S>(&mut self, name: S, mut options: FileOptions) -> ZipResult<()> where S: Into<String>257     pub fn start_file<S>(&mut self, name: S, mut options: FileOptions) -> ZipResult<()>
258         where S: Into<String>
259     {
260         if options.permissions.is_none() {
261             options.permissions = Some(0o644);
262         }
263         *options.permissions.as_mut().unwrap() |= 0o100000;
264         try!(self.start_entry(name, options));
265         Ok(())
266     }
267 
268     /// Add a directory entry.
269     ///
270     /// You should not write data to the file afterwards.
add_directory<S>(&mut self, name: S, mut options: FileOptions) -> ZipResult<()> where S: Into<String>271     pub fn add_directory<S>(&mut self, name: S, mut options: FileOptions) -> ZipResult<()>
272         where S: Into<String>
273     {
274         if options.permissions.is_none() {
275             options.permissions = Some(0o755);
276         }
277         *options.permissions.as_mut().unwrap() |= 0o40000;
278         options.compression_method = CompressionMethod::Stored;
279         try!(self.start_entry(name, options));
280         Ok(())
281     }
282 
283     /// Finish the last file and write all other zip-structures
284     ///
285     /// This will return the writer, but one should normally not append any data to the end of the file.
286     /// Note that the zipfile will also be finished on drop.
finish(&mut self) -> ZipResult<W>287     pub fn finish(&mut self) -> ZipResult<W>
288     {
289         try!(self.finalize());
290         let inner = mem::replace(&mut self.inner, GenericZipWriter::Closed);
291         Ok(inner.unwrap())
292     }
293 
finalize(&mut self) -> ZipResult<()>294     fn finalize(&mut self) -> ZipResult<()>
295     {
296         try!(self.finish_file());
297 
298         {
299             let writer = self.inner.get_plain();
300 
301             let central_start = try!(writer.seek(io::SeekFrom::Current(0)));
302             for file in self.files.iter()
303             {
304                 try!(write_central_directory_header(writer, file));
305             }
306             let central_size = try!(writer.seek(io::SeekFrom::Current(0))) - central_start;
307 
308             let footer = spec::CentralDirectoryEnd
309             {
310                 disk_number: 0,
311                 disk_with_central_directory: 0,
312                 number_of_files_on_this_disk: self.files.len() as u16,
313                 number_of_files: self.files.len() as u16,
314                 central_directory_size: central_size as u32,
315                 central_directory_offset: central_start as u32,
316                 zip_file_comment: b"zip-rs".to_vec(),
317             };
318 
319             try!(footer.write(writer));
320         }
321 
322         Ok(())
323     }
324 }
325 
326 impl<W: Write+io::Seek> Drop for ZipWriter<W>
327 {
drop(&mut self)328     fn drop(&mut self)
329     {
330         if !self.inner.is_closed()
331         {
332             if let Err(e) = self.finalize() {
333                 let _ = write!(&mut io::stderr(), "ZipWriter drop failed: {:?}", e);
334             }
335         }
336     }
337 }
338 
339 impl<W: Write+io::Seek> GenericZipWriter<W>
340 {
switch_to(&mut self, compression: CompressionMethod) -> ZipResult<()>341     fn switch_to(&mut self, compression: CompressionMethod) -> ZipResult<()>
342     {
343         match self.current_compression() {
344             Some(method) if method == compression => return Ok(()),
345             None => try!(Err(io::Error::new(io::ErrorKind::BrokenPipe, "ZipWriter was already closed"))),
346             _ => {},
347         }
348 
349         let bare = match mem::replace(self, GenericZipWriter::Closed)
350         {
351             GenericZipWriter::Storer(w) => w,
352             #[cfg(feature = "flate2")]
353             GenericZipWriter::Deflater(w) => try!(w.finish()),
354             #[cfg(feature = "bzip2")]
355             GenericZipWriter::Bzip2(w) => try!(w.finish()),
356             GenericZipWriter::Closed => try!(Err(io::Error::new(io::ErrorKind::BrokenPipe, "ZipWriter was already closed"))),
357         };
358 
359         *self = match compression
360         {
361             CompressionMethod::Stored => GenericZipWriter::Storer(bare),
362             #[cfg(feature = "flate2")]
363             CompressionMethod::Deflated => GenericZipWriter::Deflater(DeflateEncoder::new(bare, flate2::Compression::default())),
364             #[cfg(feature = "bzip2")]
365             CompressionMethod::Bzip2 => GenericZipWriter::Bzip2(BzEncoder::new(bare, bzip2::Compression::Default)),
366             CompressionMethod::Unsupported(..) => return Err(ZipError::UnsupportedArchive("Unsupported compression")),
367         };
368 
369         Ok(())
370     }
371 
ref_mut(&mut self) -> Option<&mut Write>372     fn ref_mut(&mut self) -> Option<&mut Write> {
373         match *self {
374             GenericZipWriter::Storer(ref mut w) => Some(w as &mut Write),
375             #[cfg(feature = "flate2")]
376             GenericZipWriter::Deflater(ref mut w) => Some(w as &mut Write),
377             #[cfg(feature = "bzip2")]
378             GenericZipWriter::Bzip2(ref mut w) => Some(w as &mut Write),
379             GenericZipWriter::Closed => None,
380         }
381     }
382 
is_closed(&self) -> bool383     fn is_closed(&self) -> bool
384     {
385         match *self
386         {
387             GenericZipWriter::Closed => true,
388             _ => false,
389         }
390     }
391 
get_plain(&mut self) -> &mut W392     fn get_plain(&mut self) -> &mut W
393     {
394         match *self
395         {
396             GenericZipWriter::Storer(ref mut w) => w,
397             _ => panic!("Should have switched to stored beforehand"),
398         }
399     }
400 
current_compression(&self) -> Option<CompressionMethod>401     fn current_compression(&self) -> Option<CompressionMethod> {
402         match *self {
403             GenericZipWriter::Storer(..) => Some(CompressionMethod::Stored),
404             #[cfg(feature = "flate2")]
405             GenericZipWriter::Deflater(..) => Some(CompressionMethod::Deflated),
406             #[cfg(feature = "bzip2")]
407             GenericZipWriter::Bzip2(..) => Some(CompressionMethod::Bzip2),
408             GenericZipWriter::Closed => None,
409         }
410     }
411 
unwrap(self) -> W412     fn unwrap(self) -> W
413     {
414         match self
415         {
416             GenericZipWriter::Storer(w) => w,
417             _ => panic!("Should have switched to stored beforehand"),
418         }
419     }
420 }
421 
write_local_file_header<T: Write>(writer: &mut T, file: &ZipFileData) -> ZipResult<()>422 fn write_local_file_header<T: Write>(writer: &mut T, file: &ZipFileData) -> ZipResult<()>
423 {
424     // local file header signature
425     try!(writer.write_u32::<LittleEndian>(spec::LOCAL_FILE_HEADER_SIGNATURE));
426     // version needed to extract
427     try!(writer.write_u16::<LittleEndian>(file.version_needed()));
428     // general purpose bit flag
429     let flag = if !file.file_name.is_ascii() { 1u16 << 11 } else { 0 };
430     try!(writer.write_u16::<LittleEndian>(flag));
431     // Compression method
432     try!(writer.write_u16::<LittleEndian>(file.compression_method.to_u16()));
433     // last mod file time and last mod file date
434     let msdos_datetime = try!(file.last_modified_time.to_msdos());
435     try!(writer.write_u16::<LittleEndian>(msdos_datetime.timepart));
436     try!(writer.write_u16::<LittleEndian>(msdos_datetime.datepart));
437     // crc-32
438     try!(writer.write_u32::<LittleEndian>(file.crc32));
439     // compressed size
440     try!(writer.write_u32::<LittleEndian>(file.compressed_size as u32));
441     // uncompressed size
442     try!(writer.write_u32::<LittleEndian>(file.uncompressed_size as u32));
443     // file name length
444     try!(writer.write_u16::<LittleEndian>(file.file_name.as_bytes().len() as u16));
445     // extra field length
446     let extra_field = try!(build_extra_field(file));
447     try!(writer.write_u16::<LittleEndian>(extra_field.len() as u16));
448     // file name
449     try!(writer.write_all(file.file_name.as_bytes()));
450     // extra field
451     try!(writer.write_all(&extra_field));
452 
453     Ok(())
454 }
455 
update_local_file_header<T: Write+io::Seek>(writer: &mut T, file: &ZipFileData) -> ZipResult<()>456 fn update_local_file_header<T: Write+io::Seek>(writer: &mut T, file: &ZipFileData) -> ZipResult<()>
457 {
458     const CRC32_OFFSET : u64 = 14;
459     try!(writer.seek(io::SeekFrom::Start(file.header_start + CRC32_OFFSET)));
460     try!(writer.write_u32::<LittleEndian>(file.crc32));
461     try!(writer.write_u32::<LittleEndian>(file.compressed_size as u32));
462     try!(writer.write_u32::<LittleEndian>(file.uncompressed_size as u32));
463     Ok(())
464 }
465 
write_central_directory_header<T: Write>(writer: &mut T, file: &ZipFileData) -> ZipResult<()>466 fn write_central_directory_header<T: Write>(writer: &mut T, file: &ZipFileData) -> ZipResult<()>
467 {
468     // central file header signature
469     try!(writer.write_u32::<LittleEndian>(spec::CENTRAL_DIRECTORY_HEADER_SIGNATURE));
470     // version made by
471     let version_made_by = (file.system as u16) << 8 | (file.version_made_by as u16);
472     try!(writer.write_u16::<LittleEndian>(version_made_by));
473     // version needed to extract
474     try!(writer.write_u16::<LittleEndian>(file.version_needed()));
475     // general puprose bit flag
476     let flag = if !file.file_name.is_ascii() { 1u16 << 11 } else { 0 };
477     try!(writer.write_u16::<LittleEndian>(flag));
478     // compression method
479     try!(writer.write_u16::<LittleEndian>(file.compression_method.to_u16()));
480     // last mod file time + date
481     let msdos_datetime = try!(file.last_modified_time.to_msdos());
482     try!(writer.write_u16::<LittleEndian>(msdos_datetime.timepart));
483     try!(writer.write_u16::<LittleEndian>(msdos_datetime.datepart));
484     // crc-32
485     try!(writer.write_u32::<LittleEndian>(file.crc32));
486     // compressed size
487     try!(writer.write_u32::<LittleEndian>(file.compressed_size as u32));
488     // uncompressed size
489     try!(writer.write_u32::<LittleEndian>(file.uncompressed_size as u32));
490     // file name length
491     try!(writer.write_u16::<LittleEndian>(file.file_name.as_bytes().len() as u16));
492     // extra field length
493     let extra_field = try!(build_extra_field(file));
494     try!(writer.write_u16::<LittleEndian>(extra_field.len() as u16));
495     // file comment length
496     try!(writer.write_u16::<LittleEndian>(0));
497     // disk number start
498     try!(writer.write_u16::<LittleEndian>(0));
499     // internal file attribytes
500     try!(writer.write_u16::<LittleEndian>(0));
501     // external file attributes
502     try!(writer.write_u32::<LittleEndian>(file.external_attributes));
503     // relative offset of local header
504     try!(writer.write_u32::<LittleEndian>(file.header_start as u32));
505     // file name
506     try!(writer.write_all(file.file_name.as_bytes()));
507     // extra field
508     try!(writer.write_all(&extra_field));
509     // file comment
510     // <none>
511 
512     Ok(())
513 }
514 
build_extra_field(_file: &ZipFileData) -> ZipResult<Vec<u8>>515 fn build_extra_field(_file: &ZipFileData) -> ZipResult<Vec<u8>>
516 {
517     let writer = Vec::new();
518     // Future work
519     Ok(writer)
520 }
521 
522 #[cfg(test)]
523 mod test {
524     use std::io;
525     use std::io::Write;
526     use time;
527     use super::{FileOptions, ZipWriter};
528     use compression::CompressionMethod;
529 
530     #[test]
write_empty_zip()531     fn write_empty_zip() {
532         let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()));
533         let result = writer.finish().unwrap();
534         assert_eq!(result.get_ref().len(), 28);
535         let v: Vec<u8> = vec![80, 75, 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 122, 105, 112, 45, 114, 115];
536         assert_eq!(result.get_ref(), &v);
537     }
538 
539     #[test]
write_mimetype_zip()540     fn write_mimetype_zip() {
541         let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()));
542         let mut mtime = time::empty_tm();
543         mtime.tm_year = 80;
544         mtime.tm_mday = 1;
545         let options = FileOptions {
546             compression_method: CompressionMethod::Stored,
547             last_modified_time: mtime,
548             permissions: Some(33188),
549         };
550         writer.start_file("mimetype", options).unwrap();
551         writer.write(b"application/vnd.oasis.opendocument.text").unwrap();
552         let result = writer.finish().unwrap();
553         assert_eq!(result.get_ref().len(), 159);
554         let mut v = Vec::new();
555         v.extend_from_slice(include_bytes!("../tests/data/mimetype.zip"));
556         assert_eq!(result.get_ref(), &v);
557     }
558 }
559