use std::borrow::Cow;
use ownable::{IntoOwned, ToOwned};
use tracing::trace;
use winnow::{
binary::{le_u16, le_u32},
prelude::PResult,
token::{literal, take},
Parser, Partial,
};
use crate::{
encoding::detect_utf8,
encoding::Encoding,
error::{Error, FormatError},
parse::{
zero_datetime, Entry, ExtraField, ExtraFieldSettings, HostSystem, Mode, MsdosMode,
MsdosTimestamp, UnixMode, Version,
},
};
use super::Method;
#[derive(IntoOwned, ToOwned)]
pub struct CentralDirectoryFileHeader<'a> {
pub creator_version: Version,
pub reader_version: Version,
pub flags: u16,
pub method: Method,
pub modified: MsdosTimestamp,
pub crc32: u32,
pub compressed_size: u32,
pub uncompressed_size: u32,
pub disk_nbr_start: u16,
pub internal_attrs: u16,
pub external_attrs: u32,
pub header_offset: u32,
pub name: Cow<'a, [u8]>,
pub extra: Cow<'a, [u8]>,
pub comment: Cow<'a, [u8]>,
}
impl<'a> CentralDirectoryFileHeader<'a> {
const SIGNATURE: &'static str = "PK\x01\x02";
pub fn parser(i: &mut Partial<&'a [u8]>) -> PResult<Self> {
_ = literal(Self::SIGNATURE).parse_next(i)?;
let creator_version = Version::parser.parse_next(i)?;
let reader_version = Version::parser.parse_next(i)?;
let flags = le_u16.parse_next(i)?;
let method = Method::parser.parse_next(i)?;
let modified = MsdosTimestamp::parser.parse_next(i)?;
let crc32 = le_u32.parse_next(i)?;
let compressed_size = le_u32.parse_next(i)?;
let uncompressed_size = le_u32.parse_next(i)?;
let name_len = le_u16.parse_next(i)?;
let extra_len = le_u16.parse_next(i)?;
let comment_len = le_u16.parse_next(i)?;
let disk_nbr_start = le_u16.parse_next(i)?;
let internal_attrs = le_u16.parse_next(i)?;
let external_attrs = le_u32.parse_next(i)?;
let header_offset = le_u32.parse_next(i)?;
let name = take(name_len).parse_next(i)?;
let extra = take(extra_len).parse_next(i)?;
let comment = take(comment_len).parse_next(i)?;
Ok(Self {
creator_version,
reader_version,
flags,
method,
modified,
crc32,
compressed_size,
uncompressed_size,
disk_nbr_start,
internal_attrs,
external_attrs,
header_offset,
name: Cow::Borrowed(name),
extra: Cow::Borrowed(extra),
comment: Cow::Borrowed(comment),
})
}
}
impl CentralDirectoryFileHeader<'_> {
pub fn is_non_utf8(&self) -> bool {
let (valid1, require1) = detect_utf8(&self.name[..]);
let (valid2, require2) = detect_utf8(&self.comment[..]);
if !valid1 || !valid2 {
return true;
}
if !require1 && !require2 {
return false;
}
self.flags & 0x800 == 0
}
pub fn as_entry(&self, encoding: Encoding, global_offset: u64) -> Result<Entry, Error> {
let mut entry = Entry {
name: encoding.decode(&self.name[..])?,
method: self.method,
comment: encoding.decode(&self.comment[..])?,
modified: self.modified.to_datetime().unwrap_or_else(zero_datetime),
created: None,
accessed: None,
header_offset: (self.header_offset as u64)
.checked_add(global_offset)
.ok_or(FormatError::InvalidHeaderOffset)?,
reader_version: self.reader_version,
flags: self.flags,
uid: None,
gid: None,
crc32: self.crc32,
compressed_size: self.compressed_size as _,
uncompressed_size: self.uncompressed_size as _,
mode: Mode(0),
};
entry.mode = match self.creator_version.host_system {
HostSystem::Unix | HostSystem::Osx => UnixMode(self.external_attrs >> 16).into(),
HostSystem::WindowsNtfs | HostSystem::Vfat | HostSystem::MsDos => {
MsdosMode(self.external_attrs).into()
}
_ => Mode(0),
};
if entry.name.ends_with('/') {
entry.mode |= Mode::DIR
};
let settings = ExtraFieldSettings {
uncompressed_size_u32: self.uncompressed_size,
compressed_size_u32: self.compressed_size,
header_offset_u32: self.header_offset,
};
let mut slice = Partial::new(&self.extra[..]);
while !slice.is_empty() {
match ExtraField::mk_parser(settings).parse_next(&mut slice) {
Ok(ef) => {
entry.set_extra_field(&ef);
}
Err(e) => {
trace!("extra field error: {:#?}", e);
return Err(FormatError::InvalidExtraField.into());
}
}
}
Ok(entry)
}
}