From ed24a41c436c8324b1183ad4e7a8035eed6a37b1 Mon Sep 17 00:00:00 2001 From: Leonard Lesinski <84378319+Le0X8@users.noreply.github.com> Date: Thu, 1 Aug 2024 20:42:36 +0200 Subject: [PATCH 1/3] feat!: Implemented #18 --- src/archive.rs | 156 ++++++++++++++++++++ src/file.rs | 55 ++++++- src/formats.rs | 29 ++++ src/formats/zip.rs | 178 +++++++++++++++++++++++ src/formats/zip/parser.rs | 41 +++--- src/formats/zip/writer.rs | 24 ++-- src/helpers/hash/crc32.rs | 2 +- src/lib.rs | 4 +- src/types.rs | 55 ------- tests/zip-external.rs | 295 ++++++++++++++++++++++++++++++++++++++ tests/zip.rs | 61 ++++---- 11 files changed, 784 insertions(+), 116 deletions(-) create mode 100644 src/archive.rs delete mode 100644 src/types.rs create mode 100644 tests/zip-external.rs diff --git a/src/archive.rs b/src/archive.rs new file mode 100644 index 0000000..27d8dfb --- /dev/null +++ b/src/archive.rs @@ -0,0 +1,156 @@ +use crate::{ + file::{FileEntry, FileReader, FileWriter, FsFile}, + formats::{self, zip::{ZipFile, ZipFileEntry}, Formats}, helpers::hash::crc32, +}; +use std::fs::create_dir_all; + +pub enum OriginalArchiveMetadata<'a> { + Zip(formats::zip::ZipArchiveMetadata<'a>), +} + +pub trait ArchiveMetadata<'a> { + fn get_format(&self) -> Formats; + fn get_files(&self) -> Vec<&dyn FileEntry>; + fn get_original(&'a self) -> OriginalArchiveMetadata<'a>; +} + +pub fn metadata<'a>( + format: Formats, + input: String, + check_integrity: bool, + buffer_size: u64, +) -> Result>, String> { + let mut file = FileReader::new(&input); + let metadata = match format { + Formats::Zip => { + let metadata = formats::zip::parser::metadata(&mut file); + if check_integrity { + if !formats::zip::parser::check_integrity_all( + &mut file, + &metadata.files, + &buffer_size, + ) { + return Err("Integrity check failed".to_string()); + } + } + OriginalArchiveMetadata::Zip(metadata) + } + }; + + Ok(Box::new(metadata)) +} + +pub fn extract( + format: Formats, + input: String, + output: String, + index: Option, + path: Option, + all: bool, + check_integrity: bool, + buffer_size: u64, +) -> Result<(), String> { + let mut file = FileReader::new(&input); + create_dir_all(&output).unwrap(); + + let metadata: &dyn ArchiveMetadata = match format { + Formats::Zip => { + let metadata = formats::zip::parser::metadata(&mut file); + if check_integrity { + if !formats::zip::parser::check_integrity_all( + &mut file, + &metadata.files, + &buffer_size, + ) { + return Err("Integrity check failed".to_string()); + } + } + &metadata.clone() as &dyn ArchiveMetadata + } + }; + + let files = metadata.get_files(); + + if all { + match format { + Formats::Zip => { + let zip_files = formats::zip::to_zip_entries(files); + formats::zip::parser::extract(&mut file, &zip_files, &buffer_size, &|path| { + format!("{}/{}", &output, &path) + }); + } + } + } else { + if index != None { + let index = index.unwrap(); + if index >= files.len() as u32 { + return Err("Index out of range".to_string()); + } + formats::zip::parser::extract( + &mut file, + &formats::zip::to_zip_entries(files), + &buffer_size, + &|path| format!("{}/{}", &output, &path), + ); + } else { + let path = path.unwrap(); + let files: Vec = metadata + .get_files() + .iter() + .filter_map(|file| { + if file.get_path().starts_with(&path) { + Some(formats::zip::to_zip_entry(*file)) + } else { + None + } + }) + .collect(); + formats::zip::parser::extract(&mut file, &files, &buffer_size, &|path| { + format!("{}/{}", &output, &path) + }); + } + }; + + Ok(()) +} + +pub struct EntrySource<'a> { + pub path: String, + pub source: &'a mut FsFile, +} + +pub fn create(format: Formats, output: String, input: &mut Vec, buffer_size: u64) -> Result<(), String> { + let mut file = FileWriter::new(&output, &false); + + match format { + Formats::Zip => { + let files: Vec = input.iter_mut().map(|entry| { + if entry.source.is_directory { + return ZipFile { + checksum: 0, + path: entry.path.clone(), + offset: 0, + size: 0, + modified: entry.source.modified, + is_directory: true, + source: None, + }; + }; + let size = entry.source.size.to_owned(); + let reader = entry.source.reader.as_mut().unwrap(); + ZipFile { + checksum: crc32::hash(reader, &0, &size, &buffer_size), + path: entry.path.clone(), + offset: 0, + size, + modified: entry.source.modified, + is_directory: entry.source.is_directory, + source: Some(reader), + } + }).collect(); + formats::zip::writer::write(&mut file, &mut formats::zip::ZipArchiveData { files }, &buffer_size); + } + } + + Ok(()) +} diff --git a/src/file.rs b/src/file.rs index adc7337..9b8b453 100644 --- a/src/file.rs +++ b/src/file.rs @@ -1,12 +1,65 @@ use std::{ cmp::min, - fs::{FileTimes, OpenOptions}, + fs::{self, FileTimes, OpenOptions}, io::{Read, Seek, Write}, mem::drop, }; use chrono::{DateTime, Utc}; +use crate::formats::zip::ZipFileEntry; + +pub struct FsFile { + pub size: u64, + pub reader: Option, + pub modified: DateTime, + pub is_directory: bool, +} + +impl FsFile { + pub fn new(path: &String) -> Self { + if fs::metadata(path).unwrap().is_dir() { + return Self { + size: 0, + reader: None, + modified: fs::metadata(path).unwrap().modified().unwrap().into(), + is_directory: true, + }; + }; + let reader = FileReader::new(path); + Self { + size: reader.get_size(), + modified: reader.get_times().modified, + reader: Some(reader), + is_directory: false, + } + } +} + +pub trait File { + fn get_path(&self) -> &String; + fn get_offset(&self) -> &u64; + fn get_size(&self) -> &u64; + fn get_modified(&self) -> &DateTime; + fn get_is_directory(&self) -> &bool; + fn get_source(&mut self) -> Option<&mut FileReader>; + fn get_checksum(&self) -> &u32; +} + +pub enum OriginalFileEntry<'a> { + Zip(&'a ZipFileEntry<'a>), +} + +pub trait FileEntry<'a> { + fn get_path(&self) -> &String; + fn get_offset(&self) -> &u64; + fn get_size(&self) -> &u64; + fn get_modified(&self) -> &DateTime; + fn get_is_directory(&self) -> &bool; + fn get_uncompressed_size(&self) -> &u32; + fn get_original(&'a self) -> OriginalFileEntry<'a>; +} + #[derive(Debug)] pub struct Times { pub created: DateTime, diff --git a/src/formats.rs b/src/formats.rs index 16a80cb..3e82799 100644 --- a/src/formats.rs +++ b/src/formats.rs @@ -1 +1,30 @@ +use crate::archive::ArchiveMetadata; + pub mod zip; + +pub enum Formats { + Zip, +} + +pub fn from_string(format: &String) -> Formats { + match format.as_str() { + "zip" => Formats::Zip, + _ => panic!("Unsupported format"), + } +} + +pub fn to_string(format: &Formats) -> String { + match format { + Formats::Zip => "zip".to_string(), + } +} + +pub enum FormatMetadata<'a> { + Zip(zip::ZipArchiveMetadata<'a>), +} + +pub fn to_format_metadata<'a>(format: Formats, metadata: &'a dyn ArchiveMetadata<'a>) -> FormatMetadata<'a> { + match format { + Formats::Zip => FormatMetadata::Zip(zip::to_zip_archive_metadata(metadata)), + } +} diff --git a/src/formats/zip.rs b/src/formats/zip.rs index 73b6279..fe5727a 100644 --- a/src/formats/zip.rs +++ b/src/formats/zip.rs @@ -1,3 +1,11 @@ +use chrono::{DateTime, Utc}; + +use crate::{ + archive::{ArchiveMetadata, OriginalArchiveMetadata}, + file::{File, FileEntry, FileReader, OriginalFileEntry}, + formats::Formats, +}; + pub mod parser; pub mod writer; @@ -8,3 +16,173 @@ Useful links: - http://justsolve.archiveteam.org/wiki/ZIP - https://developers.acridotheres.com/formats/zip */ + +#[derive(Debug)] +pub struct ZipArchiveMetadata<'a> { + pub files: Vec>, +} + +impl<'a> ArchiveMetadata<'a> for ZipArchiveMetadata<'a> { + fn get_format(&self) -> Formats { + Formats::Zip + } + + fn get_files(&self) -> Vec<&dyn FileEntry> { + self.files + .iter() + .map(|file| file as &dyn FileEntry) + .collect() + } + + fn get_original(&'a self) -> OriginalArchiveMetadata<'a> { + OriginalArchiveMetadata::Zip(self.clone()) + } +} + +impl<'a> Clone for ZipArchiveMetadata<'a> { + fn clone(&self) -> Self { + ZipArchiveMetadata { + files: self.files.clone(), + } + } +} + +pub fn to_zip_archive_metadata<'a>(from: &'a (dyn ArchiveMetadata<'a> + 'a)) -> ZipArchiveMetadata<'a> { + let original = from.get_original(); + match original { + OriginalArchiveMetadata::Zip(zip_archive) => zip_archive.clone(), + //_ => panic!("This could never happen, this is only here for type safety"), + } +} + +#[derive(Debug)] +pub struct ZipFileEntry<'a> { + pub path: String, + pub offset: u64, + pub size: u64, + pub modified: DateTime, + pub is_directory: bool, + pub uncompressed_size: u32, + pub checksum: u32, + pub extra_field: Vec, + pub version: u16, + pub bit_flag: u16, + pub compression: &'a str, +} + +impl<'a> FileEntry<'a> for ZipFileEntry<'a> { + fn get_path(&self) -> &String { + &self.path + } + + fn get_offset(&self) -> &u64 { + &self.offset + } + + fn get_size(&self) -> &u64 { + &self.size + } + + fn get_modified(&self) -> &DateTime { + &self.modified + } + + fn get_is_directory(&self) -> &bool { + &self.is_directory + } + + fn get_uncompressed_size(&self) -> &u32 { + &self.uncompressed_size + } + + fn get_original(&'a self) -> OriginalFileEntry<'a> { + OriginalFileEntry::Zip(&self) + } +} + +impl<'a> Clone for ZipFileEntry<'a> { + fn clone(&self) -> Self { + ZipFileEntry { + path: self.path.clone(), + offset: self.offset, + size: self.size, + modified: self.modified, + is_directory: self.is_directory, + uncompressed_size: self.uncompressed_size, + checksum: self.checksum, + extra_field: self.extra_field.clone(), + version: self.version, + bit_flag: self.bit_flag, + compression: self.compression, + } + } +} + +pub fn to_zip_entry<'a>(from: &'a (dyn FileEntry<'a> + 'a)) -> ZipFileEntry<'a> { + let original = from.get_original(); + match original { + OriginalFileEntry::Zip(zip_file) => zip_file.clone(), + //_ => panic!("This could never happen, this is only here for type safety"), + } +} + +pub fn to_zip_entries<'a>(from: Vec<&'a (dyn FileEntry<'a> + 'a)>) -> Vec> { + from.into_iter() + .map(|file| { + let original = file.get_original(); + match original { + OriginalFileEntry::Zip(zip_file) => zip_file.clone(), + //_ => panic!("This could never happen, this is only here for type safety"), + } + }) + .collect() +} + +#[derive(Debug)] +pub struct ZipFile<'a> { + pub path: String, + pub offset: u64, + pub size: u64, + pub modified: DateTime, + pub is_directory: bool, + pub source: Option<&'a mut FileReader>, + pub checksum: u32, +} + +impl File for ZipFile<'_> { + fn get_path(&self) -> &String { + &self.path + } + + fn get_offset(&self) -> &u64 { + &self.offset + } + + fn get_size(&self) -> &u64 { + &self.size + } + + fn get_modified(&self) -> &DateTime { + &self.modified + } + + fn get_is_directory(&self) -> &bool { + &self.is_directory + } + + fn get_source(&mut self) -> Option<&mut FileReader> { + match &mut self.source { + Some(source) => Some(*source), + None => None, + } + } + + fn get_checksum(&self) -> &u32 { + &self.checksum + } +} + +#[derive(Debug)] +pub struct ZipArchiveData<'a> { + pub files: Vec>, +} diff --git a/src/formats/zip/parser.rs b/src/formats/zip/parser.rs index 5eb3eea..12d7378 100644 --- a/src/formats/zip/parser.rs +++ b/src/formats/zip/parser.rs @@ -1,7 +1,8 @@ use crate::{ helpers::{datetime::msdos, hash::crc32}, - ArchiveMetadata, FileEntry, FileReader, FileWriter, ZipArchiveMetadata, ZipFileEntry, + file::{FileReader, FileWriter}, }; +use super::{ZipArchiveMetadata, ZipFileEntry}; pub fn metadata<'a>(file: &mut FileReader) -> ZipArchiveMetadata<'a> { let local_files = read_local_files(file); @@ -12,13 +13,12 @@ pub fn metadata<'a>(file: &mut FileReader) -> ZipArchiveMetadata<'a> { //println!("0x{:x}", signature); ZipArchiveMetadata { - archive: ArchiveMetadata { format: "zip" }, files: local_files.0, } } pub fn get_file(file: &mut FileReader, entry: &ZipFileEntry) -> Vec { - file.seek(&entry.file.offset); + file.seek(&entry.offset); file.read_u8array(&(entry.uncompressed_size as u64)) } @@ -26,17 +26,17 @@ pub fn extract( file: &mut FileReader, entries: &Vec, buffer_size: &u64, - path_rewriter: &dyn Fn(&str) -> String, + path_rewriter: &dyn Fn(&String) -> String, ) { for entry in entries { - let path = path_rewriter(&entry.file.path); - if !entry.file.is_directory { + let path = path_rewriter(&entry.path); + if !entry.is_directory { let mut target = FileWriter::new(&path, &false); file.export( - &entry.file.offset, - &entry.file.size, + &entry.offset, + &entry.size, &mut target, - &entry.file.modified.into(), + &entry.modified.into(), buffer_size, ); } else { @@ -93,13 +93,11 @@ fn read_local_files<'a>(file: &mut FileReader) -> (Vec>, u32) { let name = file.read_utf8(&name_length); let extra = file.read_u8array(&extra_length); files.push(ZipFileEntry { - file: FileEntry { - offset: file.get_position(), - size: size_compressed as u64, - modified: msdos::parse(&lastmod_date, &lastmod_time).into(), - is_directory: name.ends_with('/'), - path: name, - }, + offset: file.get_position(), + size: size_compressed as u64, + modified: msdos::parse(&lastmod_date, &lastmod_time).into(), + is_directory: name.ends_with('/'), + path: name, version, bit_flag, compression: compression_method, @@ -115,6 +113,15 @@ fn read_local_files<'a>(file: &mut FileReader) -> (Vec>, u32) { } pub fn check_integrity(source: &mut FileReader, file: &ZipFileEntry, buffer_size: &u64) -> bool { - let hash = crc32::hash(source, &file.file.offset, &file.file.size, buffer_size); + let hash = crc32::hash(source, &file.offset, &file.size, buffer_size); hash == file.checksum } + +pub fn check_integrity_all(source: &mut FileReader, files: &Vec, buffer_size: &u64) -> bool { + for file in files { + if !check_integrity(source, file, buffer_size) { + return false; + } + } + true +} diff --git a/src/formats/zip/writer.rs b/src/formats/zip/writer.rs index b87ce9f..90e93af 100644 --- a/src/formats/zip/writer.rs +++ b/src/formats/zip/writer.rs @@ -1,21 +1,25 @@ -use crate::{helpers::datetime::msdos, types::ZipArchiveData, FileWriter}; +use crate::{helpers::datetime::msdos, file::FileWriter}; +use super::ZipArchiveData; pub fn write(target: &mut FileWriter, data: &mut ZipArchiveData, buffer_size: &u64) { for file in &mut data.files { + if file.is_directory { + todo!(); + } target.write(b"PK\x03\x04"); let version: u16 = 20; let bit_flag: u16 = 0; let compression_method: u16 = 0; - let last_modified = msdos::serialize(&file.file.modified.into()); + let last_modified = msdos::serialize(&file.modified.into()); let lastmod_time = last_modified.1; let lastmod_date = last_modified.0; let crc32 = file.checksum; - let size_compressed = file.file.size as u32; - let size_uncompressed = file.file.size as u32; - let name_length = file.file.path.len() as u16; + let size_compressed = file.size as u32; + let size_uncompressed = file.size as u32; + let name_length = file.path.len() as u16; let extra_field_length: u16 = 0; - let name = &file.file.path; + let name = &file.path; let extra: &Vec = &vec![]; target.write_u16le(&version); @@ -31,11 +35,11 @@ pub fn write(target: &mut FileWriter, data: &mut ZipArchiveData, buffer_size: &u target.write_utf8(name); target.write_u8array(extra); - file.file.source.export( - &file.file.offset, - &file.file.size, + file.source.as_mut().unwrap().export( + &file.offset, + &file.size, target, - &file.file.modified.into(), + &file.modified.into(), buffer_size, ); } diff --git a/src/helpers/hash/crc32.rs b/src/helpers/hash/crc32.rs index a1a7ade..4a0f5a8 100644 --- a/src/helpers/hash/crc32.rs +++ b/src/helpers/hash/crc32.rs @@ -2,7 +2,7 @@ use std::cmp::min; use crc32fast::Hasher; -use crate::FileReader; +use crate::file::FileReader; pub fn hash(file: &mut FileReader, offset: &u64, size: &u64, buffer_size: &u64) -> u32 { let pos_before = file.get_position(); diff --git a/src/lib.rs b/src/lib.rs index 8f369e6..04b62b1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,7 @@ pub mod file; pub mod formats; pub mod helpers; -pub mod types; -pub use file::*; -pub use types::*; +pub mod archive; pub fn get_version() -> &'static str { env!("CARGO_PKG_VERSION") diff --git a/src/types.rs b/src/types.rs deleted file mode 100644 index 9e497ba..0000000 --- a/src/types.rs +++ /dev/null @@ -1,55 +0,0 @@ -use chrono::{DateTime, Utc}; - -use crate::FileReader; - -#[derive(Debug)] -pub struct ArchiveMetadata { - pub format: &'static str, -} - -#[derive(Debug)] -pub struct ZipArchiveMetadata<'a> { - pub archive: ArchiveMetadata, - pub files: Vec>, -} - -#[derive(Debug)] -pub struct FileEntry { - pub path: String, - pub offset: u64, - pub size: u64, - pub modified: DateTime, - pub is_directory: bool, -} - -#[derive(Debug)] -pub struct ZipFileEntry<'a> { - pub file: FileEntry, - pub uncompressed_size: u32, - pub checksum: u32, - pub extra_field: Vec, - pub version: u16, - pub bit_flag: u16, - pub compression: &'a str, -} - -#[derive(Debug)] -pub struct File<'a> { - pub path: String, - pub offset: u64, - pub size: u64, - pub modified: DateTime, - pub is_directory: bool, - pub source: &'a mut FileReader, -} - -#[derive(Debug)] -pub struct ZipFile<'a> { - pub file: File<'a>, - pub checksum: u32, -} - -#[derive(Debug)] -pub struct ZipArchiveData<'a> { - pub files: Vec>, -} diff --git a/tests/zip-external.rs b/tests/zip-external.rs new file mode 100644 index 0000000..4df2b01 --- /dev/null +++ b/tests/zip-external.rs @@ -0,0 +1,295 @@ +use corelib::{archive::{self, EntrySource, OriginalArchiveMetadata}, file::{FileReader, FileWriter, FsFile}, formats::Formats}; + +#[test] +fn sample_000_metadata() { + let metadata = match *archive::metadata( + Formats::Zip, + "tests/samples/zip/000.zip".to_string(), + true, + 1024, + ) + .unwrap() + { + OriginalArchiveMetadata::Zip(metadata) => metadata, + }; + assert_eq!(metadata.files.len(), 1); + assert_eq!(metadata.files[0].path, "test.txt"); + assert_eq!(metadata.files[0].size, 14); + assert_eq!(metadata.files[0].compression, "stored"); + assert_eq!(metadata.files[0].uncompressed_size, 14); + assert_eq!( + metadata.files[0].modified.to_rfc3339(), + "2024-07-11T18:14:42+00:00" + ); +} + +#[test] +fn sample_000_extract() { + std::fs::create_dir_all("tests/samples/zip/000-external").unwrap(); + archive::extract( + Formats::Zip, + "tests/samples/zip/000.zip".to_string(), + "tests/samples/zip/000-external".to_string(), + None, + None, + true, + true, + 1024, + ) + .unwrap(); + let mut reader = FileReader::new(&"tests/samples/zip/000-external/test.txt".to_string()); + assert_eq!(reader.read_utf8(&reader.get_size()), "Hello, world!\n"); + reader.close(); + std::fs::remove_dir_all("tests/samples/zip/000-external").unwrap(); +} + +#[test] +fn sample_001_metadata() { + let metadata = match *archive::metadata( + Formats::Zip, + "tests/samples/zip/001.zip".to_string(), + true, + 1024, + ) + .unwrap() + { + OriginalArchiveMetadata::Zip(metadata) => metadata, + }; + assert_eq!(metadata.files.len(), 2); + assert_eq!(metadata.files[0].path, "test.txt"); + assert_eq!(metadata.files[0].size, 14); + assert_eq!(metadata.files[0].compression, "stored"); + assert_eq!(metadata.files[0].uncompressed_size, 14); + assert_eq!( + metadata.files[0].modified.to_rfc3339(), + "2024-07-12T18:11:08+00:00" + ); + assert_eq!(metadata.files[1].path, "test2.txt"); + assert_eq!(metadata.files[1].size, 16); + assert_eq!(metadata.files[1].compression, "stored"); + assert_eq!(metadata.files[1].uncompressed_size, 16); + assert_eq!( + metadata.files[1].modified.to_rfc3339(), + "2024-07-12T18:11:26+00:00" + ); +} + +#[test] +fn sample_001_extract() { + std::fs::create_dir_all("tests/samples/zip/001-external").unwrap(); + archive::extract( + Formats::Zip, + "tests/samples/zip/001.zip".to_string(), + "tests/samples/zip/001-external".to_string(), + None, + None, + true, + true, + 1024, + ) + .unwrap(); + let mut reader = FileReader::new(&"tests/samples/zip/001-external/test.txt".to_string()); + assert_eq!(reader.read_utf8(&reader.get_size()), "Hello, world!\n"); + reader.close(); + let mut reader = FileReader::new(&"tests/samples/zip/001-external/test2.txt".to_string()); + assert_eq!(reader.read_utf8(&reader.get_size()), "Hello, world! 2\n"); + reader.close(); + std::fs::remove_dir_all("tests/samples/zip/001-external").unwrap(); +} + +#[test] +fn sample_002_metadata() { + let metadata = match *archive::metadata( + Formats::Zip, + "tests/samples/zip/002.zip".to_string(), + true, + 1024, + ) + .unwrap() + { + OriginalArchiveMetadata::Zip(metadata) => metadata, + }; + assert_eq!(metadata.files.len(), 3); + assert_eq!(metadata.files[0].path, "test/"); + assert_eq!(metadata.files[0].size, 0); + assert_eq!(metadata.files[0].compression, "stored"); + assert_eq!(metadata.files[0].uncompressed_size, 0); + assert_eq!( + metadata.files[0].modified.to_rfc3339(), + "2024-07-13T14:27:00+00:00" + ); + assert_eq!(metadata.files[1].path, "test/test.txt"); + assert_eq!(metadata.files[1].size, 14); + assert_eq!(metadata.files[1].compression, "stored"); + assert_eq!(metadata.files[1].uncompressed_size, 14); + assert_eq!( + metadata.files[1].modified.to_rfc3339(), + "2024-07-13T14:26:48+00:00" + ); + assert_eq!(metadata.files[2].path, "test.txt"); + assert_eq!(metadata.files[2].size, 14); + assert_eq!(metadata.files[2].compression, "stored"); + assert_eq!(metadata.files[2].uncompressed_size, 14); + assert_eq!( + metadata.files[2].modified.to_rfc3339(), + "2024-07-13T14:26:48+00:00" + ); +} + +#[test] +fn sample_002_extract() { + std::fs::create_dir_all("tests/samples/zip/002-external").unwrap(); + archive::extract( + Formats::Zip, + "tests/samples/zip/002.zip".to_string(), + "tests/samples/zip/002-external".to_string(), + None, + None, + true, + true, + 1024, + ) + .unwrap(); + let mut reader = FileReader::new(&"tests/samples/zip/002-external/test.txt".to_string()); + assert_eq!(reader.read_utf8(&reader.get_size()), "Hello, world!\n"); + reader.close(); + let mut reader = FileReader::new(&"tests/samples/zip/002-external/test/test.txt".to_string()); + assert_eq!(reader.read_utf8(&reader.get_size()), "Hello, world!\n"); + reader.close(); + std::fs::remove_dir_all("tests/samples/zip/002-external").unwrap(); +} + +#[test] +fn create_000_metadata() { + std::fs::create_dir_all("tests/samples/zip/c000-external").unwrap(); + let mut test_txt = FileWriter::new(&"tests/samples/zip/c000-external/test.txt".to_string(), &false); + test_txt.write(b"Hello, world!\n"); + test_txt.close(); + + archive::create( + Formats::Zip, + "tests/samples/zip/c000-external.zip".to_string(), + &mut vec![EntrySource { + path: "test.txt".to_string(), + source: &mut FsFile::new(&"tests/samples/zip/c000-external/test.txt".to_string()), + }], + 1024, + ).unwrap(); + + std::fs::remove_dir_all("tests/samples/zip/c000-external").unwrap(); + + let metadata = match *archive::metadata( + Formats::Zip, + "tests/samples/zip/c000-external.zip".to_string(), + true, + 1024, + ).unwrap() { + OriginalArchiveMetadata::Zip(metadata) => metadata + }; + + assert_eq!(metadata.files.len(), 1); + assert_eq!(metadata.files[0].path, "test.txt"); + assert_eq!(metadata.files[0].size, 14); + assert_eq!(metadata.files[0].compression, "stored"); + assert_eq!(metadata.files[0].uncompressed_size, 14); + + std::fs::remove_file("tests/samples/zip/c000-external.zip").unwrap(); +} + +#[test] +fn create_000_extract() { + std::fs::create_dir_all("tests/samples/zip/c000-external2").unwrap(); // this is has another name to avoid conflicts + let mut test_txt = FileWriter::new(&"tests/samples/zip/c000-external2/test.txt".to_string(), &false); + test_txt.write(b"Hello, world!\n"); + test_txt.close(); + + std::fs::create_dir_all("tests/samples/zip/c000-external2").unwrap(); + let mut test_txt = FileWriter::new(&"tests/samples/zip/c000-external2/test.txt".to_string(), &false); + test_txt.write(b"Hello, world!\n"); + test_txt.close(); + + archive::create( + Formats::Zip, + "tests/samples/zip/c000-external2.zip".to_string(), + &mut vec![EntrySource { + path: "test.txt".to_string(), + source: &mut FsFile::new(&"tests/samples/zip/c000-external2/test.txt".to_string()), + }], + 1024, + ).unwrap(); + + std::fs::remove_dir_all("tests/samples/zip/c000-external2").unwrap(); + + archive::extract( + Formats::Zip, + "tests/samples/zip/c000-external2.zip".to_string(), + "tests/samples/zip/c000-external2".to_string(), + None, + None, + true, + true, + 1024, + ).unwrap(); + + let mut reader = FileReader::new(&"tests/samples/zip/c000-external2/test.txt".to_string()); + assert_eq!(reader.read_utf8(&reader.get_size()), "Hello, world!\n"); + reader.close(); + + std::fs::remove_dir_all("tests/samples/zip/c000-external2").unwrap(); + std::fs::remove_file("tests/samples/zip/c000-external2.zip").unwrap(); +} + +/* +#[test] +fn create_000() { + let mut output = FileWriter::new(&"tests/samples/zip/c000.zip".to_string(), &false); + + std::fs::create_dir_all("tests/samples/zip/c000").unwrap(); + let mut test_txt = FileWriter::new(&"tests/samples/zip/c000/test.txt".to_string(), &false); + test_txt.write(b"Hello, world!\n"); + test_txt.close(); + + let mut input = FileReader::new(&"tests/samples/zip/c000/test.txt".to_string()); + let size = input.get_size(); + corelib::formats::zip::writer::write( + &mut output, + &mut ZipArchiveData { + files: vec![ZipFile { + checksum: crc32::hash(&mut input, &0, &size, &1024), + path: "test.txt".to_string(), + offset: 0, + size, + modified: input.get_times().modified, + is_directory: false, + source: &mut input, + }], + }, + &1024, + ); + input.close(); + + output.close(); + + let mut file = FileReader::new(&"tests/samples/zip/c000.zip".to_string()); + + let metadata = corelib::formats::zip::parser::metadata(&mut file); + assert_eq!(metadata.files.len(), 1); + assert_eq!(metadata.files[0].path, "test.txt"); + assert_eq!(metadata.files[0].size, 14); + assert_eq!(metadata.files[0].compression, "stored"); + assert_eq!(metadata.files[0].uncompressed_size, 14); + + let test_txt = corelib::formats::zip::parser::get_file(&mut file, &metadata.files[0]); + assert_eq!(String::from_utf8(test_txt).unwrap(), "Hello, world!\n"); + + std::fs::remove_dir_all("tests/samples/zip/c000").unwrap(); + + assert!(corelib::formats::zip::parser::check_integrity( + &mut file, + &metadata.files[0], + &1024 + )); + + std::fs::remove_file("tests/samples/zip/c000.zip").unwrap(); +} +*/ diff --git a/tests/zip.rs b/tests/zip.rs index 5c81c3a..1ca08f9 100644 --- a/tests/zip.rs +++ b/tests/zip.rs @@ -1,4 +1,9 @@ -use corelib::{self, helpers::hash::crc32, File, FileReader, FileWriter, ZipArchiveData, ZipFile}; +use corelib::{ + self, + file::{FileReader, FileWriter}, + formats::zip::{ZipArchiveData, ZipFile}, + helpers::hash::crc32, +}; #[test] fn sample_000() { @@ -6,12 +11,12 @@ fn sample_000() { let metadata = corelib::formats::zip::parser::metadata(&mut file); assert_eq!(metadata.files.len(), 1); - assert_eq!(metadata.files[0].file.path, "test.txt"); - assert_eq!(metadata.files[0].file.size, 14); + assert_eq!(metadata.files[0].path, "test.txt"); + assert_eq!(metadata.files[0].size, 14); assert_eq!(metadata.files[0].compression, "stored"); assert_eq!(metadata.files[0].uncompressed_size, 14); assert_eq!( - metadata.files[0].file.modified.to_rfc3339(), + metadata.files[0].modified.to_rfc3339(), "2024-07-11T18:14:42+00:00" ); @@ -31,20 +36,20 @@ fn sample_001() { let metadata = corelib::formats::zip::parser::metadata(&mut file); assert_eq!(metadata.files.len(), 2); - assert_eq!(metadata.files[0].file.path, "test.txt"); - assert_eq!(metadata.files[0].file.size, 14); + assert_eq!(metadata.files[0].path, "test.txt"); + assert_eq!(metadata.files[0].size, 14); assert_eq!(metadata.files[0].compression, "stored"); assert_eq!(metadata.files[0].uncompressed_size, 14); assert_eq!( - metadata.files[0].file.modified.to_rfc3339(), + metadata.files[0].modified.to_rfc3339(), "2024-07-12T18:11:08+00:00" ); - assert_eq!(metadata.files[1].file.path, "test2.txt"); - assert_eq!(metadata.files[1].file.size, 16); + assert_eq!(metadata.files[1].path, "test2.txt"); + assert_eq!(metadata.files[1].size, 16); assert_eq!(metadata.files[1].compression, "stored"); assert_eq!(metadata.files[1].uncompressed_size, 16); assert_eq!( - metadata.files[1].file.modified.to_rfc3339(), + metadata.files[1].modified.to_rfc3339(), "2024-07-12T18:11:26+00:00" ); @@ -90,28 +95,28 @@ fn sample_002() { let metadata = corelib::formats::zip::parser::metadata(&mut file); assert_eq!(metadata.files.len(), 3); - assert_eq!(metadata.files[0].file.path, "test/"); - assert_eq!(metadata.files[0].file.size, 0); + assert_eq!(metadata.files[0].path, "test/"); + assert_eq!(metadata.files[0].size, 0); assert_eq!(metadata.files[0].compression, "stored"); assert_eq!(metadata.files[0].uncompressed_size, 0); assert_eq!( - metadata.files[0].file.modified.to_rfc3339(), + metadata.files[0].modified.to_rfc3339(), "2024-07-13T14:27:00+00:00" ); - assert_eq!(metadata.files[1].file.path, "test/test.txt"); - assert_eq!(metadata.files[1].file.size, 14); + assert_eq!(metadata.files[1].path, "test/test.txt"); + assert_eq!(metadata.files[1].size, 14); assert_eq!(metadata.files[1].compression, "stored"); assert_eq!(metadata.files[1].uncompressed_size, 14); assert_eq!( - metadata.files[1].file.modified.to_rfc3339(), + metadata.files[1].modified.to_rfc3339(), "2024-07-13T14:26:48+00:00" ); - assert_eq!(metadata.files[2].file.path, "test.txt"); - assert_eq!(metadata.files[2].file.size, 14); + assert_eq!(metadata.files[2].path, "test.txt"); + assert_eq!(metadata.files[2].size, 14); assert_eq!(metadata.files[2].compression, "stored"); assert_eq!(metadata.files[2].uncompressed_size, 14); assert_eq!( - metadata.files[2].file.modified.to_rfc3339(), + metadata.files[2].modified.to_rfc3339(), "2024-07-13T14:26:48+00:00" ); @@ -172,14 +177,12 @@ fn create_000() { &mut ZipArchiveData { files: vec![ZipFile { checksum: crc32::hash(&mut input, &0, &size, &1024), - file: File { - path: "test.txt".to_string(), - offset: 0, - size, - modified: input.get_times().modified, - is_directory: false, - source: &mut input, - }, + path: "test.txt".to_string(), + offset: 0, + size, + modified: input.get_times().modified, + is_directory: false, + source: Some(&mut input), }], }, &1024, @@ -192,8 +195,8 @@ fn create_000() { let metadata = corelib::formats::zip::parser::metadata(&mut file); assert_eq!(metadata.files.len(), 1); - assert_eq!(metadata.files[0].file.path, "test.txt"); - assert_eq!(metadata.files[0].file.size, 14); + assert_eq!(metadata.files[0].path, "test.txt"); + assert_eq!(metadata.files[0].size, 14); assert_eq!(metadata.files[0].compression, "stored"); assert_eq!(metadata.files[0].uncompressed_size, 14); From 5a0d4490c90b6a8f54b0d5caea88341b3972c90b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 1 Aug 2024 18:42:59 +0000 Subject: [PATCH 2/3] Format Rust code using rustfmt --- src/archive.rs | 65 ++++++++++++++++++++++++--------------- src/formats.rs | 5 ++- src/formats/zip.rs | 4 ++- src/formats/zip/parser.rs | 10 ++++-- src/formats/zip/writer.rs | 2 +- src/lib.rs | 2 +- tests/zip-external.rs | 36 ++++++++++++++++------ 7 files changed, 84 insertions(+), 40 deletions(-) diff --git a/src/archive.rs b/src/archive.rs index 27d8dfb..cf4edb5 100644 --- a/src/archive.rs +++ b/src/archive.rs @@ -1,6 +1,11 @@ use crate::{ file::{FileEntry, FileReader, FileWriter, FsFile}, - formats::{self, zip::{ZipFile, ZipFileEntry}, Formats}, helpers::hash::crc32, + formats::{ + self, + zip::{ZipFile, ZipFileEntry}, + Formats, + }, + helpers::hash::crc32, }; use std::fs::create_dir_all; @@ -119,36 +124,48 @@ pub struct EntrySource<'a> { pub source: &'a mut FsFile, } -pub fn create(format: Formats, output: String, input: &mut Vec, buffer_size: u64) -> Result<(), String> { +pub fn create( + format: Formats, + output: String, + input: &mut Vec, + buffer_size: u64, +) -> Result<(), String> { let mut file = FileWriter::new(&output, &false); match format { Formats::Zip => { - let files: Vec = input.iter_mut().map(|entry| { - if entry.source.is_directory { - return ZipFile { - checksum: 0, + let files: Vec = input + .iter_mut() + .map(|entry| { + if entry.source.is_directory { + return ZipFile { + checksum: 0, + path: entry.path.clone(), + offset: 0, + size: 0, + modified: entry.source.modified, + is_directory: true, + source: None, + }; + }; + let size = entry.source.size.to_owned(); + let reader = entry.source.reader.as_mut().unwrap(); + ZipFile { + checksum: crc32::hash(reader, &0, &size, &buffer_size), path: entry.path.clone(), offset: 0, - size: 0, + size, modified: entry.source.modified, - is_directory: true, - source: None, - }; - }; - let size = entry.source.size.to_owned(); - let reader = entry.source.reader.as_mut().unwrap(); - ZipFile { - checksum: crc32::hash(reader, &0, &size, &buffer_size), - path: entry.path.clone(), - offset: 0, - size, - modified: entry.source.modified, - is_directory: entry.source.is_directory, - source: Some(reader), - } - }).collect(); - formats::zip::writer::write(&mut file, &mut formats::zip::ZipArchiveData { files }, &buffer_size); + is_directory: entry.source.is_directory, + source: Some(reader), + } + }) + .collect(); + formats::zip::writer::write( + &mut file, + &mut formats::zip::ZipArchiveData { files }, + &buffer_size, + ); } } diff --git a/src/formats.rs b/src/formats.rs index 3e82799..1241e31 100644 --- a/src/formats.rs +++ b/src/formats.rs @@ -23,7 +23,10 @@ pub enum FormatMetadata<'a> { Zip(zip::ZipArchiveMetadata<'a>), } -pub fn to_format_metadata<'a>(format: Formats, metadata: &'a dyn ArchiveMetadata<'a>) -> FormatMetadata<'a> { +pub fn to_format_metadata<'a>( + format: Formats, + metadata: &'a dyn ArchiveMetadata<'a>, +) -> FormatMetadata<'a> { match format { Formats::Zip => FormatMetadata::Zip(zip::to_zip_archive_metadata(metadata)), } diff --git a/src/formats/zip.rs b/src/formats/zip.rs index fe5727a..54aacd1 100644 --- a/src/formats/zip.rs +++ b/src/formats/zip.rs @@ -47,7 +47,9 @@ impl<'a> Clone for ZipArchiveMetadata<'a> { } } -pub fn to_zip_archive_metadata<'a>(from: &'a (dyn ArchiveMetadata<'a> + 'a)) -> ZipArchiveMetadata<'a> { +pub fn to_zip_archive_metadata<'a>( + from: &'a (dyn ArchiveMetadata<'a> + 'a), +) -> ZipArchiveMetadata<'a> { let original = from.get_original(); match original { OriginalArchiveMetadata::Zip(zip_archive) => zip_archive.clone(), diff --git a/src/formats/zip/parser.rs b/src/formats/zip/parser.rs index 12d7378..d51dc0b 100644 --- a/src/formats/zip/parser.rs +++ b/src/formats/zip/parser.rs @@ -1,8 +1,8 @@ +use super::{ZipArchiveMetadata, ZipFileEntry}; use crate::{ - helpers::{datetime::msdos, hash::crc32}, file::{FileReader, FileWriter}, + helpers::{datetime::msdos, hash::crc32}, }; -use super::{ZipArchiveMetadata, ZipFileEntry}; pub fn metadata<'a>(file: &mut FileReader) -> ZipArchiveMetadata<'a> { let local_files = read_local_files(file); @@ -117,7 +117,11 @@ pub fn check_integrity(source: &mut FileReader, file: &ZipFileEntry, buffer_size hash == file.checksum } -pub fn check_integrity_all(source: &mut FileReader, files: &Vec, buffer_size: &u64) -> bool { +pub fn check_integrity_all( + source: &mut FileReader, + files: &Vec, + buffer_size: &u64, +) -> bool { for file in files { if !check_integrity(source, file, buffer_size) { return false; diff --git a/src/formats/zip/writer.rs b/src/formats/zip/writer.rs index 90e93af..d5067d3 100644 --- a/src/formats/zip/writer.rs +++ b/src/formats/zip/writer.rs @@ -1,5 +1,5 @@ -use crate::{helpers::datetime::msdos, file::FileWriter}; use super::ZipArchiveData; +use crate::{file::FileWriter, helpers::datetime::msdos}; pub fn write(target: &mut FileWriter, data: &mut ZipArchiveData, buffer_size: &u64) { for file in &mut data.files { diff --git a/src/lib.rs b/src/lib.rs index 04b62b1..9bdf20b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,7 @@ +pub mod archive; pub mod file; pub mod formats; pub mod helpers; -pub mod archive; pub fn get_version() -> &'static str { env!("CARGO_PKG_VERSION") diff --git a/tests/zip-external.rs b/tests/zip-external.rs index 4df2b01..8d24148 100644 --- a/tests/zip-external.rs +++ b/tests/zip-external.rs @@ -1,4 +1,8 @@ -use corelib::{archive::{self, EntrySource, OriginalArchiveMetadata}, file::{FileReader, FileWriter, FsFile}, formats::Formats}; +use corelib::{ + archive::{self, EntrySource, OriginalArchiveMetadata}, + file::{FileReader, FileWriter, FsFile}, + formats::Formats, +}; #[test] fn sample_000_metadata() { @@ -162,7 +166,10 @@ fn sample_002_extract() { #[test] fn create_000_metadata() { std::fs::create_dir_all("tests/samples/zip/c000-external").unwrap(); - let mut test_txt = FileWriter::new(&"tests/samples/zip/c000-external/test.txt".to_string(), &false); + let mut test_txt = FileWriter::new( + &"tests/samples/zip/c000-external/test.txt".to_string(), + &false, + ); test_txt.write(b"Hello, world!\n"); test_txt.close(); @@ -174,7 +181,8 @@ fn create_000_metadata() { source: &mut FsFile::new(&"tests/samples/zip/c000-external/test.txt".to_string()), }], 1024, - ).unwrap(); + ) + .unwrap(); std::fs::remove_dir_all("tests/samples/zip/c000-external").unwrap(); @@ -183,8 +191,10 @@ fn create_000_metadata() { "tests/samples/zip/c000-external.zip".to_string(), true, 1024, - ).unwrap() { - OriginalArchiveMetadata::Zip(metadata) => metadata + ) + .unwrap() + { + OriginalArchiveMetadata::Zip(metadata) => metadata, }; assert_eq!(metadata.files.len(), 1); @@ -199,12 +209,18 @@ fn create_000_metadata() { #[test] fn create_000_extract() { std::fs::create_dir_all("tests/samples/zip/c000-external2").unwrap(); // this is has another name to avoid conflicts - let mut test_txt = FileWriter::new(&"tests/samples/zip/c000-external2/test.txt".to_string(), &false); + let mut test_txt = FileWriter::new( + &"tests/samples/zip/c000-external2/test.txt".to_string(), + &false, + ); test_txt.write(b"Hello, world!\n"); test_txt.close(); std::fs::create_dir_all("tests/samples/zip/c000-external2").unwrap(); - let mut test_txt = FileWriter::new(&"tests/samples/zip/c000-external2/test.txt".to_string(), &false); + let mut test_txt = FileWriter::new( + &"tests/samples/zip/c000-external2/test.txt".to_string(), + &false, + ); test_txt.write(b"Hello, world!\n"); test_txt.close(); @@ -216,7 +232,8 @@ fn create_000_extract() { source: &mut FsFile::new(&"tests/samples/zip/c000-external2/test.txt".to_string()), }], 1024, - ).unwrap(); + ) + .unwrap(); std::fs::remove_dir_all("tests/samples/zip/c000-external2").unwrap(); @@ -229,7 +246,8 @@ fn create_000_extract() { true, true, 1024, - ).unwrap(); + ) + .unwrap(); let mut reader = FileReader::new(&"tests/samples/zip/c000-external2/test.txt".to_string()); assert_eq!(reader.read_utf8(&reader.get_size()), "Hello, world!\n"); From 703649cdc2cfc59a54b3d5f8481ce4a1aa370b04 Mon Sep 17 00:00:00 2001 From: Leonard Lesinski <84378319+Le0X8@users.noreply.github.com> Date: Thu, 1 Aug 2024 21:01:28 +0200 Subject: [PATCH 3/3] chore: Removed unused comment --- tests/zip-external.rs | 55 ------------------------------------------- 1 file changed, 55 deletions(-) diff --git a/tests/zip-external.rs b/tests/zip-external.rs index 8d24148..1cdae52 100644 --- a/tests/zip-external.rs +++ b/tests/zip-external.rs @@ -256,58 +256,3 @@ fn create_000_extract() { std::fs::remove_dir_all("tests/samples/zip/c000-external2").unwrap(); std::fs::remove_file("tests/samples/zip/c000-external2.zip").unwrap(); } - -/* -#[test] -fn create_000() { - let mut output = FileWriter::new(&"tests/samples/zip/c000.zip".to_string(), &false); - - std::fs::create_dir_all("tests/samples/zip/c000").unwrap(); - let mut test_txt = FileWriter::new(&"tests/samples/zip/c000/test.txt".to_string(), &false); - test_txt.write(b"Hello, world!\n"); - test_txt.close(); - - let mut input = FileReader::new(&"tests/samples/zip/c000/test.txt".to_string()); - let size = input.get_size(); - corelib::formats::zip::writer::write( - &mut output, - &mut ZipArchiveData { - files: vec![ZipFile { - checksum: crc32::hash(&mut input, &0, &size, &1024), - path: "test.txt".to_string(), - offset: 0, - size, - modified: input.get_times().modified, - is_directory: false, - source: &mut input, - }], - }, - &1024, - ); - input.close(); - - output.close(); - - let mut file = FileReader::new(&"tests/samples/zip/c000.zip".to_string()); - - let metadata = corelib::formats::zip::parser::metadata(&mut file); - assert_eq!(metadata.files.len(), 1); - assert_eq!(metadata.files[0].path, "test.txt"); - assert_eq!(metadata.files[0].size, 14); - assert_eq!(metadata.files[0].compression, "stored"); - assert_eq!(metadata.files[0].uncompressed_size, 14); - - let test_txt = corelib::formats::zip::parser::get_file(&mut file, &metadata.files[0]); - assert_eq!(String::from_utf8(test_txt).unwrap(), "Hello, world!\n"); - - std::fs::remove_dir_all("tests/samples/zip/c000").unwrap(); - - assert!(corelib::formats::zip::parser::check_integrity( - &mut file, - &metadata.files[0], - &1024 - )); - - std::fs::remove_file("tests/samples/zip/c000.zip").unwrap(); -} -*/