This commit is contained in:
Jean-Marie Mineau 2024-01-19 11:46:36 +01:00
parent 99ecf178df
commit 3f521b5754
Signed by: histausse
GPG key ID: B66AEEDA9B645AD2
4 changed files with 277 additions and 5 deletions

View file

@ -1,10 +1,14 @@
use std::io::{Read, Seek, SeekFrom, Write};
use std::io;
use std::io::{Cursor, Read, Seek, SeekFrom, Write};
use crate::compression::CompressionMethod;
use crate::end_of_central_directory::{
EndCentralDirectory, Zip64EndCentralDirectory, Zip64EndCentralDirectoryLocator,
};
use crate::{FileInfo, ZipFileReader};
use crate::{general_purpose_flags, FileHeader, FileInfo, LocalFileHeader, ZipFileReader};
use androscalpel_serializer::Serializable;
use flate2::write::DeflateEncoder;
use flate2::{Compression, CrcWriter};
pub struct ZipFileWriter<T: Write> {
files: Vec<FileInfo>,
@ -25,7 +29,7 @@ impl<T: Write> ZipFileWriter<T> {
}
}
/// Insert an file from an existing zip
/// Insert a file from an existing zip
pub fn insert_file_from_zip<U: Read + Seek>(
&mut self,
mut file_info: FileInfo,
@ -50,6 +54,146 @@ impl<T: Write> ZipFileWriter<T> {
self.files.push(file_info);
}
/// Insert a file
/// `header` provide for the values that are not computed from the file.
/// `local_header` can be provided is information in the local header differ
/// from the central directory header.
pub fn insert_file<U: Read + Seek>(
&mut self,
file: &mut U,
mut header: FileHeader,
local_header: Option<LocalFileHeader>,
) {
assert!(header.general_purpose_flags & general_purpose_flags::MASK_ENCRYPTED == 0);
assert!(
header.general_purpose_flags & general_purpose_flags::MASK_USE_DATA_DESCRIPTOR == 0
); // TODO
assert!(header.general_purpose_flags & general_purpose_flags::MASK_STRONG_ENCRYPTION == 0);
assert!(
header.general_purpose_flags & general_purpose_flags::MASK_ENCRYPTED_CENTRAL_DIR == 0
);
assert!(
header.general_purpose_flags
& (general_purpose_flags::MASK_COMPRESS_OPTION_1
| general_purpose_flags::MASK_COMPRESS_OPTION_2
| general_purpose_flags::MASK_UTF8_FILENAME)
== 0
);
assert!(
header.compression_method == CompressionMethod::Deflated
|| header.compression_method == CompressionMethod::Stored
);
let mut local_header = if let Some(header) = local_header {
assert!(header.general_purpose_flags & general_purpose_flags::MASK_ENCRYPTED == 0);
assert!(
header.general_purpose_flags & general_purpose_flags::MASK_USE_DATA_DESCRIPTOR == 0
); // TODO
assert!(
header.general_purpose_flags & general_purpose_flags::MASK_STRONG_ENCRYPTION == 0
);
assert!(
header.general_purpose_flags & general_purpose_flags::MASK_ENCRYPTED_CENTRAL_DIR
== 0
);
assert!(
header.general_purpose_flags
& (general_purpose_flags::MASK_COMPRESS_OPTION_1
| general_purpose_flags::MASK_COMPRESS_OPTION_2
| general_purpose_flags::MASK_UTF8_FILENAME)
== 0
);
assert!(
header.compression_method == CompressionMethod::Deflated
|| header.compression_method == CompressionMethod::Stored
);
LocalFileHeader {
version_needed_to_extract: header.version_needed_to_extract,
general_purpose_flags: if header.compression_method == CompressionMethod::Deflated {
header.general_purpose_flags
& (general_purpose_flags::MASK_COMPRESS_OPTION_1
| general_purpose_flags::MASK_COMPRESS_OPTION_2)
} else {
0
},
compression_method: header.compression_method,
last_mod_file_time: header.last_mod_file_time,
last_mod_file_data: header.last_mod_file_data,
crc_32: 0,
compressed_size: 0,
uncompressed_size: 0,
file_name: header.file_name,
extra_field: header.extra_field,
malformed_extra_field: header.malformed_extra_field.clone(),
decryption_header: None,
}
} else {
LocalFileHeader {
version_needed_to_extract: header.version_needed_to_extract,
general_purpose_flags: if header.compression_method == CompressionMethod::Deflated {
header.general_purpose_flags
& (general_purpose_flags::MASK_COMPRESS_OPTION_1
| general_purpose_flags::MASK_COMPRESS_OPTION_2)
} else {
0
},
compression_method: header.compression_method,
last_mod_file_time: header.last_mod_file_time,
last_mod_file_data: header.last_mod_file_data,
crc_32: 0,
compressed_size: 0,
uncompressed_size: 0,
file_name: header.file_name.clone(),
extra_field: header.extra_field.clone(),
malformed_extra_field: header.malformed_extra_field.clone(),
decryption_header: None,
}
};
let pos = file.stream_position().unwrap();
let pos_end = file.seek(SeekFrom::End(0)).unwrap();
file.seek(SeekFrom::Start(pos)).unwrap();
let uncompressed_size = pos_end - pos;
let header_offset = self.current_offset;
local_header.set_uncompressed_size(uncompressed_size);
let data = match local_header.compression_method {
CompressionMethod::Stored => {
let mut crc_writer = CrcWriter::new(Vec::new());
io::copy(file, &mut crc_writer).unwrap();
local_header.crc_32 = crc_writer.crc().sum();
crc_writer.into_inner()
}
CompressionMethod::Deflated => {
// TODO: find a way to do this in place, the compressed data can be large, and storing it
// in memory is bad, but Deflate consume the Reader, so we cannot juste give it self.data
// TODO: Compression::default -> nop, use flags
let mut compressor =
DeflateEncoder::new(CrcWriter::new(Vec::new()), Compression::default());
io::copy(file, &mut compressor).unwrap();
let finished = compressor.finish().unwrap();
local_header.crc_32 = finished.crc().sum();
finished.into_inner()
}
_ => unimplemented!(),
};
local_header.set_compressed_size(data.len() as u64);
local_header.serialize(&mut self.data).unwrap();
self.current_offset += local_header.size() as u64;
io::copy(&mut Cursor::new(data), &mut self.data).unwrap();
self.current_offset += local_header.get_compressed_size();
header.crc_32 = local_header.crc_32;
header.set_compressed_size(local_header.get_compressed_size());
header.set_uncompressed_size(local_header.get_uncompressed_size());
header.set_offset_local_header(header_offset);
let file_info = FileInfo {
local_header,
header,
};
self.files.push(file_info);
}
/// Finish the zip file by writing the central directory.
pub fn write_central_directory(&mut self) {
for file_info in &self.files {