diff options
Diffstat (limited to 'src/zip/file')
| -rw-r--r-- | src/zip/file/info.rs | 161 | ||||
| -rw-r--r-- | src/zip/file/mod.rs | 7 | ||||
| -rw-r--r-- | src/zip/file/read.rs | 125 | ||||
| -rw-r--r-- | src/zip/file/write.rs | 30 |
4 files changed, 323 insertions, 0 deletions
diff --git a/src/zip/file/info.rs b/src/zip/file/info.rs new file mode 100644 index 0000000..2891b59 --- /dev/null +++ b/src/zip/file/info.rs | |||
| @@ -0,0 +1,161 @@ | |||
| 1 | use crate::driver::ArchiveFileInfo; | ||
| 2 | use crate::zip::{ZipError, ZipResult}; | ||
| 3 | use chrono::{DateTime, Local}; | ||
| 4 | |||
| 5 | #[derive(Debug, Clone)] | ||
| 6 | pub enum CompressionMethod { | ||
| 7 | Store, | ||
| 8 | Deflate, | ||
| 9 | BZip2, | ||
| 10 | LZMA, | ||
| 11 | XZ, | ||
| 12 | } | ||
| 13 | |||
| 14 | impl CompressionMethod { | ||
| 15 | pub(crate) fn from_struct_id(id: u16) -> ZipResult<Self> { | ||
| 16 | match id { | ||
| 17 | 0 => Ok(Self::Store), | ||
| 18 | 8 => Ok(Self::Deflate), | ||
| 19 | 12 => Ok(Self::BZip2), | ||
| 20 | 14 => Ok(Self::LZMA), | ||
| 21 | 95 => Ok(Self::XZ), | ||
| 22 | 1..=7 | 9..=11 | 13 | 15..=20 | 93..=94 | 96..=99 => { | ||
| 23 | Err(ZipError::UnsupportedCompressionMethod.into()) | ||
| 24 | } | ||
| 25 | 21..=92 | 100.. => Err(ZipError::InvalidCompressionMethod.into()), | ||
| 26 | } | ||
| 27 | } | ||
| 28 | } | ||
| 29 | |||
| 30 | #[derive(Debug, Clone)] | ||
| 31 | pub struct BitFlag { | ||
| 32 | flag: u16, | ||
| 33 | } | ||
| 34 | |||
| 35 | pub mod bit { | ||
| 36 | #[derive(Debug, PartialEq, Eq)] | ||
| 37 | pub enum DeflateMode { | ||
| 38 | Normal, | ||
| 39 | Maximum, | ||
| 40 | Fast, | ||
| 41 | SuperFast, | ||
| 42 | } | ||
| 43 | } | ||
| 44 | |||
| 45 | macro_rules! get_set_bit_flag { | ||
| 46 | {$($get:ident $set:ident $bit:expr)+} => { | ||
| 47 | $( | ||
| 48 | pub fn $get(&self) -> bool { | ||
| 49 | self.get_bit($bit) | ||
| 50 | } | ||
| 51 | |||
| 52 | pub fn $set(&mut self, enable: bool) { | ||
| 53 | self.set_bit($bit, enable); | ||
| 54 | } | ||
| 55 | )* | ||
| 56 | }; | ||
| 57 | } | ||
| 58 | |||
| 59 | impl BitFlag { | ||
| 60 | pub fn new(flag: u16) -> Self { | ||
| 61 | Self { flag } | ||
| 62 | } | ||
| 63 | |||
| 64 | #[inline] | ||
| 65 | fn get_bit(&self, bit: u32) -> bool { | ||
| 66 | (self.flag & 2u16.pow(bit)) > 0 | ||
| 67 | } | ||
| 68 | |||
| 69 | #[inline] | ||
| 70 | fn set_bit(&mut self, bit: u32, enable: bool) { | ||
| 71 | if enable { | ||
| 72 | self.flag |= 2u16.pow(bit); | ||
| 73 | } else { | ||
| 74 | self.flag &= !2u16.pow(bit); | ||
| 75 | } | ||
| 76 | } | ||
| 77 | |||
| 78 | pub fn deflate_mode(&self) -> bit::DeflateMode { | ||
| 79 | match self.flag & 6 { | ||
| 80 | 0 => bit::DeflateMode::Normal, | ||
| 81 | 2 => bit::DeflateMode::Maximum, | ||
| 82 | 4 => bit::DeflateMode::Fast, | ||
| 83 | 6 => bit::DeflateMode::SuperFast, | ||
| 84 | _ => panic!("impossible"), | ||
| 85 | } | ||
| 86 | } | ||
| 87 | |||
| 88 | pub fn set_deflate_mode(&mut self, mode: bit::DeflateMode) { | ||
| 89 | match mode { | ||
| 90 | bit::DeflateMode::Normal => { | ||
| 91 | self.set_bit(1, false); | ||
| 92 | self.set_bit(2, false); | ||
| 93 | } | ||
| 94 | bit::DeflateMode::Maximum => { | ||
| 95 | self.set_bit(1, true); | ||
| 96 | self.set_bit(2, false); | ||
| 97 | } | ||
| 98 | bit::DeflateMode::Fast => { | ||
| 99 | self.set_bit(1, false); | ||
| 100 | self.set_bit(2, true); | ||
| 101 | } | ||
| 102 | bit::DeflateMode::SuperFast => { | ||
| 103 | self.set_bit(1, true); | ||
| 104 | self.set_bit(2, true); | ||
| 105 | } | ||
| 106 | } | ||
| 107 | } | ||
| 108 | |||
| 109 | get_set_bit_flag! { | ||
| 110 | is_encrypted set_encrypted 0 | ||
| 111 | is_imploding_8k set_imploding_8k 1 | ||
| 112 | is_imploding_3sf_trees set_imploding_3sf_trees 2 | ||
| 113 | is_lzma_has_eos_marker set_lzma_has_eos_marker 1 | ||
| 114 | is_has_data_descriptor set_has_data_descriptor 3 | ||
| 115 | is_patched_data set_patched_data 5 | ||
| 116 | is_strong_encryption set_strong_encryption 6 | ||
| 117 | is_utf8 set_utf8 11 | ||
| 118 | is_cd_encryption set_cd_encryption 13 | ||
| 119 | } | ||
| 120 | } | ||
| 121 | |||
| 122 | #[derive(Debug, Clone)] | ||
| 123 | pub struct ZipFileInfo { | ||
| 124 | pub compression_method: CompressionMethod, | ||
| 125 | pub bit_flag: BitFlag, | ||
| 126 | pub datetime: DateTime<Local>, | ||
| 127 | pub crc: u32, | ||
| 128 | pub compressed_size: u64, | ||
| 129 | pub size: u64, | ||
| 130 | pub header_pointer: u64, | ||
| 131 | pub name: String, | ||
| 132 | pub comment: String, | ||
| 133 | } | ||
| 134 | |||
| 135 | impl ZipFileInfo { | ||
| 136 | pub fn new( | ||
| 137 | compression_method: CompressionMethod, | ||
| 138 | bit_flag: BitFlag, | ||
| 139 | datetime: DateTime<Local>, | ||
| 140 | crc: u32, | ||
| 141 | compressed_size: u64, | ||
| 142 | size: u64, | ||
| 143 | header_pointer: u64, | ||
| 144 | name: String, | ||
| 145 | comment: String, | ||
| 146 | ) -> Self { | ||
| 147 | Self { | ||
| 148 | compression_method, | ||
| 149 | bit_flag, | ||
| 150 | datetime, | ||
| 151 | crc, | ||
| 152 | compressed_size, | ||
| 153 | size, | ||
| 154 | header_pointer, | ||
| 155 | name, | ||
| 156 | comment, | ||
| 157 | } | ||
| 158 | } | ||
| 159 | } | ||
| 160 | |||
| 161 | impl ArchiveFileInfo for ZipFileInfo {} | ||
diff --git a/src/zip/file/mod.rs b/src/zip/file/mod.rs new file mode 100644 index 0000000..43ccc04 --- /dev/null +++ b/src/zip/file/mod.rs | |||
| @@ -0,0 +1,7 @@ | |||
| 1 | mod info; | ||
| 2 | mod read; | ||
| 3 | mod write; | ||
| 4 | |||
| 5 | pub use info::{bit, BitFlag, CompressionMethod, ZipFileInfo}; | ||
| 6 | pub use read::ZipFileReader; | ||
| 7 | pub use write::ZipFileWriter; | ||
diff --git a/src/zip/file/read.rs b/src/zip/file/read.rs new file mode 100644 index 0000000..56f42da --- /dev/null +++ b/src/zip/file/read.rs | |||
| @@ -0,0 +1,125 @@ | |||
| 1 | use crate::driver::FileDriver; | ||
| 2 | use crate::zip::{CompressionMethod, ZipError, ZipFileInfo, ZipResult}; | ||
| 3 | use bzip2::read::BzDecoder; | ||
| 4 | use flate2::read::DeflateDecoder; | ||
| 5 | use std::io::{ | ||
| 6 | Error as IoError, ErrorKind as IoErrorKind, Read, Result as IoResult, Seek, SeekFrom, | ||
| 7 | }; | ||
| 8 | use xz2::read::XzDecoder; | ||
| 9 | |||
| 10 | enum IoProxy<Io: Read> { | ||
| 11 | Store(Io), | ||
| 12 | Deflate(DeflateDecoder<Io>), | ||
| 13 | BZip2(BzDecoder<Io>), | ||
| 14 | XZ(XzDecoder<Io>), | ||
| 15 | } | ||
| 16 | |||
| 17 | pub struct ZipFileReader<'d, Io: Read> { | ||
| 18 | io: IoProxy<&'d mut Io>, | ||
| 19 | info: &'d ZipFileInfo, | ||
| 20 | |||
| 21 | bounds: (u64, u64), | ||
| 22 | cursor: u64, | ||
| 23 | } | ||
| 24 | |||
| 25 | impl<'d, Io: Read> FileDriver for ZipFileReader<'d, Io> { | ||
| 26 | type Io = Io; | ||
| 27 | type FileInfo = ZipFileInfo; | ||
| 28 | |||
| 29 | fn info(&self) -> &Self::FileInfo { | ||
| 30 | self.info | ||
| 31 | } | ||
| 32 | } | ||
| 33 | |||
| 34 | impl<'d, Io: Read + Seek> ZipFileReader<'d, Io> { | ||
| 35 | pub fn new(io: &'d mut Io, info: &'d ZipFileInfo) -> ZipResult<Self> { | ||
| 36 | io.seek(SeekFrom::Start(info.header_pointer))?; | ||
| 37 | let buf = { | ||
| 38 | let mut buf = [0; 30]; | ||
| 39 | io.read(&mut buf)?; | ||
| 40 | buf | ||
| 41 | }; | ||
| 42 | if u32::from_le_bytes(buf[..4].try_into().unwrap()) != 0x04034b50 { | ||
| 43 | return Err(ZipError::InvalidFileHeaderSignature.into()); | ||
| 44 | } | ||
| 45 | let data_pointer = info.header_pointer | ||
| 46 | + 30 | ||
| 47 | + u16::from_le_bytes(buf[26..28].try_into().unwrap()) as u64 | ||
| 48 | + u16::from_le_bytes(buf[28..30].try_into().unwrap()) as u64; | ||
| 49 | io.seek(SeekFrom::Start(data_pointer))?; | ||
| 50 | |||
| 51 | Ok(Self { | ||
| 52 | io: match info.compression_method { | ||
| 53 | CompressionMethod::Store => IoProxy::Store(io), | ||
| 54 | CompressionMethod::Deflate => IoProxy::Deflate(DeflateDecoder::new(io)), | ||
| 55 | CompressionMethod::BZip2 => IoProxy::BZip2(BzDecoder::new(io)), | ||
| 56 | CompressionMethod::LZMA => IoProxy::XZ(XzDecoder::new(io)), | ||
| 57 | CompressionMethod::XZ => IoProxy::XZ(XzDecoder::new(io)), | ||
| 58 | }, | ||
| 59 | info, | ||
| 60 | |||
| 61 | bounds: (data_pointer, data_pointer + info.compressed_size), | ||
| 62 | cursor: data_pointer, | ||
| 63 | }) | ||
| 64 | } | ||
| 65 | |||
| 66 | pub fn seekable(&self) -> bool { | ||
| 67 | match self.io { | ||
| 68 | IoProxy::Store(..) => true, | ||
| 69 | _ => false, | ||
| 70 | } | ||
| 71 | } | ||
| 72 | } | ||
| 73 | |||
| 74 | impl<'d, Io: Read> Read for ZipFileReader<'d, Io> { | ||
| 75 | fn read(&mut self, buf: &mut [u8]) -> IoResult<usize> { | ||
| 76 | let upper = buf.len().min((self.bounds.1 - self.cursor) as usize); | ||
| 77 | let bytes = match &mut self.io { | ||
| 78 | IoProxy::Store(io) => io.read(&mut buf[..upper]), | ||
| 79 | IoProxy::Deflate(io) => io.read(&mut buf[..upper]), | ||
| 80 | IoProxy::BZip2(io) => io.read(&mut buf[..upper]), | ||
| 81 | IoProxy::XZ(io) => io.read(&mut buf[..upper]), | ||
| 82 | }?; | ||
| 83 | self.cursor += upper as u64; | ||
| 84 | Ok(bytes) | ||
| 85 | } | ||
| 86 | } | ||
| 87 | |||
| 88 | impl<'d, Io: Read + Seek> Seek for ZipFileReader<'d, Io> { | ||
| 89 | fn seek(&mut self, pos: SeekFrom) -> IoResult<u64> { | ||
| 90 | match &mut self.io { | ||
| 91 | IoProxy::Store(io) => { | ||
| 92 | self.cursor = match pos { | ||
| 93 | SeekFrom::Start(offset) => self.bounds.0 + offset, | ||
| 94 | SeekFrom::End(offset) => { | ||
| 95 | let cursor = self.bounds.1.saturating_add_signed(offset); | ||
| 96 | if cursor < self.bounds.0 { | ||
| 97 | return Err(IoError::new( | ||
| 98 | IoErrorKind::InvalidInput, | ||
| 99 | ZipError::NegativeFileOffset, | ||
| 100 | )); | ||
| 101 | } | ||
| 102 | cursor | ||
| 103 | } | ||
| 104 | SeekFrom::Current(offset) => { | ||
| 105 | let cursor = self.cursor.saturating_add_signed(offset); | ||
| 106 | if cursor < self.bounds.0 { | ||
| 107 | return Err(IoError::new( | ||
| 108 | IoErrorKind::InvalidInput, | ||
| 109 | ZipError::NegativeFileOffset, | ||
| 110 | )); | ||
| 111 | } | ||
| 112 | cursor | ||
| 113 | } | ||
| 114 | } | ||
| 115 | .min(self.bounds.1); | ||
| 116 | |||
| 117 | Ok(io.seek(SeekFrom::Start(self.cursor))? - self.bounds.0) | ||
| 118 | } | ||
| 119 | _ => Err(IoError::new( | ||
| 120 | IoErrorKind::Unsupported, | ||
| 121 | ZipError::CompressionDataIsUnseekable, | ||
| 122 | )), | ||
| 123 | } | ||
| 124 | } | ||
| 125 | } | ||
diff --git a/src/zip/file/write.rs b/src/zip/file/write.rs new file mode 100644 index 0000000..627db6d --- /dev/null +++ b/src/zip/file/write.rs | |||
| @@ -0,0 +1,30 @@ | |||
| 1 | use crate::driver::FileDriver; | ||
| 2 | use crate::zip::ZipFileInfo; | ||
| 3 | use bzip2::write::BzEncoder; | ||
| 4 | use flate2::write::DeflateEncoder; | ||
| 5 | use std::io::Write; | ||
| 6 | use xz2::write::XzEncoder; | ||
| 7 | |||
| 8 | enum IoProxy<Io: Write> { | ||
| 9 | Store(Io), | ||
| 10 | Deflate(DeflateEncoder<Io>), | ||
| 11 | BZip2(BzEncoder<Io>), | ||
| 12 | XZ(XzEncoder<Io>), | ||
| 13 | } | ||
| 14 | |||
| 15 | pub struct ZipFileWriter<'d, Io: Write> { | ||
| 16 | io: IoProxy<&'d mut Io>, | ||
| 17 | info: &'d ZipFileInfo, | ||
| 18 | |||
| 19 | bounds: (u64, u64), | ||
| 20 | cursor: u64, | ||
| 21 | } | ||
| 22 | |||
| 23 | impl<'d, Io: Write> FileDriver for ZipFileWriter<'d, Io> { | ||
| 24 | type Io = Io; | ||
| 25 | type FileInfo = ZipFileInfo; | ||
| 26 | |||
| 27 | fn info(&self) -> &Self::FileInfo { | ||
| 28 | self.info | ||
| 29 | } | ||
| 30 | } | ||
