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