use super::error;
use std::{borrow::Cow, io};
use chrono::{DateTime, Datelike, TimeZone, Timelike, Utc};
use tokio::io::AsyncReadExt as _;
const LOCAL_FILE_HEADER_SIGNATURE: u32 = 0x04034b50;
const DATA_DESCRIPTOR_SIGNATURE: u32 = 0x08074b50;
const CENTRAL_DIRECTORY_HEADER_SIGNATURE: u32 = 0x02014b50;
const CENTRAL_DIRECTORY_END_SIGNATURE: u32 = 0x06054b50;
const ZIP64_CENTRAL_DIRECTORY_END_SIGNATURE: u32 = 0x06064b50;
const ZIP64_CENTRAL_DIRECTORY_END_LOCATOR_SIGNATURE: u32 = 0x07064b50;
const UNIX: u8 = 3;
const DEFAULT_VERSION: u16 = 45; const VERSION_MADE_BY: u16 = ((UNIX as u16) << 8) | DEFAULT_VERSION;
pub(super) const ZIP64_THRESHOLD_FILES: u64 = u16::MAX as u64;
pub(super) const ZIP64_THRESHOLD_BYTES: u64 = u32::MAX as u64;
fn to_msdos_time(dt: DateTime<Utc>) -> u16 {
((dt.second() as u16) >> 1) | ((dt.minute() as u16) << 5) | ((dt.hour() as u16) << 11)
}
fn to_msdos_date(dt: DateTime<Utc>) -> u16 {
(dt.day() as u16) | ((dt.month() as u16) << 5) | (((dt.year() - 1980) as u16) << 9)
}
fn from_msdos_datetime(date: u16, time: u16) -> DateTime<Utc> {
let year = ((date >> 9) & 0b1111111) as i32 + 1980;
let month = ((date >> 5) & 0b1111) as u32;
let day = (date & 0b11111) as u32;
let hour = ((time >> 11) & 0b11111) as u32;
let minute = ((time >> 5) & 0b111111) as u32;
let second = ((time & 0b11111) as u32) << 1;
if let chrono::offset::LocalResult::Single(dt) =
Utc.with_ymd_and_hms(year, month, day, hour, minute, second)
{
dt
} else {
"1980-01-01T00:00:00Z"
.parse::<DateTime<Utc>>()
.expect("this conversion can never fail")
}
}
fn cp437_to_utf8(data: &[u8]) -> Cow<'_, str> {
String::from_utf8_lossy(data)
}
#[derive(Debug, Default, PartialEq)]
pub(super) struct LocalFileHeader<'a> {
pub(super) compression_method: u16,
pub(super) crc32: u32,
pub(super) flags: u16,
pub(super) modified: DateTime<Utc>,
pub(super) name: Cow<'a, str>,
pub(super) size: u32,
pub(super) header_size: u32,
}
impl LocalFileHeader<'_> {
pub(super) fn build(&self) -> Vec<u8> {
let mut buffer = Vec::with_capacity(30 + self.name.len());
buffer.extend(&LOCAL_FILE_HEADER_SIGNATURE.to_le_bytes());
buffer.extend(&DEFAULT_VERSION.to_le_bytes());
buffer.extend(&self.flags.to_le_bytes());
buffer.extend(&self.compression_method.to_le_bytes());
buffer.extend(&to_msdos_time(self.modified).to_le_bytes());
buffer.extend(&to_msdos_date(self.modified).to_le_bytes());
buffer.extend(&self.crc32.to_le_bytes());
buffer.extend(&self.size.to_le_bytes());
buffer.extend(&self.size.to_le_bytes());
buffer.extend(&(self.name.len() as u16).to_le_bytes());
buffer.extend(&0u16.to_le_bytes());
buffer.extend(self.name.as_bytes());
buffer
}
pub(super) async fn parse<R: tokio::io::AsyncBufRead + Unpin>(
reader: &mut R,
) -> Result<Self, error::HeaderParseError> {
const HEADER_MIN_LENGTH: usize = 30;
let mut buffer = [0u8; HEADER_MIN_LENGTH];
reader.read_exact(&mut buffer).await?;
if buffer[0..4] != LOCAL_FILE_HEADER_SIGNATURE.to_le_bytes() {
return Err(error::HeaderParseError::InvalidSignature);
}
let name_len = bytes_to_u16(&buffer, 26) as usize;
let flags = bytes_to_u16(&buffer, 6);
let is_utf8 = flags & (1 << 11) != 0;
let mut name = vec![0u8; name_len];
reader.read_exact(&mut name).await?;
let name = if is_utf8 {
String::from_utf8_lossy(&name)
} else {
cp437_to_utf8(&name)
};
let extra_field_len = bytes_to_u16(&buffer, 28);
Ok(Self {
compression_method: bytes_to_u16(&buffer, 8),
crc32: bytes_to_u32(&buffer, 14),
flags,
modified: from_msdos_datetime(bytes_to_u16(&buffer, 12), bytes_to_u16(&buffer, 10)),
name: name.into_owned().into(),
size: bytes_to_u32(&buffer, 18),
header_size: HEADER_MIN_LENGTH as u32 + name_len as u32 + extra_field_len as u32,
})
}
}
#[derive(Debug)]
pub(super) struct DataDescriptor {
pub(super) crc32: u32,
pub(super) size: u64,
}
impl DataDescriptor {
pub(super) fn build(&self) -> Vec<u8> {
let mut buffer = Vec::with_capacity(24);
buffer.extend(&DATA_DESCRIPTOR_SIGNATURE.to_le_bytes());
buffer.extend(&self.crc32.to_le_bytes());
if self.size >= ZIP64_THRESHOLD_BYTES {
buffer.extend(&self.size.to_le_bytes()); buffer.extend(&self.size.to_le_bytes()); } else {
buffer.extend(&(self.size as u32).to_le_bytes()); buffer.extend(&(self.size as u32).to_le_bytes()); }
buffer
}
}
#[derive(Debug, Default, PartialEq)]
pub(super) struct CentralDirectoryHeader<'a> {
pub(super) compression_method: u16,
pub(super) crc32: u32,
pub(super) external_attributes: u32,
pub(super) flags: u16,
pub(super) modified: DateTime<Utc>,
pub(super) name: Cow<'a, str>,
pub(super) offset: u64,
pub(super) size: u64,
}
impl CentralDirectoryHeader<'_> {
pub(super) fn build(&self) -> Vec<u8> {
let mut buffer = Vec::with_capacity(82);
let size_u32 = self.size.min(ZIP64_THRESHOLD_BYTES) as u32;
let zip64_extra_field = self.build_zip64_extra_field();
buffer.extend(&CENTRAL_DIRECTORY_HEADER_SIGNATURE.to_le_bytes());
buffer.extend(&VERSION_MADE_BY.to_le_bytes());
buffer.extend(&DEFAULT_VERSION.to_le_bytes());
buffer.extend(&self.flags.to_le_bytes());
buffer.extend(&self.compression_method.to_le_bytes());
buffer.extend(&to_msdos_time(self.modified).to_le_bytes());
buffer.extend(&to_msdos_date(self.modified).to_le_bytes());
buffer.extend(&self.crc32.to_le_bytes());
buffer.extend(&size_u32.to_le_bytes());
buffer.extend(&size_u32.to_le_bytes());
buffer.extend(&(self.name.len() as u16).to_le_bytes());
buffer.extend(&(zip64_extra_field.len() as u16).to_le_bytes());
buffer.extend(&0u16.to_le_bytes());
buffer.extend(&0u16.to_le_bytes());
buffer.extend(&0u16.to_le_bytes());
buffer.extend(&self.external_attributes.to_le_bytes());
buffer.extend(&(self.offset.min(ZIP64_THRESHOLD_BYTES) as u32).to_le_bytes());
buffer.extend(self.name.as_bytes());
buffer.extend(zip64_extra_field);
buffer
}
fn zip64_extra_field_size(size: u64, offset: u64) -> u16 {
let is_zip64_size = size >= ZIP64_THRESHOLD_BYTES;
let is_zip64_offset = offset >= ZIP64_THRESHOLD_BYTES;
match (is_zip64_size, is_zip64_offset) {
(true, true) => 24,
(true, false) => 16,
(false, true) => 8,
(false, false) => 0,
}
}
fn build_zip64_extra_field(&self) -> Vec<u8> {
let size_internal = Self::zip64_extra_field_size(self.size, self.offset);
if size_internal == 0 {
return Vec::with_capacity(0);
}
let mut buffer = Vec::with_capacity(28);
buffer.extend(&1u16.to_le_bytes());
buffer.extend(size_internal.to_le_bytes());
if self.size >= ZIP64_THRESHOLD_BYTES {
buffer.extend(self.size.to_le_bytes());
buffer.extend(self.size.to_le_bytes());
}
if self.offset >= ZIP64_THRESHOLD_BYTES {
buffer.extend(self.offset.to_le_bytes());
}
buffer
}
pub(super) fn find<R: io::Read + io::Seek>(
reader: &mut R,
) -> Result<u64, error::SeekHeaderError> {
let mut buffer = [0u8; 4];
while let Ok(()) = reader.read_exact(&mut buffer[3..4]) {
if buffer == CENTRAL_DIRECTORY_HEADER_SIGNATURE.to_le_bytes() {
return Ok(reader.seek(io::SeekFrom::Current(-4))?);
}
buffer.rotate_left(1);
}
Err(error::SeekHeaderError::NotFound(
"Zip/Zip64 central directory header not found",
))
}
fn parse_extra_field<R: io::Read + io::Seek>(
reader: &mut R,
file_size: u64,
file_offset: u64,
extra_field_size: u16,
) -> Result<(u64, u64), error::HeaderParseError> {
let position_start = reader.stream_position()?;
let mut file_size = file_size;
let mut file_offset = file_offset;
if file_size == ZIP64_THRESHOLD_BYTES || file_offset == ZIP64_THRESHOLD_BYTES {
if extra_field_size < Self::zip64_extra_field_size(file_size, file_offset) {
return Err(error::HeaderParseError::InvalidExtraField);
}
let mut buffer = [0u8; 4];
loop {
reader.read_exact(&mut buffer)?;
let field_header = bytes_to_u16(&buffer, 0);
let field_size = bytes_to_u16(&buffer, 2);
if field_header == 1 {
if file_size == ZIP64_THRESHOLD_BYTES {
let mut buffer = [0u8; 16];
reader.read_exact(&mut buffer)?;
let uncompressed_size = bytes_to_u64(&buffer, 0);
let compressed_size = bytes_to_u64(&buffer, 8);
if uncompressed_size != compressed_size {
return Err(error::HeaderParseError::InvalidInput);
}
file_size = uncompressed_size;
}
if file_offset == ZIP64_THRESHOLD_BYTES {
let mut buffer = [0u8; 8];
reader.read_exact(&mut buffer)?;
file_offset = u64::from_le_bytes(buffer);
}
break;
}
let position_current = reader.seek(io::SeekFrom::Current(field_size as i64))?;
if position_current - position_start >= extra_field_size as u64 {
return Err(error::HeaderParseError::MissingZipInfo(
"invalid Zip64 central directory header: no 'Zip64 info' \
field found in 'extra field' section",
));
}
}
}
let bytes_left =
extra_field_size as i64 - (reader.stream_position()? - position_start) as i64;
reader.seek(io::SeekFrom::Current(bytes_left))?;
Ok((file_size, file_offset))
}
pub(super) fn parse<R: io::Read + io::Seek>(
reader: &mut R,
) -> Result<Self, error::HeaderParseError> {
let mut buffer = [0u8; 46];
reader.read_exact(&mut buffer)?;
if buffer[0..4] != CENTRAL_DIRECTORY_HEADER_SIGNATURE.to_le_bytes() {
return Err(error::HeaderParseError::InvalidSignature);
}
let name_len = bytes_to_u16(&buffer, 28) as usize;
let flags = bytes_to_u16(&buffer, 8);
let is_utf8 = flags & (1 << 11) != 0;
let mut name = vec![0u8; name_len];
reader.read_exact(&mut name)?;
let name = if is_utf8 {
String::from_utf8_lossy(&name)
} else {
cp437_to_utf8(&name)
};
let (size, offset) = Self::parse_extra_field(
reader,
bytes_to_u32(&buffer, 20) as u64,
bytes_to_u32(&buffer, 42) as u64,
bytes_to_u16(&buffer, 30),
)?;
let comment_len = bytes_to_u16(&buffer, 32);
reader.seek(io::SeekFrom::Current(comment_len as i64))?;
Ok(Self {
flags,
compression_method: bytes_to_u16(&buffer, 10),
modified: from_msdos_datetime(bytes_to_u16(&buffer, 14), bytes_to_u16(&buffer, 12)),
crc32: bytes_to_u32(&buffer, 16),
external_attributes: bytes_to_u32(&buffer, 38),
name: name.into_owned().into(),
size,
offset,
})
}
}
fn bytes_to_u16(buf: &[u8], offset: usize) -> u16 {
u16::from_le_bytes([buf[offset], buf[offset + 1]])
}
fn bytes_to_u32(buf: &[u8], offset: usize) -> u32 {
u32::from_le_bytes([
buf[offset],
buf[offset + 1],
buf[offset + 2],
buf[offset + 3],
])
}
fn bytes_to_u64(buf: &[u8], offset: usize) -> u64 {
u64::from_le_bytes([
buf[offset],
buf[offset + 1],
buf[offset + 2],
buf[offset + 3],
buf[offset + 4],
buf[offset + 5],
buf[offset + 6],
buf[offset + 7],
])
}
#[derive(Debug, Default, PartialEq)]
pub(super) struct CentralDirectoryEnd {
pub(super) disk_number: u16,
pub(super) disk_with_central_directory: u16,
pub(super) disk_number_of_records: u16,
pub(super) total_number_of_records: u16,
pub(super) central_directory_size: u32,
pub(super) central_directory_offset: u32,
pub(super) comment: Vec<u8>,
}
impl CentralDirectoryEnd {
const MIN_HEADER_SIZE: usize = 22;
pub(super) fn build(&self) -> Vec<u8> {
let mut buffer = Vec::with_capacity(Self::MIN_HEADER_SIZE);
buffer.extend(&CENTRAL_DIRECTORY_END_SIGNATURE.to_le_bytes());
buffer.extend(&self.disk_number.to_le_bytes());
buffer.extend(&self.disk_with_central_directory.to_le_bytes());
buffer.extend(&self.disk_number_of_records.to_le_bytes());
buffer.extend(&self.total_number_of_records.to_le_bytes());
buffer.extend(&self.central_directory_size.to_le_bytes());
buffer.extend(&self.central_directory_offset.to_le_bytes());
buffer.extend(&(self.comment.len() as u16).to_le_bytes());
buffer.extend(&self.comment);
buffer
}
pub(super) fn find<R: io::Read + io::Seek>(
reader: &mut R,
) -> Result<u64, error::SeekHeaderError> {
const COMMENT_SIZE: u64 = u16::MAX as u64;
let end = reader.seek(io::SeekFrom::End(0))?;
reader.seek(io::SeekFrom::Start(
end.saturating_sub(Self::MIN_HEADER_SIZE as u64 + COMMENT_SIZE),
))?;
let mut buffer = [0u8; 4];
while let Ok(()) = reader.read_exact(&mut buffer[3..4]) {
if buffer == CENTRAL_DIRECTORY_END_SIGNATURE.to_le_bytes() {
return Ok(reader.seek(io::SeekFrom::Current(-4))?);
}
buffer.rotate_left(1);
}
Err(error::SeekHeaderError::NotFound(
"Central directory end not found",
))
}
pub(super) fn parse(reader: &mut impl io::Read) -> Result<Self, error::HeaderParseError> {
let mut buffer = [0u8; Self::MIN_HEADER_SIZE];
reader
.read_exact(&mut buffer)
.map_err(|_| error::HeaderParseError::InvalidSignature)?;
if buffer[0..4] != CENTRAL_DIRECTORY_END_SIGNATURE.to_le_bytes() {
return Err(error::HeaderParseError::InvalidSignature);
}
Ok(Self {
disk_number: bytes_to_u16(&buffer, 4),
disk_with_central_directory: bytes_to_u16(&buffer, 6),
disk_number_of_records: bytes_to_u16(&buffer, 8),
total_number_of_records: bytes_to_u16(&buffer, 10),
central_directory_size: bytes_to_u32(&buffer, 12),
central_directory_offset: bytes_to_u32(&buffer, 16),
comment: {
let mut comment = vec![0u8; bytes_to_u16(&buffer, 20) as usize];
reader.read_exact(&mut comment)?;
comment
},
})
}
pub(super) fn size(&self) -> u64 {
(Self::MIN_HEADER_SIZE + self.comment.len()) as u64
}
}
#[derive(Debug, Clone, PartialEq)]
pub(super) struct Zip64CentralDirectoryEnd {
pub(super) record_size: u64,
pub(super) version_made_by: u16,
pub(super) version_needed_to_extract: u16,
pub(super) disk_number: u32,
pub(super) disk_with_central_directory: u32,
pub(super) disk_number_of_records: u64,
pub(super) total_number_of_records: u64,
pub(super) central_directory_size: u64,
pub(super) central_directory_offset: u64,
}
impl Default for Zip64CentralDirectoryEnd {
fn default() -> Self {
Self {
record_size: 44,
version_made_by: VERSION_MADE_BY,
version_needed_to_extract: DEFAULT_VERSION,
disk_number: 0,
disk_with_central_directory: 0,
disk_number_of_records: 0,
total_number_of_records: 0,
central_directory_size: 0,
central_directory_offset: 0,
}
}
}
impl Zip64CentralDirectoryEnd {
pub(super) fn build(&self) -> Vec<u8> {
let mut buffer = Vec::with_capacity(56);
buffer.extend(&ZIP64_CENTRAL_DIRECTORY_END_SIGNATURE.to_le_bytes());
buffer.extend(&self.record_size.to_le_bytes());
buffer.extend(&self.version_made_by.to_le_bytes());
buffer.extend(&self.version_needed_to_extract.to_le_bytes());
buffer.extend(&self.disk_number.to_le_bytes());
buffer.extend(&self.disk_with_central_directory.to_le_bytes());
buffer.extend(&self.disk_number_of_records.to_le_bytes());
buffer.extend(&self.total_number_of_records.to_le_bytes());
buffer.extend(&self.central_directory_size.to_le_bytes());
buffer.extend(&self.central_directory_offset.to_le_bytes());
buffer
}
pub(super) fn find<R: io::Read + io::Seek>(
reader: &mut R,
) -> Result<u64, error::SeekHeaderError> {
let mut buffer = [0u8; 4];
while let Ok(()) = reader.read_exact(&mut buffer[3..4]) {
if buffer == ZIP64_CENTRAL_DIRECTORY_END_SIGNATURE.to_le_bytes() {
return Ok(reader.seek(io::SeekFrom::Current(-4))?);
}
buffer.rotate_left(1);
}
Err(error::SeekHeaderError::NotFound(
"Zip64 central directory end not found",
))
}
pub(super) fn parse<R: io::Read + io::Seek>(
reader: &mut R,
) -> Result<Self, error::HeaderParseError> {
let mut buffer = [0u8; 56];
reader.read_exact(&mut buffer)?;
if buffer[0..4] != ZIP64_CENTRAL_DIRECTORY_END_SIGNATURE.to_le_bytes() {
return Err(error::HeaderParseError::InvalidSignature);
}
Ok(Self {
record_size: bytes_to_u64(&buffer, 4),
version_made_by: bytes_to_u16(&buffer, 12),
version_needed_to_extract: bytes_to_u16(&buffer, 14),
disk_number: bytes_to_u32(&buffer, 16),
disk_with_central_directory: bytes_to_u32(&buffer, 20),
disk_number_of_records: bytes_to_u64(&buffer, 24),
total_number_of_records: bytes_to_u64(&buffer, 32),
central_directory_size: bytes_to_u64(&buffer, 40),
central_directory_offset: bytes_to_u64(&buffer, 48),
})
}
}
#[derive(Debug, Clone, PartialEq)]
pub(super) struct Zip64CentralDirectoryEndLocator {
pub(super) disk_with_zip64_central_directory_end: u32,
pub(super) zip64_central_directory_end_offset: u64,
pub(super) total_number_of_disks: u32,
}
impl Zip64CentralDirectoryEndLocator {
pub(super) const SIZE: u64 = 20;
pub(super) fn build(&self) -> Vec<u8> {
let mut buffer = Vec::with_capacity(20);
buffer.extend(&ZIP64_CENTRAL_DIRECTORY_END_LOCATOR_SIGNATURE.to_le_bytes());
buffer.extend(&self.disk_with_zip64_central_directory_end.to_le_bytes());
buffer.extend(&self.zip64_central_directory_end_offset.to_le_bytes());
buffer.extend(&self.total_number_of_disks.to_le_bytes());
buffer
}
pub(super) fn parse(
reader: &mut impl io::Read,
) -> Result<Option<Self>, error::HeaderParseError> {
let mut buffer = [0u8; 20];
reader.read_exact(&mut buffer)?;
if bytes_to_u32(&buffer, 0) == ZIP64_CENTRAL_DIRECTORY_END_LOCATOR_SIGNATURE {
return Ok(Some(Self {
disk_with_zip64_central_directory_end: bytes_to_u32(&buffer, 4),
zip64_central_directory_end_offset: bytes_to_u64(&buffer, 8),
total_number_of_disks: bytes_to_u32(&buffer, 16),
}));
}
Ok(None)
}
}
#[cfg(test)]
mod tests {
use std::io::Cursor;
use chrono::TimeZone;
use super::*;
#[test]
fn test_to_msdos_time() {
let dt = Utc.with_ymd_and_hms(2006, 10, 11, 15, 40, 56).unwrap();
assert_eq!(0x7d1cu16, to_msdos_time(dt));
}
#[test]
fn test_to_msdos_date() {
let dt = Utc.with_ymd_and_hms(2006, 10, 11, 15, 40, 56).unwrap();
assert_eq!(0x354bu16, to_msdos_date(dt));
}
#[test]
fn test_from_msdos_time() {
let dt = Utc.with_ymd_and_hms(2006, 10, 11, 15, 40, 56).unwrap();
assert_eq!(dt, from_msdos_datetime(0x354b, 0x7d1c));
}
#[tokio::test]
async fn test_local_file_header() {
const LOCAL_FILE_HEADER_RAW: [u8; 38] = [
0x50, 0x4b, 0x03, 0x04, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x7d, 0x4b, 0x35, 0x00, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x74, 0x78, 0x74, ];
let header = LocalFileHeader {
compression_method: 0,
crc32: 0,
flags: 0,
modified: Utc.with_ymd_and_hms(2006, 10, 11, 15, 40, 56).unwrap(),
name: Cow::Borrowed("test.txt"),
size: 42,
header_size: 38,
};
assert_eq!(LOCAL_FILE_HEADER_RAW, header.build().as_slice());
assert_eq!(
header,
LocalFileHeader::parse(&mut Cursor::new(LOCAL_FILE_HEADER_RAW))
.await
.unwrap()
);
}
#[test]
fn test_data_descriptor_32() {
let descriptor = DataDescriptor {
crc32: 0x12345678u32,
size: 42,
};
let expected = [
0x50, 0x4b, 0x07, 0x08, 0x78, 0x56, 0x34, 0x12, 0x2a, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, ];
assert_eq!(expected, descriptor.build().as_slice());
}
#[test]
fn test_data_descriptor_64() {
let descriptor = DataDescriptor {
crc32: 0x12345678u32,
size: u32::MAX as u64 * 2,
};
let expected = [
0x50, 0x4b, 0x07, 0x08, 0x78, 0x56, 0x34, 0x12, 0xfe, 0xff, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0xfe, 0xff, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, ];
assert_eq!(expected, descriptor.build().as_slice());
}
#[test]
fn test_central_directory_header() {
fn generate_central_directory_header(zip64: bool) -> CentralDirectoryHeader<'static> {
CentralDirectoryHeader {
compression_method: 0,
crc32: 0x12345678u32,
external_attributes: 0o100644 << 16,
flags: 0,
modified: Utc.with_ymd_and_hms(2006, 10, 11, 15, 40, 56).unwrap(),
name: Cow::Borrowed("test.txt"),
offset: if zip64 { u32::MAX as u64 * 2 } else { 0xff },
size: if zip64 { u32::MAX as u64 * 2 } else { 42 },
}
}
const CENTRAL_DIRECTORY_HEADER_ZIP32_RAW: [u8; 54] = [
0x50, 0x4b, 0x01, 0x02, 0x2d, 0x03, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x7d, 0x4b, 0x35, 0x78, 0x56, 0x34, 0x12, 0x2a, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x81, 0xff, 0x00, 0x00, 0x00, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x74, 0x78, 0x74, ];
const CENTRAL_DIRECTORY_HEADER_ZIP64_RAW: [u8; 82] = [
0x50, 0x4b, 0x01, 0x02, 0x2d, 0x03, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x7d, 0x4b, 0x35, 0x78, 0x56, 0x34, 0x12, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x08, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x81, 0xff, 0xff, 0xff, 0xff, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x74, 0x78, 0x74, 0x01, 0x00, 0x18, 0x00, 0xfe, 0xff, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0xfe, 0xff, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0xfe, 0xff, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, ];
assert_eq!(
CENTRAL_DIRECTORY_HEADER_ZIP32_RAW,
generate_central_directory_header(false).build().as_slice()
);
assert_eq!(
CENTRAL_DIRECTORY_HEADER_ZIP64_RAW,
generate_central_directory_header(true).build().as_slice()
);
assert_eq!(
generate_central_directory_header(false),
CentralDirectoryHeader::parse(&mut Cursor::new(CENTRAL_DIRECTORY_HEADER_ZIP32_RAW))
.unwrap()
);
assert_eq!(
generate_central_directory_header(true),
CentralDirectoryHeader::parse(&mut Cursor::new(CENTRAL_DIRECTORY_HEADER_ZIP64_RAW))
.unwrap()
);
}
#[test]
fn test_central_directory_end() {
const CENTRAL_DIRECTORY_END_RAW: [u8; 22] = [
0x50, 0x4b, 0x05, 0x06, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x2d, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, ];
const CENTRAL_DIRECTORY_END: CentralDirectoryEnd = CentralDirectoryEnd {
disk_number: 0,
disk_with_central_directory: 0,
disk_number_of_records: 1,
total_number_of_records: 1,
central_directory_size: 0x2d,
central_directory_offset: 0xff,
comment: vec![],
};
assert_eq!(
CENTRAL_DIRECTORY_END_RAW,
CENTRAL_DIRECTORY_END.build().as_slice()
);
assert_eq!(
CENTRAL_DIRECTORY_END,
CentralDirectoryEnd::parse(&mut Cursor::new(CENTRAL_DIRECTORY_END_RAW)).unwrap()
);
}
#[test]
fn test_zip64_central_directory_end() {
const ZIP64_CENTRAL_DIRECTORY_END_RAW: [u8; 56] = [
0x50, 0x4b, 0x06, 0x06, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x03, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x2d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
const ZIP64_CENTRAL_DIRECTORY_END: Zip64CentralDirectoryEnd = Zip64CentralDirectoryEnd {
disk_number_of_records: 1,
total_number_of_records: 1,
central_directory_size: 0x2d,
central_directory_offset: 0xff,
version_made_by: VERSION_MADE_BY,
version_needed_to_extract: DEFAULT_VERSION,
disk_number: 0,
disk_with_central_directory: 0,
record_size: 44,
};
assert_eq!(
ZIP64_CENTRAL_DIRECTORY_END_RAW,
ZIP64_CENTRAL_DIRECTORY_END.build().as_slice()
);
assert_eq!(
ZIP64_CENTRAL_DIRECTORY_END,
Zip64CentralDirectoryEnd::parse(&mut Cursor::new(ZIP64_CENTRAL_DIRECTORY_END_RAW))
.unwrap()
);
}
#[test]
fn test_zip64_central_directory_end_locator() {
const ZIP64_CENTRAL_DIRECTORY_END_LOCATOR: Zip64CentralDirectoryEndLocator =
Zip64CentralDirectoryEndLocator {
disk_with_zip64_central_directory_end: 0,
zip64_central_directory_end_offset: u32::MAX as u64 * 2,
total_number_of_disks: 1,
};
const ZIP64_CENTRAL_DIRECTORY_END_LOCATOR_RAW: [u8; 20] = [
0x50, 0x4b, 0x06, 0x07, 0x00, 0x00, 0x00, 0x00,
0xfe, 0xff, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
];
assert_eq!(
ZIP64_CENTRAL_DIRECTORY_END_LOCATOR_RAW,
ZIP64_CENTRAL_DIRECTORY_END_LOCATOR.build().as_slice()
);
assert_eq!(
Some(ZIP64_CENTRAL_DIRECTORY_END_LOCATOR),
Zip64CentralDirectoryEndLocator::parse(&mut Cursor::new(
ZIP64_CENTRAL_DIRECTORY_END_LOCATOR_RAW
))
.unwrap()
);
}
}