diff options
| -rw-r--r-- | .vscode/settings.json | 8 | ||||
| -rw-r--r-- | Cargo.toml | 2 | ||||
| -rw-r--r-- | src/archive.rs | 12 | ||||
| -rw-r--r-- | src/driver/driver.rs | 19 | ||||
| -rw-r--r-- | src/zip/driver.rs | 88 | ||||
| -rw-r--r-- | src/zip/error.rs | 2 | ||||
| -rw-r--r-- | src/zip/file/info.rs (renamed from src/zip/file_info.rs) | 8 | ||||
| -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 | ||||
| -rw-r--r-- | src/zip/file_driver.rs | 98 | ||||
| -rw-r--r-- | src/zip/mod.rs | 6 | ||||
| -rw-r--r-- | src/zip/tests.rs | 2 | ||||
| -rw-r--r-- | tests/files/archive.zip | bin | 0 -> 609 bytes | |||
| -rw-r--r-- | tests/files/zip.zip | bin | 466 -> 0 bytes | |||
| -rw-r--r-- | tests/zip.rs | 34 |
16 files changed, 267 insertions, 174 deletions
diff --git a/.vscode/settings.json b/.vscode/settings.json index de3e119..b0a2099 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json | |||
| @@ -5,11 +5,15 @@ | |||
| 5 | "chrono", | 5 | "chrono", |
| 6 | "datatypes", | 6 | "datatypes", |
| 7 | "datetime", | 7 | "datetime", |
| 8 | "decompressor", | ||
| 8 | "eocdr", | 9 | "eocdr", |
| 9 | "LZMA", | 10 | "flate", |
| 11 | "lzma", | ||
| 10 | "newtype", | 12 | "newtype", |
| 11 | "repr", | 13 | "repr", |
| 12 | "rposition" | 14 | "rposition", |
| 15 | "seekable", | ||
| 16 | "unseekable" | ||
| 13 | ], | 17 | ], |
| 14 | "rust-analyzer.linkedProjects": ["./Cargo.toml", "./Cargo.toml"] | 18 | "rust-analyzer.linkedProjects": ["./Cargo.toml", "./Cargo.toml"] |
| 15 | } | 19 | } |
| @@ -12,6 +12,8 @@ categories = ["compression", "filesystem"] | |||
| 12 | exclude = [".vscode/"] | 12 | exclude = [".vscode/"] |
| 13 | 13 | ||
| 14 | [dependencies] | 14 | [dependencies] |
| 15 | bzip2 = "0.4.4" | ||
| 15 | chrono = "0.4.29" | 16 | chrono = "0.4.29" |
| 16 | flate2 = "1.0.30" | 17 | flate2 = "1.0.30" |
| 17 | serde = { version = "1.0.203", features = ["derive"] } | 18 | serde = { version = "1.0.203", features = ["derive"] } |
| 19 | xz2 = "0.1.7" | ||
diff --git a/src/archive.rs b/src/archive.rs index 8a9e8de..2dab393 100644 --- a/src/archive.rs +++ b/src/archive.rs | |||
| @@ -25,19 +25,19 @@ where | |||
| 25 | Self::read(File::open(path)?) | 25 | Self::read(File::open(path)?) |
| 26 | } | 26 | } |
| 27 | 27 | ||
| 28 | pub fn files(&self) -> &Vec<D::FileInfo> { | 28 | pub fn files(&self) -> Vec<&D::FileInfo> { |
| 29 | self.driver.files() | 29 | self.driver.files() |
| 30 | } | 30 | } |
| 31 | 31 | ||
| 32 | pub fn get_file_info(&self, index: usize) -> ArchiveResult<&D::FileInfo, D::Error> { | 32 | pub fn get_file_info(&self, name: &str) -> ArchiveResult<&D::FileInfo, D::Error> { |
| 33 | self.driver.get_file_info(index) | 33 | self.driver.get_file_info(name) |
| 34 | } | 34 | } |
| 35 | 35 | ||
| 36 | pub fn get_file_reader<'d>( | 36 | pub fn get_file_reader<'d>( |
| 37 | &'d mut self, | 37 | &'d mut self, |
| 38 | index: usize, | 38 | name: &str, |
| 39 | ) -> ArchiveResult<ArchiveFile<D::FileDriver<'d>>, D::Error> { | 39 | ) -> ArchiveResult<ArchiveFile<D::FileReader<'d>>, D::Error> { |
| 40 | Ok(ArchiveFile::new(self.driver.get_file_reader(index)?)) | 40 | Ok(ArchiveFile::new(self.driver.get_file_reader(name)?)) |
| 41 | } | 41 | } |
| 42 | } | 42 | } |
| 43 | 43 | ||
diff --git a/src/driver/driver.rs b/src/driver/driver.rs index 4c6baad..f7f1f24 100644 --- a/src/driver/driver.rs +++ b/src/driver/driver.rs | |||
| @@ -8,34 +8,37 @@ pub trait Driver: Sized { | |||
| 8 | 8 | ||
| 9 | type Io; | 9 | type Io; |
| 10 | type FileInfo: ArchiveFileInfo; | 10 | type FileInfo: ArchiveFileInfo; |
| 11 | type FileDriver<'d>: FileDriver | ||
| 12 | where | ||
| 13 | Self::FileInfo: 'd, | ||
| 14 | Self::Io: 'd; | ||
| 15 | } | 11 | } |
| 16 | 12 | ||
| 17 | pub trait ArchiveRead: Driver | 13 | pub trait ArchiveRead: Driver |
| 18 | where | 14 | where |
| 19 | Self::Io: Read, | 15 | Self::Io: Read, |
| 20 | { | 16 | { |
| 17 | type FileReader<'d>: FileDriver | ||
| 18 | where | ||
| 19 | Self: 'd; | ||
| 20 | |||
| 21 | // Create driver instance | 21 | // Create driver instance |
| 22 | fn read(io: Self::Io) -> ArchiveResult<Self, Self::Error>; | 22 | fn read(io: Self::Io) -> ArchiveResult<Self, Self::Error>; |
| 23 | 23 | ||
| 24 | // Return vec of file info | 24 | // Return vec of file info |
| 25 | fn files(&self) -> &Vec<Self::FileInfo>; | 25 | fn files(&self) -> Vec<&Self::FileInfo>; |
| 26 | 26 | ||
| 27 | // Return file info by index | 27 | // Return file info by index |
| 28 | fn get_file_info(&self, index: usize) -> ArchiveResult<&Self::FileInfo, Self::Error>; | 28 | fn get_file_info(&self, name: &str) -> ArchiveResult<&Self::FileInfo, Self::Error>; |
| 29 | 29 | ||
| 30 | // Return file reader by index | 30 | // Return file reader by index |
| 31 | fn get_file_reader<'d>( | 31 | fn get_file_reader<'d>( |
| 32 | &'d mut self, | 32 | &'d mut self, |
| 33 | index: usize, | 33 | name: &str, |
| 34 | ) -> ArchiveResult<Self::FileDriver<'d>, Self::Error>; | 34 | ) -> ArchiveResult<Self::FileReader<'d>, Self::Error>; |
| 35 | } | 35 | } |
| 36 | 36 | ||
| 37 | pub trait ArchiveWrite: Driver | 37 | pub trait ArchiveWrite: Driver |
| 38 | where | 38 | where |
| 39 | Self::Io: Read + Write, | 39 | Self::Io: Read + Write, |
| 40 | { | 40 | { |
| 41 | type FileWriter<'d>: FileDriver | ||
| 42 | where | ||
| 43 | Self: 'd; | ||
| 41 | } | 44 | } |
diff --git a/src/zip/driver.rs b/src/zip/driver.rs index a280dc7..e276844 100644 --- a/src/zip/driver.rs +++ b/src/zip/driver.rs | |||
| @@ -1,13 +1,17 @@ | |||
| 1 | use crate::driver::{ArchiveRead, ArchiveWrite, Driver}; | 1 | use crate::driver::{ArchiveRead, ArchiveWrite, Driver}; |
| 2 | use crate::zip::structs::{deserialize, EOCDR64Locator, ExtraHeader, CDR, EOCDR, EOCDR64}; | 2 | use crate::zip::structs::{deserialize, EOCDR64Locator, ExtraHeader, CDR, EOCDR, EOCDR64}; |
| 3 | use crate::zip::{BitFlag, CompressionMethod, ZipError, ZipFile, ZipFileInfo, ZipResult}; | 3 | use crate::zip::{ |
| 4 | BitFlag, CompressionMethod, ZipError, ZipFileInfo, ZipFileReader, ZipFileWriter, ZipResult, | ||
| 5 | }; | ||
| 4 | use chrono::{Local, NaiveDate, NaiveDateTime, NaiveTime}; | 6 | use chrono::{Local, NaiveDate, NaiveDateTime, NaiveTime}; |
| 7 | use std::collections::HashMap as Map; | ||
| 8 | use std::fs::File; | ||
| 5 | use std::io::{Read, Seek, SeekFrom, Write}; | 9 | use std::io::{Read, Seek, SeekFrom, Write}; |
| 6 | 10 | ||
| 7 | pub struct Zip<Io> { | 11 | pub struct Zip<Io = File> { |
| 8 | io: Io, | 12 | io: Io, |
| 9 | 13 | ||
| 10 | files: Vec<ZipFileInfo>, | 14 | files: Map<String, ZipFileInfo>, |
| 11 | comment: String, | 15 | comment: String, |
| 12 | } | 16 | } |
| 13 | 17 | ||
| @@ -15,11 +19,12 @@ impl<Io> Driver for Zip<Io> { | |||
| 15 | type Error = ZipError; | 19 | type Error = ZipError; |
| 16 | 20 | ||
| 17 | type Io = Io; | 21 | type Io = Io; |
| 18 | type FileDriver<'d> = ZipFile<'d, Self::Io> where Self::Io: 'd; | ||
| 19 | type FileInfo = ZipFileInfo; | 22 | type FileInfo = ZipFileInfo; |
| 20 | } | 23 | } |
| 21 | 24 | ||
| 22 | impl<Io: Read + Seek> ArchiveRead for Zip<Io> { | 25 | impl<Io: Read + Seek> ArchiveRead for Zip<Io> { |
| 26 | type FileReader<'d> = ZipFileReader<'d, Io> where Io: 'd; | ||
| 27 | |||
| 23 | fn read(mut io: Self::Io) -> ZipResult<Self> { | 28 | fn read(mut io: Self::Io) -> ZipResult<Self> { |
| 24 | // Search eocdr | 29 | // Search eocdr |
| 25 | let limit = 65557.min(io.seek(SeekFrom::End(0))?) as i64; | 30 | let limit = 65557.min(io.seek(SeekFrom::End(0))?) as i64; |
| @@ -79,7 +84,7 @@ impl<Io: Read + Seek> ArchiveRead for Zip<Io> { | |||
| 79 | }; | 84 | }; |
| 80 | 85 | ||
| 81 | // Read cd records | 86 | // Read cd records |
| 82 | let mut files = Vec::with_capacity(cd_records as usize); | 87 | let mut files = Map::with_capacity(cd_records as usize); |
| 83 | io.seek(SeekFrom::Start(cd_pointer))?; | 88 | io.seek(SeekFrom::Start(cd_pointer))?; |
| 84 | let buf = { | 89 | let buf = { |
| 85 | let mut buf = vec![0; cd_size as usize]; | 90 | let mut buf = vec![0; cd_size as usize]; |
| @@ -134,49 +139,54 @@ impl<Io: Read + Seek> ArchiveRead for Zip<Io> { | |||
| 134 | } | 139 | } |
| 135 | } | 140 | } |
| 136 | 141 | ||
| 137 | files.push(ZipFileInfo::new( | 142 | files.insert( |
| 138 | CompressionMethod::from_struct_id(cdr.compression_method)?, | 143 | name.clone(), |
| 139 | BitFlag::new(cdr.bit_flag), | 144 | ZipFileInfo::new( |
| 140 | NaiveDateTime::new( | 145 | CompressionMethod::from_struct_id(cdr.compression_method)?, |
| 141 | NaiveDate::from_ymd_opt( | 146 | BitFlag::new(cdr.bit_flag), |
| 142 | (cdr.dos_date as i32 >> 9 & 0x7F) + 1980, | 147 | NaiveDateTime::new( |
| 143 | cdr.dos_date as u32 >> 5 & 0xF, | 148 | NaiveDate::from_ymd_opt( |
| 144 | cdr.dos_date as u32 & 0x1F, | 149 | (cdr.dos_date as i32 >> 9 & 0x7F) + 1980, |
| 145 | ) | 150 | cdr.dos_date as u32 >> 5 & 0xF, |
| 146 | .ok_or(ZipError::InvalidDate)?, | 151 | cdr.dos_date as u32 & 0x1F, |
| 147 | NaiveTime::from_hms_opt( | 152 | ) |
| 148 | (cdr.dos_time as u32 >> 11) & 0x1F, | 153 | .ok_or(ZipError::InvalidDate)?, |
| 149 | (cdr.dos_time as u32 >> 5) & 0x3F, | 154 | NaiveTime::from_hms_opt( |
| 150 | (cdr.dos_time as u32 & 0x1F) * 2, | 155 | (cdr.dos_time as u32 >> 11) & 0x1F, |
| 156 | (cdr.dos_time as u32 >> 5) & 0x3F, | ||
| 157 | (cdr.dos_time as u32 & 0x1F) * 2, | ||
| 158 | ) | ||
| 159 | .ok_or(ZipError::InvalidTime)?, | ||
| 151 | ) | 160 | ) |
| 152 | .ok_or(ZipError::InvalidTime)?, | 161 | .and_local_timezone(Local) |
| 153 | ) | 162 | .unwrap(), |
| 154 | .and_local_timezone(Local) | 163 | cdr.crc, |
| 155 | .unwrap(), | 164 | compressed_size, |
| 156 | cdr.crc, | 165 | size, |
| 157 | compressed_size, | 166 | header_pointer, |
| 158 | size, | 167 | name, |
| 159 | header_pointer, | 168 | comment, |
| 160 | name, | 169 | ), |
| 161 | comment, | 170 | ); |
| 162 | )); | ||
| 163 | } | 171 | } |
| 164 | 172 | ||
| 165 | Ok(Self { io, files, comment }) | 173 | Ok(Self { io, files, comment }) |
| 166 | } | 174 | } |
| 167 | 175 | ||
| 168 | fn files(&self) -> &Vec<Self::FileInfo> { | 176 | fn files(&self) -> Vec<&Self::FileInfo> { |
| 169 | &self.files | 177 | let mut files: Vec<&Self::FileInfo> = self.files.values().collect(); |
| 178 | files.sort_by_key(|f| &f.name); | ||
| 179 | files | ||
| 170 | } | 180 | } |
| 171 | 181 | ||
| 172 | fn get_file_info(&self, index: usize) -> ZipResult<&Self::FileInfo> { | 182 | fn get_file_info(&self, name: &str) -> ZipResult<&Self::FileInfo> { |
| 173 | self.files.get(index).ok_or(ZipError::FileNotFound.into()) | 183 | self.files.get(name).ok_or(ZipError::FileNotFound.into()) |
| 174 | } | 184 | } |
| 175 | 185 | ||
| 176 | fn get_file_reader<'d>(&'d mut self, index: usize) -> ZipResult<Self::FileDriver<'d>> { | 186 | fn get_file_reader<'d>(&'d mut self, name: &str) -> ZipResult<Self::FileReader<'d>> { |
| 177 | Ok(ZipFile::new( | 187 | Ok(ZipFileReader::new( |
| 178 | &mut self.io, | 188 | &mut self.io, |
| 179 | self.files.get(index).ok_or(ZipError::FileNotFound)?, | 189 | self.files.get(name).ok_or(ZipError::FileNotFound)?, |
| 180 | )?) | 190 | )?) |
| 181 | } | 191 | } |
| 182 | } | 192 | } |
| @@ -187,6 +197,8 @@ impl<Io: Read + Seek> Zip<Io> { | |||
| 187 | } | 197 | } |
| 188 | } | 198 | } |
| 189 | 199 | ||
| 190 | impl<Io: Read + Write + Seek> ArchiveWrite for Zip<Io> {} | 200 | impl<Io: Read + Write + Seek> ArchiveWrite for Zip<Io> { |
| 201 | type FileWriter<'d> = ZipFileWriter<'d, Io> where Io: 'd; | ||
| 202 | } | ||
| 191 | 203 | ||
| 192 | impl<Io: Read + Write + Seek> Zip<Io> {} | 204 | impl<Io: Read + Write + Seek> Zip<Io> {} |
diff --git a/src/zip/error.rs b/src/zip/error.rs index 3acbf8c..5659f93 100644 --- a/src/zip/error.rs +++ b/src/zip/error.rs | |||
| @@ -21,6 +21,7 @@ pub enum ZipError { | |||
| 21 | 21 | ||
| 22 | NegativeFileOffset, | 22 | NegativeFileOffset, |
| 23 | FileNotFound, | 23 | FileNotFound, |
| 24 | CompressionDataIsUnseekable, | ||
| 24 | } | 25 | } |
| 25 | 26 | ||
| 26 | impl From<ZipError> for ArchiveError<ZipError> { | 27 | impl From<ZipError> for ArchiveError<ZipError> { |
| @@ -59,6 +60,7 @@ impl Display for ZipError { | |||
| 59 | 60 | ||
| 60 | Self::NegativeFileOffset => write!(f, "Negative file offset"), | 61 | Self::NegativeFileOffset => write!(f, "Negative file offset"), |
| 61 | Self::FileNotFound => write!(f, "File not found"), | 62 | Self::FileNotFound => write!(f, "File not found"), |
| 63 | Self::CompressionDataIsUnseekable => write!(f, "Compression data is unseekable"), | ||
| 62 | } | 64 | } |
| 63 | } | 65 | } |
| 64 | } | 66 | } |
diff --git a/src/zip/file_info.rs b/src/zip/file/info.rs index 92d52f7..2891b59 100644 --- a/src/zip/file_info.rs +++ b/src/zip/file/info.rs | |||
| @@ -6,9 +6,8 @@ use chrono::{DateTime, Local}; | |||
| 6 | pub enum CompressionMethod { | 6 | pub enum CompressionMethod { |
| 7 | Store, | 7 | Store, |
| 8 | Deflate, | 8 | Deflate, |
| 9 | BZIP2, | 9 | BZip2, |
| 10 | LZMA, | 10 | LZMA, |
| 11 | ZStd, | ||
| 12 | XZ, | 11 | XZ, |
| 13 | } | 12 | } |
| 14 | 13 | ||
| @@ -17,11 +16,10 @@ impl CompressionMethod { | |||
| 17 | match id { | 16 | match id { |
| 18 | 0 => Ok(Self::Store), | 17 | 0 => Ok(Self::Store), |
| 19 | 8 => Ok(Self::Deflate), | 18 | 8 => Ok(Self::Deflate), |
| 20 | 12 => Ok(Self::BZIP2), | 19 | 12 => Ok(Self::BZip2), |
| 21 | 14 => Ok(Self::LZMA), | 20 | 14 => Ok(Self::LZMA), |
| 22 | 93 => Ok(Self::ZStd), | ||
| 23 | 95 => Ok(Self::XZ), | 21 | 95 => Ok(Self::XZ), |
| 24 | 1..=7 | 9..=11 | 13 | 15..=20 | 94 | 96..=99 => { | 22 | 1..=7 | 9..=11 | 13 | 15..=20 | 93..=94 | 96..=99 => { |
| 25 | Err(ZipError::UnsupportedCompressionMethod.into()) | 23 | Err(ZipError::UnsupportedCompressionMethod.into()) |
| 26 | } | 24 | } |
| 27 | 21..=92 | 100.. => Err(ZipError::InvalidCompressionMethod.into()), | 25 | 21..=92 | 100.. => Err(ZipError::InvalidCompressionMethod.into()), |
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 | } | ||
diff --git a/src/zip/file_driver.rs b/src/zip/file_driver.rs deleted file mode 100644 index 51075e4..0000000 --- a/src/zip/file_driver.rs +++ /dev/null | |||
| @@ -1,98 +0,0 @@ | |||
| 1 | use crate::driver::FileDriver; | ||
| 2 | use crate::zip::{ZipError, ZipFileInfo, ZipResult}; | ||
| 3 | use std::io::{ | ||
| 4 | Error as IoError, ErrorKind as IoErrorKind, Read, Result as IoResult, Seek, SeekFrom, Write, | ||
| 5 | }; | ||
| 6 | |||
| 7 | pub struct ZipFile<'d, Io> { | ||
| 8 | io: &'d mut Io, | ||
| 9 | info: &'d ZipFileInfo, | ||
| 10 | |||
| 11 | bounds: (u64, u64), | ||
| 12 | cursor: u64, | ||
| 13 | } | ||
| 14 | |||
| 15 | impl<'d, Io> FileDriver for ZipFile<'d, Io> { | ||
| 16 | type Io = Io; | ||
| 17 | type FileInfo = ZipFileInfo; | ||
| 18 | |||
| 19 | fn info(&self) -> &Self::FileInfo { | ||
| 20 | self.info | ||
| 21 | } | ||
| 22 | } | ||
| 23 | |||
| 24 | impl<'d, Io: Read + Seek> ZipFile<'d, Io> { | ||
| 25 | pub fn new(io: &'d mut Io, info: &'d ZipFileInfo) -> ZipResult<Self> { | ||
| 26 | io.seek(SeekFrom::Start(info.header_pointer))?; | ||
| 27 | let buf = { | ||
| 28 | let mut buf = [0; 30]; | ||
| 29 | io.read(&mut buf)?; | ||
| 30 | buf | ||
| 31 | }; | ||
| 32 | if u32::from_le_bytes(buf[..4].try_into().unwrap()) != 0x04034b50 { | ||
| 33 | return Err(ZipError::InvalidFileHeaderSignature.into()); | ||
| 34 | } | ||
| 35 | let data_pointer = info.header_pointer | ||
| 36 | + 30 | ||
| 37 | + u16::from_le_bytes(buf[26..28].try_into().unwrap()) as u64 | ||
| 38 | + u16::from_le_bytes(buf[28..30].try_into().unwrap()) as u64; | ||
| 39 | io.seek(SeekFrom::Start(data_pointer))?; | ||
| 40 | |||
| 41 | Ok(Self { | ||
| 42 | io, | ||
| 43 | info, | ||
| 44 | |||
| 45 | bounds: (data_pointer, data_pointer + info.compressed_size), | ||
| 46 | cursor: data_pointer, | ||
| 47 | }) | ||
| 48 | } | ||
| 49 | } | ||
| 50 | |||
| 51 | impl<'d, Io: Read> Read for ZipFile<'d, Io> { | ||
| 52 | fn read(&mut self, buf: &mut [u8]) -> IoResult<usize> { | ||
| 53 | let upper = buf.len().min((self.bounds.1 - self.cursor) as usize); | ||
| 54 | self.cursor += upper as u64; | ||
| 55 | self.io.read(&mut buf[..upper]) | ||
| 56 | } | ||
| 57 | } | ||
| 58 | |||
| 59 | impl<'d, Io: Write> Write for ZipFile<'d, Io> { | ||
| 60 | fn write(&mut self, buf: &[u8]) -> IoResult<usize> { | ||
| 61 | todo!() | ||
| 62 | } | ||
| 63 | |||
| 64 | fn flush(&mut self) -> IoResult<()> { | ||
| 65 | todo!() | ||
| 66 | } | ||
| 67 | } | ||
| 68 | |||
| 69 | impl<'d, Io: Seek> Seek for ZipFile<'d, Io> { | ||
| 70 | fn seek(&mut self, pos: SeekFrom) -> IoResult<u64> { | ||
| 71 | self.cursor = match pos { | ||
| 72 | SeekFrom::Start(offset) => self.bounds.0 + offset, | ||
| 73 | SeekFrom::End(offset) => { | ||
| 74 | let cursor = self.bounds.1.saturating_add_signed(offset); | ||
| 75 | if cursor < self.bounds.0 { | ||
| 76 | return Err(IoError::new( | ||
| 77 | IoErrorKind::InvalidInput, | ||
| 78 | ZipError::NegativeFileOffset, | ||
| 79 | )); | ||
| 80 | } | ||
| 81 | cursor | ||
| 82 | } | ||
| 83 | SeekFrom::Current(offset) => { | ||
| 84 | let cursor = self.cursor.saturating_add_signed(offset); | ||
| 85 | if cursor < self.bounds.0 { | ||
| 86 | return Err(IoError::new( | ||
| 87 | IoErrorKind::InvalidInput, | ||
| 88 | ZipError::NegativeFileOffset, | ||
| 89 | )); | ||
| 90 | } | ||
| 91 | cursor | ||
| 92 | } | ||
| 93 | } | ||
| 94 | .min(self.bounds.1); | ||
| 95 | |||
| 96 | Ok(self.io.seek(SeekFrom::Start(self.cursor))? - self.bounds.0) | ||
| 97 | } | ||
| 98 | } | ||
diff --git a/src/zip/mod.rs b/src/zip/mod.rs index 3fe8384..0f19824 100644 --- a/src/zip/mod.rs +++ b/src/zip/mod.rs | |||
| @@ -1,14 +1,12 @@ | |||
| 1 | mod archive; | 1 | mod archive; |
| 2 | mod driver; | 2 | mod driver; |
| 3 | mod error; | 3 | mod error; |
| 4 | mod file_driver; | 4 | mod file; |
| 5 | mod file_info; | ||
| 6 | mod structs; | 5 | mod structs; |
| 7 | 6 | ||
| 8 | pub use driver::Zip; | 7 | pub use driver::Zip; |
| 9 | pub use error::{ZipError, ZipResult}; | 8 | pub use error::{ZipError, ZipResult}; |
| 10 | pub use file_driver::ZipFile; | 9 | pub use file::{bit, BitFlag, CompressionMethod, ZipFileInfo, ZipFileReader, ZipFileWriter}; |
| 11 | pub use file_info::{bit, BitFlag, CompressionMethod, ZipFileInfo}; | ||
| 12 | 10 | ||
| 13 | #[cfg(test)] | 11 | #[cfg(test)] |
| 14 | mod tests; | 12 | mod tests; |
diff --git a/src/zip/tests.rs b/src/zip/tests.rs index 05e076d..92a9c3f 100644 --- a/src/zip/tests.rs +++ b/src/zip/tests.rs | |||
| @@ -1,4 +1,4 @@ | |||
| 1 | use crate::zip::file_info::{bit::DeflateMode, BitFlag}; | 1 | use crate::zip::{bit::DeflateMode, BitFlag}; |
| 2 | 2 | ||
| 3 | #[test] | 3 | #[test] |
| 4 | fn test_bit_flag() { | 4 | fn test_bit_flag() { |
diff --git a/tests/files/archive.zip b/tests/files/archive.zip new file mode 100644 index 0000000..f5c0137 --- /dev/null +++ b/tests/files/archive.zip | |||
| Binary files differ | |||
diff --git a/tests/files/zip.zip b/tests/files/zip.zip deleted file mode 100644 index f3a090b..0000000 --- a/tests/files/zip.zip +++ /dev/null | |||
| Binary files differ | |||
diff --git a/tests/zip.rs b/tests/zip.rs index e5872c5..eb195f2 100644 --- a/tests/zip.rs +++ b/tests/zip.rs | |||
| @@ -3,7 +3,7 @@ use std::io::{Read, Seek, SeekFrom}; | |||
| 3 | 3 | ||
| 4 | #[test] | 4 | #[test] |
| 5 | fn test_zip() { | 5 | fn test_zip() { |
| 6 | let mut archive = Archive::<Zip<_>>::read_from_file("tests/files/zip.zip").unwrap(); | 6 | let mut archive = Archive::<Zip<_>>::read_from_file("tests/files/archive.zip").unwrap(); |
| 7 | 7 | ||
| 8 | assert_eq!(archive.comment(), "archive comment"); | 8 | assert_eq!(archive.comment(), "archive comment"); |
| 9 | assert_eq!( | 9 | assert_eq!( |
| @@ -12,36 +12,46 @@ fn test_zip() { | |||
| 12 | .iter() | 12 | .iter() |
| 13 | .map(|f| &f.name) | 13 | .map(|f| &f.name) |
| 14 | .collect::<Vec<&String>>(), | 14 | .collect::<Vec<&String>>(), |
| 15 | vec!["b", "c", "a"] | 15 | vec!["bzip", "deflate", "lzma", "store", "xz"] |
| 16 | ); | 16 | ); |
| 17 | 17 | ||
| 18 | let mut f = archive.get_file_reader(2).unwrap(); | 18 | let mut f = archive.get_file_reader("store").unwrap(); |
| 19 | 19 | ||
| 20 | let mut data = String::new(); | 20 | let mut data = String::new(); |
| 21 | f.read_to_string(&mut data).unwrap(); | 21 | f.read_to_string(&mut data).unwrap(); |
| 22 | assert_eq!(data, "file data \"a\""); | 22 | assert_eq!(data, "test file data"); |
| 23 | 23 | ||
| 24 | assert_eq!(f.seek(SeekFrom::Start(5)).unwrap(), 5); | 24 | assert_eq!(f.seek(SeekFrom::Start(5)).unwrap(), 5); |
| 25 | let mut data = String::new(); | 25 | let mut data = String::new(); |
| 26 | f.read_to_string(&mut data).unwrap(); | 26 | f.read_to_string(&mut data).unwrap(); |
| 27 | assert_eq!(data, "data \"a\""); | 27 | assert_eq!(data, "file data"); |
| 28 | 28 | ||
| 29 | assert_eq!(f.seek(SeekFrom::Start(0)).unwrap(), 0); | 29 | assert_eq!(f.seek(SeekFrom::Start(0)).unwrap(), 0); |
| 30 | let mut data = vec![0; 4]; | 30 | let mut data = vec![0; 4]; |
| 31 | f.read(&mut data).unwrap(); | 31 | f.read(&mut data).unwrap(); |
| 32 | assert_eq!(String::from_utf8(data).unwrap(), "file"); | 32 | assert_eq!(String::from_utf8(data).unwrap(), "test"); |
| 33 | 33 | ||
| 34 | assert_eq!(f.seek(SeekFrom::Current(1)).unwrap(), 5); | 34 | assert_eq!(f.seek(SeekFrom::Current(1)).unwrap(), 5); |
| 35 | let mut data = vec![0; 4]; | 35 | let mut data = vec![0; 4]; |
| 36 | f.read(&mut data).unwrap(); | 36 | f.read(&mut data).unwrap(); |
| 37 | assert_eq!(String::from_utf8(data).unwrap(), "data"); | 37 | assert_eq!(String::from_utf8(data).unwrap(), "file"); |
| 38 | 38 | ||
| 39 | assert_eq!(f.seek(SeekFrom::End(-3)).unwrap(), 10); | 39 | assert_eq!(f.seek(SeekFrom::End(-4)).unwrap(), 10); |
| 40 | let mut data = vec![0; 3]; | 40 | let mut data = vec![0; 4]; |
| 41 | f.read(&mut data).unwrap(); | 41 | f.read(&mut data).unwrap(); |
| 42 | assert_eq!(String::from_utf8(data).unwrap(), "\"a\""); | 42 | assert_eq!(String::from_utf8(data).unwrap(), "data"); |
| 43 | 43 | ||
| 44 | assert_eq!(f.seek(SeekFrom::Current(0)).unwrap(), 13); | 44 | f.seek(SeekFrom::Start(7)).unwrap(); |
| 45 | assert_eq!(f.seek(SeekFrom::Current(0)).unwrap(), 7); | ||
| 45 | f.seek(SeekFrom::End(-100)).unwrap_err(); | 46 | f.seek(SeekFrom::End(-100)).unwrap_err(); |
| 46 | assert_eq!(f.seek(SeekFrom::Current(0)).unwrap(), 13); | 47 | assert_eq!(f.seek(SeekFrom::Current(0)).unwrap(), 7); |
| 48 | |||
| 49 | assert_eq!(f.seek(SeekFrom::Start(100)).unwrap(), 14); | ||
| 50 | |||
| 51 | for name in ["store", "deflate", "bzip", "lzma", "xz"] { | ||
| 52 | let mut f = archive.get_file_reader(name).unwrap(); | ||
| 53 | let mut data = String::new(); | ||
| 54 | f.read_to_string(&mut data).unwrap(); | ||
| 55 | assert_eq!(data, "test file data"); | ||
| 56 | } | ||
| 47 | } | 57 | } |
