#[cfg(feature = "aes-crypto")] usecrate::aes::AesWriter; usecrate::compression::CompressionMethod; usecrate::read::{parse_single_extra_field, Config, ZipArchive, ZipFile}; usecrate::result::{ZipError, ZipResult}; usecrate::spec::{self, FixedSizeBlock, Zip32CDEBlock}; #[cfg(feature = "aes-crypto")] usecrate::types::AesMode; usecrate::types::{
ffi, AesVendorVersion, DateTime, Zip64ExtraFieldBlock, ZipFileData, ZipLocalEntryBlock,
ZipRawValues, MIN_VERSION,
}; usecrate::write::ffi::S_IFLNK; #[cfg(all(feature = "_deflate-any", feature = "deflate-zopfli",))] use core::num::NonZeroU64; use crc32fast::Hasher; use indexmap::IndexMap; use std::borrow::ToOwned; use std::default::Default; use std::fmt::{Debug, Formatter}; use std::io; use std::io::prelude::*; use std::io::Cursor; use std::io::{BufReader, SeekFrom}; use std::marker::PhantomData; use std::mem; use std::str::{from_utf8, Utf8Error}; use std::sync::Arc;
#[cfg(feature = "deflate-flate2")] use flate2::{write::DeflateEncoder, Compression};
#[cfg(feature = "bzip2")] use bzip2::write::BzEncoder;
#[cfg(feature = "deflate-zopfli")] use zopfli::Options;
#[cfg(feature = "deflate-zopfli")] use std::io::BufWriter; use std::mem::size_of; use std::path::Path;
#[cfg(feature = "zstd")] use zstd::stream::write::Encoder as ZstdEncoder;
// Put the struct declaration in a private module to convince rustdoc to display ZipWriter nicely pub(crate) mod zip_writer { usesuper::*; /// ZIP archive generator /// /// Handles the bookkeeping involved in building an archive, and provides an /// API to edit its contents. /// /// ``` /// # fn doit() -> zip::result::ZipResult<()> /// # { /// # use zip::ZipWriter; /// use std::io::Write; /// use zip::write::SimpleFileOptions; /// /// // We use a buffer here, though you'd normally use a `File` /// let mut buf = [0; 65536]; /// let mut zip = ZipWriter::new(std::io::Cursor::new(&mut buf[..])); /// /// let options = SimpleFileOptions::default().compression_method(zip::CompressionMethod::Stored); /// zip.start_file("hello_world.txt", options)?; /// zip.write(b"Hello, World!")?; /// /// // Apply the changes you've made. /// // Dropping the `ZipWriter` will have the same effect, but may silently fail /// zip.finish()?; /// /// # Ok(()) /// # } /// # doit().unwrap(); /// ``` pubstruct ZipWriter<W: Write + Seek> { pub(super) inner: GenericZipWriter<W>, pub(super) files: IndexMap<Box<str>, ZipFileData>, pub(super) stats: ZipWriterStats, pub(super) writing_to_file: bool, pub(super) writing_raw: bool, pub(super) comment: Box<[u8]>, pub(super) zip64_comment: Option<Box<[u8]>>, pub(super) flush_on_finish_file: bool,
}
/// Metadata for a file to be written #[derive(Clone, Debug, Copy, Eq, PartialEq)] pubstruct FileOptions<'k, T: FileOptionExtension> { pub(crate) compression_method: CompressionMethod, pub(crate) compression_level: Option<i64>, pub(crate) last_modified_time: DateTime, pub(crate) permissions: Option<u32>, pub(crate) large_file: bool, pub(crate) encrypt_with: Option<EncryptWith<'k>>, pub(crate) extended_options: T, pub(crate) alignment: u16, #[cfg(feature = "deflate-zopfli")] pub(super) zopfli_buffer_size: Option<usize>,
} /// Simple File Options. Can be copied and good for simple writing zip files pubtype SimpleFileOptions = FileOptions<'static, ()>; /// Adds Extra Data and Central Extra Data. It does not implement copy. pubtype FullFileOptions<'k> = FileOptions<'k, ExtendedFileOptions>; /// The Extension for Extra Data and Central Extra Data #[derive(Clone, Default, Eq, PartialEq)] pubstruct ExtendedFileOptions {
extra_data: Arc<Vec<u8>>,
central_extra_data: Arc<Vec<u8>>,
}
impl ExtendedFileOptions { /// Adds an extra data field, unless we detect that it's invalid. pubfn add_extra_data(
&mutself,
header_id: u16,
data: Box<[u8]>,
central_only: bool,
) -> ZipResult<()> { let len = data.len() + 4; ifself.extra_data.len() + self.central_extra_data.len() + len > u16::MAX as usize {
Err(InvalidArchive( "Extra data field would be longer than allowed",
))
} else { let field = if central_only {
&mutself.central_extra_data
} else {
&mutself.extra_data
}; let vec = Arc::get_mut(field); let vec = match vec {
Some(exclusive) => exclusive,
None => {
*field = Arc::new(field.to_vec());
Arc::get_mut(field).unwrap()
}
}; Self::add_extra_data_unchecked(vec, header_id, data)?; Self::validate_extra_data(vec, true)?;
Ok(())
}
}
/// Set the compression method for the new file /// /// The default is `CompressionMethod::Deflated` if it is enabled. If not, /// `CompressionMethod::Bzip2` is the default if it is enabled. If neither `bzip2` nor `deflate` /// is enabled, `CompressionMethod::Zlib` is the default. If all else fails, /// `CompressionMethod::Stored` becomes the default and files are written uncompressed. #[must_use] pubconstfn compression_method(mutself, method: CompressionMethod) -> Self { self.compression_method = method; self
}
/// Set the compression level for the new file /// /// `None` value specifies default compression level. /// /// Range of values depends on compression method: /// * `Deflated`: 10 - 264 for Zopfli, 0 - 9 for other encoders. Default is 24 if Zopfli is the /// only encoder, or 6 otherwise. /// * `Bzip2`: 0 - 9. Default is 6 /// * `Zstd`: -7 - 22, with zero being mapped to default level. Default is 3 /// * others: only `None` is allowed #[must_use] pubconstfn compression_level(mutself, level: Option<i64>) -> Self { self.compression_level = level; self
}
/// Set the last modified time /// /// The default is the current timestamp if the 'time' feature is enabled, and 1980-01-01 /// otherwise #[must_use] pubconstfn last_modified_time(mutself, mod_time: DateTime) -> Self { self.last_modified_time = mod_time; self
}
/// Set the permissions for the new file. /// /// The format is represented with unix-style permissions. /// The default is `0o644`, which represents `rw-r--r--` for files, /// and `0o755`, which represents `rwxr-xr-x` for directories. /// /// This method only preserves the file permissions bits (via a `& 0o777`) and discards /// higher file mode bits. So it cannot be used to denote an entry as a directory, /// symlink, or other special file type. #[must_use] pubconstfn unix_permissions(mutself, mode: u32) -> Self { self.permissions = Some(mode & 0o777); self
}
/// Set whether the new file's compressed and uncompressed size is less than 4 GiB. /// /// If set to `false` and the file exceeds the limit, an I/O error is thrown and the file is /// aborted. If set to `true`, readers will require ZIP64 support and if the file does not /// exceed the limit, 20 B are wasted. The default is `false`. #[must_use] pubconstfn large_file(mutself, large: bool) -> Self { self.large_file = large; self
}
/// Sets the size of the buffer used to hold the next block that Zopfli will compress. The /// larger the buffer, the more effective the compression, but the more memory is required. /// A value of `None` indicates no buffer, which is recommended only when all non-empty writes /// are larger than about 32 KiB. #[must_use] #[cfg(feature = "deflate-zopfli")] pubconstfn with_zopfli_buffer(mutself, size: Option<usize>) -> Self { self.zopfli_buffer_size = size; self
}
/// Returns the compression level currently set. pubconstfn get_compression_level(&self) -> Option<i64> { self.compression_level
} /// Sets the alignment to the given number of bytes. #[must_use] pubconstfn with_alignment(mutself, alignment: u16) -> Self { self.alignment = alignment; self
}
} impl FileOptions<'_, ExtendedFileOptions> { /// Adds an extra data field. pubfn add_extra_data(
&mutself,
header_id: u16,
data: Box<[u8]>,
central_only: bool,
) -> ZipResult<()> { self.extended_options
.add_extra_data(header_id, data, central_only)
}
/// Removes the extra data fields. #[must_use] pubfn clear_extra_data(mutself) -> 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<T: FileOptionExtension> Default for FileOptions<'_, T> { /// Construct a new FileOptions object fn default() -> Self { Self {
compression_method: Default::default(),
compression_level: None,
last_modified_time: DateTime::default_for_write(),
permissions: None,
large_file: false,
encrypt_with: None,
extended_options: T::default(),
alignment: 1, #[cfg(feature = "deflate-zopfli")]
zopfli_buffer_size: Some(1 << 15),
}
}
}
impl<W: Write + Seek> Write for ZipWriter<W> { fn write(&mutself, buf: &[u8]) -> io::Result<usize> { if !self.writing_to_file { return Err(io::Error::new(
io::ErrorKind::Other, "No file has been started",
));
} if buf.is_empty() { return Ok(0);
} matchself.inner.ref_mut() {
Some(refmut w) => { let write_result = w.write(buf); iflet Ok(count) = write_result { self.stats.update(&buf[0..count]); ifself.stats.bytes_written > spec::ZIP64_BYTES_THR
&& !self.files.last_mut().unwrap().1.large_file
{ let _ = self.abort_file(); return Err(io::Error::new(
io::ErrorKind::Other, "Large file option has not been set",
));
}
}
write_result
}
None => Err(io::Error::new(
io::ErrorKind::BrokenPipe, "write(): ZipWriter was already closed",
)),
}
}
impl<A: Read + Write + Seek> ZipWriter<A> { /// Initializes the archive from an existing ZIP archive, making it ready for append. /// /// This uses a default configuration to initially read the archive. pubfn new_append(readwriter: A) -> ZipResult<ZipWriter<A>> { Self::new_append_with_config(Default::default(), readwriter)
}
/// Initializes the archive from an existing ZIP archive, making it ready for append. /// /// This uses the given read configuration to initially read the archive. pubfn 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)?;
/// `flush_on_finish_file` is designed to support a streaming `inner` that may unload flushed /// bytes. It flushes a file's header and body once it starts writing another file. A ZipWriter /// will not try to seek back into where a previous file was written unless /// either [`ZipWriter::abort_file`] is called while [`ZipWriter::is_writing_file`] returns /// false, or [`ZipWriter::deep_copy_file`] is called. In the latter case, it will only need to /// read previously-written files and not overwrite them. /// /// Note: when using an `inner` that cannot overwrite flushed bytes, do not wrap it in a /// [BufWriter], because that has a [Seek::seek] method that implicitly calls /// [BufWriter::flush], and ZipWriter needs to seek backward to update each file's header with /// the size and checksum after writing the body. /// /// This setting is false by default. pubfn set_flush_on_finish_file(&mutself, flush_on_finish_file: bool) { self.flush_on_finish_file = flush_on_finish_file;
}
}
/// Like `deep_copy_file`, but uses Path arguments. /// /// This function ensures that the '/' path separator is used and normalizes `.` and `..`. It /// ignores any `..` or Windows drive letter that would produce a path outside the ZIP file's /// root. pubfn deep_copy_file_from_path<T: AsRef<Path>, U: AsRef<Path>>(
&mutself,
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)
}
/// Write the zip file into the backing stream, then produce a readable archive of that data. /// /// This method avoids parsing the central directory records at the end of the stream for /// a slight performance improvement over running [`ZipArchive::new()`] on the output of /// [`Self::finish()`]. /// ///``` /// # fn main() -> Result<(), zip::result::ZipError> { /// use std::io::{Cursor, prelude::*}; /// use zip::{ZipArchive, ZipWriter, write::SimpleFileOptions}; /// /// let buf = Cursor::new(Vec::new()); /// let mut zip = ZipWriter::new(buf); /// let options = SimpleFileOptions::default(); /// zip.start_file("a.txt", options)?; /// zip.write_all(b"hello\n")?; /// /// let mut zip = zip.finish_into_readable()?; /// let mut s: String = String::new(); /// zip.by_name("a.txt")?.read_to_string(&mut s)?; /// assert_eq!(s, "hello\n"); /// # Ok(()) /// # } ///``` pubfn finish_into_readable(mutself) -> ZipResult<ZipArchive<A>> { let central_start = self.finalize()?; let inner = mem::replace(&mutself.inner, Closed).unwrap(); let comment = mem::take(&mutself.comment); let zip64_comment = mem::take(&mutself.zip64_comment); let files = mem::take(&mutself.files);
impl<W: Write + Seek> ZipWriter<W> { /// Initializes the archive. /// /// Before writing to this object, the [`ZipWriter::start_file`] function should be called. /// After a successful write, the file remains open for writing. After a failed write, call /// [`ZipWriter::is_writing_file`] to determine if the file remains open. pubfn new(inner: W) -> ZipWriter<W> {
ZipWriter {
inner: Storer(MaybeEncrypted::Unencrypted(inner)),
files: IndexMap::new(),
stats: Default::default(),
writing_to_file: false,
writing_raw: false,
comment: Box::new([]),
zip64_comment: None,
flush_on_finish_file: false,
}
}
/// Returns true if a file is currently open for writing. pubconstfn is_writing_file(&self) -> bool { self.writing_to_file && !self.inner.is_closed()
}
/// Set ZIP archive comment. pubfn set_comment<S>(&mutself, comment: S) where
S: Into<Box<str>>,
{ self.set_raw_comment(comment.into().into_boxed_bytes())
}
/// Set ZIP archive comment. /// /// This sets the raw bytes of the comment. The comment /// is typically expected to be encoded in UTF-8. pubfn set_raw_comment(&mutself, comment: Box<[u8]>) { self.comment = comment;
}
/// Get ZIP archive comment. pubfn get_comment(&mutself) -> Result<&str, Utf8Error> {
from_utf8(self.get_raw_comment())
}
/// Get ZIP archive comment. /// /// This returns the raw bytes of the comment. The comment /// is typically expected to be encoded in UTF-8. pubconstfn get_raw_comment(&self) -> &[u8] {
&self.comment
}
/// Set ZIP64 archive comment. pubfn set_zip64_comment<S>(&mutself, comment: Option<S>) where
S: Into<Box<str>>,
{ self.set_raw_zip64_comment(comment.map(|v| v.into().into_boxed_bytes()))
}
/// Set ZIP64 archive comment. /// /// This sets the raw bytes of the comment. The comment /// is typically expected to be encoded in UTF-8. pubfn set_raw_zip64_comment(&mutself, comment: Option<Box<[u8]>>) { self.zip64_comment = comment;
}
/// Get ZIP archive comment. /// /// This returns the raw bytes of the comment. The comment /// is typically expected to be encoded in UTF-8. pubfn get_raw_zip64_comment(&self) -> Option<&[u8]> { self.zip64_comment.as_deref()
}
/// Set the file length and crc32 manually. /// /// # Safety /// /// This overwrites the internal crc32 calculation. It should only be used in case /// the underlying [Write] is written independently and you need to adjust the zip metadata. pubunsafefn set_file_metadata(&mutself, length: u64, crc32: u32) -> ZipResult<()> { if !self.writing_to_file { return Err(ZipError::Io(io::Error::new(
io::ErrorKind::Other, "No file has been started",
)));
} self.stats.hasher = Hasher::new_with_initial_len(crc32, length); self.stats.bytes_written = length;
Ok(())
}
fn ok_or_abort_file<T, E: Into<ZipError>>(&mutself, result: Result<T, E>) -> ZipResult<T> { match result {
Err(e) => { let _ = self.abort_file();
Err(e.into())
}
Ok(t) => Ok(t),
}
}
/// Start a new file for with the requested options. fn start_entry<S: ToString, T: FileOptionExtension>(
&mutself,
name: S,
options: FileOptions<T>,
raw_values: Option<ZipRawValues>,
) -> ZipResult<()> { self.finish_file()?;
let header_start = self.inner.get_plain().stream_position()?; let raw_values = raw_values.unwrap_or(ZipRawValues {
crc32: 0,
compressed_size: 0,
uncompressed_size: 0,
});
let make_plain_writer = self.inner.prepare_next_writer(
Stored,
None, #[cfg(feature = "deflate-zopfli")]
None,
)?; self.inner.switch_to(make_plain_writer)?; self.switch_to_non_encrypting_writer()?; let writer = self.inner.get_plain();
if !self.writing_raw { let file = matchself.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; letmut crc = true; iflet Some(aes_mode) = &mut file.aes_mode { // We prefer using AE-1 which provides an extra CRC check, but for small files we // switch to AE-2 to prevent being able to use the CRC value to to reconstruct the // unencrypted contents. // // C.f. https://www.winzip.com/en/support/aes-encryption/#crc-faq
aes_mode.1 = ifself.stats.bytes_written < 20 {
crc = false;
AesVendorVersion::Ae2
} else {
AesVendorVersion::Ae1
};
}
file.crc32 = if crc { self.stats.hasher.clone().finalize()
} else { 0
};
update_aes_extra_data(writer, file)?;
update_local_file_header(writer, file)?;
writer.seek(SeekFrom::Start(file_end))?;
} ifself.flush_on_finish_file { let result = writer.flush(); self.ok_or_abort_file(result)?;
}
/// Removes the file currently being written from the archive if there is one, or else removes /// the file most recently written. pubfn abort_file(&mutself) -> 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()?; // Make sure this is the last file, and that no shallow copies of it remain; otherwise we'd // overwrite a valid file and corrupt the archive let rewind_safe: bool = match last_file.data_start.get() {
None => self.files.is_empty(),
Some(last_file_start) => self.files.values().all(|file| {
file.data_start
.get()
.is_some_and(|start| start < last_file_start)
}),
}; if rewind_safe { self.inner
.get_plain()
.seek(SeekFrom::Start(last_file.header_start))?;
} self.writing_to_file = false;
Ok(())
}
/// Create a file in the archive and start writing its' contents. The file must not have the /// same name as a file already in the archive. /// /// The data should be written using the [`Write`] implementation on this [`ZipWriter`] pubfn start_file<S: ToString, T: FileOptionExtension>(
&mutself,
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(())
}
/* TODO: link to/use Self::finish_into_readable() from https://github.com/zip-rs/zip/pull/400 in
* this docstring. */ /// Copy over the entire contents of another archive verbatim. /// /// This method extracts file metadata from the `source` archive, then simply performs a single /// big [`io::copy()`](io::copy) to transfer all the actual file contents without any /// decompression or decryption. This is more performant than the equivalent operation of /// calling [`Self::raw_copy_file()`] for each entry from the `source` archive in sequence. /// ///``` /// # fn main() -> Result<(), zip::result::ZipError> { /// use std::io::{Cursor, prelude::*}; /// use zip::{ZipArchive, ZipWriter, write::SimpleFileOptions}; /// /// let buf = Cursor::new(Vec::new()); /// let mut zip = ZipWriter::new(buf); /// zip.start_file("a.txt", SimpleFileOptions::default())?; /// zip.write_all(b"hello\n")?; /// let src = ZipArchive::new(zip.finish()?)?; /// /// let buf = Cursor::new(Vec::new()); /// let mut zip = ZipWriter::new(buf); /// zip.start_file("b.txt", SimpleFileOptions::default())?; /// zip.write_all(b"hey\n")?; /// let src2 = ZipArchive::new(zip.finish()?)?; /// /// let buf = Cursor::new(Vec::new()); /// let mut zip = ZipWriter::new(buf); /// zip.merge_archive(src)?; /// zip.merge_archive(src2)?; /// let mut result = ZipArchive::new(zip.finish()?)?; /// /// let mut s: String = String::new(); /// result.by_name("a.txt")?.read_to_string(&mut s)?; /// assert_eq!(s, "hello\n"); /// s.clear(); /// result.by_name("b.txt")?.read_to_string(&mut s)?; /// assert_eq!(s, "hey\n"); /// # Ok(()) /// # } ///``` pubfn merge_archive<R>(&mutself, mut source: ZipArchive<R>) -> ZipResult<()> where
R: Read + Seek,
{ self.finish_file()?;
/* Ensure we accept the file contents on faith (and avoid overwriting the data).
* See raw_copy_file_rename(). */ self.writing_to_file = true; self.writing_raw = true;
let writer = self.inner.get_plain(); /* Get the file entries from the source archive. */ let new_files = source.merge_contents(writer)?;
/* These file entries are now ours! */ self.files.extend(new_files);
Ok(())
}
/// Starts a file, taking a Path as argument. /// /// This function ensures that the '/' path separator is used and normalizes `.` and `..`. It /// ignores any `..` or Windows drive letter that would produce a path outside the ZIP file's /// root. pubfn start_file_from_path<E: FileOptionExtension, P: AsRef<Path>>(
&mutself,
path: P,
options: FileOptions<E>,
) -> ZipResult<()> { self.start_file(path_to_string(path), options)
}
/// Add a new file using the already compressed data from a ZIP file being read and renames it, this /// allows faster copies of the `ZipFile` since there is no need to decompress and compress it again. /// Any `ZipFile` metadata is copied and not checked, for example the file CRC. /// /// ```no_run /// use std::fs::File; /// use std::io::{Read, Seek, Write}; /// use zip::{ZipArchive, ZipWriter}; /// /// fn copy_rename<R, W>( /// src: &mut ZipArchive<R>, /// dst: &mut ZipWriter<W>, /// ) -> zip::result::ZipResult<()> /// where /// R: Read + Seek, /// W: Write + Seek, /// { /// // Retrieve file entry by name /// let file = src.by_name("src_file.txt")?; /// /// // Copy and rename the previously obtained file entry to the destination zip archive /// dst.raw_copy_file_rename(file, "new_name.txt")?; /// /// Ok(()) /// } /// ``` pubfn raw_copy_file_rename<S: ToString>(&mutself, file: ZipFile, name: S) -> ZipResult<()> { let options = file.options(); self.raw_copy_file_rename_internal(file, name, options)
}
/// Like `raw_copy_file_to_path`, but uses Path arguments. /// /// This function ensures that the '/' path separator is used and normalizes `.` and `..`. It /// ignores any `..` or Windows drive letter that would produce a path outside the ZIP file's /// root. pubfn raw_copy_file_to_path<P: AsRef<Path>>(
&mutself,
file: ZipFile,
path: P,
) -> ZipResult<()> { self.raw_copy_file_rename(file, path_to_string(path))
}
/// Add a new file using the already compressed data from a ZIP file being read, this allows faster /// copies of the `ZipFile` since there is no need to decompress and compress it again. Any `ZipFile` /// metadata is copied and not checked, for example the file CRC. /// /// ```no_run /// use std::fs::File; /// use std::io::{Read, Seek, Write}; /// use zip::{ZipArchive, ZipWriter}; /// /// fn copy<R, W>(src: &mut ZipArchive<R>, dst: &mut ZipWriter<W>) -> zip::result::ZipResult<()> /// where /// R: Read + Seek, /// W: Write + Seek, /// { /// // Retrieve file entry by name /// let file = src.by_name("src_file.txt")?; /// /// // Copy the previously obtained file entry to the destination zip archive /// dst.raw_copy_file(file)?; /// /// Ok(()) /// } /// ``` pubfn raw_copy_file(&mutself, file: ZipFile) -> ZipResult<()> { let name = file.name().to_owned(); self.raw_copy_file_rename(file, name)
}
/// Add a new file using the already compressed data from a ZIP file being read and set the last /// modified date and unix mode. This allows faster copies of the `ZipFile` since there is no need /// to decompress and compress it again. Any `ZipFile` metadata other than the last modified date /// and the unix mode is copied and not checked, for example the file CRC. /// /// ```no_run /// use std::io::{Read, Seek, Write}; /// use zip::{DateTime, ZipArchive, ZipWriter}; /// /// fn copy<R, W>(src: &mut ZipArchive<R>, dst: &mut ZipWriter<W>) -> zip::result::ZipResult<()> /// where /// R: Read + Seek, /// W: Write + Seek, /// { /// // Retrieve file entry by name /// let file = src.by_name("src_file.txt")?; /// /// // Copy the previously obtained file entry to the destination zip archive /// dst.raw_copy_file_touch(file, DateTime::default(), Some(0o644))?; /// /// Ok(()) /// } /// ``` pubfn raw_copy_file_touch(
&mutself,
file: ZipFile,
last_modified_time: DateTime,
unix_mode: Option<u32>,
) -> ZipResult<()> { let name = file.name().to_owned();
/// Add a directory entry. /// /// As directories have no content, you must not call [`ZipWriter::write`] before adding a new file. pubfn add_directory<S, T: FileOptionExtension>(
&mutself,
name: S, mut options: FileOptions<T>,
) -> ZipResult<()> where
S: Into<String>,
{ if options.permissions.is_none() {
options.permissions = Some(0o755);
}
*options.permissions.as_mut().unwrap() |= 0o40000;
options.compression_method = Stored;
options.encrypt_with = None;
let name_as_string = name.into(); // Append a slash to the filename if it does not end with it. let name_with_slash = match name_as_string.chars().last() {
Some('/') | Some('\\') => name_as_string,
_ => name_as_string + "/",
};
/// Add a directory entry, taking a Path as argument. /// /// This function ensures that the '/' path separator is used and normalizes `.` and `..`. It /// ignores any `..` or Windows drive letter that would produce a path outside the ZIP file's /// root. pubfn add_directory_from_path<T: FileOptionExtension, P: AsRef<Path>>(
&mutself,
path: P,
options: FileOptions<T>,
) -> ZipResult<()> { self.add_directory(path_to_string(path), options)
}
/// Finish the last file and write all other zip-structures /// /// This will return the writer, but one should normally not append any data to the end of the file. /// Note that the zipfile will also be finished on drop. pubfn finish(mutself) -> ZipResult<W> { let _central_start = self.finalize()?; let inner = mem::replace(&mutself.inner, Closed);
Ok(inner.unwrap())
}
/// Add a symlink entry. /// /// The zip archive will contain an entry for path `name` which is a symlink to `target`. /// /// No validation or normalization of the paths is performed. For best results, /// callers should normalize `\` to `/` and ensure symlinks are relative to other /// paths within the zip archive. /// /// WARNING: not all zip implementations preserve symlinks on extract. Some zip /// implementations may materialize a symlink as a regular file, possibly with the /// content incorrectly set to the symlink target. For maximum portability, consider /// storing a regular file instead. pubfn add_symlink<N: ToString, T: ToString, E: FileOptionExtension>(
&mutself,
name: N,
target: T, mut options: FileOptions<E>,
) -> ZipResult<()> { if options.permissions.is_none() {
options.permissions = Some(0o777);
}
*options.permissions.as_mut().unwrap() |= S_IFLNK; // The symlink target is stored as file content. And compressing the target path // likely wastes space. So always store.
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(())
}
/// Add a symlink entry, taking Paths to the location and target as arguments. /// /// This function ensures that the '/' path separator is used and normalizes `.` and `..`. It /// ignores any `..` or Windows drive letter that would produce a path outside the ZIP file's /// root. pubfn add_symlink_from_path<P: AsRef<Path>, T: AsRef<Path>, E: FileOptionExtension>(
&mutself,
path: P,
target: T,
options: FileOptions<E>,
) -> ZipResult<()> { self.add_symlink(path_to_string(path), path_to_string(target), options)
}
letmut central_start = self.write_central_and_footer()?; let writer = self.inner.get_plain(); let footer_end = writer.stream_position()?; let archive_end = writer.seek(SeekFrom::End(0))?; if footer_end < archive_end { // Data from an aborted file is past the end of the footer.
// Overwrite the magic so the footer is no longer valid.
writer.seek(SeekFrom::Start(central_start))?;
writer.write_u32_le(0)?;
writer.seek(SeekFrom::Start(
footer_end - size_of::<Zip32CDEBlock>() as u64 - self.comment.len() as u64,
))?;
writer.write_u32_le(0)?;
// Rewrite the footer at the actual end. let central_and_footer_size = footer_end - central_start;
writer.seek(SeekFrom::End(-(central_and_footer_size as i64)))?;
central_start = self.write_central_and_footer()?;
debug_assert!(self.inner.get_plain().stream_position()? == archive_end);
}
Ok(central_start)
}
fn write_central_and_footer(&mutself) -> Result<u64, ZipError> { let writer = self.inner.get_plain();
letmut version_needed = MIN_VERSION as u16; let central_start = writer.stream_position()?; for file inself.files.values() {
write_central_directory_header(writer, file)?;
version_needed = version_needed.max(file.version_needed());
} let central_size = writer.stream_position()? - central_start; let is64 = self.files.len() > spec::ZIP64_ENTRY_THR
|| central_size.max(central_start) > spec::ZIP64_BYTES_THR
|| self.zip64_comment.is_some();
if is64 { let comment = self.zip64_comment.clone().unwrap_or_default();
let zip64_footer = spec::Zip64CentralDirectoryEnd {
record_size: comment.len() as u64 + 44,
version_made_by: version_needed,
version_needed_to_extract: version_needed,
disk_number: 0,
disk_with_central_directory: 0,
number_of_files_on_this_disk: self.files.len() as u64,
number_of_files: self.files.len() as u64,
central_directory_size: central_size,
central_directory_offset: central_start,
extensible_data_sector: comment,
};
/// Adds another entry to the central directory referring to the same content as an existing /// entry. The file's local-file header will still refer to it by its original name, so /// unzipping the file will technically be unspecified behavior. [ZipArchive] ignores the /// filename in the local-file header and treat the central directory as authoritative. However, /// some other software (e.g. Minecraft) will refuse to extract a file copied this way. pubfn shallow_copy_file(&mutself, src_name: &str, dest_name: &str) -> ZipResult<()> { self.finish_file()?; if src_name == dest_name { return Err(InvalidArchive("Trying to copy a file to itself"));
} let src_index = self.index_by_name(src_name)?; letmut dest_data = self.files[src_index].to_owned();
dest_data.file_name = dest_name.to_string().into();
dest_data.file_name_raw = dest_name.to_string().into_bytes().into();
dest_data.central_header_start = 0; self.insert_file_data(dest_data)?;
Ok(())
}
/// Like `shallow_copy_file`, but uses Path arguments. /// /// This function ensures that the '/' path separator is used and normalizes `.` and `..`. It /// ignores any `..` or Windows drive letter that would produce a path outside the ZIP file's /// root. pubfn shallow_copy_file_from_path<T: AsRef<Path>, U: AsRef<Path>>(
&mutself,
src_path: T,
dest_path: U,
) -> ZipResult<()> { self.shallow_copy_file(&path_to_string(src_path), &path_to_string(dest_path))
}
}
impl<W: Write + Seek> Drop for ZipWriter<W> { fn drop(&mutself) { if !self.inner.is_closed() { iflet Err(e) = self.finalize() { let _ = write!(io::stderr(), "ZipWriter drop failed: {:?}", e);
}
}
}
}
type SwitchWriterFunction<W> = Box<dyn FnOnce(MaybeEncrypted<W>) -> GenericZipWriter<W>>;
fn get_plain(&mutself) -> &mut W { match *self {
Storer(MaybeEncrypted::Unencrypted(refmut w)) => w,
_ => panic!("Should have switched to stored and unencrypted beforehand"),
}
}
fn unwrap(self) -> W { matchself {
Storer(MaybeEncrypted::Unencrypted(w)) => w,
_ => panic!("Should have switched to stored and unencrypted beforehand"),
}
}
}
#[cfg(feature = "_deflate-any")] fn deflate_compression_level_range() -> std::ops::RangeInclusive<i64> { let min = if cfg!(feature = "deflate-flate2") {
Compression::fast().level() as i64
} else {
Compression::best().level() as i64 + 1
};
let max = Compression::best().level() as i64
+ if cfg!(feature = "deflate-zopfli") {
u8::MAX as i64
} else { 0
};
min..=max
}
#[cfg(feature = "bzip2")] fn bzip2_compression_level_range() -> std::ops::RangeInclusive<i64> { let min = bzip2::Compression::fast().level() as i64; let max = bzip2::Compression::best().level() as i64;
min..=max
}
/* TODO: implement this using the Block trait! */ // Extra field header ID.
buf.write_u16_le(0x9901)?; // Data size.
buf.write_u16_le(7)?; // Integer version number.
buf.write_u16_le(version as u16)?; // Vendor ID.
buf.write_all(b"AE")?; // AES encryption strength.
buf.write_all(&[aes_mode as u8])?; // Real compression method.
buf.write_u16_le(compression_method.serialize_to_u16())?;
writer.write_all(&buf)?;
let aes_extra_data_start = file.aes_extra_data_start as usize; let extra_field = Arc::get_mut(file.extra_field.as_mut().unwrap()).unwrap();
extra_field[aes_extra_data_start..aes_extra_data_start + buf.len()].copy_from_slice(&buf);
file.compressed_size = spec::ZIP64_BYTES_THR;
file.uncompressed_size = spec::ZIP64_BYTES_THR;
} else { // check compressed size as well as it can also be slightly larger than uncompressed size if file.compressed_size > spec::ZIP64_BYTES_THR { return Err(ZipError::Io(io::Error::new(
io::ErrorKind::Other, "Large file option has not been set",
)));
}
writer.write_u32_le(file.compressed_size as u32)?; // uncompressed size is already checked on write to catch it as soon as possible
writer.write_u32_le(file.uncompressed_size as u32)?;
}
Ok(())
}
fn write_central_directory_header<T: Write>(writer: &mut T, file: &ZipFileData) -> ZipResult<()> { let block = file.block()?;
block.write(writer)?; // file name
writer.write_all(&file.file_name_raw)?; // extra field iflet Some(extra_field) = &file.extra_field {
writer.write_all(extra_field)?;
} iflet Some(central_extra_field) = &file.central_extra_field {
writer.write_all(central_extra_field)?;
} // file comment
writer.write_all(file.file_comment.as_bytes())?;
Ok(())
}
fn update_local_zip64_extra_field<T: Write + Seek>(
writer: &mut T,
file: &mut ZipFileData,
) -> ZipResult<()> { let block = file.zip64_extra_field_block().ok_or(InvalidArchive( "Attempted to update a nonexistent ZIP64 extra field",
))?;
let zip64_extra_field_start = file.header_start
+ size_of::<ZipLocalEntryBlock>() as u64
+ file.file_name_raw.len() as u64;
writer.seek(SeekFrom::Start(zip64_extra_field_start))?; let block = block.serialize();
writer.write_all(&block)?;
let extra_field = Arc::get_mut(file.extra_field.as_mut().unwrap()).unwrap();
extra_field[..block.len()].copy_from_slice(&block);
#[cfg(test)] #[allow(unknown_lints)] // needless_update is new in clippy pre 1.29.0 #[allow(clippy::needless_update)] // So we can use the same FileOptions decls with and without zopfli_buffer_size #[allow(clippy::octal_escapes)] // many false positives in converted fuzz cases mod test { usesuper::{ExtendedFileOptions, FileOptions, FullFileOptions, ZipWriter}; usecrate::compression::CompressionMethod; usecrate::result::ZipResult; usecrate::types::DateTime; usecrate::write::EncryptWith::ZipCrypto; usecrate::write::SimpleFileOptions; usecrate::zipcrypto::ZipCryptoKeys; usecrate::CompressionMethod::Stored; usecrate::ZipArchive; use std::io::{Cursor, Read, Write}; use std::marker::PhantomData; use std::path::PathBuf;
assert_eq!(result.get_ref().len(), 153); letmut v = Vec::new();
v.extend_from_slice(include_bytes!("../tests/data/mimetype.zip"));
assert_eq!(result.get_ref(), &v);
}
const RT_TEST_TEXT: &str = "And I can't stop thinking about the moments that I lost to you\
And I can't stop thinking of things I used to do\
And I can't stop making bad decisions\
And I can't stop eating stuff you make me chew\
I put on a smile like you wanna see\
Another day goes by that I long to be like you"; const RT_TEST_FILENAME: &str = "subfolder/sub-subfolder/can't_stop.txt"; const SECOND_FILENAME: &str = "different_name.xyz"; const THIRD_FILENAME: &str = "third_name.xyz";
// There was not enough underlying data to fulfill some request for raw
[fa-"java.lang.StringIndexOutOfBoundsException: Index 34 out of bounds for length 34 fn fuzz_crash_2024_07_19a() -> ZipResult<()> { usecrate::write::java.lang.StringIndexOutOfBoundsException: Index 31 out of bounds for length 3 usecrate:AesMode:Aes128;
java.lang.StringIndexOutOfBoundsException: Range [39, 8) out of bounds for length 65
writer.set_flush_on_finish_file(false); let options = FileOptions {
compression_method: Stored,
compression_level: None,
last_modified_time: DateTime::from_date_and_time(2107, 6, 5, 13, 0, 21)?,
permissions: None,
large_file: true,
encrypt_with: Some(Aes {
mode: Aes128,
password: "",
}),
extended_options: ExtendedFileOptions {
extra_data: vec![3, 0, 4, 0, 209, 53, 53, 8, 2, 61, 0, 0].into(),
central_extra_data: vec![].into(),
},
alignment: 65535,
..Default::default()
};
writer.start_file_from_path("", options)?; let _ = ZipWriter::new_append(writer.finish_into_readable()?.into_inner())?;
Ok(())
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.