Merge pull request #290 from aweinstock314/more-spec-structs

Add `zip::spec::{CentralDirectoryHeader, DataDescriptor, LocalFileHea…
This commit is contained in:
Alexander Zaitsev 2022-03-26 02:13:28 +03:00 committed by GitHub
commit a1da6860ea
Signed by: DevComp
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 396 additions and 126 deletions

View file

@ -640,70 +640,43 @@ pub(crate) fn central_header_to_zip_file<R: Read + io::Seek>(
archive_offset: u64,
) -> ZipResult<ZipFileData> {
let central_header_start = reader.seek(io::SeekFrom::Current(0))?;
// Parse central header
let signature = reader.read_u32::<LittleEndian>()?;
if signature != spec::CENTRAL_DIRECTORY_HEADER_SIGNATURE {
return Err(ZipError::InvalidArchive("Invalid Central Directory header"));
}
let central_header = spec::CentralDirectoryHeader::parse(reader)?;
let version_made_by = reader.read_u16::<LittleEndian>()?;
let _version_to_extract = reader.read_u16::<LittleEndian>()?;
let flags = reader.read_u16::<LittleEndian>()?;
let encrypted = flags & 1 == 1;
let is_utf8 = flags & (1 << 11) != 0;
let using_data_descriptor = flags & (1 << 3) != 0;
let compression_method = reader.read_u16::<LittleEndian>()?;
let last_mod_time = reader.read_u16::<LittleEndian>()?;
let last_mod_date = reader.read_u16::<LittleEndian>()?;
let crc32 = reader.read_u32::<LittleEndian>()?;
let compressed_size = reader.read_u32::<LittleEndian>()?;
let uncompressed_size = reader.read_u32::<LittleEndian>()?;
let file_name_length = reader.read_u16::<LittleEndian>()? as usize;
let extra_field_length = reader.read_u16::<LittleEndian>()? as usize;
let file_comment_length = reader.read_u16::<LittleEndian>()? as usize;
let _disk_number = reader.read_u16::<LittleEndian>()?;
let _internal_file_attributes = reader.read_u16::<LittleEndian>()?;
let external_file_attributes = reader.read_u32::<LittleEndian>()?;
let offset = reader.read_u32::<LittleEndian>()? as u64;
let mut file_name_raw = vec![0; file_name_length];
reader.read_exact(&mut file_name_raw)?;
let mut extra_field = vec![0; extra_field_length];
reader.read_exact(&mut extra_field)?;
let mut file_comment_raw = vec![0; file_comment_length];
reader.read_exact(&mut file_comment_raw)?;
let file_name = match is_utf8 {
true => String::from_utf8_lossy(&*file_name_raw).into_owned(),
false => file_name_raw.clone().from_cp437(),
let file_name = match central_header.flags.is_utf8() {
true => String::from_utf8_lossy(&*central_header.file_name_raw).into_owned(),
false => central_header.file_name_raw.clone().from_cp437(),
};
let file_comment = match is_utf8 {
true => String::from_utf8_lossy(&*file_comment_raw).into_owned(),
false => file_comment_raw.from_cp437(),
let file_comment = match central_header.flags.is_utf8() {
true => String::from_utf8_lossy(&*central_header.file_comment_raw).into_owned(),
false => central_header.file_comment_raw.clone().from_cp437(),
};
// Construct the result
let mut result = ZipFileData {
system: System::from_u8((version_made_by >> 8) as u8),
version_made_by: version_made_by as u8,
encrypted,
using_data_descriptor,
system: System::from_u8((central_header.version_made_by >> 8) as u8),
version_made_by: central_header.version_made_by as u8,
encrypted: central_header.flags.encrypted(),
using_data_descriptor: central_header.flags.using_data_descriptor(),
compression_method: {
#[allow(deprecated)]
CompressionMethod::from_u16(compression_method)
CompressionMethod::from_u16(central_header.compression_method)
},
compression_level: None,
last_modified_time: DateTime::from_msdos(last_mod_date, last_mod_time),
crc32,
compressed_size: compressed_size as u64,
uncompressed_size: uncompressed_size as u64,
last_modified_time: DateTime::from_msdos(
central_header.last_mod_date,
central_header.last_mod_time,
),
crc32: central_header.crc32,
compressed_size: central_header.compressed_size as u64,
uncompressed_size: central_header.uncompressed_size as u64,
file_name,
file_name_raw,
extra_field,
file_name_raw: central_header.file_name_raw,
extra_field: central_header.extra_field,
file_comment,
header_start: offset,
header_start: central_header.offset as u64,
central_header_start,
data_start: AtomicU64::new(0),
external_attributes: external_file_attributes,
external_attributes: central_header.external_file_attributes,
large_file: false,
aes_mode: None,
};

View file

@ -1,3 +1,4 @@
#![allow(missing_docs, dead_code)]
use crate::result::{ZipError, ZipResult};
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use std::io;
@ -8,10 +9,12 @@ pub const CENTRAL_DIRECTORY_HEADER_SIGNATURE: u32 = 0x02014b50;
const CENTRAL_DIRECTORY_END_SIGNATURE: u32 = 0x06054b50;
pub const ZIP64_CENTRAL_DIRECTORY_END_SIGNATURE: u32 = 0x06064b50;
const ZIP64_CENTRAL_DIRECTORY_END_LOCATOR_SIGNATURE: u32 = 0x07064b50;
pub const DATA_DESCRIPTOR_SIGNATURE: u32 = 0x08074b50;
pub const ZIP64_BYTES_THR: u64 = u32::MAX as u64;
pub const ZIP64_ENTRY_THR: usize = u16::MAX as usize;
#[derive(Clone, Debug, PartialEq)]
pub struct CentralDirectoryEnd {
pub disk_number: u16,
pub disk_with_central_directory: u16,
@ -23,6 +26,10 @@ pub struct CentralDirectoryEnd {
}
impl CentralDirectoryEnd {
pub fn len(&self) -> usize {
22 + self.zip_file_comment.len()
}
pub fn parse<T: Read>(reader: &mut T) -> ZipResult<CentralDirectoryEnd> {
let magic = reader.read_u32::<LittleEndian>()?;
if magic != CENTRAL_DIRECTORY_END_SIGNATURE {
@ -205,3 +212,315 @@ impl Zip64CentralDirectoryEnd {
Ok(())
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct GeneralPurposeBitFlags(pub u16);
impl GeneralPurposeBitFlags {
#[inline]
pub fn encrypted(&self) -> bool {
self.0 & 1 == 1
}
#[inline]
pub fn is_utf8(&self) -> bool {
self.0 & (1 << 11) != 0
}
#[inline]
pub fn using_data_descriptor(&self) -> bool {
self.0 & (1 << 3) != 0
}
#[inline]
pub fn set_using_data_descriptor(&mut self, b: bool) {
self.0 &= !(1 << 3);
if b {
self.0 |= 1 << 3;
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct CentralDirectoryHeader {
pub version_made_by: u16,
pub version_to_extract: u16,
pub flags: GeneralPurposeBitFlags,
pub compression_method: u16,
pub last_mod_time: u16,
pub last_mod_date: u16,
pub crc32: u32,
pub compressed_size: u32,
pub uncompressed_size: u32,
pub disk_number: u16,
pub internal_file_attributes: u16,
pub external_file_attributes: u32,
pub offset: u32,
pub file_name_raw: Vec<u8>,
pub extra_field: Vec<u8>,
pub file_comment_raw: Vec<u8>,
}
impl CentralDirectoryHeader {
pub fn len(&self) -> usize {
46 + self.file_name_raw.len() + self.extra_field.len() + self.file_comment_raw.len()
}
pub fn parse<R: Read>(reader: &mut R) -> ZipResult<CentralDirectoryHeader> {
let signature = reader.read_u32::<LittleEndian>()?;
if signature != CENTRAL_DIRECTORY_HEADER_SIGNATURE {
return Err(ZipError::InvalidArchive("Invalid Central Directory header"));
}
let version_made_by = reader.read_u16::<LittleEndian>()?;
let version_to_extract = reader.read_u16::<LittleEndian>()?;
let flags = reader.read_u16::<LittleEndian>()?;
let compression_method = reader.read_u16::<LittleEndian>()?;
let last_mod_time = reader.read_u16::<LittleEndian>()?;
let last_mod_date = reader.read_u16::<LittleEndian>()?;
let crc32 = reader.read_u32::<LittleEndian>()?;
let compressed_size = reader.read_u32::<LittleEndian>()?;
let uncompressed_size = reader.read_u32::<LittleEndian>()?;
let file_name_length = reader.read_u16::<LittleEndian>()?;
let extra_field_length = reader.read_u16::<LittleEndian>()?;
let file_comment_length = reader.read_u16::<LittleEndian>()?;
let disk_number = reader.read_u16::<LittleEndian>()?;
let internal_file_attributes = reader.read_u16::<LittleEndian>()?;
let external_file_attributes = reader.read_u32::<LittleEndian>()?;
let offset = reader.read_u32::<LittleEndian>()?;
let mut file_name_raw = vec![0; file_name_length as usize];
reader.read_exact(&mut file_name_raw)?;
let mut extra_field = vec![0; extra_field_length as usize];
reader.read_exact(&mut extra_field)?;
let mut file_comment_raw = vec![0; file_comment_length as usize];
reader.read_exact(&mut file_comment_raw)?;
Ok(CentralDirectoryHeader {
version_made_by,
version_to_extract,
flags: GeneralPurposeBitFlags(flags),
compression_method,
last_mod_time,
last_mod_date,
crc32,
compressed_size,
uncompressed_size,
disk_number,
internal_file_attributes,
external_file_attributes,
offset,
file_name_raw,
extra_field,
file_comment_raw,
})
}
pub fn write<T: Write>(&self, writer: &mut T) -> ZipResult<()> {
writer.write_u32::<LittleEndian>(CENTRAL_DIRECTORY_HEADER_SIGNATURE)?;
writer.write_u16::<LittleEndian>(self.version_made_by)?;
writer.write_u16::<LittleEndian>(self.version_to_extract)?;
writer.write_u16::<LittleEndian>(self.flags.0)?;
writer.write_u16::<LittleEndian>(self.compression_method)?;
writer.write_u16::<LittleEndian>(self.last_mod_time)?;
writer.write_u16::<LittleEndian>(self.last_mod_date)?;
writer.write_u32::<LittleEndian>(self.crc32)?;
writer.write_u32::<LittleEndian>(self.compressed_size)?;
writer.write_u32::<LittleEndian>(self.uncompressed_size)?;
writer.write_u16::<LittleEndian>(self.file_name_raw.len() as u16)?;
writer.write_u16::<LittleEndian>(self.extra_field.len() as u16)?;
writer.write_u16::<LittleEndian>(self.file_comment_raw.len() as u16)?;
writer.write_u16::<LittleEndian>(self.disk_number)?;
writer.write_u16::<LittleEndian>(self.internal_file_attributes)?;
writer.write_u32::<LittleEndian>(self.external_file_attributes)?;
writer.write_u32::<LittleEndian>(self.offset)?;
writer.write_all(&self.file_name_raw)?;
writer.write_all(&self.extra_field)?;
writer.write_all(&self.file_comment_raw)?;
Ok(())
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct LocalFileHeader {
pub version_to_extract: u16,
pub flags: GeneralPurposeBitFlags,
pub compression_method: u16,
pub last_mod_time: u16,
pub last_mod_date: u16,
pub crc32: u32,
pub compressed_size: u32,
pub uncompressed_size: u32,
pub file_name_raw: Vec<u8>,
pub extra_field: Vec<u8>,
}
impl LocalFileHeader {
pub fn len(&self) -> usize {
30 + self.file_name_raw.len() + self.extra_field.len()
}
pub fn parse<R: Read>(reader: &mut R) -> ZipResult<LocalFileHeader> {
let signature = reader.read_u32::<LittleEndian>()?;
if signature != LOCAL_FILE_HEADER_SIGNATURE {
return Err(ZipError::InvalidArchive("Invalid local file header"));
}
let version_to_extract = reader.read_u16::<LittleEndian>()?;
let flags = reader.read_u16::<LittleEndian>()?;
let compression_method = reader.read_u16::<LittleEndian>()?;
let last_mod_time = reader.read_u16::<LittleEndian>()?;
let last_mod_date = reader.read_u16::<LittleEndian>()?;
let crc32 = reader.read_u32::<LittleEndian>()?;
let compressed_size = reader.read_u32::<LittleEndian>()?;
let uncompressed_size = reader.read_u32::<LittleEndian>()?;
let file_name_length = reader.read_u16::<LittleEndian>()?;
let extra_field_length = reader.read_u16::<LittleEndian>()?;
let mut file_name_raw = vec![0; file_name_length as usize];
reader.read_exact(&mut file_name_raw)?;
let mut extra_field = vec![0; extra_field_length as usize];
reader.read_exact(&mut extra_field)?;
Ok(LocalFileHeader {
version_to_extract,
flags: GeneralPurposeBitFlags(flags),
compression_method,
last_mod_time,
last_mod_date,
crc32,
compressed_size,
uncompressed_size,
file_name_raw,
extra_field,
})
}
pub fn write<T: Write>(&self, writer: &mut T) -> ZipResult<()> {
writer.write_u32::<LittleEndian>(LOCAL_FILE_HEADER_SIGNATURE)?;
writer.write_u16::<LittleEndian>(self.version_to_extract)?;
writer.write_u16::<LittleEndian>(self.flags.0)?;
writer.write_u16::<LittleEndian>(self.compression_method)?;
writer.write_u16::<LittleEndian>(self.last_mod_time)?;
writer.write_u16::<LittleEndian>(self.last_mod_date)?;
writer.write_u32::<LittleEndian>(self.crc32)?;
writer.write_u32::<LittleEndian>(self.compressed_size)?;
writer.write_u32::<LittleEndian>(self.uncompressed_size)?;
writer.write_u16::<LittleEndian>(self.file_name_raw.len() as u16)?;
writer.write_u16::<LittleEndian>(self.extra_field.len() as u16)?;
writer.write_all(&self.file_name_raw)?;
writer.write_all(&self.extra_field)?;
Ok(())
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct DataDescriptor {
pub crc32: u32,
pub compressed_size: u32,
pub uncompressed_size: u32,
}
impl DataDescriptor {
pub fn read<T: Read>(reader: &mut T) -> ZipResult<DataDescriptor> {
let first_word = reader.read_u32::<LittleEndian>()?;
let crc32 = if first_word == DATA_DESCRIPTOR_SIGNATURE {
reader.read_u32::<LittleEndian>()?
} else {
first_word
};
let compressed_size = reader.read_u32::<LittleEndian>()?;
let uncompressed_size = reader.read_u32::<LittleEndian>()?;
Ok(DataDescriptor {
crc32,
compressed_size,
uncompressed_size,
})
}
pub fn write<T: Write>(&self, writer: &mut T) -> ZipResult<()> {
writer.write_u32::<LittleEndian>(DATA_DESCRIPTOR_SIGNATURE)?;
writer.write_u32::<LittleEndian>(self.crc32)?;
writer.write_u32::<LittleEndian>(self.compressed_size)?;
writer.write_u32::<LittleEndian>(self.uncompressed_size)?;
Ok(())
}
}
#[cfg(test)]
mod test {
use super::{
CentralDirectoryHeader, DataDescriptor, GeneralPurposeBitFlags, LocalFileHeader, ZipResult,
};
use std::io::Cursor;
#[test]
fn test_cdh_roundtrip() -> ZipResult<()> {
let cdh1 = CentralDirectoryHeader {
version_made_by: 1,
version_to_extract: 2,
flags: GeneralPurposeBitFlags(3),
compression_method: 4,
last_mod_time: 5,
last_mod_date: 6,
crc32: 7,
compressed_size: 8,
uncompressed_size: 9,
disk_number: 10,
internal_file_attributes: 11,
external_file_attributes: 12,
offset: 13,
file_name_raw: b"a".to_vec(),
extra_field: b"bb".to_vec(),
file_comment_raw: b"ccc".to_vec(),
};
let mut bytes = Vec::new();
{
let mut cursor = Cursor::new(&mut bytes);
cdh1.write(&mut cursor)?;
}
let cdh2 = CentralDirectoryHeader::parse(&mut &bytes[..])?;
assert_eq!(cdh1, cdh2);
Ok(())
}
#[test]
fn test_lfh_roundtrip() -> ZipResult<()> {
let lfh1 = LocalFileHeader {
version_to_extract: 1,
flags: GeneralPurposeBitFlags(2),
compression_method: 3,
last_mod_time: 4,
last_mod_date: 5,
crc32: 6,
compressed_size: 7,
uncompressed_size: 8,
file_name_raw: b"a".to_vec(),
extra_field: b"bb".to_vec(),
};
let mut bytes = Vec::new();
{
let mut cursor = Cursor::new(&mut bytes);
lfh1.write(&mut cursor)?;
}
let lfh2 = LocalFileHeader::parse(&mut &bytes[..])?;
assert_eq!(lfh1, lfh2);
Ok(())
}
#[test]
fn test_dd_roundtrip() -> ZipResult<()> {
let dd1 = DataDescriptor {
crc32: 1,
compressed_size: 2,
uncompressed_size: 3,
};
let mut bytes = Vec::new();
{
let mut cursor = Cursor::new(&mut bytes);
dd1.write(&mut cursor)?;
}
let dd2 = DataDescriptor::read(&mut &bytes[..])?;
assert_eq!(dd1, dd2);
let dd3 = DataDescriptor::read(&mut &bytes[4..])?;
assert_eq!(dd1, dd3);
Ok(())
}
}

View file

@ -998,44 +998,38 @@ fn clamp_opt<T: Ord + Copy>(value: T, range: RangeInclusive<T>) -> Option<T> {
}
fn write_local_file_header<T: Write>(writer: &mut T, file: &ZipFileData) -> ZipResult<()> {
// local file header signature
writer.write_u32::<LittleEndian>(spec::LOCAL_FILE_HEADER_SIGNATURE)?;
// version needed to extract
writer.write_u16::<LittleEndian>(file.version_needed())?;
// general purpose bit flag
let flag = if !file.file_name.is_ascii() {
let flags = if !file.file_name.is_ascii() {
1u16 << 11
} else {
0
};
writer.write_u16::<LittleEndian>(flag)?;
// Compression method
#[allow(deprecated)]
writer.write_u16::<LittleEndian>(file.compression_method.to_u16())?;
// last mod file time and last mod file date
writer.write_u16::<LittleEndian>(file.last_modified_time.timepart())?;
writer.write_u16::<LittleEndian>(file.last_modified_time.datepart())?;
// crc-32
writer.write_u32::<LittleEndian>(file.crc32)?;
// compressed size and uncompressed size
if file.large_file {
writer.write_u32::<LittleEndian>(spec::ZIP64_BYTES_THR as u32)?;
writer.write_u32::<LittleEndian>(spec::ZIP64_BYTES_THR as u32)?;
let compression_method = file.compression_method.to_u16();
let compressed_size = file.compressed_size.min(spec::ZIP64_BYTES_THR) as u32;
let uncompressed_size = file.uncompressed_size.min(spec::ZIP64_BYTES_THR) as u32;
let mut extra_field = if file.large_file {
let mut zip64_extra_field = vec![0; 20];
write_local_zip64_extra_field(&mut zip64_extra_field, file)?;
zip64_extra_field
} else {
writer.write_u32::<LittleEndian>(file.compressed_size as u32)?;
writer.write_u32::<LittleEndian>(file.uncompressed_size as u32)?;
}
// file name length
writer.write_u16::<LittleEndian>(file.file_name.as_bytes().len() as u16)?;
// extra field length
let extra_field_length = if file.large_file { 20 } else { 0 } + file.extra_field.len() as u16;
writer.write_u16::<LittleEndian>(extra_field_length)?;
// file name
writer.write_all(file.file_name.as_bytes())?;
// zip64 extra field
if file.large_file {
write_local_zip64_extra_field(writer, file)?;
}
Vec::new()
};
extra_field.extend_from_slice(&file.extra_field[..]);
let local_file_header = spec::LocalFileHeader {
version_to_extract: file.version_needed(),
flags: spec::GeneralPurposeBitFlags(flags),
compression_method,
last_mod_time: file.last_modified_time.timepart(),
last_mod_date: file.last_modified_time.datepart(),
crc32: file.crc32,
compressed_size,
uncompressed_size,
file_name_raw: file.file_name.as_bytes().to_vec(),
extra_field,
};
local_file_header.write(writer)?;
Ok(())
}
@ -1070,56 +1064,40 @@ fn write_central_directory_header<T: Write>(writer: &mut T, file: &ZipFileData)
let zip64_extra_field_length =
write_central_zip64_extra_field(&mut zip64_extra_field.as_mut(), file)?;
// central file header signature
writer.write_u32::<LittleEndian>(spec::CENTRAL_DIRECTORY_HEADER_SIGNATURE)?;
// version made by
let version_made_by = (file.system as u16) << 8 | (file.version_made_by as u16);
writer.write_u16::<LittleEndian>(version_made_by)?;
// version needed to extract
writer.write_u16::<LittleEndian>(file.version_needed())?;
// general puprose bit flag
let flag = if !file.file_name.is_ascii() {
let flags = if !file.file_name.is_ascii() {
1u16 << 11
} else {
0
};
writer.write_u16::<LittleEndian>(flag)?;
// compression method
#[allow(deprecated)]
writer.write_u16::<LittleEndian>(file.compression_method.to_u16())?;
// last mod file time + date
writer.write_u16::<LittleEndian>(file.last_modified_time.timepart())?;
writer.write_u16::<LittleEndian>(file.last_modified_time.datepart())?;
// crc-32
writer.write_u32::<LittleEndian>(file.crc32)?;
// compressed size
writer.write_u32::<LittleEndian>(file.compressed_size.min(spec::ZIP64_BYTES_THR) as u32)?;
// uncompressed size
writer.write_u32::<LittleEndian>(file.uncompressed_size.min(spec::ZIP64_BYTES_THR) as u32)?;
// file name length
writer.write_u16::<LittleEndian>(file.file_name.as_bytes().len() as u16)?;
// extra field length
writer.write_u16::<LittleEndian>(zip64_extra_field_length + file.extra_field.len() as u16)?;
// file comment length
writer.write_u16::<LittleEndian>(0)?;
// disk number start
writer.write_u16::<LittleEndian>(0)?;
// internal file attribytes
writer.write_u16::<LittleEndian>(0)?;
// external file attributes
writer.write_u32::<LittleEndian>(file.external_attributes)?;
// relative offset of local header
writer.write_u32::<LittleEndian>(file.header_start.min(spec::ZIP64_BYTES_THR) as u32)?;
// file name
writer.write_all(file.file_name.as_bytes())?;
// zip64 extra field
writer.write_all(&zip64_extra_field[..zip64_extra_field_length as usize])?;
// extra field
writer.write_all(&file.extra_field)?;
// file comment
// <none>
let compression_method = file.compression_method.to_u16();
let compressed_size = file.compressed_size.min(spec::ZIP64_BYTES_THR) as u32;
let uncompressed_size = file.uncompressed_size.min(spec::ZIP64_BYTES_THR) as u32;
let offset = file.header_start.min(spec::ZIP64_BYTES_THR) as u32;
Ok(())
let mut extra_field = zip64_extra_field[..zip64_extra_field_length as usize].to_vec();
extra_field.extend_from_slice(&file.extra_field[..]);
let header = spec::CentralDirectoryHeader {
version_made_by: (file.system as u16) << 8 | (file.version_made_by as u16),
version_to_extract: file.version_needed(),
flags: spec::GeneralPurposeBitFlags(flags),
compression_method,
last_mod_time: file.last_modified_time.timepart(),
last_mod_date: file.last_modified_time.datepart(),
crc32: file.crc32,
compressed_size,
uncompressed_size,
disk_number: 0,
internal_file_attributes: 0,
external_file_attributes: file.external_attributes,
offset,
file_name_raw: file.file_name.as_bytes().to_vec(),
extra_field,
file_comment_raw: Vec::new(),
};
header.write(writer)
}
fn validate_extra_data(file: &ZipFileData) -> ZipResult<()> {