From a83767f9fbd51df654901b52bdba7838f6a10bf9 Mon Sep 17 00:00:00 2001 From: Igor Tolmachev Date: Tue, 16 Jul 2024 01:59:53 +0900 Subject: Add traditional PKWARE decryption. - Compression and encryption may not work together - Password check is not yet implemented - Unoptimized crc32 function --- src/zip/file/info.rs | 47 ++++++++++----- src/zip/file/mod.rs | 2 +- src/zip/file/read.rs | 159 ++++++++++++++++++++++++++++++++++----------------- 3 files changed, 143 insertions(+), 65 deletions(-) (limited to 'src/zip/file') diff --git a/src/zip/file/info.rs b/src/zip/file/info.rs index 4e1b293..f5d4d8a 100644 --- a/src/zip/file/info.rs +++ b/src/zip/file/info.rs @@ -2,7 +2,7 @@ use crate::driver::ArchiveFileInfo; use crate::zip::{ZipError, ZipResult}; use chrono::{DateTime, Local}; -#[derive(Debug, Clone)] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum CompressionMethod { Store, Deflate, @@ -10,31 +10,49 @@ pub enum CompressionMethod { Lzma, Zstd, Xz, - Unsupported, + Unsupported(u16), } 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), - 93 => Ok(Self::Zstd), - 95 => Ok(Self::Xz), - 1..=7 | 9..=11 | 13 | 15..=20 | 94 | 96..=99 => Ok(Self::Unsupported), - 21..=92 | 100.. => Err(ZipError::InvalidCompressionMethod.into()), + Ok(match id { + 0 => Self::Store, + 8 => Self::Deflate, + 12 => Self::BZip2, + 14 => Self::Lzma, + 93 => Self::Zstd, + 95 => Self::Xz, + 1..=7 | 9..=11 | 13 | 15..=20 | 94 | 96..=99 => Self::Unsupported(id), + 21..=92 | 100.. => return Err(ZipError::InvalidCompressionMethod(id).into()), + }) + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum EncryptionMethod { + None, + Weak, + Unsupported, +} + +impl EncryptionMethod { + pub(crate) fn from_bit_flag(bit_flag: BitFlag) -> EncryptionMethod { + match (bit_flag.is_encrypted(), bit_flag.is_strong_encryption()) { + (false, false) => EncryptionMethod::None, + (true, false) => EncryptionMethod::Weak, + (true, true) => EncryptionMethod::Unsupported, + _ => panic!("impossible"), } } } -#[derive(Debug, Clone)] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] pub struct BitFlag { flag: u16, } pub mod bit { - #[derive(Debug, PartialEq, Eq)] + #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum DeflateMode { Normal, Maximum, @@ -123,6 +141,7 @@ impl BitFlag { #[derive(Debug, Clone)] pub struct ZipFileInfo { pub compression_method: CompressionMethod, + pub encryption_method: EncryptionMethod, pub bit_flag: BitFlag, pub mtime: DateTime, pub atime: Option>, @@ -138,6 +157,7 @@ pub struct ZipFileInfo { impl ZipFileInfo { pub fn new( compression_method: CompressionMethod, + encryption_method: EncryptionMethod, bit_flag: BitFlag, mtime: DateTime, atime: Option>, @@ -151,6 +171,7 @@ impl ZipFileInfo { ) -> Self { Self { compression_method, + encryption_method, bit_flag, mtime, atime, diff --git a/src/zip/file/mod.rs b/src/zip/file/mod.rs index 43ccc04..ce3c21d 100644 --- a/src/zip/file/mod.rs +++ b/src/zip/file/mod.rs @@ -2,6 +2,6 @@ mod info; mod read; mod write; -pub use info::{bit, BitFlag, CompressionMethod, ZipFileInfo}; +pub use info::{bit, BitFlag, CompressionMethod, EncryptionMethod, ZipFileInfo}; pub use read::ZipFileReader; pub use write::ZipFileWriter; diff --git a/src/zip/file/read.rs b/src/zip/file/read.rs index f5a54f3..aa665c3 100644 --- a/src/zip/file/read.rs +++ b/src/zip/file/read.rs @@ -1,6 +1,7 @@ use crate::driver::FileDriver; use crate::utils::{IoCursor, ReadUtils}; -use crate::zip::{CompressionMethod, ZipError, ZipFileInfo, ZipResult}; +use crate::zip::encryption::WeakDecoder; +use crate::zip::{CompressionMethod, EncryptionMethod, ZipError, ZipFileInfo, ZipResult}; use bzip2::read::BzDecoder; use flate2::read::DeflateDecoder; use liblzma::read::XzDecoder; @@ -10,6 +11,47 @@ use std::io::{ }; use zstd::stream::Decoder as ZstdDecoder; +enum Encryption { + None(Io), + Weak(WeakDecoder), +} + +impl Encryption { + pub fn new(io: Io, method: EncryptionMethod, password: Option<&str>) -> ZipResult { + Ok(match method { + EncryptionMethod::None => Self::None(io), + EncryptionMethod::Weak => Self::Weak(WeakDecoder::new( + io, + password.ok_or(ZipError::PasswordIsNotSpecified)?, + )?), + EncryptionMethod::Unsupported => { + return Err(ZipError::UnsupportedEncryptionMethod.into()) + } + }) + } +} + +impl Read for Encryption { + fn read(&mut self, buf: &mut [u8]) -> IoResult { + match self { + Self::None(io) => io.read(buf), + Self::Weak(io) => io.read(buf), + } + } +} + +impl Seek for Encryption { + fn seek(&mut self, pos: SeekFrom) -> IoResult { + match self { + Self::None(io) => io.seek(pos), + _ => Err(IoError::new( + IoErrorKind::Unsupported, + ZipError::EncryptedDataIsUnseekable, + )), + } + } +} + enum Compression { Store(Io), Deflate(DeflateDecoder), @@ -18,8 +60,48 @@ enum Compression { Xz(XzDecoder), } +impl Compression { + pub fn new(io: Io, method: CompressionMethod) -> ZipResult { + Ok(match method { + CompressionMethod::Store => Self::Store(io), + CompressionMethod::Deflate => Self::Deflate(DeflateDecoder::new(io)), + CompressionMethod::BZip2 => Self::BZip2(BzDecoder::new(io)), + CompressionMethod::Lzma => panic!(), + CompressionMethod::Zstd => Self::Zstd(ZstdDecoder::new(io)?), + CompressionMethod::Xz => Self::Xz(XzDecoder::new(io)), + CompressionMethod::Unsupported(id) => { + return Err(ZipError::UnsupportedCompressionMethod(id).into()) + } + }) + } +} + +impl Read for Compression { + fn read(&mut self, buf: &mut [u8]) -> IoResult { + match self { + Compression::Store(io) => io.read(buf), + Compression::Deflate(io) => io.read(buf), + Compression::BZip2(io) => io.read(buf), + Compression::Zstd(io) => io.read(buf), + Compression::Xz(io) => io.read(buf), + } + } +} + +impl Seek for Compression { + fn seek(&mut self, pos: SeekFrom) -> IoResult { + match self { + Compression::Store(io) => io.seek(pos), + _ => Err(IoError::new( + IoErrorKind::Unsupported, + ZipError::CompressedDataIsUnseekable, + )), + } + } +} + pub struct ZipFileReader<'d, Io: Read> { - io: Compression>, + io: Compression>>, info: &'d ZipFileInfo, } @@ -33,7 +115,7 @@ impl<'d, Io: Read> FileDriver for ZipFileReader<'d, Io> { } impl<'d, Io: Read + Seek> ZipFileReader<'d, Io> { - pub fn new(io: &'d mut Io, info: &'d ZipFileInfo) -> ZipResult { + pub fn new(io: &'d mut Io, info: &'d ZipFileInfo, password: Option<&str>) -> ZipResult { io.seek(SeekFrom::Start(info.header_pointer))?; let buf = io.read_arr::<30>()?; @@ -45,27 +127,22 @@ impl<'d, Io: Read + Seek> ZipFileReader<'d, Io> { + 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 => Compression::Store(IoCursor::new( - io, - data_pointer, - data_pointer + info.compressed_size, - )?), - CompressionMethod::Deflate => Compression::Deflate(DeflateDecoder::new( - IoCursor::new(io, data_pointer, data_pointer + info.compressed_size)?, - )), - CompressionMethod::BZip2 => Compression::BZip2(BzDecoder::new(IoCursor::new( - io, - data_pointer, - data_pointer + info.compressed_size, - )?)), CompressionMethod::Lzma => { - io.seek(SeekFrom::Start(data_pointer))?; let buf = io.read_arr::<9>()?; - Compression::Xz(XzDecoder::new_stream( - IoCursor::new(io, data_pointer + 9, data_pointer + info.compressed_size)?, + Encryption::new( + IoCursor::new( + io, + data_pointer + 9, + data_pointer + info.compressed_size, + )?, + info.encryption_method, + password, + )?, Stream::new_raw_decoder( Filters::new().lzma1( LzmaOptions::new() @@ -80,30 +157,22 @@ impl<'d, Io: Read + Seek> ZipFileReader<'d, Io> { .unwrap(), )) } - CompressionMethod::Zstd => Compression::Zstd( - ZstdDecoder::new(IoCursor::new( - io, - data_pointer, - data_pointer + info.compressed_size, - )?) - .unwrap(), - ), - CompressionMethod::Xz => Compression::Xz(XzDecoder::new(IoCursor::new( - io, - data_pointer, - data_pointer + info.compressed_size, - )?)), - CompressionMethod::Unsupported => { - return Err(ZipError::UnsupportedCompressionMethod.into()) - } + _ => Compression::new( + Encryption::new( + IoCursor::new(io, data_pointer, data_pointer + info.compressed_size)?, + info.encryption_method, + password, + )?, + info.compression_method, + )?, }, info, }) } - pub fn seekable(&self) -> bool { + pub fn is_seekable(&self) -> bool { match self.io { - Compression::Store(..) => true, + Compression::Store(Encryption::None(..)) => true, _ => false, } } @@ -111,24 +180,12 @@ impl<'d, Io: Read + Seek> ZipFileReader<'d, Io> { impl<'d, Io: Read> Read for ZipFileReader<'d, Io> { fn read(&mut self, buf: &mut [u8]) -> IoResult { - match &mut self.io { - Compression::Store(io) => io.read(buf), - Compression::Deflate(io) => io.read(buf), - Compression::BZip2(io) => io.read(buf), - Compression::Zstd(io) => io.read(buf), - Compression::Xz(io) => io.read(buf), - } + self.io.read(buf) } } impl<'d, Io: Read + Seek> Seek for ZipFileReader<'d, Io> { fn seek(&mut self, pos: SeekFrom) -> IoResult { - match &mut self.io { - Compression::Store(io) => io.seek(pos), - _ => Err(IoError::new( - IoErrorKind::Unsupported, - ZipError::CompressedDataIsUnseekable, - )), - } + self.io.seek(pos) } } -- cgit v1.2.3