Bug fix: create a valid archive even when last file was aborted with content

This commit is contained in:
Chris Hennick 2023-05-21 15:24:00 -07:00
parent 2c897b52b1
commit bef9fce30a
No known key found for this signature in database
GPG key ID: 25653935CC8B6C74
3 changed files with 100 additions and 51 deletions

View file

@ -173,3 +173,9 @@
of sequence. However, it may leave an unused entry in the archive.
- Calling `abort_file()` while writing a ZipCrypto-encrypted file no longer
causes a crash.
- Calling `abort_file()` on the last file before `finish()` no longer produces
an invalid ZIP file or garbage in the comment.
### Added
- `ZipWriter` methods `get_comment()` and `get_raw_comment()`.

View file

@ -76,7 +76,8 @@ fn do_operation<T>(writer: &mut RefCell<zip_next::ZipWriter<T>>,
writer.borrow_mut().abort_file().unwrap();
}
if operation.reopen {
let new_writer = zip_next::ZipWriter::new_append(writer.borrow_mut().finish().unwrap()).unwrap();
let mut new_writer = zip_next::ZipWriter::new_append(writer.borrow_mut().finish().unwrap()).unwrap();
assert_eq!(Ok(""), new_writer.get_comment());
*writer = new_writer.into();
}
Ok(())

View file

@ -14,6 +14,7 @@ use std::io;
use std::io::prelude::*;
use std::io::{BufReader, SeekFrom};
use std::mem;
use std::str::{from_utf8, Utf8Error};
use std::sync::Arc;
#[cfg(any(
@ -504,6 +505,19 @@ impl<W: Write + Seek> ZipWriter<W> {
self.comment = comment;
}
/// Get ZIP archive comment.
pub fn get_comment(&mut self) -> Result<&str, Utf8Error> {
from_utf8(self.get_raw_comment())
}
/// Get ZIP archive comment.
///
/// This returns the raw bytes of the comment. The comment
/// is typically expected to be encoded in UTF-8
pub fn get_raw_comment(&self) -> &Vec<u8> {
&self.comment
}
/// Start a new file for with the requested options.
fn start_entry<S>(
&mut self,
@ -714,11 +728,9 @@ impl<W: Write + Seek> ZipWriter<W> {
.prepare_next_writer(CompressionMethod::Stored, None)?;
self.inner.switch_to(make_plain_writer)?;
self.switch_to_non_encrypting_writer()?;
// Make sure this is the last file, and that no shallow copies of it remain; otherwise we'd
// overwrite a valid file and corrupt the archive
if !self.writing_to_file
&& self
if self
.files
.iter()
.all(|file| file.data_start.load() < last_file.data_start.load())
@ -957,6 +969,23 @@ impl<W: Write + Seek> ZipWriter<W> {
self.finish_file()?;
{
let central_start = self.write_central_and_footer()?;
let writer = self.inner.get_plain();
let footer_end = writer.stream_position()?;
let file_end = writer.seek(SeekFrom::End(0))?;
if footer_end < file_end {
// Data from an aborted file is past the end of the footer, so rewrite the footer at
// the actual end.
let central_and_footer_size = footer_end - central_start;
writer.seek(SeekFrom::End(-(central_and_footer_size as i64)))?;
self.write_central_and_footer()?;
}
}
Ok(())
}
fn write_central_and_footer(&mut self) -> Result<u64, ZipError> {
let writer = self.inner.get_plain();
let central_start = writer.stream_position()?;
@ -1002,9 +1031,7 @@ impl<W: Write + Seek> ZipWriter<W> {
};
footer.write(writer)?;
}
Ok(())
Ok(central_start)
}
fn index_by_name(&self, name: &str) -> ZipResult<usize> {
@ -1818,6 +1845,21 @@ mod test {
writer.start_file("", FileOptions::default()).unwrap();
Ok(())
}
#[test]
fn remove_encrypted_aligned_symlink() -> ZipResult<()> {
let mut options = FileOptions::default();
options = options.with_deprecated_encryption(b"Password");
options.alignment = 65535;
let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()));
writer.add_symlink("", "s\t\0\0ggggg\0\0", options).unwrap();
writer.abort_file().unwrap();
let zip = writer.finish().unwrap();
println!("{:0>2x?}", zip.get_ref());
let mut writer = ZipWriter::new_append(zip).unwrap();
writer.start_file("", FileOptions::default()).unwrap();
Ok(())
}
}
#[cfg(not(feature = "unreserved"))]