Fixes and refactors for no-features build

This commit is contained in:
Chris Hennick 2023-05-30 18:17:59 -07:00
parent eb8709afdf
commit 2407ef95c6
No known key found for this signature in database
GPG key ID: 25653935CC8B6C74
17 changed files with 310 additions and 170 deletions

View file

@ -25,18 +25,24 @@ jobs:
toolchain: ${{ matrix.rust }} toolchain: ${{ matrix.rust }}
override: true override: true
- name: check - name: Check
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
with: with:
command: check command: check
args: --all --bins --examples args: --all --bins --examples
- name: tests - name: Tests
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
with: with:
command: test command: test
args: --all args: --all
- name: Tests (no features)
uses: actions-rs/cargo@v1
with:
command: test
args: --all --no-default-features
clippy: clippy:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -117,6 +123,32 @@ jobs:
- name: run fuzz - name: run fuzz
run: | run: |
cargo fuzz run fuzz_write -- -timeout=5s -jobs=100 -workers=2 -runs=10000 -max_len=5000000000 cargo fuzz run fuzz_write -- -timeout=5s -jobs=100 -workers=2 -runs=10000 -max_len=5000000000
- name: Upload any failure inputs
if: always()
uses: actions/upload-artifact@v3
with:
name: fuzz_write_bad_inputs
path: fuzz/artifacts/fuzz_write/crash-*
if-no-files-found: ignore
fuzz_write_with_no_features:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly
override: true
- run: cargo install cargo-fuzz
- name: compile fuzz
run: |
cargo fuzz build --no-default-features fuzz_write
- name: run fuzz
run: |
cargo fuzz run fuzz_write -- -timeout=5s -jobs=100 -workers=2 -runs=1000000 -max_len=5000000000
- name: Upload any failure inputs - name: Upload any failure inputs
if: always() if: always()
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3

View file

@ -197,4 +197,16 @@
### Added ### Added
- `zlib-ng` for fast Deflate compression. This is now the default for compression levels 0-9. - `zlib-ng` for fast Deflate compression. This is now the default for compression levels 0-9.
- `chrono` to convert zip_next::DateTime to and from chrono::NaiveDateTime - `chrono` to convert zip_next::DateTime to and from chrono::NaiveDateTime
## [0.10.0]
### Changed
- Replaces the `flush_on_finish_file` parameter of `ZipWriter::new` and `ZipWriter::Append` with
a `set_flush_on_finish_file` method.
### Fixed
- Fixes build errors that occur when all default features are disabled.
- Fixes more cases of a bug when ZIP64 magic bytes occur in filenames.

View file

@ -1,6 +1,6 @@
[package] [package]
name = "zip_next" name = "zip_next"
version = "0.9.2" version = "0.10.0"
authors = ["Mathijs van de Nes <git@mathijs.vd-nes.nl>", "Marli Frost <marli@frost.red>", "Ryan Levick <ryan.levick@gmail.com>", authors = ["Mathijs van de Nes <git@mathijs.vd-nes.nl>", "Marli Frost <marli@frost.red>", "Ryan Levick <ryan.levick@gmail.com>",
"Chris Hennick <hennickc@amazon.com>"] "Chris Hennick <hennickc@amazon.com>"]
license = "MIT" license = "MIT"
@ -16,7 +16,7 @@ edition = "2021"
aes = { version = "0.8.2", optional = true } aes = { version = "0.8.2", optional = true }
byteorder = "1.4.3" byteorder = "1.4.3"
bzip2 = { version = "0.4.4", optional = true } bzip2 = { version = "0.4.4", optional = true }
chrono = { version = "0.4.25", optional = true } chrono = { version = "0.4.26", optional = true }
constant_time_eq = { version = "0.2.5", optional = true } constant_time_eq = { version = "0.2.5", optional = true }
crc32fast = "1.3.2" crc32fast = "1.3.2"
flate2 = { version = "1.0.26", default-features = false, optional = true } flate2 = { version = "1.0.26", default-features = false, optional = true }

View file

@ -32,14 +32,14 @@ With all default features:
```toml ```toml
[dependencies] [dependencies]
zip_next = "0.9.2" zip_next = "0.10.0"
``` ```
Without the default features: Without the default features:
```toml ```toml
[dependencies] [dependencies]
zip_next = { version = "0.9.2", default-features = false } zip_next = { version = "0.10.0", default-features = false }
``` ```
The features available are: The features available are:

View file

