Merge pull request #288 from qu1x/fix-zip64

Fix ZIP64 write support.
This commit is contained in:
Alexander Zaitsev 2022-03-26 00:32:10 +03:00 committed by GitHub
commit 448bbb7e3f
Signed by: DevComp
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 37 additions and 68 deletions

View file

@ -727,17 +727,17 @@ fn parse_extra_field(file: &mut ZipFileData) -> ZipResult<()> {
match kind { match kind {
// Zip64 extended information extra field // Zip64 extended information extra field
0x0001 => { 0x0001 => {
if file.uncompressed_size == 0xFFFFFFFF { if file.uncompressed_size == spec::ZIP64_BYTES_THR {
file.large_file = true; file.large_file = true;
file.uncompressed_size = reader.read_u64::<LittleEndian>()?; file.uncompressed_size = reader.read_u64::<LittleEndian>()?;
len_left -= 8; len_left -= 8;
} }
if file.compressed_size == 0xFFFFFFFF { if file.compressed_size == spec::ZIP64_BYTES_THR {
file.large_file = true; file.large_file = true;
file.compressed_size = reader.read_u64::<LittleEndian>()?; file.compressed_size = reader.read_u64::<LittleEndian>()?;
len_left -= 8; len_left -= 8;
} }
if file.header_start == 0xFFFFFFFF { if file.header_start == spec::ZIP64_BYTES_THR {
file.header_start = reader.read_u64::<LittleEndian>()?; file.header_start = reader.read_u64::<LittleEndian>()?;
len_left -= 8; len_left -= 8;
} }

View file

@ -9,6 +9,9 @@ const CENTRAL_DIRECTORY_END_SIGNATURE: u32 = 0x06054b50;
pub const ZIP64_CENTRAL_DIRECTORY_END_SIGNATURE: u32 = 0x06064b50; pub const ZIP64_CENTRAL_DIRECTORY_END_SIGNATURE: u32 = 0x06064b50;
const ZIP64_CENTRAL_DIRECTORY_END_LOCATOR_SIGNATURE: u32 = 0x07064b50; const ZIP64_CENTRAL_DIRECTORY_END_LOCATOR_SIGNATURE: u32 = 0x07064b50;
pub const ZIP64_BYTES_THR: u64 = u32::MAX as u64;
pub const ZIP64_ENTRY_THR: usize = u16::MAX as usize;
pub struct CentralDirectoryEnd { pub struct CentralDirectoryEnd {
pub disk_number: u16, pub disk_number: u16,
pub disk_with_central_directory: u16, pub disk_with_central_directory: u16,

View file

@ -216,7 +216,7 @@ impl<W: Write + io::Seek> Write for ZipWriter<W> {
let write_result = w.write(buf); let write_result = w.write(buf);
if let Ok(count) = write_result { if let Ok(count) = write_result {
self.stats.update(&buf[0..count]); self.stats.update(&buf[0..count]);
if self.stats.bytes_written > 0xFFFFFFFF if self.stats.bytes_written > spec::ZIP64_BYTES_THR
&& !self.files.last_mut().unwrap().large_file && !self.files.last_mut().unwrap().large_file
{ {
let _inner = mem::replace(&mut self.inner, GenericZipWriter::Closed); let _inner = mem::replace(&mut self.inner, GenericZipWriter::Closed);
@ -647,6 +647,7 @@ impl<W: Write + io::Seek> ZipWriter<W> {
S: Into<String>, S: Into<String>,
{ {
let mut options = FileOptions::default() let mut options = FileOptions::default()
.large_file(file.compressed_size().max(file.size()) > spec::ZIP64_BYTES_THR)
.last_modified_time(file.last_modified()) .last_modified_time(file.last_modified())
.compression_method(file.compression()); .compression_method(file.compression());
if let Some(perms) = file.unix_mode() { if let Some(perms) = file.unix_mode() {
@ -759,7 +760,8 @@ impl<W: Write + io::Seek> ZipWriter<W> {
} }
let central_size = writer.seek(io::SeekFrom::Current(0))? - central_start; let central_size = writer.seek(io::SeekFrom::Current(0))? - central_start;
if self.files.len() > 0xFFFF || central_size > 0xFFFFFFFF || central_start > 0xFFFFFFFF if self.files.len() > spec::ZIP64_ENTRY_THR
|| central_size.max(central_start) > spec::ZIP64_BYTES_THR
{ {
let zip64_footer = spec::Zip64CentralDirectoryEnd { let zip64_footer = spec::Zip64CentralDirectoryEnd {
version_made_by: DEFAULT_VERSION as u16, version_made_by: DEFAULT_VERSION as u16,
@ -783,27 +785,15 @@ impl<W: Write + io::Seek> ZipWriter<W> {
zip64_footer.write(writer)?; zip64_footer.write(writer)?;
} }
let number_of_files = if self.files.len() > 0xFFFF { let number_of_files = self.files.len().min(spec::ZIP64_ENTRY_THR) as u16;
0xFFFF
} else {
self.files.len() as u16
};
let footer = spec::CentralDirectoryEnd { let footer = spec::CentralDirectoryEnd {
disk_number: 0, disk_number: 0,
disk_with_central_directory: 0, disk_with_central_directory: 0,
zip_file_comment: self.comment.clone(), zip_file_comment: self.comment.clone(),
number_of_files_on_this_disk: number_of_files, number_of_files_on_this_disk: number_of_files,
number_of_files, number_of_files,
central_directory_size: if central_size > 0xFFFFFFFF { central_directory_size: central_size.min(spec::ZIP64_BYTES_THR) as u32,
0xFFFFFFFF central_directory_offset: central_start.min(spec::ZIP64_BYTES_THR) as u32,
} else {
central_size as u32
},
central_directory_offset: if central_start > 0xFFFFFFFF {
0xFFFFFFFF
} else {
central_start as u32
},
}; };
footer.write(writer)?; footer.write(writer)?;
@ -866,7 +856,7 @@ impl<W: Write + io::Seek> GenericZipWriter<W> {
#[allow(deprecated)] #[allow(deprecated)]
match compression { match compression {
CompressionMethod::Stored => { CompressionMethod::Stored => {
if let Some(_) = compression_level { if compression_level.is_some() {
return Err(ZipError::UnsupportedArchive( return Err(ZipError::UnsupportedArchive(
"Unsupported compression level", "Unsupported compression level",
)); ));
@ -917,7 +907,7 @@ impl<W: Write + io::Seek> GenericZipWriter<W> {
bare, bare,
clamp_opt( clamp_opt(
compression_level.unwrap_or(zstd::DEFAULT_COMPRESSION_LEVEL), compression_level.unwrap_or(zstd::DEFAULT_COMPRESSION_LEVEL),
zstd::compression_level_range().clone(), zstd::compression_level_range(),
) )
.ok_or(ZipError::UnsupportedArchive( .ok_or(ZipError::UnsupportedArchive(
"Unsupported compression level", "Unsupported compression level",
@ -1027,18 +1017,14 @@ fn write_local_file_header<T: Write>(writer: &mut T, file: &ZipFileData) -> ZipR
writer.write_u16::<LittleEndian>(file.last_modified_time.datepart())?; writer.write_u16::<LittleEndian>(file.last_modified_time.datepart())?;
// crc-32 // crc-32
writer.write_u32::<LittleEndian>(file.crc32)?; writer.write_u32::<LittleEndian>(file.crc32)?;
// compressed size // compressed size and uncompressed size
writer.write_u32::<LittleEndian>(if file.compressed_size > 0xFFFFFFFF { if file.large_file {
0xFFFFFFFF writer.write_u32::<LittleEndian>(spec::ZIP64_BYTES_THR as u32)?;
writer.write_u32::<LittleEndian>(spec::ZIP64_BYTES_THR as u32)?;
} else { } else {
file.compressed_size as u32 writer.write_u32::<LittleEndian>(file.compressed_size as u32)?;
})?; writer.write_u32::<LittleEndian>(file.uncompressed_size as u32)?;
// uncompressed size }
writer.write_u32::<LittleEndian>(if file.uncompressed_size > 0xFFFFFFFF {
0xFFFFFFFF
} else {
file.uncompressed_size as u32
})?;
// file name length // file name length
writer.write_u16::<LittleEndian>(file.file_name.as_bytes().len() as u16)?; writer.write_u16::<LittleEndian>(file.file_name.as_bytes().len() as u16)?;
// extra field length // extra field length
@ -1061,27 +1047,19 @@ fn update_local_file_header<T: Write + io::Seek>(
const CRC32_OFFSET: u64 = 14; const CRC32_OFFSET: u64 = 14;
writer.seek(io::SeekFrom::Start(file.header_start + CRC32_OFFSET))?; writer.seek(io::SeekFrom::Start(file.header_start + CRC32_OFFSET))?;
writer.write_u32::<LittleEndian>(file.crc32)?; writer.write_u32::<LittleEndian>(file.crc32)?;
writer.write_u32::<LittleEndian>(if file.compressed_size > 0xFFFFFFFF { if file.large_file {
if file.large_file { update_local_zip64_extra_field(writer, file)?;
0xFFFFFFFF } else {
} else { // check compressed size as well as it can also be slightly larger than uncompressed size
// compressed size can be slightly larger than uncompressed size if file.compressed_size > spec::ZIP64_BYTES_THR {
return Err(ZipError::Io(io::Error::new( return Err(ZipError::Io(io::Error::new(
io::ErrorKind::Other, io::ErrorKind::Other,
"Large file option has not been set", "Large file option has not been set",
))); )));
} }
} else { writer.write_u32::<LittleEndian>(file.compressed_size as u32)?;
file.compressed_size as u32 // uncompressed size is already checked on write to catch it as soon as possible
})?; writer.write_u32::<LittleEndian>(file.uncompressed_size as u32)?;
writer.write_u32::<LittleEndian>(if file.uncompressed_size > 0xFFFFFFFF {
// uncompressed size is checked on write to catch it as soon as possible
0xFFFFFFFF
} else {
file.uncompressed_size as u32
})?;
if file.large_file {
update_local_zip64_extra_field(writer, file)?;
} }
Ok(()) Ok(())
} }
@ -1115,17 +1093,9 @@ fn write_central_directory_header<T: Write>(writer: &mut T, file: &ZipFileData)
// crc-32 // crc-32
writer.write_u32::<LittleEndian>(file.crc32)?; writer.write_u32::<LittleEndian>(file.crc32)?;
// compressed size // compressed size
writer.write_u32::<LittleEndian>(if file.compressed_size > 0xFFFFFFFF { writer.write_u32::<LittleEndian>(file.compressed_size.min(spec::ZIP64_BYTES_THR) as u32)?;
0xFFFFFFFF
} else {
file.compressed_size as u32
})?;
// uncompressed size // uncompressed size
writer.write_u32::<LittleEndian>(if file.uncompressed_size > 0xFFFFFFFF { writer.write_u32::<LittleEndian>(file.uncompressed_size.min(spec::ZIP64_BYTES_THR) as u32)?;
0xFFFFFFFF
} else {
file.uncompressed_size as u32
})?;
// file name length // file name length
writer.write_u16::<LittleEndian>(file.file_name.as_bytes().len() as u16)?; writer.write_u16::<LittleEndian>(file.file_name.as_bytes().len() as u16)?;
// extra field length // extra field length
@ -1139,11 +1109,7 @@ fn write_central_directory_header<T: Write>(writer: &mut T, file: &ZipFileData)
// external file attributes // external file attributes
writer.write_u32::<LittleEndian>(file.external_attributes)?; writer.write_u32::<LittleEndian>(file.external_attributes)?;
// relative offset of local header // relative offset of local header
writer.write_u32::<LittleEndian>(if file.header_start > 0xFFFFFFFF { writer.write_u32::<LittleEndian>(file.header_start.min(spec::ZIP64_BYTES_THR) as u32)?;
0xFFFFFFFF
} else {
file.header_start as u32
})?;
// file name // file name
writer.write_all(file.file_name.as_bytes())?; writer.write_all(file.file_name.as_bytes())?;
// zip64 extra field // zip64 extra field
@ -1159,7 +1125,7 @@ fn write_central_directory_header<T: Write>(writer: &mut T, file: &ZipFileData)
fn validate_extra_data(file: &ZipFileData) -> ZipResult<()> { fn validate_extra_data(file: &ZipFileData) -> ZipResult<()> {
let mut data = file.extra_field.as_slice(); let mut data = file.extra_field.as_slice();
if data.len() > 0xFFFF { if data.len() > spec::ZIP64_ENTRY_THR {
return Err(ZipError::Io(io::Error::new( return Err(ZipError::Io(io::Error::new(
io::ErrorKind::InvalidData, io::ErrorKind::InvalidData,
"Extra data exceeds extra field", "Extra data exceeds extra field",
@ -1242,9 +1208,9 @@ fn write_central_zip64_extra_field<T: Write>(writer: &mut T, file: &ZipFileData)
// only appear if the corresponding Local or Central // only appear if the corresponding Local or Central
// directory record field is set to 0xFFFF or 0xFFFFFFFF. // directory record field is set to 0xFFFF or 0xFFFFFFFF.
let mut size = 0; let mut size = 0;
let uncompressed_size = file.uncompressed_size > 0xFFFFFFFF; let uncompressed_size = file.uncompressed_size > spec::ZIP64_BYTES_THR;
let compressed_size = file.compressed_size > 0xFFFFFFFF; let compressed_size = file.compressed_size > spec::ZIP64_BYTES_THR;
let header_start = file.header_start > 0xFFFFFFFF; let header_start = file.header_start > spec::ZIP64_BYTES_THR;
if uncompressed_size { if uncompressed_size {
size += 8; size += 8;
} }