use crate::compression::CompressionMethod;
use crate::datetime::DateTime;
use crate::extra_fields::AexEncryption;
use crate::extra_fields::UsedExtraField;
use crate::extra_fields::Zip64ExtendedInformation;
use crate::read::{Config, ZipArchive, ZipFile, parse_single_extra_field};
use crate::result::{ZipError, ZipResult, invalid};
use crate::spec::{self, FixedSizeBlock, Magic, Pod, Zip32CDEBlock, ZipLocalEntryBlock};
use crate::types::{AesVendorVersion, MIN_VERSION, System, ZipFileData, ZipRawValues, ffi};
use core::default::Default;
use core::fmt::{Debug, Formatter};
use core::marker::PhantomData;
use core::mem::{self, offset_of, size_of};
use core::str::{Utf8Error, from_utf8};
use crc32fast::Hasher;
use indexmap::IndexMap;
use std::borrow::ToOwned;
use std::io::{self, Read, Seek, Write};
use std::io::{BufReader, SeekFrom};
use std::io::{Cursor, ErrorKind};
use std::path::Path;
use std::sync::Arc;
pub use crate::types::{FileOptions, SimpleFileOptions};
#[allow(clippy::large_enum_variant)]
enum MaybeEncrypted<W> {
Unencrypted(W),
#[cfg(feature = "aes-crypto")]
Aes(crate::aes::AesWriter<W>),
ZipCrypto(crate::zipcrypto::ZipCryptoWriter<W>),
}
impl<W: Write> MaybeEncrypted<W> {
fn get_ref(&self) -> &W {
match self {
MaybeEncrypted::Unencrypted(w) => w,
#[cfg(feature = "aes-crypto")]
MaybeEncrypted::Aes(w) => w.get_ref(),
MaybeEncrypted::ZipCrypto(w) => w.get_ref(),
}
}
unsafe fn get_mut(&mut self) -> &mut W {
match self {
MaybeEncrypted::Unencrypted(w) => w,
#[cfg(feature = "aes-crypto")]
MaybeEncrypted::Aes(w) => unsafe { w.get_mut() },
MaybeEncrypted::ZipCrypto(w) => w.get_mut(),
}
}
}
impl<W> Debug for MaybeEncrypted<W> {
fn fmt(&self, f: &mut Formatter<'_>) -> core::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(),
}
}
}
#[allow(clippy::large_enum_variant)]
enum GenericZipWriter<W: Write + Seek> {
Closed,
Storer(MaybeEncrypted<W>),
#[cfg(feature = "deflate-flate2")]
Deflater(flate2::write::DeflateEncoder<MaybeEncrypted<W>>),
#[cfg(feature = "deflate-zopfli")]
ZopfliDeflater(zopfli::DeflateEncoder<MaybeEncrypted<W>>),
#[cfg(feature = "deflate-zopfli")]
BufferedZopfliDeflater(std::io::BufWriter<zopfli::DeflateEncoder<MaybeEncrypted<W>>>),
#[cfg(feature = "_bzip2_any")]
Bzip2(bzip2::write::BzEncoder<MaybeEncrypted<W>>),
#[cfg(feature = "zstd")]
Zstd(zstd::stream::write::Encoder<'static, MaybeEncrypted<W>>),
#[cfg(feature = "xz")]
Xz(Box<lzma_rust2::XzWriter<MaybeEncrypted<W>>>),
#[cfg(feature = "ppmd")]
Ppmd(Box<ppmd_rust::Ppmd8Encoder<MaybeEncrypted<W>>>),
}
impl<W: Write + Seek> Debug for GenericZipWriter<W> {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
match self {
Self::Closed => f.write_str("Closed"),
Self::Storer(w) => f.write_fmt(format_args!("Storer({w:?})")),
#[cfg(feature = "deflate-flate2")]
Self::Deflater(w) => f.write_fmt(format_args!("Deflater({:?})", w.get_ref())),
#[cfg(feature = "deflate-zopfli")]
Self::ZopfliDeflater(_) => f.write_str("ZopfliDeflater"),
#[cfg(feature = "deflate-zopfli")]
Self::BufferedZopfliDeflater(_) => f.write_str("BufferedZopfliDeflater"),
#[cfg(feature = "_bzip2_any")]
Self::Bzip2(w) => f.write_fmt(format_args!("Bzip2({:?})", w.get_ref())),
#[cfg(feature = "zstd")]
Self::Zstd(w) => f.write_fmt(format_args!("Zstd({:?})", w.get_ref())),
#[cfg(feature = "xz")]
Self::Xz(w) => f.write_fmt(format_args!("Xz({:?})", w.inner())),
#[cfg(feature = "ppmd")]
Self::Ppmd(_) => f.write_fmt(format_args!("Ppmd8Encoder")),
}
}
}
pub(crate) mod zip_writer {
use crate::{
types::ZipFileData,
write::{GenericZipWriter, ZipWriterStats},
};
use core::fmt::{Debug, Formatter};
use indexmap::IndexMap;
use std::io::{Seek, Write};
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_extensible_data_sector: Option<Box<[u8]>>,
pub(super) flush_on_finish_file: bool,
pub(super) seek_possible: bool,
pub(crate) auto_large_file: bool,
}
impl<W: Write + Seek> Debug for ZipWriter<W> {
fn fmt(&self, f: &mut Formatter<'_>) -> core::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))
}
}
impl<W: Write + Seek> ZipWriter<W> {
pub fn get_ref(&self) -> Option<&W> {
match &self.inner {
GenericZipWriter::Closed => None,
GenericZipWriter::Storer(w) => Some(w.get_ref()),
#[cfg(feature = "deflate-flate2")]
GenericZipWriter::Deflater(w) => Some(w.get_ref().get_ref()),
#[cfg(feature = "deflate-zopfli")]
GenericZipWriter::ZopfliDeflater(w) => Some(w.get_ref().get_ref()),
#[cfg(feature = "deflate-zopfli")]
GenericZipWriter::BufferedZopfliDeflater(w) => {
Some(w.get_ref().get_ref().get_ref())
}
#[cfg(feature = "_bzip2_any")]
GenericZipWriter::Bzip2(w) => Some(w.get_ref().get_ref()),
#[cfg(feature = "zstd")]
GenericZipWriter::Zstd(w) => Some(w.get_ref().get_ref()),
#[cfg(feature = "xz")]
GenericZipWriter::Xz(w) => Some(w.inner().get_ref()),
#[cfg(feature = "ppmd")]
GenericZipWriter::Ppmd(w) => Some(w.get_ref().get_ref()),
}
}
pub unsafe fn get_mut(&mut self) -> Option<&mut W> {
unsafe {
match &mut self.inner {
GenericZipWriter::Closed => None,
GenericZipWriter::Storer(w) => Some(w.get_mut()),
#[cfg(feature = "deflate-flate2")]
GenericZipWriter::Deflater(w) => Some(w.get_mut().get_mut()),
#[cfg(feature = "deflate-zopfli")]
GenericZipWriter::ZopfliDeflater(w) => Some(w.get_mut().get_mut()),
#[cfg(feature = "deflate-zopfli")]
GenericZipWriter::BufferedZopfliDeflater(w) => {
Some(w.get_mut().get_mut().get_mut())
}
#[cfg(feature = "_bzip2_any")]
GenericZipWriter::Bzip2(w) => Some(w.get_mut().get_mut()),
#[cfg(feature = "zstd")]
GenericZipWriter::Zstd(w) => Some(w.get_mut().get_mut()),
#[cfg(feature = "xz")]
GenericZipWriter::Xz(w) => Some(w.inner_mut().get_mut()),
#[cfg(feature = "ppmd")]
GenericZipWriter::Ppmd(w) => Some(w.get_mut().get_mut()),
}
}
}
}
}
#[doc(inline)]
pub use self::sealed::FileOptionExtension;
use crate::CompressionMethod::Stored;
use crate::result::ZipError::UnsupportedArchive;
use crate::unstable::LittleEndianWriteExt;
use crate::unstable::path_to_string;
use crate::zipcrypto::{CHUNK_SIZE, EncryptWith, ZipCryptoKeys};
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>>>;
fn file_comment(&self) -> Option<&str>;
fn take_file_comment(&mut self) -> Option<Box<str>>;
}
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
}
fn file_comment(&self) -> Option<&str> {
None
}
fn take_file_comment(&mut self) -> Option<Box<str>> {
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)
}
fn file_comment(&self) -> Option<&str> {
self.file_comment.as_ref().map(Box::as_ref)
}
fn take_file_comment(&mut self) -> Option<Box<str>> {
self.file_comment.take()
}
}
}
pub type FullFileOptions<'k> = FileOptions<'k, ExtendedFileOptions>;
#[cfg_attr(feature = "_arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Clone, Default, Eq, PartialEq)]
pub struct ExtendedFileOptions {
extra_data: Arc<Vec<u8>>,
central_extra_data: Arc<Vec<u8>>,
file_comment: Option<Box<str>>,
}
impl ExtendedFileOptions {
pub fn add_extra_data<D: AsRef<[u8]>>(
&mut self,
header_id: u16,
data: D,
central_only: bool,
) -> ZipResult<()> {
let data = data.as_ref();
let len = data.len() + 4;
let local_extra_data_len = self.extra_data.len();
let central_extra_data_len = self.central_extra_data.len();
let field = if central_only {
&mut self.central_extra_data
} else {
&mut self.extra_data
};
if local_extra_data_len + central_extra_data_len + len > u16::MAX as usize {
Err(invalid!("Extra data field would be longer than allowed"))
} else {
let vec = Arc::make_mut(field);
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: &[u8],
) -> Result<(), ZipError> {
vec.reserve_exact(data.len() + size_of::<u16>() + size_of::<u16>());
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 > u64::from(u16::MAX) {
return Err(ZipError::Io(io::Error::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::other(
"Extra-data field doesn't have room for ID and length",
)));
}
#[cfg(not(feature = "unreserved"))]
{
use crate::{
extra_fields::{EXTRA_FIELD_MAPPING, UsedExtraField},
unstable::LittleEndianReadExt,
};
let header_id = data.read_u16_le()?;
if let Err(()) = UsedExtraField::try_from(header_id)
&& EXTRA_FIELD_MAPPING.contains(&header_id)
{
return Err(ZipError::Io(io::Error::other(format!(
"Extra data header ID {header_id:#06} (0x{header_id:x}) \
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<(), core::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(feature = "_arbitrary")]
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)
}
}
const DEFAULT_FILE_PERMISSIONS: u32 = 0o644; const DEFAULT_DIR_PERMISSIONS: u32 = 0o755;
impl<T: FileOptionExtension> FileOptions<'_, T> {
pub(crate) fn normalize(&mut self) {
if !self.last_modified_time.is_valid() {
self.last_modified_time = FileOptions::<T>::default().last_modified_time;
}
*self.permissions.get_or_insert(DEFAULT_FILE_PERMISSIONS) |= ffi::S_IFREG;
}
pub const fn has_encryption(&self) -> bool {
#[cfg(feature = "aes-crypto")]
{
self.encrypt_with.is_some() || self.aes_mode.is_some()
}
#[cfg(not(feature = "aes-crypto"))]
{
self.encrypt_with.is_some()
}
}
#[must_use]
pub const fn compression_method(mut self, method: CompressionMethod) -> Self {
self.compression_method = method;
self
}
#[must_use]
pub const fn system(mut self, system: System) -> Self {
self.system = Some(system);
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_and_salt(
self,
password: &[u8],
salt: crate::aes::AesSalt,
) -> FileOptions<'_, T> {
FileOptions {
encrypt_with: Some(EncryptWith::Aes {
mode: salt.mode(),
password,
salt: Some(salt),
}),
..self
}
}
#[cfg(feature = "aes-crypto")]
pub fn with_aes_encryption(self, mode: crate::AesMode, password: &str) -> FileOptions<'_, T> {
self.with_aes_encryption_bytes(mode, password.as_bytes())
}
#[cfg(feature = "aes-crypto")]
pub fn with_aes_encryption_bytes(
self,
mode: crate::AesMode,
password: &[u8],
) -> FileOptions<'_, T> {
FileOptions {
encrypt_with: Some(EncryptWith::Aes {
mode,
password,
salt: None,
}),
..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> {
#[must_use]
pub fn with_file_comment<S: Into<Box<str>>>(mut self, comment: S) -> Self {
self.extended_options.file_comment = Some(comment.into());
self
}
pub fn add_extra_data<D: AsRef<[u8]>>(
&mut self,
header_id: u16,
data: D,
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.is_empty() {
self.extended_options.extra_data = Arc::new(vec![]);
}
if !self.extended_options.central_extra_data.is_empty() {
self.extended_options.central_extra_data = Arc::new(vec![]);
}
self
}
}
impl FileOptions<'static, ()> {
pub const DEFAULT: Self = Self {
compression_method: CompressionMethod::DEFAULT,
compression_level: None,
last_modified_time: DateTime::DEFAULT,
large_file: false,
permissions: None,
encrypt_with: None,
extended_options: (),
alignment: 1,
#[cfg(feature = "deflate-zopfli")]
zopfli_buffer_size: Some(1 << 15),
#[cfg(feature = "aes-crypto")]
aes_mode: None,
system: None,
};
}
impl<'k> FileOptions<'k, ()> {
#[must_use]
pub fn into_full_options(self) -> FullFileOptions<'k> {
FileOptions {
compression_method: self.compression_method,
compression_level: self.compression_level,
last_modified_time: self.last_modified_time,
permissions: self.permissions,
large_file: self.large_file,
encrypt_with: self.encrypt_with,
extended_options: ExtendedFileOptions::default(),
alignment: self.alignment,
#[cfg(feature = "deflate-zopfli")]
zopfli_buffer_size: self.zopfli_buffer_size,
#[cfg(feature = "aes-crypto")]
aes_mode: self.aes_mode,
system: self.system,
}
}
}
impl<T: FileOptionExtension> Default for FileOptions<'_, T> {
fn default() -> Self {
Self {
compression_method: CompressionMethod::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),
#[cfg(feature = "aes-crypto")]
aes_mode: None,
system: None,
}
}
}
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::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[..count]);
if self.stats.bytes_written > spec::ZIP64_BYTES_THR {
let is_large_file = self
.files
.last()
.ok_or_else(|| std::io::Error::other("Cannot get last file"))?
.1
.large_file;
if !is_large_file {
return Err(if let Err(e) = self.abort_file() {
let abort_io_err: io::Error = e.into();
io::Error::new(
abort_io_err.kind(),
format!(
"Large file option has not been set and abort_file() failed: {abort_io_err}"
),
)
} else {
io::Error::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(Config::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: GenericZipWriter::Storer(MaybeEncrypted::Unencrypted(readwriter)),
files: shared.files,
stats: ZipWriterStats::default(),
writing_to_file: false,
comment: shared.comment,
zip64_extensible_data_sector: shared.zip64_extensible_data_sector,
writing_raw: true, flush_on_finish_file: false,
seek_possible: true,
auto_large_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(invalid!("That file already exists"));
}
let write_position = self.inner.try_inner_mut()?.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(self.inner.try_inner_mut()?)?;
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(self.inner.try_inner_mut()?);
reader.seek(SeekFrom::Start(src_data_start))?;
let mut copy = vec![0; compressed_size as usize];
reader.take(compressed_size).read_exact(&mut copy)?;
self.inner
.try_inner_mut()?
.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.header_start = write_position;
let extra_data_start = write_position
+ (size_of::<Magic>() + size_of::<ZipLocalEntryBlock>()) as u64
+ new_data.file_name_raw.len() as u64;
new_data.extra_data_start = Some(extra_data_start);
if let Some(extra) = &src_data.extra_field {
let stripped = strip_alignment_extra_field(extra, false);
if stripped.is_empty() {
new_data.extra_field = None;
} else {
new_data.extra_field = Some(Arc::from(stripped.into_boxed_slice()));
}
}
let mut data_start = extra_data_start;
if let Some(extra) = &new_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 new_data = &self.files[index];
let result: io::Result<()> = {
let plain_writer = self.inner.try_inner_mut()?;
block.write(plain_writer)?;
plain_writer.write_all(&new_data.file_name_raw)?;
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(©)?;
if self.flush_on_finish_file {
plain_writer.flush()?;
}
Ok(())
};
self.ok_or_abort_file(result)?;
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 = self.close_writer()?;
let comment = mem::take(&mut self.comment);
let zip64_extensible_data_sector = mem::take(&mut self.zip64_extensible_data_sector);
let files = mem::take(&mut self.files);
Ok(ZipArchive::from_finalized_writer(
files,
comment,
zip64_extensible_data_sector,
inner,
central_start,
))
}
}
impl<W: Write + Seek> ZipWriter<W> {
pub fn new(inner: W) -> ZipWriter<W> {
ZipWriter {
inner: GenericZipWriter::Storer(MaybeEncrypted::Unencrypted(inner)),
files: IndexMap::new(),
stats: ZipWriterStats::default(),
writing_to_file: false,
writing_raw: false,
comment: Box::new([]),
zip64_extensible_data_sector: None,
flush_on_finish_file: false,
seek_possible: true,
auto_large_file: false,
}
}
#[must_use]
pub fn set_auto_large_file(mut self) -> Self {
self.auto_large_file = true;
self
}
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) -> ZipResult<()>
where
S: Into<Box<str>>,
{
self.set_raw_comment(comment.into().into_boxed_bytes())
}
pub fn set_raw_comment(&mut self, comment: Box<[u8]>) -> ZipResult<()> {
let max_comment_len = u16::MAX as usize; if comment.len() <= max_comment_len {
self.comment = comment;
return Ok(());
}
let (allowed, rest) = comment.split_at(max_comment_len);
self.comment = allowed.into();
Err(ZipError::Io(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("Unexpected data comment - {} bytes too long", rest.len()),
)))
}
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
}
#[deprecated(
note = "Zip64 comment is not part of the zip specification - see https://github.com/zip-rs/zip2/pull/747"
)]
pub fn set_zip64_comment<S>(&mut self, comment: Option<S>) -> ZipResult<()>
where
S: Into<Box<str>>,
{
if let Some(com) = comment {
self.set_comment(com)?;
}
Ok(())
}
#[deprecated(
note = "Zip64 comment is not part of the zip specification - see https://github.com/zip-rs/zip2/pull/747"
)]
pub fn set_raw_zip64_comment(&mut self, comment: Option<Box<[u8]>>) -> ZipResult<()> {
if let Some(com) = comment {
self.set_raw_comment(com)?;
}
Ok(())
}
pub fn set_raw_zip64_extensible_data_sector(&mut self, extensible_data: Box<[u8]>) {
self.zip64_extensible_data_sector = Some(extensible_data);
}
#[deprecated(
note = "Zip64 comment is not part of the zip specification - see https://github.com/zip-rs/zip2/pull/747"
)]
pub fn get_zip64_comment(&mut self) -> Option<Result<&str, Utf8Error>> {
None
}
#[deprecated(
note = "Zip64 comment is not part of the zip specification - see https://github.com/zip-rs/zip2/pull/747"
)]
pub fn get_raw_zip64_comment(&self) -> Option<&[u8]> {
None
}
pub unsafe fn set_file_metadata(&mut self, length: u64, crc32: u32) -> ZipResult<()> {
if !self.writing_to_file {
return Err(ZipError::Io(io::Error::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,
mut options: FileOptions<'_, T>,
raw_values: Option<ZipRawValues>,
) -> ZipResult<()> {
self.finish_file()?;
let header_start = self.inner.try_inner_mut()?.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) = Zip64ExtendedInformation::new_local(options.large_file) {
let mut new_extra_data = zip64_block.serialize().into_vec();
new_extra_data.append(&mut extra_data);
extra_data = new_extra_data;
}
let (compression_method, aes_mode) = match options.encrypt_with {
#[cfg(feature = "aes-crypto")]
None if options.aes_mode.is_some() => (CompressionMethod::Aes, options.aes_mode),
#[cfg(feature = "aes-crypto")]
Some(EncryptWith::Aes { mode, salt, .. }) => {
let aes_mode = crate::aes::AesModeOptions::new(
mode,
AesVendorVersion::Ae2,
options.compression_method,
salt,
);
(CompressionMethod::Aes, Some(aes_mode))
}
_ => (options.compression_method, None),
};
#[allow(unused_mut)]
let mut aes_extra_data_start = 0;
#[cfg(feature = "aes-crypto")]
if let Some(crate::aes::AesModeOptions {
mode,
vendor_version,
actual_compression_method,
..
}) = aes_mode
{
let aex_extra_field =
AexEncryption::new(vendor_version, mode, actual_compression_method);
let buf = &aex_extra_field.as_bytes()[offset_of!(AexEncryption, version)..];
aes_extra_data_start = extra_data.len() as u64;
ExtendedFileOptions::add_extra_data_unchecked(
&mut extra_data,
UsedExtraField::AeXEncryption.as_u16(),
buf,
)?;
}
let header_end = header_start
+ (size_of::<Magic>() + 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 = u64::from(options.alignment);
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 new_len = extra_data.len() + pad_length;
if new_len > u16::MAX as usize {
} else {
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,
UsedExtraField::DataStreamAlignment.as_u16(),
&pad_body,
)?;
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(invalid!(
"Extra data and central extra data must be less than 64KiB when combined"
));
}
ExtendedFileOptions::validate_extra_data(data, true)?;
}
#[cfg(feature = "aes-crypto")]
let aes_mode = aes_mode.map(super::aes::AesModeOptions::to_tuple);
let mut file = ZipFileData::initialize_local_block(
name,
&options,
&raw_values,
header_start,
None,
aes_extra_data_start,
compression_method,
aes_mode,
&extra_data,
);
if let Some(comment) = options.extended_options.take_file_comment() {
if comment.len() > u16::MAX as usize {
return Err(invalid!("File comment must be less than 64KiB"));
}
if !comment.is_ascii() {
file.is_utf8 = true;
}
file.file_comment = comment;
}
file.using_data_descriptor =
!self.seek_possible || matches!(options.encrypt_with, Some(EncryptWith::ZipCrypto(..)));
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.try_inner_mut()?;
block.write(writer)?;
writer.write_all(&file.file_name_raw)?;
if extra_data_len > 0 {
writer.write_all(&extra_data)?;
file.extra_field = Some(Arc::from(extra_data.into_boxed_slice()));
}
Ok(())
};
self.ok_or_abort_file(result)?;
self.stats.start = self.inner.try_inner_mut()?.stream_position()?;
match options.encrypt_with {
#[cfg(feature = "aes-crypto")]
Some(EncryptWith::Aes {
mode,
password,
salt,
}) => {
let writer = self.close_writer()?;
let aeswriter =
crate::aes::AesWriter::new_with_options(writer, mode, password, salt)?;
self.inner = GenericZipWriter::Storer(MaybeEncrypted::Aes(aeswriter));
}
Some(EncryptWith::ZipCrypto(keys, ..)) => {
let writer = self.close_writer()?;
let file = &mut self.files[index];
let mut zipwriter = crate::zipcrypto::ZipCryptoWriter {
writer,
keys,
buffer: [0u8; CHUNK_SIZE],
};
self.stats.start = zipwriter.writer.stream_position()?;
let mut crypto_header = [0u8; 12];
crypto_header[10..=11].copy_from_slice(
&file
.last_modified_time
.unwrap_or_else(DateTime::default_for_write)
.timepart()
.to_le_bytes(),
);
let result = zipwriter.write_all(&crypto_header);
self.ok_or_abort_file(result)?;
self.inner = GenericZipWriter::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(invalid!("Duplicate filename: {}", file.file_name));
}
let (index, _) = self.files.insert_full(file.file_name.clone(), file);
Ok(index)
}
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.try_inner_mut()?;
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;
if !file.using_data_descriptor {
update_aes_extra_data(writer, file, self.stats.bytes_written)?;
}
let crc = !matches!(file.aes_mode, Some((_, AesVendorVersion::Ae2, _)));
file.crc32 = if crc {
self.stats.hasher.clone().finalize()
} else {
0
};
if file.using_data_descriptor {
file.write_data_descriptor(writer, self.auto_large_file)?;
} else {
file.update_local_file_header(writer)?;
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> {
self.inner = GenericZipWriter::Storer(
match mem::replace(&mut self.inner, GenericZipWriter::Closed) {
#[cfg(feature = "aes-crypto")]
GenericZipWriter::Storer(MaybeEncrypted::Aes(writer)) => {
MaybeEncrypted::Unencrypted(writer.finish()?)
}
GenericZipWriter::Storer(MaybeEncrypted::ZipCrypto(writer)) => {
MaybeEncrypted::Unencrypted(writer.finish()?)
}
GenericZipWriter::Storer(MaybeEncrypted::Unencrypted(w)) => {
MaybeEncrypted::Unencrypted(w)
}
_ => unreachable!(
"switch_to_non_encrypting_writer called with incompatible GenericZipWriter variant"
),
},
);
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
.try_inner_mut()?
.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<()> {
options.normalize();
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.try_inner_mut()?;
let new_files = source.merge_contents(writer)?;
self.files.extend(new_files);
Ok(())
}
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<R: Read, S: ToString>(
&mut self,
file: ZipFile<'_, R>,
name: S,
) -> ZipResult<()> {
let mut options = file.options().into_full_options();
if !file.comment().is_empty() {
options = options.with_file_comment(file.comment());
}
self.raw_copy_file_rename_internal(file, &name, options)
}
fn raw_copy_file_rename_internal<R: Read, S: ToString, T: FileOptionExtension>(
&mut self,
mut file: ZipFile<'_, R>,
name: &S,
options: FileOptions<'_, T>,
) -> 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<R: Read, P: AsRef<Path>>(
&mut self,
file: ZipFile<'_, R>,
path: P,
) -> ZipResult<()> {
self.raw_copy_file_rename(file, path_to_string(path)?)
}
pub fn raw_copy_file<R: Read>(&mut self, file: ZipFile<'_, R>) -> ZipResult<()> {
let name = file.name().to_owned();
self.raw_copy_file_rename(file, name)
}
pub fn raw_copy_file_touch<R: Read>(
&mut self,
file: ZipFile<'_, R>,
last_modified_time: DateTime,
unix_mode: Option<u32>,
) -> ZipResult<()> {
let name = file.name().to_owned();
let mut options = file.options().into_full_options();
options = options.last_modified_time(last_modified_time);
if let Some(perms) = unix_mode {
options = options.unix_permissions(perms);
}
if !file.comment().is_empty() {
options = options.with_file_comment(file.comment());
}
options.normalize();
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(DEFAULT_DIR_PERMISSIONS);
}
*options
.permissions
.as_mut()
.ok_or_else(|| std::io::Error::other("Cannot get permissions as mutable"))? |= 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('/' | '\\') => 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()?;
self.close_writer()
}
fn close_writer(&mut self) -> ZipResult<W> {
let inner = mem::replace(&mut self.inner, GenericZipWriter::Closed);
match inner {
GenericZipWriter::Storer(MaybeEncrypted::Unencrypted(w)) => Ok(w),
_ => Err(invalid!(
"Should have switched to stored and unencrypted beforehand",
)),
}
}
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()
.ok_or_else(|| std::io::Error::other("Cannot get permissions as mutable"))? |=
ffi::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<()> {
let path = path_to_string(path)?;
let target = path_to_string(target)?;
self.add_symlink(path, target, options)
}
fn finalize(&mut self) -> ZipResult<u64> {
self.finish_file()?;
let mut central_start = self.write_central_and_footer()?;
let writer = self.inner.try_inner_mut()?;
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::<Magic>() + 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()?;
debug_assert!(self.inner.try_inner_mut()?.stream_position()? == archive_end);
}
Ok(central_start)
}
fn write_central_and_footer(&mut self) -> Result<u64, ZipError> {
let writer = self.inner.try_inner_mut()?;
let mut version_needed = u16::from(MIN_VERSION);
let central_start = writer.stream_position()?;
for file in self.files.values() {
file.write_central_directory_header(writer)?;
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_extensible_data_sector.is_some();
if is64 {
let zip64_extensible_data_sector = self.zip64_extensible_data_sector.clone();
let extensible_len = zip64_extensible_data_sector
.as_ref()
.map(|e| e.len() as u64)
.unwrap_or(0);
let zip64_footer = spec::Zip64CentralDirectoryEnd {
record_size: extensible_len + 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,
zip64_extensible_data_sector,
};
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 central_directory_size = if is64 {
spec::ZIP64_BYTES_THR as u32
} else {
central_size.min(spec::ZIP64_BYTES_THR) as u32
};
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_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(invalid!("Trying to copy a file to itself"));
}
let src_index = self.index_by_name(src_name)?;
let mut dest_data = self.files[src_index].clone();
dest_data.file_name = dest_name.into();
dest_data.file_name_raw = dest_name.as_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<()> {
let src = path_to_string(src_path)?;
let dest = path_to_string(dest_path)?;
self.shallow_copy_file(&src, &dest)
}
}
impl<W: Write> ZipWriter<StreamWriter<W>> {
pub fn new_stream(inner: W) -> ZipWriter<StreamWriter<W>> {
ZipWriter {
inner: GenericZipWriter::Storer(MaybeEncrypted::Unencrypted(StreamWriter::new(inner))),
files: IndexMap::new(),
stats: ZipWriterStats::default(),
writing_to_file: false,
writing_raw: false,
comment: Box::new([]),
zip64_extensible_data_sector: None,
flush_on_finish_file: false,
seek_possible: false,
auto_large_file: false,
}
}
}
impl<W: Write + Seek> Drop for ZipWriter<W> {
fn drop(&mut self) {
if !self.inner.is_closed()
&& let Err(e) = self.finalize()
{
let _ = write!(
io::stderr(),
"ZipWriter::drop: failed to finalize archive: {e:?}"
);
}
}
}
type SwitchWriterFunction<W> = Box<dyn FnOnce(MaybeEncrypted<W>) -> ZipResult<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 GenericZipWriter::Closed = self {
return Err(
io::Error::new(io::ErrorKind::BrokenPipe, "ZipWriter was already closed").into(),
);
}
match compression {
Stored => {
if compression_level.is_some() {
Err(UnsupportedArchive("Unsupported compression level"))
} else {
Ok(Box::new(|bare| Ok(GenericZipWriter::Storer(bare))))
}
}
#[allow(unreachable_code)]
#[cfg(feature = "_deflate-any")]
CompressionMethod::Deflated => {
let default = {
#[cfg(feature = "deflate-flate2")]
{
i64::from(flate2::Compression::default().level())
}
#[cfg(all(not(feature = "deflate-flate2"), feature = "deflate-zopfli"))]
{
24
}
#[cfg(not(any(feature = "deflate-zopfli", feature = "deflate-flate2")))]
{
compile_error!("could not calculate default: unknown deflate variant")
}
};
let level = validate_value_in_range(
compression_level.unwrap_or(default),
deflate_compression_level_range(),
)
.ok_or(UnsupportedArchive("Unsupported compression level"))?;
#[cfg(feature = "deflate-zopfli")]
{
macro_rules! deflate_zopfli_and_return {
($bare:expr, $best_non_zopfli:expr) => {{
let options = zopfli::Options {
iteration_count: core::num::NonZeroU64::try_from(u64::from(
level - $best_non_zopfli,
))
.map_err(|e| {
std::io::Error::other(format!(
"Failed to get the iteration count for zopfli : {e}"
))
})?,
..Default::default()
};
Ok(Box::new(move |bare| {
Ok(match zopfli_buffer_size {
Some(size) => GenericZipWriter::BufferedZopfliDeflater(
std::io::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")]
{
let best_non_zopfli = flate2::Compression::best().level();
if level > best_non_zopfli {
return deflate_zopfli_and_return!(bare, best_non_zopfli);
}
}
#[cfg(not(feature = "deflate-flate2"))]
{
let best_non_zopfli = 9;
return deflate_zopfli_and_return!(bare, best_non_zopfli);
}
}
#[cfg(feature = "deflate-flate2")]
{
Ok(Box::new(move |bare| {
Ok(GenericZipWriter::Deflater(
flate2::write::DeflateEncoder::new(
bare,
flate2::Compression::new(level),
),
))
}))
}
#[cfg(not(feature = "deflate-flate2"))]
{
unreachable!("deflate writer: have no fallback for this case")
}
}
#[cfg(feature = "deflate64")]
CompressionMethod::Deflate64 => {
Err(UnsupportedArchive("Compressing Deflate64 is not supported"))
}
#[cfg(feature = "_bzip2_any")]
CompressionMethod::Bzip2 => {
let level: u32 = validate_value_in_range(
compression_level.unwrap_or(i64::from(bzip2::Compression::default().level())),
bzip2_compression_level_range(),
)
.ok_or(UnsupportedArchive("Unsupported compression level"))?;
Ok(Box::new(move |bare| {
Ok(GenericZipWriter::Bzip2(bzip2::write::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 = validate_value_in_range(
compression_level.unwrap_or(i64::from(zstd::DEFAULT_COMPRESSION_LEVEL)),
zstd::compression_level_range(),
)
.ok_or(UnsupportedArchive("Unsupported compression level"))?;
Ok(Box::new(move |bare| {
Ok(GenericZipWriter::Zstd(
zstd::stream::write::Encoder::new(bare, level as i32)
.map_err(ZipError::Io)?,
))
}))
}
#[cfg(feature = "legacy-zip")]
CompressionMethod::Shrink => Err(ZipError::UnsupportedArchive(
"Shrink compression unsupported",
)),
#[cfg(feature = "legacy-zip")]
CompressionMethod::Reduce(_) => Err(ZipError::UnsupportedArchive(
"Reduce compression unsupported",
)),
#[cfg(feature = "legacy-zip")]
CompressionMethod::Implode => Err(ZipError::UnsupportedArchive(
"Implode compression unsupported",
)),
#[cfg(feature = "lzma")]
CompressionMethod::Lzma => {
Err(UnsupportedArchive("LZMA isn't supported for compression"))
}
#[cfg(feature = "xz")]
CompressionMethod::Xz => {
let level = validate_value_in_range(compression_level.unwrap_or(6), 0_u32..=9_u32)
.ok_or(UnsupportedArchive("Unsupported compression level"))?;
Ok(Box::new(move |bare| {
Ok(GenericZipWriter::Xz(Box::new(
lzma_rust2::XzWriter::new(bare, lzma_rust2::XzOptions::with_preset(level))
.map_err(ZipError::Io)?,
)))
}))
}
#[cfg(feature = "ppmd")]
CompressionMethod::Ppmd => {
const ORDERS: [u32; 10] = [0, 4, 5, 6, 7, 8, 9, 10, 11, 12];
let level = validate_value_in_range(compression_level.unwrap_or(7), 1_u32..=9_u32)
.ok_or(UnsupportedArchive("Unsupported compression level"))?;
let order = ORDERS[level as usize];
let memory_size = 1 << (level + 19);
let memory_size_mb = memory_size / 1024 / 1024;
Ok(Box::new(move |mut bare| {
let parameter: u16 = (order as u16 - 1)
+ ((memory_size_mb - 1) << 4) as u16
+ ((ppmd_rust::RestoreMethod::Restart as u16) << 12);
bare.write_all(¶meter.to_le_bytes())
.map_err(ZipError::Io)?;
let encoder = ppmd_rust::Ppmd8Encoder::new(
bare,
order,
memory_size,
ppmd_rust::RestoreMethod::Restart,
)
.map_err(|error| match error {
ppmd_rust::Error::RangeDecoderInitialization => ZipError::InvalidArchive(
"PPMd range coder initialization failed".into(),
),
ppmd_rust::Error::InvalidParameter => {
ZipError::InvalidArchive("Invalid PPMd parameter".into())
}
ppmd_rust::Error::IoError(io_error) => ZipError::Io(io_error),
ppmd_rust::Error::MemoryAllocation => ZipError::Io(io::Error::new(
ErrorKind::OutOfMemory,
"PPMd could not allocate memory",
)),
})?;
Ok(GenericZipWriter::Ppmd(Box::new(encoder)))
}))
}
#[allow(deprecated)]
CompressionMethod::Unsupported(..) => {
Err(UnsupportedArchive("Unsupported compression"))
}
}
}
fn switch_to(&mut self, make_new_self: SwitchWriterFunction<W>) -> ZipResult<()> {
let bare = match mem::replace(self, GenericZipWriter::Closed) {
GenericZipWriter::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_any")]
GenericZipWriter::Bzip2(w) => w.finish()?,
#[cfg(feature = "zstd")]
GenericZipWriter::Zstd(w) => w.finish()?,
#[cfg(feature = "xz")]
GenericZipWriter::Xz(w) => w.finish()?,
#[cfg(feature = "ppmd")]
GenericZipWriter::Ppmd(w) => {
w.finish(true)?
}
GenericZipWriter::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 {
GenericZipWriter::Storer(w) => Some(w as &mut dyn Write),
#[cfg(feature = "deflate-flate2")]
GenericZipWriter::Deflater(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_any")]
GenericZipWriter::Bzip2(w) => Some(w as &mut dyn Write),
#[cfg(feature = "zstd")]
GenericZipWriter::Zstd(w) => Some(w as &mut dyn Write),
#[cfg(feature = "xz")]
GenericZipWriter::Xz(w) => Some(w as &mut dyn Write),
#[cfg(feature = "ppmd")]
GenericZipWriter::Ppmd(w) => Some(w as &mut dyn Write),
GenericZipWriter::Closed => None,
}
}
const fn is_closed(&self) -> bool {
matches!(*self, GenericZipWriter::Closed)
}
fn try_inner_mut(&mut self) -> Result<&mut W, std::io::Error> {
match *self {
GenericZipWriter::Storer(MaybeEncrypted::Unencrypted(ref mut w)) => Ok(w),
_ => Err(std::io::Error::other(
"Should have switched to stored and unencrypted beforehand",
)),
}
}
}
#[cfg(feature = "_deflate-any")]
fn deflate_compression_level_range() -> std::ops::RangeInclusive<u32> {
#[cfg(not(any(feature = "deflate-zopfli", feature = "deflate-flate2")))]
{
compile_error!("min: unknown deflate variant - enable deflate-zopfli or deflate-flate2")
}
let min = {
#[cfg(feature = "deflate-flate2")]
{
flate2::Compression::fast().level()
}
#[cfg(all(not(feature = "deflate-flate2"), feature = "deflate-zopfli"))]
{
1
}
};
let max = {
#[cfg(feature = "deflate-zopfli")]
{
264
}
#[cfg(all(not(feature = "deflate-zopfli"), feature = "deflate-flate2"))]
{
flate2::Compression::best().level()
}
};
min..=max
}
#[cfg(feature = "_bzip2_any")]
fn bzip2_compression_level_range() -> std::ops::RangeInclusive<u32> {
let min = bzip2::Compression::fast().level();
let max = bzip2::Compression::best().level();
min..=max
}
#[cfg(any(
feature = "_deflate-any",
feature = "_bzip2_any",
feature = "ppmd",
feature = "xz",
feature = "zstd",
))]
fn validate_value_in_range<T: Ord + Copy, U: Ord + Copy + TryFrom<T>>(
value: T,
range: std::ops::RangeInclusive<U>,
) -> Option<U> {
let value: U = value.try_into().ok()?;
if range.contains(&value) {
Some(value)
} else {
None
}
}
fn update_aes_extra_data<W: Write + Seek>(
writer: &mut W,
file: &mut ZipFileData,
bytes_written: u64,
) -> ZipResult<()> {
let Some((aes_mode, version, compression_method)) = &mut file.aes_mode else {
return Ok(());
};
*version = if bytes_written < 20 {
AesVendorVersion::Ae2
} else {
AesVendorVersion::Ae1
};
let extra_data_start = file
.extra_data_start
.ok_or_else(|| std::io::Error::other("Cannot get the extra data start"))?;
writer.seek(SeekFrom::Start(
extra_data_start + file.aes_extra_data_start,
))?;
let aes_extra_field = AexEncryption::new(*version, *aes_mode, *compression_method);
let buf = aes_extra_field.as_bytes();
writer.write_all(buf)?;
let aes_extra_data_start = file.aes_extra_data_start as usize;
let Some(ref mut extra_field) = file.extra_field else {
return Err(invalid!(
"update_aes_extra_data called on a file that has no extra-data field"
));
};
let mut vec = extra_field.to_vec();
vec[aes_extra_data_start..aes_extra_data_start + size_of::<AexEncryption>()]
.copy_from_slice(buf);
*extra_field = Arc::from(vec.into_boxed_slice());
Ok(())
}
impl ZipFileData {
pub(crate) fn update_local_file_header<T: Write + Seek>(
&mut self,
writer: &mut T,
) -> ZipResult<()> {
writer.seek(SeekFrom::Start(
self.header_start + (size_of::<Magic>() + offset_of!(ZipLocalEntryBlock, crc32)) as u64,
))?;
writer.write_u32_le(self.crc32)?;
if self.large_file {
writer.write_u32_le(spec::ZIP64_BYTES_THR as u32)?;
writer.write_u32_le(spec::ZIP64_BYTES_THR as u32)?;
self.update_local_zip64_extra_field(writer)?;
} else {
if self.compressed_size > spec::ZIP64_BYTES_THR {
return Err(ZipError::Io(std::io::Error::other(
"large_file(true) option has not been set",
)));
}
writer.write_u32_le(self.compressed_size as u32)?;
writer.write_u32_le(self.uncompressed_size as u32)?;
}
Ok(())
}
fn update_local_zip64_extra_field<T: Write + Seek>(&mut self, writer: &mut T) -> ZipResult<()> {
let zip64_block = Zip64ExtendedInformation::local_header(
self.large_file,
self.uncompressed_size,
self.compressed_size,
)
.ok_or(invalid!(
"Attempted to update a nonexistent ZIP64 extra field"
))?;
let zip64_extra_field_start = self.header_start
+ (size_of::<Magic>() + size_of::<ZipLocalEntryBlock>()) as u64
+ self.file_name_raw.len() as u64;
writer.seek(SeekFrom::Start(zip64_extra_field_start))?;
let zip64_block = zip64_block.serialize();
writer.write_all(&zip64_block)?;
Ok(())
}
pub(crate) fn write_central_directory_header<T: Write>(&self, writer: &mut T) -> ZipResult<()> {
let mut block = self.block()?;
let stripped_extra = if let Some(extra) = &self.extra_field {
strip_alignment_extra_field(extra, true)
} else {
Vec::new()
};
let central_len = self.central_extra_field_len();
let zip64_extra_field_block = Zip64ExtendedInformation::central_header(
self.large_file,
self.uncompressed_size,
self.compressed_size,
self.header_start,
);
let zip64_block_len = if let Some(zip64) = zip64_extra_field_block {
zip64.full_size()
} else {
0
};
let total_extra_len = zip64_block_len + stripped_extra.len() + central_len;
block.extra_field_length = u16::try_from(total_extra_len)
.map_err(|_| invalid!("Extra field length in central directory exceeds 64KiB"))?;
block.write(writer)?;
writer.write_all(&self.file_name_raw)?;
if let Some(zip64_extra_field) = zip64_extra_field_block {
writer.write_all(&zip64_extra_field.serialize())?;
}
if !stripped_extra.is_empty() {
writer.write_all(&stripped_extra)?;
}
if let Some(central_extra_field) = &self.central_extra_field {
writer.write_all(central_extra_field)?;
}
writer.write_all(self.file_comment.as_bytes())?;
Ok(())
}
}
pub(crate) fn strip_alignment_extra_field(extra_field: &[u8], remove_zip64: bool) -> Vec<u8> {
let mut new_extra = Vec::with_capacity(extra_field.len());
let mut cursor = 0;
while cursor + 4 <= extra_field.len() {
let tag = u16::from_le_bytes([extra_field[cursor], extra_field[cursor + 1]]);
let len = u16::from_le_bytes([extra_field[cursor + 2], extra_field[cursor + 3]]) as usize;
if cursor + 4 + len > extra_field.len() {
new_extra.extend_from_slice(&extra_field[cursor..]);
break;
}
if tag != UsedExtraField::DataStreamAlignment.as_u16()
&& !(tag == UsedExtraField::Zip64ExtendedInfo.as_u16() && remove_zip64)
{
new_extra.extend_from_slice(&extra_field[cursor..cursor + 4 + len]);
}
cursor += 4 + len;
}
if cursor < extra_field.len() {
new_extra.extend_from_slice(&extra_field[cursor..]);
}
new_extra
}
pub struct StreamWriter<W: Write> {
inner: W,
bytes_written: u64,
}
impl<W: Write> StreamWriter<W> {
pub fn new(inner: W) -> StreamWriter<W> {
Self {
inner,
bytes_written: 0,
}
}
pub fn get_ref(&self) -> &W {
&self.inner
}
pub fn get_mut(&mut self) -> &mut W {
&mut self.inner
}
pub fn into_inner(self) -> W {
self.inner
}
}
impl<W: Write> Write for StreamWriter<W> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let bytes_written = self.inner.write(buf)?;
self.bytes_written += bytes_written as u64;
Ok(bytes_written)
}
fn flush(&mut self) -> io::Result<()> {
self.inner.flush()
}
}
impl<W: Write> Seek for StreamWriter<W> {
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
match pos {
SeekFrom::Current(0) | SeekFrom::End(0) => return Ok(self.bytes_written),
SeekFrom::Start(x) if x == self.bytes_written => {
return Ok(self.bytes_written);
}
_ => {}
}
Err(io::Error::new(
ErrorKind::Unsupported,
"seek is not supported",
))
}
}
#[cfg(test)]
#[allow(unknown_lints)] #[allow(clippy::needless_update)] #[allow(clippy::octal_escapes)] mod tests {
use super::{ExtendedFileOptions, FileOptions, FullFileOptions, ZipWriter};
use crate::CompressionMethod::Stored;
use crate::compression::CompressionMethod;
use crate::datetime::DateTime;
use crate::result::ZipResult;
use crate::types::System;
use crate::write::EncryptWith::ZipCrypto;
use crate::write::SimpleFileOptions;
use crate::zipcrypto::ZipCryptoKeys;
use crate::{HasZipMetadata, ZipArchive};
#[cfg(feature = "deflate-flate2")]
use std::io::Read;
use std::io::{Cursor, Write};
use std::marker::PhantomData;
use std::path::PathBuf;
const SYSTEM_BYTE: u8 = if cfg!(windows) {
System::Dos
} else {
System::Unix
} as u8;
#[test]
fn write_empty_zip() {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
writer.set_comment("ZIP").unwrap();
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 write_file_comment_roundtrip() -> ZipResult<()> {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
let options = FileOptions {
extended_options: ExtendedFileOptions {
extra_data: vec![].into(),
central_extra_data: vec![].into(),
file_comment: Some("file comment".into()),
},
#[cfg(all(feature = "deflate-zopfli", not(feature = "deflate-flate2")))]
compression_method: crate::CompressionMethod::Stored,
..Default::default()
};
writer.start_file("foo.txt", options)?;
writer.write_all(b"data")?;
let options2 = FileOptions {
extended_options: ExtendedFileOptions {
extra_data: vec![].into(),
central_extra_data: vec![].into(),
file_comment: Some("file2 comment".into()),
},
#[cfg(all(feature = "deflate-zopfli", not(feature = "deflate-flate2")))]
compression_method: crate::CompressionMethod::Stored,
..Default::default()
};
writer.start_file("foo2.txt", options2)?;
writer.write_all(b"data2")?;
let bytes = writer.finish()?.into_inner();
let mut archive = ZipArchive::new(Cursor::new(bytes))?;
{
let file = archive.by_name("foo.txt")?;
assert_eq!(file.comment(), "file comment");
}
let file2 = archive.by_name("foo2.txt")?;
assert_eq!(file2.comment(), "file2 comment");
Ok(())
}
#[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);
const DOS_DIR_ATTRIBUTES_BYTE: u8 = if cfg!(windows) { 0x10 } else { 0 };
#[rustfmt::skip]
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, SYSTEM_BYTE, 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,
DOS_DIR_ATTRIBUTES_BYTE, 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);
#[rustfmt::skip]
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, System::Unix as u8, 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);
#[rustfmt::skip]
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, System::Unix as u8, 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,
#[cfg(feature = "aes-crypto")]
aes_mode: None,
system: 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);
#[rustfmt::skip]
assert_eq!(result.get_ref(), &[80, 75, 3, 4, 10, 0, 0, 0, 0, 0, 0, 0, 33, 0, 94, 198, 50,
12, 39, 0, 0, 0, 39, 0, 0, 0, 8, 0, 0, 0, 109, 105, 109, 101, 116, 121, 112, 101, 97,
112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 47, 118, 110, 100, 46, 111, 97, 115,
105, 115, 46, 111, 112, 101, 110, 100, 111, 99, 117, 109, 101, 110, 116, 46, 116, 101,
120, 116, 80, 75, 1, 2, 10, SYSTEM_BYTE, 10, 0, 0, 0, 0, 0, 0, 0, 33, 0, 94, 198, 50,
12, 39, 0, 0, 0, 39, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 164, 129, 0, 0, 0, 0,
109, 105, 109, 101, 116, 121, 112, 101, 80, 75, 5, 6, 0, 0, 0, 0, 1, 0, 1, 0, 54, 0, 0,
0, 77, 0, 0, 0, 0, 0]);
}
#[cfg(feature = "deflate-flate2")]
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";
#[cfg(feature = "deflate-flate2")]
const RT_TEST_FILENAME: &str = "subfolder/sub-subfolder/can't_stop.txt";
#[cfg(feature = "deflate-flate2")]
const SECOND_FILENAME: &str = "different_name.xyz";
#[cfg(feature = "deflate-flate2")]
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,
#[cfg(feature = "aes-crypto")]
aes_mode: None,
system: 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);
#[rustfmt::skip]
assert_eq!(result.get_ref(), &[80, 75, 3, 4, 10, 0, 0, 0, 0, 0, 0, 0, 33, 0, 29, 183, 125,
75, 16, 0, 0, 0, 16, 0, 0, 0, 4, 0, 0, 0, 214, 208, 206, 196, 101, 110, 99, 111, 100,
105, 110, 103, 32, 71, 66, 49, 56, 48, 51, 48, 80, 75, 3, 4, 10, 0, 0, 0, 0, 0, 0, 0,
33, 0, 75, 110, 14, 242, 18, 0, 0, 0, 18, 0, 0, 0, 4, 0, 0, 0, 147, 250, 149, 182, 101,
110, 99, 111, 100, 105, 110, 103, 32, 83, 72, 73, 70, 84, 95, 74, 73, 83, 80, 75, 1, 2,
10, SYSTEM_BYTE, 10, 0, 0, 0, 0, 0, 0, 0, 33, 0, 29, 183, 125, 75, 16, 0, 0, 0, 16, 0,
0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 164, 129, 0, 0, 0, 0, 214, 208, 206, 196, 80,
75, 1, 2, 10, SYSTEM_BYTE, 10, 0, 0, 0, 0, 0, 0, 0, 33, 0, 75, 110, 14, 242, 18, 0, 0, 0, 18, 0,
0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 164, 129, 50, 0, 0, 0, 147, 250, 149, 182, 80,
75, 5, 6, 0, 0, 0, 0, 2, 0, 2, 0, 100, 0, 0, 0, 102, 0, 0, 0, 0, 0]);
}
#[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).unwrap();
assert_eq!(&*path_str, "system32");
}
#[test]
#[cfg(feature = "deflate-flate2")]
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,
#[cfg(feature = "aes-crypto")]
aes_mode: None,
system: 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]
#[cfg(feature = "deflate-flate2")]
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,
#[cfg(feature = "aes-crypto")]
aes_mode: None,
system: 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().into_inner();
zip.iter().copied().for_each(|x| print!("{x:02x}"));
println!();
let mut writer = ZipWriter::new_append(Cursor::new(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]
#[cfg(feature = "deflate-flate2")]
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(0xFFFF)
.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 = 0xFFFF;
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");
let data_start = file.data_start().unwrap();
assert_eq!(data_start, u64::from(page_size));
}
#[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");
let data_start = file.data_start().unwrap();
assert_eq!(data_start, u64::from(page_size));
}
}
#[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).unwrap();
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(all(feature = "_bzip2_any", not(miri)))]
#[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(),
file_comment: None,
},
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(),
file_comment: None,
},
..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(),
file_comment: None,
},
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(),
file_comment: None,
},
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(),
file_comment: None,
},
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(),
file_comment: None,
},
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(),
file_comment: None,
},
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(),
file_comment: None,
},
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(),
file_comment: None,
},
alignment: 0xFFF1,
..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(0xFFFF),
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(),
file_comment: None,
},
alignment: 0xFFFF,
..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(),
file_comment: None,
},
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::AesMode::Aes256;
use crate::write::EncryptWith::Aes;
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: &[],
salt: None,
}),
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(),
file_comment: None,
},
alignment: 0xFFFF,
..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(),
file_comment: None,
},
alignment: 0xFFFF,
..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(
0xFFFF,
),
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(),
file_comment: None,
},
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(),
file_comment: None,
},
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(),
file_comment: None,
},
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(),
file_comment: None,
},
alignment: 0xFFFF,
..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(),
file_comment: None,
},
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(),
file_comment: None,
},
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(),
file_comment: None,
},
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(all(feature = "_bzip2_any", not(miri)))]
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(),
file_comment: None,
},
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(),
file_comment: None,
},
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,
]))
.unwrap();
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([])).unwrap();
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([])).unwrap();
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(),
file_comment: None,
},
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_any", feature = "aes-crypto", not(miri)))]
#[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: &[],
salt: None,
}),
extended_options: ExtendedFileOptions {
extra_data: vec![].into(),
central_extra_data: vec![].into(),
file_comment: None,
},
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(),
file_comment: None,
},
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(),
file_comment: None,
},
..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(all(feature = "_bzip2_any", not(miri)))]
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(),
file_comment: None,
},
alignment: 0xFFFF,
..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(),
file_comment: None,
},
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::AesMode::Aes128;
use crate::write::EncryptWith::Aes;
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: &[],
salt: None,
}),
extended_options: ExtendedFileOptions {
extra_data: vec![3, 0, 4, 0, 209, 53, 53, 8, 2, 61, 0, 0].into(),
central_extra_data: vec![].into(),
file_comment: None,
},
alignment: 0xFFFF,
..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(),
file_comment: None,
},
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(),
file_comment: None,
},
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(),
file_comment: None,
},
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(),
file_comment: None,
},
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(),
file_comment: None,
},
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(())
}
#[test]
fn test_explicit_system_roundtrip() -> ZipResult<()> {
let systems = vec![System::Unix, System::Dos, System::WindowsNTFS];
for system in systems {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
let options = SimpleFileOptions::default()
.compression_method(Stored)
.system(system);
let filename = format!("test_{:?}.txt", system);
writer.start_file(&filename, options)?;
writer.write_all(b"content")?;
let bytes = writer.finish()?.into_inner();
let mut reader = ZipArchive::new(Cursor::new(bytes))?;
let file = reader.by_index(0)?;
assert_eq!(
file.get_metadata().system,
system,
"System mismatch for {:?}",
system
);
}
Ok(())
}
#[test]
fn test_system_default_behavior() -> ZipResult<()> {
let mut writer = ZipWriter::new(Cursor::new(Vec::new()));
let options = SimpleFileOptions::default().compression_method(Stored);
writer.start_file("test.txt", options)?;
writer.write_all(b"test")?;
let bytes = writer.finish()?.into_inner();
let mut reader = ZipArchive::new(Cursor::new(bytes))?;
let file = reader.by_index(0)?;
let expected_system = if cfg!(windows) {
System::Dos
} else {
System::Unix
};
assert_eq!(file.get_metadata().system, expected_system);
Ok(())
}
}