@ -8,7 +8,7 @@ use zip_next::{ZipArchive, ZipWriter};
fn generate_random_archive(size: usize) -> Vec<u8> { fn generate_random_archive(size: usize) -> Vec<u8> {
let data = Vec::new(); let data = Vec::new();
let mut writer = ZipWriter::new(Cursor::new(data), false); let mut writer = ZipWriter::new(Cursor::new(data));
let options = zip_next::write::FileOptions::default() let options = zip_next::write::FileOptions::default()
.compression_method(zip_next::CompressionMethod::Stored); .compression_method(zip_next::CompressionMethod::Stored);

View file

@ -11,7 +11,7 @@ const FILE_SIZE: usize = 1024;
fn generate_random_archive(count_files: usize, file_size: usize) -> Vec<u8> { fn generate_random_archive(count_files: usize, file_size: usize) -> Vec<u8> {
let data = Vec::new(); let data = Vec::new();
let mut writer = ZipWriter::new(Cursor::new(data), false); let mut writer = ZipWriter::new(Cursor::new(data));
let options = FileOptions::default().compression_method(CompressionMethod::Stored); let options = FileOptions::default().compression_method(CompressionMethod::Stored);
let bytes = vec![0u8; file_size]; let bytes = vec![0u8; file_size];

View file

@ -76,7 +76,7 @@ fn zip_dir<T>(
where where
T: Write + Seek, T: Write + Seek,
{ {
let mut zip = zip_next::ZipWriter::new(writer, false); let mut zip = zip_next::ZipWriter::new(writer);
let options = FileOptions::default() let options = FileOptions::default()
.compression_method(method) .compression_method(method)
.unix_permissions(0o755); .unix_permissions(0o755);

View file

@ -25,7 +25,7 @@ fn doit(filename: &str) -> zip_next::result::ZipResult<()> {
let path = std::path::Path::new(filename); let path = std::path::Path::new(filename);
let file = std::fs::File::create(path).unwrap(); let file = std::fs::File::create(path).unwrap();
let mut zip = zip_next::ZipWriter::new(file, false); let mut zip = zip_next::ZipWriter::new(file);
zip.add_directory("test/", Default::default())?; zip.add_directory("test/", Default::default())?;

View file

@ -51,6 +51,7 @@ fn do_operation<T>(writer: &mut RefCell<zip_next::ZipWriter<T>>,
operation: FileOperation, operation: FileOperation,
abort: bool, flush_on_finish_file: bool) -> Result<(), Box<dyn std::error::Error>> abort: bool, flush_on_finish_file: bool) -> Result<(), Box<dyn std::error::Error>>
where T: Read + Write + Seek { where T: Read + Write + Seek {
writer.borrow_mut().set_flush_on_finish_file(flush_on_finish_file);
let name = operation.name; let name = operation.name;
match operation.basic { match operation.basic {
BasicFileOperation::WriteNormalFile {contents, mut options, ..} => { BasicFileOperation::WriteNormalFile {contents, mut options, ..} => {
@ -86,7 +87,7 @@ fn do_operation<T>(writer: &mut RefCell<zip_next::ZipWriter<T>>,
if operation.reopen { if operation.reopen {
let old_comment = writer.borrow().get_raw_comment().to_owned(); let old_comment = writer.borrow().get_raw_comment().to_owned();
let new_writer = zip_next::ZipWriter::new_append( let new_writer = zip_next::ZipWriter::new_append(
writer.borrow_mut().finish().unwrap(), flush_on_finish_file).unwrap(); writer.borrow_mut().finish().unwrap()).unwrap();
assert_eq!(&old_comment, new_writer.get_raw_comment()); assert_eq!(&old_comment, new_writer.get_raw_comment());
*writer = new_writer.into(); *writer = new_writer.into();
} }
@ -94,8 +95,7 @@ fn do_operation<T>(writer: &mut RefCell<zip_next::ZipWriter<T>>,
} }
fuzz_target!(|test_case: FuzzTestCase| { fuzz_target!(|test_case: FuzzTestCase| {
let mut writer = RefCell::new(zip_next::ZipWriter::new(Cursor::new(Vec::new()), let mut writer = RefCell::new(zip_next::ZipWriter::new(Cursor::new(Vec::new())));
test_case.flush_on_finish_file));
writer.borrow_mut().set_raw_comment(test_case.comment); writer.borrow_mut().set_raw_comment(test_case.comment);
for (operation, abort) in test_case.operations { for (operation, abort) in test_case.operations {
let _ = do_operation(&mut writer, operation, abort, test_case.flush_on_finish_file); let _ = do_operation(&mut writer, operation, abort, test_case.flush_on_finish_file);

View file

@ -152,6 +152,55 @@ impl CompressionMethod {
} }
} }
impl Default for CompressionMethod {
fn default() -> Self {
#[cfg(any(
feature = "deflate",
feature = "deflate-miniz",
feature = "deflate-zlib",
feature = "deflate-zlib-ng",
feature = "deflate-zopfli"
))]
return CompressionMethod::Deflated;
#[cfg(all(
not(any(
feature = "deflate",
feature = "deflate-miniz",
feature = "deflate-zlib",
feature = "deflate-zlib-ng",
feature = "deflate-zopfli"
)),
feature = "bzip2"
))]
return CompressionMethod::Bzip2;
#[cfg(all(
not(any(
feature = "deflate",
feature = "deflate-miniz",
feature = "deflate-zlib",
feature = "deflate-zlib-ng",
feature = "deflate-zopfli",
feature = "bzip2"
)),
feature = "zstd"
))]
return CompressionMethod::Zstd;
#[cfg(not(any(
feature = "deflate",
feature = "deflate-miniz",
feature = "deflate-zlib",
feature = "deflate-zlib-ng",
feature = "deflate-zopfli",
feature = "bzip2",
feature = "zstd"
)))]
return CompressionMethod::Stored;
}
}
impl fmt::Display for CompressionMethod { impl fmt::Display for CompressionMethod {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// Just duplicate what the Debug format looks like, i.e, the enum key: // Just duplicate what the Debug format looks like, i.e, the enum key:

View file

@ -49,6 +49,6 @@ mod zipcrypto;
/// ///
/// ```toml /// ```toml
/// [dependencies] /// [dependencies]
/// zip_next = "=0.9.2" /// zip_next = "=0.10.0"
/// ``` /// ```
pub mod unstable; pub mod unstable;

View file

@ -72,6 +72,7 @@ pub(crate) mod zip_archive {
} }
pub use zip_archive::ZipArchive; pub use zip_archive::ZipArchive;
#[allow(clippy::large_enum_variant)] #[allow(clippy::large_enum_variant)]
pub(crate) enum CryptoReader<'a> { pub(crate) enum CryptoReader<'a> {
Plaintext(io::Take<&'a mut dyn Read>), Plaintext(io::Take<&'a mut dyn Read>),
@ -296,11 +297,19 @@ pub(crate) fn make_reader(
} }
} }
pub(crate) struct DirectoryCounts {
pub(crate) archive_offset: u64,
pub(crate) directory_start: u64,
pub(crate) number_of_files: usize,
pub(crate) disk_number: u32,
pub(crate) disk_with_central_directory: u32,
}
impl<R: Read + Seek> ZipArchive<R> { impl<R: Read + Seek> ZipArchive<R> {
fn get_directory_counts_zip32( fn get_directory_counts_zip32(
footer: &spec::CentralDirectoryEnd, footer: &spec::CentralDirectoryEnd,
cde_start_pos: u64, cde_start_pos: u64,
) -> ZipResult<(u64, u64, usize)> { ) -> ZipResult<DirectoryCounts> {
// Some zip files have data prepended to them, resulting in the // Some zip files have data prepended to them, resulting in the
// offsets all being too small. Get the amount of error by comparing // offsets all being too small. Get the amount of error by comparing
// the actual file position we found the CDE at with the offset // the actual file position we found the CDE at with the offset
@ -314,14 +323,20 @@ impl<R: Read + Seek> ZipArchive<R> {
let directory_start = footer.central_directory_offset as u64 + archive_offset; let directory_start = footer.central_directory_offset as u64 + archive_offset;
let number_of_files = footer.number_of_files_on_this_disk as usize; let number_of_files = footer.number_of_files_on_this_disk as usize;
Ok((archive_offset, directory_start, number_of_files)) Ok(DirectoryCounts {
archive_offset,
directory_start,
number_of_files,
disk_number: footer.disk_number as u32,
disk_with_central_directory: footer.disk_with_central_directory as u32,
})
} }
fn get_directory_counts_zip64( fn get_directory_counts_zip64(
reader: &mut R, reader: &mut R,
footer: &spec::CentralDirectoryEnd, footer: &spec::CentralDirectoryEnd,
cde_start_pos: u64, cde_start_pos: u64,
) -> ZipResult<(u64, u64, usize, ZipResult<()>)> { ) -> ZipResult<DirectoryCounts> {
// See if there's a ZIP64 footer. The ZIP64 locator if present will // See if there's a ZIP64 footer. The ZIP64 locator if present will
// have its signature 20 bytes in front of the standard footer. The // have its signature 20 bytes in front of the standard footer. The
// standard footer, in turn, is 22+N bytes large, where N is the // standard footer, in turn, is 22+N bytes large, where N is the
@ -373,21 +388,13 @@ impl<R: Read + Seek> ZipArchive<R> {
)); ));
} }
let supported = if (footer64.disk_number != footer64.disk_with_central_directory) Ok(DirectoryCounts {
|| (!footer.record_too_small()
&& footer.disk_number as u32 != locator64.disk_with_central_directory)
{
unsupported_zip_error("Support for multi-disk files is not implemented")
} else {
Ok(())
};
Ok((
archive_offset, archive_offset,
directory_start, directory_start,
footer64.number_of_files as usize, number_of_files: footer64.number_of_files as usize,
supported, disk_number: footer64.disk_number,
)) disk_with_central_directory: footer64.disk_with_central_directory,
})
} }
/// Get the directory start offset and number of files. This is done in a /// Get the directory start offset and number of files. This is done in a
@ -396,29 +403,48 @@ impl<R: Read + Seek> ZipArchive<R> {
reader: &mut R, reader: &mut R,
footer: &spec::CentralDirectoryEnd, footer: &spec::CentralDirectoryEnd,
cde_start_pos: u64, cde_start_pos: u64,
) -> ZipResult<(u64, u64, usize)> { ) -> ZipResult<DirectoryCounts> {
// Check if file has a zip64 footer // Check if file has a zip64 footer
let (archive_offset_64, directory_start_64, number_of_files_64, supported_64) = let counts_64 = Self::get_directory_counts_zip64(reader, footer, cde_start_pos);
match Self::get_directory_counts_zip64(reader, footer, cde_start_pos) { let counts_32 = Self::get_directory_counts_zip32(footer, cde_start_pos);
Ok(result) => result, match counts_64 {
Err(_) => return Self::get_directory_counts_zip32(footer, cde_start_pos), Err(_) => match counts_32 {
}; Err(e) => Err(e),
// Check if it also has a zip32 footer Ok(counts) => {
let (archive_offset_32, directory_start_32, number_of_files_32) = if counts.disk_number != counts.disk_with_central_directory {
match Self::get_directory_counts_zip32(footer, cde_start_pos) { return unsupported_zip_error(
Ok(result) => result, "Support for multi-disk files is not implemented",
Err(_) => { );
supported_64?; }
return Ok((archive_offset_64, directory_start_64, number_of_files_64)); Ok(counts)
} }
}; },
// It has both, so check if the zip64 footer is valid; if not, assume zip32 Ok(counts_64) => {
if number_of_files_64 != number_of_files_32 && number_of_files_32 != u16::MAX as usize { match counts_32 {
return Ok((archive_offset_32, directory_start_32, number_of_files_32)); Err(_) => Ok(counts_64),
Ok(counts_32) => {
// Both zip32 and zip64 footers exist, so check if the zip64 footer is valid; if not, try zip32
if counts_64.number_of_files != counts_32.number_of_files
&& counts_32.number_of_files != u16::MAX as usize
{
return Ok(counts_32);
}
if counts_64.disk_number != counts_32.disk_number
&& counts_32.disk_number != u16::MAX as u32
{
return Ok(counts_32);
}
if counts_64.disk_with_central_directory
!= counts_32.disk_with_central_directory
&& counts_32.disk_with_central_directory != u16::MAX as u32
{
return Ok(counts_32);
}
Ok(counts_64)
}
}
}
} }
// It is, so we assume a zip64
supported_64?;
Ok((archive_offset_64, directory_start_64, number_of_files_64))
} }
/// Read a ZIP archive, collecting the files it contains /// Read a ZIP archive, collecting the files it contains
@ -427,32 +453,34 @@ impl<R: Read + Seek> ZipArchive<R> {
pub fn new(mut reader: R) -> ZipResult<ZipArchive<R>> { pub fn new(mut reader: R) -> ZipResult<ZipArchive<R>> {
let (footer, cde_start_pos) = spec::CentralDirectoryEnd::find_and_parse(&mut reader)?; let (footer, cde_start_pos) = spec::CentralDirectoryEnd::find_and_parse(&mut reader)?;
if !footer.record_too_small() && footer.disk_number != footer.disk_with_central_directory { let counts = Self::get_directory_counts(&mut reader, &footer, cde_start_pos)?;
if counts.disk_number != counts.disk_with_central_directory {
return unsupported_zip_error("Support for multi-disk files is not implemented"); return unsupported_zip_error("Support for multi-disk files is not implemented");
} }
let (archive_offset, directory_start, number_of_files) =
Self::get_directory_counts(&mut reader, &footer, cde_start_pos)?;
// If the parsed number of files is greater than the offset then // If the parsed number of files is greater than the offset then
// something fishy is going on and we shouldn't trust number_of_files. // something fishy is going on and we shouldn't trust number_of_files.
let file_capacity = if number_of_files > cde_start_pos as usize { let file_capacity = if counts.number_of_files > cde_start_pos as usize {
0 0
} else { } else {
number_of_files counts.number_of_files
}; };
let mut files = Vec::with_capacity(file_capacity); let mut files = Vec::with_capacity(file_capacity);
let mut names_map = HashMap::with_capacity(file_capacity); let mut names_map = HashMap::with_capacity(file_capacity);
if reader.seek(io::SeekFrom::Start(directory_start)).is_err() { if reader
.seek(io::SeekFrom::Start(counts.directory_start))
.is_err()
{
return Err(ZipError::InvalidArchive( return Err(ZipError::InvalidArchive(
"Could not seek to start of central directory", "Could not seek to start of central directory",
)); ));
} }
for _ in 0..number_of_files { for _ in 0..counts.number_of_files {
let file = central_header_to_zip_file(&mut reader, archive_offset)?; let file = central_header_to_zip_file(&mut reader, counts.archive_offset)?;
names_map.insert(file.file_name.clone(), files.len()); names_map.insert(file.file_name.clone(), files.len());
files.push(file); files.push(file);
} }
@ -460,7 +488,7 @@ impl<R: Read + Seek> ZipArchive<R> {
let shared = Arc::new(zip_archive::Shared { let shared = Arc::new(zip_archive::Shared {
files, files,
names_map, names_map,
offset: archive_offset, offset: counts.archive_offset,
comment: footer.zip_file_comment, comment: footer.zip_file_comment,
}); });

View file

@ -23,18 +23,6 @@ pub struct CentralDirectoryEnd {
} }
impl CentralDirectoryEnd { impl CentralDirectoryEnd {
// Per spec 4.4.1.4 - a CentralDirectoryEnd field might be insufficient to hold the
// required data. In this case the file SHOULD contain a ZIP64 format record
// and the field of this record will be set to -1
pub(crate) fn record_too_small(&self) -> bool {
self.disk_number == 0xFFFF
|| self.disk_with_central_directory == 0xFFFF
|| self.number_of_files_on_this_disk == 0xFFFF
|| self.number_of_files == 0xFFFF
|| self.central_directory_size == 0xFFFFFFFF
|| self.central_directory_offset == 0xFFFFFFFF
}
pub fn parse<T: Read>(reader: &mut T) -> ZipResult<CentralDirectoryEnd> { pub fn parse<T: Read>(reader: &mut T) -> ZipResult<CentralDirectoryEnd> {
let magic = reader.read_u32::<LittleEndian>()?; let magic = reader.read_u32::<LittleEndian>()?;
if magic != CENTRAL_DIRECTORY_END_SIGNATURE { if magic != CENTRAL_DIRECTORY_END_SIGNATURE {

View file

@ -11,8 +11,6 @@ use chrono::{Datelike, NaiveDate, NaiveDateTime, NaiveTime, Timelike};
target_arch = "powerpc" target_arch = "powerpc"
)))] )))]
use std::sync::atomic; use std::sync::atomic;
#[cfg(not(feature = "time"))]
use std::time::SystemTime;
#[cfg(doc)] #[cfg(doc)]
use {crate::read::ZipFile, crate::write::FileOptions}; use {crate::read::ZipFile, crate::write::FileOptions};
@ -53,8 +51,6 @@ mod atomic {
} }
} }
#[cfg(feature = "time")]
use crate::result::DateTimeRangeError;
#[cfg(feature = "time")] #[cfg(feature = "time")]
use time::{error::ComponentRange, Date, Month, OffsetDateTime, PrimitiveDateTime, Time}; use time::{error::ComponentRange, Date, Month, OffsetDateTime, PrimitiveDateTime, Time};
@ -121,16 +117,16 @@ impl arbitrary::Arbitrary<'_> for DateTime {
#[cfg(feature = "chrono")] #[cfg(feature = "chrono")]
#[allow(clippy::result_unit_err)] #[allow(clippy::result_unit_err)]
impl TryFrom<NaiveDateTime> for DateTime { impl TryFrom<NaiveDateTime> for DateTime {
type Error = DateTimeRangeError; type Error = ();
fn try_from(value: NaiveDateTime) -> Result<Self, Self::Error> { fn try_from(value: NaiveDateTime) -> Result<Self, Self::Error> {
DateTime::from_date_and_time( DateTime::from_date_and_time(
value.year().try_into()?, value.year().try_into().map_err(|_| ())?,
value.month().try_into()?, value.month().try_into().map_err(|_| ())?,
value.day().try_into()?, value.day().try_into().map_err(|_| ())?,
value.hour().try_into()?, value.hour().try_into().map_err(|_| ())?,
value.minute().try_into()?, value.minute().try_into().map_err(|_| ())?,
value.second().try_into()?, value.second().try_into().map_err(|_| ())?,
) )
} }
} }
@ -201,7 +197,7 @@ impl DateTime {
hour: u8, hour: u8,
minute: u8, minute: u8,
second: u8, second: u8,
) -> Result<DateTime, DateTimeRangeError> { ) -> Result<DateTime, ()> {
if (1980..=2107).contains(&year) if (1980..=2107).contains(&year)
&& (1..=12).contains(&month) && (1..=12).contains(&month)
&& (1..=31).contains(&day) && (1..=31).contains(&day)
@ -218,7 +214,7 @@ impl DateTime {
second, second,
}) })
} else { } else {
Err(DateTimeRangeError) Err(())
} }
} }
@ -316,8 +312,9 @@ impl DateTime {
} }
#[cfg(feature = "time")] #[cfg(feature = "time")]
#[allow(clippy::result_unit_err)]
impl TryFrom<OffsetDateTime> for DateTime { impl TryFrom<OffsetDateTime> for DateTime {
type Error = DateTimeRangeError; type Error = ();
fn try_from(dt: OffsetDateTime) -> Result<Self, Self::Error> { fn try_from(dt: OffsetDateTime) -> Result<Self, Self::Error> {
if dt.year() >= 1980 && dt.year() <= 2107 { if dt.year() >= 1980 && dt.year() <= 2107 {
@ -330,7 +327,7 @@ impl TryFrom<OffsetDateTime> for DateTime {
second: dt.second(), second: dt.second(),
}) })
} else { } else {
Err(DateTimeRangeError) Err(())
} }
} }
} }

