#[cfg(feature = "aes-crypto")]
use crate::aes::AesWriter;
use crate::compression::CompressionMethod;
use crate::read::{
find_content, parse_single_extra_field, Config, ZipArchive, ZipFile, ZipFileReader,
};
use crate::result::{ZipError, ZipResult};
use crate::spec::{self, FixedSizeBlock, Pod, Zip32CDEBlock};
#[cfg(feature = "aes-crypto")]
use crate::types::AesMode;
use crate::types::{
ffi, AesVendorVersion, DateTime, Zip64ExtraFieldBlock, ZipFileData, ZipLocalEntryBlock,
ZipRawValues, MIN_VERSION,
};
use crate::write::ffi::S_IFLNK;
#[cfg(any(feature = "_deflate-any", feature = "bzip2", feature = "zstd",))]
use core::num::NonZeroU64;
use crc32fast::Hasher;
use indexmap::IndexMap;
use std::borrow::ToOwned;
use std::default::Default;
use std::fmt::{Debug, Formatter};
use std::io;
use std::io::prelude::*;
use std::io::Cursor;
use std::io::{BufReader, SeekFrom};
use std::marker::PhantomData;
use std::mem;
use std::str::{from_utf8, Utf8Error};
use std::sync::Arc;
#[cfg(feature = "deflate-flate2")]
use flate2::{write::DeflateEncoder, Compression};
#[cfg(feature = "bzip2")]
use bzip2::write::BzEncoder;
#[cfg(feature = "deflate-zopfli")]
use zopfli::Options;
#[cfg(feature = "deflate-zopfli")]
use std::io::BufWriter;
use std::mem::size_of;
use std::path::Path;
#[cfg(feature = "zstd")]
use zstd::stream::write::Encoder as ZstdEncoder;
enum MaybeEncrypted<W> {
Unencrypted(W),
#[cfg(feature = "aes-crypto")]
Aes(AesWriter<W>),
ZipCrypto(crate::zipcrypto::ZipCryptoWriter<W>),
}
impl<W> Debug for MaybeEncrypted<W> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
MaybeEncrypted::Unencrypted(_) => "Unencrypted",
#[cfg(feature = "aes-crypto")]
MaybeEncrypted::Aes(_) => "AES",
MaybeEncrypted::ZipCrypto(_) => "ZipCrypto",
})
}
}
impl<W: Write> Write for MaybeEncrypted<W> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
match self {
MaybeEncrypted::Unencrypted(w) => w.write(buf),
#[cfg(feature = "aes-crypto")]
MaybeEncrypted::Aes(w) => w.write(buf),
MaybeEncrypted::ZipCrypto(w) => w.write(buf),
}
}
fn flush(&mut self) -> io::Result<()> {
match self {
MaybeEncrypted::Unencrypted(w) => w.flush(),
#[cfg(feature = "aes-crypto")]
MaybeEncrypted::Aes(w) => w.flush(),
MaybeEncrypted::ZipCrypto(w) => w.flush(),
}
}
}
enum GenericZipWriter<W: Write + Seek> {
Closed,
Storer(MaybeEncrypted<W>),
#[cfg(feature = "deflate-flate2")]
Deflater(DeflateEncoder<MaybeEncrypted<W>>),
#[cfg(feature = "deflate-zopfli")]
ZopfliDeflater(zopfli::DeflateEncoder<MaybeEncrypted<W>>),
#[cfg(feature = "deflate-zopfli")]
BufferedZopfliDeflater(BufWriter<zopfli::DeflateEncoder<MaybeEncrypted<W>>>),
#[cfg(feature = "bzip2")]
Bzip2(BzEncoder<MaybeEncrypted<W>>),
#[cfg(feature = "zstd")]
Zstd(ZstdEncoder<'static, MaybeEncrypted<W>>),
}
impl<W: Write + Seek> Debug for GenericZipWriter<W> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Closed => f.write_str("Closed"),
Storer(w) => f.write_fmt(format_args!("Storer({:?})", w)),
#[cfg(feature = "deflate-flate2")]
GenericZipWriter::Deflater(w) => {
f.write_fmt(format_args!("Deflater({:?})", w.get_ref()))
}
#[cfg(feature = "deflate-zopfli")]
GenericZipWriter::ZopfliDeflater(_) => f.write_str("ZopfliDeflater"),
#[cfg(feature = "deflate-zopfli")]
GenericZipWriter::BufferedZopfliDeflater(_) => f.write_str("BufferedZopfliDeflater"),
#[cfg(feature = "bzip2")]
GenericZipWriter::Bzip2(w) => f.write_fmt(format_args!("Bzip2({:?})", w.get_ref())),
#[cfg(feature = "zstd")]
GenericZipWriter::Zstd(w) => f.write_fmt(format_args!("Zstd({:?})", w.get_ref())),
}
}
}
pub(crate) mod zip_writer {
use super::*;
pub struct ZipWriter<W: Write + Seek> {
pub(super) inner: GenericZipWriter<W>,
pub(super) files: IndexMap<Box<str>, ZipFileData>,
pub(super) stats: ZipWriterStats,
pub(super) writing_to_file: bool,
pub(super) writing_raw: bool,
pub(super) comment: Box<[u8]>,
pub(super) zip64_comment: Option<Box<[u8]>>,
pub(super) flush_on_finish_file: bool,
}
impl<W: Write + Seek> Debug for ZipWriter<W> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!(
"ZipWriter {{files: {:?}, stats: {:?}, writing_to_file: {}, writing_raw: {}, comment: {:?}, flush_on_finish_file: {}}}",
self.files, self.stats, self.writing_to_file, self.writing_raw,
self.comment, self.flush_on_finish_file))
}
}
}
#[doc(inline)]
pub use self::sealed::FileOptionExtension;
use crate::result::ZipError::{InvalidArchive, UnsupportedArchive};
use crate::unstable::path_to_string;
use crate::unstable::LittleEndianWriteExt;
use crate::write::GenericZipWriter::{Closed, Storer};
use crate::zipcrypto::ZipCryptoKeys;
use crate::CompressionMethod::Stored;
pub use zip_writer::ZipWriter;
#[derive(Default, Debug)]
struct ZipWriterStats {
hasher: Hasher,
start: u64,
bytes_written: u64,
}
mod sealed {
use std::sync::Arc;
use super::ExtendedFileOptions;
pub trait Sealed {}
#[doc(hidden)]
pub trait FileOptionExtension: Default + Sealed {
fn extra_data(&self) -> Option<&Arc<Vec<u8>>>;
fn central_extra_data(&self) -> Option<&Arc<Vec<u8>>>;
}
impl Sealed for () {}
impl FileOptionExtension for () {
fn extra_data(&self) -> Option<&Arc<Vec<u8>>> {
None
}
fn central_extra_data(&self) -> Option<&Arc<Vec<u8>>> {
None
}
}
impl Sealed for ExtendedFileOptions {}
impl FileOptionExtension for ExtendedFileOptions {
fn extra_data(&self) -> Option<&Arc<Vec<u8>>> {
Some(&self.extra_data)
}
fn central_extra_data(&self) -> Option<&Arc<Vec<u8>>> {
Some(&self.central_extra_data)
}
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(crate) enum EncryptWith<'k> {
#[cfg(feature = "aes-crypto")]
Aes {
mode: AesMode,
password: &'k str,
},
ZipCrypto(ZipCryptoKeys, PhantomData<&'k ()>),
}
#[cfg(fuzzing)]
impl<'a> arbitrary::Arbitrary<'a> for EncryptWith<'a> {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
#[cfg(feature = "aes-crypto")]
if bool::arbitrary(u)? {
return Ok(EncryptWith::Aes {
mode: AesMode::arbitrary(u)?,
password: u.arbitrary::<&str>()?,
});
}
Ok(EncryptWith::ZipCrypto(
ZipCryptoKeys::arbitrary(u)?,
PhantomData,
))
}
}
#[derive(Clone, Debug, Copy, Eq, PartialEq)]
pub struct FileOptions<'k, T: FileOptionExtension> {
pub(crate) compression_method: CompressionMethod,
pub(crate) compression_level: Option<i64>,
pub(crate) last_modified_time: DateTime,
pub(crate) permissions: Option<u32>,
pub(crate) large_file: bool,
pub(crate) encrypt_with: Option<EncryptWith<'k>>,
pub(crate) extended_options: T,
pub(crate) alignment: u16,
#[cfg(feature = "deflate-zopfli")]
pub(super) zopfli_buffer_size: Option<usize>,
}
pub type SimpleFileOptions = FileOptions<'static, ()>;
pub type FullFileOptions<'k> = FileOptions<'k, ExtendedFileOptions>;
#[derive(Clone, Default, Eq, PartialEq)]
pub struct ExtendedFileOptions {
extra_data: Arc<Vec<u8>>,
central_extra_data: Arc<Vec<u8>>,
}
impl ExtendedFileOptions {
pub fn add_extra_data(
&mut self,
header_id: u16,
data: Box<[u8]>,
central_only: bool,
) -> ZipResult<()> {
let len = data.len() + 4;
if self.extra_data.len() + self.central_extra_data.len() + len > u16::MAX as usize {
Err(InvalidArchive(
"Extra data field would be longer than allowed",
))
} else {
let field = if central_only {
&mut self.central_extra_data
} else {
&mut self.extra_data
};
let vec = Arc::get_mut(field);
let vec = match vec {
Some(exclusive) => exclusive,
None => {
*field = Arc::new(field.to_vec());
Arc::get_mut(field).unwrap()
}
};
Self::add_extra_data_unchecked(vec, header_id, data)?;
Self::validate_extra_data(vec, true)?;
Ok(())
}
}
pub(crate) fn add_extra_data_unchecked(
vec: &mut Vec<u8>,
header_id: u16,
data: Box<[u8]>,
) -> Result<(), ZipError> {
vec.reserve_exact(data.len() + 4);
vec.write_u16_le(header_id)?;
vec.write_u16_le(data.len() as u16)?;
vec.write_all(&data)?;
Ok(())
}
fn validate_extra_data(data: &[u8], disallow_zip64: bool) -> ZipResult<()> {
let len = data.len() as u64;
if len == 0 {
return Ok(());
}
if len > u16::MAX as u64 {
return Err(ZipError::Io(io::Error::new(
io::ErrorKind::Other,
"Extra-data field can't exceed u16::MAX bytes",
)));
}
let mut data = Cursor::new(data);
let mut pos = data.position();
while pos < len {
if len - data.position() < 4 {
return Err(ZipError::Io(io::Error::new(
io::ErrorKind::Other,
"Extra-data field doesn't have room for ID and length",
)));
}
#[cfg(not(feature = "unreserved"))]
{
use crate::unstable::LittleEndianReadExt;
let header_id = data.read_u16_le()?;
if EXTRA_FIELD_MAPPING
.iter()
.any(|&mapped| mapped == header_id)
{
return Err(ZipError::Io(io::Error::new(
io::ErrorKind::Other,
format!(
"Extra data header ID {header_id:#06} requires crate feature \"unreserved\"",
),
)));
}
data.seek(SeekFrom::Current(-2))?;
}
parse_single_extra_field(&mut ZipFileData::default(), &mut data, pos, disallow_zip64)?;
pos = data.position();
}
Ok(())
}
}
impl Debug for ExtendedFileOptions {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
f.write_fmt(format_args!("ExtendedFileOptions {{extra_data: vec!{:?}.into(), central_extra_data: vec!{:?}.into()}}",
self.extra_data, self.central_extra_data))
}
}
#[cfg(fuzzing)]
impl<'a> arbitrary::Arbitrary<'a> for FileOptions<'a, ExtendedFileOptions> {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
let mut options = FullFileOptions {
compression_method: CompressionMethod::arbitrary(u)?,
compression_level: if bool::arbitrary(u)? {
Some(u.int_in_range(0..=24)?)
} else {
None
},
last_modified_time: DateTime::arbitrary(u)?,
permissions: Option::<u32>::arbitrary(u)?,
large_file: bool::arbitrary(u)?,
encrypt_with: Option::<EncryptWith>::arbitrary(u)?,
alignment: u16::arbitrary(u)?,
#[cfg(feature = "deflate-zopfli")]
zopfli_buffer_size: None,
..Default::default()
};
#[cfg(feature = "deflate-zopfli")]
if options.compression_method == CompressionMethod::Deflated && bool::arbitrary(u)? {
options.zopfli_buffer_size =
Some(if bool::arbitrary(u)? { 2 } else { 3 } << u.int_in_range(8..=20)?);
}
u.arbitrary_loop(Some(0), Some(10), |u| {
options
.add_extra_data(
u.int_in_range(2..=u16::MAX)?,
Box::<[u8]>::arbitrary(u)?,
bool::arbitrary(u)?,
)
.map_err(|_| arbitrary::Error::IncorrectFormat)?;
Ok(core::ops::ControlFlow::Continue(()))
})?;
ZipWriter::new(Cursor::new(Vec::new()))
.start_file("", options.clone())
.map_err(|_| arbitrary::Error::IncorrectFormat)?;
Ok(options)
}
}
impl<T: FileOptionExtension> FileOptions<'_, T> {
#[must_use]
pub const fn compression_method(mut self, method: CompressionMethod) -> Self {
self.compression_method = method;
self
}
#[must_use]
pub const fn compression_level(mut self, level: Option<i64>) -> Self {
self.compression_level = level;
self
}
#[must_use]
pub const fn last_modified_time(mut self, mod_time: DateTime) -> Self {
self.last_modified_time = mod_time;
self
}
#[must_use]
pub const fn unix_permissions(mut self, mode: u32) -> Self {
self.permissions = Some(mode & 0o777);
self
}
#[must_use]
pub const fn large_file(mut self, large: bool) -> Self {
self.large_file = large;
self
}
pub(crate) fn with_deprecated_encryption(self, password: &[u8]) -> FileOptions<'static, T> {
FileOptions {
encrypt_with: Some(EncryptWith::ZipCrypto(
ZipCryptoKeys::derive(password),
PhantomData,
)),
..self
}
}
#[cfg(feature = "aes-crypto")]
pub fn with_aes_encryption(self, mode: AesMode, password: &str) -> FileOptions<'_, T> {
FileOptions {
encrypt_with: Some(EncryptWith::Aes { mode, password }),
..self
}
}
#[must_use]
#[cfg(feature = "deflate-zopfli")]
pub const fn with_zopfli_buffer(mut self, size: Option<usize>) -> Self {
self.zopfli_buffer_size = size;
self
}
pub const fn get_compression_level(&self) -> Option<i64> {
self.compression_level
}
#[must_use]
pub const fn with_alignment(mut self, alignment: u16) -> Self {
self.alignment = alignment;
self
}
}
impl FileOptions<'_, ExtendedFileOptions> {
pub fn add_extra_data(
&mut self,
header_id: u16,
data: Box<[u8]>,
central_only: bool,
) -> ZipResult<()> {
self.extended_options
.add_extra_data(header_id, data, central_only)
}
#[must_use]
pub fn clear_extra_data(mut self) -> Self {
if self.extended_options.extra_data.len() > 0 {
self.extended_options.extra_data = Arc::new(vec![]);
}
if self.extended_options.central_extra_data.len() > 0 {
self.extended_options.central_extra_data = Arc::new(vec![]);
}
self
}
}
impl<T: FileOptionExtension> Default for FileOptions<'_, T> {
fn default() -> Self {
Self {
compression_method: Default::default(),
compression_level: None,
last_modified_time: DateTime::default_for_write(),
permissions: None,
large_file: false,
encrypt_with: None,
extended_options: T::default(),
alignment: 1,
#[cfg(feature = "deflate-zopfli")]
zopfli_buffer_size: Some(1 << 15),
}
}
}
impl<W: Write + Seek> Write for ZipWriter<W> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
if !self.writing_to_file {
return Err(io::Error::new(
io::ErrorKind::Other,
"No file has been started",
));
}
if buf.is_empty() {
return Ok(0);
}
match self.inner.ref_mut() {
Some(ref mut w) => {
let write_result = w.write(buf);
if let Ok(count) = write_result {
self.stats.update(&buf[0..count]);
if self.stats.bytes_written > spec::ZIP64_BYTES_THR
&& !self.files.last_mut().unwrap().1.large_file
{
let _ = self.abort_file();
return Err(io::Error::new(
io::ErrorKind::Other,
"Large file option has not been set",
));
}
}
write_result
}
None => Err(io::Error::new(
io::ErrorKind::BrokenPipe,
"write(): ZipWriter was already closed",
)),
}
}
fn flush(&mut self) -> io::Result<()> {
match self.inner.ref_mut() {
Some(ref mut w) => w.flush(),
None => Err(io::Error::new(
io::ErrorKind::BrokenPipe,
"flush(): ZipWriter was already closed",
)),
}
}
}
impl ZipWriterStats {
fn update(&mut self, buf: &[u8]) {
self.hasher.update(buf);
self.bytes_written += buf.len() as u64;
}
}
impl<A: Read + Write + Seek> ZipWriter<A> {
pub fn new_append(readwriter: A) -> ZipResult<ZipWriter<A>> {
Self::new_append_with_config(Default::default(), readwriter)
}
pub fn new_append_with_config(config: Config, mut readwriter: A) -> ZipResult<ZipWriter<A>> {
readwriter.seek(SeekFrom::Start(0))?;
let shared = ZipArchive::get_metadata(config, &mut readwriter)?;
Ok(ZipWriter {
inner: Storer(MaybeEncrypted::Unencrypted(readwriter)),
files: shared.files,
stats: Default::default(),
writing_to_file: false,
comment: shared.comment,
zip64_comment: shared.zip64_comment,
writing_raw: true, flush_on_finish_file: false,
})
}
pub fn set_flush_on_finish_file(&mut self, flush_on_finish_file: bool) {
self.flush_on_finish_file = flush_on_finish_file;
}
}
impl<A: Read + Write + Seek> ZipWriter<A> {
pub fn deep_copy_file(&mut self, src_name: &str, dest_name: &str) -> ZipResult<()> {
self.finish_file()?;
if src_name == dest_name || self.files.contains_key(dest_name) {
return Err(InvalidArchive("That file already exists"));
}
let write_position = self.inner.get_plain().stream_position()?;
let src_index = self.index_by_name(src_name)?;
let src_data = &mut self.files[src_index];
let src_data_start = src_data.data_start();
debug_assert!(src_data_start <= write_position);
let mut compressed_size = src_data.compressed_size;
if compressed_size > (write_position - src_data_start) {
compressed_size = write_position - src_data_start;
src_data.compressed_size = compressed_size;
}
let mut reader = BufReader::new(ZipFileReader::Raw(find_content(
src_data,
self.inner.get_plain(),
)?));
let mut copy = Vec::with_capacity(compressed_size as usize);
reader.read_to_end(&mut copy)?;
drop(reader);
self.inner
.get_plain()
.seek(SeekFrom::Start(write_position))?;
let mut new_data = src_data.clone();
let dest_name_raw = dest_name.as_bytes();
new_data.file_name = dest_name.into();
new_data.file_name_raw = dest_name_raw.into();
new_data.is_utf8 = !dest_name.is_ascii();
new_data.header_start = write_position;
let extra_data_start = write_position
+ size_of::<ZipLocalEntryBlock>() as u64
+ new_data.file_name_raw.len() as u64;
new_data.extra_data_start = Some(extra_data_start);
let mut data_start = extra_data_start;
if let Some(extra) = &src_data.extra_field {
data_start += extra.len() as u64;
}
new_data.data_start.take();
new_data.data_start.get_or_init(|| data_start);
new_data.central_header_start = 0;
let block = new_data.local_block()?;
let index = self.insert_file_data(new_data)?;
let result = (|| {
let plain_writer = self.inner.get_plain();
plain_writer.write_all(block.as_bytes())?;
plain_writer.write_all(dest_name_raw)?;
let new_data = &self.files[index];
if let Some(data) = &new_data.extra_field {
plain_writer.write_all(data)?;
}
debug_assert_eq!(data_start, plain_writer.stream_position()?);
self.writing_to_file = true;
plain_writer.write_all(©)
})();
self.ok_or_abort_file(result)?;
self.files
.values_mut()
.for_each(|file| file.central_header_start = 0);
self.writing_to_file = false;
Ok(())
}
pub fn deep_copy_file_from_path<T: AsRef<Path>, U: AsRef<Path>>(
&mut self,
src_path: T,
dest_path: U,
) -> ZipResult<()> {
let src = path_to_string(src_path);
let dest = path_to_string(dest_path);
self.deep_copy_file(&src, &dest)
}
pub fn finish_into_readable(mut self) -> ZipResult<ZipArchive<A>> {
let central_start = self.finalize()?;
let inner = mem::replace(&mut self.inner, Closed).unwrap();
let comment = mem::take(&mut self.comment);
let zip64_comment = mem::take(&mut self.zip64_comment);
let files = mem::take(&mut self.files);
let archive =
ZipArchive::from_finalized_writer(files, comment, zip64_comment, inner, central_start)?;
Ok(archive)
}
}
impl<W: Write + Seek> ZipWriter<W> {
pub fn new(inner: W) -> ZipWriter<W> {
ZipWriter {
inner: Storer(MaybeEncrypted::Unencrypted(inner)),
files: IndexMap::new(),
stats: Default::default(),
writing_to_file: false,
writing_raw: false,
comment: Box::new([]),
zip64_comment: None,
flush_on_finish_file: false,
}
}
pub const fn is_writing_file(&self) -> bool {
self.writing_to_file && !self.inner.is_closed()
}
pub fn set_comment<S>(&mut self, comment: S)
where
S: Into<Box<str>>,
{
self.set_raw_comment(comment.into().into_boxed_bytes())
}
pub fn set_raw_comment(&mut self, comment: Box<[u8]>) {
self.comment = comment;
}
pub fn get_comment(&mut self) -> Result<&str, Utf8Error> {
from_utf8(self.get_raw_comment())
}
pub const fn get_raw_comment(&self) -> &[u8] {
&self.comment
}
pub fn set_zip64_comment<S>(&mut self, comment: Option<S>)
where
S: Into<Box<str>>,
{
self.set_raw_zip64_comment(comment.map(|v| v.into().into_boxed_bytes()))
}
pub fn set_raw_zip64_comment(&mut self, comment: Option<Box<[u8]>>) {
self.zip64_comment = comment;
}
pub fn get_zip64_comment(&mut self) -> Option<Result<&str, Utf8Error>> {
self.get_raw_zip64_comment().map(from_utf8)
}
pub fn get_raw_zip64_comment(&self) -> Option<&[u8]> {
self.zip64_comment.as_deref()
}
pub unsafe fn set_file_metadata(&mut self, length: u64, crc32: u32) -> ZipResult<()> {
if !self.writing_to_file {
return Err(ZipError::Io(io::Error::new(
io::ErrorKind::Other,
"No file has been started",
)));
}
self.stats.hasher = Hasher::new_with_initial_len(crc32, length);
self.stats.bytes_written = length;
Ok(())
}
fn ok_or_abort_file<T, E: Into<ZipError>>(&mut self, result: Result<T, E>) -> ZipResult<T> {
match result {
Err(e) => {
let _ = self.abort_file();
Err(e.into())
}
Ok(t) => Ok(t),
}
}
fn start_entry<S: ToString, T: FileOptionExtension>(
&mut self,
name: S,
options: FileOptions<T>,
raw_values: Option<ZipRawValues>,
) -> ZipResult<()> {
self.finish_file()?;
let header_start = self.inner.get_plain().stream_position()?;
let raw_values = raw_values.unwrap_or(ZipRawValues {
crc32: 0,
compressed_size: 0,
uncompressed_size: 0,
});
let mut extra_data = match options.extended_options.extra_data() {
Some(data) => data.to_vec(),
None => vec![],
};
let central_extra_data = options.extended_options.central_extra_data();
if let Some(zip64_block) =
Zip64ExtraFieldBlock::maybe_new(options.large_file, 0, 0, header_start)
{
let mut new_extra_data = zip64_block.serialize().into_vec();
new_extra_data.append(&mut extra_data);
extra_data = new_extra_data;
}
#[allow(unused_mut)]
let mut aes_extra_data_start = 0;
#[cfg(feature = "aes-crypto")]
if let Some(EncryptWith::Aes { mode, .. }) = options.encrypt_with {
let aes_dummy_extra_data =
vec![0x02, 0x00, 0x41, 0x45, mode as u8, 0x00, 0x00].into_boxed_slice();
aes_extra_data_start = extra_data.len() as u64;
ExtendedFileOptions::add_extra_data_unchecked(
&mut extra_data,
0x9901,
aes_dummy_extra_data,
)?;
}
let (compression_method, aes_mode) = match options.encrypt_with {
#[cfg(feature = "aes-crypto")]
Some(EncryptWith::Aes { mode, .. }) => (
CompressionMethod::Aes,
Some((mode, AesVendorVersion::Ae2, options.compression_method)),
),
_ => (options.compression_method, None),
};
let header_end =
header_start + size_of::<ZipLocalEntryBlock>() as u64 + name.to_string().len() as u64;
if options.alignment > 1 {
let extra_data_end = header_end + extra_data.len() as u64;
let align = options.alignment as u64;
let unaligned_header_bytes = extra_data_end % align;
if unaligned_header_bytes != 0 {
let mut pad_length = (align - unaligned_header_bytes) as usize;
while pad_length < 6 {
pad_length += align as usize;
}
let mut pad_body = vec![0; pad_length - 4];
debug_assert!(pad_body.len() >= 2);
[pad_body[0], pad_body[1]] = options.alignment.to_le_bytes();
ExtendedFileOptions::add_extra_data_unchecked(
&mut extra_data,
0xa11e,
pad_body.into_boxed_slice(),
)?;
debug_assert_eq!((extra_data.len() as u64 + header_end) % align, 0);
}
}
let extra_data_len = extra_data.len();
if let Some(data) = central_extra_data {
if extra_data_len + data.len() > u16::MAX as usize {
return Err(InvalidArchive(
"Extra data and central extra data must be less than 64KiB when combined",
));
}
ExtendedFileOptions::validate_extra_data(data, true)?;
}
let mut file = ZipFileData::initialize_local_block(
name,
&options,
raw_values,
header_start,
None,
aes_extra_data_start,
compression_method,
aes_mode,
&extra_data,
);
file.version_made_by = file.version_made_by.max(file.version_needed() as u8);
file.extra_data_start = Some(header_end);
let index = self.insert_file_data(file)?;
self.writing_to_file = true;
let result: ZipResult<()> = (|| {
ExtendedFileOptions::validate_extra_data(&extra_data, false)?;
let file = &mut self.files[index];
let block = file.local_block()?;
let writer = self.inner.get_plain();
block.write(writer)?;
writer.write_all(&file.file_name_raw)?;
if extra_data_len > 0 {
writer.write_all(&extra_data)?;
file.extra_field = Some(extra_data.into());
}
Ok(())
})();
self.ok_or_abort_file(result)?;
let writer = self.inner.get_plain();
self.stats.start = writer.stream_position()?;
match options.encrypt_with {
#[cfg(feature = "aes-crypto")]
Some(EncryptWith::Aes { mode, password }) => {
let aeswriter = AesWriter::new(
mem::replace(&mut self.inner, Closed).unwrap(),
mode,
password.as_bytes(),
)?;
self.inner = Storer(MaybeEncrypted::Aes(aeswriter));
}
Some(EncryptWith::ZipCrypto(keys, ..)) => {
let mut zipwriter = crate::zipcrypto::ZipCryptoWriter {
writer: mem::replace(&mut self.inner, Closed).unwrap(),
buffer: vec![],
keys,
};
self.stats.start = zipwriter.writer.stream_position()?;
let crypto_header = [0u8; 12];
let result = zipwriter.write_all(&crypto_header);
self.ok_or_abort_file(result)?;
self.inner = Storer(MaybeEncrypted::ZipCrypto(zipwriter));
}
None => {}
}
let file = &mut self.files[index];
debug_assert!(file.data_start.get().is_none());
file.data_start.get_or_init(|| self.stats.start);
self.stats.bytes_written = 0;
self.stats.hasher = Hasher::new();
Ok(())
}
fn insert_file_data(&mut self, file: ZipFileData) -> ZipResult<usize> {
if self.files.contains_key(&file.file_name) {
return Err(InvalidArchive("Duplicate filename"));
}
let name = file.file_name.to_owned();
self.files.insert(name.clone(), file);
Ok(self.files.get_index_of(&name).unwrap())
}
fn finish_file(&mut self) -> ZipResult<()> {
if !self.writing_to_file {
return Ok(());
}
let make_plain_writer = self.inner.prepare_next_writer(
Stored,
None,
#[cfg(feature = "deflate-zopfli")]
None,
)?;
self.inner.switch_to(make_plain_writer)?;
self.switch_to_non_encrypting_writer()?;
let writer = self.inner.get_plain();
if !self.writing_raw {
let file = match self.files.last_mut() {
None => return Ok(()),
Some((_, f)) => f,
};
file.uncompressed_size = self.stats.bytes_written;
let file_end = writer.stream_position()?;
debug_assert!(file_end >= self.stats.start);
file.compressed_size = file_end - self.stats.start;
let mut crc = true;
if let Some(aes_mode) = &mut file.aes_mode {
aes_mode.1 = if self.stats.bytes_written < 20 {
crc = false;
AesVendorVersion::Ae2
} else {
AesVendorVersion::Ae1
};
}
file.crc32 = if crc {
self.stats.hasher.clone().finalize()
} else {
0
};
update_aes_extra_data(writer, file)?;
update_local_file_header(writer, file)?;
writer.seek(SeekFrom::Start(file_end))?;
}
if self.flush_on_finish_file {
let result = writer.flush();
self.ok_or_abort_file(result)?;
}
self.writing_to_file = false;
Ok(())
}
fn switch_to_non_encrypting_writer(&mut self) -> Result<(), ZipError> {
match mem::replace(&mut self.inner, Closed) {
#[cfg(feature = "aes-crypto")]
Storer(MaybeEncrypted::Aes(writer)) => {
self.inner = Storer(MaybeEncrypted::Unencrypted(writer.finish()?));
}
Storer(MaybeEncrypted::ZipCrypto(writer)) => {
let crc32 = self.stats.hasher.clone().finalize();
self.inner = Storer(MaybeEncrypted::Unencrypted(writer.finish(crc32)?))
}
Storer(MaybeEncrypted::Unencrypted(w)) => {
self.inner = Storer(MaybeEncrypted::Unencrypted(w))
}
_ => unreachable!(),
}
Ok(())
}
pub fn abort_file(&mut self) -> ZipResult<()> {
let (_, last_file) = self.files.pop().ok_or(ZipError::FileNotFound)?;
let make_plain_writer = self.inner.prepare_next_writer(
Stored,
None,
#[cfg(feature = "deflate-zopfli")]
None,
)?;
self.inner.switch_to(make_plain_writer)?;
self.switch_to_non_encrypting_writer()?;
let rewind_safe: bool = match last_file.data_start.get() {
None => self.files.is_empty(),
Some(last_file_start) => self.files.values().all(|file| {
file.data_start
.get()
.is_some_and(|start| start < last_file_start)
}),
};
if rewind_safe {
self.inner
.get_plain()
.seek(SeekFrom::Start(last_file.header_start))?;
}
self.writing_to_file = false;
Ok(())
}
pub fn start_file<S: ToString, T: FileOptionExtension>(
&mut self,
name: S,
mut options: FileOptions<T>,
) -> ZipResult<()> {
Self::normalize_options(&mut options);
let make_new_self = self.inner.prepare_next_writer(
options.compression_method,
options.compression_level,
#[cfg(feature = "deflate-zopfli")]
options.zopfli_buffer_size,
)?;
self.start_entry(name, options, None)?;
let result = self.inner.switch_to(make_new_self);
self.ok_or_abort_file(result)?;
self.writing_raw = false;
Ok(())
}
pub fn merge_archive<R>(&mut self, mut source: ZipArchive<R>) -> ZipResult<()>
where
R: Read + Seek,
{
self.finish_file()?;
self.writing_to_file = true;
self.writing_raw = true;
let writer = self.inner.get_plain();
let new_files = source.merge_contents(writer)?;
self.files.extend(new_files);
Ok(())
}
fn normalize_options<T: FileOptionExtension>(options: &mut FileOptions<T>) {
if options.permissions.is_none() {
options.permissions = Some(0o644);
}
if !options.last_modified_time.is_valid() {
options.last_modified_time = FileOptions::<T>::default().last_modified_time;
}
*options.permissions.as_mut().unwrap() |= ffi::S_IFREG;
}
pub fn start_file_from_path<E: FileOptionExtension, P: AsRef<Path>>(
&mut self,
path: P,
options: FileOptions<E>,
) -> ZipResult<()> {
self.start_file(path_to_string(path), options)
}
pub fn raw_copy_file_rename<S: ToString>(&mut self, file: ZipFile, name: S) -> ZipResult<()> {
let mut options = SimpleFileOptions::default()
.large_file(file.compressed_size().max(file.size()) > spec::ZIP64_BYTES_THR)
.last_modified_time(
file.last_modified()
.unwrap_or_else(DateTime::default_for_write),
)
.compression_method(file.compression());
if let Some(perms) = file.unix_mode() {
options = options.unix_permissions(perms);
}
Self::normalize_options(&mut options);
self.raw_copy_file_rename_internal(file, name, options)
}
fn raw_copy_file_rename_internal<S: ToString>(
&mut self,
mut file: ZipFile,
name: S,
options: SimpleFileOptions,
) -> ZipResult<()> {
let raw_values = ZipRawValues {
crc32: file.crc32(),
compressed_size: file.compressed_size(),
uncompressed_size: file.size(),
};
self.start_entry(name, options, Some(raw_values))?;
self.writing_to_file = true;
self.writing_raw = true;
io::copy(&mut file.take_raw_reader()?, self)?;
self.finish_file()
}
pub fn raw_copy_file_to_path<P: AsRef<Path>>(
&mut self,
file: ZipFile,
path: P,
) -> ZipResult<()> {
self.raw_copy_file_rename(file, path_to_string(path))
}
pub fn raw_copy_file(&mut self, file: ZipFile) -> ZipResult<()> {
let name = file.name().to_owned();
self.raw_copy_file_rename(file, name)
}
pub fn raw_copy_file_touch(
&mut self,
file: ZipFile,
last_modified_time: DateTime,
unix_mode: Option<u32>,
) -> ZipResult<()> {
let name = file.name().to_owned();
let mut options = SimpleFileOptions::default()
.large_file(file.compressed_size().max(file.size()) > spec::ZIP64_BYTES_THR)
.last_modified_time(last_modified_time)
.compression_method(file.compression());
if let Some(perms) = unix_mode {
options = options.unix_permissions(perms);
}
Self::normalize_options(&mut options);
self.raw_copy_file_rename_internal(file, name, options)
}
pub fn add_directory<S, T: FileOptionExtension>(
&mut self,
name: S,
mut options: FileOptions<T>,
) -> ZipResult<()>
where
S: Into<String>,
{
if options.permissions.is_none() {
options.permissions = Some(0o755);
}
*options.permissions.as_mut().unwrap() |= 0o40000;
options.compression_method = Stored;
options.encrypt_with = None;
let name_as_string = name.into();
let name_with_slash = match name_as_string.chars().last() {
Some('/') | Some('\\') => name_as_string,
_ => name_as_string + "/",
};
self.start_entry(name_with_slash, options, None)?;
self.writing_to_file = false;
self.switch_to_non_encrypting_writer()?;
Ok(())
}
pub fn add_directory_from_path<T: FileOptionExtension, P: AsRef<Path>>(
&mut self,
path: P,
options: FileOptions<T>,
) -> ZipResult<()> {
self.add_directory(path_to_string(path), options)
}
pub fn finish(mut self) -> ZipResult<W> {
let _central_start = self.finalize()?;
let inner = mem::replace(&mut self.inner, Closed);
Ok(inner.unwrap())
}
pub fn add_symlink<N: ToString, T: ToString, E: FileOptionExtension>(
&mut self,
name: N,
target: T,
mut options: FileOptions<E>,
) -> ZipResult<()> {
if options.permissions.is_none() {
options.permissions = Some(0o777);
}
*options.permissions.as_mut().unwrap() |= S_IFLNK;
options.compression_method = Stored;
self.start_entry(name, options, None)?;
self.writing_to_file = true;
let result = self.write_all(target.to_string().as_bytes());
self.ok_or_abort_file(result)?;
self.writing_raw = false;
self.finish_file()?;
Ok(())
}
pub fn add_symlink_from_path<P: AsRef<Path>, T: AsRef<Path>, E: FileOptionExtension>(
&mut self,
path: P,
target: T,
options: FileOptions<E>,
) -> ZipResult<()> {
self.add_symlink(path_to_string(path), path_to_string(target), options)
}
fn finalize(&mut self) -> ZipResult<u64> {
self.finish_file()?;
let mut central_start = self.write_central_and_footer()?;
let writer = self.inner.get_plain();
let footer_end = writer.stream_position()?;
let archive_end = writer.seek(SeekFrom::End(0))?;
if footer_end < archive_end {
writer.seek(SeekFrom::Start(central_start))?;
writer.write_u32_le(0)?;
writer.seek(SeekFrom::Start(
footer_end - size_of::<Zip32CDEBlock>() as u64 - self.comment.len() as u64,
))?;
writer.write_u32_le(0)?;
let central_and_footer_size = footer_end - central_start;
writer.seek(SeekFrom::End(-(central_and_footer_size as i64)))?;
central_start = self.write_central_and_footer()?;
}
Ok(central_start)
}
fn write_central_and_footer(&mut self) -> Result<u64, ZipError> {
let writer = self.inner.get_plain();
let mut version_needed = MIN_VERSION as u16;
let central_start = writer.stream_position()?;
for file in self.files.values() {
write_central_directory_header(writer, file)?;
version_needed = version_needed.max(file.version_needed());
}
let central_size = writer.stream_position()? - central_start;
let is64 = self.files.len() > spec::ZIP64_ENTRY_THR
|| central_size.max(central_start) > spec::ZIP64_BYTES_THR
|| self.zip64_comment.is_some();
if is64 {
let comment = self.zip64_comment.clone().unwrap_or_default();
let zip64_footer = spec::Zip64CentralDirectoryEnd {
record_size: comment.len() as u64 + 44,
version_made_by: version_needed,
version_needed_to_extract: version_needed,
disk_number: 0,
disk_with_central_directory: 0,
number_of_files_on_this_disk: self.files.len() as u64,
number_of_files: self.files.len() as u64,
central_directory_size: central_size,
central_directory_offset: central_start,
extensible_data_sector: comment,
};
zip64_footer.write(writer)?;
let zip64_footer = spec::Zip64CentralDirectoryEndLocator {
disk_with_central_directory: 0,
end_of_central_directory_offset: central_start + central_size,
number_of_disks: 1,
};
zip64_footer.write(writer)?;
}
let number_of_files = self.files.len().min(spec::ZIP64_ENTRY_THR) as u16;
let footer = spec::Zip32CentralDirectoryEnd {
disk_number: 0,
disk_with_central_directory: 0,
zip_file_comment: self.comment.clone(),
number_of_files_on_this_disk: number_of_files,
number_of_files,
central_directory_size: central_size.min(spec::ZIP64_BYTES_THR) as u32,
central_directory_offset: central_start.min(spec::ZIP64_BYTES_THR) as u32,
};
footer.write(writer)?;
Ok(central_start)
}
fn index_by_name(&self, name: &str) -> ZipResult<usize> {
self.files.get_index_of(name).ok_or(ZipError::FileNotFound)
}
pub fn shallow_copy_file(&mut self, src_name: &str, dest_name: &str) -> ZipResult<()> {
self.finish_file()?;
if src_name == dest_name {
return Err(InvalidArchive("Trying to copy a file to itself"));
}
let src_index = self.index_by_name(src_name)?;
let mut dest_data = self.files[src_index].to_owned();
dest_data.file_name = dest_name.to_string().into();
dest_data.file_name_raw = dest_name.to_string().into_bytes().into();
dest_data.central_header_start = 0;
self.insert_file_data(dest_data)?;
Ok(())
}
pub fn shallow_copy_file_from_path<T: AsRef<Path>, U: AsRef<Path>>(
&mut self,
src_path: T,
dest_path: U,
) -> ZipResult<()> {
self.shallow_copy_file(&path_to_string(src_path), &path_to_string(dest_path))
}
}
impl<W: Write + Seek> Drop for ZipWriter<W> {
fn drop(&mut self) {
if !self.inner.is_closed() {
if let Err(e) = self.finalize() {
let _ = write!(io::stderr(), "ZipWriter drop failed: {:?}", e);
}
}
}
}
type SwitchWriterFunction<W> = Box<dyn FnOnce(MaybeEncrypted<W>) -> GenericZipWriter<W>>;
impl<W: Write + Seek> GenericZipWriter<W> {
fn prepare_next_writer(
&self,
compression: CompressionMethod,
compression_level: Option<i64>,
#[cfg(feature = "deflate-zopfli")] zopfli_buffer_size: Option<usize>,
) -> ZipResult<SwitchWriterFunction<W>> {
if let Closed = self {
return Err(
io::Error::new(io::ErrorKind::BrokenPipe, "ZipWriter was already closed").into(),
);
}
{
#[allow(deprecated)]
#[allow(unreachable_code)]
match compression {
Stored => {
if compression_level.is_some() {
Err(UnsupportedArchive("Unsupported compression level"))
} else {
Ok(Box::new(|bare| Storer(bare)))
}
}
#[cfg(feature = "_deflate-any")]
CompressionMethod::Deflated => {
let default = if cfg!(all(
feature = "deflate-zopfli",
not(feature = "deflate-flate2")
)) {
24
} else {
Compression::default().level() as i64
};
let level = clamp_opt(
compression_level.unwrap_or(default),
deflate_compression_level_range(),
)
.ok_or(UnsupportedArchive("Unsupported compression level"))?
as u32;
#[cfg(feature = "deflate-zopfli")]
{
let best_non_zopfli = Compression::best().level();
if level > best_non_zopfli {
let options = Options {
iteration_count: NonZeroU64::try_from(
(level - best_non_zopfli) as u64,
)
.unwrap(),
..Default::default()
};
return Ok(Box::new(move |bare| match zopfli_buffer_size {
Some(size) => GenericZipWriter::BufferedZopfliDeflater(
BufWriter::with_capacity(
size,
zopfli::DeflateEncoder::new(
options,
Default::default(),
bare,
),
),
),
None => GenericZipWriter::ZopfliDeflater(
zopfli::DeflateEncoder::new(options, Default::default(), bare),
),
}));
}
}
#[cfg(feature = "deflate-flate2")]
{
Ok(Box::new(move |bare| {
GenericZipWriter::Deflater(DeflateEncoder::new(
bare,
Compression::new(level),
))
}))
}
}
#[cfg(feature = "deflate64")]
CompressionMethod::Deflate64 => {
Err(UnsupportedArchive("Compressing Deflate64 is not supported"))
}
#[cfg(feature = "bzip2")]
CompressionMethod::Bzip2 => {
let level = clamp_opt(
compression_level.unwrap_or(bzip2::Compression::default().level() as i64),
bzip2_compression_level_range(),
)
.ok_or(UnsupportedArchive("Unsupported compression level"))?
as u32;
Ok(Box::new(move |bare| {
GenericZipWriter::Bzip2(BzEncoder::new(
bare,
bzip2::Compression::new(level),
))
}))
}
CompressionMethod::AES => Err(UnsupportedArchive(
"AES encryption is enabled through FileOptions::with_aes_encryption",
)),
#[cfg(feature = "zstd")]
CompressionMethod::Zstd => {
let level = clamp_opt(
compression_level.unwrap_or(zstd::DEFAULT_COMPRESSION_LEVEL as i64),
zstd::compression_level_range(),
)
.ok_or(UnsupportedArchive("Unsupported compression level"))?;
Ok(Box::new(move |bare| {
GenericZipWriter::Zstd(ZstdEncoder::new(bare, level as i32).unwrap())
}))
}
#[cfg(feature = "lzma")]
CompressionMethod::Lzma => {
Err(UnsupportedArchive("LZMA isn't supported for compression"))
}
#[cfg(feature = "xz")]
CompressionMethod::Xz => {
Err(UnsupportedArchive("XZ isn't supported for compression"))
}
CompressionMethod::Unsupported(..) => {
Err(UnsupportedArchive("Unsupported compression"))
}
}
}
}
fn switch_to(&mut self, make_new_self: SwitchWriterFunction<W>) -> ZipResult<()> {
let bare = match mem::replace(self, Closed) {
Storer(w) => w,
#[cfg(feature = "deflate-flate2")]
GenericZipWriter::Deflater(w) => w.finish()?,
#[cfg(feature = "deflate-zopfli")]
GenericZipWriter::ZopfliDeflater(w) => w.finish()?,
#[cfg(feature = "deflate-zopfli")]
GenericZipWriter::BufferedZopfliDeflater(w) => w
.into_inner()
.map_err(|e| ZipError::Io(e.into_error()))?
.finish()?,
#[cfg(feature = "bzip2")]
GenericZipWriter::Bzip2(w) => w.finish()?,
#[cfg(feature = "zstd")]
GenericZipWriter::Zstd(w) => w.finish()?,
Closed => {
return Err(io::Error::new(
io::ErrorKind::BrokenPipe,
"ZipWriter was already closed",
)
.into());
}
};
*self = make_new_self(bare);
Ok(())
}
fn ref_mut(&mut self) -> Option<&mut dyn Write> {
match self {
Storer(ref mut w) => Some(w as &mut dyn Write),
#[cfg(feature = "deflate-flate2")]
GenericZipWriter::Deflater(ref mut w) => Some(w as &mut dyn Write),
#[cfg(feature = "deflate-zopfli")]
GenericZipWriter::ZopfliDeflater(w) => Some(w as &mut dyn Write),
#[cfg(feature = "deflate-zopfli")]
GenericZipWriter::BufferedZopfliDeflater(w) => Some(w as &mut dyn Write),
#[cfg(feature = "bzip2")]
GenericZipWriter::Bzip2(ref mut w) => Some(w as &mut dyn Write),
#[cfg(feature = "zstd")]
GenericZipWriter::Zstd(ref mut w) => Some(w as &mut dyn Write),
Closed => None,
}
}
const fn is_closed(&self) -> bool {
matches!(*self, Closed)
}
fn get_plain(&mut self) -> &mut W {
match *self {
Storer(MaybeEncrypted::Unencrypted(ref mut w)) => w,
_ => panic!("Should have switched to stored and unencrypted beforehand"),
}
}
fn unwrap(self) -> W {
match self {
Storer(MaybeEncrypted::Unencrypted(w)) => w,
_ => panic!("Should have switched to stored and unencrypted beforehand"),
}
}
}
#[cfg(feature = "_deflate-any")]
fn deflate_compression_level_range() -> std::ops::RangeInclusive<i64> {
let min = if cfg!(feature = "deflate-flate2") {
Compression::fast().level() as i64
} else {
Compression::best().level() as i64 + 1
};
let max = Compression::best().level() as i64
+ if cfg!(feature = "deflate-zopfli") {
u8::MAX as i64
} else {
0
};
min..=max
}
#[cfg(feature = "bzip2")]
fn bzip2_compression_level_range() -> std::ops::RangeInclusive<i64> {
let min = bzip2::Compression::fast().level() as i64;
let max = bzip2::Compression::best().level() as i64;
min..=max
}
#[cfg(any(feature = "_deflate-any", feature = "bzip2", feature = "zstd"))]
fn clamp_opt<T: Ord + Copy, U: Ord + Copy + TryFrom<T>>(
value: T,
range: std::ops::RangeInclusive<U>,
) -> Option<T> {
if range.contains(&value.try_into().ok()?) {
Some(value)
} else {
None
}
}
fn update_aes_extra_data<W: Write + Seek>(writer: &mut W, file: &mut ZipFileData) -> ZipResult<()> {
let Some((aes_mode, version, compression_method)) = file.aes_mode else {
return Ok(());
};
let extra_data_start = file.extra_data_start.unwrap();
writer.seek(SeekFrom::Start(
extra_data_start + file.aes_extra_data_start,
))?;
let mut buf = Vec::new();
buf.write_u16_le(0x9901)?;
buf.write_u16_le(7)?;
buf.write_u16_le(version as u16)?;
buf.write_all(b"AE")?;
buf.write_all(&[aes_mode as u8])?;
buf.write_u16_le(compression_method.serialize_to_u16())?;
writer.write_all(&buf)?;
let aes_extra_data_start = file.aes_extra_data_start as usize;
let extra_field = Arc::get_mut(file.extra_field.as_mut().unwrap()).unwrap();
extra_field[aes_extra_data_start..aes_extra_data_start + buf.len()].copy_from_slice(&buf);
Ok(())
}
fn update_local_file_header<T: Write + Seek>(writer: &mut T, file: &ZipFileData) -> ZipResult<()> {
const CRC32_OFFSET: u64 = 14;
writer.seek(SeekFrom::Start(file.header_start + CRC32_OFFSET))?;
writer.write_u32_le(file.crc32)?;
if file.large_file {
update_local_zip64_extra_field(writer, file)?;
} else {
if file.compressed_size > spec::ZIP64_BYTES_THR {
return Err(ZipError::Io(io::Error::new(
io::ErrorKind::Other,
"Large file option has not been set",
)));
}
writer.write_u32_le(file.compressed_size as u32)?;
writer.write_u32_le(file.uncompressed_size as u32)?;
}
Ok(())
}
fn write_central_directory_header<T: Write>(writer: &mut T, file: &ZipFileData) -> ZipResult<()> {
let block = file.block()?;
block.write(writer)?;
writer.write_all(&file.file_name_raw)?;
if let Some(extra_field) = &file.extra_field {
writer.write_all(extra_field)?;
}
if let Some(central_extra_field) = &file.central_extra_field {
writer.write_all(central_extra_field)?;
}
writer.write_all(file.file_comment.as_bytes())?;
Ok(())
}
fn update_local_zip64_extra_field<T: Write + Seek>(
writer: &mut T,
file: &ZipFileData,
) -> ZipResult<()> {
let block = file.zip64_extra_field_block().ok_or(InvalidArchive(
"Attempted to update a nonexistent ZIP64 extra field",
))?;
let zip64_extra_field_start = file.header_start
+ size_of::<ZipLocalEntryBlock>() as u64
+ file.file_name_raw.len() as u64;
writer.seek(SeekFrom::Start(zip64_extra_field_start))?;
let block = block.serialize();
writer.write_all(&block)?;
Ok(())
}
#[cfg(not(feature = "unreserved"))]
const EXTRA_FIELD_MAPPING: [u16; 43] = [
0x0007, 0x0008, 0x0009, 0x000a, 0x000c, 0x000d, 0x000e, 0x000f, 0x0014, 0x0015, 0x0016, 0x0017,
0x0018, 0x0019, 0x0020, 0x0021, 0x0022, 0x0023, 0x0065, 0x0066, 0x4690, 0x07c8, 0x2605, 0x2705,
0x2805, 0x334d, 0x4341, 0x4453, 0x4704, 0x470f, 0x4b46, 0x4c41, 0x4d49, 0x4f4c, 0x5356, 0x554e,
0x5855, 0x6542, 0x756e, 0x7855, 0xa220, 0xfd4a, 0x9902,
];
#[cfg(test)]
#[allow(unknown_lints)] #[allow(clippy::needless_update)] #[allow(clippy::octal_escapes)] mod test {
use super::{ExtendedFileOptions, FileOptions, FullFileOptions, ZipWriter};
use crate::compression::CompressionMethod;
use crate::result::ZipResult;
use crate::types::DateTime;
use crate::write::EncryptWith::ZipCrypto;
use crate::write::SimpleFileOptions;
use crate::zipcrypto::ZipCryptoKeys;
use crate::CompressionMethod::Stored;
use crate::ZipArchive;
use std::io::{Cursor, Read, Write};
use std::marker::PhantomData;
use std::path::PathBuf;
#[test]
fn write_empty_zip() {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.set_comment("ZIP");
let result = writer.finish().unwrap();
assert_eq!(result.get_ref().len(), 25);
assert_eq!(
*result.get_ref(),
[80, 75, 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 90, 73, 80]
);
}
#[test]
fn unix_permissions_bitmask() {
let options = SimpleFileOptions::default().unix_permissions(0o120777);
assert_eq!(options.permissions, Some(0o777));
}
#[test]
fn write_zip_dir() {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer
.add_directory(
"test",
SimpleFileOptions::default().last_modified_time(
DateTime::from_date_and_time(2018, 8, 15, 20, 45, 6).unwrap(),
),
)
.unwrap();
assert!(writer
.write(b"writing to a directory is not allowed, and will not write any data")
.is_err());
let result = writer.finish().unwrap();
assert_eq!(result.get_ref().len(), 108);
assert_eq!(
*result.get_ref(),
&[
80u8, 75, 3, 4, 20, 0, 0, 0, 0, 0, 163, 165, 15, 77, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 5, 0, 0, 0, 116, 101, 115, 116, 47, 80, 75, 1, 2, 20, 3, 20, 0, 0, 0, 0, 0,
163, 165, 15, 77, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 237, 65, 0, 0, 0, 0, 116, 101, 115, 116, 47, 80, 75, 5, 6, 0, 0, 0, 0, 1, 0,
1, 0, 51, 0, 0, 0, 35, 0, 0, 0, 0, 0,
] as &[u8]
);
}
#[test]
fn write_symlink_simple() {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer
.add_symlink(
"name",
"target",
SimpleFileOptions::default().last_modified_time(
DateTime::from_date_and_time(2018, 8, 15, 20, 45, 6).unwrap(),
),
)
.unwrap();
assert!(writer
.write(b"writing to a symlink is not allowed and will not write any data")
.is_err());
let result = writer.finish().unwrap();
assert_eq!(result.get_ref().len(), 112);
assert_eq!(
*result.get_ref(),
&[
80u8, 75, 3, 4, 10, 0, 0, 0, 0, 0, 163, 165, 15, 77, 252, 47, 111, 70, 6, 0, 0, 0,
6, 0, 0, 0, 4, 0, 0, 0, 110, 97, 109, 101, 116, 97, 114, 103, 101, 116, 80, 75, 1,
2, 10, 3, 10, 0, 0, 0, 0, 0, 163, 165, 15, 77, 252, 47, 111, 70, 6, 0, 0, 0, 6, 0,
0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 161, 0, 0, 0, 0, 110, 97, 109, 101,
80, 75, 5, 6, 0, 0, 0, 0, 1, 0, 1, 0, 50, 0, 0, 0, 40, 0, 0, 0, 0, 0
] as &[u8],
);
}
#[test]
fn test_path_normalization() {
let mut path = PathBuf::new();
path.push("foo");
path.push("bar");
path.push("..");
path.push(".");
path.push("example.txt");
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer
.start_file_from_path(path, SimpleFileOptions::default())
.unwrap();
let archive = writer.finish_into_readable().unwrap();
assert_eq!(Some("foo/example.txt"), archive.name_for_index(0));
}
#[test]
fn write_symlink_wonky_paths() {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer
.add_symlink(
"directory\\link",
"/absolute/symlink\\with\\mixed/slashes",
SimpleFileOptions::default().last_modified_time(
DateTime::from_date_and_time(2018, 8, 15, 20, 45, 6).unwrap(),
),
)
.unwrap();
assert!(writer
.write(b"writing to a symlink is not allowed and will not write any data")
.is_err());
let result = writer.finish().unwrap();
assert_eq!(result.get_ref().len(), 162);
assert_eq!(
*result.get_ref(),
&[
80u8, 75, 3, 4, 10, 0, 0, 0, 0, 0, 163, 165, 15, 77, 95, 41, 81, 245, 36, 0, 0, 0,
36, 0, 0, 0, 14, 0, 0, 0, 100, 105, 114, 101, 99, 116, 111, 114, 121, 92, 108, 105,
110, 107, 47, 97, 98, 115, 111, 108, 117, 116, 101, 47, 115, 121, 109, 108, 105,
110, 107, 92, 119, 105, 116, 104, 92, 109, 105, 120, 101, 100, 47, 115, 108, 97,
115, 104, 101, 115, 80, 75, 1, 2, 10, 3, 10, 0, 0, 0, 0, 0, 163, 165, 15, 77, 95,
41, 81, 245, 36, 0, 0, 0, 36, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255,
161, 0, 0, 0, 0, 100, 105, 114, 101, 99, 116, 111, 114, 121, 92, 108, 105, 110,
107, 80, 75, 5, 6, 0, 0, 0, 0, 1, 0, 1, 0, 60, 0, 0, 0, 80, 0, 0, 0, 0, 0
] as &[u8],
);
}
#[test]
fn write_mimetype_zip() {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
let options = FileOptions {
compression_method: Stored,
compression_level: None,
last_modified_time: DateTime::default(),
permissions: Some(33188),
large_file: false,
encrypt_with: None,
extended_options: (),
alignment: 1,
#[cfg(feature = "deflate-zopfli")]
zopfli_buffer_size: None,
};
writer.start_file("mimetype", options).unwrap();
writer
.write_all(b"application/vnd.oasis.opendocument.text")
.unwrap();
let result = writer.finish().unwrap();
assert_eq!(result.get_ref().len(), 153);
let mut v = Vec::new();
v.extend_from_slice(include_bytes!("../tests/data/mimetype.zip"));
assert_eq!(result.get_ref(), &v);
}
const RT_TEST_TEXT: &str = "And I can't stop thinking about the moments that I lost to you\
And I can't stop thinking of things I used to do\
And I can't stop making bad decisions\
And I can't stop eating stuff you make me chew\
I put on a smile like you wanna see\
Another day goes by that I long to be like you";
const RT_TEST_FILENAME: &str = "subfolder/sub-subfolder/can't_stop.txt";
const SECOND_FILENAME: &str = "different_name.xyz";
const THIRD_FILENAME: &str = "third_name.xyz";
#[test]
fn write_non_utf8() {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
let options = FileOptions {
compression_method: Stored,
compression_level: None,
last_modified_time: DateTime::default(),
permissions: Some(33188),
large_file: false,
encrypt_with: None,
extended_options: (),
alignment: 1,
#[cfg(feature = "deflate-zopfli")]
zopfli_buffer_size: None,
};
let filename = unsafe { String::from_utf8_unchecked(vec![214, 208, 206, 196]) };
writer.start_file(filename, options).unwrap();
writer.write_all(b"encoding GB18030").unwrap();
let filename = unsafe { String::from_utf8_unchecked(vec![147, 250, 149, 182]) };
writer.start_file(filename, options).unwrap();
writer.write_all(b"encoding SHIFT_JIS").unwrap();
let result = writer.finish().unwrap();
assert_eq!(result.get_ref().len(), 224);
let mut v = Vec::new();
v.extend_from_slice(include_bytes!("../tests/data/non_utf8.zip"));
assert_eq!(result.get_ref(), &v);
}
#[test]
fn path_to_string() {
let mut path = PathBuf::new();
#[cfg(windows)]
path.push(r"C:\");
#[cfg(unix)]
path.push("/");
path.push("windows");
path.push("..");
path.push(".");
path.push("system32");
let path_str = super::path_to_string(&path);
assert_eq!(&*path_str, "system32");
}
#[test]
fn test_shallow_copy() {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
let options = FileOptions {
compression_method: CompressionMethod::default(),
compression_level: None,
last_modified_time: DateTime::default(),
permissions: Some(33188),
large_file: false,
encrypt_with: None,
extended_options: (),
alignment: 0,
#[cfg(feature = "deflate-zopfli")]
zopfli_buffer_size: None,
};
writer.start_file(RT_TEST_FILENAME, options).unwrap();
writer.write_all(RT_TEST_TEXT.as_ref()).unwrap();
writer
.shallow_copy_file(RT_TEST_FILENAME, SECOND_FILENAME)
.unwrap();
writer
.shallow_copy_file(RT_TEST_FILENAME, SECOND_FILENAME)
.expect_err("Duplicate filename");
let zip = writer.finish().unwrap();
let mut writer = ZipWriter::new_append(zip).unwrap();
writer
.shallow_copy_file(SECOND_FILENAME, SECOND_FILENAME)
.expect_err("Duplicate filename");
let mut reader = writer.finish_into_readable().unwrap();
let mut file_names: Vec<&str> = reader.file_names().collect();
file_names.sort();
let mut expected_file_names = vec![RT_TEST_FILENAME, SECOND_FILENAME];
expected_file_names.sort();
assert_eq!(file_names, expected_file_names);
let mut first_file_content = String::new();
reader
.by_name(RT_TEST_FILENAME)
.unwrap()
.read_to_string(&mut first_file_content)
.unwrap();
assert_eq!(first_file_content, RT_TEST_TEXT);
let mut second_file_content = String::new();
reader
.by_name(SECOND_FILENAME)
.unwrap()
.read_to_string(&mut second_file_content)
.unwrap();
assert_eq!(second_file_content, RT_TEST_TEXT);
}
#[test]
fn test_deep_copy() {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
let options = FileOptions {
compression_method: CompressionMethod::default(),
compression_level: None,
last_modified_time: DateTime::default(),
permissions: Some(33188),
large_file: false,
encrypt_with: None,
extended_options: (),
alignment: 0,
#[cfg(feature = "deflate-zopfli")]
zopfli_buffer_size: None,
};
writer.start_file(RT_TEST_FILENAME, options).unwrap();
writer.write_all(RT_TEST_TEXT.as_ref()).unwrap();
writer
.deep_copy_file(RT_TEST_FILENAME, SECOND_FILENAME)
.unwrap();
let zip = writer.finish().unwrap();
let mut writer = ZipWriter::new_append(zip).unwrap();
writer
.deep_copy_file(RT_TEST_FILENAME, THIRD_FILENAME)
.unwrap();
let zip = writer.finish().unwrap();
let mut reader = ZipArchive::new(zip).unwrap();
let mut file_names: Vec<&str> = reader.file_names().collect();
file_names.sort();
let mut expected_file_names = vec![RT_TEST_FILENAME, SECOND_FILENAME, THIRD_FILENAME];
expected_file_names.sort();
assert_eq!(file_names, expected_file_names);
let mut first_file_content = String::new();
reader
.by_name(RT_TEST_FILENAME)
.unwrap()
.read_to_string(&mut first_file_content)
.unwrap();
assert_eq!(first_file_content, RT_TEST_TEXT);
let mut second_file_content = String::new();
reader
.by_name(SECOND_FILENAME)
.unwrap()
.read_to_string(&mut second_file_content)
.unwrap();
assert_eq!(second_file_content, RT_TEST_TEXT);
}
#[test]
fn duplicate_filenames() {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer
.start_file("foo/bar/test", SimpleFileOptions::default())
.unwrap();
writer
.write_all("The quick brown 🦊 jumps over the lazy 🐕".as_bytes())
.unwrap();
writer
.start_file("foo/bar/test", SimpleFileOptions::default())
.expect_err("Expected duplicate filename not to be allowed");
}
#[test]
fn test_filename_looks_like_zip64_locator() {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer
.start_file(
"PK\u{6}\u{7}\0\0\0\u{11}\0\0\0\0\0\0\0\0\0\0\0\0",
SimpleFileOptions::default(),
)
.unwrap();
let zip = writer.finish().unwrap();
let _ = ZipArchive::new(zip).unwrap();
}
#[test]
fn test_filename_looks_like_zip64_locator_2() {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer
.start_file(
"PK\u{6}\u{6}\0\0\0\0\0\0\0\0\0\0PK\u{6}\u{7}\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0",
SimpleFileOptions::default(),
)
.unwrap();
let zip = writer.finish().unwrap();
let _ = ZipArchive::new(zip).unwrap();
}
#[test]
fn test_filename_looks_like_zip64_locator_2a() {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer
.start_file(
"PK\u{6}\u{6}PK\u{6}\u{7}\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0",
SimpleFileOptions::default(),
)
.unwrap();
let zip = writer.finish().unwrap();
let _ = ZipArchive::new(zip).unwrap();
}
#[test]
fn test_filename_looks_like_zip64_locator_3() {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer
.start_file("\0PK\u{6}\u{6}", SimpleFileOptions::default())
.unwrap();
writer
.start_file(
"\0\u{4}\0\0PK\u{6}\u{7}\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\u{3}",
SimpleFileOptions::default(),
)
.unwrap();
let zip = writer.finish().unwrap();
let _ = ZipArchive::new(zip).unwrap();
}
#[test]
fn test_filename_looks_like_zip64_locator_4() {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer
.start_file("PK\u{6}\u{6}", SimpleFileOptions::default())
.unwrap();
writer
.start_file("\0\0\0\0\0\0", SimpleFileOptions::default())
.unwrap();
writer
.start_file("\0", SimpleFileOptions::default())
.unwrap();
writer.start_file("", SimpleFileOptions::default()).unwrap();
writer
.start_file("\0\0", SimpleFileOptions::default())
.unwrap();
writer
.start_file(
"\0\0\0PK\u{6}\u{7}\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0",
SimpleFileOptions::default(),
)
.unwrap();
let zip = writer.finish().unwrap();
let _ = ZipArchive::new(zip).unwrap();
}
#[test]
fn test_filename_looks_like_zip64_locator_5() -> ZipResult<()> {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer
.add_directory("", SimpleFileOptions::default().with_alignment(21))
.unwrap();
let mut writer = ZipWriter::new_append(writer.finish().unwrap()).unwrap();
writer.shallow_copy_file("/", "").unwrap();
writer.shallow_copy_file("", "\0").unwrap();
writer.shallow_copy_file("\0", "PK\u{6}\u{6}").unwrap();
let mut writer = ZipWriter::new_append(writer.finish().unwrap()).unwrap();
writer
.start_file("\0\0\0\0\0\0", SimpleFileOptions::default())
.unwrap();
let mut writer = ZipWriter::new_append(writer.finish().unwrap()).unwrap();
writer
.start_file(
"#PK\u{6}\u{7}\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0",
SimpleFileOptions::default(),
)
.unwrap();
let zip = writer.finish().unwrap();
let _ = ZipArchive::new(zip).unwrap();
Ok(())
}
#[test]
fn remove_shallow_copy_keeps_original() -> ZipResult<()> {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer
.start_file("original", SimpleFileOptions::default())
.unwrap();
writer.write_all(RT_TEST_TEXT.as_bytes()).unwrap();
writer
.shallow_copy_file("original", "shallow_copy")
.unwrap();
writer.abort_file().unwrap();
let mut zip = ZipArchive::new(writer.finish().unwrap()).unwrap();
let mut file = zip.by_name("original").unwrap();
let mut contents = Vec::new();
file.read_to_end(&mut contents).unwrap();
assert_eq!(RT_TEST_TEXT.as_bytes(), contents);
Ok(())
}
#[test]
fn remove_encrypted_file() -> ZipResult<()> {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
let first_file_options = SimpleFileOptions::default()
.with_alignment(65535)
.with_deprecated_encryption(b"Password");
writer.start_file("", first_file_options).unwrap();
writer.abort_file().unwrap();
let zip = writer.finish().unwrap();
let mut writer = ZipWriter::new(zip);
writer.start_file("", SimpleFileOptions::default()).unwrap();
Ok(())
}
#[test]
fn remove_encrypted_aligned_symlink() -> ZipResult<()> {
let mut options = SimpleFileOptions::default();
options = options.with_deprecated_encryption(b"Password");
options.alignment = 65535;
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.add_symlink("", "s\t\0\0ggggg\0\0", options).unwrap();
writer.abort_file().unwrap();
let zip = writer.finish().unwrap();
let mut writer = ZipWriter::new_append(zip).unwrap();
writer.start_file("", SimpleFileOptions::default()).unwrap();
Ok(())
}
#[cfg(feature = "deflate-zopfli")]
#[test]
fn zopfli_empty_write() -> ZipResult<()> {
let mut options = SimpleFileOptions::default();
options = options
.compression_method(CompressionMethod::default())
.compression_level(Some(264));
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.start_file("", options).unwrap();
writer.write_all(&[]).unwrap();
writer.write_all(&[]).unwrap();
Ok(())
}
#[test]
fn crash_with_no_features() -> ZipResult<()> {
const ORIGINAL_FILE_NAME: &str = "PK\u{6}\u{6}\0\0\0\0\0\0\0\0\0\u{2}g\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\u{1}\0\0\0\0\0\0\0\0\0\0PK\u{6}\u{7}\0\0\0\0\0\0\0\0\0\0\0\0\u{7}\0\t'";
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
let mut options = SimpleFileOptions::default();
options = options.with_alignment(3584).compression_method(Stored);
writer.start_file(ORIGINAL_FILE_NAME, options)?;
let archive = writer.finish()?;
let mut writer = ZipWriter::new_append(archive)?;
writer.shallow_copy_file(ORIGINAL_FILE_NAME, "\u{6}\\")?;
writer.finish()?;
Ok(())
}
#[test]
fn test_alignment() {
let page_size = 4096;
let options = SimpleFileOptions::default()
.compression_method(Stored)
.with_alignment(page_size);
let mut zip = ZipWriter::new(Cursor::new(Vec::new()));
let contents = b"sleeping";
let () = zip.start_file("sleep", options).unwrap();
let _count = zip.write(&contents[..]).unwrap();
let mut zip = zip.finish_into_readable().unwrap();
let file = zip.by_index(0).unwrap();
assert_eq!(file.name(), "sleep");
assert_eq!(file.data_start(), page_size.into());
}
#[test]
fn test_alignment_2() {
let page_size = 4096;
let mut data = Vec::new();
{
let options = SimpleFileOptions::default()
.compression_method(Stored)
.with_alignment(page_size);
let mut zip = ZipWriter::new(Cursor::new(&mut data));
let contents = b"sleeping";
let () = zip.start_file("sleep", options).unwrap();
let _count = zip.write(&contents[..]).unwrap();
}
assert_eq!(data[4096..4104], b"sleeping"[..]);
{
let mut zip = ZipArchive::new(Cursor::new(&mut data)).unwrap();
let file = zip.by_index(0).unwrap();
assert_eq!(file.name(), "sleep");
assert_eq!(file.data_start(), page_size.into());
}
}
#[test]
fn test_crash_short_read() {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
let comment = vec![
1, 80, 75, 5, 6, 237, 237, 237, 237, 237, 237, 237, 237, 44, 255, 191, 255, 255, 255,
255, 255, 255, 255, 255, 16,
]
.into_boxed_slice();
writer.set_raw_comment(comment);
let options = SimpleFileOptions::default()
.compression_method(Stored)
.with_alignment(11823);
writer.start_file("", options).unwrap();
writer.write_all(&[255, 255, 44, 255, 0]).unwrap();
let written = writer.finish().unwrap();
let _ = ZipWriter::new_append(written).unwrap();
}
#[cfg(all(feature = "_deflate-any", feature = "aes-crypto"))]
#[test]
fn test_fuzz_failure_2024_05_08() -> ZipResult<()> {
let mut first_writer = ZipWriter::new(Cursor::new(Vec::new()));
let mut second_writer = ZipWriter::new(Cursor::new(Vec::new()));
let options = SimpleFileOptions::default()
.compression_method(Stored)
.with_alignment(46036);
second_writer.add_symlink("\0", "", options)?;
let second_archive = second_writer.finish_into_readable()?.into_inner();
let mut second_writer = ZipWriter::new_append(second_archive)?;
let options = SimpleFileOptions::default()
.compression_method(CompressionMethod::Deflated)
.large_file(true)
.with_alignment(46036)
.with_aes_encryption(crate::AesMode::Aes128, "\0\0");
second_writer.add_symlink("", "", options)?;
let second_archive = second_writer.finish_into_readable()?.into_inner();
let mut second_writer = ZipWriter::new_append(second_archive)?;
let options = SimpleFileOptions::default().compression_method(Stored);
second_writer.start_file(" ", options)?;
let second_archive = second_writer.finish_into_readable()?;
first_writer.merge_archive(second_archive)?;
let _ = ZipArchive::new(first_writer.finish()?)?;
Ok(())
}
#[cfg(feature = "bzip2")]
#[test]
fn test_fuzz_failure_2024_06_08() -> ZipResult<()> {
use crate::write::ExtendedFileOptions;
use CompressionMethod::Bzip2;
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.set_flush_on_finish_file(false);
const SYMLINK_PATH: &str = "PK\u{6}\u{6}K\u{6}\u{6}\u{6}\0\0\0\0\u{18}\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\u{1}\0\0\0\0\0\0\0\0\u{1}\0\0PK\u{1}\u{2},\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\u{1}\0\0PK\u{1}\u{2},\0\0\0\0\0\0\0\0\0\0l\0\0\0\0\0\0PK\u{6}\u{7}P\0\0\0\0\0\0\0\0\0\0\0\0\u{1}\0\0";
let sub_writer = {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.set_flush_on_finish_file(false);
let options = FileOptions {
compression_method: Bzip2,
compression_level: None,
last_modified_time: DateTime::from_date_and_time(1980, 5, 20, 21, 0, 57)?,
permissions: None,
large_file: false,
encrypt_with: None,
extended_options: ExtendedFileOptions {
extra_data: vec![].into(),
central_extra_data: vec![].into(),
},
alignment: 2048,
..Default::default()
};
writer.add_symlink_from_path(SYMLINK_PATH, "||\0\0\0\0", options)?;
writer = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
writer.deep_copy_file_from_path(SYMLINK_PATH, "")?;
writer = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
writer.abort_file()?;
writer
};
writer.merge_archive(sub_writer.finish_into_readable()?)?;
writer = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
writer.deep_copy_file_from_path(SYMLINK_PATH, "foo")?;
let _ = writer.finish_into_readable()?;
Ok(())
}
#[test]
fn test_short_extra_data() {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.set_flush_on_finish_file(false);
let options = FileOptions {
extended_options: ExtendedFileOptions {
extra_data: vec![].into(),
central_extra_data: vec![99, 0, 15, 0, 207].into(),
},
..Default::default()
};
assert!(writer.start_file_from_path("", options).is_err());
}
#[test]
#[cfg(not(feature = "unreserved"))]
fn test_invalid_extra_data() -> ZipResult<()> {
use crate::write::ExtendedFileOptions;
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.set_flush_on_finish_file(false);
let options = FileOptions {
compression_method: Stored,
compression_level: None,
last_modified_time: DateTime::from_date_and_time(1980, 1, 4, 6, 54, 0)?,
permissions: None,
large_file: false,
encrypt_with: None,
extended_options: ExtendedFileOptions {
extra_data: vec![].into(),
central_extra_data: vec![
7, 0, 15, 0, 207, 117, 177, 117, 112, 2, 0, 255, 255, 131, 255, 255, 255, 80,
185,
]
.into(),
},
alignment: 32787,
..Default::default()
};
assert!(writer.start_file_from_path("", options).is_err());
Ok(())
}
#[test]
#[cfg(not(feature = "unreserved"))]
fn test_invalid_extra_data_unreserved() {
use crate::write::ExtendedFileOptions;
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
let options = FileOptions {
compression_method: Stored,
compression_level: None,
last_modified_time: DateTime::from_date_and_time(2021, 8, 8, 1, 0, 29).unwrap(),
permissions: None,
large_file: true,
encrypt_with: None,
extended_options: ExtendedFileOptions {
extra_data: vec![].into(),
central_extra_data: vec![
1, 41, 4, 0, 1, 255, 245, 117, 117, 112, 5, 0, 80, 255, 149, 255, 247,
]
.into(),
},
alignment: 4103,
..Default::default()
};
assert!(writer.start_file_from_path("", options).is_err());
}
#[cfg(feature = "deflate64")]
#[test]
fn test_fuzz_crash_2024_06_13a() -> ZipResult<()> {
use crate::write::ExtendedFileOptions;
use CompressionMethod::Deflate64;
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.set_flush_on_finish_file(false);
let options = FileOptions {
compression_method: Deflate64,
compression_level: None,
last_modified_time: DateTime::from_date_and_time(2039, 4, 17, 6, 18, 19)?,
permissions: None,
large_file: true,
encrypt_with: None,
extended_options: ExtendedFileOptions {
extra_data: vec![].into(),
central_extra_data: vec![].into(),
},
alignment: 4,
..Default::default()
};
writer.add_directory_from_path("", options)?;
let _ = writer.finish_into_readable()?;
Ok(())
}
#[test]
fn test_fuzz_crash_2024_06_13b() -> ZipResult<()> {
use crate::write::ExtendedFileOptions;
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.set_flush_on_finish_file(false);
let sub_writer = {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.set_flush_on_finish_file(false);
let options = FileOptions {
compression_method: Stored,
compression_level: None,
last_modified_time: DateTime::from_date_and_time(1980, 4, 14, 6, 11, 54)?,
permissions: None,
large_file: false,
encrypt_with: None,
extended_options: ExtendedFileOptions {
extra_data: vec![].into(),
central_extra_data: vec![].into(),
},
alignment: 185,
..Default::default()
};
writer.add_symlink_from_path("", "", options)?;
writer
};
writer.merge_archive(sub_writer.finish_into_readable()?)?;
writer.deep_copy_file_from_path("", "_copy")?;
let _ = writer.finish_into_readable()?;
Ok(())
}
#[test]
fn test_fuzz_crash_2024_06_14() -> ZipResult<()> {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.set_flush_on_finish_file(false);
let sub_writer = {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.set_flush_on_finish_file(false);
let options = FullFileOptions {
compression_method: Stored,
large_file: true,
alignment: 93,
..Default::default()
};
writer.start_file_from_path("\0", options)?;
writer = ZipWriter::new_append(writer.finish()?)?;
writer.deep_copy_file_from_path("\0", "")?;
writer
};
writer.merge_archive(sub_writer.finish_into_readable()?)?;
writer.deep_copy_file_from_path("", "copy")?;
let _ = writer.finish_into_readable()?;
Ok(())
}
#[test]
fn test_fuzz_crash_2024_06_14a() -> ZipResult<()> {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.set_flush_on_finish_file(false);
let options = FileOptions {
compression_method: Stored,
compression_level: None,
last_modified_time: DateTime::from_date_and_time(2083, 5, 30, 21, 45, 35)?,
permissions: None,
large_file: false,
encrypt_with: None,
extended_options: ExtendedFileOptions {
extra_data: vec![].into(),
central_extra_data: vec![].into(),
},
alignment: 2565,
..Default::default()
};
writer.add_symlink_from_path("", "", options)?;
writer.abort_file()?;
let options = FileOptions {
compression_method: Stored,
compression_level: None,
last_modified_time: DateTime::default(),
permissions: None,
large_file: false,
encrypt_with: None,
extended_options: ExtendedFileOptions {
extra_data: vec![].into(),
central_extra_data: vec![].into(),
},
alignment: 0,
..Default::default()
};
writer.start_file_from_path("", options)?;
let _ = writer.finish_into_readable()?;
Ok(())
}
#[allow(deprecated)]
#[test]
fn test_fuzz_crash_2024_06_14b() -> ZipResult<()> {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.set_flush_on_finish_file(false);
let options = FileOptions {
compression_method: Stored,
compression_level: None,
last_modified_time: DateTime::from_date_and_time(2078, 3, 6, 12, 48, 58)?,
permissions: None,
large_file: true,
encrypt_with: None,
extended_options: ExtendedFileOptions {
extra_data: vec![].into(),
central_extra_data: vec![].into(),
},
alignment: 65521,
..Default::default()
};
writer.start_file_from_path("\u{4}\0@\n//\u{c}", options)?;
writer = ZipWriter::new_append(writer.finish()?)?;
writer.abort_file()?;
let options = FileOptions {
compression_method: CompressionMethod::Unsupported(65535),
compression_level: None,
last_modified_time: DateTime::from_date_and_time(2055, 10, 2, 11, 48, 49)?,
permissions: None,
large_file: true,
encrypt_with: None,
extended_options: ExtendedFileOptions {
extra_data: vec![255, 255, 1, 0, 255, 0, 0, 0, 0].into(),
central_extra_data: vec![].into(),
},
alignment: 65535,
..Default::default()
};
writer.add_directory_from_path("", options)?;
let _ = writer.finish_into_readable()?;
Ok(())
}
#[test]
fn test_fuzz_crash_2024_06_14c() -> ZipResult<()> {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.set_flush_on_finish_file(false);
let sub_writer = {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.set_flush_on_finish_file(false);
let options = FileOptions {
compression_method: Stored,
compression_level: None,
last_modified_time: DateTime::from_date_and_time(2060, 4, 6, 13, 13, 3)?,
permissions: None,
large_file: true,
encrypt_with: None,
extended_options: ExtendedFileOptions {
extra_data: vec![].into(),
central_extra_data: vec![].into(),
},
alignment: 0,
..Default::default()
};
writer.start_file_from_path("\0", options)?;
writer.write_all(&([]))?;
writer = ZipWriter::new_append(writer.finish()?)?;
writer.deep_copy_file_from_path("\0", "")?;
writer
};
writer.merge_archive(sub_writer.finish_into_readable()?)?;
writer.deep_copy_file_from_path("", "_copy")?;
let _ = writer.finish_into_readable()?;
Ok(())
}
#[cfg(all(feature = "_deflate-any", feature = "aes-crypto"))]
#[test]
fn test_fuzz_crash_2024_06_14d() -> ZipResult<()> {
use crate::write::EncryptWith::Aes;
use crate::AesMode::Aes256;
use CompressionMethod::Deflated;
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.set_flush_on_finish_file(false);
let options = FileOptions {
compression_method: Deflated,
compression_level: Some(5),
last_modified_time: DateTime::from_date_and_time(2107, 4, 8, 15, 54, 19)?,
permissions: None,
large_file: true,
encrypt_with: Some(Aes {
mode: Aes256,
password: "",
}),
extended_options: ExtendedFileOptions {
extra_data: vec![2, 0, 1, 0, 0].into(),
central_extra_data: vec![
35, 229, 2, 0, 41, 41, 231, 44, 2, 0, 52, 233, 82, 201, 0, 0, 3, 0, 2, 0, 233,
255, 3, 0, 2, 0, 26, 154, 38, 251, 0, 0,
]
.into(),
},
alignment: 65535,
..Default::default()
};
assert!(writer.add_directory_from_path("", options).is_err());
Ok(())
}
#[test]
fn test_fuzz_crash_2024_06_14e() -> ZipResult<()> {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.set_flush_on_finish_file(false);
let options = FileOptions {
compression_method: Stored,
compression_level: None,
last_modified_time: DateTime::from_date_and_time(1988, 1, 1, 1, 6, 26)?,
permissions: None,
large_file: true,
encrypt_with: None,
extended_options: ExtendedFileOptions {
extra_data: vec![76, 0, 1, 0, 0, 2, 0, 0, 0].into(),
central_extra_data: vec![
1, 149, 1, 0, 255, 3, 0, 0, 0, 2, 255, 0, 0, 12, 65, 1, 0, 0, 67, 149, 0, 0,
76, 149, 2, 0, 149, 149, 67, 149, 0, 0,
]
.into(),
},
alignment: 65535,
..Default::default()
};
assert!(writer.add_directory_from_path("", options).is_err());
let _ = writer.finish_into_readable()?;
Ok(())
}
#[allow(deprecated)]
#[test]
fn test_fuzz_crash_2024_06_17() -> ZipResult<()> {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.set_flush_on_finish_file(false);
let sub_writer = {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.set_flush_on_finish_file(false);
let sub_writer = {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.set_flush_on_finish_file(false);
let sub_writer = {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.set_flush_on_finish_file(false);
let sub_writer = {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.set_flush_on_finish_file(false);
let sub_writer = {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.set_flush_on_finish_file(false);
let sub_writer = {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.set_flush_on_finish_file(false);
let sub_writer = {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.set_flush_on_finish_file(false);
let sub_writer = {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.set_flush_on_finish_file(false);
let sub_writer = {
let mut writer =
ZipWriter::new(Cursor::new(Vec::new()));
writer.set_flush_on_finish_file(false);
let options = FileOptions {
compression_method: CompressionMethod::Unsupported(
65535,
),
compression_level: Some(5),
last_modified_time: DateTime::from_date_and_time(
2107, 2, 8, 15, 0, 0,
)?,
permissions: None,
large_file: true,
encrypt_with: Some(ZipCrypto(
ZipCryptoKeys::of(
0x63ff, 0xc62d3103, 0xfffe00ea,
),
PhantomData,
)),
extended_options: ExtendedFileOptions {
extra_data: vec![].into(),
central_extra_data: vec![].into(),
},
alignment: 255,
..Default::default()
};
writer.add_symlink_from_path("1\0PK\u{6}\u{6}\u{b}\u{6}\u{6}\u{6}\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\u{1}\0\0\0\0\0\0\0\0\u{b}\0\0PK\u{1}\u{2},\0\0\0\0\0\0\0\0\0\0\0\u{10}\0\0\0K\u{6}\u{6}\0\0\0\0\0\0\0\0PK\u{2}\u{6}", "", options)?;
writer = ZipWriter::new_append(
writer.finish_into_readable()?.into_inner(),
)?;
writer
};
writer.merge_archive(sub_writer.finish_into_readable()?)?;
writer = ZipWriter::new_append(
writer.finish_into_readable()?.into_inner(),
)?;
let options = FileOptions {
compression_method: Stored,
compression_level: None,
last_modified_time: DateTime::from_date_and_time(
1992, 7, 3, 0, 0, 0,
)?,
permissions: None,
large_file: true,
encrypt_with: None,
extended_options: ExtendedFileOptions {
extra_data: vec![].into(),
central_extra_data: vec![].into(),
},
alignment: 43,
..Default::default()
};
writer.start_file_from_path(
"\0\0\0\u{3}\0\u{1a}\u{1a}\u{1a}\u{1a}\u{1a}\u{1a}",
options,
)?;
let options = FileOptions {
compression_method: Stored,
compression_level: None,
last_modified_time: DateTime::from_date_and_time(
2006, 3, 27, 2, 24, 26,
)?,
permissions: None,
large_file: false,
encrypt_with: None,
extended_options: ExtendedFileOptions {
extra_data: vec![].into(),
central_extra_data: vec![].into(),
},
alignment: 26,
..Default::default()
};
writer.start_file_from_path("\0K\u{6}\u{6}\0PK\u{6}\u{7}PK\u{6}\u{6}\0\0\0\0\0\0\0\0PK\u{2}\u{6}", options)?;
writer = ZipWriter::new_append(
writer.finish_into_readable()?.into_inner(),
)?;
let options = FileOptions {
compression_method: Stored,
compression_level: Some(17),
last_modified_time: DateTime::from_date_and_time(
2103, 4, 10, 23, 15, 18,
)?,
permissions: Some(3284386755),
large_file: true,
encrypt_with: Some(ZipCrypto(
ZipCryptoKeys::of(
0x8888c5bf, 0x88888888, 0xff888888,
),
PhantomData,
)),
extended_options: ExtendedFileOptions {
extra_data: vec![3, 0, 1, 0, 255, 144, 136, 0, 0]
.into(),
central_extra_data: vec![].into(),
},
alignment: 65535,
..Default::default()
};
writer.add_symlink_from_path("", "\nu", options)?;
writer = ZipWriter::new_append(writer.finish()?)?;
writer
};
writer.merge_archive(sub_writer.finish_into_readable()?)?;
writer = ZipWriter::new_append(
writer.finish_into_readable()?.into_inner(),
)?;
writer
};
writer.merge_archive(sub_writer.finish_into_readable()?)?;
writer = ZipWriter::new_append(writer.finish()?)?;
writer
};
writer.merge_archive(sub_writer.finish_into_readable()?)?;
writer =
ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
writer.abort_file()?;
let options = FileOptions {
compression_method: CompressionMethod::Unsupported(49603),
compression_level: Some(20),
last_modified_time: DateTime::from_date_and_time(
2047, 4, 14, 3, 15, 14,
)?,
permissions: Some(3284386755),
large_file: true,
encrypt_with: Some(ZipCrypto(
ZipCryptoKeys::of(0xc3, 0x0, 0x0),
PhantomData,
)),
extended_options: ExtendedFileOptions {
extra_data: vec![].into(),
central_extra_data: vec![].into(),
},
alignment: 0,
..Default::default()
};
writer.add_directory_from_path("", options)?;
writer.deep_copy_file_from_path("/", "")?;
writer.shallow_copy_file_from_path("", "copy")?;
assert!(writer.shallow_copy_file_from_path("", "copy").is_err());
assert!(writer.shallow_copy_file_from_path("", "copy").is_err());
assert!(writer.shallow_copy_file_from_path("", "copy").is_err());
assert!(writer.shallow_copy_file_from_path("", "copy").is_err());
assert!(writer.shallow_copy_file_from_path("", "copy").is_err());
assert!(writer.shallow_copy_file_from_path("", "copy").is_err());
writer
};
writer.merge_archive(sub_writer.finish_into_readable()?)?;
writer
};
writer.merge_archive(sub_writer.finish_into_readable()?)?;
writer
};
writer.merge_archive(sub_writer.finish_into_readable()?)?;
writer
};
writer.merge_archive(sub_writer.finish_into_readable()?)?;
writer
};
writer.merge_archive(sub_writer.finish_into_readable()?)?;
let _ = writer.finish_into_readable()?;
Ok(())
}
#[test]
fn test_fuzz_crash_2024_06_17a() -> ZipResult<()> {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.set_flush_on_finish_file(false);
const PATH_1: &str = "\0I\01\0P\0\0\u{2}\0\0\u{1a}\u{1a}\u{1a}\u{1a}\u{1b}\u{1a}UT\u{5}\0\0\u{1a}\u{1a}\u{1a}\u{1a}UT\u{5}\0\u{1}\0\u{1a}\u{1a}\u{1a}UT\t\0uc\u{5}\0\0\0\0\u{7f}\u{7f}\u{7f}\u{7f}PK\u{6}";
let sub_writer = {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.set_flush_on_finish_file(false);
let sub_writer = {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.set_flush_on_finish_file(false);
let options = FileOptions {
compression_method: Stored,
compression_level: None,
last_modified_time: DateTime::from_date_and_time(1981, 1, 1, 0, 24, 21)?,
permissions: Some(16908288),
large_file: false,
encrypt_with: None,
extended_options: ExtendedFileOptions {
extra_data: vec![].into(),
central_extra_data: vec![].into(),
},
alignment: 20555,
..Default::default()
};
writer.start_file_from_path(
"\0\u{7}\u{1}\0\0\0\0\0\0\0\0\u{1}\0\0PK\u{1}\u{2};",
options,
)?;
writer.write_all(
&([
255, 255, 255, 255, 253, 253, 253, 203, 203, 203, 253, 253, 253, 253, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 249, 191, 225, 225,
241, 197,
]),
)?;
writer.write_all(
&([
197, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 75, 0,
]),
)?;
writer
};
writer.merge_archive(sub_writer.finish_into_readable()?)?;
writer = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
let options = FileOptions {
compression_method: Stored,
compression_level: None,
last_modified_time: DateTime::from_date_and_time(1980, 11, 14, 10, 46, 47)?,
permissions: None,
large_file: false,
encrypt_with: None,
extended_options: ExtendedFileOptions {
extra_data: vec![].into(),
central_extra_data: vec![].into(),
},
alignment: 0,
..Default::default()
};
writer.start_file_from_path(PATH_1, options)?;
writer.deep_copy_file_from_path(PATH_1, "eee\u{6}\0\0\0\0\0\0\0\0\0\0\0$\0\0\0\0\0\0\u{7f}\u{7f}PK\u{6}\u{6}K\u{6}\u{6}\u{6}\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\u{1}\0\0\0\0\0\0\0\0\u{1}\0\0PK\u{1}\u{1e},\0\0\0\0\0\0\0\0\0\0\0\u{8}\0*\0\0\u{1}PK\u{6}\u{7}PK\u{6}\u{6}\0\0\0\0\0\0\0\0}K\u{2}\u{6}")?;
writer
};
writer.merge_archive(sub_writer.finish_into_readable()?)?;
writer = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
writer.deep_copy_file_from_path(PATH_1, "")?;
writer = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
writer.shallow_copy_file_from_path("", "copy")?;
writer = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
let _ = writer.finish_into_readable()?;
Ok(())
}
#[test]
#[allow(clippy::octal_escapes)]
#[cfg(feature = "bzip2")]
fn test_fuzz_crash_2024_06_17b() -> ZipResult<()> {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.set_flush_on_finish_file(false);
let sub_writer = {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.set_flush_on_finish_file(false);
let sub_writer = {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.set_flush_on_finish_file(false);
let sub_writer = {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.set_flush_on_finish_file(false);
let sub_writer = {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.set_flush_on_finish_file(false);
let sub_writer = {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.set_flush_on_finish_file(false);
let sub_writer = {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.set_flush_on_finish_file(false);
let sub_writer = {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.set_flush_on_finish_file(false);
let sub_writer = {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.set_flush_on_finish_file(false);
let options = FileOptions {
compression_method: Stored,
compression_level: None,
last_modified_time: DateTime::from_date_and_time(
1981, 1, 1, 0, 0, 21,
)?,
permissions: Some(16908288),
large_file: false,
encrypt_with: None,
extended_options: ExtendedFileOptions {
extra_data: vec![].into(),
central_extra_data: vec![].into(),
},
alignment: 20555,
..Default::default()
};
writer.start_file_from_path("\0\u{7}\u{1}\0\0\0\0\0\0\0\0\u{1}\0\0PK\u{1}\u{2};\u{1a}\u{18}\u{1a}UT\t.........................\0u", options)?;
writer
};
writer.merge_archive(sub_writer.finish_into_readable()?)?;
let options = FileOptions {
compression_method: CompressionMethod::Bzip2,
compression_level: Some(5),
last_modified_time: DateTime::from_date_and_time(
2055, 7, 7, 3, 6, 6,
)?,
permissions: None,
large_file: false,
encrypt_with: None,
extended_options: ExtendedFileOptions {
extra_data: vec![].into(),
central_extra_data: vec![].into(),
},
alignment: 0,
..Default::default()
};
writer.start_file_from_path("\0\0\0\0..\0\0\0\0\0\u{7f}\u{7f}PK\u{6}\u{6}K\u{6}\u{6}\u{6}\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\u{1}\0\0\0\0\0\0\0\0\u{1}\0\0PK\u{1}\u{1e},\0\0\0\0\0\0\0\0\0\0\0\u{8}\0*\0\0\u{1}PK\u{6}\u{7}PK\u{6}\u{6}\0\0\0\0\0\0\0\0}K\u{2}\u{6}", options)?;
writer = ZipWriter::new_append(
writer.finish_into_readable()?.into_inner(),
)?;
writer
};
writer.merge_archive(sub_writer.finish_into_readable()?)?;
writer = ZipWriter::new_append(
writer.finish_into_readable()?.into_inner(),
)?;
writer
};
writer.merge_archive(sub_writer.finish_into_readable()?)?;
writer =
ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
writer
};
writer.merge_archive(sub_writer.finish_into_readable()?)?;
writer =
ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
writer
};
writer.merge_archive(sub_writer.finish_into_readable()?)?;
writer
};
writer.merge_archive(sub_writer.finish_into_readable()?)?;
writer
};
writer.merge_archive(sub_writer.finish_into_readable()?)?;
writer
};
writer.merge_archive(sub_writer.finish_into_readable()?)?;
let _ = writer.finish_into_readable()?;
Ok(())
}
#[test]
fn test_fuzz_crash_2024_06_18() -> ZipResult<()> {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.set_raw_comment(Box::<[u8]>::from([
80, 75, 5, 6, 255, 255, 255, 255, 255, 255, 80, 75, 5, 6, 255, 255, 255, 255, 255, 255,
13, 0, 13, 13, 13, 13, 13, 255, 255, 255, 255, 255, 255, 255, 255,
]));
let sub_writer = {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.set_flush_on_finish_file(false);
writer.set_raw_comment(Box::new([]));
writer
};
writer.merge_archive(sub_writer.finish_into_readable()?)?;
writer = ZipWriter::new_append(writer.finish()?)?;
let _ = writer.finish_into_readable()?;
Ok(())
}
#[test]
fn test_fuzz_crash_2024_06_18a() -> ZipResult<()> {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.set_flush_on_finish_file(false);
writer.set_raw_comment(Box::<[u8]>::from([]));
let sub_writer = {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.set_flush_on_finish_file(false);
let sub_writer = {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.set_flush_on_finish_file(false);
let sub_writer = {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.set_flush_on_finish_file(false);
let options = FullFileOptions {
compression_method: Stored,
compression_level: None,
last_modified_time: DateTime::from_date_and_time(2107, 4, 8, 14, 0, 19)?,
permissions: None,
large_file: false,
encrypt_with: None,
extended_options: ExtendedFileOptions {
extra_data: vec![
182, 180, 1, 0, 180, 182, 74, 0, 0, 200, 0, 0, 0, 2, 0, 0, 0,
]
.into(),
central_extra_data: vec![].into(),
},
alignment: 1542,
..Default::default()
};
writer.start_file_from_path("\0\0PK\u{6}\u{6}K\u{6}PK\u{3}\u{4}\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\u{1}\0\0\0\0\0\0\0\0\u{1}\u{1}\0PK\u{1}\u{2},\0\0\0\0\0\0\0\0\0\0\0P\u{7}\u{4}/.\0KP\0\0;\0\0\0\u{1e}\0\0\0\0\0\0\0\0\0\0\0\0\0", options)?;
let finished = writer.finish_into_readable()?;
assert_eq!(1, finished.file_names().count());
writer = ZipWriter::new_append(finished.into_inner())?;
let options = FullFileOptions {
compression_method: Stored,
compression_level: Some(5),
last_modified_time: DateTime::from_date_and_time(2107, 4, 1, 0, 0, 0)?,
permissions: None,
large_file: false,
encrypt_with: Some(ZipCrypto(
ZipCryptoKeys::of(0x0, 0x62e4b50, 0x100),
PhantomData,
)),
..Default::default()
};
writer.add_symlink_from_path(
"\0K\u{6}\0PK\u{6}\u{7}PK\u{6}\u{6}\0\0\0\0\0\0\0\0PK\u{2}\u{6}",
"\u{8}\0\0\0\0/\0",
options,
)?;
let finished = writer.finish_into_readable()?;
assert_eq!(2, finished.file_names().count());
writer = ZipWriter::new_append(finished.into_inner())?;
assert_eq!(2, writer.files.len());
writer
};
let finished = sub_writer.finish_into_readable()?;
assert_eq!(2, finished.file_names().count());
writer.merge_archive(finished)?;
writer = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
writer
};
writer.merge_archive(sub_writer.finish_into_readable()?)?;
writer
};
writer.merge_archive(sub_writer.finish_into_readable()?)?;
let _ = writer.finish_into_readable()?;
Ok(())
}
#[cfg(all(feature = "bzip2", feature = "aes-crypto"))]
#[test]
fn test_fuzz_crash_2024_06_18b() -> ZipResult<()> {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.set_flush_on_finish_file(true);
writer.set_raw_comment([0].into());
writer = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
assert_eq!(writer.get_raw_comment()[0], 0);
let options = FileOptions {
compression_method: CompressionMethod::Bzip2,
compression_level: None,
last_modified_time: DateTime::from_date_and_time(2009, 6, 3, 13, 37, 39)?,
permissions: Some(2644352413),
large_file: true,
encrypt_with: Some(crate::write::EncryptWith::Aes {
mode: crate::AesMode::Aes256,
password: "",
}),
extended_options: ExtendedFileOptions {
extra_data: vec![].into(),
central_extra_data: vec![].into(),
},
alignment: 255,
..Default::default()
};
writer.add_symlink_from_path("", "", options)?;
writer.deep_copy_file_from_path("", "PK\u{5}\u{6}\0\0\0\0\0\0\0\0\0\0\0\0\0\u{4}\0\0\0")?;
writer = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
assert_eq!(writer.get_raw_comment()[0], 0);
writer.deep_copy_file_from_path(
"PK\u{5}\u{6}\0\0\0\0\0\0\0\0\0\0\0\0\0\u{4}\0\0\0",
"\u{2}yy\u{5}qu\0",
)?;
let finished = writer.finish()?;
let archive = ZipArchive::new(finished.clone())?;
assert_eq!(archive.comment(), [0]);
writer = ZipWriter::new_append(finished)?;
assert_eq!(writer.get_raw_comment()[0], 0);
let _ = writer.finish_into_readable()?;
Ok(())
}
#[test]
fn test_fuzz_crash_2024_06_19() -> ZipResult<()> {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.set_flush_on_finish_file(false);
let options = FileOptions {
compression_method: Stored,
compression_level: None,
last_modified_time: DateTime::from_date_and_time(1980, 3, 1, 19, 55, 58)?,
permissions: None,
large_file: false,
encrypt_with: None,
extended_options: ExtendedFileOptions {
extra_data: vec![].into(),
central_extra_data: vec![].into(),
},
alignment: 256,
..Default::default()
};
writer.start_file_from_path(
"\0\0\0PK\u{5}\u{6}\0\0\0\0\u{1}\0\u{12}\u{6}\0\0\0\0\0\u{1}\0\0\0\0\0\0\0\0\0",
options,
)?;
writer.set_flush_on_finish_file(false);
writer.shallow_copy_file_from_path(
"\0\0\0PK\u{5}\u{6}\0\0\0\0\u{1}\0\u{12}\u{6}\0\0\0\0\0\u{1}\0\0\0\0\0\0\0\0\0",
"",
)?;
writer.set_flush_on_finish_file(false);
writer.deep_copy_file_from_path("", "copy")?;
writer.abort_file()?;
writer.set_flush_on_finish_file(false);
writer.set_raw_comment([255, 0].into());
writer.abort_file()?;
assert_eq!(writer.get_raw_comment(), [255, 0]);
writer = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
assert_eq!(writer.get_raw_comment(), [255, 0]);
writer.set_flush_on_finish_file(false);
let options = FileOptions {
compression_method: Stored,
compression_level: None,
last_modified_time: DateTime::default(),
permissions: None,
large_file: false,
encrypt_with: None,
extended_options: ExtendedFileOptions {
extra_data: vec![].into(),
central_extra_data: vec![].into(),
},
..Default::default()
};
writer.start_file_from_path("", options)?;
assert_eq!(writer.get_raw_comment(), [255, 0]);
let archive = writer.finish_into_readable()?;
assert_eq!(archive.comment(), [255, 0]);
Ok(())
}
#[test]
fn fuzz_crash_2024_06_21() -> ZipResult<()> {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.set_flush_on_finish_file(false);
let options = FullFileOptions {
compression_method: Stored,
compression_level: None,
last_modified_time: DateTime::from_date_and_time(1980, 2, 1, 0, 0, 0)?,
permissions: None,
large_file: false,
encrypt_with: None,
..Default::default()
};
const LONG_PATH: &str = "\0@PK\u{6}\u{6}\u{7}\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0@/\0\0\00ΝPK\u{5}\u{6}O\0\u{10}\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0@PK\u{6}\u{7}\u{6}\0/@\0\0\0\0\0\0\0\0 \0\0";
writer.start_file_from_path(LONG_PATH, options)?;
writer = ZipWriter::new_append(writer.finish()?)?;
writer.deep_copy_file_from_path(LONG_PATH, "oo\0\0\0")?;
writer.abort_file()?;
writer.set_raw_comment([33].into());
let archive = writer.finish_into_readable()?;
writer = ZipWriter::new_append(archive.into_inner())?;
assert!(writer.get_raw_comment().starts_with(&[33]));
let archive = writer.finish_into_readable()?;
assert!(archive.comment().starts_with(&[33]));
Ok(())
}
#[test]
#[cfg(feature = "bzip2")]
fn fuzz_crash_2024_07_17() -> ZipResult<()> {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.set_flush_on_finish_file(false);
let options = FileOptions {
compression_method: CompressionMethod::Bzip2,
compression_level: None,
last_modified_time: DateTime::from_date_and_time(2095, 2, 16, 21, 0, 1)?,
permissions: Some(84238341),
large_file: true,
encrypt_with: None,
extended_options: ExtendedFileOptions {
extra_data: vec![117, 99, 6, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 0, 2, 0, 0, 0].into(),
central_extra_data: vec![].into(),
},
alignment: 65535,
..Default::default()
};
writer.start_file_from_path("", options)?;
writer.deep_copy_file_from_path("", "copy")?;
let _ = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
Ok(())
}
#[test]
fn fuzz_crash_2024_07_19() -> ZipResult<()> {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.set_flush_on_finish_file(false);
let options = FileOptions {
compression_method: Stored,
compression_level: None,
last_modified_time: DateTime::from_date_and_time(1980, 6, 1, 0, 34, 47)?,
permissions: None,
large_file: true,
encrypt_with: None,
extended_options: ExtendedFileOptions {
extra_data: vec![].into(),
central_extra_data: vec![].into(),
},
alignment: 45232,
..Default::default()
};
writer.add_directory_from_path("", options)?;
writer.deep_copy_file_from_path("/", "")?;
writer = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
writer.deep_copy_file_from_path("", "copy")?;
let _ = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
Ok(())
}
#[test]
#[cfg(feature = "aes-crypto")]
fn fuzz_crash_2024_07_19a() -> ZipResult<()> {
use crate::write::EncryptWith::Aes;
use crate::AesMode::Aes128;
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.set_flush_on_finish_file(false);
let options = FileOptions {
compression_method: Stored,
compression_level: None,
last_modified_time: DateTime::from_date_and_time(2107, 6, 5, 13, 0, 21)?,
permissions: None,
large_file: true,
encrypt_with: Some(Aes {
mode: Aes128,
password: "",
}),
extended_options: ExtendedFileOptions {
extra_data: vec![3, 0, 4, 0, 209, 53, 53, 8, 2, 61, 0, 0].into(),
central_extra_data: vec![].into(),
},
alignment: 65535,
..Default::default()
};
writer.start_file_from_path("", options)?;
let _ = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
Ok(())
}
#[test]
fn fuzz_crash_2024_07_20() -> ZipResult<()> {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.set_flush_on_finish_file(true);
let options = FileOptions {
compression_method: Stored,
compression_level: None,
last_modified_time: DateTime::from_date_and_time(2041, 8, 2, 19, 38, 0)?,
permissions: None,
large_file: false,
encrypt_with: None,
extended_options: ExtendedFileOptions {
extra_data: vec![].into(),
central_extra_data: vec![].into(),
},
alignment: 0,
..Default::default()
};
writer.add_directory_from_path("\0\0\0\0\0\0\07黻", options)?;
let sub_writer = {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.set_flush_on_finish_file(false);
let options = FileOptions {
compression_method: Stored,
compression_level: None,
last_modified_time: DateTime::default(),
permissions: None,
large_file: false,
encrypt_with: None,
extended_options: ExtendedFileOptions {
extra_data: vec![].into(),
central_extra_data: vec![].into(),
},
alignment: 4,
..Default::default()
};
writer.add_directory_from_path("\0\0\0黻", options)?;
writer = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
writer.abort_file()?;
let options = FileOptions {
compression_method: Stored,
compression_level: None,
last_modified_time: DateTime::from_date_and_time(1980, 1, 1, 0, 7, 0)?,
permissions: Some(2663103419),
large_file: false,
encrypt_with: None,
extended_options: ExtendedFileOptions {
extra_data: vec![].into(),
central_extra_data: vec![].into(),
},
alignment: 32256,
..Default::default()
};
writer.add_directory_from_path("\0", options)?;
writer = ZipWriter::new_append(writer.finish()?)?;
writer
};
writer.merge_archive(sub_writer.finish_into_readable()?)?;
let _ = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
Ok(())
}
#[test]
fn fuzz_crash_2024_07_21() -> ZipResult<()> {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
let sub_writer = {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.add_directory_from_path(
"",
FileOptions {
compression_method: Stored,
compression_level: None,
last_modified_time: DateTime::from_date_and_time(2105, 8, 1, 15, 0, 0)?,
permissions: None,
large_file: false,
encrypt_with: None,
extended_options: ExtendedFileOptions {
extra_data: vec![].into(),
central_extra_data: vec![].into(),
},
alignment: 0,
..Default::default()
},
)?;
writer.abort_file()?;
let mut writer = ZipWriter::new_append(writer.finish()?)?;
writer.add_directory_from_path(
"",
FileOptions {
compression_method: Stored,
compression_level: None,
last_modified_time: DateTime::default(),
permissions: None,
large_file: false,
encrypt_with: None,
extended_options: ExtendedFileOptions {
extra_data: vec![].into(),
central_extra_data: vec![].into(),
},
alignment: 16,
..Default::default()
},
)?;
ZipWriter::new_append(writer.finish()?)?
};
writer.merge_archive(sub_writer.finish_into_readable()?)?;
let writer = ZipWriter::new_append(writer.finish()?)?;
let _ = writer.finish_into_readable()?;
Ok(())
}
}