feat: support append to an existing archive

This commit is contained in:
zhanghar 2020-12-23 14:37:09 -05:00
parent 229fe94bdf
commit 10da026ff6
3 changed files with 63 additions and 3 deletions

View file

@ -209,7 +209,7 @@ fn make_reader<'a>(
impl<R: Read + io::Seek> ZipArchive<R> { impl<R: Read + io::Seek> ZipArchive<R> {
/// Get the directory start offset and number of files. This is done in a /// Get the directory start offset and number of files. This is done in a
/// separate function to ease the control flow design. /// separate function to ease the control flow design.
fn get_directory_counts( pub fn get_directory_counts(
reader: &mut R, reader: &mut R,
footer: &spec::CentralDirectoryEnd, footer: &spec::CentralDirectoryEnd,
cde_start_pos: u64, cde_start_pos: u64,
@ -514,7 +514,8 @@ fn unsupported_zip_error<T>(detail: &'static str) -> ZipResult<T> {
Err(ZipError::UnsupportedArchive(detail)) Err(ZipError::UnsupportedArchive(detail))
} }
fn central_header_to_zip_file<R: Read + io::Seek>( /// Parse a central directory entry to collect the information for the file.
pub fn central_header_to_zip_file<R: Read + io::Seek>(
reader: &mut R, reader: &mut R,
archive_offset: u64, archive_offset: u64,
) -> ZipResult<ZipFileData> { ) -> ZipResult<ZipFileData> {

View file

@ -1,7 +1,7 @@
//! Types for creating ZIP archives //! Types for creating ZIP archives
use crate::compression::CompressionMethod; use crate::compression::CompressionMethod;
use crate::read::ZipFile; use crate::read::{central_header_to_zip_file, ZipArchive, ZipFile};
use crate::result::{ZipError, ZipResult}; use crate::result::{ZipError, ZipResult};
use crate::spec; use crate::spec;
use crate::types::{DateTime, System, ZipFileData, DEFAULT_VERSION}; use crate::types::{DateTime, System, ZipFileData, DEFAULT_VERSION};
@ -194,6 +194,46 @@ impl ZipWriterStats {
} }
} }
impl<A: Read + Write + io::Seek> ZipWriter<A> {
/// Initializes the archive from an existing ZIP archive, making it ready for append.
pub fn new_append(mut readwriter: A) -> ZipResult<ZipWriter<A>> {
let (footer, cde_start_pos) = spec::CentralDirectoryEnd::find_and_parse(&mut readwriter)?;
if footer.disk_number != footer.disk_with_central_directory {
return Err(ZipError::UnsupportedArchive(
"Support for multi-disk files is not implemented",
));
}
let (archive_offset, directory_start, number_of_files) =
ZipArchive::get_directory_counts(&mut readwriter, &footer, cde_start_pos)?;
let mut files = Vec::new();
if let Err(_) = readwriter.seek(io::SeekFrom::Start(directory_start)) {
return Err(ZipError::InvalidArchive(
"Could not seek to start of central directory",
));
}
for _ in 0..number_of_files {
let file = central_header_to_zip_file(&mut readwriter, archive_offset)?;
files.push(file);
}
let _ = readwriter.seek(io::SeekFrom::Start(directory_start)); // seek directory_start to overwrite it
Ok(ZipWriter {
inner: GenericZipWriter::Storer(readwriter),
files,
stats: Default::default(),
writing_to_file: false,
comment: String::new(),
writing_raw: true, // avoid recomputing the last file's header
})
}
}
impl<W: Write + io::Seek> ZipWriter<W> { impl<W: Write + io::Seek> ZipWriter<W> {
/// Initializes the archive. /// Initializes the archive.
/// ///

View file

@ -46,6 +46,25 @@ fn copy() {
check_zip_file_contents(&mut tgt_archive, COPY_ENTRY_NAME); check_zip_file_contents(&mut tgt_archive, COPY_ENTRY_NAME);
} }
// This test asserts that after appending to a `ZipWriter`, then reading its contents back out,
// both the prior data and the appended data will be exactly the same as their originals.
#[test]
fn append() {
let mut file = &mut Cursor::new(Vec::new());
write_to_zip(file).expect("file written");
{
let mut zip = zip::ZipWriter::new_append(&mut file).unwrap();
zip.start_file(COPY_ENTRY_NAME, Default::default()).unwrap();
zip.write_all(LOREM_IPSUM).unwrap();
zip.finish().unwrap();
}
let mut zip = zip::ZipArchive::new(&mut file).unwrap();
check_zip_file_contents(&mut zip, ENTRY_NAME);
check_zip_file_contents(&mut zip, COPY_ENTRY_NAME);
}
fn write_to_zip(file: &mut Cursor<Vec<u8>>) -> zip::result::ZipResult<()> { fn write_to_zip(file: &mut Cursor<Vec<u8>>) -> zip::result::ZipResult<()> {
let mut zip = zip::ZipWriter::new(file); let mut zip = zip::ZipWriter::new(file);