From 5d3d32ded672b67471d9d7c85ebbe691129cc51c Mon Sep 17 00:00:00 2001 From: Igor Tolmachev Date: Mon, 1 Jul 2024 19:12:40 +0900 Subject: Add compression support (lzma and xz are broken) --- src/zip/file/info.rs | 161 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/zip/file/mod.rs | 7 +++ src/zip/file/read.rs | 125 +++++++++++++++++++++++++++++++++++++++ src/zip/file/write.rs | 30 ++++++++++ 4 files changed, 323 insertions(+) create mode 100644 src/zip/file/info.rs create mode 100644 src/zip/file/mod.rs create mode 100644 src/zip/file/read.rs create mode 100644 src/zip/file/write.rs (limited to 'src/zip/file') 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 @@ +use crate::driver::ArchiveFileInfo; +use crate::zip::{ZipError, ZipResult}; +use chrono::{DateTime, Local}; + +#[derive(Debug, Clone)] +pub enum CompressionMethod { + Store, + Deflate, + BZip2, + LZMA, + XZ, +} + +impl CompressionMethod { + pub(crate) fn from_struct_id(id: u16) -> ZipResult { + match id { + 0 => Ok(Self::Store), + 8 => Ok(Self::Deflate), + 12 => Ok(Self::BZip2), + 14 => Ok(Self::LZMA), + 95 => Ok(Self::XZ), + 1..=7 | 9..=11 | 13 | 15..=20 | 93..=94 | 96..=99 => { + Err(ZipError::UnsupportedCompressionMethod.into()) + } + 21..=92 | 100.. => Err(ZipError::InvalidCompressionMethod.into()), + } + } +} + +#[derive(Debug, Clone)] +pub struct BitFlag { + flag: u16, +} + +pub mod bit { + #[derive(Debug, PartialEq, Eq)] + pub enum DeflateMode { + Normal, + Maximum, + Fast, + SuperFast, + } +} + +macro_rules! get_set_bit_flag { + {$($get:ident $set:ident $bit:expr)+} => { + $( + pub fn $get(&self) -> bool { + self.get_bit($bit) + } + + pub fn $set(&mut self, enable: bool) { + self.set_bit($bit, enable); + } + )* + }; +} + +impl BitFlag { + pub fn new(flag: u16) -> Self { + Self { flag } + } + + #[inline] + fn get_bit(&self, bit: u32) -> bool { + (self.flag & 2u16.pow(bit)) > 0 + } + + #[inline] + fn set_bit(&mut self, bit: u32, enable: bool) { + if enable { + self.flag |= 2u16.pow(bit); + } else { + self.flag &= !2u16.pow(bit); + } + } + + pub fn deflate_mode(&self) -> bit::DeflateMode { + match self.flag & 6 { + 0 => bit::DeflateMode::Normal, + 2 => bit::DeflateMode::Maximum, + 4 => bit::DeflateMode::Fast, + 6 => bit::DeflateMode::SuperFast, + _ => panic!("impossible"), + } + } + + pub fn set_deflate_mode(&mut self, mode: bit::DeflateMode) { + match mode { + bit::DeflateMode::Normal => { + self.set_bit(1, false); + self.set_bit(2, false); + } + bit::DeflateMode::Maximum => { + self.set_bit(1, true); + self.set_bit(2, false); + } + bit::DeflateMode::Fast => { + self.set_bit(1, false); + self.set_bit(2, true); + } + bit::DeflateMode::SuperFast => { + self.set_bit(1, true); + self.set_bit(2, true); + } + } + } + + get_set_bit_flag! { + is_encrypted set_encrypted 0 + is_imploding_8k set_imploding_8k 1 + is_imploding_3sf_trees set_imploding_3sf_trees 2 + is_lzma_has_eos_marker set_lzma_has_eos_marker 1 + is_has_data_descriptor set_has_data_descriptor 3 + is_patched_data set_patched_data 5 + is_strong_encryption set_strong_encryption 6 + is_utf8 set_utf8 11 + is_cd_encryption set_cd_encryption 13 + } +} + +#[derive(Debug, Clone)] +pub struct ZipFileInfo { + pub compression_method: CompressionMethod, + pub bit_flag: BitFlag, + pub datetime: DateTime, + pub crc: u32, + pub compressed_size: u64, + pub size: u64, + pub header_pointer: u64, + pub name: String, + pub comment: String, +} + +impl ZipFileInfo { + pub fn new( + compression_method: CompressionMethod, + bit_flag: BitFlag, + datetime: DateTime, + crc: u32, + compressed_size: u64, + size: u64, + header_pointer: u64, + name: String, + comment: String, + ) -> Self { + Self { + compression_method, + bit_flag, + datetime, + crc, + compressed_size, + size, + header_pointer, + name, + comment, + } + } +} + +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 @@ +mod info; +mod read; +mod write; + +pub use info::{bit, BitFlag, CompressionMethod, ZipFileInfo}; +pub use read::ZipFileReader; +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 @@ +use crate::driver::FileDriver; +use crate::zip::{CompressionMethod, ZipError, ZipFileInfo, ZipResult}; +use bzip2::read::BzDecoder; +use flate2::read::DeflateDecoder; +use std::io::{ + Error as IoError, ErrorKind as IoErrorKind, Read, Result as IoResult, Seek, SeekFrom, +}; +use xz2::read::XzDecoder; + +enum IoProxy { + Store(Io), + Deflate(DeflateDecoder), + BZip2(BzDecoder), + XZ(XzDecoder), +} + +pub struct ZipFileReader<'d, Io: Read> { + io: IoProxy<&'d mut Io>, + info: &'d ZipFileInfo, + + bounds: (u64, u64), + cursor: u64, +} + +impl<'d, Io: Read> FileDriver for ZipFileReader<'d, Io> { + type Io = Io; + type FileInfo = ZipFileInfo; + + fn info(&self) -> &Self::FileInfo { + self.info + } +} + +impl<'d, Io: Read + Seek> ZipFileReader<'d, Io> { + pub fn new(io: &'d mut Io, info: &'d ZipFileInfo) -> ZipResult { + io.seek(SeekFrom::Start(info.header_pointer))?; + let buf = { + let mut buf = [0; 30]; + io.read(&mut buf)?; + buf + }; + if u32::from_le_bytes(buf[..4].try_into().unwrap()) != 0x04034b50 { + return Err(ZipError::InvalidFileHeaderSignature.into()); + } + let data_pointer = info.header_pointer + + 30 + + u16::from_le_bytes(buf[26..28].try_into().unwrap()) as u64 + + u16::from_le_bytes(buf[28..30].try_into().unwrap()) as u64; + io.seek(SeekFrom::Start(data_pointer))?; + + Ok(Self { + io: match info.compression_method { + CompressionMethod::Store => IoProxy::Store(io), + CompressionMethod::Deflate => IoProxy::Deflate(DeflateDecoder::new(io)), + CompressionMethod::BZip2 => IoProxy::BZip2(BzDecoder::new(io)), + CompressionMethod::LZMA => IoProxy::XZ(XzDecoder::new(io)), + CompressionMethod::XZ => IoProxy::XZ(XzDecoder::new(io)), + }, + info, + + bounds: (data_pointer, data_pointer + info.compressed_size), + cursor: data_pointer, + }) + } + + pub fn seekable(&self) -> bool { + match self.io { + IoProxy::Store(..) => true, + _ => false, + } + } +} + +impl<'d, Io: Read> Read for ZipFileReader<'d, Io> { + fn read(&mut self, buf: &mut [u8]) -> IoResult { + let upper = buf.len().min((self.bounds.1 - self.cursor) as usize); + let bytes = match &mut self.io { + IoProxy::Store(io) => io.read(&mut buf[..upper]), + IoProxy::Deflate(io) => io.read(&mut buf[..upper]), + IoProxy::BZip2(io) => io.read(&mut buf[..upper]), + IoProxy::XZ(io) => io.read(&mut buf[..upper]), + }?; + self.cursor += upper as u64; + Ok(bytes) + } +} + +impl<'d, Io: Read + Seek> Seek for ZipFileReader<'d, Io> { + fn seek(&mut self, pos: SeekFrom) -> IoResult { + match &mut self.io { + IoProxy::Store(io) => { + self.cursor = match pos { + SeekFrom::Start(offset) => self.bounds.0 + offset, + SeekFrom::End(offset) => { + let cursor = self.bounds.1.saturating_add_signed(offset); + if cursor < self.bounds.0 { + return Err(IoError::new( + IoErrorKind::InvalidInput, + ZipError::NegativeFileOffset, + )); + } + cursor + } + SeekFrom::Current(offset) => { + let cursor = self.cursor.saturating_add_signed(offset); + if cursor < self.bounds.0 { + return Err(IoError::new( + IoErrorKind::InvalidInput, + ZipError::NegativeFileOffset, + )); + } + cursor + } + } + .min(self.bounds.1); + + Ok(io.seek(SeekFrom::Start(self.cursor))? - self.bounds.0) + } + _ => Err(IoError::new( + IoErrorKind::Unsupported, + ZipError::CompressionDataIsUnseekable, + )), + } + } +} 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 @@ +use crate::driver::FileDriver; +use crate::zip::ZipFileInfo; +use bzip2::write::BzEncoder; +use flate2::write::DeflateEncoder; +use std::io::Write; +use xz2::write::XzEncoder; + +enum IoProxy { + Store(Io), + Deflate(DeflateEncoder), + BZip2(BzEncoder), + XZ(XzEncoder), +} + +pub struct ZipFileWriter<'d, Io: Write> { + io: IoProxy<&'d mut Io>, + info: &'d ZipFileInfo, + + bounds: (u64, u64), + cursor: u64, +} + +impl<'d, Io: Write> FileDriver for ZipFileWriter<'d, Io> { + type Io = Io; + type FileInfo = ZipFileInfo; + + fn info(&self) -> &Self::FileInfo { + self.info + } +} -- cgit v1.3