diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /third_party/rust/zip/src | |
parent | Initial commit. (diff) | |
download | firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/zip/src')
-rw-r--r-- | third_party/rust/zip/src/compression.rs | 114 | ||||
-rw-r--r-- | third_party/rust/zip/src/cp437.rs | 212 | ||||
-rw-r--r-- | third_party/rust/zip/src/crc32.rs | 128 | ||||
-rw-r--r-- | third_party/rust/zip/src/lib.rs | 24 | ||||
-rw-r--r-- | third_party/rust/zip/src/read.rs | 691 | ||||
-rw-r--r-- | third_party/rust/zip/src/result.rs | 94 | ||||
-rw-r--r-- | third_party/rust/zip/src/spec.rs | 190 | ||||
-rw-r--r-- | third_party/rust/zip/src/types.rs | 136 | ||||
-rw-r--r-- | third_party/rust/zip/src/write.rs | 558 |
9 files changed, 2147 insertions, 0 deletions
diff --git a/third_party/rust/zip/src/compression.rs b/third_party/rust/zip/src/compression.rs new file mode 100644 index 0000000000..79c41a8f46 --- /dev/null +++ b/third_party/rust/zip/src/compression.rs @@ -0,0 +1,114 @@ +//! Possible ZIP compression methods. + +use std::fmt; + +/// Compression methods for the contents of a ZIP file. +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum CompressionMethod +{ + /// The file is stored (no compression) + Stored, + /// The file is Deflated + #[cfg(feature = "flate2")] + Deflated, + /// File is compressed using BZIP2 algorithm + #[cfg(feature = "bzip2")] + Bzip2, + /// Unsupported compression method + Unsupported(u16), +} + +impl CompressionMethod { + /// Converts an u16 to its corresponding CompressionMethod + pub fn from_u16(val: u16) -> CompressionMethod { + match val { + 0 => CompressionMethod::Stored, + #[cfg(feature = "flate2")] + 8 => CompressionMethod::Deflated, + #[cfg(feature = "bzip2")] + 12 => CompressionMethod::Bzip2, + v => CompressionMethod::Unsupported(v), + } + } + + /// Converts a CompressionMethod to a u16 + pub fn to_u16(self) -> u16 { + match self { + CompressionMethod::Stored => 0, + #[cfg(feature = "flate2")] + CompressionMethod::Deflated => 8, + #[cfg(feature = "bzip2")] + CompressionMethod::Bzip2 => 12, + CompressionMethod::Unsupported(v) => v, + } + } +} + +impl fmt::Display for CompressionMethod { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // Just duplicate what the Debug format looks like, i.e, the enum key: + write!(f, "{:?}", self) + } +} + +#[cfg(test)] +mod test { + use super::CompressionMethod; + + #[test] + fn from_eq_to() { + for v in 0..(::std::u16::MAX as u32 + 1) + { + let from = CompressionMethod::from_u16(v as u16); + let to = from.to_u16() as u32; + assert_eq!(v, to); + } + } + + #[cfg(all(not(feature = "bzip2"), feature = "flate2"))] + fn methods() -> Vec<CompressionMethod> { + vec![CompressionMethod::Stored, CompressionMethod::Deflated] + } + + #[cfg(all(not(feature = "flate2"), feature = "bzip2"))] + fn methods() -> Vec<CompressionMethod> { + vec![CompressionMethod::Stored, CompressionMethod::Bzip2] + } + + #[cfg(all(feature = "bzip2", feature = "flate2"))] + fn methods() -> Vec<CompressionMethod> { + vec![CompressionMethod::Stored, CompressionMethod::Deflated, CompressionMethod::Bzip2] + } + + #[cfg(all(not(feature = "bzip2"), not(feature = "flate2")))] + fn methods() -> Vec<CompressionMethod> { + vec![CompressionMethod::Stored] + } + + #[test] + fn to_eq_from() { + fn check_match(method: CompressionMethod) { + let to = method.to_u16(); + let from = CompressionMethod::from_u16(to); + let back = from.to_u16(); + assert_eq!(to, back); + } + + for method in methods() { + check_match(method); + } + } + + #[test] + fn to_display_fmt() { + fn check_match(method: CompressionMethod) { + let debug_str = format!("{:?}", method); + let display_str = format!("{}", method); + assert_eq!(debug_str, display_str); + } + + for method in methods() { + check_match(method); + } + } +} diff --git a/third_party/rust/zip/src/cp437.rs b/third_party/rust/zip/src/cp437.rs new file mode 100644 index 0000000000..202dc8fbb8 --- /dev/null +++ b/third_party/rust/zip/src/cp437.rs @@ -0,0 +1,212 @@ +//! Convert a string in IBM codepage 437 to UTF-8 + +/// Trait to convert IBM codepage 437 to the target type +pub trait FromCp437 { + /// Target type + type Target; + + /// Function that does the conversion from cp437. + /// Gennerally allocations will be avoided if all data falls into the ASCII range. + fn from_cp437(self) -> Self::Target; +} + +impl<'a> FromCp437 for &'a [u8] { + type Target = ::std::borrow::Cow<'a, str>; + + fn from_cp437(self) -> Self::Target + { + if self.iter().all(|c| *c < 0x80) { + ::std::str::from_utf8(self).unwrap().into() + } + else { + self.iter().map(|c| to_char(*c)).collect::<String>().into() + } + } +} + +impl FromCp437 for Vec<u8> { + type Target = String; + + fn from_cp437(self) -> Self::Target { + if self.iter().all(|c| *c < 0x80) { + String::from_utf8(self).unwrap() + } + else { + self.into_iter().map(|c| to_char(c)).collect() + } + } +} + +fn to_char(input: u8) -> char +{ + let output = match input + { + 0x00 ... 0x7f => input as u32, + 0x80 => 0x00c7, + 0x81 => 0x00fc, + 0x82 => 0x00e9, + 0x83 => 0x00e2, + 0x84 => 0x00e4, + 0x85 => 0x00e0, + 0x86 => 0x00e5, + 0x87 => 0x00e7, + 0x88 => 0x00ea, + 0x89 => 0x00eb, + 0x8a => 0x00e8, + 0x8b => 0x00ef, + 0x8c => 0x00ee, + 0x8d => 0x00ec, + 0x8e => 0x00c4, + 0x8f => 0x00c5, + 0x90 => 0x00c9, + 0x91 => 0x00e6, + 0x92 => 0x00c6, + 0x93 => 0x00f4, + 0x94 => 0x00f6, + 0x95 => 0x00f2, + 0x96 => 0x00fb, + 0x97 => 0x00f9, + 0x98 => 0x00ff, + 0x99 => 0x00d6, + 0x9a => 0x00dc, + 0x9b => 0x00a2, + 0x9c => 0x00a3, + 0x9d => 0x00a5, + 0x9e => 0x20a7, + 0x9f => 0x0192, + 0xa0 => 0x00e1, + 0xa1 => 0x00ed, + 0xa2 => 0x00f3, + 0xa3 => 0x00fa, + 0xa4 => 0x00f1, + 0xa5 => 0x00d1, + 0xa6 => 0x00aa, + 0xa7 => 0x00ba, + 0xa8 => 0x00bf, + 0xa9 => 0x2310, + 0xaa => 0x00ac, + 0xab => 0x00bd, + 0xac => 0x00bc, + 0xad => 0x00a1, + 0xae => 0x00ab, + 0xaf => 0x00bb, + 0xb0 => 0x2591, + 0xb1 => 0x2592, + 0xb2 => 0x2593, + 0xb3 => 0x2502, + 0xb4 => 0x2524, + 0xb5 => 0x2561, + 0xb6 => 0x2562, + 0xb7 => 0x2556, + 0xb8 => 0x2555, + 0xb9 => 0x2563, + 0xba => 0x2551, + 0xbb => 0x2557, + 0xbc => 0x255d, + 0xbd => 0x255c, + 0xbe => 0x255b, + 0xbf => 0x2510, + 0xc0 => 0x2514, + 0xc1 => 0x2534, + 0xc2 => 0x252c, + 0xc3 => 0x251c, + 0xc4 => 0x2500, + 0xc5 => 0x253c, + 0xc6 => 0x255e, + 0xc7 => 0x255f, + 0xc8 => 0x255a, + 0xc9 => 0x2554, + 0xca => 0x2569, + 0xcb => 0x2566, + 0xcc => 0x2560, + 0xcd => 0x2550, + 0xce => 0x256c, + 0xcf => 0x2567, + 0xd0 => 0x2568, + 0xd1 => 0x2564, + 0xd2 => 0x2565, + 0xd3 => 0x2559, + 0xd4 => 0x2558, + 0xd5 => 0x2552, + 0xd6 => 0x2553, + 0xd7 => 0x256b, + 0xd8 => 0x256a, + 0xd9 => 0x2518, + 0xda => 0x250c, + 0xdb => 0x2588, + 0xdc => 0x2584, + 0xdd => 0x258c, + 0xde => 0x2590, + 0xdf => 0x2580, + 0xe0 => 0x03b1, + 0xe1 => 0x00df, + 0xe2 => 0x0393, + 0xe3 => 0x03c0, + 0xe4 => 0x03a3, + 0xe5 => 0x03c3, + 0xe6 => 0x00b5, + 0xe7 => 0x03c4, + 0xe8 => 0x03a6, + 0xe9 => 0x0398, + 0xea => 0x03a9, + 0xeb => 0x03b4, + 0xec => 0x221e, + 0xed => 0x03c6, + 0xee => 0x03b5, + 0xef => 0x2229, + 0xf0 => 0x2261, + 0xf1 => 0x00b1, + 0xf2 => 0x2265, + 0xf3 => 0x2264, + 0xf4 => 0x2320, + 0xf5 => 0x2321, + 0xf6 => 0x00f7, + 0xf7 => 0x2248, + 0xf8 => 0x00b0, + 0xf9 => 0x2219, + 0xfa => 0x00b7, + 0xfb => 0x221a, + 0xfc => 0x207f, + 0xfd => 0x00b2, + 0xfe => 0x25a0, + 0xff => 0x00a0, + _ => unreachable!(), + }; + ::std::char::from_u32(output).unwrap() +} + +#[cfg(test)] +mod test +{ + #[test] + fn to_char_valid() + { + for i in 0x00_u32 .. 0x100 + { + super::to_char(i as u8); + } + } + + #[test] + fn ascii() { + for i in 0x00 .. 0x80 { + assert_eq!(super::to_char(i), i as char); + } + } + + #[test] + fn example_slice() { + use super::FromCp437; + let data = b"Cura\x87ao"; + assert!(::std::str::from_utf8(data).is_err()); + assert_eq!(data.from_cp437(), "Curaçao"); + } + + #[test] + fn example_vec() { + use super::FromCp437; + let data = vec![0xCC, 0xCD, 0xCD, 0xB9]; + assert!(String::from_utf8(data.clone()).is_err()); + assert_eq!(&data.from_cp437(), "╠══╣"); + } +} diff --git a/third_party/rust/zip/src/crc32.rs b/third_party/rust/zip/src/crc32.rs new file mode 100644 index 0000000000..35e6b2c583 --- /dev/null +++ b/third_party/rust/zip/src/crc32.rs @@ -0,0 +1,128 @@ +//! Helper module to compute a CRC32 checksum + +use std::io; +use std::io::prelude::*; + +const CRC32_TABLE : [u32; 256] = [ + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, + 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, + 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, + 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, + 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, + 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, + 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, + 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, + 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, + 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, + 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, + 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, + 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, + 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, + 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, + 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, + 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, + 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, + 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, + 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, + 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, + 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, + 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, + 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, + 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, + 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, + 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, + 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, + 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, + 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d +]; + +/// Update the checksum prev based upon the contents of buf. +pub fn update(prev: u32, buf: &[u8]) -> u32 +{ + let mut crc = !prev; + + for &byte in buf.iter() + { + crc = CRC32_TABLE[((crc as u8) ^ byte) as usize] ^ (crc >> 8); + } + + !crc +} + +/// Reader that validates the CRC32 when it reaches the EOF. +pub struct Crc32Reader<R> +{ + inner: R, + crc: u32, + check: u32, +} + +impl<R> Crc32Reader<R> +{ + /// Get a new Crc32Reader which check the inner reader against checksum. + pub fn new(inner: R, checksum: u32) -> Crc32Reader<R> + { + Crc32Reader + { + inner: inner, + crc: 0, + check: checksum, + } + } + + fn check_matches(&self) -> bool + { + self.check == self.crc + } + + pub fn into_inner(self) -> R { + self.inner + } +} + +impl<R: Read> Read for Crc32Reader<R> +{ + fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> + { + let count = match self.inner.read(buf) + { + Ok(0) if !self.check_matches() => { return Err(io::Error::new(io::ErrorKind::Other, "Invalid checksum")) }, + Ok(n) => n, + Err(e) => return Err(e), + }; + self.crc = update(self.crc, &buf[0..count]); + Ok(count) + } +} + +#[cfg(test)] +mod test { + #[test] + fn samples() { + assert_eq!(super::update(0, b""), 0); + + // test vectors from the iPXE project (input and output are bitwise negated) + assert_eq!(super::update(!0x12345678, b""), !0x12345678); + assert_eq!(super::update(!0xffffffff, b"hello world"), !0xf2b5ee7a); + assert_eq!(super::update(!0xffffffff, b"hello"), !0xc9ef5979); + assert_eq!(super::update(!0xc9ef5979, b" world"), !0xf2b5ee7a); + + // Some vectors found on Rosetta code + assert_eq!(super::update(0, b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), 0x190A55AD); + assert_eq!(super::update(0, b"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"), 0xFF6CAB0B); + assert_eq!(super::update(0, b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"), 0x91267E8A); + } +} diff --git a/third_party/rust/zip/src/lib.rs b/third_party/rust/zip/src/lib.rs new file mode 100644 index 0000000000..f694dfc0db --- /dev/null +++ b/third_party/rust/zip/src/lib.rs @@ -0,0 +1,24 @@ +//! A basic ZipReader/Writer crate + +#![warn(missing_docs)] + +#[cfg(feature = "bzip2")] +extern crate bzip2; +#[cfg(feature = "flate2")] +extern crate flate2; +extern crate msdos_time; +extern crate podio; +extern crate time; + +pub use read::ZipArchive; +pub use write::ZipWriter; +pub use compression::CompressionMethod; + +mod spec; +mod crc32; +mod types; +pub mod read; +mod compression; +pub mod write; +mod cp437; +pub mod result; diff --git a/third_party/rust/zip/src/read.rs b/third_party/rust/zip/src/read.rs new file mode 100644 index 0000000000..85f76652c0 --- /dev/null +++ b/third_party/rust/zip/src/read.rs @@ -0,0 +1,691 @@ +//! 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::borrow::Cow; + +use podio::{ReadPodExt, LittleEndian}; +use types::{ZipFileData, System}; +use cp437::FromCp437; +use msdos_time::{TmMsDosExt, MsDosDateTime}; + +#[cfg(feature = "flate2")] +use flate2; +#[cfg(feature = "flate2")] +use flate2::read::DeflateDecoder; + +#[cfg(feature = "bzip2")] +use bzip2::read::BzDecoder; + +mod ffi { + pub const S_IFDIR: u32 = 0o0040000; + pub const S_IFREG: u32 = 0o0100000; +} + +const TM_1980_01_01 : ::time::Tm = ::time::Tm { + tm_sec: 0, + tm_min: 0, + tm_hour: 0, + tm_mday: 1, + tm_mon: 0, + tm_year: 80, + tm_wday: 2, + tm_yday: 0, + tm_isdst: -1, + tm_utcoff: 0, + tm_nsec: 0 +}; + +/// Wrapper for reading the contents of a ZIP file. +/// +/// ``` +/// fn doit() -> zip::result::ZipResult<()> +/// { +/// use std::io::prelude::*; +/// +/// // For demonstration purposes we read from an empty buffer. +/// // Normally a File object would be used. +/// let buf: &[u8] = &[0u8; 128]; +/// let mut reader = std::io::Cursor::new(buf); +/// +/// let mut zip = try!(zip::ZipArchive::new(reader)); +/// +/// for i in 0..zip.len() +/// { +/// let mut file = zip.by_index(i).unwrap(); +/// println!("Filename: {}", file.name()); +/// let first_byte = try!(file.bytes().next().unwrap()); +/// println!("{}", first_byte); +/// } +/// Ok(()) +/// } +/// +/// println!("Result: {:?}", doit()); +/// ``` +#[derive(Debug)] +pub struct ZipArchive<R: Read + io::Seek> +{ + reader: R, + files: Vec<ZipFileData>, + names_map: HashMap<String, usize>, + offset: u64, + comment: Vec<u8>, +} + +enum ZipFileReader<'a> { + NoReader, + Stored(Crc32Reader<io::Take<&'a mut Read>>), + #[cfg(feature = "flate2")] + Deflated(Crc32Reader<flate2::read::DeflateDecoder<io::Take<&'a mut Read>>>), + #[cfg(feature = "bzip2")] + Bzip2(Crc32Reader<BzDecoder<io::Take<&'a mut Read>>>), +} + +/// A struct for reading a zip file +pub struct ZipFile<'a> { + data: Cow<'a, ZipFileData>, + reader: ZipFileReader<'a>, +} + +fn unsupported_zip_error<T>(detail: &'static str) -> ZipResult<T> +{ + Err(ZipError::UnsupportedArchive(detail)) +} + + +fn make_reader<'a>( + compression_method: ::compression::CompressionMethod, + crc32: u32, + reader: io::Take<&'a mut io::Read>) + -> ZipResult<ZipFileReader<'a>> { + + match compression_method { + CompressionMethod::Stored => + { + Ok(ZipFileReader::Stored(Crc32Reader::new( + reader, + crc32))) + }, + #[cfg(feature = "flate2")] + CompressionMethod::Deflated => + { + let deflate_reader = DeflateDecoder::new(reader); + Ok(ZipFileReader::Deflated(Crc32Reader::new( + deflate_reader, + crc32))) + }, + #[cfg(feature = "bzip2")] + CompressionMethod::Bzip2 => + { + let bzip2_reader = BzDecoder::new(reader); + Ok(ZipFileReader::Bzip2(Crc32Reader::new( + bzip2_reader, + crc32))) + }, + _ => unsupported_zip_error("Compression method not supported"), + } +} + +impl<R: Read+io::Seek> ZipArchive<R> +{ + /// Get the directory start offset and number of files. This is done in a + /// separate function to ease the control flow design. + fn get_directory_counts(reader: &mut R, + footer: &spec::CentralDirectoryEnd, + cde_start_pos: u64) -> ZipResult<(u64, u64, usize)> { + // See if there's a ZIP64 footer. The ZIP64 locator if present will + // have its signature 20 bytes in front of the standard footer. The + // standard footer, in turn, is 22+N bytes large, where N is the + // comment length. Therefore: + let zip64locator = if reader.seek(io::SeekFrom::End(-(20 + 22 + footer.zip_file_comment.len() as i64))).is_ok() { + match spec::Zip64CentralDirectoryEndLocator::parse(reader) { + Ok(loc) => Some(loc), + Err(ZipError::InvalidArchive(_)) => { + // No ZIP64 header; that's actually fine. We're done here. + None + }, + Err(e) => { + // Yikes, a real problem + return Err(e); + } + } + } + else { + // Empty Zip files will have nothing else so this error might be fine. If + // not, we'll find out soon. + None + }; + + match zip64locator { + None => { + // Some zip files have data prepended to them, resulting in the + // offsets all being too small. Get the amount of error by comparing + // the actual file position we found the CDE at with the offset + // recorded in the CDE. + let archive_offset = cde_start_pos.checked_sub(footer.central_directory_size as u64) + .and_then(|x| x.checked_sub(footer.central_directory_offset as u64)) + .ok_or(ZipError::InvalidArchive("Invalid central directory size or offset"))?; + + let directory_start = footer.central_directory_offset as u64 + archive_offset; + let number_of_files = footer.number_of_files_on_this_disk as usize; + return Ok((archive_offset, directory_start, number_of_files)); + }, + Some(locator64) => { + // If we got here, this is indeed a ZIP64 file. + + if footer.disk_number as u32 != locator64.disk_with_central_directory { + return unsupported_zip_error("Support for multi-disk files is not implemented") + } + + // We need to reassess `archive_offset`. We know where the ZIP64 + // central-directory-end structure *should* be, but unfortunately we + // don't know how to precisely relate that location to our current + // actual offset in the file, since there may be junk at its + // beginning. Therefore we need to perform another search, as in + // read::CentralDirectoryEnd::find_and_parse, except now we search + // forward. + + let search_upper_bound = cde_start_pos + .checked_sub(60) // minimum size of Zip64CentralDirectoryEnd + Zip64CentralDirectoryEndLocator + .ok_or(ZipError::InvalidArchive("File cannot contain ZIP64 central directory end"))?; + let (footer, archive_offset) = spec::Zip64CentralDirectoryEnd::find_and_parse( + reader, + locator64.end_of_central_directory_offset, + search_upper_bound)?; + + 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 + archive_offset; + Ok((archive_offset, directory_start, footer.number_of_files as usize)) + }, + } + } + + /// Opens a Zip archive and parses the central directory + pub fn new(mut reader: R) -> ZipResult<ZipArchive<R>> { + let (footer, cde_start_pos) = 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 (archive_offset, directory_start, number_of_files) = + try!(Self::get_directory_counts(&mut reader, &footer, cde_start_pos)); + + let mut files = Vec::new(); + let mut names_map = HashMap::new(); + + if let Err(_) = reader.seek(io::SeekFrom::Start(directory_start)) { + return Err(ZipError::InvalidArchive("Could not seek to start of central directory")); + } + + for _ in 0 .. number_of_files + { + let file = try!(central_header_to_zip_file(&mut reader, archive_offset)); + names_map.insert(file.file_name.clone(), files.len()); + files.push(file); + } + + Ok(ZipArchive { + reader: reader, + files: files, + names_map: names_map, + offset: archive_offset, + comment: footer.zip_file_comment, + }) + } + + /// Number of files contained in this zip. + /// + /// ``` + /// fn iter() { + /// let mut zip = zip::ZipArchive::new(std::io::Cursor::new(vec![])).unwrap(); + /// + /// for i in 0..zip.len() { + /// let mut file = zip.by_index(i).unwrap(); + /// // Do something with file i + /// } + /// } + /// ``` + pub fn len(&self) -> usize + { + self.files.len() + } + + /// Get the offset from the beginning of the underlying reader that this zip begins at, in bytes. + /// + /// Normally this value is zero, but if the zip has arbitrary data prepended to it, then this value will be the size + /// of that prepended data. + pub fn offset(&self) -> u64 { + self.offset + } + + /// Search for a file entry by name + pub fn by_name<'a>(&'a mut self, name: &str) -> ZipResult<ZipFile<'a>> + { + 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<ZipFile<'a>> + { + if file_number >= self.files.len() { return Err(ZipError::FileNotFound); } + let ref data = self.files[file_number]; + let pos = data.data_start; + + if data.encrypted + { + return unsupported_zip_error("Encrypted files are not supported") + } + + try!(self.reader.seek(io::SeekFrom::Start(pos))); + let limit_reader = (self.reader.by_ref() as &mut Read).take(data.compressed_size); + + Ok(ZipFile { reader: try!(make_reader(data.compression_method, data.crc32, limit_reader)), data: Cow::Borrowed(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<R: Read+io::Seek>(reader: &mut R, archive_offset: u64) -> ZipResult<ZipFileData> +{ + // Parse central header + let signature = try!(reader.read_u32::<LittleEndian>()); + if signature != spec::CENTRAL_DIRECTORY_HEADER_SIGNATURE + { + return Err(ZipError::InvalidArchive("Invalid Central Directory header")) + } + + let version_made_by = try!(reader.read_u16::<LittleEndian>()); + let _version_to_extract = try!(reader.read_u16::<LittleEndian>()); + let flags = try!(reader.read_u16::<LittleEndian>()); + let encrypted = flags & 1 == 1; + let is_utf8 = flags & (1 << 11) != 0; + let compression_method = try!(reader.read_u16::<LittleEndian>()); + let last_mod_time = try!(reader.read_u16::<LittleEndian>()); + let last_mod_date = try!(reader.read_u16::<LittleEndian>()); + let crc32 = try!(reader.read_u32::<LittleEndian>()); + let compressed_size = try!(reader.read_u32::<LittleEndian>()); + let uncompressed_size = try!(reader.read_u32::<LittleEndian>()); + let file_name_length = try!(reader.read_u16::<LittleEndian>()) as usize; + let extra_field_length = try!(reader.read_u16::<LittleEndian>()) as usize; + let file_comment_length = try!(reader.read_u16::<LittleEndian>()) as usize; + let _disk_number = try!(reader.read_u16::<LittleEndian>()); + let _internal_file_attributes = try!(reader.read_u16::<LittleEndian>()); + let external_file_attributes = try!(reader.read_u32::<LittleEndian>()); + let mut offset = try!(reader.read_u32::<LittleEndian>()) as u64; + let file_name_raw = try!(ReadPodExt::read_exact(reader, file_name_length)); + let extra_field = try!(ReadPodExt::read_exact(reader, extra_field_length)); + let file_comment_raw = try!(ReadPodExt::read_exact(reader, file_comment_length)); + + // Account for shifted zip offsets. + offset += archive_offset; + + let file_name = match is_utf8 + { + true => String::from_utf8_lossy(&*file_name_raw).into_owned(), + false => file_name_raw.clone().from_cp437(), + }; + let file_comment = match is_utf8 + { + true => String::from_utf8_lossy(&*file_comment_raw).into_owned(), + false => file_comment_raw.from_cp437(), + }; + + // Construct the result + let mut result = ZipFileData + { + system: System::from_u8((version_made_by >> 8) as u8), + version_made_by: version_made_by as u8, + encrypted: encrypted, + compression_method: CompressionMethod::from_u16(compression_method), + last_modified_time: ::time::Tm::from_msdos(MsDosDateTime::new(last_mod_time, last_mod_date)).unwrap_or(TM_1980_01_01), + crc32: crc32, + compressed_size: compressed_size as u64, + uncompressed_size: uncompressed_size as u64, + file_name: file_name, + file_name_raw: file_name_raw, + file_comment: file_comment, + header_start: offset, + data_start: 0, + external_attributes: external_file_attributes, + }; + + match parse_extra_field(&mut result, &*extra_field) { + Ok(..) | Err(ZipError::Io(..)) => {}, + Err(e) => try!(Err(e)), + } + + // Remember end of central header + let return_position = try!(reader.seek(io::SeekFrom::Current(0))); + + // Parse local header + try!(reader.seek(io::SeekFrom::Start(result.header_start))); + let signature = try!(reader.read_u32::<LittleEndian>()); + if signature != spec::LOCAL_FILE_HEADER_SIGNATURE + { + return Err(ZipError::InvalidArchive("Invalid local file header")) + } + + try!(reader.seek(io::SeekFrom::Current(22))); + let file_name_length = try!(reader.read_u16::<LittleEndian>()) as u64; + let extra_field_length = try!(reader.read_u16::<LittleEndian>()) as u64; + let magic_and_header = 4 + 22 + 2 + 2; + result.data_start = result.header_start + magic_and_header + file_name_length + extra_field_length; + + // Go back after the central header + try!(reader.seek(io::SeekFrom::Start(return_position))); + + Ok(result) +} + +fn parse_extra_field(file: &mut ZipFileData, data: &[u8]) -> ZipResult<()> +{ + let mut reader = io::Cursor::new(data); + + while (reader.position() as usize) < data.len() + { + let kind = try!(reader.read_u16::<LittleEndian>()); + let len = try!(reader.read_u16::<LittleEndian>()); + match kind + { + // Zip64 extended information extra field + 0x0001 => { + file.uncompressed_size = try!(reader.read_u64::<LittleEndian>()); + file.compressed_size = try!(reader.read_u64::<LittleEndian>()); + try!(reader.read_u64::<LittleEndian>()); // relative header offset + try!(reader.read_u32::<LittleEndian>()); // disk start number + }, + _ => { try!(reader.seek(io::SeekFrom::Current(len as i64))); }, + }; + } + Ok(()) +} + +fn get_reader<'a>(reader: &'a mut ZipFileReader) -> &'a mut Read { + match *reader { + ZipFileReader::NoReader => panic!("ZipFileReader was in an invalid state"), + ZipFileReader::Stored(ref mut r) => r as &mut Read, + #[cfg(feature = "flate2")] + ZipFileReader::Deflated(ref mut r) => r as &mut Read, + #[cfg(feature = "bzip2")] + ZipFileReader::Bzip2(ref mut r) => r as &mut Read, + } +} + +/// Methods for retreiving information on zip files +impl<'a> ZipFile<'a> { + fn get_reader(&mut self) -> &mut Read { + get_reader(&mut self.reader) + } + /// Get the version of the file + pub fn version_made_by(&self) -> (u8, u8) { + (self.data.version_made_by / 10, self.data.version_made_by % 10) + } + /// Get the name of the file + pub fn name(&self) -> &str { + &*self.data.file_name + } + /// Get the name of the file, in the raw (internal) byte representation. + pub fn name_raw(&self) -> &[u8] { + &*self.data.file_name_raw + } + /// Get the name of the file in a sanitized form. It truncates the name to the first NULL byte, + /// removes a leading '/' and removes '..' parts. + pub fn sanitized_name(&self) -> ::std::path::PathBuf { + self.data.file_name_sanitized() + } + /// Get the comment of the file + pub fn comment(&self) -> &str { + &*self.data.file_comment + } + /// Get the compression method used to store the file + pub fn compression(&self) -> CompressionMethod { + self.data.compression_method + } + /// Get the size of the file in the archive + pub fn compressed_size(&self) -> u64 { + self.data.compressed_size + } + /// Get the size of the file when uncompressed + pub fn size(&self) -> u64 { + self.data.uncompressed_size + } + /// Get the time the file was last modified + pub fn last_modified(&self) -> ::time::Tm { + self.data.last_modified_time + } + /// Get unix mode for the file + pub fn unix_mode(&self) -> Option<u32> { + if self.data.external_attributes == 0 { + return None; + } + + match self.data.system { + System::Unix => { + Some(self.data.external_attributes >> 16) + }, + System::Dos => { + // Interpret MSDOS directory bit + let mut mode = if 0x10 == (self.data.external_attributes & 0x10) { + ffi::S_IFDIR | 0o0775 + } else { + ffi::S_IFREG | 0o0664 + }; + if 0x01 == (self.data.external_attributes & 0x01) { + // Read-only bit; strip write permissions + mode &= 0o0555; + } + Some(mode) + }, + _ => None, + } + } + /// Get the CRC32 hash of the original file + pub fn crc32(&self) -> u32 { + self.data.crc32 + } + + /// Get the starting offset of the data of the compressed file + pub fn data_start(&self) -> u64 { + self.data.data_start + } +} + +impl<'a> Read for ZipFile<'a> { + fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { + self.get_reader().read(buf) + } +} + +impl<'a> Drop for ZipFile<'a> { + fn drop(&mut self) { + // self.data is Owned, this reader is constructed by a streaming reader. + // In this case, we want to exhaust the reader so that the next file is accessible. + if let Cow::Owned(_) = self.data { + let mut buffer = [0; 1<<16]; + + // Get the inner `Take` reader so all decompression and CRC calculation is skipped. + let innerreader = ::std::mem::replace(&mut self.reader, ZipFileReader::NoReader); + let mut reader = match innerreader { + ZipFileReader::NoReader => panic!("ZipFileReader was in an invalid state"), + ZipFileReader::Stored(crcreader) => crcreader.into_inner(), + #[cfg(feature = "flate2")] + ZipFileReader::Deflated(crcreader) => crcreader.into_inner().into_inner(), + #[cfg(feature = "bzip2")] + ZipFileReader::Bzip2(crcreader) => crcreader.into_inner().into_inner(), + }; + + loop { + match reader.read(&mut buffer) { + Ok(0) => break, + Ok(_) => (), + Err(e) => panic!("Could not consume all of the output of the current ZipFile: {:?}", e), + } + } + } + } +} + +/// Read ZipFile structures from a non-seekable reader. +/// +/// This is an alternative method to read a zip file. If possible, use the ZipArchive functions +/// as some information will be missing when reading this manner. +/// +/// Reads a file header from the start of the stream. Will return `Ok(Some(..))` if a file is +/// present at the start of the stream. Returns `Ok(None)` if the start of the central directory +/// is encountered. No more files should be read after this. +/// +/// The Drop implementation of ZipFile ensures that the reader will be correctly positioned after +/// the structure is done. +/// +/// Missing fields are: +/// * `comment`: set to an empty string +/// * `data_start`: set to 0 +/// * `external_attributes`: `unix_mode()`: will return None +pub fn read_zipfile_from_stream<'a, R: io::Read>(reader: &'a mut R) -> ZipResult<Option<ZipFile>> { + let signature = try!(reader.read_u32::<LittleEndian>()); + + match signature { + spec::LOCAL_FILE_HEADER_SIGNATURE => (), + spec::CENTRAL_DIRECTORY_HEADER_SIGNATURE => return Ok(None), + _ => return Err(ZipError::InvalidArchive("Invalid local file header")), + } + + let version_made_by = try!(reader.read_u16::<LittleEndian>()); + let flags = try!(reader.read_u16::<LittleEndian>()); + let encrypted = flags & 1 == 1; + let is_utf8 = flags & (1 << 11) != 0; + let using_data_descriptor = flags & (1 << 3) != 0; + let compression_method = CompressionMethod::from_u16(try!(reader.read_u16::<LittleEndian>())); + let last_mod_time = try!(reader.read_u16::<LittleEndian>()); + let last_mod_date = try!(reader.read_u16::<LittleEndian>()); + let crc32 = try!(reader.read_u32::<LittleEndian>()); + let compressed_size = try!(reader.read_u32::<LittleEndian>()); + let uncompressed_size = try!(reader.read_u32::<LittleEndian>()); + let file_name_length = try!(reader.read_u16::<LittleEndian>()) as usize; + let extra_field_length = try!(reader.read_u16::<LittleEndian>()) as usize; + + let file_name_raw = try!(ReadPodExt::read_exact(reader, file_name_length)); + let extra_field = try!(ReadPodExt::read_exact(reader, extra_field_length)); + + let file_name = match is_utf8 + { + true => String::from_utf8_lossy(&*file_name_raw).into_owned(), + false => file_name_raw.clone().from_cp437(), + }; + + let mut result = ZipFileData + { + system: System::from_u8((version_made_by >> 8) as u8), + version_made_by: version_made_by as u8, + encrypted: encrypted, + compression_method: compression_method, + last_modified_time: ::time::Tm::from_msdos(MsDosDateTime::new(last_mod_time, last_mod_date)).unwrap_or(TM_1980_01_01), + crc32: crc32, + compressed_size: compressed_size as u64, + uncompressed_size: uncompressed_size as u64, + file_name: file_name, + file_name_raw: file_name_raw, + file_comment: String::new(), // file comment is only available in the central directory + // header_start and data start are not available, but also don't matter, since seeking is + // not available. + header_start: 0, + data_start: 0, + // The external_attributes field is only available in the central directory. + // We set this to zero, which should be valid as the docs state 'If input came + // from standard input, this field is set to zero.' + external_attributes: 0, + }; + + match parse_extra_field(&mut result, &extra_field) { + Ok(..) | Err(ZipError::Io(..)) => {}, + Err(e) => try!(Err(e)), + } + + if encrypted { + return unsupported_zip_error("Encrypted files are not supported") + } + if using_data_descriptor { + return unsupported_zip_error("The file length is not available in the local header"); + } + + let limit_reader = (reader as &'a mut io::Read).take(result.compressed_size as u64); + + let result_crc32 = result.crc32; + let result_compression_method = result.compression_method; + Ok(Some(ZipFile { + data: Cow::Owned(result), + reader: try!(make_reader(result_compression_method, result_crc32, limit_reader)) + })) +} + +#[cfg(test)] +mod test { + #[test] + fn invalid_offset() { + use std::io; + use super::ZipArchive; + + let mut v = Vec::new(); + v.extend_from_slice(include_bytes!("../tests/data/invalid_offset.zip")); + let reader = ZipArchive::new(io::Cursor::new(v)); + assert!(reader.is_err()); + } + + #[test] + fn zip64_with_leading_junk() { + use std::io; + use super::ZipArchive; + + let mut v = Vec::new(); + v.extend_from_slice(include_bytes!("../tests/data/zip64_demo.zip")); + let reader = ZipArchive::new(io::Cursor::new(v)).unwrap(); + assert!(reader.len() == 1); + } + + #[test] + fn zip_comment() { + use std::io; + use super::ZipArchive; + + let mut v = Vec::new(); + v.extend_from_slice(include_bytes!("../tests/data/mimetype.zip")); + let reader = ZipArchive::new(io::Cursor::new(v)).unwrap(); + assert!(reader.comment == b"zip-rs"); + } + + #[test] + fn zip_read_streaming() { + use std::io; + use super::read_zipfile_from_stream; + + let mut v = Vec::new(); + v.extend_from_slice(include_bytes!("../tests/data/mimetype.zip")); + let mut reader = io::Cursor::new(v); + loop { + match read_zipfile_from_stream(&mut reader).unwrap() { + None => break, + _ => (), + } + } + } +} diff --git a/third_party/rust/zip/src/result.rs b/third_party/rust/zip/src/result.rs new file mode 100644 index 0000000000..d82259ea3d --- /dev/null +++ b/third_party/rust/zip/src/result.rs @@ -0,0 +1,94 @@ +//! Error types that can be emitted from this library + +use std::convert; +use std::error; +use std::fmt; +use std::io; + +/// Generic result type with ZipError as its error variant +pub type ZipResult<T> = Result<T, ZipError>; + +/// Error type for Zip +#[derive(Debug)] +pub enum ZipError +{ + /// An Error caused by I/O + Io(io::Error), + + /// This file is probably not a zip archive + InvalidArchive(&'static str), + + /// This archive is not supported + UnsupportedArchive(&'static str), + + /// The requested file could not be found in the archive + FileNotFound, +} + +impl ZipError +{ + fn detail(&self) -> ::std::borrow::Cow<str> + { + use std::error::Error; + + match *self + { + ZipError::Io(ref io_err) => { + ("Io Error: ".to_string() + (io_err as &error::Error).description()).into() + }, + ZipError::InvalidArchive(msg) | ZipError::UnsupportedArchive(msg) => { + (self.description().to_string() + ": " + msg).into() + }, + ZipError::FileNotFound => { + self.description().into() + }, + } + } +} + +impl convert::From<io::Error> for ZipError +{ + fn from(err: io::Error) -> ZipError + { + ZipError::Io(err) + } +} + +impl convert::From<ZipError> for io::Error +{ + fn from(err: ZipError) -> io::Error + { + io::Error::new(io::ErrorKind::Other, err) + } +} + +impl fmt::Display for ZipError +{ + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> + { + fmt.write_str(&*self.detail()) + } +} + +impl error::Error for ZipError +{ + fn description(&self) -> &str + { + match *self + { + ZipError::Io(ref io_err) => (io_err as &error::Error).description(), + ZipError::InvalidArchive(..) => "Invalid Zip archive", + ZipError::UnsupportedArchive(..) => "Unsupported Zip archive", + ZipError::FileNotFound => "Specified file not found in archive", + } + } + + fn cause(&self) -> Option<&error::Error> + { + match *self + { + ZipError::Io(ref io_err) => Some(io_err as &error::Error), + _ => None, + } + } +} diff --git a/third_party/rust/zip/src/spec.rs b/third_party/rust/zip/src/spec.rs new file mode 100644 index 0000000000..2f5466f744 --- /dev/null +++ b/third_party/rust/zip/src/spec.rs @@ -0,0 +1,190 @@ +use std::io; +use std::io::prelude::*; +use result::{ZipResult, ZipError}; +use podio::{ReadPodExt, WritePodExt, LittleEndian}; + +pub const LOCAL_FILE_HEADER_SIGNATURE : u32 = 0x04034b50; +pub const CENTRAL_DIRECTORY_HEADER_SIGNATURE : u32 = 0x02014b50; +const CENTRAL_DIRECTORY_END_SIGNATURE : u32 = 0x06054b50; +pub const ZIP64_CENTRAL_DIRECTORY_END_SIGNATURE : u32 = 0x06064b50; +const ZIP64_CENTRAL_DIRECTORY_END_LOCATOR_SIGNATURE : u32 = 0x07064b50; + +pub struct CentralDirectoryEnd +{ + pub disk_number: u16, + pub disk_with_central_directory: u16, + pub number_of_files_on_this_disk: u16, + pub number_of_files: u16, + pub central_directory_size: u32, + pub central_directory_offset: u32, + pub zip_file_comment: Vec<u8>, +} + +impl CentralDirectoryEnd +{ + pub fn parse<T: Read>(reader: &mut T) -> ZipResult<CentralDirectoryEnd> + { + let magic = try!(reader.read_u32::<LittleEndian>()); + if magic != CENTRAL_DIRECTORY_END_SIGNATURE + { + return Err(ZipError::InvalidArchive("Invalid digital signature header")) + } + let disk_number = try!(reader.read_u16::<LittleEndian>()); + let disk_with_central_directory = try!(reader.read_u16::<LittleEndian>()); + let number_of_files_on_this_disk = try!(reader.read_u16::<LittleEndian>()); + let number_of_files = try!(reader.read_u16::<LittleEndian>()); + let central_directory_size = try!(reader.read_u32::<LittleEndian>()); + let central_directory_offset = try!(reader.read_u32::<LittleEndian>()); + let zip_file_comment_length = try!(reader.read_u16::<LittleEndian>()) as usize; + let zip_file_comment = try!(ReadPodExt::read_exact(reader, zip_file_comment_length)); + + Ok(CentralDirectoryEnd + { + disk_number: disk_number, + disk_with_central_directory: disk_with_central_directory, + number_of_files_on_this_disk: number_of_files_on_this_disk, + number_of_files: number_of_files, + central_directory_size: central_directory_size, + central_directory_offset: central_directory_offset, + zip_file_comment: zip_file_comment, + }) + } + + pub fn find_and_parse<T: Read+io::Seek>(reader: &mut T) -> ZipResult<(CentralDirectoryEnd, u64)> + { + const HEADER_SIZE: u64 = 22; + const BYTES_BETWEEN_MAGIC_AND_COMMENT_SIZE: u64 = HEADER_SIZE - 6; + let file_length = try!(reader.seek(io::SeekFrom::End(0))); + + let search_upper_bound = file_length.checked_sub(HEADER_SIZE + ::std::u16::MAX as u64).unwrap_or(0); + + if file_length < HEADER_SIZE { + return Err(ZipError::InvalidArchive("Invalid zip header")); + } + + let mut pos = file_length - HEADER_SIZE; + while pos >= search_upper_bound + { + try!(reader.seek(io::SeekFrom::Start(pos as u64))); + if try!(reader.read_u32::<LittleEndian>()) == CENTRAL_DIRECTORY_END_SIGNATURE + { + try!(reader.seek(io::SeekFrom::Current(BYTES_BETWEEN_MAGIC_AND_COMMENT_SIZE as i64))); + let comment_length = try!(reader.read_u16::<LittleEndian>()) as u64; + if file_length - pos - HEADER_SIZE == comment_length + { + let cde_start_pos = try!(reader.seek(io::SeekFrom::Start(pos as u64))); + return CentralDirectoryEnd::parse(reader).map(|cde| (cde, cde_start_pos)); + } + } + pos = match pos.checked_sub(1) { + Some(p) => p, + None => break, + }; + } + Err(ZipError::InvalidArchive("Could not find central directory end")) + } + + pub fn write<T: Write>(&self, writer: &mut T) -> ZipResult<()> + { + try!(writer.write_u32::<LittleEndian>(CENTRAL_DIRECTORY_END_SIGNATURE)); + try!(writer.write_u16::<LittleEndian>(self.disk_number)); + try!(writer.write_u16::<LittleEndian>(self.disk_with_central_directory)); + try!(writer.write_u16::<LittleEndian>(self.number_of_files_on_this_disk)); + try!(writer.write_u16::<LittleEndian>(self.number_of_files)); + try!(writer.write_u32::<LittleEndian>(self.central_directory_size)); + try!(writer.write_u32::<LittleEndian>(self.central_directory_offset)); + try!(writer.write_u16::<LittleEndian>(self.zip_file_comment.len() as u16)); + try!(writer.write_all(&self.zip_file_comment)); + Ok(()) + } +} + +pub struct Zip64CentralDirectoryEndLocator +{ + pub disk_with_central_directory: u32, + pub end_of_central_directory_offset: u64, + pub number_of_disks: u32, +} + +impl Zip64CentralDirectoryEndLocator +{ + pub fn parse<T: Read>(reader: &mut T) -> ZipResult<Zip64CentralDirectoryEndLocator> + { + let magic = try!(reader.read_u32::<LittleEndian>()); + if magic != ZIP64_CENTRAL_DIRECTORY_END_LOCATOR_SIGNATURE + { + return Err(ZipError::InvalidArchive("Invalid zip64 locator digital signature header")) + } + let disk_with_central_directory = try!(reader.read_u32::<LittleEndian>()); + let end_of_central_directory_offset = try!(reader.read_u64::<LittleEndian>()); + let number_of_disks = try!(reader.read_u32::<LittleEndian>()); + + Ok(Zip64CentralDirectoryEndLocator + { + disk_with_central_directory: disk_with_central_directory, + end_of_central_directory_offset: end_of_central_directory_offset, + number_of_disks: number_of_disks, + }) + } +} + +pub struct Zip64CentralDirectoryEnd +{ + pub version_made_by: u16, + pub version_needed_to_extract: u16, + pub disk_number: u32, + pub disk_with_central_directory: u32, + pub number_of_files_on_this_disk: u64, + pub number_of_files: u64, + pub central_directory_size: u64, + pub central_directory_offset: u64, + //pub extensible_data_sector: Vec<u8>, <-- We don't do anything with this at the moment. +} + +impl Zip64CentralDirectoryEnd +{ + pub fn find_and_parse<T: Read+io::Seek>(reader: &mut T, + nominal_offset: u64, + search_upper_bound: u64) -> ZipResult<(Zip64CentralDirectoryEnd, u64)> + { + let mut pos = nominal_offset; + + while pos <= search_upper_bound + { + reader.seek(io::SeekFrom::Start(pos))?; + + if reader.read_u32::<LittleEndian>()? == ZIP64_CENTRAL_DIRECTORY_END_SIGNATURE + { + let archive_offset = pos - nominal_offset; + + let _record_size = try!(reader.read_u64::<LittleEndian>()); + // We would use this value if we did anything with the "zip64 extensible data sector". + + let version_made_by = try!(reader.read_u16::<LittleEndian>()); + let version_needed_to_extract = try!(reader.read_u16::<LittleEndian>()); + let disk_number = try!(reader.read_u32::<LittleEndian>()); + let disk_with_central_directory = try!(reader.read_u32::<LittleEndian>()); + let number_of_files_on_this_disk = try!(reader.read_u64::<LittleEndian>()); + let number_of_files = try!(reader.read_u64::<LittleEndian>()); + let central_directory_size = try!(reader.read_u64::<LittleEndian>()); + let central_directory_offset = try!(reader.read_u64::<LittleEndian>()); + + return Ok((Zip64CentralDirectoryEnd + { + version_made_by: version_made_by, + version_needed_to_extract: version_needed_to_extract, + disk_number: disk_number, + disk_with_central_directory: disk_with_central_directory, + number_of_files_on_this_disk: number_of_files_on_this_disk, + number_of_files: number_of_files, + central_directory_size: central_directory_size, + central_directory_offset: central_directory_offset, + }, archive_offset)); + } + + pos += 1; + } + + Err(ZipError::InvalidArchive("Could not find ZIP64 central directory end")) + } +} diff --git a/third_party/rust/zip/src/types.rs b/third_party/rust/zip/src/types.rs new file mode 100644 index 0000000000..a8609b3b20 --- /dev/null +++ b/third_party/rust/zip/src/types.rs @@ -0,0 +1,136 @@ +//! Types that specify what is contained in a ZIP. + +use time; + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum System +{ + Dos = 0, + Unix = 3, + Unknown, + #[doc(hidden)] + __Nonexhaustive, +} + +impl System { + pub fn from_u8(system: u8) -> System + { + use self::System::*; + + match system { + 0 => Dos, + 3 => Unix, + _ => Unknown, + } + } +} + +pub const DEFAULT_VERSION: u8 = 46; + +/// Structure representing a ZIP file. +#[derive(Debug, Clone)] +pub struct ZipFileData +{ + /// Compatibility of the file attribute information + pub system: System, + /// Specification version + pub version_made_by: u8, + /// True if the file is encrypted. + pub encrypted: bool, + /// Compression method used to store the file + pub compression_method: ::compression::CompressionMethod, + /// Last modified time. This will only have a 2 second precision. + pub last_modified_time: time::Tm, + /// CRC32 checksum + pub crc32: u32, + /// Size of the file in the ZIP + pub compressed_size: u64, + /// Size of the file when extracted + pub uncompressed_size: u64, + /// Name of the file + pub file_name: String, + /// Raw file name. To be used when file_name was incorrectly decoded. + pub file_name_raw: Vec<u8>, + /// File comment + pub file_comment: String, + /// Specifies where the local header of the file starts + pub header_start: u64, + /// Specifies where the compressed data of the file starts + pub data_start: u64, + /// External file attributes + pub external_attributes: u32, +} + +impl ZipFileData { + pub fn file_name_sanitized(&self) -> ::std::path::PathBuf { + let no_null_filename = match self.file_name.find('\0') { + Some(index) => &self.file_name[0..index], + None => &self.file_name, + }.to_string(); + + // zip files can contain both / and \ as separators regardless of the OS + // and as we want to return a sanitized PathBuf that only supports the + // OS separator let's convert incompatible separators to compatible ones + let separator = ::std::path::MAIN_SEPARATOR; + let opposite_separator = match separator { + '/' => '\\', + '\\' | _ => '/', + }; + let filename = + no_null_filename.replace(&opposite_separator.to_string(), &separator.to_string()); + + ::std::path::Path::new(&filename) + .components() + .filter(|component| match *component { + ::std::path::Component::Normal(..) => true, + _ => false, + }) + .fold(::std::path::PathBuf::new(), |mut path, ref cur| { + path.push(cur.as_os_str()); + path + }) + } + + pub fn version_needed(&self) -> u16 { + match self.compression_method { + #[cfg(feature = "bzip2")] + ::compression::CompressionMethod::Bzip2 => 46, + _ => 20, + } + } +} + +#[cfg(test)] +mod test { + #[test] + fn system() { + use super::System; + assert_eq!(System::Dos as u16, 0u16); + assert_eq!(System::Unix as u16, 3u16); + assert_eq!(System::from_u8(0), System::Dos); + assert_eq!(System::from_u8(3), System::Unix); + } + + #[test] + fn sanitize() { + use super::*; + let file_name = "/path/../../../../etc/./passwd\0/etc/shadow".to_string(); + let data = ZipFileData { + system: System::Dos, + version_made_by: 0, + encrypted: false, + compression_method: ::compression::CompressionMethod::Stored, + last_modified_time: time::empty_tm(), + crc32: 0, + compressed_size: 0, + uncompressed_size: 0, + file_name: file_name.clone(), + file_name_raw: file_name.into_bytes(), + file_comment: String::new(), + header_start: 0, + data_start: 0, + external_attributes: 0, + }; + assert_eq!(data.file_name_sanitized(), ::std::path::PathBuf::from("path/etc/passwd")); + } +} diff --git a/third_party/rust/zip/src/write.rs b/third_party/rust/zip/src/write.rs new file mode 100644 index 0000000000..a7d172c316 --- /dev/null +++ b/third_party/rust/zip/src/write.rs @@ -0,0 +1,558 @@ +//! Structs for creating a new zip archive + +use compression::CompressionMethod; +use types::{ZipFileData, System, DEFAULT_VERSION}; +use spec; +use crc32; +use result::{ZipResult, ZipError}; +use std::default::Default; +use std::io; +use std::io::prelude::*; +use std::mem; +use time; +use podio::{WritePodExt, LittleEndian}; +use msdos_time::TmMsDosExt; + +#[cfg(feature = "flate2")] +use flate2; +#[cfg(feature = "flate2")] +use flate2::write::DeflateEncoder; + +#[cfg(feature = "bzip2")] +use bzip2; +#[cfg(feature = "bzip2")] +use bzip2::write::BzEncoder; +#[allow(unused_imports)] // Rust <1.23 compat +use std::ascii::AsciiExt; + +enum GenericZipWriter<W: Write + io::Seek> +{ + Closed, + Storer(W), + #[cfg(feature = "flate2")] + Deflater(DeflateEncoder<W>), + #[cfg(feature = "bzip2")] + Bzip2(BzEncoder<W>), +} + +/// Generator for ZIP files. +/// +/// ``` +/// 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: &mut [u8] = &mut [0u8; 65536]; +/// let mut w = std::io::Cursor::new(buf); +/// let mut zip = zip::ZipWriter::new(w); +/// +/// let options = zip::write::FileOptions::default().compression_method(zip::CompressionMethod::Stored); +/// try!(zip.start_file("hello_world.txt", options)); +/// try!(zip.write(b"Hello, World!")); +/// +/// // Optionally finish the zip. (this is also done on drop) +/// try!(zip.finish()); +/// +/// Ok(()) +/// } +/// +/// println!("Result: {:?}", doit().unwrap()); +/// ``` +pub struct ZipWriter<W: Write + io::Seek> +{ + inner: GenericZipWriter<W>, + files: Vec<ZipFileData>, + stats: ZipWriterStats, +} + +#[derive(Default)] +struct ZipWriterStats +{ + crc32: u32, + start: u64, + bytes_written: u64, +} + +/// Metadata for a file to be written +#[derive(Copy, Clone)] +pub struct FileOptions { + compression_method: CompressionMethod, + last_modified_time: time::Tm, + permissions: Option<u32>, +} + +impl FileOptions { + #[cfg(feature = "flate2")] + /// Construct a new FileOptions object + pub fn default() -> FileOptions { + FileOptions { + compression_method: CompressionMethod::Deflated, + last_modified_time: time::now(), + permissions: None, + } + } + + #[cfg(not(feature = "flate2"))] + /// Construct a new FileOptions object + pub fn default() -> FileOptions { + FileOptions { + compression_method: CompressionMethod::Stored, + last_modified_time: time::now(), + permissions: None, + } + } + + /// Set the compression method for the new file + /// + /// The default is `CompressionMethod::Deflated`. If the deflate compression feature is + /// disabled, `CompressionMethod::Stored` becomes the default. + /// otherwise. + pub fn compression_method(mut self, method: CompressionMethod) -> FileOptions { + self.compression_method = method; + self + } + + /// Set the last modified time + /// + /// The default is the current timestamp + pub fn last_modified_time(mut self, mod_time: time::Tm) -> FileOptions { + self.last_modified_time = mod_time; + self + } + + /// Set the permissions for the new file. + /// + /// The format is represented with unix-style permissions. + /// The default is `0o644`, which represents `rw-r--r--` for files, + /// and `0o755`, which represents `rwxr-xr-x` for directories + pub fn unix_permissions(mut self, mode: u32) -> FileOptions { + self.permissions = Some(mode & 0o777); + self + } +} + +impl<W: Write+io::Seek> Write for ZipWriter<W> +{ + fn write(&mut self, buf: &[u8]) -> io::Result<usize> + { + if self.files.len() == 0 { return Err(io::Error::new(io::ErrorKind::Other, "No file has been started")) } + match self.inner.ref_mut() + { + Some(ref mut w) => { + let write_result = w.write(buf); + if let Ok(count) = write_result { + self.stats.update(&buf[0..count]); + } + write_result + + } + None => Err(io::Error::new(io::ErrorKind::BrokenPipe, "ZipWriter was already closed")), + } + } + + fn flush(&mut self) -> io::Result<()> + { + match self.inner.ref_mut() + { + Some(ref mut w) => w.flush(), + None => Err(io::Error::new(io::ErrorKind::BrokenPipe, "ZipWriter was already closed")), + } + } +} + +impl ZipWriterStats +{ + fn update(&mut self, buf: &[u8]) + { + self.crc32 = crc32::update(self.crc32, buf); + self.bytes_written += buf.len() as u64; + } +} + +impl<W: Write+io::Seek> ZipWriter<W> +{ + /// Initializes the ZipWriter. + /// + /// Before writing to this object, the start_file command should be called. + pub fn new(inner: W) -> ZipWriter<W> + { + ZipWriter + { + inner: GenericZipWriter::Storer(inner), + files: Vec::new(), + stats: Default::default(), + } + } + + /// Start a new file for with the requested options. + fn start_entry<S>(&mut self, name: S, options: FileOptions) -> ZipResult<()> + where S: Into<String> + { + try!(self.finish_file()); + + { + let writer = self.inner.get_plain(); + let header_start = try!(writer.seek(io::SeekFrom::Current(0))); + + let permissions = options.permissions.unwrap_or(0o100644); + let file_name = name.into(); + let file_name_raw = file_name.clone().into_bytes(); + let mut file = ZipFileData + { + system: System::Unix, + version_made_by: DEFAULT_VERSION, + encrypted: false, + compression_method: options.compression_method, + last_modified_time: options.last_modified_time, + crc32: 0, + compressed_size: 0, + uncompressed_size: 0, + file_name: file_name, + file_name_raw: file_name_raw, + file_comment: String::new(), + header_start: header_start, + data_start: 0, + external_attributes: permissions << 16, + }; + try!(write_local_file_header(writer, &file)); + + let header_end = try!(writer.seek(io::SeekFrom::Current(0))); + self.stats.start = header_end; + file.data_start = header_end; + + self.stats.bytes_written = 0; + self.stats.crc32 = 0; + + self.files.push(file); + } + + try!(self.inner.switch_to(options.compression_method)); + + Ok(()) + } + + fn finish_file(&mut self) -> ZipResult<()> + { + try!(self.inner.switch_to(CompressionMethod::Stored)); + let writer = self.inner.get_plain(); + + let file = match self.files.last_mut() + { + None => return Ok(()), + Some(f) => f, + }; + file.crc32 = self.stats.crc32; + file.uncompressed_size = self.stats.bytes_written; + + let file_end = try!(writer.seek(io::SeekFrom::Current(0))); + file.compressed_size = file_end - self.stats.start; + + try!(update_local_file_header(writer, file)); + try!(writer.seek(io::SeekFrom::Start(file_end))); + Ok(()) + } + + /// Starts a file. + pub fn start_file<S>(&mut self, name: S, mut options: FileOptions) -> ZipResult<()> + where S: Into<String> + { + if options.permissions.is_none() { + options.permissions = Some(0o644); + } + *options.permissions.as_mut().unwrap() |= 0o100000; + try!(self.start_entry(name, options)); + Ok(()) + } + + /// Add a directory entry. + /// + /// You should not write data to the file afterwards. + pub fn add_directory<S>(&mut self, name: S, mut options: FileOptions) -> ZipResult<()> + where S: Into<String> + { + if options.permissions.is_none() { + options.permissions = Some(0o755); + } + *options.permissions.as_mut().unwrap() |= 0o40000; + options.compression_method = CompressionMethod::Stored; + try!(self.start_entry(name, options)); + Ok(()) + } + + /// Finish the last file and write all other zip-structures + /// + /// This will return the writer, but one should normally not append any data to the end of the file. + /// Note that the zipfile will also be finished on drop. + pub fn finish(&mut self) -> ZipResult<W> + { + try!(self.finalize()); + let inner = mem::replace(&mut self.inner, GenericZipWriter::Closed); + Ok(inner.unwrap()) + } + + fn finalize(&mut self) -> ZipResult<()> + { + try!(self.finish_file()); + + { + let writer = self.inner.get_plain(); + + let central_start = try!(writer.seek(io::SeekFrom::Current(0))); + for file in self.files.iter() + { + try!(write_central_directory_header(writer, file)); + } + let central_size = try!(writer.seek(io::SeekFrom::Current(0))) - 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: b"zip-rs".to_vec(), + }; + + try!(footer.write(writer)); + } + + Ok(()) + } +} + +impl<W: Write+io::Seek> Drop for ZipWriter<W> +{ + fn drop(&mut self) + { + if !self.inner.is_closed() + { + if let Err(e) = self.finalize() { + let _ = write!(&mut io::stderr(), "ZipWriter drop failed: {:?}", e); + } + } + } +} + +impl<W: Write+io::Seek> GenericZipWriter<W> +{ + fn switch_to(&mut self, compression: CompressionMethod) -> ZipResult<()> + { + match self.current_compression() { + Some(method) if method == compression => return Ok(()), + None => try!(Err(io::Error::new(io::ErrorKind::BrokenPipe, "ZipWriter was already closed"))), + _ => {}, + } + + let bare = match mem::replace(self, GenericZipWriter::Closed) + { + GenericZipWriter::Storer(w) => w, + #[cfg(feature = "flate2")] + GenericZipWriter::Deflater(w) => try!(w.finish()), + #[cfg(feature = "bzip2")] + GenericZipWriter::Bzip2(w) => try!(w.finish()), + GenericZipWriter::Closed => try!(Err(io::Error::new(io::ErrorKind::BrokenPipe, "ZipWriter was already closed"))), + }; + + *self = match compression + { + CompressionMethod::Stored => GenericZipWriter::Storer(bare), + #[cfg(feature = "flate2")] + CompressionMethod::Deflated => GenericZipWriter::Deflater(DeflateEncoder::new(bare, flate2::Compression::default())), + #[cfg(feature = "bzip2")] + CompressionMethod::Bzip2 => GenericZipWriter::Bzip2(BzEncoder::new(bare, bzip2::Compression::Default)), + CompressionMethod::Unsupported(..) => return Err(ZipError::UnsupportedArchive("Unsupported compression")), + }; + + Ok(()) + } + + fn ref_mut(&mut self) -> Option<&mut Write> { + match *self { + GenericZipWriter::Storer(ref mut w) => Some(w as &mut Write), + #[cfg(feature = "flate2")] + GenericZipWriter::Deflater(ref mut w) => Some(w as &mut Write), + #[cfg(feature = "bzip2")] + GenericZipWriter::Bzip2(ref mut w) => Some(w as &mut Write), + GenericZipWriter::Closed => None, + } + } + + fn is_closed(&self) -> bool + { + match *self + { + GenericZipWriter::Closed => true, + _ => false, + } + } + + fn get_plain(&mut self) -> &mut W + { + match *self + { + GenericZipWriter::Storer(ref mut w) => w, + _ => panic!("Should have switched to stored beforehand"), + } + } + + fn current_compression(&self) -> Option<CompressionMethod> { + match *self { + GenericZipWriter::Storer(..) => Some(CompressionMethod::Stored), + #[cfg(feature = "flate2")] + GenericZipWriter::Deflater(..) => Some(CompressionMethod::Deflated), + #[cfg(feature = "bzip2")] + GenericZipWriter::Bzip2(..) => Some(CompressionMethod::Bzip2), + GenericZipWriter::Closed => None, + } + } + + fn unwrap(self) -> W + { + match self + { + GenericZipWriter::Storer(w) => w, + _ => panic!("Should have switched to stored beforehand"), + } + } +} + +fn write_local_file_header<T: Write>(writer: &mut T, file: &ZipFileData) -> ZipResult<()> +{ + // local file header signature + try!(writer.write_u32::<LittleEndian>(spec::LOCAL_FILE_HEADER_SIGNATURE)); + // version needed to extract + try!(writer.write_u16::<LittleEndian>(file.version_needed())); + // general purpose bit flag + let flag = if !file.file_name.is_ascii() { 1u16 << 11 } else { 0 }; + try!(writer.write_u16::<LittleEndian>(flag)); + // Compression method + try!(writer.write_u16::<LittleEndian>(file.compression_method.to_u16())); + // last mod file time and last mod file date + let msdos_datetime = try!(file.last_modified_time.to_msdos()); + try!(writer.write_u16::<LittleEndian>(msdos_datetime.timepart)); + try!(writer.write_u16::<LittleEndian>(msdos_datetime.datepart)); + // crc-32 + try!(writer.write_u32::<LittleEndian>(file.crc32)); + // compressed size + try!(writer.write_u32::<LittleEndian>(file.compressed_size as u32)); + // uncompressed size + try!(writer.write_u32::<LittleEndian>(file.uncompressed_size as u32)); + // file name length + try!(writer.write_u16::<LittleEndian>(file.file_name.as_bytes().len() as u16)); + // extra field length + let extra_field = try!(build_extra_field(file)); + try!(writer.write_u16::<LittleEndian>(extra_field.len() as u16)); + // file name + try!(writer.write_all(file.file_name.as_bytes())); + // extra field + try!(writer.write_all(&extra_field)); + + Ok(()) +} + +fn update_local_file_header<T: Write+io::Seek>(writer: &mut T, file: &ZipFileData) -> ZipResult<()> +{ + const CRC32_OFFSET : u64 = 14; + try!(writer.seek(io::SeekFrom::Start(file.header_start + CRC32_OFFSET))); + try!(writer.write_u32::<LittleEndian>(file.crc32)); + try!(writer.write_u32::<LittleEndian>(file.compressed_size as u32)); + try!(writer.write_u32::<LittleEndian>(file.uncompressed_size as u32)); + Ok(()) +} + +fn write_central_directory_header<T: Write>(writer: &mut T, file: &ZipFileData) -> ZipResult<()> +{ + // central file header signature + try!(writer.write_u32::<LittleEndian>(spec::CENTRAL_DIRECTORY_HEADER_SIGNATURE)); + // version made by + let version_made_by = (file.system as u16) << 8 | (file.version_made_by as u16); + try!(writer.write_u16::<LittleEndian>(version_made_by)); + // version needed to extract + try!(writer.write_u16::<LittleEndian>(file.version_needed())); + // general puprose bit flag + let flag = if !file.file_name.is_ascii() { 1u16 << 11 } else { 0 }; + try!(writer.write_u16::<LittleEndian>(flag)); + // compression method + try!(writer.write_u16::<LittleEndian>(file.compression_method.to_u16())); + // last mod file time + date + let msdos_datetime = try!(file.last_modified_time.to_msdos()); + try!(writer.write_u16::<LittleEndian>(msdos_datetime.timepart)); + try!(writer.write_u16::<LittleEndian>(msdos_datetime.datepart)); + // crc-32 + try!(writer.write_u32::<LittleEndian>(file.crc32)); + // compressed size + try!(writer.write_u32::<LittleEndian>(file.compressed_size as u32)); + // uncompressed size + try!(writer.write_u32::<LittleEndian>(file.uncompressed_size as u32)); + // file name length + try!(writer.write_u16::<LittleEndian>(file.file_name.as_bytes().len() as u16)); + // extra field length + let extra_field = try!(build_extra_field(file)); + try!(writer.write_u16::<LittleEndian>(extra_field.len() as u16)); + // file comment length + try!(writer.write_u16::<LittleEndian>(0)); + // disk number start + try!(writer.write_u16::<LittleEndian>(0)); + // internal file attribytes + try!(writer.write_u16::<LittleEndian>(0)); + // external file attributes + try!(writer.write_u32::<LittleEndian>(file.external_attributes)); + // relative offset of local header + try!(writer.write_u32::<LittleEndian>(file.header_start as u32)); + // file name + try!(writer.write_all(file.file_name.as_bytes())); + // extra field + try!(writer.write_all(&extra_field)); + // file comment + // <none> + + Ok(()) +} + +fn build_extra_field(_file: &ZipFileData) -> ZipResult<Vec<u8>> +{ + let writer = Vec::new(); + // Future work + Ok(writer) +} + +#[cfg(test)] +mod test { + use std::io; + use std::io::Write; + use time; + use super::{FileOptions, ZipWriter}; + use compression::CompressionMethod; + + #[test] + fn write_empty_zip() { + let mut writer = ZipWriter::new(io::Cursor::new(Vec::new())); + let result = writer.finish().unwrap(); + assert_eq!(result.get_ref().len(), 28); + 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]; + assert_eq!(result.get_ref(), &v); + } + + #[test] + fn write_mimetype_zip() { + let mut writer = ZipWriter::new(io::Cursor::new(Vec::new())); + let mut mtime = time::empty_tm(); + mtime.tm_year = 80; + mtime.tm_mday = 1; + let options = FileOptions { + compression_method: CompressionMethod::Stored, + last_modified_time: mtime, + permissions: Some(33188), + }; + writer.start_file("mimetype", options).unwrap(); + writer.write(b"application/vnd.oasis.opendocument.text").unwrap(); + let result = writer.finish().unwrap(); + assert_eq!(result.get_ref().len(), 159); + let mut v = Vec::new(); + v.extend_from_slice(include_bytes!("../tests/data/mimetype.zip")); + assert_eq!(result.get_ref(), &v); + } +} |