use crate::driver::FileDriver; use crate::zip::{CompressionMethod, ZipError, ZipFileInfo, ZipResult}; use bzip2::read::BzDecoder; use flate2::read::DeflateDecoder; use liblzma::read::XzDecoder; use liblzma::stream::{Filters, LzmaOptions, Stream}; use std::io::{ Error as IoError, ErrorKind as IoErrorKind, Read, Result as IoResult, Seek, SeekFrom, }; 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; let mut cursor = 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 => { let buf = { let mut buf = [0; 9]; io.read(&mut buf)?; cursor += 9; buf }; IoProxy::Xz(XzDecoder::new_stream( io, Stream::new_raw_decoder( Filters::new().lzma1( LzmaOptions::new() .literal_context_bits((buf[4] % 9) as u32) .literal_position_bits((buf[4] / 9 % 5) as u32) .position_bits((buf[4] / 45) as u32) .dict_size( u32::from_le_bytes(buf[5..9].try_into().unwrap()).min(4096), ), ), ) .unwrap(), )) } CompressionMethod::Xz => IoProxy::Xz(XzDecoder::new(io)), }, info, bounds: (cursor, data_pointer + info.compressed_size), cursor, }) } 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::CompressedDataIsUnseekable, )), } } }