View file

@ -8,12 +8,31 @@ use crate::types::{ffi, AtomicU64, DateTime, System, ZipFileData, DEFAULT_VERSIO
use byteorder::{LittleEndian, WriteBytesExt}; use byteorder::{LittleEndian, WriteBytesExt};
use crc32fast::Hasher; use crc32fast::Hasher;
use std::collections::HashMap; use std::collections::HashMap;
#[cfg(any(
feature = "deflate",
feature = "deflate-miniz",
feature = "deflate-zlib",
feature = "deflate-zlib-ng",
feature = "deflate-zopfli",
feature = "bzip2",
feature = "zstd",
feature = "time"
))]
use std::convert::TryInto; use std::convert::TryInto;
use std::default::Default; use std::default::Default;
use std::io; use std::io;
use std::io::prelude::*; use std::io::prelude::*;
use std::io::{BufReader, BufWriter, SeekFrom}; use std::io::{BufReader, SeekFrom};
use std::mem; use std::mem;
#[cfg(any(
feature = "deflate",
feature = "deflate-miniz",
feature = "deflate-zlib",
feature = "deflate-zlib-ng",
feature = "zopfli",
feature = "bzip2",
feature = "zstd",
))]
use std::num::NonZeroU8; use std::num::NonZeroU8;
use std::str::{from_utf8, Utf8Error}; use std::str::{from_utf8, Utf8Error};
use std::sync::Arc; use std::sync::Arc;
@ -24,16 +43,20 @@ use std::sync::Arc;
feature = "deflate-zlib", feature = "deflate-zlib",
feature = "deflate-zlib-ng" feature = "deflate-zlib-ng"
))] ))]
use flate2::write::DeflateEncoder; use flate2::{write::DeflateEncoder, Compression};
#[cfg(feature = "bzip2")] #[cfg(feature = "bzip2")]
use bzip2::write::BzEncoder; use bzip2::write::BzEncoder;
use flate2::Compression;
#[cfg(feature = "time")] #[cfg(feature = "time")]
use time::OffsetDateTime; use time::OffsetDateTime;
#[cfg(feature = "deflate-zopfli")]
use zopfli::Options; use zopfli::Options;
#[cfg(feature = "deflate-zopfli")]
use std::io::BufWriter;
#[cfg(feature = "zstd")] #[cfg(feature = "zstd")]
use zstd::stream::write::Encoder as ZstdEncoder; use zstd::stream::write::Encoder as ZstdEncoder;
@ -93,7 +116,7 @@ pub(crate) mod zip_writer {
/// ///
/// // We use a buffer here, though you'd normally use a `File` /// // We use a buffer here, though you'd normally use a `File`
/// let mut buf = [0; 65536]; /// let mut buf = [0; 65536];
/// let mut zip = ZipWriter::new(std::io::Cursor::new(&mut buf[..]), false); /// let mut zip = ZipWriter::new(std::io::Cursor::new(&mut buf[..]));
/// ///
/// let options = FileOptions::default().compression_method(zip_next::CompressionMethod::Stored); /// let options = FileOptions::default().compression_method(zip_next::CompressionMethod::Stored);
/// zip.start_file("hello_world.txt", options)?; /// zip.start_file("hello_world.txt", options)?;
@ -121,7 +144,7 @@ pub(crate) mod zip_writer {
use crate::result::ZipError::InvalidArchive; use crate::result::ZipError::InvalidArchive;
use crate::write::GenericZipWriter::{Closed, Storer}; use crate::write::GenericZipWriter::{Closed, Storer};
use crate::zipcrypto::ZipCryptoKeys; use crate::zipcrypto::ZipCryptoKeys;
use crate::CompressionMethod::{Deflated, Stored}; use crate::CompressionMethod::Stored;
pub use zip_writer::ZipWriter; pub use zip_writer::ZipWriter;
#[derive(Default)] #[derive(Default)]
@ -170,11 +193,11 @@ impl arbitrary::Arbitrary<'_> for FileOptions {
zopfli_buffer_size: None, zopfli_buffer_size: None,
}; };
match options.compression_method { match options.compression_method {
Deflated => { #[cfg(feature = "deflate-zopfli")]
CompressionMethod::Deflated => {
if bool::arbitrary(u)? { if bool::arbitrary(u)? {
let level = u.int_in_range(0..=24)?; let level = u.int_in_range(0..=24)?;
options.compression_level = Some(level); options.compression_level = Some(level);
#[cfg(feature = "deflate-zopfli")]
if level > Compression::best().level().try_into().unwrap() { if level > Compression::best().level().try_into().unwrap() {
options.zopfli_buffer_size = Some(1 << u.int_in_range(9..=30)?); options.zopfli_buffer_size = Some(1 << u.int_in_range(9..=30)?);
} }
@ -347,22 +370,7 @@ impl Default for FileOptions {
/// Construct a new FileOptions object /// Construct a new FileOptions object
fn default() -> Self { fn default() -> Self {
Self { Self {
#[cfg(any( compression_method: Default::default(),
feature = "deflate",
feature = "deflate-miniz",
feature = "deflate-zlib",
feature = "deflate-zlib-ng",
feature = "deflate-zopfli"
))]
compression_method: Deflated,
#[cfg(not(any(
feature = "deflate",
feature = "deflate-miniz",
feature = "deflate-zlib",
feature = "deflate-zlib-ng",
feature = "deflate-zopfli"
)))]
compression_method: Stored,
compression_level: None, compression_level: None,
#[cfg(feature = "time")] #[cfg(feature = "time")]
last_modified_time: OffsetDateTime::now_utc().try_into().unwrap_or_default(), last_modified_time: OffsetDateTime::now_utc().try_into().unwrap_or_default(),
@ -435,28 +443,28 @@ impl ZipWriterStats {
impl<A: Read + Write + Seek> ZipWriter<A> { impl<A: Read + Write + Seek> ZipWriter<A> {
/// Initializes the archive from an existing ZIP archive, making it ready for append. /// Initializes the archive from an existing ZIP archive, making it ready for append.
/// pub fn new_append(mut readwriter: A) -> ZipResult<ZipWriter<A>> {
/// See [`ZipWriter::new`] for the caveats that apply when `flush_on_finish_file` is set.
pub fn new_append(mut readwriter: A, flush_on_finish_file: bool) -> ZipResult<ZipWriter<A>> {
let (footer, cde_start_pos) = spec::CentralDirectoryEnd::find_and_parse(&mut readwriter)?; let (footer, cde_start_pos) = spec::CentralDirectoryEnd::find_and_parse(&mut readwriter)?;
if footer.disk_number != footer.disk_with_central_directory { let counts = ZipArchive::get_directory_counts(&mut readwriter, &footer, cde_start_pos)?;
if counts.disk_number != counts.disk_with_central_directory {
return Err(ZipError::UnsupportedArchive( return Err(ZipError::UnsupportedArchive(
"Support for multi-disk files is not implemented", "Support for multi-disk files is not implemented",
)); ));
} }
let (archive_offset, directory_start, number_of_files) = if readwriter
ZipArchive::get_directory_counts(&mut readwriter, &footer, cde_start_pos)?; .seek(SeekFrom::Start(counts.directory_start))
.is_err()
if readwriter.seek(SeekFrom::Start(directory_start)).is_err() { {
return Err(InvalidArchive( return Err(InvalidArchive(
"Could not seek to start of central directory", "Could not seek to start of central directory",
)); ));
} }
let files = (0..number_of_files) let files = (0..counts.number_of_files)
.map(|_| central_header_to_zip_file(&mut readwriter, archive_offset)) .map(|_| central_header_to_zip_file(&mut readwriter, counts.archive_offset))
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
let mut files_by_name = HashMap::new(); let mut files_by_name = HashMap::new();
@ -464,7 +472,7 @@ impl<A: Read + Write + Seek> ZipWriter<A> {
files_by_name.insert(file.file_name.to_owned(), index); files_by_name.insert(file.file_name.to_owned(), index);
} }
let _ = readwriter.seek(SeekFrom::Start(directory_start)); // seek directory_start to overwrite it let _ = readwriter.seek(SeekFrom::Start(counts.directory_start)); // seek directory_start to overwrite it
Ok(ZipWriter { Ok(ZipWriter {
inner: Storer(MaybeEncrypted::Unencrypted(readwriter)), inner: Storer(MaybeEncrypted::Unencrypted(readwriter)),
@ -474,9 +482,26 @@ impl<A: Read + Write + Seek> ZipWriter<A> {
writing_to_file: false, writing_to_file: false,
comment: footer.zip_file_comment, comment: footer.zip_file_comment,
writing_raw: true, // avoid recomputing the last file's header writing_raw: true, // avoid recomputing the last file's header
flush_on_finish_file, flush_on_finish_file: false,
}) })
} }
/// `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
/// [std::io::BufWriter], because that has a [seek] method that implicitly calls [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.
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> { impl<A: Read + Write + Seek> ZipWriter<A> {
@ -540,15 +565,7 @@ impl<W: Write + Seek> ZipWriter<W> {
/// Before writing to this object, the [`ZipWriter::start_file`] function should be called. /// 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 /// 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. /// [`ZipWriter::is_writing_file`] to determine if the file remains open.
/// pub fn new(inner: W) -> ZipWriter<W> {
/// `flush_on_finish_file` is designed to support a streaming `inner` that may unload flushed
/// bytes. This ZipWriter will not try to seek further back than the last flushed byte 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 flushed bytes and not overwrite them. Do not enable this with a [BufWriter], because
/// that implicitly calls [`Writer::flush`] whenever [`Seek::seek`] is called. Likewise, when
/// using Deflate compression, set []
pub fn new(inner: W, flush_on_finish_file: bool) -> ZipWriter<W> {
ZipWriter { ZipWriter {
inner: Storer(MaybeEncrypted::Unencrypted(inner)), inner: Storer(MaybeEncrypted::Unencrypted(inner)),
files: Vec::new(), files: Vec::new(),
@ -557,7 +574,7 @@ impl<W: Write + Seek> ZipWriter<W> {
writing_to_file: false, writing_to_file: false,
writing_raw: false, writing_raw: false,
comment: Vec::new(), comment: Vec::new(),
flush_on_finish_file, flush_on_finish_file: false,
} }
} }
@ -754,7 +771,12 @@ impl<W: Write + Seek> ZipWriter<W> {
return Ok(()); return Ok(());
} }
let make_plain_writer = self.inner.prepare_next_writer(Stored, None, None)?; let make_plain_writer = self.inner.prepare_next_writer(
Stored,
None,
#[cfg(feature = "deflate-zopfli")]
None,
)?;
self.inner.switch_to(make_plain_writer)?; self.inner.switch_to(make_plain_writer)?;
self.switch_to_non_encrypting_writer()?; self.switch_to_non_encrypting_writer()?;
let writer = self.inner.get_plain(); let writer = self.inner.get_plain();
@ -804,7 +826,12 @@ impl<W: Write + Seek> ZipWriter<W> {
pub fn abort_file(&mut self) -> ZipResult<()> { pub fn abort_file(&mut self) -> ZipResult<()> {
let last_file = self.files.pop().ok_or(ZipError::FileNotFound)?; let last_file = self.files.pop().ok_or(ZipError::FileNotFound)?;
self.files_by_name.remove(&last_file.file_name); self.files_by_name.remove(&last_file.file_name);
let make_plain_writer = self.inner.prepare_next_writer(Stored, None, None)?; let make_plain_writer = self.inner.prepare_next_writer(
Stored,
None,
#[cfg(feature = "deflate-zopfli")]
None,
)?;
self.inner.switch_to(make_plain_writer)?; self.inner.switch_to(make_plain_writer)?;
self.switch_to_non_encrypting_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 // Make sure this is the last file, and that no shallow copies of it remain; otherwise we'd
@ -1180,7 +1207,7 @@ impl<W: Write + Seek> GenericZipWriter<W> {
feature = "deflate-zlib-ng", feature = "deflate-zlib-ng",
feature = "deflate-zopfli" feature = "deflate-zopfli"
))] ))]
Deflated => { CompressionMethod::Deflated => {
let default = if cfg!(feature = "deflate") let default = if cfg!(feature = "deflate")
|| cfg!(feature = "deflate-miniz") || cfg!(feature = "deflate-miniz")
|| cfg!(feature = "deflate-zlib") || cfg!(feature = "deflate-zlib")
@ -1605,6 +1632,13 @@ mod test {
use crate::compression::CompressionMethod; use crate::compression::CompressionMethod;
use crate::result::ZipResult; use crate::result::ZipResult;
use crate::types::DateTime; use crate::types::DateTime;
#[cfg(any(
feature = "deflate",
feature = "deflate-zlib",
feature = "deflate-zlib-ng",
feature = "deflate-miniz",
feature = "deflate-zopfli"
))]
use crate::CompressionMethod::Deflated; use crate::CompressionMethod::Deflated;
use crate::ZipArchive; use crate::ZipArchive;
use std::io; use std::io;
@ -1613,7 +1647,7 @@ mod test {
#[test] #[test]
fn write_empty_zip() { fn write_empty_zip() {
let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()), false); let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()));
writer.set_comment("ZIP"); writer.set_comment("ZIP");
let result = writer.finish().unwrap(); let result = writer.finish().unwrap();
assert_eq!(result.get_ref().len(), 25); assert_eq!(result.get_ref().len(), 25);
@ -1632,7 +1666,7 @@ mod test {
#[test] #[test]
fn write_zip_dir() { fn write_zip_dir() {
let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()), false); let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()));
writer writer
.add_directory( .add_directory(
"test", "test",
@ -1660,7 +1694,7 @@ mod test {
#[test] #[test]
fn write_symlink_simple() { fn write_symlink_simple() {
let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()), false); let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()));
writer writer
.add_symlink( .add_symlink(
"name", "name",
@ -1689,7 +1723,7 @@ mod test {
#[test] #[test]
fn write_symlink_wonky_paths() { fn write_symlink_wonky_paths() {
let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()), false); let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()));
writer writer
.add_symlink( .add_symlink(
"directory\\link", "directory\\link",
@ -1721,7 +1755,7 @@ mod test {
#[test] #[test]
fn write_mimetype_zip() { fn write_mimetype_zip() {
let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()), false); let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()));
let options = FileOptions { let options = FileOptions {
compression_method: CompressionMethod::Stored, compression_method: CompressionMethod::Stored,
compression_level: None, compression_level: None,
@ -1763,10 +1797,10 @@ mod test {
#[test] #[test]
fn test_shallow_copy() { fn test_shallow_copy() {
let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()), false); let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()));
let options = FileOptions { let options = FileOptions {
compression_method: Deflated, compression_method: CompressionMethod::default(),
compression_level: Some(9), compression_level: None,
last_modified_time: DateTime::default(), last_modified_time: DateTime::default(),
permissions: Some(33188), permissions: Some(33188),
large_file: false, large_file: false,
@ -1786,7 +1820,7 @@ mod test {
.shallow_copy_file(RT_TEST_FILENAME, SECOND_FILENAME) .shallow_copy_file(RT_TEST_FILENAME, SECOND_FILENAME)
.expect_err("Duplicate filename"); .expect_err("Duplicate filename");
let zip = writer.finish().unwrap(); let zip = writer.finish().unwrap();
let mut writer = ZipWriter::new_append(zip, false).unwrap(); let mut writer = ZipWriter::new_append(zip).unwrap();
writer writer
.shallow_copy_file(SECOND_FILENAME, SECOND_FILENAME) .shallow_copy_file(SECOND_FILENAME, SECOND_FILENAME)
.expect_err("Duplicate filename"); .expect_err("Duplicate filename");
@ -1815,10 +1849,10 @@ mod test {
#[test] #[test]
fn test_deep_copy() { fn test_deep_copy() {
let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()), false); let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()));
let options = FileOptions { let options = FileOptions {
compression_method: Deflated, compression_method: CompressionMethod::default(),
compression_level: Some(9), compression_level: None,
last_modified_time: DateTime::default(), last_modified_time: DateTime::default(),
permissions: Some(33188), permissions: Some(33188),
large_file: false, large_file: false,
@ -1835,7 +1869,7 @@ mod test {
.deep_copy_file(RT_TEST_FILENAME, SECOND_FILENAME) .deep_copy_file(RT_TEST_FILENAME, SECOND_FILENAME)
.unwrap(); .unwrap();
let zip = writer.finish().unwrap(); let zip = writer.finish().unwrap();
let mut writer = ZipWriter::new_append(zip, false).unwrap(); let mut writer = ZipWriter::new_append(zip).unwrap();
writer writer
.deep_copy_file(RT_TEST_FILENAME, THIRD_FILENAME) .deep_copy_file(RT_TEST_FILENAME, THIRD_FILENAME)
.unwrap(); .unwrap();
@ -1864,7 +1898,7 @@ mod test {
#[test] #[test]
fn duplicate_filenames() { fn duplicate_filenames() {
let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()), false); let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()));
writer writer
.start_file("foo/bar/test", FileOptions::default()) .start_file("foo/bar/test", FileOptions::default())
.unwrap(); .unwrap();
@ -1878,7 +1912,7 @@ mod test {
#[test] #[test]
fn test_filename_looks_like_zip64_locator() { fn test_filename_looks_like_zip64_locator() {
let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()), false); let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()));
writer writer
.start_file( .start_file(
"PK\u{6}\u{7}\0\0\0\u{11}\0\0\0\0\0\0\0\0\0\0\0\0", "PK\u{6}\u{7}\0\0\0\u{11}\0\0\0\0\0\0\0\0\0\0\0\0",
@ -1891,7 +1925,7 @@ mod test {
#[test] #[test]
fn test_filename_looks_like_zip64_locator_2() { fn test_filename_looks_like_zip64_locator_2() {
let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()), false); let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()));
writer writer
.start_file( .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", "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",
@ -1905,7 +1939,7 @@ mod test {
#[test] #[test]
fn test_filename_looks_like_zip64_locator_2a() { fn test_filename_looks_like_zip64_locator_2a() {
let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()), false); let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()));
writer writer
.start_file( .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", "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",
@ -1919,7 +1953,7 @@ mod test {
#[test] #[test]
fn test_filename_looks_like_zip64_locator_3() { fn test_filename_looks_like_zip64_locator_3() {
let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()), false); let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()));
writer writer
.start_file("\0PK\u{6}\u{6}", FileOptions::default()) .start_file("\0PK\u{6}\u{6}", FileOptions::default())
.unwrap(); .unwrap();
@ -1936,7 +1970,7 @@ mod test {
#[test] #[test]
fn test_filename_looks_like_zip64_locator_4() { fn test_filename_looks_like_zip64_locator_4() {
let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()), false); let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()));
writer writer
.start_file("PK\u{6}\u{6}", FileOptions::default()) .start_file("PK\u{6}\u{6}", FileOptions::default())
.unwrap(); .unwrap();
@ -1959,19 +1993,19 @@ mod test {
#[test] #[test]
fn test_filename_looks_like_zip64_locator_5() -> ZipResult<()> { fn test_filename_looks_like_zip64_locator_5() -> ZipResult<()> {
let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()), false); let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()));
writer writer
.add_directory("", FileOptions::default().with_alignment(21)) .add_directory("", FileOptions::default().with_alignment(21))
.unwrap(); .unwrap();
let mut writer = ZipWriter::new_append(writer.finish().unwrap(), false).unwrap(); let mut writer = ZipWriter::new_append(writer.finish().unwrap()).unwrap();
writer.shallow_copy_file("/", "").unwrap(); writer.shallow_copy_file("/", "").unwrap();
writer.shallow_copy_file("", "\0").unwrap(); writer.shallow_copy_file("", "\0").unwrap();
writer.shallow_copy_file("\0", "PK\u{6}\u{6}").unwrap(); writer.shallow_copy_file("\0", "PK\u{6}\u{6}").unwrap();
let mut writer = ZipWriter::new_append(writer.finish().unwrap(), false).unwrap(); let mut writer = ZipWriter::new_append(writer.finish().unwrap()).unwrap();
writer writer
.start_file("\0\0\0\0\0\0", FileOptions::default()) .start_file("\0\0\0\0\0\0", FileOptions::default())
.unwrap(); .unwrap();
let mut writer = ZipWriter::new_append(writer.finish().unwrap(), false).unwrap(); let mut writer = ZipWriter::new_append(writer.finish().unwrap()).unwrap();
writer writer
.start_file( .start_file(
"#PK\u{6}\u{7}\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", "#PK\u{6}\u{7}\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0",
@ -1986,7 +2020,7 @@ mod test {
#[test] #[test]
fn remove_shallow_copy_keeps_original() -> ZipResult<()> { fn remove_shallow_copy_keeps_original() -> ZipResult<()> {
let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()), false); let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()));
writer writer
.start_file("original", FileOptions::default()) .start_file("original", FileOptions::default())
.unwrap(); .unwrap();
@ -2005,14 +2039,14 @@ mod test {
#[test] #[test]
fn remove_encrypted_file() -> ZipResult<()> { fn remove_encrypted_file() -> ZipResult<()> {
let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()), false); let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()));
let first_file_options = FileOptions::default() let first_file_options = FileOptions::default()
.with_alignment(65535) .with_alignment(65535)
.with_deprecated_encryption(b"Password"); .with_deprecated_encryption(b"Password");
writer.start_file("", first_file_options).unwrap(); writer.start_file("", first_file_options).unwrap();
writer.abort_file().unwrap(); writer.abort_file().unwrap();
let zip = writer.finish().unwrap(); let zip = writer.finish().unwrap();
let mut writer = ZipWriter::new(zip, false); let mut writer = ZipWriter::new(zip);
writer.start_file("", FileOptions::default()).unwrap(); writer.start_file("", FileOptions::default()).unwrap();
Ok(()) Ok(())
} }
@ -2022,12 +2056,12 @@ mod test {
let mut options = FileOptions::default(); let mut options = FileOptions::default();
options = options.with_deprecated_encryption(b"Password"); options = options.with_deprecated_encryption(b"Password");
options.alignment = 65535; options.alignment = 65535;
let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()), false); let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()));
writer.add_symlink("", "s\t\0\0ggggg\0\0", options).unwrap(); writer.add_symlink("", "s\t\0\0ggggg\0\0", options).unwrap();
writer.abort_file().unwrap(); writer.abort_file().unwrap();
let zip = writer.finish().unwrap(); let zip = writer.finish().unwrap();
println!("{:0>2x?}", zip.get_ref()); println!("{:0>2x?}", zip.get_ref());
let mut writer = ZipWriter::new_append(zip, false).unwrap(); let mut writer = ZipWriter::new_append(zip).unwrap();
writer.start_file("", FileOptions::default()).unwrap(); writer.start_file("", FileOptions::default()).unwrap();
Ok(()) Ok(())
} }
@ -2037,9 +2071,9 @@ mod test {
fn zopfli_empty_write() -> ZipResult<()> { fn zopfli_empty_write() -> ZipResult<()> {
let mut options = FileOptions::default(); let mut options = FileOptions::default();
options = options options = options
.compression_method(Deflated) .compression_method(CompressionMethod::default())
.compression_level(Some(264)); .compression_level(Some(264));
let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()), false); let mut writer = ZipWriter::new(io::Cursor::new(Vec::new()));
writer.start_file("", options).unwrap(); writer.start_file("", options).unwrap();
writer.write_all(&[]).unwrap(); writer.write_all(&[]).unwrap();
writer.write_all(&[]).unwrap(); writer.write_all(&[]).unwrap();

View file

@ -35,7 +35,7 @@ fn copy() {
{ {
let mut src_archive = zip_next::ZipArchive::new(src_file).unwrap(); let mut src_archive = zip_next::ZipArchive::new(src_file).unwrap();
let mut zip = ZipWriter::new(&mut tgt_file, false); let mut zip = ZipWriter::new(&mut tgt_file);
{ {
let file = src_archive let file = src_archive
@ -73,7 +73,7 @@ fn append() {
write_test_archive(file, method, *shallow_copy); write_test_archive(file, method, *shallow_copy);
{ {
let mut zip = ZipWriter::new_append(&mut file, false).unwrap(); let mut zip = ZipWriter::new_append(&mut file).unwrap();
zip.start_file( zip.start_file(
COPY_ENTRY_NAME, COPY_ENTRY_NAME,
FileOptions::default() FileOptions::default()
@ -95,7 +95,7 @@ fn append() {
// Write a test zip archive to buffer. // Write a test zip archive to buffer.
fn write_test_archive(file: &mut Cursor<Vec<u8>>, method: CompressionMethod, shallow_copy: bool) { fn write_test_archive(file: &mut Cursor<Vec<u8>>, method: CompressionMethod, shallow_copy: bool) {
let mut zip = ZipWriter::new(file, false); let mut zip = ZipWriter::new(file);
zip.add_directory("test/", Default::default()).unwrap(); zip.add_directory("test/", Default::default()).unwrap();

View file

@ -25,7 +25,7 @@ fn encrypting_file() {
use std::io::{Read, Write}; use std::io::{Read, Write};
use zip_next::unstable::write::FileOptionsExt; use zip_next::unstable::write::FileOptionsExt;
let mut buf = vec![0; 2048]; let mut buf = vec![0; 2048];
let mut archive = zip_next::write::ZipWriter::new(Cursor::new(&mut buf), false); let mut archive = zip_next::write::ZipWriter::new(Cursor::new(&mut buf));
archive archive
.start_file( .start_file(
"name", "name",