use crate::{
crc,
errors::ErrorKind,
extra_fields::{ExtraFieldId, ExtraFieldsContainer},
mode::CREATOR_UNIX,
path::{NormalizedPath, ZipFilePath},
time::{DosDateTime, UtcDateTime},
CompressionMethod, DataDescriptor, Error, Header, ZipFileHeaderFixed, ZipLocalFileHeaderFixed,
CENTRAL_HEADER_SIGNATURE, END_OF_CENTRAL_DIR_LOCATOR_SIGNATURE, END_OF_CENTRAL_DIR_SIGNATURE64,
END_OF_CENTRAL_DIR_SIGNAUTRE_BYTES,
};
use std::io::{self, Write};
const ZIP64_VERSION_NEEDED: u16 = 45; const ZIP64_EOCD_SIZE: usize = 56;
const FLAG_DATA_DESCRIPTOR: u16 = 0x08; const FLAG_UTF8_ENCODING: u16 = 0x800;
const ZIP64_THRESHOLD_FILE_SIZE: u64 = u32::MAX as u64;
const ZIP64_THRESHOLD_OFFSET: u64 = u32::MAX as u64;
const ZIP64_THRESHOLD_ENTRIES: usize = u16::MAX as usize;
#[derive(Debug)]
struct CountWriter<W> {
writer: W,
count: u64,
}
impl<W> CountWriter<W> {
fn new(writer: W, count: u64) -> Self {
CountWriter { writer, count }
}
fn count(&self) -> u64 {
self.count
}
}
impl<W: Write> Write for CountWriter<W> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let bytes_written = self.writer.write(buf)?;
self.count += bytes_written as u64;
Ok(bytes_written)
}
fn flush(&mut self) -> io::Result<()> {
self.writer.flush()
}
}
#[derive(Debug, Default)]
pub struct ZipArchiveWriterBuilder {
count: u64,
capacity: usize,
}
impl ZipArchiveWriterBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn with_capacity(mut self, capacity: usize) -> Self {
self.capacity = capacity;
self
}
pub fn with_offset(mut self, offset: u64) -> Self {
self.count = offset;
self
}
pub fn build<W>(&self, writer: W) -> ZipArchiveWriter<W> {
ZipArchiveWriter {
writer: CountWriter::new(writer, self.count),
files: Vec::with_capacity(self.capacity),
file_names: Vec::new(),
}
}
}
#[derive(Debug)]
pub struct ZipArchiveWriter<W> {
files: Vec<FileHeader>,
file_names: Vec<u8>,
writer: CountWriter<W>,
}
impl ZipArchiveWriter<()> {
pub fn builder() -> ZipArchiveWriterBuilder {
ZipArchiveWriterBuilder::new()
}
}
impl<W> ZipArchiveWriter<W> {
pub fn new(writer: W) -> Self {
ZipArchiveWriterBuilder::new().build(writer)
}
pub fn stream_offset(&self) -> u64 {
self.writer.count()
}
}
#[derive(Debug, Clone, Copy, Default)]
pub enum Crc32Option {
#[default]
Calculate,
Custom(u32),
Skip,
}
impl Crc32Option {
#[inline]
pub fn initial_value(&self) -> u32 {
match self {
Crc32Option::Calculate => 0,
Crc32Option::Custom(value) => *value,
Crc32Option::Skip => 0,
}
}
}
#[derive(Debug)]
pub struct ZipFileBuilder<'archive, 'name, W> {
archive: &'archive mut ZipArchiveWriter<W>,
name: &'name str,
compression_method: CompressionMethod,
modification_time: Option<UtcDateTime>,
unix_permissions: Option<u32>,
extra_fields: ExtraFieldsContainer,
crc32_option: Crc32Option,
}
impl<'archive, W> ZipFileBuilder<'archive, '_, W>
where
W: Write,
{
#[must_use]
#[inline]
pub fn compression_method(mut self, compression_method: CompressionMethod) -> Self {
self.compression_method = compression_method;
self
}
#[must_use]
#[inline]
pub fn last_modified(mut self, modification_time: UtcDateTime) -> Self {
self.modification_time = Some(modification_time);
self
}
#[must_use]
#[inline]
pub fn unix_permissions(mut self, permissions: u32) -> Self {
self.unix_permissions = Some(permissions);
self
}
pub fn extra_field(
mut self,
id: ExtraFieldId,
data: &[u8],
location: Header,
) -> Result<Self, Error> {
self.extra_fields.add_field(id, data, location)?;
Ok(self)
}
#[must_use]
#[inline]
pub fn crc32(mut self, crc32_option: Crc32Option) -> Self {
self.crc32_option = crc32_option;
self
}
#[deprecated(
since = "0.4.0",
note = "Use `start()` method instead as it allows for more flexibility (ie: CRC configuration)"
)]
pub fn create(self) -> Result<ZipEntryWriter<'archive, W>, Error> {
let (entry_writer, _) = self.start()?;
Ok(entry_writer)
}
pub fn start(self) -> Result<(ZipEntryWriter<'archive, W>, ZipDataWriterConfig), Error> {
let crc32_option = self.crc32_option;
let options = ZipEntryOptions {
compression_method: self.compression_method,
modification_time: self.modification_time,
unix_permissions: self.unix_permissions,
extra_fields: self.extra_fields,
};
let entry_writer = self.archive.new_file_with_options(self.name, options)?;
let data_writer_config = ZipDataWriterConfig { crc32_option };
Ok((entry_writer, data_writer_config))
}
}
#[derive(Debug)]
pub struct ZipDirBuilder<'a, W> {
archive: &'a mut ZipArchiveWriter<W>,
name: &'a str,
modification_time: Option<UtcDateTime>,
unix_permissions: Option<u32>,
extra_fields: ExtraFieldsContainer,
}
impl<W> ZipDirBuilder<'_, W>
where
W: Write,
{
#[must_use]
#[inline]
pub fn last_modified(mut self, modification_time: UtcDateTime) -> Self {
self.modification_time = Some(modification_time);
self
}
#[must_use]
#[inline]
pub fn unix_permissions(mut self, permissions: u32) -> Self {
self.unix_permissions = Some(permissions);
self
}
pub fn extra_field(
mut self,
id: ExtraFieldId,
data: &[u8],
location: Header,
) -> Result<Self, Error> {
self.extra_fields.add_field(id, data, location)?;
Ok(self)
}
pub fn create(self) -> Result<(), Error> {
let options = ZipEntryOptions {
compression_method: CompressionMethod::Store, modification_time: self.modification_time,
unix_permissions: self.unix_permissions,
extra_fields: self.extra_fields,
};
self.archive.new_dir_with_options(self.name, options)
}
}
impl<W> ZipArchiveWriter<W>
where
W: Write,
{
fn write_local_header(
&mut self,
file_path: &ZipFilePath<NormalizedPath>,
flags: u16,
compression_method: CompressionMethod,
options: &mut ZipEntryOptions,
) -> Result<(), Error> {
let (dos_time, dos_date) = options
.modification_time
.as_ref()
.map(|dt| DosDateTime::from(dt).into_parts())
.unwrap_or((0, 0));
if let Some(datetime) = options.modification_time.as_ref() {
let unix_time = datetime.to_unix().max(0) as u32;
let mut data = [0u8; 5];
data[0] = 1; data[1..].copy_from_slice(&unix_time.to_le_bytes());
options.extra_fields.add_field(
ExtraFieldId::EXTENDED_TIMESTAMP,
&data,
Header::CENTRAL,
)?;
}
let header = ZipLocalFileHeaderFixed {
signature: ZipLocalFileHeaderFixed::SIGNATURE,
version_needed: 20,
flags,
compression_method: compression_method.as_id(),
last_mod_time: dos_time,
last_mod_date: dos_date,
crc32: 0, compressed_size: 0,
uncompressed_size: 0,
file_name_len: file_path.len() as u16,
extra_field_len: options.extra_fields.local_size,
};
header.write(&mut self.writer)?;
self.writer.write_all(file_path.as_ref().as_bytes())?;
options
.extra_fields
.write_extra_fields(&mut self.writer, Header::LOCAL)?;
Ok(())
}
#[must_use]
pub fn new_dir<'a>(&'a mut self, name: &'a str) -> ZipDirBuilder<'a, W> {
ZipDirBuilder {
archive: self,
name,
modification_time: None,
unix_permissions: None,
extra_fields: ExtraFieldsContainer::new(),
}
}
fn new_dir_with_options(
&mut self,
name: &str,
mut options: ZipEntryOptions,
) -> Result<(), Error> {
let file_path = ZipFilePath::from_str(name);
if !file_path.is_dir() {
return Err(Error::from(ErrorKind::InvalidInput {
msg: "not a directory".to_string(),
}));
}
if file_path.len() > u16::MAX as usize {
return Err(Error::from(ErrorKind::InvalidInput {
msg: "directory name too long".to_string(),
}));
}
let local_header_offset = self.writer.count();
let mut flags = 0u16;
if file_path.needs_utf8_encoding() {
flags |= FLAG_UTF8_ENCODING;
} else {
flags &= !FLAG_UTF8_ENCODING;
}
let name_bytes = file_path.as_ref().as_bytes();
let name_len = name_bytes.len() as u16;
self.file_names.extend_from_slice(name_bytes);
self.write_local_header(&file_path, flags, CompressionMethod::Store, &mut options)?;
let file_header = FileHeader {
name_len,
compression_method: CompressionMethod::Store,
local_header_offset,
compressed_size: 0,
uncompressed_size: 0,
crc: 0,
flags,
modification_time: options.modification_time,
unix_permissions: options.unix_permissions,
extra_fields: options.extra_fields,
};
self.files.push(file_header);
Ok(())
}
#[must_use]
pub fn new_file<'name>(&mut self, name: &'name str) -> ZipFileBuilder<'_, 'name, W> {
ZipFileBuilder {
archive: self,
name,
compression_method: CompressionMethod::Store,
modification_time: None,
unix_permissions: None,
extra_fields: ExtraFieldsContainer::new(),
crc32_option: Crc32Option::default(),
}
}
fn new_file_with_options(
&mut self,
name: &str,
mut options: ZipEntryOptions,
) -> Result<ZipEntryWriter<'_, W>, Error> {
let file_path = ZipFilePath::from_str(name.trim_end_matches('/'));
if file_path.len() > u16::MAX as usize {
return Err(Error::from(ErrorKind::InvalidInput {
msg: "file name too long".to_string(),
}));
}
let local_header_offset = self.writer.count();
let mut flags = FLAG_DATA_DESCRIPTOR;
if file_path.needs_utf8_encoding() {
flags |= FLAG_UTF8_ENCODING;
} else {
flags &= !FLAG_UTF8_ENCODING;
}
let name_bytes = file_path.as_ref().as_bytes();
let name_len = name_bytes.len() as u16;
self.file_names.extend_from_slice(name_bytes);
self.write_local_header(&file_path, flags, options.compression_method, &mut options)?;
Ok(ZipEntryWriter {
inner: self,
compressed_bytes: 0,
name_len,
local_header_offset,
compression_method: options.compression_method,
flags,
modification_time: options.modification_time,
unix_permissions: options.unix_permissions,
extra_fields: options.extra_fields,
})
}
pub fn finish(mut self) -> Result<W, Error>
where
W: Write,
{
let central_directory_offset = self.writer.count();
let total_entries = self.files.len();
let needs_zip64 = total_entries >= ZIP64_THRESHOLD_ENTRIES
|| central_directory_offset >= ZIP64_THRESHOLD_OFFSET
|| self.files.iter().any(|f| f.needs_zip64());
let mut name_offset = 0;
for file in &self.files {
let version_needed = if file.needs_zip64() {
ZIP64_VERSION_NEEDED
} else {
20
};
let version_made_by_hi = file.unix_permissions.map(|_| CREATOR_UNIX).unwrap_or(0);
let version_made_by = (version_made_by_hi << 8) | version_needed;
let (dos_time, dos_date) = file
.modification_time
.as_ref()
.map(|dt| DosDateTime::from(dt).into_parts())
.unwrap_or((0, 0));
let header = ZipFileHeaderFixed {
signature: CENTRAL_HEADER_SIGNATURE,
version_made_by,
version_needed,
flags: file.flags,
compression_method: file.compression_method.as_id(),
last_mod_time: dos_time,
last_mod_date: dos_date,
crc32: file.crc,
compressed_size: file.compressed_size.min(ZIP64_THRESHOLD_FILE_SIZE) as u32,
uncompressed_size: file.uncompressed_size.min(ZIP64_THRESHOLD_FILE_SIZE) as u32,
file_name_len: file.name_len,
extra_field_len: file.extra_fields.central_size,
file_comment_len: 0,
disk_number_start: 0,
internal_file_attrs: 0,
external_file_attrs: file.unix_permissions.map(|x| x << 16).unwrap_or(0),
local_header_offset: file.local_header_offset.min(ZIP64_THRESHOLD_OFFSET) as u32,
};
header.write(&mut self.writer)?;
let new_name_offset = name_offset + file.name_len as usize;
self.writer
.write_all(&self.file_names[name_offset..new_name_offset])?;
name_offset = new_name_offset;
file.extra_fields
.write_extra_fields(&mut self.writer, Header::CENTRAL)?;
}
let central_directory_end = self.writer.count();
let central_directory_size = central_directory_end - central_directory_offset;
if needs_zip64 {
let zip64_eocd_offset = self.writer.count();
write_zip64_eocd(
&mut self.writer,
total_entries as u64,
central_directory_size,
central_directory_offset,
)?;
write_zip64_eocd_locator(&mut self.writer, zip64_eocd_offset)?;
}
self.writer.write_all(&END_OF_CENTRAL_DIR_SIGNAUTRE_BYTES)?;
self.writer.write_all(&[0u8; 4])?;
let entries_count = total_entries.min(ZIP64_THRESHOLD_ENTRIES) as u16;
self.writer.write_all(&entries_count.to_le_bytes())?;
self.writer.write_all(&entries_count.to_le_bytes())?;
let cd_size = central_directory_size.min(ZIP64_THRESHOLD_OFFSET) as u32;
self.writer.write_all(&cd_size.to_le_bytes())?;
let cd_offset = central_directory_offset.min(ZIP64_THRESHOLD_OFFSET) as u32;
self.writer.write_all(&cd_offset.to_le_bytes())?;
self.writer.write_all(&0u16.to_le_bytes())?;
self.writer.flush()?;
Ok(self.writer.writer)
}
}
#[derive(Debug)]
pub struct ZipEntryWriter<'a, W> {
inner: &'a mut ZipArchiveWriter<W>,
compressed_bytes: u64,
name_len: u16,
local_header_offset: u64,
compression_method: CompressionMethod,
flags: u16,
modification_time: Option<UtcDateTime>,
unix_permissions: Option<u32>,
extra_fields: ExtraFieldsContainer,
}
#[derive(Debug)]
pub struct ZipDataWriterConfig {
crc32_option: Crc32Option,
}
impl ZipDataWriterConfig {
pub fn wrap<E>(self, encoder: E) -> ZipDataWriter<E> {
ZipDataWriter::with_crc32(encoder, self.crc32_option)
}
}
impl<'a, W> ZipEntryWriter<'a, W> {
pub fn compressed_bytes(&self) -> u64 {
self.compressed_bytes
}
pub fn stream_offset(&self) -> u64 {
self.inner.stream_offset()
}
pub fn finish(self, mut output: DataDescriptorOutput) -> Result<u64, Error>
where
W: Write,
{
output.compressed_size = self.compressed_bytes;
let mut buffer = [0u8; 24];
buffer[0..4].copy_from_slice(&DataDescriptor::SIGNATURE.to_le_bytes());
buffer[4..8].copy_from_slice(&output.crc.to_le_bytes());
let out_data = if output.compressed_size >= ZIP64_THRESHOLD_FILE_SIZE
|| output.uncompressed_size >= ZIP64_THRESHOLD_FILE_SIZE
{
buffer[8..16].copy_from_slice(&output.compressed_size.to_le_bytes());
buffer[16..24].copy_from_slice(&output.uncompressed_size.to_le_bytes());
&buffer[..]
} else {
buffer[8..12].copy_from_slice(&(output.compressed_size as u32).to_le_bytes());
buffer[12..16].copy_from_slice(&(output.uncompressed_size as u32).to_le_bytes());
&buffer[..16]
};
self.inner.writer.write_all(out_data)?;
let mut file_header = FileHeader {
name_len: self.name_len,
compression_method: self.compression_method,
local_header_offset: self.local_header_offset,
compressed_size: output.compressed_size,
uncompressed_size: output.uncompressed_size,
crc: output.crc,
flags: self.flags,
modification_time: self.modification_time,
unix_permissions: self.unix_permissions,
extra_fields: self.extra_fields,
};
file_header.finalize_extra_fields()?;
self.inner.files.push(file_header);
Ok(self.compressed_bytes)
}
}
impl<W> Write for ZipEntryWriter<'_, W>
where
W: Write,
{
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let bytes_written = self.inner.writer.write(buf)?;
self.compressed_bytes += bytes_written as u64;
Ok(bytes_written)
}
fn flush(&mut self) -> io::Result<()> {
self.inner.writer.flush()
}
}
#[derive(Debug)]
pub struct ZipDataWriter<W> {
inner: W,
uncompressed_bytes: u64,
crc: u32,
crc32_option: Crc32Option,
}
impl<W> ZipDataWriter<W> {
#[deprecated(
since = "0.4.0",
note = "Use the tuple-based API: `ZipFileBuilder::start()` returns `(writer, builder)` which can propagate the CRC32 option"
)]
pub fn new(inner: W) -> Self {
Self::with_crc32_option(inner, Crc32Option::default())
}
pub(crate) fn with_crc32(inner: W, crc32_option: Crc32Option) -> Self {
Self::with_crc32_option(inner, crc32_option)
}
fn with_crc32_option(inner: W, crc32_option: Crc32Option) -> Self {
let crc = crc32_option.initial_value();
ZipDataWriter {
inner,
uncompressed_bytes: 0,
crc,
crc32_option,
}
}
pub fn get_mut(&mut self) -> &mut W {
&mut self.inner
}
pub fn finish(mut self) -> Result<(W, DataDescriptorOutput), Error>
where
W: Write,
{
self.flush()?;
let output = DataDescriptorOutput {
crc: self.crc,
compressed_size: 0,
uncompressed_size: self.uncompressed_bytes,
};
Ok((self.inner, output))
}
}
impl<W> Write for ZipDataWriter<W>
where
W: Write,
{
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let bytes_written = self.inner.write(buf)?;
self.uncompressed_bytes += bytes_written as u64;
if matches!(self.crc32_option, Crc32Option::Calculate) {
self.crc = crc::crc32_chunk(&buf[..bytes_written], self.crc);
}
Ok(bytes_written)
}
fn flush(&mut self) -> io::Result<()> {
self.inner.flush()
}
}
#[derive(Debug, Clone)]
pub struct DataDescriptorOutput {
crc: u32,
compressed_size: u64,
uncompressed_size: u64,
}
impl DataDescriptorOutput {
pub fn crc(&self) -> u32 {
self.crc
}
pub fn uncompressed_size(&self) -> u64 {
self.uncompressed_size
}
}
#[derive(Debug)]
struct FileHeader {
name_len: u16,
compression_method: CompressionMethod,
local_header_offset: u64,
compressed_size: u64,
uncompressed_size: u64,
crc: u32,
flags: u16,
modification_time: Option<UtcDateTime>,
unix_permissions: Option<u32>,
extra_fields: ExtraFieldsContainer,
}
impl FileHeader {
fn needs_zip64(&self) -> bool {
self.compressed_size >= ZIP64_THRESHOLD_FILE_SIZE
|| self.uncompressed_size >= ZIP64_THRESHOLD_FILE_SIZE
|| self.local_header_offset >= ZIP64_THRESHOLD_OFFSET
}
fn finalize_extra_fields(&mut self) -> Result<(), Error> {
if self.needs_zip64() {
let mut sink = [0u8; 24];
let mut pos = 0;
if self.uncompressed_size >= ZIP64_THRESHOLD_FILE_SIZE {
sink[pos..pos + 8].copy_from_slice(&self.uncompressed_size.to_le_bytes());
pos += 8;
}
if self.compressed_size >= ZIP64_THRESHOLD_FILE_SIZE {
sink[pos..pos + 8].copy_from_slice(&self.compressed_size.to_le_bytes());
pos += 8;
}
if self.local_header_offset >= ZIP64_THRESHOLD_OFFSET {
sink[pos..pos + 8].copy_from_slice(&self.local_header_offset.to_le_bytes());
pos += 8;
}
self.extra_fields
.add_field(ExtraFieldId::ZIP64, &sink[..pos], Header::CENTRAL)?;
}
Ok(())
}
}
fn write_zip64_eocd<W>(
writer: &mut W,
total_entries: u64,
central_directory_size: u64,
central_directory_offset: u64,
) -> Result<(), Error>
where
W: Write,
{
writer.write_all(&END_OF_CENTRAL_DIR_SIGNATURE64.to_le_bytes())?;
let record_size = (ZIP64_EOCD_SIZE - 12) as u64;
writer.write_all(&record_size.to_le_bytes())?;
writer.write_all(&ZIP64_VERSION_NEEDED.to_le_bytes())?;
writer.write_all(&ZIP64_VERSION_NEEDED.to_le_bytes())?;
writer.write_all(&0u32.to_le_bytes())?;
writer.write_all(&0u32.to_le_bytes())?;
writer.write_all(&total_entries.to_le_bytes())?;
writer.write_all(&total_entries.to_le_bytes())?;
writer.write_all(¢ral_directory_size.to_le_bytes())?;
writer.write_all(¢ral_directory_offset.to_le_bytes())?;
Ok(())
}
fn write_zip64_eocd_locator<W>(writer: &mut W, zip64_eocd_offset: u64) -> Result<(), Error>
where
W: Write,
{
writer.write_all(&END_OF_CENTRAL_DIR_LOCATOR_SIGNATURE.to_le_bytes())?;
writer.write_all(&0u32.to_le_bytes())?;
writer.write_all(&zip64_eocd_offset.to_le_bytes())?;
writer.write_all(&1u32.to_le_bytes())?;
Ok(())
}
#[derive(Debug, Clone)]
struct ZipEntryOptions {
compression_method: CompressionMethod,
modification_time: Option<UtcDateTime>,
unix_permissions: Option<u32>,
extra_fields: ExtraFieldsContainer,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ZipArchive;
use std::io::Cursor;
#[test]
fn test_name_lifetime_independence() {
let mut output = Cursor::new(Vec::new());
let mut archive = ZipArchiveWriter::new(&mut output);
{
let (mut entry, config) = {
let temp_name = format!("temp-{}.txt", 42);
archive.new_file(&temp_name).start().unwrap()
};
let mut writer = config.wrap(&mut entry);
writer.write_all(b"test").unwrap();
let (_, desc) = writer.finish().unwrap();
entry.finish(desc).unwrap();
}
archive.finish().unwrap();
}
#[test]
fn test_builder_with_offset_and_capacity() {
let mut output = Cursor::new(Vec::new());
output.write_all(b"PREFIX DATA").unwrap();
let offset = output.position();
let mut archive = ZipArchiveWriterBuilder::new()
.with_capacity(5)
.with_offset(offset)
.build(&mut output);
let (mut entry, config) = archive.new_file("test.txt").start().unwrap();
let mut writer = config.wrap(&mut entry);
writer.write_all(b"Hello World").unwrap();
let (_, desc) = writer.finish().unwrap();
entry.finish(desc).unwrap();
archive.finish().unwrap();
}
#[test]
fn test_stream_offset_methods() {
let mut output = Cursor::new(Vec::new());
let mut archive = ZipArchiveWriter::new(&mut output);
let local_header_offset = archive.stream_offset();
let (mut file, config) = archive.new_file("test.txt").start().unwrap();
let data_start_offset = file.stream_offset();
let mut writer = config.wrap(&mut file);
writer.write_all(b"Hello World").unwrap();
let (_, desc) = writer.finish().unwrap();
let end_data_offset = file.stream_offset();
let compressed_bytes = file.finish(desc).unwrap();
let end_descriptor_offset = archive.stream_offset();
archive.finish().unwrap();
assert_eq!(local_header_offset, 0);
assert!(data_start_offset > local_header_offset);
assert_eq!(
end_data_offset,
data_start_offset + b"Hello World".len() as u64
);
assert_eq!(end_descriptor_offset, end_data_offset + 16); assert_eq!(compressed_bytes, end_data_offset - data_start_offset);
}
#[test]
fn test_crc32_options() {
use std::io::Write;
let data = b"Hello, world!";
let correct_crc = crate::crc32(data);
let incorrect_crc = 0x12345678u32;
{
let mut output = Cursor::new(Vec::new());
let mut archive = ZipArchiveWriter::new(&mut output);
let (mut entry, config) = archive.new_file("normal.txt").start().unwrap();
let mut writer = config.wrap(&mut entry);
writer.write_all(data).unwrap();
let (_, descriptor) = writer.finish().unwrap();
entry.finish(descriptor).unwrap();
archive.finish().unwrap();
}
{
let mut output = Cursor::new(Vec::new());
let mut archive = ZipArchiveWriter::new(&mut output);
let (mut entry, config) = archive
.new_file("correct.txt")
.crc32(Crc32Option::Custom(correct_crc))
.start()
.unwrap();
let mut writer = config.wrap(&mut entry);
writer.write_all(data).unwrap();
let (_, descriptor) = writer.finish().unwrap();
entry.finish(descriptor).unwrap();
archive.finish().unwrap();
let output = output.into_inner();
let archive = ZipArchive::from_slice(&output).unwrap();
let mut entries = archive.entries();
let entry = entries.next_entry().unwrap().unwrap();
let wayfinder = entry.wayfinder();
let entry = archive.get_entry(wayfinder).unwrap();
let mut verifier = entry.verifying_reader(entry.data());
let mut actual = Vec::new();
std::io::copy(&mut verifier, &mut actual).unwrap();
assert_eq!(&actual, data);
}
{
let mut output = Cursor::new(Vec::new());
let mut archive = ZipArchiveWriter::new(&mut output);
let (mut entry, config) = archive
.new_file("incorrect.txt")
.crc32(Crc32Option::Custom(incorrect_crc))
.start()
.unwrap();
let mut writer = config.wrap(&mut entry);
writer.write_all(data).unwrap();
let (_, descriptor) = writer.finish().unwrap();
entry.finish(descriptor).unwrap();
archive.finish().unwrap();
let output = output.into_inner();
let archive = ZipArchive::from_slice(&output).unwrap();
let mut entries = archive.entries();
let entry = entries.next_entry().unwrap().unwrap();
let wayfinder = entry.wayfinder();
let entry = archive.get_entry(wayfinder).unwrap();
let mut verifier = entry.verifying_reader(entry.data());
let mut actual = Vec::new();
let result = std::io::copy(&mut verifier, &mut actual);
assert!(result.is_err());
let err = result.unwrap_err();
assert_eq!(err.kind(), std::io::ErrorKind::InvalidData);
let source = err.into_inner().unwrap();
let zip_error = source.downcast::<crate::Error>().unwrap();
match zip_error.kind() {
ErrorKind::InvalidChecksum { expected, actual } => {
assert_eq!(*expected, incorrect_crc);
assert_eq!(*actual, correct_crc);
}
_ => panic!("Expected InvalidChecksum error, got {:?}", zip_error.kind()),
}
}
{
let mut output = Cursor::new(Vec::new());
let mut archive = ZipArchiveWriter::new(&mut output);
let (mut entry, config) = archive
.new_file("skipped.txt")
.crc32(Crc32Option::Skip)
.start()
.unwrap();
let mut writer = config.wrap(&mut entry);
writer.write_all(data).unwrap();
let (_, descriptor) = writer.finish().unwrap();
entry.finish(descriptor).unwrap();
archive.finish().unwrap();
let output = output.into_inner();
let archive = ZipArchive::from_slice(&output).unwrap();
let mut entries = archive.entries();
let entry = entries.next_entry().unwrap().unwrap();
let wayfinder = entry.wayfinder();
let entry = archive.get_entry(wayfinder).unwrap();
let mut verifier = entry.verifying_reader(entry.data());
let mut actual = Vec::new();
std::io::copy(&mut verifier, &mut actual).unwrap();
assert_eq!(&actual, data);
}
}
#[test]
fn test_tuple_api() {
use std::io::Write;
let data = b"Hello, world!";
let custom_crc = 0x12345678u32;
let mut output = Cursor::new(Vec::new());
let mut archive = ZipArchiveWriter::new(&mut output);
let (mut entry, config) = archive
.new_file("test.txt")
.crc32(Crc32Option::Custom(custom_crc))
.start()
.unwrap();
let mut writer = config.wrap(&mut entry);
writer.write_all(data).unwrap();
let (_, descriptor) = writer.finish().unwrap();
assert_eq!(descriptor.crc, custom_crc);
entry.finish(descriptor).unwrap();
archive.finish().unwrap();
}
#[test]
#[allow(deprecated)]
fn test_deprecated_create_method() {
use std::io::Write;
let data = b"Hello, deprecated API!";
let mut output = Cursor::new(Vec::new());
let mut archive = ZipArchiveWriter::new(&mut output);
let mut entry = archive.new_file("deprecated.txt").create().unwrap();
let mut writer = ZipDataWriter::new(&mut entry);
writer.write_all(data).unwrap();
let (_, descriptor) = writer.finish().unwrap();
entry.finish(descriptor).unwrap();
archive.finish().unwrap();
let output = output.into_inner();
let archive = ZipArchive::from_slice(&output).unwrap();
let mut entries = archive.entries();
let entry = entries.next_entry().unwrap().unwrap();
let wayfinder = entry.wayfinder();
let entry = archive.get_entry(wayfinder).unwrap();
let mut verifier = entry.verifying_reader(entry.data());
let mut actual = Vec::new();
std::io::copy(&mut verifier, &mut actual).unwrap();
assert_eq!(&actual, data);
}
}