WIP: Use ZipExtraDataField

This commit is contained in:
Chris Hennick 2023-05-08 10:03:30 -07:00
parent 80f836a661
commit 92c45cf8dd
No known key found for this signature in database
GPG key ID: 25653935CC8B6C74

View file

@ -64,6 +64,15 @@ enum GenericZipWriter<W: Write + Seek> {
Zstd(ZstdEncoder<'static, MaybeEncrypted<W>>), Zstd(ZstdEncoder<'static, MaybeEncrypted<W>>),
} }
enum ZipWriterState {
NotWritingFile,
WritingLocalExtraData,
WritingCentralOnlyExtraData,
WritingFileContents,
WritingFileContentsRaw,
Closed
}
// Put the struct declaration in a private module to convince rustdoc to display ZipWriter nicely // Put the struct declaration in a private module to convince rustdoc to display ZipWriter nicely
pub(crate) mod zip_writer { pub(crate) mod zip_writer {
use super::*; use super::*;
@ -101,16 +110,15 @@ pub(crate) mod zip_writer {
pub(super) files: Vec<ZipFileData>, pub(super) files: Vec<ZipFileData>,
pub(super) files_by_name: HashMap<String, usize>, pub(super) files_by_name: HashMap<String, usize>,
pub(super) stats: ZipWriterStats, pub(super) stats: ZipWriterStats,
pub(super) writing_to_file: bool, pub(super) state: ZipWriterState,
pub(super) writing_to_extra_field: bool,
pub(super) writing_to_central_extra_field_only: bool,
pub(super) writing_raw: bool,
pub(super) comment: Vec<u8>, pub(super) comment: Vec<u8>,
} }
} }
use crate::result::ZipError::InvalidArchive; use crate::result::ZipError::InvalidArchive;
use crate::write::GenericZipWriter::{Closed, Storer}; use crate::write::GenericZipWriter::{Closed, Storer};
pub use zip_writer::ZipWriter; pub use zip_writer::ZipWriter;
use crate::write::ZipFileInitialWritingMode::Content;
use crate::write::ZipWriterState::NotWritingFile;
#[derive(Default)] #[derive(Default)]
struct ZipWriterStats { struct ZipWriterStats {
@ -125,6 +133,23 @@ struct ZipRawValues {
uncompressed_size: u64, uncompressed_size: u64,
} }
/// What operation the new file will be ready for when it is opened.
#[derive(Copy, Clone, Debug)]
pub enum ZipFileInitialWritingMode {
/// File will be empty and not opened for writing.
Empty,
/// File will be ready to have extra data added.
ExtraData,
/// File will be open for writing its contents.
Content
}
#[derive(Copy, Clone, Debug)]
pub struct ZipExtraDataField {
header_id: u16,
data: Vec<u8>
}
/// Metadata for a file to be written /// Metadata for a file to be written
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
#[cfg_attr(fuzzing, derive(arbitrary::Arbitrary))] #[cfg_attr(fuzzing, derive(arbitrary::Arbitrary))]
@ -135,6 +160,7 @@ pub struct FileOptions {
pub(crate) permissions: Option<u32>, pub(crate) permissions: Option<u32>,
pub(crate) large_file: bool, pub(crate) large_file: bool,
encrypt_with: Option<crate::zipcrypto::ZipCryptoKeys>, encrypt_with: Option<crate::zipcrypto::ZipCryptoKeys>,
pub(crate) write_first: ZipFileInitialWritingMode,
} }
impl FileOptions { impl FileOptions {
@ -202,6 +228,14 @@ impl FileOptions {
self.encrypt_with = Some(crate::zipcrypto::ZipCryptoKeys::derive(password)); self.encrypt_with = Some(crate::zipcrypto::ZipCryptoKeys::derive(password));
self self
} }
/// Set whether extra data will be written before the content, or only content, or nothing at
/// all (the file will be empty).
#[must_use]
pub fn write_first(mut self, write_first: ZipFileInitialWritingMode) -> FileOptions {
self.write_first = write_first;
self
}
} }
impl Default for FileOptions { impl Default for FileOptions {
@ -228,22 +262,38 @@ impl Default for FileOptions {
permissions: None, permissions: None,
large_file: false, large_file: false,
encrypt_with: None, encrypt_with: None,
write_first: Content
} }
} }
} }
impl<W: Write + Seek> Write for ZipWriter<W> { impl<W: Write + Seek> Write for ZipWriter<W> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> { fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
if !self.writing_to_file { match self.inner.ref_mut() {
Some(ref mut w) => {
match self.state {
NotWritingFile => {
return Err(io::Error::new( return Err(io::Error::new(
io::ErrorKind::Other, io::ErrorKind::Other,
"No file has been started", "No file has been started",
)); ));
} },
match self.inner.ref_mut() { ZipWriterState::Closed => {
Some(ref mut w) => { return Err(io::Error::new(
if self.writing_to_extra_field { io::ErrorKind::BrokenPipe,
"ZipWriter is closed",
));
},
ZipWriterState::WritingLocalExtraData => {
self.files.last_mut().unwrap().extra_field.write(buf) self.files.last_mut().unwrap().extra_field.write(buf)
}
ZipWriterState::WritingCentralOnlyExtraData => {
}
ZipWriterState::WritingFileContents => {}
ZipWriterState::WritingFileContentsRaw => {}
}
if self.writing_to_extra_field {
} else { } else {
let write_result = w.write(buf); let write_result = w.write(buf);
if let Ok(count) = write_result { if let Ok(count) = write_result {
@ -322,11 +372,8 @@ impl<A: Read + Write + Seek> ZipWriter<A> {
files, files,
files_by_name, files_by_name,
stats: Default::default(), stats: Default::default(),
writing_to_file: false, state: NotWritingFile,
writing_to_extra_field: false, comment: footer.zip_file_comment
writing_to_central_extra_field_only: false,
comment: footer.zip_file_comment,
writing_raw: true, // avoid recomputing the last file's header
}) })
} }
} }
@ -389,17 +436,21 @@ impl<W: Write + Seek> ZipWriter<W> {
files: Vec::new(), files: Vec::new(),
files_by_name: HashMap::new(), files_by_name: HashMap::new(),
stats: Default::default(), stats: Default::default(),
writing_to_file: false, state: NotWritingFile,
writing_to_extra_field: false,
writing_to_central_extra_field_only: false,
writing_raw: false,
comment: Vec::new(), comment: Vec::new(),
} }
} }
/// Returns true if a file is currently open for writing. /// Returns true if a file is currently open for writing.
pub fn is_writing_file(&self) -> bool { pub fn is_writing_file(&self) -> bool {
self.writing_to_file && !self.inner.is_closed() match self.state {
NotWritingFile => false,
ZipWriterState::WritingLocalExtraData => true,
ZipWriterState::WritingCentralOnlyExtraData => true,
ZipWriterState::WritingFileContents => true,
ZipWriterState::WritingFileContentsRaw => true,
ZipWriterState::Closed => false
}
} }
/// Set ZIP archive comment. /// Set ZIP archive comment.
@ -502,14 +553,17 @@ impl<W: Write + Seek> ZipWriter<W> {
} }
fn finish_file(&mut self) -> ZipResult<()> { fn finish_file(&mut self) -> ZipResult<()> {
if !self.writing_to_file { match self.state {
debug_assert!(!self.writing_to_extra_field); NotWritingFile => {
return Ok(()); return Ok(());
} }
if self.writing_to_extra_field { ZipWriterState::WritingLocalExtraData => {
// Implicitly calling [`ZipWriter::end_extra_data`] for empty files.
self.end_extra_data()?; self.end_extra_data()?;
} }
ZipWriterState::WritingCentralOnlyExtraData => {
self.end_extra_data()?;
}
ZipWriterState::WritingFileContents => {
let make_plain_writer = self let make_plain_writer = self
.inner .inner
.prepare_next_writer(CompressionMethod::Stored, None)?; .prepare_next_writer(CompressionMethod::Stored, None)?;
@ -539,8 +593,13 @@ impl<W: Write + Seek> ZipWriter<W> {
update_local_file_header(writer, file)?; update_local_file_header(writer, file)?;
writer.seek(SeekFrom::Start(file_end))?; writer.seek(SeekFrom::Start(file_end))?;
} }
}
self.writing_to_file = false; ZipWriterState::WritingFileContentsRaw => {}
ZipWriterState::Closed => {
return Ok(());
}
}
self.state = NotWritingFile;
Ok(()) Ok(())
} }
@ -556,9 +615,7 @@ impl<W: Write + Seek> ZipWriter<W> {
.inner .inner
.prepare_next_writer(CompressionMethod::Stored, None)?; .prepare_next_writer(CompressionMethod::Stored, None)?;
self.inner.switch_to(make_plain_writer)?; self.inner.switch_to(make_plain_writer)?;
self.writing_to_extra_field = false; self.state = NotWritingFile;
self.writing_to_file = false;
self.writing_raw = false;
Ok(()) Ok(())
} }
@ -1344,8 +1401,7 @@ fn write_central_directory_header<T: Write>(writer: &mut T, file: &ZipFileData)
Ok(()) Ok(())
} }
fn validate_extra_data(file: &ZipFileData) -> ZipResult<()> { fn validate_extra_data(field: &ZipExtraDataField) -> ZipResult<()> {
for field in &file.extra_field {
if field.data.len() > u16::MAX as usize { if field.data.len() > u16::MAX as usize {
return Err(ZipError::Io(io::Error::new( return Err(ZipError::Io(io::Error::new(
io::ErrorKind::Other, io::ErrorKind::Other,
@ -1370,7 +1426,6 @@ fn validate_extra_data(file: &ZipFileData) -> ZipResult<()> {
))); )));
} }
} }
}
Ok(()) Ok(())
} }